Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 4B341E898 for ; Mon, 11 Feb 2013 11:12:17 +0000 (UTC) Received: (qmail 48700 invoked by uid 500); 11 Feb 2013 11:12:17 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 48468 invoked by uid 500); 11 Feb 2013 11:12:15 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 48290 invoked by uid 99); 11 Feb 2013 11:12:15 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 11 Feb 2013 11:12:14 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 8C5E93C734; Mon, 11 Feb 2013 11:12:14 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: jan@apache.org To: commits@couchdb.apache.org X-Mailer: ASF-Git Admin Mailer Subject: [40/50] [abbrv] git commit: Initial view editor functionality Message-Id: <20130211111214.8C5E93C734@tyr.zones.apache.org> Date: Mon, 11 Feb 2013 11:12:14 +0000 (UTC) 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/43dab9b6 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/43dab9b6 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/43dab9b6 Branch: refs/heads/fauxton Commit: 43dab9b6e808bbead39175d1d9031cce323dfc15 Parents: 6ab9c65 Author: Russell Branca Authored: Tue Feb 5 17:09:48 2013 -0800 Committer: Russell Branca Committed: Tue Feb 5 17:11:18 2013 -0800 ---------------------------------------------------------------------- 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/43dab9b6/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/43dab9b6/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 efaa374..19897e9 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 @@ -260,6 +301,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/43dab9b6/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 30a799a..41d0d43 100644 --- a/src/fauxton/app/modules/documents/views.js +++ b/src/fauxton/app/modules/documents/views.js @@ -472,16 +472,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.", @@ -574,11 +572,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" @@ -605,11 +775,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/43dab9b6/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 @@
New doc
- + New view

http://git-wip-us.apache.org/repos/asf/couchdb/blob/43dab9b6/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 @@ +
+
+
+ + Warning! Preview executes the Map/Reduce functions in your browser, and may behave differently from CouchDB. +
+
+
+

Define your index

+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + Reduce functions are optional. +
+
+
+ +
+ +
+
+
+
+
+ + + +
+
+
+
+
http://git-wip-us.apache.org/repos/asf/couchdb/blob/43dab9b6/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/43dab9b6/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; +}