summaryrefslogtreecommitdiff
path: root/share
diff options
context:
space:
mode:
authorChristopher Lenz <cmlenz@apache.org>2009-01-14 13:41:32 +0000
committerChristopher Lenz <cmlenz@apache.org>2009-01-14 13:41:32 +0000
commit75ab4ae3e4a423c0f85cd87a2237453fd43bfba8 (patch)
tree2bb07fcfb9a9458cd998b38598ae726c3c2d22fc /share
parent54986fe78df83e75b369c5a0c3049f36f96f1ba1 (diff)
Refactor the inline editing Javascript code in Futon to make it reusable.
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@734390 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'share')
-rw-r--r--share/Makefile.am1
-rw-r--r--share/www/database.html1
-rw-r--r--share/www/document.html1
-rw-r--r--share/www/script/futon.browse.js239
-rw-r--r--share/www/script/futon.format.js12
-rw-r--r--share/www/script/jquery.editinline.js104
-rw-r--r--share/www/style/layout.css35
7 files changed, 234 insertions, 159 deletions
diff --git a/share/Makefile.am b/share/Makefile.am
index be6e81d9..4e9b96a4 100644
--- a/share/Makefile.am
+++ b/share/Makefile.am
@@ -65,6 +65,7 @@ nobase_dist_localdata_DATA = \
www/script/jquery.cookies.js \
www/script/jquery.couch.js \
www/script/jquery.dialog.js \
+ www/script/jquery.editinline.js \
www/script/jquery.form.js \
www/script/jquery.resizer.js \
www/script/jquery.suggest.js \
diff --git a/share/www/database.html b/share/www/database.html
index 8f0b6d5d..3b968487 100644
--- a/share/www/database.html
+++ b/share/www/database.html
@@ -37,6 +37,7 @@ specific language governing permissions and limitations under the License.
});
$(function() {
+ if (page.redirecting) return;
$("h1 strong").text(page.db.name);
var viewPath = (page.viewName || "_all_docs").replace(/^_design\//, "_view/");
if (viewPath != "_slow_view" && viewPath != "_design_docs") {
diff --git a/share/www/document.html b/share/www/document.html
index 922a651a..c19e2e98 100644
--- a/share/www/document.html
+++ b/share/www/document.html
@@ -23,6 +23,7 @@ specific language governing permissions and limitations under the License.
<script src="script/jquery.cookies.js?0.9.0"></script>
<script src="script/jquery.couch.js?0.9.0"></script>
<script src="script/jquery.dialog.js?0.9.0"></script>
+ <script src="script/jquery.editinline.js?0.9.0"></script>
<script src="script/jquery.form.js?0.9.0"></script>
<script src="script/jquery.resizer.js?0.9.0"></script>
<script src="script/futon.js?0.9.0"></script>
diff --git a/share/www/script/futon.browse.js b/share/www/script/futon.browse.js
index 492202d1..832ac1b7 100644
--- a/share/www/script/futon.browse.js
+++ b/share/www/script/futon.browse.js
@@ -109,6 +109,7 @@
} else {
viewName = $.cookies.get(dbName + ".view", "");
if (viewName) {
+ this.redirecting = true;
location.href = "database.html?" + dbName + "/" + viewName;
}
}
@@ -596,7 +597,7 @@
page.doc[fieldName] = null;
var row = _addRowForField(page.doc, fieldName);
page.isDirty = true;
- _editKey(page.doc, row.find("th"), fieldName);
+ row.find("th b").dblclick();
}
var _sortFields = function(a, b) {
@@ -694,6 +695,10 @@
this.saveDocument = function() {
$(document.body).addClass("loading");
db.saveDoc(page.doc, {
+ error: function(status, error, reason) {
+ alert("Error: " + error + "\n\n" + reason);
+ $(document.body).removeClass("loading");
+ },
success: function(resp) {
page.isDirty = false;
location.href = "?" + encodeURIComponent(dbName) +
@@ -740,167 +745,123 @@
}
function _addRowForField(doc, fieldName) {
- var row = $("<tr><th></th><td></td></tr>").find("th").append($("<b></b>")
- .text(fieldName)).end().appendTo("#fields tbody.content");
+ 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]));
+ row.find("td").append(_renderAttachmentList(doc[fieldName]));
} else {
- var value = _renderValue(doc[fieldName]);
- row
- .find("th b").dblclick(function() {
- _editKey(doc, this, $(this).text());
- }).end()
- .find("td").append(value).dblclick(function() {
- _editValue(doc, this, $(this).prev("th").text());
- }).end();
- if (fieldName != "_id" && fieldName != "_rev") {
- row.find("th, td").attr("title", "Double click to edit");
- _initKey(doc, row, fieldName);
- _initValue(value);
- }
+ 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 _editKey(doc, cell, fieldName) {
- if (fieldName == "_id" || fieldName == "_rev") return;
- var th = $(cell);
- th.empty();
- var input = $("<input type='text' spellcheck='false'>");
- input.dblclick(function() { return false; }).keydown(function(evt) {
- switch (evt.keyCode) {
- case 13: applyChange(); break;
- case 27: cancelChange(); break;
- }
- });
- var tools = $("<div class='tools'></div>");
- 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($("<b></b>").text(newName));
- _initKey(doc, th.parent("tr"), newName);
- page.isDirty = true;
- }
- function cancelChange() {
- th.children().remove();
- th.append($("<b></b>").text(fieldName));
- _initKey(doc, th.parent("tr"), fieldName);
+ function _initKey(doc, row, fieldName) {
+ if (fieldName == "_id" || fieldName == "_rev") {
+ return;
}
- $("<button type='button' class='apply'></button>").click(function() {
- applyChange();
- }).appendTo(tools);
- $("<button type='button' class='cancel'></button>").click(function() {
- cancelChange();
- }).appendTo(tools);
- tools.appendTo(th);
- input.val(fieldName).appendTo(th);
- input.each(function() { this.focus(); this.select(); });
- }
+ var cell = row.find("th");
- function _editValue(doc, cell, fieldName) {
- if (!fieldName || 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 = $("<textarea rows='8' cols='40' spellcheck='false'></textarea>");
- } else {
- var input = $("<input type='text' spellcheck='false'>");
- }
- input.dblclick(function() { return false; }).keydown(function(evt) {
- switch (evt.keyCode) {
- case 13: if (!needsTextarea) applyChange(); break;
- case 27: cancelChange(); break;
- }
- });
- var tools = $("<div class='tools'></div>");
- 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();
+ $("<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;
- 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\").";
+ },
+ 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();
}
- $("<div class='error'></div>").text(msg).insertAfter(input);
}
- }
- function cancelChange() {
- td.children().remove();
- var value = _renderValue(doc[fieldName]);
- td.append(value);
- _initValue(value);
- }
-
- $("<button type='button' class='apply' title='Apply change'></button>").click(function() {
- applyChange();
- }).appendTo(tools);
- $("<button type='button' class='cancel' title='Revert change'></button>").click(function() {
- cancelChange();
- }).appendTo(tools);
- tools.appendTo(td);
- input.val($.futon.formatJSON(value)).appendTo(td);
- input.each(function() { this.focus(); this.select(); });
- if (needsTextarea) input.makeResizable({vertical: true});
+ });
}
- function _initKey(doc, row, fieldName) {
- if (fieldName != "_id" && fieldName != "_rev") {
- $("<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(row.find("th"));
+ function _initValue(doc, row, fieldName) {
+ if (fieldName == "_id" || fieldName == "_rev") {
+ return;
}
- }
- function _initValue(value) {
- value.find("dd: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();
+ row.find("td").makeEditable({allowEmpty: true,
+ createInput: function(value) {
+ if ($("dl", this).length > 0 || $("code", this).text().length > 60) {
+ return $("<textarea rows='8' cols='40' spellcheck='false'></textarea>");
+ }
+ return $("<input type='text' spellcheck='false'>");
+ },
+ prepareInput: function(input) {
+ if ($(input).is("textarea")) {
+ $(input).makeResizable({vertical: true});
+ }
+ },
+ accept: function(newValue) {
+ doc[row.data("name")] = JSON.parse(newValue);
+ $(this).children().remove();
+ page.isDirty = true;
+ var value = _renderValue(doc[row.data("name")]);
+ $(this).append(value);
+ },
+ populate: function(value) {
+ return $.futon.formatJSON(doc[row.data("name")]);
+ },
+ validate: function(value) {
+ try {
+ JSON.parse(value);
+ return true;
+ } catch (err) {
+ var msg = err.message;
+ if (msg == "parseJSON") {
+ msg = "Please enter a valid JSON value (for example, \"string\").";
+ }
+ $("<div class='error'></div>").text(msg).appendTo(this);
+ return false;
+ }
+ }
});
}
function _renderValue(value) {
- var type = typeof(value);
- if (type == "object" && value !== null) {
- var list = $("<dl></dl>");
- for (var i in value) {
- if (!value.hasOwnProperty(i)) continue;
- $("<dt></dt>").text(i).appendTo(list);
- $("<dd></dd>").append(_renderValue(value[i])).appendTo(list);
+ function render(val) {
+ var type = typeof(val);
+ if (type == "object" && val !== null) {
+ var list = $("<dl></dl>");
+ for (var i in val) {
+ if (!value.hasOwnProperty(i)) continue;
+ $("<dt></dt>").text(i).appendTo(list);
+ $("<dd></dd>").append(_renderValue(val[i])).appendTo(list);
+ }
+ return list;
+ } else {
+ return $($.futon.formatJSON(val, {html: true}));
}
- return list;
- } else {
- return $("<code></code>").addClass(type).text(
- value !== null ? JSON.stringify(value) : "null"
- );
}
+ 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) {
diff --git a/share/www/script/futon.format.js b/share/www/script/futon.format.js
index b8870a94..028119ed 100644
--- a/share/www/script/futon.format.js
+++ b/share/www/script/futon.format.js
@@ -16,13 +16,11 @@
// JSON pretty printing
formatJSON: function(val, options) {
- options = options || {};
- if (options.indent === undefined) {
- options.indent = 4;
- }
- options.indent = options.indent !== undefined ? options.indent : 4;
- options.linesep = options.linesep !== undefined ? options.linesep : "\n";
- options.quoteKeys = options.quoteKeys !== undefined ? options.quoteKeys : true;
+ options = $.extend({
+ indent: 4,
+ linesep: "\n",
+ quoteKeys: true
+ }, options || {});
var itemsep = options.linesep.length ? "," + options.linesep : ", ";
function escape(string) {
diff --git a/share/www/script/jquery.editinline.js b/share/www/script/jquery.editinline.js
new file mode 100644
index 00000000..bd91e7e1
--- /dev/null
+++ b/share/www/script/jquery.editinline.js
@@ -0,0 +1,104 @@
+// 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.makeEditable = function(options) {
+ options = $.extend({
+ allowEmpty: true,
+ acceptLabel: "",
+ cancelLabel: "",
+ toolTip: "Double click to edit",
+
+ // callbacks
+ begin: function() { return true },
+ accept: function(newValue, oldValue) {},
+ cancel: function(oldValue) {},
+ createInput: function(value) { return $("<input type='text'>") },
+ prepareInput: function(input) {},
+ end: function(keyCode) {},
+ populate: function(value) { return value },
+ validate: function() { return true }
+ }, options || {});
+
+ return this.each(function() {
+ var elem = $(this);
+ elem.attr("title", options.toolTip).dblclick(function() {
+ var oldHtml = elem.html();
+ var oldText = options.populate($.trim(elem.text()));
+
+ if (!options.begin.apply(elem[0], [oldText])) {
+ return;
+ }
+
+ var input = options.createInput.apply(elem[0], [oldText])
+ .addClass("editinline").val(oldText)
+ .dblclick(function() { return false; })
+ .keydown(function(evt) {
+ switch (evt.keyCode) {
+ case 13: { // return
+ if (!input.is("textarea")) applyChange(evt.keyCode);
+ break;
+ }
+ case 27: { // escape
+ cancelChange(evt.keyCode);
+ break;
+ }
+ case 9: { // tab
+ if (!input.is("textarea")) {
+ applyChange(evt.keyCode);
+ return false;
+ }
+ }
+ }
+ });
+
+ function applyChange(keyCode) {
+ var newText = input.val();
+ if (newText == oldText) {
+ cancelChange(keyCode);
+ return true;
+ }
+ if ((!options.allowEmpty && !newText.length) ||
+ !options.validate.apply(elem[0], [newText])) {
+ input.addClass("invalid");
+ return false;
+ }
+ input.remove();
+ tools.remove();
+ options.accept.apply(elem[0], [newText, oldText]);
+ elem.removeClass("editinline-container")
+ options.end.apply(elem[0], [keyCode]);
+ return true;
+ }
+
+ function cancelChange(keyCode) {
+ options.cancel.apply(elem[0], [oldText]);
+ elem.html(oldHtml).removeClass("editinline-container");
+ options.end.apply(elem[0], [keyCode]);
+ }
+
+ var tools = $("<span class='editinline-tools'></span>");
+ $("<button type='button' class='apply'></button>")
+ .text(options.acceptLabel).click(applyChange).appendTo(tools);
+ $("<button type='button' class='cancel'></button>")
+ .text(options.cancelLabel).click(cancelChange).appendTo(tools)
+
+ elem.html("").append(tools).append(input)
+ .addClass("editinline-container");
+ options.prepareInput.apply(elem[0], [input[0]]);
+ input.each(function() { this.focus(); this.select(); });
+ });
+ });
+ }
+
+})(jQuery);
diff --git a/share/www/style/layout.css b/share/www/style/layout.css
index 67f9e430..18aefa63 100644
--- a/share/www/style/layout.css
+++ b/share/www/style/layout.css
@@ -106,7 +106,7 @@ table.listing tbody th :link, table.listing tbody th :visited {
}
table.listing tbody.content th button {
background: transparent no-repeat; border: none; cursor: pointer;
- float: left; margin: 0 5px 0 -20px; padding: 0; width: 15px; height: 15px;
+ float: left; margin: .2em 5px 0 -20px; padding: 0; width: 15px; height: 15px;
}
table.listing tbody.content th button:hover { background-position: -15px 0; }
table.listing tbody.footer tr td { background: #e9e9e9;
@@ -360,13 +360,16 @@ ul.suggest-dropdown li.selected { cursor: pointer; background: Highlight;
#fields col.field { width: 33%; }
#fields tbody.content th { padding-left: 25px; padding-right: 48px; }
#fields tbody.content th button {
- background-image: url(../image/delete-mini.png);
+ background-image: url(../image/delete-mini.png);;
}
-#fields tbody.content th b { display: block; padding: 2px; }
+#fields tbody.content th b { display: block; padding: 2px 2px 2px 3px; }
+#fields tbody.content th b.editinline-container { padding: 0; }
#fields tbody.content td { color: #999; padding-left: 14px;
padding-right: 48px;
}
-#fields tbody.content td code { display: block; font-size: 11px; padding: 2px; }
+#fields tbody.content td code { display: block; font-size: 11px;
+ padding: 2px 2px 2px 3px;
+}
#fields tbody.content td dl { margin: 0; padding: 0; }
#fields tbody.content td dt {
background: transparent url(../image/toggle-collapse.gif) 0 3px no-repeat;
@@ -391,25 +394,31 @@ ul.suggest-dropdown li.selected { cursor: pointer; background: Highlight;
font-weight: bold;
}
#fields tbody.content td input, #fields tbody.content td textarea {
- font: 10px normal "DejaVu Sans Mono",Monaco,monospace;
+ font: 11px normal "DejaVu Sans Mono",Monaco,monospace;
}
#fields tbody.content input.invalid,
#fields tbody.content textarea.invalid {
background: #f9f4f4; border-color: #b66 #ebb #ebb #b66;
}
-#fields tbody.content div.tools { margin: 2px 2px 0; float: right;
+#fields tbody.content div.grippie { padding: 0 1px; width: 100%; }
+
+#fields tbody.content span.editinline-tools { margin: 2px 2px 0; float: right;
margin-right: -45px;
}
-#fields tbody.content div.tools button { background: transparent 0 0 no-repeat;
- border: none; cursor: pointer; display: block; float: left; margin: 0 .2em;
- width: 11px; height: 11px;
+#fields tbody.content span.editinline-tools button {
+ background: transparent 0 0 no-repeat; border: none; cursor: pointer;
+ display: block; float: left; margin: 0 .2em; width: 11px; height: 11px;
+}
+#fields tbody.content span.editinline-tools button:hover {
+ background-position: 0 -22px;
+}
+#fields tbody.content span.editinline-tools button:active {
+ background-position: 0 -44px;
}
-#fields tbody.content div.tools button:hover { background-position: 0 -22px; }
-#fields tbody.content div.tools button:active { background-position: 0 -44px; }
-#fields tbody.content div.tools button.apply {
+#fields tbody.content span.editinline-tools button.apply {
background-image: url(../image/apply.gif);
}
-#fields tbody.content div.tools button.cancel {
+#fields tbody.content span.editinline-tools button.cancel {
background-image: url(../image/cancel.gif);
}
#fields tbody.content div.error { color: #d33; }