You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by cm...@apache.org on 2009/01/14 14:41:58 UTC

svn commit: r734390 - in /couchdb/trunk/share: Makefile.am www/database.html www/document.html www/script/futon.browse.js www/script/futon.format.js www/script/jquery.editinline.js www/style/layout.css

Author: cmlenz
Date: Wed Jan 14 05:41:32 2009
New Revision: 734390

URL: http://svn.apache.org/viewvc?rev=734390&view=rev
Log:
Refactor the inline editing Javascript code in Futon to make it reusable.

Added:
    couchdb/trunk/share/www/script/jquery.editinline.js
Modified:
    couchdb/trunk/share/Makefile.am
    couchdb/trunk/share/www/database.html
    couchdb/trunk/share/www/document.html
    couchdb/trunk/share/www/script/futon.browse.js
    couchdb/trunk/share/www/script/futon.format.js
    couchdb/trunk/share/www/style/layout.css

Modified: couchdb/trunk/share/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/Makefile.am?rev=734390&r1=734389&r2=734390&view=diff
==============================================================================
--- couchdb/trunk/share/Makefile.am (original)
+++ couchdb/trunk/share/Makefile.am Wed Jan 14 05:41:32 2009
@@ -65,6 +65,7 @@
     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 \

Modified: couchdb/trunk/share/www/database.html
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/database.html?rev=734390&r1=734389&r2=734390&view=diff
==============================================================================
--- couchdb/trunk/share/www/database.html [utf-8] (original)
+++ couchdb/trunk/share/www/database.html [utf-8] Wed Jan 14 05:41:32 2009
@@ -37,6 +37,7 @@
       });
 
       $(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") {

Modified: couchdb/trunk/share/www/document.html
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/document.html?rev=734390&r1=734389&r2=734390&view=diff
==============================================================================
--- couchdb/trunk/share/www/document.html [utf-8] (original)
+++ couchdb/trunk/share/www/document.html [utf-8] Wed Jan 14 05:41:32 2009
@@ -23,6 +23,7 @@
     <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>

Modified: couchdb/trunk/share/www/script/futon.browse.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/futon.browse.js?rev=734390&r1=734389&r2=734390&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/futon.browse.js [utf-8] (original)
+++ couchdb/trunk/share/www/script/futon.browse.js [utf-8] Wed Jan 14 05:41:32 2009
@@ -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];
+      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];
-          th.children().remove();
-          th.append($("<b></b>").text(newName));
-          _initKey(doc, th.parent("tr"), newName);
+          row.remove();
           page.isDirty = true;
-        }
-        function cancelChange() {
-          th.children().remove();
-          th.append($("<b></b>").text(fieldName));
-          _initKey(doc, th.parent("tr"), fieldName);
-        }
-
-        $("<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(); });
-      }
-
-      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;
+          $("#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();
+            }
           }
         });
-        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();
-            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\").";
-            }
-            $("<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) {

Modified: couchdb/trunk/share/www/script/futon.format.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/futon.format.js?rev=734390&r1=734389&r2=734390&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/futon.format.js [utf-8] (original)
+++ couchdb/trunk/share/www/script/futon.format.js [utf-8] Wed Jan 14 05:41:32 2009
@@ -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) {

Added: couchdb/trunk/share/www/script/jquery.editinline.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/jquery.editinline.js?rev=734390&view=auto
==============================================================================
--- couchdb/trunk/share/www/script/jquery.editinline.js (added)
+++ couchdb/trunk/share/www/script/jquery.editinline.js Wed Jan 14 05:41:32 2009
@@ -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);

Modified: couchdb/trunk/share/www/style/layout.css
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/style/layout.css?rev=734390&r1=734389&r2=734390&view=diff
==============================================================================
--- couchdb/trunk/share/www/style/layout.css (original)
+++ couchdb/trunk/share/www/style/layout.css Wed Jan 14 05:41:32 2009
@@ -106,7 +106,7 @@
 }
 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 @@
 #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 @@
   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 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 {
+  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 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; }