Skip to content
Browse files

grocery app

  • Loading branch information...
0 parents commit b6a9c563f49228e537f62e820ea904e6d17faae8 @jchris jchris committed Jul 29, 2011
25 README.md
@@ -0,0 +1,25 @@
+## Generated CouchApp
+
+This is meant to be an example CouchApp and to ship with most of the CouchApp goodies.
+
+Install with
+
+ couchapp push . http://localhost:5984/grocery-sync
+
+or (if you have security turned on)
+
+ couchapp push . http://myname:mypass@localhost:5984/proto
+
+You can also create this app by running
+
+ couchapp generate proto && cd proto
+ couchapp push . http://localhost:5984/proto
+
+## Todo
+
+* factor CouchApp Commonjs to jquery.couch.require.js
+* use $.couch.app in app.js
+
+## License
+
+Apache 2.0
BIN _attachments/images/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 _attachments/index.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Grocery List</title>
+ <link rel="stylesheet" href="style/main.css" type="text/css">
+ </head>
+ <body>
+ <div id="account"></div>
+
+ <h1 id="site_header"><img src="images/icon.png" />Grocery List</h1>
+
+ <div id="profile">
+ <form id="create-message">
+ <input type="text" name="text" size="60" />
+ </form>
+ <div style="clear:left;"></div>
+ </div>
+ <div id="content"></div>
+
+ <div id="sidebar">
+ <h2>Remember the what?!</h2>
+ <p>When you remember it, add it here, and sync it with your CouchDB enabled phone.</p>
+ </div>
+ </body>
+ <script src="script/sha1.js"></script>
+ <script src="script/json2.js"></script>
+ <script src="script/jquery.js"></script>
+ <script src="script/jquery.couch.js"></script>
+ <script src="vendor/couchapp/jquery.couchLogin.js"></script>
+ <script src="vendor/couchapp/jquery.couchProfile.js"></script>
+ <script src="vendor/couchapp/md5.js"></script>
+ <script src="vendor/couchapp/jquery.couchForm.js"></script>
+ <script src="vendor/couchapp/jquery.mustache.js"></script>
+
+ <script id="recent-messages" type="text/x-mustache" charset="utf-8">
+ <ul id="items">
+ {{#items}}
+ <li {{#check}}class="checked"{{/check}} id="{{id}}">
+ <p> <input type = "checkbox" {{#check}}checked{{/check}}>{{text}}</p>
+ <div style="clear:left;"></div>
+ </li>
+ {{/items}}
+ </ul>
+ <p><em>Protip:</em> If you setup continuous replication between this database and a remote one, this list will reflect remote changes in near real-time.</p>
+ <p>This would be a good place to add pagination.</p>
+ </script>
+ <script src="script/app.js"></script>
+</html>
58 _attachments/script/app.js
@@ -0,0 +1,58 @@
+// Apache 2.0 J Chris Anderson 2011
+$(function() {
+ var path = unescape(document.location.pathname).split('/'),
+ design = path[3],
+ db = $.couch.db(path[1]);
+
+ function drawItems() {
+ db.view(design + "/recent-items", {
+ descending : "true",
+ limit : 50,
+ update_seq : true,
+ success : function(data) {
+ setupChanges(data.update_seq);
+ var them = $.mustache($("#recent-messages").html(), {
+ items : data.rows.map(function(r) {return r.value;})
+ });
+ $("#content").html(them);
+ }
+ });
+ };
+
+ $("li input").live("click", function(e) {
+ var li = $(this).parents("li")
+ var docid = li.attr("id");
+ li.toggleClass("checked")
+ db.openDoc(docid, {success : function(doc) {
+ doc.check = e.currentTarget.checked
+ db.saveDoc(doc)
+ }});
+ });
+
+ drawItems();
+ var changesRunning = false;
+ function setupChanges(since) {
+ if (!changesRunning) {
+ var changeHandler = db.changes(since);
+ changesRunning = true;
+ changeHandler.onChange(drawItems);
+ }
+ }
+ $("#account").couchLogin({
+ loggedIn : function(r) {
+ $("#pleaselogin").remove();
+ $("#create-message").couchForm({
+ beforeSave : function(doc) {
+ doc._id = $("li:first")[0].id+Math.random();
+ doc.created_at = new Date();
+ doc.check = false;
+ return doc;
+ }
+ });
+ $("#create-message").find("input").focus();
+ },
+ loggedOut : function() {
+ $("#profile").append('<p id="pleaselogin">Please log in to create items.</p>');
+ }
+ });
+ });
1,091 _attachments/script/jquery.couch.js
@@ -0,0 +1,1091 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+/**
+ * @namespace
+ * $.couch is used to communicate with a CouchDB server, the server methods can
+ * be called directly without creating an instance. Typically all methods are
+ * passed an <code>options</code> object which defines a success callback which
+ * is called with the data returned from the http request to CouchDB, you can
+ * find the other settings that can be used in the <code>options</code> object
+ * from <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
+ * jQuery.ajax settings</a>
+ * <pre><code>$.couch.activeTasks({
+ * success: function (data) {
+ * console.log(data);
+ * }
+ * });</code></pre>
+ * Outputs (for example):
+ * <pre><code>[
+ * {
+ * "pid" : "<0.11599.0>",
+ * "status" : "Copied 0 of 18369 changes (0%)",
+ * "task" : "recipes",
+ * "type" : "Database Compaction"
+ * }
+ *]</code></pre>
+ */
+(function($) {
+
+ $.couch = $.couch || {};
+ /** @lends $.couch */
+
+ /**
+ * @private
+ */
+ function encodeDocId(docID) {
+ var parts = docID.split("/");
+ if (parts[0] == "_design") {
+ parts.shift();
+ return "_design/" + encodeURIComponent(parts.join('/'));
+ }
+ return encodeURIComponent(docID);
+ }
+
+ /**
+ * @private
+ */
+
+ var uuidCache = [];
+
+ $.extend($.couch, {
+ urlPrefix: '',
+
+ /**
+ * You can obtain a list of active tasks by using the /_active_tasks URL.
+ * The result is a JSON array of the currently running tasks, with each task
+ * being described with a single object.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/uploads/
+ * all/documentation/couchbase-api-misc.html#couchbase-api-misc_active-task
+ * s_get">docs for /_active_tasks</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/jQuery.ajax
+ * /#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ activeTasks: function(options) {
+ ajax(
+ {url: this.urlPrefix + "/_active_tasks"},
+ options,
+ "Active task status could not be retrieved"
+ );
+ },
+
+ /**
+ * Returns a list of all the databases in the CouchDB instance
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/uploads/
+ * all/documentation/couchbase-api-misc.html#couchbase-api-misc_active-task
+ * s_get">docs for /_all_dbs</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/jQuery.ajax
+ * /#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ allDbs: function(options) {
+ ajax(
+ {url: this.urlPrefix + "/_all_dbs"},
+ options,
+ "An error occurred retrieving the list of all databases"
+ );
+ },
+
+ /**
+ * View and edit the CouchDB configuration, called with just the options
+ * parameter the entire config is returned, you can be more specific by
+ * passing the section and option parameters, if you specify a value that
+ * value will be stored in the configuration.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/uploads/
+ * all/documentation/couchbase-api-config.html#couchbase-api-config_config
+ * -section-key_put">docs for /_config</a>
+ * @param {ajaxSettings} options
+ * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
+ * jQuery ajax settings</a>
+ * @param {String} [section] the section of the config
+ * @param {String} [option] the particular config option
+ * @param {String} [value] value to be set
+ */
+ config: function(options, section, option, value) {
+ var req = {url: this.urlPrefix + "/_config/"};
+ if (section) {
+ req.url += encodeURIComponent(section) + "/";
+ if (option) {
+ req.url += encodeURIComponent(option);
+ }
+ }
+ if (value === null) {
+ req.type = "DELETE";
+ } else if (value !== undefined) {
+ req.type = "PUT";
+ req.data = toJSON(value);
+ req.contentType = "application/json";
+ req.processData = false
+ }
+
+ ajax(req, options,
+ "An error occurred retrieving/updating the server configuration"
+ );
+ },
+
+ /**
+ * Returns the session information for the currently logged in user.
+ * @param {ajaxSettings} options
+ * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
+ * jQuery ajax settings</a>
+ */
+ session: function(options) {
+ options = options || {};
+ $.ajax({
+ type: "GET", url: this.urlPrefix + "/_session",
+ beforeSend: function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
+ },
+ complete: function(req) {
+ var resp = $.parseJSON(req.responseText);
+ if (req.status == 200) {
+ if (options.success) options.success(resp);
+ } else if (options.error) {
+ options.error(req.status, resp.error, resp.reason);
+ } else {
+ alert("An error occurred getting session info: " + resp.reason);
+ }
+ }
+ });
+ },
+
+ /**
+ * @private
+ */
+ userDb : function(callback) {
+ $.couch.session({
+ success : function(resp) {
+ var userDb = $.couch.db(resp.info.authentication_db);
+ callback(userDb);
+ }
+ });
+ },
+
+ /**
+ * Create a new user on the CouchDB server, <code>user_doc</code> is an
+ * object with a <code>name</code> field and other information you want
+ * to store relating to that user, for example
+ * <code>{"name": "daleharvey"}</code>
+ * @param {Object} user_doc Users details
+ * @param {String} password Users password
+ * @param {ajaxSettings} options
+ * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
+ * jQuery ajax settings</a>
+ */
+ signup: function(user_doc, password, options) {
+ options = options || {};
+ // prepare user doc based on name and password
+ user_doc = this.prepareUserDoc(user_doc, password);
+ $.couch.userDb(function(db) {
+ db.saveDoc(user_doc, options);
+ });
+ },
+
+ /**
+ * Populates a user doc with a new password.
+ * @param {Object} user_doc User details
+ * @param {String} new_password New Password
+ */
+ prepareUserDoc: function(user_doc, new_password) {
+ if (typeof hex_sha1 == "undefined") {
+ alert("creating a user doc requires sha1.js to be loaded in the page");
+ return;
+ }
+ var user_prefix = "org.couchdb.user:";
+ user_doc._id = user_doc._id || user_prefix + user_doc.name;
+ if (new_password) {
+ // handle the password crypto
+ user_doc.salt = $.couch.newUUID();
+ user_doc.password_sha = hex_sha1(new_password + user_doc.salt);
+ }
+ user_doc.type = "user";
+ if (!user_doc.roles) {
+ user_doc.roles = [];
+ }
+ return user_doc;
+ },
+
+ /**
+ * Authenticate against CouchDB, the <code>options</code> parameter is
+ *expected to have <code>name</code> and <code>password</code> fields.
+ * @param {ajaxSettings} options
+ * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
+ * jQuery ajax settings</a>
+ */
+ login: function(options) {
+ options = options || {};
+ $.ajax({
+ type: "POST", url: this.urlPrefix + "/_session", dataType: "json",
+ data: {name: options.name, password: options.password},
+ beforeSend: function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
+ },
+ complete: function(req) {
+ var resp = $.parseJSON(req.responseText);
+ if (req.status == 200) {
+ if (options.success) options.success(resp);
+ } else if (options.error) {
+ options.error(req.status, resp.error, resp.reason);
+ } else {
+ alert("An error occurred logging in: " + resp.reason);
+ }
+ }
+ });
+ },
+
+
+ /**
+ * Delete your current CouchDB user session
+ * @param {ajaxSettings} options
+ * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
+ * jQuery ajax settings</a>
+ */
+ logout: function(options) {
+ options = options || {};
+ $.ajax({
+ type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json",
+ username : "_", password : "_",
+ beforeSend: function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
+ },
+ complete: function(req) {
+ var resp = $.parseJSON(req.responseText);
+ if (req.status == 200) {
+ if (options.success) options.success(resp);
+ } else if (options.error) {
+ options.error(req.status, resp.error, resp.reason);
+ } else {
+ alert("An error occurred logging out: " + resp.reason);
+ }
+ }
+ });
+ },
+
+ /**
+ * @namespace
+ * $.couch.db is used to communicate with a specific CouchDB database
+ * <pre><code>var $db = $.couch.db("mydatabase");
+ *$db.allApps({
+ * success: function (data) {
+ * ... process data ...
+ * }
+ *});
+ * </code></pre>
+ */
+ db: function(name, db_opts) {
+ db_opts = db_opts || {};
+ var rawDocs = {};
+ function maybeApplyVersion(doc) {
+ if (doc._id && doc._rev && rawDocs[doc._id] &&
+ rawDocs[doc._id].rev == doc._rev) {
+ // todo: can we use commonjs require here?
+ if (typeof Base64 == "undefined") {
+ alert("please include /_utils/script/base64.js in the page for " +
+ "base64 support");
+ return false;
+ } else {
+ doc._attachments = doc._attachments || {};
+ doc._attachments["rev-"+doc._rev.split("-")[0]] = {
+ content_type :"application/json",
+ data : Base64.encode(rawDocs[doc._id].raw)
+ };
+ return true;
+ }
+ }
+ };
+ return /** @lends $.couch.db */{
+ name: name,
+ uri: this.urlPrefix + "/" + encodeURIComponent(name) + "/",
+
+ /**
+ * Request compaction of the specified database.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db_
+ * db-compact_post">docs for /db/_compact</a>
+ * @param {ajaxSettings} options
+ * <a href="http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings">
+ * jQuery ajax settings</a>
+ */
+ compact: function(options) {
+ $.extend(options, {successStatus: 202});
+ ajax({
+ type: "POST", url: this.uri + "_compact",
+ data: "", processData: false
+ },
+ options,
+ "The database could not be compacted"
+ );
+ },
+
+ /**
+ * Cleans up the cached view output on disk for a given view.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db
+ * _db-view-cleanup_post">docs for /db/_compact</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ viewCleanup: function(options) {
+ $.extend(options, {successStatus: 202});
+ ajax({
+ type: "POST", url: this.uri + "_view_cleanup",
+ data: "", processData: false
+ },
+ options,
+ "The views could not be cleaned up"
+ );
+ },
+
+ /**
+ * Compacts the view indexes associated with the specified design
+ * document. You can use this in place of the full database compaction
+ * if you know a specific set of view indexes have been affected by a
+ * recent database change.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/upl
+ * oads/all/documentation/couchbase-api-db.html#couchbase-api-db_db-
+ * compact-design-doc_post">docs for /db/_compact/design-doc</a>
+ * @param {String} groupname Name of design-doc to compact
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ compactView: function(groupname, options) {
+ $.extend(options, {successStatus: 202});
+ ajax({
+ type: "POST", url: this.uri + "_compact/" + groupname,
+ data: "", processData: false
+ },
+ options,
+ "The view could not be compacted"
+ );
+ },
+
+ /**
+ * Create a new database
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db_
+ * db_put">docs for PUT /db/</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ create: function(options) {
+ $.extend(options, {successStatus: 201});
+ ajax({
+ type: "PUT", url: this.uri, contentType: "application/json",
+ data: "", processData: false
+ },
+ options,
+ "The database could not be created"
+ );
+ },
+
+ /**
+ * Deletes the specified database, and all the documents and
+ * attachments contained within it.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db_
+ * db_delete">docs for DELETE /db/</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ drop: function(options) {
+ ajax(
+ {type: "DELETE", url: this.uri},
+ options,
+ "The database could not be deleted"
+ );
+ },
+
+ /**
+ * Gets information about the specified database.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db
+ * _db_get">docs for GET /db/</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ info: function(options) {
+ ajax(
+ {url: this.uri},
+ options,
+ "Database information could not be retrieved"
+ );
+ },
+
+ /**
+ * @namespace
+ * $.couch.db.changes provides an API for subscribing to the changes
+ * feed
+ * <pre><code>var $changes = $.couch.db("mydatabase").changes();
+ *$changes.onChange = function (data) {
+ * ... process data ...
+ * }
+ * $changes.stop();
+ * </code></pre>
+ */
+ changes: function(since, options) {
+
+ options = options || {};
+ // set up the promise object within a closure for this handler
+ var timeout = 100, db = this, active = true,
+ listeners = [],
+ promise = /** @lends $.couch.db.changes */ {
+ /**
+ * Add a listener callback
+ * @see <a href="http://techzone.couchbase.com/sites/default/
+ * files/uploads/all/documentation/couchbase-api-db.html#couch
+ * base-api-db_db-changes_get">docs for /db/_changes</a>
+ * @param {Function} fun Callback function to run when
+ * notified of changes.
+ */
+ onChange : function(fun) {
+ listeners.push(fun);
+ },
+ /**
+ * Stop subscribing to the changes feed
+ */
+ stop : function() {
+ active = false;
+ }
+ };
+ // call each listener when there is a change
+ function triggerListeners(resp) {
+ $.each(listeners, function() {
+ this(resp);
+ });
+ };
+ // when there is a change, call any listeners, then check for
+ // another change
+ options.success = function(resp) {
+ timeout = 100;
+ if (active) {
+ since = resp.last_seq;
+ triggerListeners(resp);
+ getChangesSince();
+ };
+ };
+ options.error = function() {
+ if (active) {
+ setTimeout(getChangesSince, timeout);
+ timeout = timeout * 2;
+ }
+ };
+ // actually make the changes request
+ function getChangesSince() {
+ var opts = $.extend({heartbeat : 10 * 1000}, options, {
+ feed : "longpoll",
+ since : since
+ });
+ ajax(
+ {url: db.uri + "_changes"+encodeOptions(opts)},
+ options,
+ "Error connecting to "+db.uri+"/_changes."
+ );
+ }
+ // start the first request
+ if (since) {
+ getChangesSince();
+ } else {
+ db.info({
+ success : function(info) {
+ since = info.update_seq;
+ getChangesSince();
+ }
+ });
+ }
+ return promise;
+ },
+
+ /**
+ * Fetch all the docs in this db, you can specify an array of keys to
+ * fetch by passing the <code>keys</code> field in the
+ * <code>options</code>
+ * parameter.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db_
+ * db-all-docs_get">docs for /db/all_docs/</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ allDocs: function(options) {
+ var type = "GET";
+ var data = null;
+ if (options["keys"]) {
+ type = "POST";
+ var keys = options["keys"];
+ delete options["keys"];
+ data = toJSON({ "keys": keys });
+ }
+ ajax({
+ type: type,
+ data: data,
+ url: this.uri + "_all_docs" + encodeOptions(options)
+ },
+ options,
+ "An error occurred retrieving a list of all documents"
+ );
+ },
+
+ /**
+ * Fetch all the design docs in this db
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ allDesignDocs: function(options) {
+ this.allDocs($.extend(
+ {startkey:"_design", endkey:"_design0"}, options));
+ },
+
+ /**
+ * Fetch all the design docs with an index.html, <code>options</code>
+ * parameter expects an <code>eachApp</code> field which is a callback
+ * called on each app found.
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ allApps: function(options) {
+ options = options || {};
+ var self = this;
+ if (options.eachApp) {
+ this.allDesignDocs({
+ success: function(resp) {
+ $.each(resp.rows, function() {
+ self.openDoc(this.id, {
+ success: function(ddoc) {
+ var index, appPath, appName = ddoc._id.split('/');
+ appName.shift();
+ appName = appName.join('/');
+ index = ddoc.couchapp && ddoc.couchapp.index;
+ if (index) {
+ appPath = ['', name, ddoc._id, index].join('/');
+ } else if (ddoc._attachments &&
+ ddoc._attachments["index.html"]) {
+ appPath = ['', name, ddoc._id, "index.html"].join('/');
+ }
+ if (appPath) options.eachApp(appName, appPath, ddoc);
+ }
+ });
+ });
+ }
+ });
+ } else {
+ alert("Please provide an eachApp function for allApps()");
+ }
+ },
+
+ /**
+ * Returns the specified doc from the specified db.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-dbdoc.html#couchbase-api-
+ * dbdoc_db-doc_get">docs for GET /db/doc</a>
+ * @param {String} docId id of document to fetch
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ openDoc: function(docId, options, ajaxOptions) {
+ options = options || {};
+ if (db_opts.attachPrevRev || options.attachPrevRev) {
+ $.extend(options, {
+ beforeSuccess : function(req, doc) {
+ rawDocs[doc._id] = {
+ rev : doc._rev,
+ raw : req.responseText
+ };
+ }
+ });
+ } else {
+ $.extend(options, {
+ beforeSuccess : function(req, doc) {
+ if (doc["jquery.couch.attachPrevRev"]) {
+ rawDocs[doc._id] = {
+ rev : doc._rev,
+ raw : req.responseText
+ };
+ }
+ }
+ });
+ }
+ ajax({url: this.uri + encodeDocId(docId) + encodeOptions(options)},
+ options,
+ "The document could not be retrieved",
+ ajaxOptions
+ );
+ },
+
+ /**
+ * Create a new document in the specified database, using the supplied
+ * JSON document structure. If the JSON structure includes the _id
+ * field, then the document will be created with the specified document
+ * ID. If the _id field is not specified, a new unique ID will be
+ * generated.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-dbdoc.html#couchbase-api-
+ * dbdoc_db_post">docs for GET /db/doc</a>
+ * @param {String} doc document to save
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ saveDoc: function(doc, options) {
+ options = options || {};
+ var db = this;
+ var beforeSend = fullCommit(options);
+ if (doc._id === undefined) {
+ var method = "POST";
+ var uri = this.uri;
+ } else {
+ var method = "PUT";
+ var uri = this.uri + encodeDocId(doc._id);
+ }
+ var versioned = maybeApplyVersion(doc);
+ $.ajax({
+ type: method, url: uri + encodeOptions(options),
+ contentType: "application/json",
+ dataType: "json", data: toJSON(doc),
+ beforeSend : beforeSend,
+ complete: function(req) {
+ var resp = $.parseJSON(req.responseText);
+ if (req.status == 200 || req.status == 201 || req.status == 202) {
+ doc._id = resp.id;
+ doc._rev = resp.rev;
+ if (versioned) {
+ db.openDoc(doc._id, {
+ attachPrevRev : true,
+ success : function(d) {
+ doc._attachments = d._attachments;
+ if (options.success) options.success(resp);
+ }
+ });
+ } else {
+ if (options.success) options.success(resp);
+ }
+ } else if (options.error) {
+ options.error(req.status, resp.error, resp.reason);
+ } else {
+ alert("The document could not be saved: " + resp.reason);
+ }
+ }
+ });
+ },
+
+ /**
+ * Save a list of documents
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db_
+ * db-bulk-docs_post">docs for /db/_bulk_docs</a>
+ * @param {Object[]} docs List of documents to save
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ bulkSave: function(docs, options) {
+ var beforeSend = fullCommit(options);
+ $.extend(options, {successStatus: 201, beforeSend : beforeSend});
+ ajax({
+ type: "POST",
+ url: this.uri + "_bulk_docs" + encodeOptions(options),
+ contentType: "application/json", data: toJSON(docs)
+ },
+ options,
+ "The documents could not be saved"
+ );
+ },
+
+ /**
+ * Deletes the specified document from the database. You must supply
+ * the current (latest) revision and <code>id</code> of the document
+ * to delete eg <code>removeDoc({_id:"mydoc", _rev: "1-2345"})</code>
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-dbdoc.html#couchbase-api
+ * -dbdoc_db-doc_delete">docs for DELETE /db/doc</a>
+ * @param {Object} doc Document to delete
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ removeDoc: function(doc, options) {
+ ajax({
+ type: "DELETE",
+ url: this.uri +
+ encodeDocId(doc._id) +
+ encodeOptions({rev: doc._rev})
+ },
+ options,
+ "The document could not be deleted"
+ );
+ },
+
+ /**
+ * Remove a set of documents
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db_
+ * db-bulk-docs_post">docs for /db/_bulk_docs</a>
+ * @param {String[]} docs List of document id's to remove
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ bulkRemove: function(docs, options){
+ docs.docs = $.each(
+ docs.docs, function(i, doc){
+ doc._deleted = true;
+ }
+ );
+ $.extend(options, {successStatus: 201});
+ ajax({
+ type: "POST",
+ url: this.uri + "_bulk_docs" + encodeOptions(options),
+ data: toJSON(docs)
+ },
+ options,
+ "The documents could not be deleted"
+ );
+ },
+
+ /**
+ * The COPY command (which is non-standard HTTP) copies an existing
+ * document to a new or existing document.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-dbdoc.html#couchbase-api-
+ * dbdoc_db-doc_copy">docs for COPY /db/doc</a>
+ * @param {String[]} docId document id to copy
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ copyDoc: function(docId, options, ajaxOptions) {
+ ajaxOptions = $.extend(ajaxOptions, {
+ complete: function(req) {
+ var resp = $.parseJSON(req.responseText);
+ if (req.status == 201) {
+ if (options.success) options.success(resp);
+ } else if (options.error) {
+ options.error(req.status, resp.error, resp.reason);
+ } else {
+ alert("The document could not be copied: " + resp.reason);
+ }
+ }
+ });
+ ajax({
+ type: "COPY",
+ url: this.uri + encodeDocId(docId)
+ },
+ options,
+ "The document could not be copied",
+ ajaxOptions
+ );
+ },
+
+ /**
+ * Creates (and executes) a temporary view based on the view function
+ * supplied in the JSON request.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-db.html#couchbase-api-db
+ * _db-temp-view_post">docs for /db/_temp_view</a>
+ * @param {Function} mapFun Map function
+ * @param {Function} reduceFun Reduce function
+ * @param {Function} language Language the map / reduce funs are
+ * implemented in
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ query: function(mapFun, reduceFun, language, options) {
+ language = language || "javascript";
+ if (typeof(mapFun) !== "string") {
+ mapFun = mapFun.toSource ? mapFun.toSource()
+ : "(" + mapFun.toString() + ")";
+ }
+ var body = {language: language, map: mapFun};
+ if (reduceFun != null) {
+ if (typeof(reduceFun) !== "string")
+ reduceFun = reduceFun.toSource ? reduceFun.toSource()
+ : "(" + reduceFun.toString() + ")";
+ body.reduce = reduceFun;
+ }
+ ajax({
+ type: "POST",
+ url: this.uri + "_temp_view" + encodeOptions(options),
+ contentType: "application/json", data: toJSON(body)
+ },
+ options,
+ "An error occurred querying the database"
+ );
+ },
+
+ /**
+ * Fetch a _list view output, you can specify a list of
+ * <code>keys</code> in the options object to recieve only those keys.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-design.html#couchbase-api
+ * -design_db-design-designdoc-list-listname-viewname_get">
+ * docs for /db/_design/design-doc/_list/l1/v1</a>
+ * @param {String} list Listname in the form of ddoc/listname
+ * @param {String} view View to run list against
+ * @param {options} CouchDB <a href="http://wiki.apache.org/couchdb/
+ * HTTP_view_API">View Options</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ list: function(list, view, options, ajaxOptions) {
+ var list = list.split('/');
+ var options = options || {};
+ var type = 'GET';
+ var data = null;
+ if (options['keys']) {
+ type = 'POST';
+ var keys = options['keys'];
+ delete options['keys'];
+ data = toJSON({'keys': keys });
+ }
+ ajax({
+ type: type,
+ data: data,
+ url: this.uri + '_design/' + list[0] +
+ '/_list/' + list[1] + '/' + view + encodeOptions(options)
+ },
+ ajaxOptions, 'An error occured accessing the list'
+ );
+ },
+
+ /**
+ * Executes the specified view-name from the specified design-doc
+ * design document, you can specify a list of <code>keys</code>
+ * in the options object to recieve only those keys.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-design.html#couchbase-api-
+ * design_db-design-designdoc-view-viewname_get">docs for /db/
+ * _design/design-doc/_list/l1/v1</a>
+ * @param {String} name View to run list against
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ view: function(name, options) {
+ var name = name.split('/');
+ var options = options || {};
+ var type = "GET";
+ var data= null;
+ if (options["keys"]) {
+ type = "POST";
+ var keys = options["keys"];
+ delete options["keys"];
+ data = toJSON({ "keys": keys });
+ }
+ ajax({
+ type: type,
+ data: data,
+ url: this.uri + "_design/" + name[0] +
+ "/_view/" + name[1] + encodeOptions(options)
+ },
+ options, "An error occurred accessing the view"
+ );
+ },
+
+ /**
+ * Fetch an arbitrary CouchDB database property
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api.html">docs for /db/_prop</a>
+ * @param {String} propName Propery name to fetch
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ getDbProperty: function(propName, options, ajaxOptions) {
+ ajax({url: this.uri + propName + encodeOptions(options)},
+ options,
+ "The property could not be retrieved",
+ ajaxOptions
+ );
+ },
+
+ /**
+ * Set an arbitrary CouchDB database property
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api.html">docs for /db/_prop</a>
+ * @param {String} propName Propery name to fetch
+ * @param {String} propValue Propery value to set
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ setDbProperty: function(propName, propValue, options, ajaxOptions) {
+ ajax({
+ type: "PUT",
+ url: this.uri + propName + encodeOptions(options),
+ data : JSON.stringify(propValue)
+ },
+ options,
+ "The property could not be updated",
+ ajaxOptions
+ );
+ }
+ };
+ },
+
+ encodeDocId: encodeDocId,
+
+ /**
+ * Accessing the root of a CouchDB instance returns meta information about
+ * the instance. The response is a JSON structure containing information
+ * about the server, including a welcome message and the version of the
+ * server.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/uploads/
+ * all/documentation/couchbase-api-misc.html#couchbase-api-misc_root_get">
+ * docs for GET /</a>
+ * @param {ajaxSettings} options <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ */
+ info: function(options) {
+ ajax(
+ {url: this.urlPrefix + "/"},
+ options,
+ "Server information could not be retrieved"
+ );
+ },
+
+ /**
+ * Request, configure, or stop, a replication operation.
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-misc.html#couchbase-api-
+ * misc_replicate_post">docs for POST /_replicate</a>
+ * @param {String} source Path or url to source database
+ * @param {String} target Path or url to target database
+ * @param {ajaxSettings} ajaxOptions <a href="http://api.jquery.com/
+ * jQuery.ajax/#jQuery-ajax-settings">jQuery ajax settings</a>
+ * @param {Object} repOpts Additional replication options
+ */
+ replicate: function(source, target, ajaxOptions, repOpts) {
+ repOpts = $.extend({source: source, target: target}, repOpts);
+ if (repOpts.continuous && !repOpts.cancel) {
+ ajaxOptions.successStatus = 202;
+ }
+ ajax({
+ type: "POST", url: this.urlPrefix + "/_replicate",
+ data: JSON.stringify(repOpts),
+ contentType: "application/json"
+ },
+ ajaxOptions,
+ "Replication failed"
+ );
+ },
+
+ /**
+ * Fetch a new UUID
+ * @see <a href="http://techzone.couchbase.com/sites/default/files/
+ * uploads/all/documentation/couchbase-api-misc.html#couchbase-api-
+ * misc_uuids_get">docs for /_uuids</a>
+ * @param {Int} cacheNum Number of uuids to keep cached for future use
+ */
+ newUUID: function(cacheNum) {
+ if (cacheNum === undefined) {
+ cacheNum = 1;
+ }
+ if (!uuidCache.length) {
+ ajax({url: this.urlPrefix + "/_uuids", data: {count: cacheNum}, async:
+ false}, {
+ success: function(resp) {
+ uuidCache = resp.uuids;
+ }
+ },
+ "Failed to retrieve UUID batch."
+ );
+ }
+ return uuidCache.shift();
+ }
+ });
+
+ /**
+ * @private
+ */
+ function ajax(obj, options, errorMessage, ajaxOptions) {
+ options = $.extend({successStatus: 200}, options);
+ ajaxOptions = $.extend({contentType: "application/json"}, ajaxOptions);
+ errorMessage = errorMessage || "Unknown error";
+ $.ajax($.extend($.extend({
+ type: "GET", dataType: "json", cache : !$.browser.msie,
+ beforeSend: function(xhr){
+ if(ajaxOptions && ajaxOptions.headers){
+ for (var header in ajaxOptions.headers){
+ xhr.setRequestHeader(header, ajaxOptions.headers[header]);
+ }
+ }
+ },
+ complete: function(req) {
+ try {
+ var resp = $.parseJSON(req.responseText);
+ } catch(e) {
+ if (options.error) {
+ options.error(req.status, req, e);
+ } else {
+ alert(errorMessage + ": " + e);
+ }
+ return;
+ }
+ if (options.ajaxStart) {
+ options.ajaxStart(resp);
+ }
+ if (req.status == options.successStatus) {
+ if (options.beforeSuccess) options.beforeSuccess(req, resp);
+ if (options.success) options.success(resp);
+ } else if (options.error) {
+ options.error(req.status, resp && resp.error ||
+ errorMessage, resp && resp.reason || "no response");
+ } else {
+ alert(errorMessage + ": " + resp.reason);
+ }
+ }
+ }, obj), ajaxOptions));
+ }
+
+ /**
+ * @private
+ */
+ function fullCommit(options) {
+ var options = options || {};
+ if (typeof options.ensure_full_commit !== "undefined") {
+ var commit = options.ensure_full_commit;
+ delete options.ensure_full_commit;
+ return function(xhr) {
+ xhr.setRequestHeader('Accept', 'application/json');
+ xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString());
+ };
+ }
+ };
+
+ /**
+ * @private
+ */
+ // Convert a options object to an url query string.
+ // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
+ function encodeOptions(options) {
+ var buf = [];
+ if (typeof(options) === "object" && options !== null) {
+ for (var name in options) {
+ if ($.inArray(name,
+ ["error", "success", "beforeSuccess", "ajaxStart"]) >= 0)
+ continue;
+ var value = options[name];
+ if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) {
+ value = toJSON(value);
+ }
+ buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
+ }
+ }
+ return buf.length ? "?" + buf.join("&") : "";
+ }
+
+ /**
+ * @private
+ */
+ function toJSON(obj) {
+ return obj !== null ? JSON.stringify(obj) : null;
+ }
+
+})(jQuery);
6,240 _attachments/script/jquery.js
6,240 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
482 _attachments/script/json2.js
@@ -0,0 +1,482 @@
+/*
+ http://www.JSON.org/json2.js
+ 2010-03-20
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ this.JSON = {};
+}
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
202 _attachments/script/sha1.js
@@ -0,0 +1,202 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
+function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
+function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
+function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
+function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
+function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function core_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data
+ */
+function core_hmac_sha1(key, data)
+{
+ var bkey = str2binb(key);
+ if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
+ return core_sha1(opad.concat(hash), 512 + 160);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert an 8-bit or 16-bit string to an array of big-endian words
+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
+ */
+function str2binb(str)
+{
+ var bin = Array();
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < str.length * chrsz; i += chrsz)
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
+ return bin;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2str(bin)
+{
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for(var i = 0; i < bin.length * 32; i += chrsz)
+ str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a hex string.
+ */
+function binb2hex(binarray)
+{
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a base-64 string
+ */
+function binb2b64(binarray)
+{
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ for(var i = 0; i < binarray.length * 4; i += 3)
+ {
+ var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+}
92 _attachments/style/main.css
@@ -0,0 +1,92 @@
+/* add styles here */
+
+body {
+ font:1em Helvetica, sans-serif;
+ background: #EEEDE7;
+}
+
+h1 {
+ margin-top:0;
+}
+
+#site_header {
+ color: #940303;
+ font-size: 350%;
+ margin-bottom: 0.2em;
+}
+#site_header img {
+ border:1px solid white;
+ margin: 0.2em 0.5em;
+ vertical-align: middle;
+}
+
+#account {
+ float:right;
+}
+
+#profile {
+ border:4px solid #edd;
+ background:#fee;
+ padding:8px;
+ margin-bottom:8px;
+}
+
+#content {
+ padding:8px;
+ width:60%;
+ float:left;
+}
+
+#sidebar {
+ padding:8px;
+ float:right;
+ width:30%;
+}
+
+#items li, #profile {
+ background: none repeat scroll 0 0 #F7F7F7;
+ border: 4px solid #DEDED2;
+ border-radius: 10px 10px 10px 10px;
+ box-shadow: 1px 1px 5px #333333 inset;
+ margin: 4px 30px;
+ padding: 8px;
+}
+
+#items li.checked {
+ background: none repeat scroll 0 0 #ddd;
+}
+
+form {
+ padding:4px;
+ margin:6px;
+}
+form input {
+ font-size:120%;
+ padding:0.2em;
+}
+
+div.avatar {
+ padding:2px;
+ padding-bottom:0;
+ margin-right:4px;
+ float:left;
+ font-size:0.78em;
+ width : 60px;
+ height : 60px;
+ text-align: center;
+}
+
+div.avatar .name {
+ padding-top:2px;
+}
+
+div.avatar img {
+ margin:0 auto;
+ padding:0;
+ width : 40px;
+ height : 40px;
+}
+
+ul {
+ list-style: none;
+}
1 _id
@@ -0,0 +1 @@
+_design/grocery
1 couchapp.json
@@ -0,0 +1 @@
+{"name": "Grocery", "description": "Sync your grocery list."}
1 language
@@ -0,0 +1 @@
+javascript
235 vendor/couchapp/_attachments/jquery.couch.app.js
@@ -0,0 +1,235 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// Usage: The passed in function is called when the page is ready.
+// CouchApp passes in the app object, which takes care of linking to
+// the proper database, and provides access to the CouchApp helpers.
+// $.couch.app(function(app) {
+// app.db.view(...)
+// ...
+// });
+
+(function($) {
+
+ function Design(db, name, code) {
+ this.doc_id = "_design/"+name;
+ if (code) {
+ this.code_path = this.doc_id + "/" + code;
+ } else {
+ this.code_path = this.doc_id;
+ }
+ this.view = function(view, opts) {
+ db.view(name+'/'+view, opts);
+ };
+ this.list = function(list, view, opts) {
+ db.list(name+'/'+list, view, opts);
+ };
+ }
+
+ function docForm() { alert("docForm has been moved to vendor/couchapp/lib/docForm.js, use app.require to load") };
+
+ function resolveModule(path, names, parents, current) {
+ parents = parents || [];
+ if (names.length === 0) {
+ if (typeof current != "string") {
+ throw ["error","invalid_require_path",
+ 'Must require a JavaScript string, not: '+(typeof current)];
+ }
+ return [current, parents];
+ }
+ var n = names.shift();
+ if (n == '..') {
+ parents.pop();
+ var pp = parents.pop();
+ if (!pp) {
+ throw ["error", "invalid_require_path", path];
+ }
+ return resolveModule(path, names, parents, pp);
+ } else if (n == '.') {
+ var p = parents.pop();
+ if (!p) {
+ throw ["error", "invalid_require_path", path];
+ }
+ return resolveModule(path, names, parents, p);
+ } else {
+ parents = [];
+ }
+ if (!current[n]) {
+ throw ["error", "invalid_require_path", path];
+ }
+ parents.push(current);
+ return resolveModule(path, names, parents, current[n]);
+ }
+
+ function makeRequire(ddoc) {
+ var moduleCache = [];
+ function getCachedModule(name, parents) {
+ var key, i, len = moduleCache.length;
+ for (i=0;i<len;++i) {
+ key = moduleCache[i].key;
+ if (key[0] === name && key[1] === parents) {
+ return moduleCache[i].module;
+ }
+ }
+ return null;
+ }
+ function setCachedModule(name, parents, module) {
+ moduleCache.push({ key: [name, parents], module: module });
+ }
+ var require = function (name, parents) {
+ var cachedModule = getCachedModule(name, parents);
+ if (cachedModule !== null) {
+ return cachedModule;
+ }
+ var exports = {};
+ var resolved = resolveModule(name, name.split('/'), parents, ddoc);
+ var source = resolved[0];
+ parents = resolved[1];
+ var s = "var func = function (exports, require) { " + source + " };";
+ try {
+ eval(s);
+ func.apply(ddoc, [exports, function(name) {return require(name, parents)}]);
+ } catch(e) {
+ throw ["error","compilation_error","Module require('"+name+"') raised error "+e.toSource()];
+ }
+ setCachedModule(name, parents, exports);
+ return exports;
+ }
+ return require;
+ };
+
+ function mockReq() {
+ var p = document.location.pathname.split('/'),
+ qs = document.location.search.replace(/^\?/,'').split('&'),
+ q = {};
+ qs.forEach(function(param) {
+ var ps = param.split('='),
+ k = decodeURIComponent(ps[0]),
+ v = decodeURIComponent(ps[1]);
+ if (["startkey", "endkey", "key"].indexOf(k) != -1) {
+ q[k] = JSON.parse(v);
+ } else {
+ q[k] = v;
+ }
+ });
+ p.shift();
+ return {
+ path : p,
+ query : q
+ };
+ };
+
+ $.couch.app = $.couch.app || function(appFun, opts) {
+ opts = opts || {};
+ var urlPrefix = (opts.urlPrefix || ""),
+ index = urlPrefix.split('/').length,
+ fragments = unescape(document.location.href).split('/'),
+ dbname = opts.db || fragments[index + 2],
+ dname = opts.design || fragments[index + 4];
+ $.couch.urlPrefix = urlPrefix;
+ var db = $.couch.db(dbname),
+ design = new Design(db, dname, opts.load_path);
+ var appExports = $.extend({
+ db : db,
+ design : design,
+ view : design.view,
+ list : design.list,
+ docForm : docForm, // deprecated
+ req : mockReq()
+ }, $.couch.app.app);
+ function handleDDoc(ddoc) {
+ if (ddoc) {
+ appExports.ddoc = ddoc;
+ appExports.require = makeRequire(ddoc);
+ }
+ appFun.apply(appExports, [appExports]);
+ }
+ if (opts.ddoc) {
+ // allow the ddoc to be embedded in the html
+ // to avoid a second http request
+ $.couch.app.ddocs[design.doc_id] = opts.ddoc;
+ }
+ if ($.couch.app.ddocs[design.doc_id]) {
+ $(function() {handleDDoc($.couch.app.ddocs[design.doc_id])});
+ } else {
+ // only open 1 connection for this ddoc
+ if ($.couch.app.ddoc_handlers[design.doc_id]) {
+ // we are already fetching, just wait
+ $.couch.app.ddoc_handlers[design.doc_id].push(handleDDoc);
+ } else {
+ $.couch.app.ddoc_handlers[design.doc_id] = [handleDDoc];
+ // use getDbProperty to bypass %2F encoding on _show/app
+ db.getDbProperty(design.code_path, {
+ success : function(doc) {
+ $.couch.app.ddocs[design.doc_id] = doc;
+ $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) {
+ $(function() {h(doc)});
+ });
+ $.couch.app.ddoc_handlers[design.doc_id] = null;
+ },
+ error : function() {
+ $.couch.app.ddoc_handlers[design.doc_id].forEach(function(h) {
+ $(function() {h()});
+ });
+ $.couch.app.ddoc_handlers[design.doc_id] = null;
+ }
+ });
+ }
+ }
+ };
+ $.couch.app.ddocs = {};
+ $.couch.app.ddoc_handlers = {};
+ // legacy support. $.CouchApp is deprecated, please use $.couch.app
+ $.CouchApp = $.couch.app;
+})(jQuery);
+
+// JavaScript 1.6 compatibility functions that are missing from IE7/IE8
+
+if (!Array.prototype.forEach)
+{
+ Array.prototype.forEach = function(fun /*, thisp*/)
+ {
+ var len = this.length >>> 0;
+ if (typeof fun != "function")
+ throw new TypeError();
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ {
+ if (i in this)
+ fun.call(thisp, this[i], i, this);
+ }
+ };
+}
+
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(elt)
+ {
+ var len = this.length >>> 0;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0)
+ ? Math.ceil(from)
+ : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++)
+ {
+ if (from in this &&
+ this[from] === elt)
+ return from;
+ }
+ return -1;
+ };
+}
90 vendor/couchapp/_attachments/jquery.couch.app.util.js
@@ -0,0 +1,90 @@
+$.log = function(m) {
+ if (window && window.console && window.console.log) {
+ window.console.log(arguments.length == 1 ? m : arguments);
+ }
+};
+
+// http://stackoverflow.com/questions/1184624/serialize-form-to-json-with-jquery/1186309#1186309
+$.fn.serializeObject = function() {
+ var o = {};
+ var a = this.serializeArray();
+ $.each(a, function() {
+ if (o[this.name]) {
+ if (!o[this.name].push) {
+ o[this.name] = [o[this.name]];
+ }
+ o[this.name].push(this.value || '');
+ } else {
+ o[this.name] = this.value || '';
+ }
+ });
+ return o;
+};
+
+// todo remove this crap
+function escapeHTML(st) {
+ return(
+ st && st.replace(/&/g,'&amp;').
+ replace(/>/g,'&gt;').
+ replace(/</g,'&lt;').
+ replace(/"/g,'&quot;')
+ );
+};
+
+function safeHTML(st, len) {
+ return st ? escapeHTML(st.substring(0,len)) : '';