From 544a38dd45f6a58d34296c6c768afd086eb2ac70 Mon Sep 17 00:00:00 2001 From: Christopher Lenz Date: Fri, 28 Mar 2008 23:32:19 +0000 Subject: Imported trunk. git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@642432 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/browse.js | 632 +++++++ share/www/script/couch.js | 205 +++ share/www/script/couch_tests.js | 1027 +++++++++++ share/www/script/jquery.cookies.js | 47 + share/www/script/jquery.dialog.js | 92 + share/www/script/jquery.js | 3408 ++++++++++++++++++++++++++++++++++++ share/www/script/jquery.resizer.js | 55 + share/www/script/jquery.suggest.js | 129 ++ share/www/script/json2.js | 159 ++ share/www/script/pprint.js | 60 + share/www/script/shell.js | 700 ++++++++ 11 files changed, 6514 insertions(+) create mode 100644 share/www/script/browse.js create mode 100644 share/www/script/couch.js create mode 100644 share/www/script/couch_tests.js create mode 100644 share/www/script/jquery.cookies.js create mode 100644 share/www/script/jquery.dialog.js create mode 100644 share/www/script/jquery.js create mode 100644 share/www/script/jquery.resizer.js create mode 100644 share/www/script/jquery.suggest.js create mode 100644 share/www/script/json2.js create mode 100644 share/www/script/pprint.js create mode 100644 share/www/script/shell.js (limited to 'share/www/script') diff --git a/share/www/script/browse.js b/share/www/script/browse.js new file mode 100644 index 00000000..c1127608 --- /dev/null +++ b/share/www/script/browse.js @@ -0,0 +1,632 @@ +// 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. + +/* + * Page class for browse/index.html + */ +function CouchIndexPage() { + page = this; + + this.addDatabase = function() { + $.showDialog("_create_database.html", { + submit: function(data) { + if (!data.name || data.name.length == 0) { + return {name: "Please enter a name."}; + } + try { + new CouchDB(data.name).createDb(); + } catch (e) { + return {name: e.reason}; + } + if (window !== parent) parent.setTimeout("updateDatabaseList()", 500); + window.open("database.html?" + data.name, "content"); + } + }); + return false; + } + + this.updateDatabaseListing = function() { + var allDbs = CouchDB.allDbs(); + for (var i = 0; i < allDbs.length; i++) { + var dbName = allDbs[i]; + var info = new CouchDB(dbName).info(); + $("#databases tbody.content").append( + "" + + dbName + "" + info.doc_count +"" + + info.update_seq + ""); + $("#databases tbody tr:odd").addClass("odd"); + $("#databases tbody.footer tr td").text(allDbs.length + " database(s)"); + } + } + +} + +/* + * Page class for browse/database.html + */ +function CouchDatabasePage() { + var urlParts = location.search.substr(1).split("/"); + var dbName = urlParts.shift(); + var viewName = (urlParts.length > 0) ? urlParts.join("/") : null; + if (!viewName) { + viewName = $.cookies.get(dbName + ".view") || ""; + } else { + $.cookies.set(dbName + ".view", viewName); + } + var db = new CouchDB(dbName); + + this.dbName = dbName; + this.viewName = viewName; + this.db = db; + this.isDirty = false; + page = this; + + this.addDocument = function() { + $.showDialog("_create_document.html", { + submit: function(data) { + try { + var result = db.save(data.docid ? {_id: data.docid} : {}); + } catch (err) { + return {docid: err.reason}; + } + location.href = "document.html?" + dbName + "/" + result.id; + } + }); + } + + this.deleteDatabase = function() { + $.showDialog("_delete_database.html", { + submit: function() { + db.deleteDb(); + location.href = "index.html"; + if (window !== null) { + parent.$("#dbs li").filter(function(index) { + return $("a", this).text() == dbName; + }).remove(); + } + } + }); + } + + this.populateViewEditor = function() { + if (viewName.match(/^_design\//)) { + page.revertViewChanges(); + var dirtyTimeout = null; + function updateDirtyState() { + clearTimeout(dirtyTimeout); + dirtyTimeout = setTimeout(function() { + var buttons = $("#viewcode button.save, #viewcode button.revert"); + page.isDirty = $("#viewcode textarea").val() != page.storedViewCode; + if (page.isDirty) { + buttons.removeAttr("disabled"); + } else { + buttons.attr("disabled", "disabled"); + } + }, 100); + } + $("#viewcode textarea").bind("input", updateDirtyState); + if ($.browser.msie) { // sorry, browser detection + $("#viewcode textarea").get(0).onpropertychange = updateDirtyState + } else if ($.browser.safari) { + $("#viewcode textarea").bind("paste", updateDirtyState) + .bind("change", updateDirtyState) + .bind("keydown", updateDirtyState) + .bind("keypress", updateDirtyState) + .bind("keyup", updateDirtyState) + .bind("textInput", updateDirtyState); + } + } + } + + this.populateViewsMenu = function() { + var designDocs = db.allDocs({startkey: "_design/", endkey: "_design/ZZZ"}); + $("#switch select").each(function() { + this.options.length = 3; + for (var i = 0; i < designDocs.rows.length; i++) { + var doc = db.open(designDocs.rows[i].id); + var optGroup = $("").attr("label", doc._id.substr(8)); + for (var name in doc.views) { + if (!doc.views.hasOwnProperty(name)) continue; + $("").attr("value", doc._id + "/" + name).text(name) + .appendTo(optGroup); + } + optGroup.appendTo(this); + } + this.autocomplete = false; + }); + } + + this.revertViewChanges = function() { + if (!page.storedViewCode) { + var viewNameParts = viewName.split("/"); + var designDocId = viewNameParts[1]; + var localViewName = viewNameParts[2]; + var designDoc = db.open(["_design", designDocId].join("/")); + if (designDoc) { + page.storedViewCode = designDoc.views[localViewName]; + } + } + $("#viewcode textarea").val(page.storedViewCode); + page.isDirty = false; + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + } + + this.saveViewAs = function() { + if (viewName && /^_design/.test(viewName)) { + var viewNameParts = viewName.split("/"); + var designDocId = viewNameParts[1]; + var localViewName = viewNameParts[2]; + } else { + var designDocId = "", localViewName = "" + } + $.showDialog("_save_view_as.html", { + load: function(elem) { + $("#input_docid", elem).val(designDocId).suggest(function(text, callback) { + var matches = []; + var docs = db.allDocs({ + count: 10, startkey: "_design/" + text, + endkey: "_design/" + text + "ZZZZ" + }); + for (var i = 0; i < docs.rows.length; i++) { + matches[i] = docs.rows[i].id.substr(8); + } + callback(matches); + }); + $("#input_name", elem).val(localViewName).suggest(function(text, callback) { + var matches = []; + try { + var doc = db.open("_design/" + $("#input_docid").val()); + } catch (err) { + return; + } + if (!doc || !doc.views) return; + for (var viewName in doc.views) { + if (!doc.views.hasOwnProperty(viewName) || !viewName.match("^" + text)) { + continue; + } + matches.push(viewName); + } + callback(matches); + }); + }, + submit: function(data) { + if (!data.docid || !data.name) { + var errors = {}; + if (!data.docid) errors.docid = "Please enter a document ID"; + if (!data.name) errors.name = "Please enter a view name"; + return errors; + } + var viewCode = $("#viewcode textarea").val(); + var docId = ["_design", data.docid].join("/"); + var designDoc = db.open(docId); + if (!designDoc) designDoc = {_id: docId, language: "text/javascript"}; + if (designDoc.views === undefined) designDoc.views = {}; + designDoc.views[data.name] = viewCode; + db.save(designDoc); + page.isDirty = false; + location.href = "database.html?" + dbName + "/" + designDoc._id + "/" + data.name; + } + }); + } + + this.saveViewChanges = function() { + var viewNameParts = viewName.split("/"); + var designDocId = viewNameParts[1]; + var localViewName = viewNameParts[2]; + var designDoc = db.open(["_design", designDocId].join("/")); + var viewCode = $("#viewcode textarea").val(); + designDoc.views[localViewName] = viewCode; + db.save(designDoc); + page.isDirty = false; + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + } + + this.updateDesignDocLink = function() { + if (viewName && /^_design/.test(viewName)) { + var docId = "_design/" + viewName.split("/")[1]; + $("#designdoc-link").attr("href", "document.html?" + dbName + "/" + docId).text(docId); + } else { + $("#designdoc-link").removeAttr("href").text(""); + } + } + + this.updateDocumentListing = function(options) { + if (options === undefined) options = {}; + if (options.count === undefined) { + options.count = parseInt($("#perpage").val(), 10); + } + if ($("#documents thead th.key").is(".desc")) { + options.descending = true; + $.cookies.set(dbName + ".desc", "1"); + } else { + if (options.descending !== undefined) delete options.descending; + $.cookies.remove(dbName + ".desc"); + } + $("#paging a").unbind(); + $("#documents tbody.content").empty(); + this.updateDesignDocLink(); + + var result = null; + if (!viewName) { + $("#switch select").get(0).selectedIndex = 0; + result = db.allDocs(options); + } else { + $("#switch select").each(function() { + for (var i = 0; i < this.options.length; i++) { + if (this.options[i].value == viewName) { + this.selectedIndex = i; + break; + } + } + }); + docs = []; + if (viewName == "_temp_view") { + $("#viewcode").show().addClass("expanded"); + var query = $("#viewcode textarea").val(); + $.cookies.set(db.name + ".query", query); + try { + result = db.query(query, options); + } catch (e) { + alert(e.reason ? e.reason : e.message); + return; + } + } else if (viewName == "_design_docs") { + options.startkey = options.descending ? "_design/ZZZZ" : "_design/"; + options.endkey = options.descending ? "_design/" : "_design/ZZZZ"; + result = db.allDocs(options); + } else { + $("#viewcode").show(); + var currentViewCode = $("#viewcode textarea").val(); + if (currentViewCode != page.storedViewCode) { + result = db.query(currentViewCode, options); + } else { + result = db.view(viewName.substr(8), options); + } + } + } + if (result.offset === undefined) { + result.offset = 0; + } + if (result.offset > 0) { + $("#paging a.prev").attr("href", "#" + (result.offset - options.count)).click(function() { + var firstDoc = result.rows[0]; + page.updateDocumentListing({ + startkey: firstDoc.key !== undefined ? firstDoc.key : null, + startkey_docid: firstDoc.id, + skip: 1, + count: -options.count + }); + return false; + }); + } else { + $("#paging a.prev").removeAttr("href"); + } + if (result.total_rows - result.offset > options.count) { + $("#paging a.next").attr("href", "#" + (result.offset + options.count)).click(function() { + var lastDoc = result.rows[result.rows.length - 1]; + page.updateDocumentListing({ + startkey: lastDoc.key !== undefined ? lastDoc.key : null, + startkey_docid: lastDoc.id, + skip: 1, + count: options.count + }); + return false; + }); + } else { + $("#paging a.next").removeAttr("href"); + } + + for (var i = 0; i < result.rows.length; i++) { + var row = result.rows[i]; + var tr = $(""); + var key = row.key; + $("" + + "
ID: " + row.id + + "
").find("em").text( + key !== null ? prettyPrintJSON(key, 0, "") : "null" + ).end().appendTo(tr); + var value = row.value; + $("").text( + value !== null ? prettyPrintJSON(value, 0, "") : "null" + ).appendTo(tr).dblclick(function() { + location.href = this.previousSibling.firstChild.href; + }); + tr.appendTo("#documents tbody.content"); + } + + $("#documents tbody tr:odd").addClass("odd"); + $("#documents tbody.footer td span").text( + "Showing " + Math.min(result.total_rows, result.offset + 1) + "-" + + (result.offset + result.rows.length) + " of " + result.total_rows + + " document" + (result.total_rows != 1 ? "s" : "")); + } + + window.onbeforeunload = function() { + $("#switch select").val(viewName); + if (page.isDirty) { + return "You've made changes to the view code that have not been " + + "saved yet."; + } + } + +} + +/* + * Page class for browse/database.html + */ +function CouchDocumentPage() { + var urlParts = location.search.substr(1).split("/"); + var dbName = urlParts.shift(); + var idParts = urlParts.join("/").split("@", 2); + var docId = idParts[0]; + var docRev = (idParts.length > 1) ? idParts[1] : null; + var db = new CouchDB(dbName); + var doc = db.open(docId, {revs_info: true}); + var revs = doc._revs_info; + delete doc._revs_info; + if (docRev != null) { + try { + doc = db.open(docId, {rev: docRev}); + } catch (e) { + alert("The requested revision was not found. " + + "You will be redirected back to the latest revision."); + location.href = "?" + dbName + "/" + docId; + return; + } + } + + this.dbName = dbName; + this.db = db; + this.doc = doc; + this.isDirty = false; + page = this; + + this.addField = function() { + var fieldName = "unnamed"; + var fieldIdx = 1; + while (doc.hasOwnProperty(fieldName)) { + fieldName = "unnamed " + fieldIdx++; + } + doc[fieldName] = null; + var row = _addRowForField(fieldName); + page.isDirty = true; + _editKey(row.find("th"), fieldName); + } + + this.updateFieldListing = function() { + $("#fields tbody.content").empty(); + var propNames = []; + for (var prop in doc) { + if (!doc.hasOwnProperty(prop)) continue; + propNames.push(prop); + } + // Order properties alphabetically, but put internal fields first + propNames.sort(function(a, b) { + var a0 = a.charAt(0), b0 = b.charAt(0); + if (a0 == "_" && b0 != "_") { + return -1; + } else if (a0 != "_" && b0 == "_") { + return 1; + } else { + return a < b ? -1 : a != b ? 1 : 0; + } + }); + for (var pi = 0; pi < propNames.length; pi++) { + _addRowForField(propNames[pi]); + } + if (revs.length > 1) { + var currentIndex = 0; + for (var i = 0; i < revs.length; i++) { + if (revs[i].rev == doc._rev) { + currentIndex = i; + break; + } + } + if (currentIndex < revs.length - 1) { + var prevRev = revs[currentIndex + 1].rev; + $("#paging a.prev").attr("href", "?" + dbName + "/" + docId + "@" + prevRev); + } + if (currentIndex > 0) { + var nextRev = revs[currentIndex - 1].rev; + $("#paging a.next").attr("href", "?" + dbName + "/" + docId + "@" + nextRev); + } + $("#fields tbody.footer td span").text("Showing revision " + + (revs.length - currentIndex) + " of " + revs.length); + } + } + + this.deleteDocument = function() { + $.showDialog("_delete_document.html", { + submit: function() { + db.deleteDoc(doc); + location.href = "database.html?" + dbName; + } + }); + } + + this.saveDocument = function() { + try { + db.save(doc); + } catch (e) { + alert(e.reason); + return; + } + page.isDirty = false; + location.href = "?" + dbName + "/" + docId; + } + + window.onbeforeunload = function() { + if (page.isDirty) { + return "You've made changes to this document that have not been " + + "saved yet."; + } + } + + function _addRowForField(fieldName) { + var value = _renderValue(doc[fieldName]); + var row = $("") + .find("th").append($("").text(fieldName)).dblclick(function() { + _editKey(this, $(this).text()); + }).end() + .find("td").append(value).dblclick(function() { + _editValue(this, $(this).prev("th").text()); + }).end() + .appendTo("#fields tbody.content"); + if (fieldName != "_id" && fieldName != "_rev") { + row.find("th, td").attr("title", "Double click to edit"); + _initKey(row, fieldName); + _initValue(value); + } + $("#fields tbody tr").removeClass("odd").filter(":odd").addClass("odd"); + return row; + } + + function _editKey(cell, fieldName) { + if (fieldName == "_id" || fieldName == "_rev") return; + var th = $(cell); + th.empty(); + var input = $(""); + input.dblclick(function() { return false; }).keydown(function(evt) { + switch (evt.keyCode) { + case 13: applyChange(); break; + case 27: cancelChange(); break; + } + }); + var tools = $("
"); + function applyChange() { + input.nextAll().remove(); + var newName = input.val(); + if (!newName.length || newName == fieldName) { + cancelChange(); + return; + } + doc[newName] = doc[fieldName]; + delete doc[fieldName]; + th.children().remove(); + th.append($("").text(newName)); + _initKey(th.parent("tr"), fieldName); + page.isDirty = true; + } + function cancelChange() { + th.children().remove(); + th.append($("").text(fieldName)); + _initKey(th.parent("tr"), fieldName); + } + + $("").click(function() { + applyChange(); + }).appendTo(tools); + $("").click(function() { + cancelChange(); + }).appendTo(tools); + tools.appendTo(th); + input.val(fieldName).appendTo(th); + input.each(function() { this.focus(); this.select(); }); + } + + function _editValue(cell, fieldName) { + if (fieldName == "_id" || fieldName == "_rev") return; + var td = $(cell); + var value = doc[fieldName]; + var needsTextarea = $("dl", td).length > 0 || $("code", td).text().length > 60; + td.empty(); + if (needsTextarea) { + var input = $(""); + } else { + var input = $(""); + } + input.dblclick(function() { return false; }).keydown(function(evt) { + switch (evt.keyCode) { + case 13: if (!needsTextarea) applyChange(); break; + case 27: cancelChange(); break; + } + }); + var tools = $("
"); + function applyChange() { + input.nextAll().remove(); + try { + var newValue = input.val() || "null"; + if (newValue == doc[fieldName]) { + cancelChange(); + return; + } + doc[fieldName] = JSON.parse(newValue); + td.children().remove(); + page.isDirty = true; + var value = _renderValue(doc[fieldName]); + td.append(value); + _initValue(value); + } catch (err) { + input.addClass("invalid"); + var msg = err.message; + if (msg == "parseJSON") { + msg = "Please enter a valid JSON value (for example, \"string\")."; + } + $("
").text(msg).insertAfter(input); + } + } + function cancelChange() { + td.children().remove(); + var value = _renderValue(doc[fieldName]); + td.append(value); + _initValue(value); + } + + $("").click(function() { + applyChange(); + }).appendTo(tools); + $("").click(function() { + cancelChange(); + }).appendTo(tools); + tools.appendTo(td); + input.val(prettyPrintJSON(value)).appendTo(td); + input.each(function() { this.focus(); this.select(); }); + if (needsTextarea) input.resizable(); + } + + function _initKey(row, fieldName) { + if (fieldName != "_id" && fieldName != "_rev") { + $("").click(function() { + delete doc[fieldName]; + row.remove(); + page.isDirty = true; + $("#fields tbody tr").removeClass("odd").filter(":odd").addClass("odd"); + }).prependTo(row.find("th")); + } + } + + function _initValue(value) { + value.find("dd").filter(":has(dl)").hide().prev("dt").addClass("collapsed"); + value.find("dd").not(":has(dl)").addClass("inline").prev().addClass("inline"); + value.find("dt.collapsed").click(function() { + $(this).toggleClass("collapsed").next().toggle(); + }); + } + + function _renderValue(value) { + var type = typeof(value); + if (type == "object" && value !== null) { + var list = $("
"); + for (var i in value) { + if (!value.hasOwnProperty(i)) continue; + $("
").text(i).appendTo(list); + $("
").append(_renderValue(value[i])).appendTo(list); + } + return list; + } else { + return $("").addClass(type).text( + value !== null ? JSON.stringify(value) : "null" + ); + } + } + +} diff --git a/share/www/script/couch.js b/share/www/script/couch.js new file mode 100644 index 00000000..9815882a --- /dev/null +++ b/share/www/script/couch.js @@ -0,0 +1,205 @@ +// 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. + +// A simple class to represent a database. Uses XMLHttpRequest to interface with +// the CouchDB server. + +function CouchDB(name) { + this.name = name + this.uri = "/" + encodeURIComponent(name) + "/"; + request = CouchDB.request; + + // Creates the database on the server + this.createDb = function() { + var req = request("PUT", this.uri); + var result = JSON.parse(req.responseText); + if (req.status != 201) + throw result; + return result; + } + + // Deletes the database on the server + this.deleteDb = function() { + var req = request("DELETE", this.uri); + if (req.status == 404) + return false; + var result = JSON.parse(req.responseText); + if (req.status != 202) + throw result; + return result; + } + + // Save a document to the database + this.save = function(doc, options) { + var req; + if (doc._id == undefined) + req = request("POST", this.uri + encodeOptions(options), { + body: JSON.stringify(doc) + }); + else + req = request("PUT", this.uri + encodeURIComponent(doc._id) + encodeOptions(options), { + body: JSON.stringify(doc) + }); + var result = JSON.parse(req.responseText); + if (req.status != 201) + throw result; + // set the _id and _rev members on the input object, for caller convenience. + doc._id = result.id; + doc._rev = result.rev; + return result; + } + + // Open a document from the database + this.open = function(docId, options) { + var req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options)); + if (req.status == 404) + return null; + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + // Deletes a document from the database + this.deleteDoc = function(doc) { + var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev); + var result = JSON.parse(req.responseText); + if (req.status != 202) + throw result; + doc._rev = result.rev; //record rev in input document + doc._deleted = true; + return result; + } + + this.bulkSave = function(docs, options) { + var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), { + body: JSON.stringify({"docs": docs}) + }); + var result = JSON.parse(req.responseText); + if (req.status != 201) + throw result; + return result; + } + + // Applies the map function to the contents of database and returns the results. + this.query = function(mapFun, options) { + if (typeof(mapFun) != "string") + mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")"; + var req = request("POST", this.uri + "_temp_view" + encodeOptions(options), { + headers: {"Content-Type": "text/javascript"}, + body: mapFun + }); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + this.view = function(viewname, options) { + var req = request("GET", this.uri + "_view/" + viewname + encodeOptions(options)); + if (req.status == 404) + return null; + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + // gets information about the database + this.info = function() { + var req = request("GET", this.uri); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + this.allDocs = function(options) { + var req = request("GET", this.uri + "_all_docs" + encodeOptions(options)); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + // 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 (!options.hasOwnProperty(name)) continue; + var value = options[name]; + if (name == "key" || name == "startkey" || name == "endkey") { + value = toJSON(value); + } + buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); + } + } + if (!buf.length) { + return ""; + } + return "?" + buf.join("&"); + } + + function toJSON(obj) { + return obj !== null ? JSON.stringify(obj) : null; + } +} + +CouchDB.allDbs = function() { + var req = CouchDB.request("GET", "/_all_dbs"); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; +} + +CouchDB.getVersion = function() { + var req = CouchDB.request("GET", "/"); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result.version; +} + +CouchDB.replicate = function(source, target) { + var req = CouchDB.request("POST", "/_replicate", { + body: JSON.stringify({source: source, target: target}) + }); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; +} + +CouchDB.request = function(method, uri, options) { + options = options || {}; + var req = null; + if (typeof(XMLHttpRequest) != "undefined") { + req = new XMLHttpRequest(); + } else if (typeof(ActiveXObject) != "undefined") { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } else { + throw new Error("No XMLHTTPRequest support detected"); + } + req.open(method, uri, false); + if (options.headers) { + var headers = options.headers; + for (var headerName in headers) { + if (!headers.hasOwnProperty(headerName)) continue; + req.setRequestHeader(headerName, headers[headerName]); + } + } + req.send(options.body || null); + return req; +} diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js new file mode 100644 index 00000000..bd159310 --- /dev/null +++ b/share/www/script/couch_tests.js @@ -0,0 +1,1027 @@ +// 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. + +var tests = { + + // Do some basic tests. + basics: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // Get the database info, check the doc_count + T(db.info().doc_count == 0); + + // create a document and save it to the database + var doc = {_id:"0",a:1,b:1}; + var result = db.save(doc); + + T(result.ok==true); // return object has an ok member with a value true + T(result.id); // the _id of the document is set. + T(result.rev); // the revision id of the document is set. + + // Verify the input doc is now set with the doc id and rev + // (for caller convenience). + T(doc._id == result.id && doc._rev == result.rev); + + var id = result.id; // save off the id for later + + // Create some more documents. + // Notice the use of the ok member on the return result. + T(db.save({_id:"1",a:2,b:4}).ok); + T(db.save({_id:"2",a:3,b:9}).ok); + T(db.save({_id:"3",a:4,b:16}).ok); + + // Check the database doc count + T(db.info().doc_count == 4); + + // Check the all docs + var results = db.allDocs(); + var rows = results.rows; + + for(var i=0; i < rows.length; i++) { + T(rows[i].id >= "0" && rows[i].id <= "4"); + } + + // Test a simple map functions + + // create a map function that selects all documents whose "a" member + // has a value of 4, and then returns the document's b value. + var mapFunction = function(doc){ + if(doc.a==4) + map(null, doc.b); + }; + + results = db.query(mapFunction); + + // verify only one document found and the result value (doc.b). + T(results.total_rows == 1 && results.rows[0].value == 16); + + // reopen document we saved earlier + existingDoc = db.open(id); + + T(existingDoc.a==1); + + //modify and save + existingDoc.a=4; + db.save(existingDoc); + + // redo the map query + results = db.query(mapFunction); + + // the modified document should now be in the results. + T(results.total_rows == 2); + + // write 2 more documents + T(db.save({a:3,b:9}).ok); + T(db.save({a:4,b:16}).ok); + + results = db.query(mapFunction); + + // 1 more document should now be in the result. + T(results.total_rows == 3); + T(db.info().doc_count == 6); + + // delete a document + T(db.deleteDoc(existingDoc).ok); + + // make sure we can't open the doc + T(db.open(existingDoc._id) == null); + + results = db.query(mapFunction); + + // 1 less document should now be in the results. + T(results.total_rows == 2); + T(db.info().doc_count == 5); + }, + + // Do some edit conflict detection tests + conflicts: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // create a doc and save + var doc = {_id:"foo",a:1,b:1}; + T(db.save(doc).ok); + + // reopen + var doc2 = db.open(doc._id); + + // ensure the revisions are the same + T(doc._id == doc2._id && doc._rev == doc2._rev); + + // edit the documents. + doc.a = 2; + doc2.a = 3; + + // save one document + T(db.save(doc).ok); + + // save the other document + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 1" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + // Now clear out the _rev member and save. This indicates this document is + // new, not based on an existing revision. + doc2._rev = undefined; + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 2" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + // Now delete the document from the database + T(db.deleteDoc(doc).ok); + + T(db.save(doc2).ok); // we can save a new document over a deletion without + // knowing the deletion rev. + }, + + recreate_doc: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // First create a new document with the ID "foo", and delete it again + var doc = {_id: "foo", a: "bar", b: 42}; + T(db.save(doc).ok); + T(db.deleteDoc(doc).ok); + + // Now create a new document with the same ID, save it, and then modify it + // This should work fine, but currently results in a conflict error, at + // least "sometimes" + for (var i = 0; i < 10; i++) { + doc = {_id: "foo"}; + T(db.save(doc).ok); + doc = db.open("foo"); + doc.a = "baz"; + try { + T(db.save(doc).ok); + } finally { + // And now, we can't even delete the document anymore :/ + T(db.deleteDoc(doc).rev != undefined); + } + } + }, + + // test saving a semi-large quanitity of documents and do some view queries. + lots_of_docs: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // keep number lowish for now to keep tests fasts. Crank up manually to + // to really test. + var numDocsToCreate = 500; + + var docs = makeDocs(numDocsToCreate); + T(db.bulkSave(docs).ok); + + // query all documents, and return the doc.integer member as a key. + results = db.query(function(doc){ map(doc.integer, null) }); + + T(results.total_rows == numDocsToCreate); + + // validate the keys are ordered ascending + for(var i=0; i= 0; i -= 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + count:-10}) + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == i - 9) + var j; + for (j = 0; j < 10;j++) { + T(queryResults.rows[j].key == i - 9 + j); + } + } + + // page through the view descending and going forward + for (i = docs.length - 1; i >= 0; i -= 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + descending:true, count:10}) + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == docs.length - i - 1) + var j; + for (j = 0; j < 10; j++) { + T(queryResults.rows[j].key == i - j); + } + } + + // page through the view descending and going backward + for (i = 0; i < docs.length; i += 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + descending:true, count:-10}); + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == docs.length - i - 10) + var j; + for (j = 0; j < 10; j++) { + T(queryResults.rows[j].key == i + 9 - j); + } + } + }, + + view_sandboxing: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(2); + T(db.bulkSave(docs).ok); + + // make sure that attempting to change the document throws an error + var results = db.query(function(doc) { + doc._id = "foo"; + map(null, doc); + }); + T(results.total_rows == 0); + + // make sure that a view cannot invoke interpreter internals such as the + // garbage collector + var results = db.query(function(doc) { + gc(); + map(null, doc); + }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_funs array defined used by + // the view server + var results = db.query(function(doc) { map_funs.push(1); map(null, doc) }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_results array defined used by + // the view server + var results = db.query(function(doc) { map_results.push(1); map(null, doc) }); + T(results.total_rows == 0); + }, + + view_xml: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + db.save({content: "Testing XML"}); + db.save({content: "Testing E4X"}); + + var results = db.query( + "function(doc) {\n" + + " var xml = new XML(doc.content);\n" + + " map(xml.title.text(), null);\n" + + "}"); + T(results.total_rows == 2); + T(results.rows[0].key == "Testing E4X"); + T(results.rows[1].key == "Testing XML"); + + var results = db.query( + "function(doc) {\n" + + " var xml = new XML(doc.content);\n" + + " map(xml.title.@id, null);\n" + + "}"); + T(results.total_rows == 2); + T(results.rows[0].key == "e4x"); + T(results.rows[1].key == "xml"); + }, + + replication: function(debug) { + if (debug) debugger; + var dbPairs = [ + {source:"test_suite_db_a", + target:"test_suite_db_b"}, + {source:"test_suite_db_a", + target:"http://localhost:5984/test_suite_db_b"}, + {source:"http://localhost:5984/test_suite_db_a", + target:"test_suite_db_b"}, + {source:"http://localhost:5984/test_suite_db_a", + target:"http://localhost:5984/test_suite_db_b"} + ] + var dbA = new CouchDB("test_suite_db_a"); + var dbB = new CouchDB("test_suite_db_b"); + var numDocs = 10; + var xhr; + for (var testPair = 0; testPair < dbPairs.length; testPair++) { + var A = dbPairs[testPair].source + var B = dbPairs[testPair].target + + dbA.deleteDb(); + dbA.createDb(); + dbB.deleteDb(); + dbB.createDb(); + + var docs = makeDocs(numDocs); + T(dbA.bulkSave(docs).ok); + + T(CouchDB.replicate(A, B).ok); + + for (var j = 0; j < numDocs; j++) { + docA = dbA.open("" + j); + docB = dbB.open("" + j); + T(docA._rev == docB._rev); + } + + // now check binary attachments + var binDoc = { + _id:"bin_doc", + _attachments:{ + "foo.txt": { + "type":"base64", + "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + } + + dbA.save(binDoc); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + xhr = CouchDB.request("GET", "/test_suite_db_a/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text") + + xhr = CouchDB.request("GET", "/test_suite_db_b/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text") + + dbA.save({_id:"foo1",value:"a"}); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + docA = dbA.open("foo1"); + docB = dbB.open("foo1"); + T(docA._rev == docB._rev); + + dbA.deleteDoc(docA); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + T(dbA.open("foo1") == null); + T(dbB.open("foo1") == null); + + dbA.save({_id:"foo",value:"a"}); + dbB.save({_id:"foo",value:"b"}); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + // open documents and include the conflict meta data + docA = dbA.open("foo", {conflicts: true}); + docB = dbB.open("foo", {conflicts: true}); + + // make sure the same rev is in each db + T(docA._rev === docB._rev); + + // make sure the conflicts are the same in each db + T(docA._conflicts[0] === docB._conflicts[0]); + + // delete a conflict. + dbA.deleteDoc({_id:"foo", _rev:docA._conflicts[0]}); + + // replicate the change + T(CouchDB.replicate(A, B).ok); + + // open documents and include the conflict meta data + docA = dbA.open("foo", {conflicts: true}); + docB = dbB.open("foo", {conflicts: true}); + + // We should have no conflicts this time + T(docA._conflicts === undefined) + T(docB._conflicts === undefined); + } + }, + + etags_head: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var xhr; + + // create a new doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}" + }); + T(xhr.status == 201); + + // extract the ETag header values + var etag = xhr.getResponseHeader("etag") + + // get the doc and verify the headers match + xhr = CouchDB.request("GET", "/test_suite_db/1"); + T(etag == xhr.getResponseHeader("etag")); + + // 'head' the doc and verify the headers match + xhr = CouchDB.request("HEAD", "/test_suite_db/1", { + headers: {"if-none-match": "s"} + }); + T(etag == xhr.getResponseHeader("etag")); + + // replace a doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}", + headers: {"if-match": etag} + }); + T(xhr.status == 201); + + // extract the new ETag value + var etagOld= etag; + etag = xhr.getResponseHeader("etag") + + // fail to replace a doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}" + }); + T(xhr.status == 412) + + // verify get w/Etag + xhr = CouchDB.request("GET", "/test_suite_db/1", { + headers: {"if-none-match": etagOld} + }); + T(xhr.status == 200); + xhr = CouchDB.request("GET", "/test_suite_db/1", { + headers: {"if-none-match": etag} + }); + T(xhr.status == 304); + + // fail to delete a doc + xhr = CouchDB.request("DELETE", "/test_suite_db/1", { + headers: {"if-match": etagOld} + }); + T(xhr.status == 412); + + //now do it for real + xhr = CouchDB.request("DELETE", "/test_suite_db/1", { + headers: {"if-match": etag} + }); + T(xhr.status == 202) + } + +}; + +function makeDocs(n, templateDoc) { + var templateDocSrc = templateDoc ? templateDoc.toSource() : "{}" + var docs = [] + for(var i=0; i 0 ? "failure" : "success"; + } catch (e) { + var status = "error"; + if ($("td.details ol", row).length == 0) { + $("
    ").appendTo($("td.details", row)); + } + $("
  1. Exception raised:
  2. ") + .find("code").text(JSON.stringify(e)).end() + .appendTo($("td.details ol", row)); + if (debug) { + currentRow = null; + throw e; + } + } + if ($("td.details ol", row).length) { + $("Run with debugger").click(function() { + runTest(this, undefined, true); + }).prependTo($("td.details ol", row)); + } + var duration = new Date().getTime() - start; + $("td.status", row).removeClass("running").addClass(status).text(status); + $("td.duration", row).text(duration + "ms"); + updateTestsFooter(); + currentRow = null; + if (callback) callback(); + } + $("td.status", row).addClass("running").text("running…"); + setTimeout(run, 100); +} + +function showSource(cell) { + var name = $(cell).text(); + var win = window.open("", name, "width=700,height=500,resizable=yes,scrollbars=yes"); + win.document.title = name; + $("
    ").text(tests[name].toString()).appendTo(win.document.body).fadeIn();
    +}
    +
    +function updateTestsListing() {
    +  for (var name in tests) {
    +    if (!tests.hasOwnProperty(name)) continue;
    +    var testFunction = tests[name];
    +    var row = $("")
    +      .find("th").text(name).attr("title", "Show source").click(function() {
    +        showSource(this);
    +      }).end()
    +      .find("td:nth(0)").addClass("status").text("not run").end()
    +      .find("td:nth(1)").addClass("duration").html(" ").end()
    +      .find("td:nth(2)").addClass("details").html(" ").end();
    +    $("").click(function() {
    +      this.blur();
    +      runTest(this);
    +      return false;
    +    }).prependTo(row.find("th"));
    +    row.attr("id", name).appendTo("#tests tbody.content");
    +  }
    +  $("#tests tr").removeClass("odd").filter(":odd").addClass("odd");
    +  updateTestsFooter();
    +}
    +
    +function updateTestsFooter() {
    +  var tests = $("#tests tbody.content tr td.status");
    +  var testsRun = tests.not(":contains('not run'))");
    +  var testsFailed = testsRun.not(".success");
    +  $("#tests tbody.footer td").text(testsRun.length + " of " + tests.length +
    +    " test(s) run, " + testsFailed.length + " failures");
    +}
    +
    +// Use T to perform a test that returns false on failure and if the test fails,
    +// display the line that failed.
    +// Example:
    +// T(MyValue==1);
    +function T(arg1, arg2) {
    +  if (!arg1) {
    +    if (currentRow) {
    +      if ($("td.details ol", currentRow).length == 0) {
    +        $("
      ").appendTo($("td.details", currentRow)); + } + $("
    1. Assertion failed:
    2. ") + .find("code").text((arg2 != null ? arg2 : arg1).toString()).end() + .appendTo($("td.details ol", currentRow)); + } + numFailures += 1 + } +} + +function equals(a,b) { + if (a === b) return true; + try { + return repr(a) === repr(b); + } catch (e) { + return false; + } +} + +function repr(val) { + if (val === undefined) { + return null; + } else if (val === null) { + return "null"; + } else { + return JSON.stringify(val); + } +} + +function restartServer() { + var xhr = CouchDB.request("POST", "/_restart"); + do { + xhr = CouchDB.request("GET", "/"); + } while(xhr.status != 200); +} diff --git a/share/www/script/jquery.cookies.js b/share/www/script/jquery.cookies.js new file mode 100644 index 00000000..a2817461 --- /dev/null +++ b/share/www/script/jquery.cookies.js @@ -0,0 +1,47 @@ +// 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. + +(function($) { + $.cookies = $.cookies || {} + $.fn.extend($.cookies, { + + /* Return the value of a cookie. */ + get: function(name) { + var nameEq = name + "="; + var parts = document.cookie.split(';'); + for (var i = 0; i < parts.length; i++) { + var part = parts[i].replace(/^\s+/, ""); + if (part.indexOf(nameEq) == 0) { + return unescape(part.substring(nameEq.length, part.length)); + } + } + return null; + }, + + /* Create or update a cookie. */ + set: function(name, value, days) { + var expires = ""; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24*60*60*1000)); + expires = "; expires=" + date.toGMTString(); + } + document.cookie = name + "=" + escape(value) + expires; + }, + + /* Remove a cookie. */ + remove: function(name) { + $.cookies.set(name, "", -1); + } + + }); +})(jQuery); diff --git a/share/www/script/jquery.dialog.js b/share/www/script/jquery.dialog.js new file mode 100644 index 00000000..dc8f9c5c --- /dev/null +++ b/share/www/script/jquery.dialog.js @@ -0,0 +1,92 @@ +// 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. + +(function($) { + + $.fn.centerBox = function() { + return this.each(function() { + var s = this.style; + s.left = (($(window).width() - $(this).width()) / 2) + "px"; + s.top = (($(window).height() - $(this).height()) / 2) + "px"; + }); + } + + $.showDialog = function(url, options) { + options = options || {}; + options.load = options.load || function() {}; + options.cancel = options.cancel || function() {}; + options.validate = options.validate || function() { return true }; + options.submit = options.submit || function() {}; + + var overlay = $('
      ') + .css("opacity", "0"); + var dialog = $(''); + if ($.browser.msie) { + var frame = $('') + .css("opacity", "0").appendTo(document.body); + if (parseInt($.browser.version)<7) { + dialog.css("position", "absolute"); + overlay.css("position", "absolute"); + $("html,body").css({width: "100%", height: "100%"}); + } + } + overlay.appendTo(document.body).fadeTo(100, 0.6); + dialog.appendTo(document.body).addClass("loading").centerBox().fadeIn(400); + + $(document).keydown(function(e) { + if (e.keyCode == 27) dismiss(); // dismiss on escape key + }); + function dismiss() { + dialog.fadeOut("fast", function() { + $("#dialog, #overlay, #overlay-frame").remove(); + }); + $(document).unbind("keydown"); + } + overlay.click(function() { dismiss(); }); + + function showError(name, message) { + var input = dialog.find(":input[name=" + name + "]"); + input.addClass("error").next("div.error").remove(); + $('
      ').text(message).insertAfter(input); + } + + $.get(url, function(html) { + $(html).appendTo(dialog); + dialog.removeClass("loading").addClass("loaded").centerBox().each(function() { + options.load(dialog.children()[0]); + $(":input:first", dialog).each(function() { this.focus() }); + $("button.cancel", dialog).click(function() { // dismiss on cancel + dismiss(); + options.cancel(); + }); + $("form", dialog).submit(function(e) { // invoke callback on submit + e.preventDefault(); + dialog.find("div.error").remove().end().find(".error").removeClass("error"); + var data = {}; + $.each($("form :input", dialog).serializeArray(), function(i, field) { + data[field.name] = field.value; + }); + var errors = options.submit(data); + if (errors == {}) { + dismiss(); + } else { + for (var name in errors) { + showError(name, errors[name]); + } + } + return false; + }); + }); + }); + } + +})(jQuery); diff --git a/share/www/script/jquery.js b/share/www/script/jquery.js new file mode 100644 index 00000000..2e43a823 --- /dev/null +++ b/share/www/script/jquery.js @@ -0,0 +1,3408 @@ +(function(){ +/* + * jQuery 1.2.3 - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $ + * $Rev: 4663 $ + */ + +// Map over jQuery in case of overwrite +if ( window.jQuery ) + var _jQuery = window.jQuery; + +var jQuery = window.jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.prototype.init( selector, context ); +}; + +// Map over the $ in case of overwrite +if ( window.$ ) + var _$ = window.$; + +// Map the jQuery namespace to the '$' one +window.$ = jQuery; + +// A simple way to check for HTML strings or ID strings +// (both of which we optimize for) +var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; + +// Is it a simple selector +var isSimple = /^.[^:#\[\.]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + return this; + + // Handle HTML strings + } else if ( typeof selector == "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Make sure an element was located + if ( elem ) + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + else { + this[0] = elem; + this.length = 1; + return this; + } + + else + selector = []; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return new jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); + + return this.setArray( + // HANDLE: $(array) + selector.constructor == Array && selector || + + // HANDLE: $(arraylike) + // Watch for when an array-like object, contains DOM nodes, is passed in as the selector + (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || + + // HANDLE: $(*) + [ selector ] ); + }, + + // The current version of jQuery being used + jquery: "1.2.3", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // The number of elements contained in the matched element set + length: 0, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + var ret = -1; + + // Locate the position of the desired element + this.each(function(i){ + if ( this == elem ) + ret = i; + }); + + return ret; + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( name.constructor == String ) + if ( value == undefined ) + return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined; + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text != "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) + // The elements to wrap the target around + jQuery( html, this[0].ownerDocument ) + .clone() + .insertBefore( this[0] ) + .map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }) + .append(this); + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, false, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, true, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + find: function( selector ) { + var elems = jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + }); + + return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? + jQuery.unique( elems ) : + elems ); + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var clone = this.cloneNode(true), + container = document.createElement("div"); + container.appendChild(clone); + return jQuery.clean([container.innerHTML])[0]; + } else + return this.cloneNode(true); + }); + + // Need to set the expando to null on the cloned set if it exists + // removeData doesn't work here, IE removes it from the original as well + // this is primarily for IE but the data expando shouldn't be copied over in any browser + var clone = ret.find("*").andSelf().each(function(){ + if ( this[ expando ] != undefined ) + this[ expando ] = null; + }); + + // Copy the events from the original to the clone + if ( events === true ) + this.find("*").andSelf().each(function(i){ + if (this.nodeType == 3) + return; + var events = jQuery.data( this, "events" ); + + for ( var type in events ) + for ( var handler in events[ type ] ) + jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); + }); + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, this ) ); + }, + + not: function( selector ) { + if ( selector.constructor == String ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ) ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return !selector ? this : this.pushStack( jQuery.merge( + this.get(), + selector.constructor == String ? + jQuery( selector ).get() : + selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ? + selector : [selector] ) ); + }, + + is: function( selector ) { + return selector ? + jQuery.multiFilter( selector, this ).length > 0 : + false; + }, + + hasClass: function( selector ) { + return this.is( "." + selector ); + }, + + val: function( value ) { + if ( value == undefined ) { + + if ( this.length ) { + var elem = this[0]; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + + // Everything else, we just grab the value + } else + return (this[0].value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = value.constructor == Array ? + value : + [ value ]; + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value == undefined ? + (this.length ? + this[0].innerHTML : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + data: function( key, value ){ + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value == null ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data == undefined && this.length ) + data = jQuery.data( this[0], key ); + + return data == null && parts[1] ? + this.data( parts[0] ) : + data; + } else + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ + jQuery.data( this, key, value ); + }); + }, + + removeData: function( key ){ + return this.each(function(){ + jQuery.removeData( this, key ); + }); + }, + + domManip: function( args, table, reverse, callback ) { + var clone = this.length > 1, elems; + + return this.each(function(){ + if ( !elems ) { + elems = jQuery.clean( args, this.ownerDocument ); + + if ( reverse ) + elems.reverse(); + } + + var obj = this; + + if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); + + var scripts = jQuery( [] ); + + jQuery.each(elems, function(){ + var elem = clone ? + jQuery( this ).clone( true )[0] : + this; + + // execute all scripts after the elements have been injected + if ( jQuery.nodeName( elem, "script" ) ) { + scripts = scripts.add( elem ); + } else { + // Remove any inner scripts for later evaluation + if ( elem.nodeType == 1 ) + scripts = scripts.add( jQuery( "script", elem ).remove() ); + + // Inject the elements into the document + callback.call( obj, elem ); + } + }); + + scripts.each( evalScript ); + }); + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.prototype.init.prototype = jQuery.prototype; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( target.constructor == Boolean ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target != "object" && typeof target != "function" ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == 1 ) { + target = this; + i = 0; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + // Prevent never-ending loop + if ( target === options[ name ] ) + continue; + + // Recurse if we're merging object values + if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType ) + target[ name ] = jQuery.extend( target[ name ], options[ name ] ); + + // Don't bring in undefined values + else if ( options[ name ] != undefined ) + target[ name ] = options[ name ]; + + } + + // Return the modified object + return target; +}; + +var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning this function. + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + fn.constructor != Array && /function/i.test( fn + "" ); + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.documentElement && !elem.body || + elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + data = jQuery.trim( data ); + + if ( data ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.browser.msie ) + script.text = data; + else + script.appendChild( document.createTextNode( data ) ); + + head.appendChild( script ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + cache: {}, + + data: function( elem, name, data ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // Compute a unique ID for the element + if ( !id ) + id = elem[ expando ] = ++uuid; + + // Only generate the data cache if we're + // trying to access or manipulate it + if ( name && !jQuery.cache[ id ] ) + jQuery.cache[ id ] = {}; + + // Prevent overriding the named cache with undefined values + if ( data != undefined ) + jQuery.cache[ id ][ name ] = data; + + // Return the named cache data, or the ID for the element + return name ? + jQuery.cache[ id ][ name ] : + id; + }, + + removeData: function( elem, name ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( jQuery.cache[ id ] ) { + // Remove the section of cache data + delete jQuery.cache[ id ][ name ]; + + // If we've removed all the data, remove the element's cache + name = ""; + + for ( name in jQuery.cache[ id ] ) + break; + + if ( !name ) + jQuery.removeData( elem ); + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch(e){ + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) + elem.removeAttribute( expando ); + } + + // Completely remove the data cache + delete jQuery.cache[ id ]; + } + }, + + // args is for internal usage only + each: function( object, callback, args ) { + if ( args ) { + if ( object.length == undefined ) { + for ( var name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( var i = 0, length = object.length; i < length; i++ ) + if ( callback.apply( object[ i ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( object.length == undefined ) { + for ( var name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var i = 0, length = object.length, value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames != undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use is(".class") + has: function( elem, className ) { + return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + var padding = 0, border = 0; + jQuery.each( which, function() { + padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + val -= Math.round(padding + border); + } + + if ( jQuery(elem).is(":visible") ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, val); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret; + + // A helper method for determining if an element's values are broken + function color( elem ) { + if ( !jQuery.browser.safari ) + return false; + + var ret = document.defaultView.getComputedStyle( elem, null ); + return !ret || ret.getPropertyValue("color") == ""; + } + + // We need to handle opacity special in IE + if ( name == "opacity" && jQuery.browser.msie ) { + ret = jQuery.attr( elem.style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + // Opera sometimes will give the wrong display answer, this fixes it, see #2037 + if ( jQuery.browser.opera && name == "display" ) { + var save = elem.style.outline; + elem.style.outline = "0 solid black"; + elem.style.outline = save; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && elem.style && elem.style[ name ] ) + ret = elem.style[ name ]; + + else if ( document.defaultView && document.defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var getComputedStyle = document.defaultView.getComputedStyle( elem, null ); + + if ( getComputedStyle && !color( elem ) ) + ret = getComputedStyle.getPropertyValue( name ); + + // If the element isn't reporting its values properly in Safari + // then some display: none elements are involved + else { + var swap = [], stack = []; + + // Locate all of the parent display: none elements + for ( var a = elem; a && color(a); a = a.parentNode ) + stack.unshift(a); + + // Go through and make them visible, but in reverse + // (It would be better if we knew the exact display type that they had) + for ( var i = 0; i < stack.length; i++ ) + if ( color( stack[ i ] ) ) { + swap[ i ] = stack[ i ].style.display; + stack[ i ].style.display = "block"; + } + + // Since we flip the display style, we have to handle that + // one special, otherwise get the value + ret = name == "display" && swap[ stack.length - 1 ] != null ? + "none" : + ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || ""; + + // Finally, revert the display styles back + for ( var i = 0; i < swap.length; i++ ) + if ( swap[ i ] != null ) + stack[ i ].style.display = swap[ i ]; + } + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + elem.style.left = ret || 0; + ret = elem.style.pixelLeft + "px"; + + // Revert the changed values + elem.style.left = style; + elem.runtimeStyle.left = runtimeStyle; + } + } + + return ret; + }, + + clean: function( elems, context ) { + var ret = []; + context = context || document; + // !context.createElement fails in IE with an error but returns typeof 'object' + if (typeof context.createElement == 'undefined') + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + jQuery.each(elems, function(i, elem){ + if ( !elem ) + return; + + if ( elem.constructor == Number ) + elem = elem.toString(); + + // Convert html string into DOM nodes + if ( typeof elem == "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
      " ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and