// 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($) { $.futon = $.futon || {}; $.extend($.futon, { // Page class for browse/index.html CouchIndexPage: function() { page = this; $.futon.storage.declare("per_page", {defaultValue: 10}); this.addDatabase = function() { $.showDialog("dialog/_create_database.html", { submit: function(data, callback) { if (!data.name || data.name.length == 0) { callback({name: "Please enter a name."}); return; } $.couch.db(data.name).create({ error: function(status, id, reason) { callback({name: reason}) }, success: function(resp) { location.href = "database.html?" + encodeURIComponent(data.name); callback(); } }); } }); return false; } this.updateDatabaseListing = function(offset) { offset |= 0; var maxPerPage = parseInt($("#perpage").val(), 10); $.couch.allDbs({ success: function(dbs) { $("#paging a").unbind(); $("#databases tbody.content").empty(); var dbsOnPage = dbs.slice(offset, offset + maxPerPage); $.each(dbsOnPage, function(idx, dbName) { $("#databases tbody.content").append("" + "" + dbName + "" + "" + ""); $.couch.db(dbName).info({ success: function(info) { $("#databases tbody.content tr:eq(" + idx + ")") .find("td.size").text($.futon.formatSize(info.disk_size)).end() .find("td.count").text(info.doc_count).end() .find("td.seq").text(String(info.update_seq).split(",")[0]); }, error : function() {} }); }); $("#databases tbody tr:odd").addClass("odd"); if (offset > 0) { $("#paging a.prev").attr("href", "#" + (offset - maxPerPage)).click(function() { page.updateDatabaseListing(offset - maxPerPage); }); } else { $("#paging a.prev").removeAttr("href"); } if (offset + maxPerPage < dbs.length) { $("#paging a.next").attr("href", "#" + (offset + maxPerPage)).click(function() { page.updateDatabaseListing(offset + maxPerPage); }); } else { $("#paging a.next").removeAttr("href"); } var firstNum = offset + 1; var lastNum = firstNum + dbsOnPage.length - 1; $("#databases tbody.footer tr td span").text( "Showing " + firstNum + "-" + lastNum + " of " + dbs.length + " databases"); } }); } }, // Page class for browse/database.html CouchDatabasePage: function() { var urlParts = location.search.substr(1).split("/"); var dbName = decodeURIComponent(urlParts.shift()) var dbNameRegExp = new RegExp("[^a-z0-9.\_\$\(\)\+\/\-]", "g"); dbName = dbName.replace(dbNameRegExp, ""); $.futon.storage.declareWithPrefix(dbName + ".", { desc: {}, language: {defaultValue: "javascript"}, map_fun: {defaultValue: ""}, reduce_fun: {defaultValue: ""}, reduce: {}, group_level: {defaultValue: 100}, per_page: {defaultValue: 10}, view: {defaultValue: ""}, stale: {defaultValue: false} }); var viewName = (urlParts.length > 0) ? urlParts.join("/") : null; if (viewName) { $.futon.storage.set("view", decodeURIComponent(viewName)); } else { viewName = $.futon.storage.get("view"); if (viewName) { this.redirecting = true; location.href = "database.html?" + encodeURIComponent(dbName) + "/" + encodeURIComponent(viewName); } } var db = $.couch.db(dbName); this.dbName = dbName; viewName = decodeURIComponent(viewName); this.viewName = viewName; this.viewLanguage = "javascript"; this.db = db; this.isDirty = false; this.isTempView = viewName == "_temp_view"; page = this; var templates = { javascript: "function(doc) {\n emit(null, doc);\n}", python: "def fun(doc):\n yield None, doc", ruby: "lambda {|doc|\n emit(nil, doc);\n}" } this.newDocument = function() { location.href = "document.html?" + encodeURIComponent(db.name); } this.compactAndCleanup = function() { $.showDialog("dialog/_compact_cleanup.html", { submit: function(data, callback) { switch (data.action) { case "compact_database": db.compact({success: function(resp) { callback() }}); break; case "compact_views": var idx = page.viewName.indexOf("/_view"); if (idx == -1) { alert("Compact Views requires focus on a view!"); } else { var groupname = page.viewName.substring(8, idx); db.compactView(groupname, {success: function(resp) { callback() }}); } break; case "view_cleanup": db.viewCleanup({success: function(resp) { callback() }}); break; } } }); } this.deleteDatabase = function() { $.showDialog("dialog/_delete_database.html", { submit: function(data, callback) { db.drop({ success: function(resp) { callback(); location.href = "index.html"; if (window !== null) { $("#dbs li").filter(function(index) { return $("a", this).text() == dbName; }).remove(); $.futon.navigation.removeDatabase(dbName); } } }); } }); } this.databaseSecurity = function() { $.showDialog("dialog/_database_security.html", { load : function(d) { db.getDbProperty("_security", { success: function(r) { ["admin", "reader"].forEach(function(key) { var names = []; var roles = []; if (r && typeof r[key + "s"] === "object") { if ($.isArray(r[key + "s"]["names"])) { names = r[key + "s"]["names"]; } if ($.isArray(r[key + "s"]["roles"])) { roles = r[key + "s"]["roles"]; } } $("input[name=" + key + "_names]", d).val(JSON.stringify(names)); $("input[name=" + key + "_roles]", d).val(JSON.stringify(roles)); }); } }); }, // maybe this should be 2 forms submit: function(data, callback) { var errors = {}; var secObj = { admins: { names: [], roles: [] }, readers: { names: [], roles: [] } }; ["admin", "reader"].forEach(function(key) { var names, roles; try { names = JSON.parse(data[key + "_names"]); } catch(e) { } try { roles = JSON.parse(data[key + "_roles"]); } catch(e) { } if ($.isArray(names)) { secObj[key + "s"]["names"] = names; } else { errors[key + "_names"] = "The " + key + " names must be an array of strings"; } if ($.isArray(roles)) { secObj[key + "s"]["roles"] = roles; } else { errors[key + "_roles"] = "The " + key + " roles must be an array of strings"; } }); if ($.isEmptyObject(errors)) { db.setDbProperty("_security", secObj); } callback(errors); } }); } this.populateViewEditor = function() { if (viewName.match(/^_design\//)) { page.revertViewChanges(function() { var dirtyTimeout = null; function updateDirtyState() { clearTimeout(dirtyTimeout); dirtyTimeout = setTimeout(function() { var buttons = $("#viewcode button.save, #viewcode button.revert"); var viewCode = { map: $("#viewcode_map").val(), reduce: $("#viewcode_reduce").val() }; $("#reduce, #grouplevel").toggle(!!viewCode.reduce); page.isDirty = (viewCode.map != page.storedViewCode.map) || (viewCode.reduce != (page.storedViewCode.reduce || "")) || page.viewLanguage != page.storedViewLanguage; if (page.isDirty) { buttons.removeAttr("disabled"); } else { buttons.attr("disabled", "disabled"); } }, 100); } $("#viewcode textarea").enableTabInsertion() .bind("input", updateDirtyState); if ($.browser.msie || $.browser.safari) { $("#viewcode textarea").bind("paste", updateDirtyState) .bind("change", updateDirtyState) .bind("keydown", updateDirtyState) .bind("keypress", updateDirtyState) .bind("keyup", updateDirtyState) .bind("textInput", updateDirtyState); } $("#language").change(updateDirtyState); page.updateDocumentListing(); }); } else if (viewName == "_temp_view") { $("#viewcode textarea").enableTabInsertion(); page.viewLanguage = $.futon.storage.get("language"); page.updateViewEditor( $.futon.storage.get("map_fun", templates[page.viewLanguage]), $.futon.storage.get("reduce_fun") ); } else { $("#grouplevel, #reduce").hide(); page.updateDocumentListing(); } page.populateLanguagesMenu(); if (this.isTempView) { $("#tempwarn").show(); } } // Populate the languages dropdown, and listen to selection changes this.populateLanguagesMenu = function() { var all_langs = {}; fill_language = function() { var select = $("#language"); for (var language in all_langs) { var option = $(document.createElement("option")) .attr("value", language).text(language) .appendTo(select); } if (select[0].options.length == 1) { select[0].disabled = true; } else { select[0].disabled = false; select.val(page.viewLanguage); select.change(function() { var language = $("#language").val(); if (language != page.viewLanguage) { var mapFun = $("#viewcode_map").val(); if (mapFun == "" || mapFun == templates[page.viewLanguage]) { // no edits made, so change to the new default $("#viewcode_map").val(templates[language]); } page.viewLanguage = language; $("#viewcode_map")[0].focus(); } return false; }); } } $.couch.config({ success: function(resp) { for (var language in resp) { all_langs[language] = resp[language]; } $.couch.config({ success: function(resp) { for (var language in resp) { all_langs[language] = resp[language]; } fill_language(); } }, "native_query_servers"); }, error : function() {} }, "query_servers"); } this.populateViewsMenu = function() { var select = $("#switch select"); db.allDocs({startkey: "_design/", endkey: "_design0", include_docs: true, success: function(resp) { select[0].options.length = 3; for (var i = 0; i < resp.rows.length; i++) { var doc = resp.rows[i].doc; var optGroup = $(document.createElement("optgroup")) .attr("label", doc._id.substr(8)).appendTo(select); var viewNames = []; for (var name in doc.views) { viewNames.push(name); } viewNames.sort(); for (var j = 0; j < viewNames.length; j++) { var path = $.couch.encodeDocId(doc._id) + "/_view/" + encodeURIComponent(viewNames[j]); var option = $(document.createElement("option")) .attr("value", path).text(encodeURIComponent(viewNames[j])) .appendTo(optGroup); if (path == viewName) { option[0].selected = true; } } } } }); if (!viewName.match(/^_design\//)) { $.each(["_all_docs", "_design_docs", "_temp_view"], function(idx, name) { if (viewName == name) { select[0].options[idx].selected = true; } }); } } this.revertViewChanges = function(callback) { if (!page.storedViewCode) { var viewNameParts = viewName.split("/"); var designDocId = decodeURIComponent(viewNameParts[1]); var localViewName = decodeURIComponent(viewNameParts[3]); db.openDoc("_design/" + designDocId, { error: function(status, error, reason) { if (status == 404) { $.futon.storage.del("view"); location.href = "database.html?" + encodeURIComponent(db.name); } }, success: function(resp) { if(!resp.views || !resp.views[localViewName]) { $.futon.storage.del("view"); location.href = "database.html?" + encodeURIComponent(db.name); } var viewCode = resp.views[localViewName]; page.viewLanguage = resp.language || "javascript"; $("#language").val(encodeURIComponent(page.viewLanguage)); page.updateViewEditor(viewCode.map, viewCode.reduce || ""); $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); page.storedViewCode = viewCode; page.storedViewLanguage = page.viewLanguage; if (callback) callback(); } }, {async: false}); } else { page.updateViewEditor(page.storedViewCode.map, page.storedViewCode.reduce || ""); page.viewLanguage = page.storedViewLanguage; $("#language").val(encodeURIComponent(page.viewLanguage)); $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); page.isDirty = false; if (callback) callback(); } } this.updateViewEditor = function(mapFun, reduceFun) { if (!mapFun) return; $("#viewcode_map").val(mapFun); $("#viewcode_reduce").val(reduceFun); var lines = Math.max( mapFun.split("\n").length, reduceFun.split("\n").length ); $("#reduce, #grouplevel").toggle(!!reduceFun); $("#viewcode textarea").attr("rows", Math.min(15, Math.max(3, lines))); } this.saveViewAs = function() { if (viewName && /^_design/.test(viewName)) { var viewNameParts = viewName.split("/"); var designDocId = decodeURIComponent(viewNameParts[1]); var localViewName = decodeURIComponent(viewNameParts[3]); } else { var designDocId = "", localViewName = ""; } $.showDialog("dialog/_save_view_as.html", { load: function(elem) { $("#input_docid", elem).val(designDocId).suggest(function(text, callback) { db.allDocs({ limit: 10, startkey: "_design/" + text, endkey: "_design0", success: function(docs) { var matches = []; for (var i = 0; i < docs.rows.length; i++) { var docName = docs.rows[i].id.substr(8); if (docName.indexOf(text) == 0) { matches[i] = docName; } } callback(matches); } }); }); $("#input_name", elem).val(localViewName).suggest(function(text, callback) { db.openDoc("_design/" + $("#input_docid").val(), { error: function() {}, // ignore success: function(doc) { var matches = []; if (!doc.views) return; for (var viewName in doc.views) { if (viewName.indexOf(text) == 0) { matches.push(viewName); } } callback(matches); } }); }); }, submit: function(data, callback) { 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"; callback(errors); } else { var viewCode = { map: $("#viewcode_map").val(), reduce: $("#viewcode_reduce").val() || undefined }; var docId = ["_design", data.docid].join("/"); function save(doc) { if (!doc) { doc = {_id: docId, language: page.viewLanguage}; } else { var numViews = 0; for (var viewName in (doc.views || {})) { if (viewName != data.name) numViews++; } if (numViews > 0 && page.viewLanguage != doc.language) { callback({ docid: "Cannot save to " + data.docid + " because its language is \"" + doc.language + "\", not \"" + encodeURIComponent(page.viewLanguage) + "\"." }); return; } doc.language = page.viewLanguage; } if (doc.views === undefined) doc.views = {}; doc.views[data.name] = viewCode; db.saveDoc(doc, { success: function(resp) { callback(); page.isDirty = false; location.href = "database.html?" + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(doc._id) + "/_view/" + encodeURIComponent(data.name); } }); } db.openDoc(docId, { error: function(status, error, reason) { if (status == 404) save(null); else alert(reason); }, success: function(doc) { save(doc); } }); } } }); } this.saveViewChanges = function() { var viewNameParts = viewName.split("/"); var designDocId = decodeURIComponent(viewNameParts[1]); var localViewName = decodeURIComponent(viewNameParts[3]); db.openDoc("_design/" + designDocId, { success: function(doc) { var numViews = 0; for (var viewName in (doc.views || {})) { if (viewName != localViewName) numViews++; } if (numViews > 0 && page.viewLanguage != doc.language) { alert("Cannot save view because the design document language " + "is \"" + doc.language + "\", not \"" + page.viewLanguage + "\"."); return; } doc.language = page.viewLanguage; var viewDef = doc.views[localViewName]; viewDef.map = $("#viewcode_map").val(); viewDef.reduce = $("#viewcode_reduce").val() || undefined; db.saveDoc(doc, { success: function(resp) { page.isDirty = false; $("#viewcode button.revert, #viewcode button.save") .attr("disabled", "disabled"); } }); } }); } this.updateDesignDocLink = function() { if (viewName && /^_design/.test(viewName)) { var docId = "_design/" + encodeURIComponent(decodeURIComponent(viewName).split("/")[1]); $("#designdoc-link").attr("href", "document.html?" + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(docId)).text(docId); } else { $("#designdoc-link").removeAttr("href").text(""); } } this.jumpToDocument = function(docId) { if (docId != "") { location.href = 'document.html?' + encodeURIComponent(db.name) + "/" + $.couch.encodeDocId(docId); } } this.updateDocumentListing = function(options) { if (options === undefined) options = {}; if (options.limit === undefined) { var perPage = parseInt($("#perpage").val(), 10) // Fetch an extra row so we know when we're on the last page for // reduce views options.limit = perPage + 1; } else { perPage = options.limit - 1; } if ($("#documents thead th.key").is(".desc")) { if (typeof options.descending == 'undefined') options.descending = true; var descend = true; $.futon.storage.set("desc", "1"); } else { var descend = false; $.futon.storage.del("desc"); } $("#paging a").unbind(); $("#documents").find("tbody.content").empty().end().show(); page.updateDesignDocLink(); options.success = function(resp) { if (resp.offset === undefined) { resp.offset = 0; } var descending_reverse = ((options.descending && !descend) || (descend && (options.descending === false))); var has_reduce_prev = resp.total_rows === undefined && (descending_reverse ? resp.rows.length > perPage : options.startkey !== undefined); if (descending_reverse && resp.rows) { resp.rows = resp.rows.reverse(); if (resp.rows.length > perPage) { resp.rows.push(resp.rows.shift()); } } if (resp.rows !== null && (has_reduce_prev || (descending_reverse ? (resp.total_rows - resp.offset > perPage) : (resp.offset > 0)))) { $("#paging a.prev").attr("href", "#" + (resp.offset - perPage)).click(function() { var opt = { descending: !descend, limit: options.limit }; if (resp.rows.length > 0) { var firstDoc = resp.rows[0]; opt.startkey = firstDoc.key !== undefined ? firstDoc.key : null; if (firstDoc.id !== undefined) { opt.startkey_docid = firstDoc.id; } opt.skip = 1; } page.updateDocumentListing(opt); return false; }); } else { $("#paging a.prev").removeAttr("href"); } var has_reduce_next = resp.total_rows === undefined && (descending_reverse ? options.startkey !== undefined : resp.rows.length > perPage); if (resp.rows !== null && (has_reduce_next || (descending_reverse ? (resp.offset - resp.total_rows < perPage) : (resp.total_rows - resp.offset > perPage)))) { $("#paging a.next").attr("href", "#" + (resp.offset + perPage)).click(function() { var opt = { descending: descend, limit: options.limit }; if (resp.rows.length > 0) { var lastDoc = resp.rows[Math.min(perPage, resp.rows.length) - 1]; opt.startkey = lastDoc.key !== undefined ? lastDoc.key : null; if (lastDoc.id !== undefined) { opt.startkey_docid = lastDoc.id; } opt.skip = 1; } page.updateDocumentListing(opt); return false; }); } else { $("#paging a.next").removeAttr("href"); } for (var i = 0; i < Math.min(perPage, resp.rows.length); i++) { var row = resp.rows[i]; var tr = $(""); var key = "null"; if (row.key !== null) { key = $.futon.formatJSON(row.key, {indent: 0, linesep: ""}); } if (row.id) { key = key.replace(/\\"/, '"'); var rowlink = encodeURIComponent(db.name) + "/" + $.couch.encodeDocId(row.id); $("" + $.futon.escape(key) + "
" + "ID: " + $.futon.escape(row.id) + "
") .appendTo(tr); } else { $("") .find("strong").text(key).end() .appendTo(tr); } var value = "null"; if (row.value !== null) { value = $.futon.formatJSON(row.value, { html: true, indent: 0, linesep: "", quoteKeys: false }); } $("
").find("div").html(value).end() .appendTo(tr).dblclick(function() { location.href = this.previousSibling.firstChild.href; }); tr.appendTo("#documents tbody.content"); } var firstNum = 1; var lastNum = totalNum = Math.min(perPage, resp.rows.length); if (resp.total_rows != null) { if (descending_reverse) { lastNum = Math.min(resp.total_rows, resp.total_rows - resp.offset); firstNum = lastNum - totalNum + 1; } else { firstNum = Math.min(resp.total_rows, resp.offset + 1); lastNum = firstNum + totalNum - 1; } totalNum = resp.total_rows; } else { totalNum = "unknown"; } $("#paging").show(); $("#documents tbody.footer td span").text( "Showing " + firstNum + "-" + lastNum + " of " + totalNum + " row" + (firstNum != lastNum || totalNum == "unknown" ? "s" : "")); $("#documents tbody tr:odd").addClass("odd"); } options.error = function(status, error, reason) { alert("Error: " + error + "\n\n" + reason); } if (!viewName || viewName == "_all_docs") { $("#switch select")[0].selectedIndex = 0; db.allDocs(options); } else { if (viewName == "_temp_view") { $("#viewcode").show().removeClass("collapsed"); var mapFun = $("#viewcode_map").val(); $.futon.storage.set("map_fun", mapFun); var reduceFun = $.trim($("#viewcode_reduce").val()) || null; if (reduceFun) { $.futon.storage.set("reduce_fun", reduceFun); if ($("#reduce :checked").length) { var level = parseInt($("#grouplevel select").val(), 10); options.group = level > 0; if (options.group && level < 100) { options.group_level = level; } } else { options.reduce = false; } } $.futon.storage.set("language", page.viewLanguage); db.query(mapFun, reduceFun, page.viewLanguage, options); } else if (viewName == "_design_docs") { options.startkey = options.descending ? "_design0" : "_design"; options.endkey = options.descending ? "_design" : "_design0"; db.allDocs(options); } else { $("button.compactview").show(); $("#viewcode").show(); var currentMapCode = $("#viewcode_map").val(); var currentReduceCode = $.trim($("#viewcode_reduce").val()) || null; if (currentReduceCode) { if ($("#reduce :checked").length) { var level = parseInt($("#grouplevel select").val(), 10); options.group = level > 0; if (options.group && level < 100) { options.group_level = level; } } else { options.reduce = false; } } if (page.isDirty) { db.query(currentMapCode, currentReduceCode, page.viewLanguage, options); } else { var viewParts = decodeURIComponent(viewName).split('/'); if ($.futon.storage.get("stale")) { options.stale = "ok"; } db.view(viewParts[1] + "/" + viewParts[3], options); } } } } 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/document.html CouchDocumentPage: function() { var urlParts = location.search.substr(1).split("/"); var dbName = decodeURIComponent(urlParts.shift()); if (urlParts.length) { var idParts = urlParts.join("/").split("@", 2); var docId = decodeURIComponent(idParts[0]); var docRev = (idParts.length > 1) ? idParts[1] : null; this.isNew = false; } else { var docId = $.couch.newUUID(); var docRev = null; this.isNew = true; } var db = $.couch.db(dbName); $.futon.storage.declare("tab", {defaultValue: "tabular", scope: "cookie"}); this.dbName = dbName; this.db = db; this.docId = docId; this.doc = null; this.isDirty = this.isNew; page = this; this.activateTabularView = function() { if ($("#fields tbody.source textarea").length > 0) return; $.futon.storage.set("tab", "tabular"); $("#tabs li").removeClass("active").filter(".tabular").addClass("active"); $("#fields thead th:first").text("Field").attr("colspan", 1).next().show(); $("#fields tbody.content").show(); $("#fields tbody.source").hide(); return false; } this.activateSourceView = function() { $.futon.storage.set("tab", "source"); $("#tabs li").removeClass("active").filter(".source").addClass("active"); $("#fields thead th:first").text("Source").attr("colspan", 2).next().hide(); $("#fields tbody.content").hide(); $("#fields tbody.source").find("td").each(function() { $(this).html($("
").html($.futon.formatJSON(page.doc, {html: true})))
            .makeEditable({allowEmpty: false,
              createInput: function(value) {
                var rows = value.split("\n").length;
                return $("").enableTabInsertion();
              },
              prepareInput: function(input) {
                $(input).makeResizable({vertical: true});
              },
              end: function() {
                $(this).html($("
").html($.futon.formatJSON(page.doc, {html: true})));
              },
              accept: function(newValue) {
                page.doc = JSON.parse(newValue);
                page.isDirty = true;
                page.updateFieldListing(true);
              },
              populate: function(value) {
                return $.futon.formatJSON(page.doc);
              },
              validate: function(value) {
                try {
                  var doc = JSON.parse(value);
                  if (typeof doc != "object")
                    throw new SyntaxError("Please enter a valid JSON document (for example, {}).");
                  return true;
                } catch (err) {
                  var msg = err.message;
                  if (msg == "parseJSON" || msg == "JSON.parse") {
                    msg = "There is a syntax error in the document.";
                  }
                  $("
").text(msg).appendTo(this); return false; } } }); }).end().show(); return false; } this.addField = function() { if (!$("#fields tbody.content:visible").length) { location.hash = "#tabular"; page.activateTabularView(); } var fieldName = "unnamed"; var fieldIdx = 1; while (page.doc.hasOwnProperty(fieldName)) { fieldName = "unnamed " + fieldIdx++; } page.doc[fieldName] = null; var row = _addRowForField(page.doc, fieldName); page.isDirty = true; row.find("th b").dblclick(); } var _sortFields = function(a, b) { var a0 = a.charAt(0), b0 = b.charAt(0); if (a0 == "_" && b0 != "_") { return -1; } else if (a0 != "_" && b0 == "_") { return 1; } else if (a == "_attachments" || b == "_attachments") { return a0 == "_attachments" ? 1 : -1; } else { return a < b ? -1 : a != b ? 1 : 0; } } this.updateFieldListing = function(noReload) { $("#fields tbody.content").empty(); function handleResult(doc, revs) { page.doc = doc; var propNames = []; for (var prop in doc) { propNames.push(prop); } // Order properties alphabetically, but put internal fields first propNames.sort(_sortFields); for (var pi = 0; pi < propNames.length; pi++) { _addRowForField(doc, 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", "?" + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(docId) + "@" + prevRev); } if (currentIndex > 0) { var nextRev = revs[currentIndex - 1].rev; $("#paging a.next").attr("href", "?" + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(docId) + "@" + nextRev); } $("#fields tbody.footer td span").text("Showing revision " + (revs.length - currentIndex) + " of " + revs.length); } if ($.futon.storage.get("tab") == "source") { page.activateSourceView(); } } if (noReload) { handleResult(page.doc, []); return; } if (!page.isNew) { db.openDoc(docId, {revs_info: true, success: function(doc) { var revs = doc._revs_info || []; delete doc._revs_info; if (docRev != null) { db.openDoc(docId, {rev: docRev, error: function(status, error, reason) { alert("The requested revision was not found. You will " + "be redirected back to the latest revision."); location.href = "?" + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(docId); }, success: function(doc) { handleResult(doc, revs); } }); } else { handleResult(doc, revs); } } }); } else { handleResult({_id: docId}, []); $("#fields tbody td").dblclick(); } } this.deleteDocument = function() { $.showDialog("dialog/_delete_document.html", { submit: function(data, callback) { db.removeDoc(page.doc, { success: function(resp) { callback(); location.href = "database.html?" + encodeURIComponent(dbName); } }); } }); } this.saveDocument = function() { db.saveDoc(page.doc, { error: function(status, error, reason) { alert("Error: " + error + "\n\n" + reason); }, success: function(resp) { page.isDirty = false; location.href = "?" + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(page.docId); } }); } this.uploadAttachment = function() { if (page.isDirty) { alert("You need to save or revert any changes you have made to the " + "document before you can attach a new file."); return false; } $.showDialog("dialog/_upload_attachment.html", { load: function(elem) { $("input[name='_rev']", elem).val(page.doc._rev); }, submit: function(data, callback) { if (!data._attachments || data._attachments.length == 0) { callback({_attachments: "Please select a file to upload."}); return; } var form = $("#upload-form"); form.find("#progress").css("visibility", "visible"); form.ajaxSubmit({ url: db.uri + $.couch.encodeDocId(page.docId), success: function(resp) { form.find("#progress").css("visibility", "hidden"); page.isDirty = false; location.href = "?" + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(page.docId); } }); } }); } window.onbeforeunload = function() { if (page.isDirty) { return "You've made changes to this document that have not been " + "saved yet."; } } function _addRowForField(doc, fieldName) { var row = $("") .find("th").append($("").text(fieldName)).end() .appendTo("#fields tbody.content"); if (fieldName == "_attachments") { row.find("td").append(_renderAttachmentList(doc[fieldName])); } else { row.find("td").append(_renderValue(doc[fieldName])); _initKey(doc, row, fieldName); _initValue(doc, row, fieldName); } $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); row.data("name", fieldName); return row; } function _initKey(doc, row, fieldName) { if (fieldName == "_id" || fieldName == "_rev") { return; } var cell = row.find("th"); $("").click(function() { delete doc[fieldName]; row.remove(); page.isDirty = true; $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); }).prependTo(cell); cell.find("b").makeEditable({allowEmpty: false, accept: function(newName, oldName) { doc[newName] = doc[oldName]; delete doc[oldName]; row.data("name", newName); $(this).text(newName); page.isDirty = true; }, begin: function() { row.find("th button.delete").hide(); return true; }, end: function(keyCode) { row.find("th button.delete").show(); if (keyCode == 9) { // tab, move to editing the value row.find("td").dblclick(); } }, validate: function(newName, oldName) { $("div.error", this).remove(); if (newName != oldName && doc[newName] !== undefined) { $("
Already have field with that name.
") .appendTo(this); return false; } return true; } }); } function _initValue(doc, row, fieldName) { if ((fieldName == "_id" && !page.isNew) || fieldName == "_rev") { return; } row.find("td").makeEditable({acceptOnBlur: false, allowEmpty: true, createInput: function(value) { value = doc[row.data("name")]; var elem = $(this); if (elem.find("dl").length > 0 || elem.find("code").is(".array, .object") || typeof(value) == "string" && (value.length > 60 || value.match(/\n/))) { return $(""); } return $(""); }, end: function() { $(this).children().remove(); $(this).append(_renderValue(doc[row.data("name")])); }, prepareInput: function(input) { if ($(input).is("textarea")) { var height = Math.min(input.scrollHeight, document.body.clientHeight - 100); $(input).height(height).makeResizable({vertical: true}).enableTabInsertion(); } }, accept: function(newValue) { var fieldName = row.data("name"); try { doc[fieldName] = JSON.parse(newValue); } catch (err) { doc[fieldName] = newValue; } page.isDirty = true; if (fieldName == "_id") { page.docId = page.doc._id = doc[fieldName]; $("h1 strong").text(page.docId); } }, populate: function(value) { value = doc[row.data("name")]; if (typeof(value) == "string") { return value; } return $.futon.formatJSON(value); }, validate: function(value) { $("div.error", this).remove(); try { var parsed = JSON.parse(value); if (row.data("name") == "_id" && typeof(parsed) != "string") { $("
The document ID must be a string.
") .appendTo(this); return false; } return true; } catch (err) { return true; } } }); } function _renderValue(value) { function isNullOrEmpty(val) { if (val == null) return true; for (var i in val) return false; return true; } function render(val) { var type = typeof(val); if (type == "object" && !isNullOrEmpty(val)) { var list = $("
"); for (var i in val) { $("
").text(i).appendTo(list); $("
").append(render(val[i])).appendTo(list); } return list; } else { var html = $.futon.formatJSON(val, { html: true, escapeStrings: false }); var n = $(html); if (n.text().length > 140) { // This code reduces a long string in to a summarized string with a link to expand it. // Someone, somewhere, is doing something nasty with the event after it leaves these handlers. // At this time I can't track down the offender, it might actually be a jQuery propogation issue. var fulltext = n.text(); var mintext = n.text().slice(0, 140); var e = $('...'); var m = $('X'); var expand = function (evt) { n.empty(); n.text(fulltext); n.append(m); evt.stopPropagation(); evt.stopImmediatePropagation(); evt.preventDefault(); } var minimize = function (evt) { n.empty(); n.text(mintext); // For some reason the old element's handler won't fire after removed and added again. e = $('...'); e.click(expand); n.append(e); evt.stopPropagation(); evt.stopImmediatePropagation(); evt.preventDefault(); } e.click(expand); n.click(minimize); n.text(mintext); n.append(e) } return n; } } var elem = render(value); elem.find("dd:has(dl)").hide().prev("dt").addClass("collapsed"); elem.find("dd:not(:has(dl))").addClass("inline").prev().addClass("inline"); elem.find("dt.collapsed").click(function() { $(this).toggleClass("collapsed").next().toggle(); }); return elem; } function _renderAttachmentList(attachments) { var ul = $("").addClass("attachments"); $.each(attachments, function(idx, attachment) { _renderAttachmentItem(idx, attachment).appendTo(ul); }); return ul; } function _renderAttachmentItem(name, attachment) { var attachmentHref = db.uri + $.couch.encodeDocId(page.docId) + "/" + encodeAttachment(name); var li = $("
  • "); $("").text(name) .attr("href", attachmentHref) .wrapInner("").appendTo(li); $("()").text("" + $.futon.formatSize(attachment.length) + ", " + attachment.content_type).addClass("info").appendTo(li); if (name == "tests.js") { li.find('span.info').append(', open in test runner'); } _initAttachmentItem(name, attachment, li); return li; } function _initAttachmentItem(name, attachment, li) { $("").click(function() { if (!li.siblings("li").length) { delete page.doc._attachments; li.parents("tr").remove(); $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); } else { delete page.doc._attachments[name]; li.remove(); } page.isDirty = true; return false; }).prependTo($("a", li)); } }, }); function encodeAttachment(name) { var encoded = [], parts = name.split('/'); for (var i=0; i < parts.length; i++) { encoded.push(encodeURIComponent(parts[i])); }; return encoded.join('%2f'); } })(jQuery);