From 75ab4ae3e4a423c0f85cd87a2237453fd43bfba8 Mon Sep 17 00:00:00 2001 From: Christopher Lenz Date: Wed, 14 Jan 2009 13:41:32 +0000 Subject: 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 --- share/www/database.html | 1 + share/www/document.html | 1 + share/www/script/futon.browse.js | 239 ++++++++++++++-------------------- share/www/script/futon.format.js | 12 +- share/www/script/jquery.editinline.js | 104 +++++++++++++++ share/www/style/layout.css | 35 +++-- 6 files changed, 233 insertions(+), 159 deletions(-) create mode 100644 share/www/script/jquery.editinline.js (limited to 'share/www') 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. + 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 = $("").find("th").append($("") - .text(fieldName)).end().appendTo("#fields tbody.content"); + var row = $("") + .find("th").append($("").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.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(doc, th.parent("tr"), newName); - page.isDirty = true; - } - function cancelChange() { - th.children().remove(); - th.append($("").text(fieldName)); - _initKey(doc, th.parent("tr"), fieldName); + function _initKey(doc, row, fieldName) { + if (fieldName == "_id" || fieldName == "_rev") { + return; } - $("").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(); }); - } + 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 = $(""); - } 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(); + $("").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(); } - $("
").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($.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") { - $("").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 $(""); + } + return $(""); + }, + 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\")."; + } + $("
").text(msg).appendTo(this); + return false; + } + } }); } 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); + function render(val) { + var type = typeof(val); + if (type == "object" && val !== null) { + var list = $("
"); + for (var i in val) { + if (!value.hasOwnProperty(i)) continue; + $("
").text(i).appendTo(list); + $("
").append(_renderValue(val[i])).appendTo(list); + } + return list; + } else { + return $($.futon.formatJSON(val, {html: true})); } - return list; - } else { - return $("").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 $("") }, + 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 = $(""); + $("") + .text(options.acceptLabel).click(applyChange).appendTo(tools); + $("") + .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; } -- cgit v1.2.3