Skip to content

Instantly share code, notes, and snippets.

@dscape
Created April 1, 2011 20:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dscape/baa888d42be7d8e264b2 to your computer and use it in GitHub Desktop.
Save dscape/baa888d42be7d8e264b2 to your computer and use it in GitHub Desktop.
// 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("<tr>" +
"<th><a href='database.html?" + encodeURIComponent(dbName) + "'>" +
dbName + "</a></th>" +
"<td class='size'></td><td class='count'></td>" +
"<td class='seq'></td></tr>");
$.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(info.update_seq);
},
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() {
function namesAndRoles(r, 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"];
}
}
return {names : names, roles: roles};
};
$.showDialog("dialog/_database_security.html", {
load : function(d) {
db.getDbProperty("_security", {
success: function(r) {
var admins = namesAndRoles(r, "admin")
, members = namesAndRoles(r, "member");
if (members.names.length + members.roles.length == 0) {
// backwards compatibility with readers for 1.x
members = namesAndRoles(r, "reader");
}
$("input[name=admin_names]", d).val(JSON.stringify(admins.names));
$("input[name=admin_roles]", d).val(JSON.stringify(admins.roles));
$("input[name=member_names]", d).val(JSON.stringify(members.names));
$("input[name=member_roles]", d).val(JSON.stringify(members.roles));
}
});
},
// maybe this should be 2 forms
submit: function(data, callback) {
var errors = {};
var secObj = {
admins: {
names: [],
roles: []
},
members: {
names: [],
roles: []
}
};
["admin", "member"].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 = $("<tr></tr>");
var key = "null";
if (row.key !== null) {
key = $.futon.formatJSON(row.key, {indent: 0, linesep: ""});
}
if (row.id) {
$("<td class='key'><a href='document.html?" + encodeURIComponent(db.name) +
"/" + $.couch.encodeDocId(row.id) + "'><strong></strong><br>" +
"<span class='docid'>ID:&nbsp;" + $.futon.escape(row.id) + "</span></a></td>")
.find("strong").text(key).end()
.appendTo(tr);
} else {
$("<td class='key'><strong></strong></td>")
.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
});
}
$("<td class='value'><div></div></td>").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($("<pre></pre>").html($.futon.formatJSON(page.doc, {html: true})))
.makeEditable({allowEmpty: false,
createInput: function(value) {
var rows = value.split("\n").length;
return $("<textarea rows='" + rows + "' cols='80' spellcheck='false'></textarea>").enableTabInsertion();
},
prepareInput: function(input) {
$(input).makeResizable({vertical: true});
},
end: function() {
$(this).html($("<pre></pre>").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.";
}
$("<div class='error'></div>").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 = $("<tr><th></th><td></td></tr>")
.find("th").append($("<b></b>").text(fieldName)).end()
.appendTo("#fields tbody.content");
if (fieldName == "_attachments") {
row.find("td").append(_renderAttachmentList(doc[fieldName], doc["_rev"]));
} 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");
$("<button type='button' class='delete' title='Delete field'></button>").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) {
$("<div class='error'>Already have field with that name.</div>")
.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 $("<textarea rows='1' cols='40' spellcheck='false'></textarea>");
}
return $("<input type='text' spellcheck='false'>");
},
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") {
$("<div class='error'>The document ID must be a string.</div>")
.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 = $("<dl></dl>");
for (var i in val) {
$("<dt></dt>").text(i).appendTo(list);
$("<dd></dd>").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 = $('<a href="#expand">...</a>');
var m = $('<a href="#min">X</a>');
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 = $('<a href="#expand">...</a>');
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, rev) {
var ul = $("<ul></ul>").addClass("attachments");
$.each(attachments, function(idx, attachment) {
_renderAttachmentItem(idx, attachment, rev).appendTo(ul);
});
return ul;
}
function _renderAttachmentItem(name, attachment, rev) {
var attachmentHref = db.uri + $.couch.encodeDocId(page.docId)
+ "/" + encodeAttachment(name) + "?rev=" + rev;
var li = $("<li></li>");
$("<a href='' title='Download file' target='_top'></a>").text(name)
.attr("href", attachmentHref)
.wrapInner("<tt></tt>").appendTo(li);
$("<span>()</span>").text("" + $.futon.formatSize(attachment.length) +
", " + attachment.content_type).addClass("info").appendTo(li);
if (name == "tests.js") {
li.find('span.info').append(', <a href="/_utils/couch_tests.html?'
+ attachmentHref + '">open in test runner</a>');
}
_initAttachmentItem(name, attachment, li);
return li;
}
function _initAttachmentItem(name, attachment, li) {
$("<button type='button' class='delete' title='Delete attachment'></button>").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);
@dscape
Copy link
Author

dscape commented Apr 1, 2011

Lines Changed: +L1060 +L1245 +L1248 +L1253 +L1255

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment