couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From chewbra...@apache.org
Subject [9/50] git commit: Initial view editor functionality
Date Fri, 15 Mar 2013 21:50:08 GMT
Initial view editor functionality


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/b76c0288
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/b76c0288
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/b76c0288

Branch: refs/heads/fauxton-rebase
Commit: b76c028817474e8aa4767d683bac6e7a0f2c7168
Parents: 206fd49
Author: Russell Branca <chewbranca@gmail.com>
Authored: Tue Feb 5 17:09:48 2013 -0800
Committer: Russell Branca <chewbranca@gmail.com>
Committed: Fri Mar 15 14:35:12 2013 -0700

----------------------------------------------------------------------
 src/fauxton/app/api.js                             |   10 +
 src/fauxton/app/modules/documents/routes.js        |   42 +++
 src/fauxton/app/modules/documents/views.js         |  197 +++++++++++++--
 src/fauxton/app/templates/documents/sidebar.html   |    4 +-
 .../app/templates/documents/view_editor.html       |   65 +++++
 src/fauxton/assets/css/cloudant-additions.css      |    8 -
 src/fauxton/assets/less/cloudant.less              |   22 ++
 7 files changed, 322 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/b76c0288/src/fauxton/app/api.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/api.js b/src/fauxton/app/api.js
index 7aebd37..9ec2894 100644
--- a/src/fauxton/app/api.js
+++ b/src/fauxton/app/api.js
@@ -17,6 +17,16 @@ function(app, Fauxton) {
     initialize: function() {}
   };
 
+  // List of JSHINT errors to ignore
+  // Gets around problem of anonymous functions not being a valid statement
+  FauxtonAPI.excludedViewErrors = [
+    "Missing name in function declaration."
+  ];
+
+  FauxtonAPI.isIgnorableError = function(msg) {
+    return _.contains(FauxtonAPI.excludedViewErrors, msg);
+  };
+
   FauxtonAPI.View = Backbone.View.extend({
     // This should return an array of promises, an empty array, or null
     establish: function() {

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b76c0288/src/fauxton/app/modules/documents/routes.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/modules/documents/routes.js b/src/fauxton/app/modules/documents/routes.js
index dfa208f..ab66670 100644
--- a/src/fauxton/app/modules/documents/routes.js
+++ b/src/fauxton/app/modules/documents/routes.js
@@ -59,6 +59,47 @@ function(app, FauxtonAPI, Documents, Databases) {
     };
   };
 
+  var newViewEditorCallback = function(databaseName) {
+    var data = {
+      database: new Databases.Model({id:databaseName})
+    };
+    data.designDocs = new Documents.AllDocs(null, {
+      database: data.database,
+      params: {startkey: '"_design"',
+               endkey: '"_design1"',
+               include_docs: true}
+    });
+
+    return {
+      layout: "with_tabs_sidebar",
+
+      data: data,
+
+      crumbs: [
+        {"name": "Databases", "link": "/_all_dbs"},
+        {"name": data.database.id, "link": Databases.databaseUrl(data.database)}
+      ],
+
+      views: {
+        "#sidebar-content": new Documents.Views.Sidebar({
+          collection: data.designDocs
+        }),
+
+        "#tabs": new Documents.Views.Tabs({
+          collection: data.designDocs,
+          database: data.database.id
+        }),
+
+        "#dashboard-content": new Documents.Views.ViewEditor({
+          model: data.database,
+          ddocs: data.designDocs
+        })
+      },
+
+      apiUrl: data.database.url()
+    };
+  };
+
   // HACK: this kind of works
   // Basically need a way to share state between different routes, for
   // instance making a new doc won't work for switching back and forth
@@ -295,6 +336,7 @@ function(app, FauxtonAPI, Documents, Databases) {
     },
 
     "database/:database/new": newDocCodeEditorCallback,
+    "database/:database/new_view": newViewEditorCallback,
 
     // TODO: fix optional search params
     // Can't get ":view(?*search)" to work

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b76c0288/src/fauxton/app/modules/documents/views.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/modules/documents/views.js b/src/fauxton/app/modules/documents/views.js
index 0d6eb7e..d41d53e 100644
--- a/src/fauxton/app/modules/documents/views.js
+++ b/src/fauxton/app/modules/documents/views.js
@@ -507,16 +507,14 @@ function(app, FauxtonAPI, Codemirror, JSHint) {
         json = JSON.parse(this.editor.getValue());
         this.model.set(json);
         notification = FauxtonAPI.addNotification({msg: "Saving document."});
-        this.model.save().error(
-          function(xhr) {
-            var responseText = JSON.parse(xhr.responseText).reason;
-            notification = FauxtonAPI.addNotification({
-              msg: "Save failed: " + responseText,
-              type: "error",
-              clear: true
-            });
-          }
-        );
+        this.model.save().error(function(xhr) {
+          var responseText = JSON.parse(xhr.responseText).reason;
+          notification = FauxtonAPI.addNotification({
+            msg: "Save failed: " + responseText,
+            type: "error",
+            clear: true
+          });
+        });
       } else {
         notification = FauxtonAPI.addNotification({
           msg: "Please fix the JSON errors and try again.",
@@ -609,11 +607,183 @@ function(app, FauxtonAPI, Codemirror, JSHint) {
     }
   });
 
+  Views.ViewEditor = FauxtonAPI.View.extend({
+    template: "templates/documents/view_editor",
+
+    events: {
+      "click button.save": "saveView",
+      "change select#reduce-function-selector": "updateReduce"
+    },
+
+    langTemplates: {
+      "javascript": {
+        map: "function(doc) {\n  emit(null, doc);\n}",
+        reduce: "function(keys, values, rereduce){\n  if (rereduce){\n    return sum(values);\n
 } else {\n    return values.length;\n  }\n}"
+      }
+    },
+
+    defaultLang: "javascript",
+
+    initialize: function(options) {
+      this.ddocs = options.ddocs;
+    },
+
+    updateValues: function() {
+      var notification;
+      if (this.model.changedAttributes()) {
+        notification = FauxtonAPI.addNotification({
+          msg: "Document saved successfully.",
+          type: "success",
+          clear: true
+        });
+        this.editor.setValue(this.model.prettyJSON());
+      }
+    },
+
+    updateReduce: function(event) {
+      var $ele = $("#reduce-function-selector");
+      var $reduceContainer = $(".control-group.reduce-function");
+      if ($ele.val() == "CUSTOM") {
+        $reduceContainer.show();
+      } else {
+        $reduceContainer.hide();
+      }
+    },
+
+    establish: function() {
+      return [this.ddocs.fetch(), this.model.fetch()];
+    },
+
+    saveView: function(event) {
+      var json, notification;
+      if (this.hasValidCode()) {
+        var mapVal = this.mapEditor.getValue();
+        var reduceVal = this.reduceEditor.getValue();
+        notification = FauxtonAPI.addNotification({
+          msg: "Saving document.",
+          selector: "#define-view .errors-container"
+        });
+        /*
+        this.model.save().error(function(xhr) {
+          var responseText = JSON.parse(xhr.responseText).reason;
+          notification = FauxtonAPI.addNotification({
+            msg: "Save failed: " + responseText,
+            type: "error",
+            clear: true
+          });
+        });
+        */
+      } else {
+        notification = FauxtonAPI.addNotification({
+          msg: "Please fix the JSON errors and try again.",
+          type: "error",
+          selector: "#define-view .errors-container"
+        });
+      }
+    },
+
+    isCustomReduceEnabled: function() {
+      return $("#reduce-function-selector").val() == "CUSTOM";
+    },
+
+    reduceVal: function() {
+    },
+
+    hasValidCode: function() {
+      return _.every(["mapEditor", "reduceEditor"], function(editorName) {
+        var editor = this[editorName];
+        if (editorName == "reduceEditor" && ! this.isCustomReduceEnabled()) {
+          return true;
+        } else if (JSHINT(editor.getValue()) !== false) {
+          return true;
+        } else {
+          // By default CouchDB view functions don't pass lint
+          return _.every(JSHINT.errors, function(error) {
+            return FauxtonAPI.isIgnorableError(error.reason);
+          });
+        }
+      }, this);
+    },
+
+    runJSHint: function(editorName) {
+      var editor = this[editorName];
+      var json = editor.getValue();
+      var output = JSHint(json);
+
+      // Clear existing markers
+      for (var i = 0, l = editor.lineCount(); i < l; i++) {
+        editor.clearMarker(i);
+      }
+
+      if (output === false) {
+        _.map(JSHint.errors, function(error) {
+          // By default CouchDB view functions don't pass lint
+          if (FauxtonAPI.isIgnorableError(error.reason)) return true;
+
+          var line = error.line - 1;
+          var className = "view-code-error-line-" + line;
+          editor.setMarker(line, "‚óŹ", "view-code-error "+className);
+
+          setTimeout(function() {
+            $(".CodeMirror ."+className).tooltip({
+              title: "ERROR: " + error.reason
+            });
+          }, 0);
+        }, this);
+      }
+    },
+
+    serialize: function() {
+      return {
+        database: this.model,
+        ddocs: this.ddocs
+      };
+    },
+
+    afterRender: function() {
+      this.model.on("sync", this.updateValues, this);
+      var that = this;
+      var mapFun = $("#map-function");
+      mapFun.val(this.langTemplates[this.defaultLang].map);
+      var reduceFun = $("#reduce-function");
+      reduceFun.val(this.langTemplates[this.defaultLang].reduce);
+      this.mapEditor = Codemirror.fromTextArea(mapFun.get()[0], {
+        mode: "javascript",
+        lineNumbers: true,
+        matchBrackets: true,
+        lineWrapping: true,
+        onChange: function() {
+          that.runJSHint("mapEditor");
+        },
+        extraKeys: {
+          "Ctrl-S": function(instance) { that.saveDoc(); },
+          "Ctrl-/": "undo"
+        }
+      });
+      this.reduceEditor = Codemirror.fromTextArea(reduceFun.get()[0], {
+        mode: "javascript",
+        lineNumbers: true,
+        matchBrackets: true,
+        lineWrapping: true,
+        onChange: function() {
+          that.runJSHint("reduceEditor");
+        },
+        extraKeys: {
+          "Ctrl-S": function(instance) { that.saveDoc(); },
+          "Ctrl-/": "undo"
+        }
+      });
+      // HACK: this should be in the html
+      // but CodeMirror's head explodes and it won't set the hight properly.
+      // So render it first, set the editor, then hide.
+      $(".control-group.reduce-function").hide();
+    }
+  });
+
   Views.Sidebar = FauxtonAPI.View.extend({
     template: "templates/documents/sidebar",
     events: {
       "click a.new#index": "newIndex",
-      "click .nav-list.views a.new": "showNew",
       // "click .nav-list.views a.toggle-view": "toggleView",
       "click .nav-list a.toggle-view#all-docs": "toggleView",
       "click .nav-list a.toggle-view#design-docs": "toggleView"
@@ -640,11 +810,6 @@ function(app, FauxtonAPI, Codemirror, JSHint) {
       alert('coming soon');
     },
 
-    showNew: function(event){
-      event.preventDefault();
-      alert('show new view dialog');
-    },
-
     toggleView: function(event){
       alert('filter data by search/view/type');
       event.preventDefault();

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b76c0288/src/fauxton/app/templates/documents/sidebar.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/templates/documents/sidebar.html b/src/fauxton/app/templates/documents/sidebar.html
index b1e07b5..13f4c56 100644
--- a/src/fauxton/app/templates/documents/sidebar.html
+++ b/src/fauxton/app/templates/documents/sidebar.html
@@ -1,7 +1,7 @@
 <div id="sidenav">
   <a class="btn btn-small new" id="doc" href="#<%= database.url('app') %>/new"><i
class="icon-file"></i> New doc</a>
   <div class="btn-group" id="new-index">
-    <button class="btn btn-small" href="#">New view</button>
+    <a class="btn btn-small" href="#<%= database.url('app') %>/new_view">New
view</a>
   </div>
   <hr>
   <ul class="nav nav-list">
@@ -10,6 +10,6 @@
   </ul>
   <ul class="nav nav-list views">
     <li class="nav-header">Secondary Indexes</li>
-    <li><a href="#new-view-index" class="new"><i class="icon-plus"></i>
New</a></li>
+    <li><a href="#<%= database.url('app') %>/new_view" class="new"><i
class="icon-plus"></i> New</a></li>
   </ul>
 </div>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b76c0288/src/fauxton/app/templates/documents/view_editor.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/templates/documents/view_editor.html b/src/fauxton/app/templates/documents/view_editor.html
new file mode 100644
index 0000000..5be9222
--- /dev/null
+++ b/src/fauxton/app/templates/documents/view_editor.html
@@ -0,0 +1,65 @@
+<div id="define-view" class="ddoc-alert well">
+  <div class="errors-container">
+    <div class="alert">
+      <button type="button" class="close" data-dismiss="alert">&times;</button>
+      <strong>Warning!</strong> Preview executes the Map/Reduce functions in
your browser, and may behave differently from CouchDB.
+    </div>
+  </div>
+  <form class="form-horizontal">
+    <h3>Define your index</h3>
+    <div class="control-group">
+      <label class="control-label" for="ddoc">Design document <a target="_couch_docs"
href="http://docs.couchdb.org/en/latest/ddocs/#design-docs"><i class="icon-question-sign"></i></a></label>
+      <div class="controls">
+        <select id="ddoc">
+          <optgroup label="Select a document">
+            <option>New document</option>
+            <% ddocs.each(function(ddoc) { %>
+              <option><%= ddoc.id %></option>
+            <% }); %>
+            <option selected="selected">_design/views101</option>
+          </optgroup>
+        </select>
+      </div>
+    </div>
+    <div class="control-group">
+      <label class="control-label" for="index-name">Index name <a target="_couch_docs"
href="http://docs.couchdb.org/en/latest/ddocs/#view-functions"><i class="icon-question-sign"></i></a></label>
+      <div class="controls">
+        <input type="text" id="index-name" value="" placeholder="Index name" />
+      </div>
+    </div>
+    <div class="control-group">
+      <label class="control-label" for="map-function">Map function <a target="_couch_docs"
href="http://docs.couchdb.org/en/latest/ddocs/#map-functions"><i class="icon-question-sign"></i></a></label>
+      <div class="controls">
+        <textarea class="js-editor" id="map-function"></textarea>
+      </div>
+    </div>
+    <div class="control-group">
+      <label class="control-label" for="reduce-function-selector">Reduce function <a
target="_couch_docs" href="http://docs.couchdb.org/en/latest/ddocs/#reduce-and-rereduce-functions"><i
class="icon-question-sign"></i></a></label>
+      <div class="controls">
+        <select id="reduce-function-selector">
+          <option value="" selected="selected">None</option>
+          <option value="_sum">_sum</option>
+          <option value="_count">_count</option>
+          <option value="_stats">_stats</option>
+          <option value="CUSTOM">Custom reduce</option>
+        </select>
+        <span class="help-block">Reduce functions are optional.</span>
+      </div>
+    </div>
+    <div class="control-group reduce-function">
+      <label class="control-label" for="reduce-function">Custom Reduce</label>
+      <div class="controls">
+        <textarea class="js-editor" id="reduce-function"></textarea>
+      </div>
+    </div>
+    <div class="control-group">
+      <hr />
+      <div class="controls">
+        <button class="btn btn-small btn-inverse cancel">Cancel</button>
+        <button class="btn btn-small btn-info preview">Preview</button>
+        <button class="btn btn-primary save">Save</button>
+      </div>
+    </div>
+    <div class="clearfix"></div>
+  </form>
+</div>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b76c0288/src/fauxton/assets/css/cloudant-additions.css
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/css/cloudant-additions.css b/src/fauxton/assets/css/cloudant-additions.css
deleted file mode 100644
index 7acfd46..0000000
--- a/src/fauxton/assets/css/cloudant-additions.css
+++ /dev/null
@@ -1,8 +0,0 @@
-pre.view-code-error {
-    color: red;
-}
-
-.CodeMirror-scroll {
-    height: auto;
-    overflow-y: visible;
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b76c0288/src/fauxton/assets/less/cloudant.less
----------------------------------------------------------------------
diff --git a/src/fauxton/assets/less/cloudant.less b/src/fauxton/assets/less/cloudant.less
index c91e14a..deae5e0 100644
--- a/src/fauxton/assets/less/cloudant.less
+++ b/src/fauxton/assets/less/cloudant.less
@@ -58,3 +58,25 @@
   // This function is defined in mixins
   .nav-divider(transparent, @white);
 }
+
+
+// Misc
+// ------
+
+pre.view-code-error {
+    color: red !important; // yuck
+}
+
+.CodeMirror-scroll {
+    height: auto;
+    overflow-y: visible;
+}
+
+#define-view form textarea.js-editor {
+  width: 95%;
+}
+
+#define-view .CodeMirror-scroll {
+  height: auto;
+  min-height: 50px;
+}


Mime
View raw message