diff options
author | Russell Branca <chewbranca@gmail.com> | 2013-03-05 16:41:47 -0800 |
---|---|---|
committer | Russell Branca <chewbranca@gmail.com> | 2013-03-05 16:41:47 -0800 |
commit | 7d73a824acb6053b501a7f6dbd887168bad2fdb0 (patch) | |
tree | b972cbb0e57b9e9ec0071f74f96641d5781639f2 | |
parent | 829db1ab39ad2a14b6b7a810fe947e47ea01eb66 (diff) | |
parent | 2c81ee740a20999b58199c00df927eff635168b4 (diff) | |
download | couchdb-7d73a824acb6053b501a7f6dbd887168bad2fdb0.tar.gz |
Merge pull request #17 from cloudant-labs/fauxton-pouch-mr
Advanced _view options and editor
-rw-r--r-- | src/fauxton/app/modules/documents/resources.js | 13 | ||||
-rw-r--r-- | src/fauxton/app/modules/documents/routes.js | 4 | ||||
-rw-r--r-- | src/fauxton/app/modules/documents/views.js | 100 | ||||
-rw-r--r-- | src/fauxton/app/modules/pouchdb/base.js | 57 | ||||
-rw-r--r-- | src/fauxton/app/modules/pouchdb/pouch.collate.js | 115 | ||||
-rw-r--r-- | src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js | 286 | ||||
-rw-r--r-- | src/fauxton/app/router.js | 1 | ||||
-rw-r--r-- | src/fauxton/app/templates/documents/all_docs_list.html | 175 | ||||
-rw-r--r-- | src/fauxton/app/templates/documents/view_editor.html | 135 | ||||
-rw-r--r-- | src/fauxton/grunt.js | 3 |
10 files changed, 743 insertions, 146 deletions
diff --git a/src/fauxton/app/modules/documents/resources.js b/src/fauxton/app/modules/documents/resources.js index 6cd5d65e6..862226d60 100644 --- a/src/fauxton/app/modules/documents/resources.js +++ b/src/fauxton/app/modules/documents/resources.js @@ -193,8 +193,21 @@ function(app, FauxtonAPI, Views) { return url.join("/") + query; }, + totalRows: function() { + return this.viewMeta.total_rows || "unknown"; + }, + + updateSeq: function() { + return this.viewMeta.update_seq || false; + }, + parse: function(resp) { that = this; + this.viewMeta = { + total_rows: resp.total_rows, + offest: resp.offest, + update_seq: resp.update_seq + }; return _.map(resp.rows, function(row) { return { value: row.value, diff --git a/src/fauxton/app/modules/documents/routes.js b/src/fauxton/app/modules/documents/routes.js index 801fa607d..d5dfc6a74 100644 --- a/src/fauxton/app/modules/documents/routes.js +++ b/src/fauxton/app/modules/documents/routes.js @@ -378,6 +378,7 @@ function(app, FauxtonAPI, Documents, Databases) { var ddocInfo = { id: "_design/" + ddoc, + currView: view, designDocs: data.designDocs }; @@ -396,7 +397,8 @@ function(app, FauxtonAPI, Documents, Databases) { }), "#sidebar-content": new Documents.Views.Sidebar({ - collection: data.designDocs + collection: data.designDocs, + ddocInfo: ddocInfo }), "#tabs": new Documents.Views.Tabs({ diff --git a/src/fauxton/app/modules/documents/views.js b/src/fauxton/app/modules/documents/views.js index 7bae3e9ab..70ebb6a86 100644 --- a/src/fauxton/app/modules/documents/views.js +++ b/src/fauxton/app/modules/documents/views.js @@ -240,18 +240,28 @@ function(app, FauxtonAPI, Codemirror, JSHint) { Views.IndexItem = FauxtonAPI.View.extend({ template: "templates/documents/index_menu_item", tagName: "li", + initialize: function(options){ this.index = options.index; this.ddoc = options.ddoc; this.database = options.database; + this.selected = !! options.selected; }, serialize: function() { return { index: this.index, ddoc: this.ddoc, - database: this.database + database: this.database, + selected: this.selected }; + }, + + afterRender: function() { + if (this.selected) { + $("#sidenav ul.nav-list li").removeClass("active"); + this.$el.addClass("active"); + } } }); @@ -320,7 +330,8 @@ function(app, FauxtonAPI, Codemirror, JSHint) { database: this.collection, viewList: this.viewList, hasReduce: false, - params: this.params + params: this.params, + ddocs: this.designDocs }; if (this.ddoc) { data.ddoc = this.ddoc; @@ -363,14 +374,14 @@ function(app, FauxtonAPI, Codemirror, JSHint) { return FauxtonAPI.addNotification({ msg: "JSON Parse Error on field: "+param.name, type: "error", - selector: ".view.show .errors-container" + selector: ".view.show .all-docs-list.errors-container" }); }); FauxtonAPI.addNotification({ msg: "Make sure that strings are properly quoted and any other values are valid JSON structures", type: "warning", - selector: ".view.show .errors-container" + selector: ".view.show .all-docs-list.errors-container" }); return false; @@ -401,7 +412,7 @@ function(app, FauxtonAPI, Codemirror, JSHint) { var notification = FauxtonAPI.addNotification({ msg: "include_docs has been disabled as you cannot include docs on a reduced view", type: "warn", - selector: ".view.show .errors-container" + selector: ".view.show .all-docs-list.errors-container" }); } $form.find("input[name=include_docs]").prop("disabled", true); @@ -451,6 +462,14 @@ function(app, FauxtonAPI, Codemirror, JSHint) { }, beforeRender: function() { + this.setDdocInfo(); + if (this.viewList) { + this.viewEditorView = this.insertView("#edit-index-container", new Views.ViewEditor({ + model: this.ddoc, + ddocs: this.designDocs, + viewCollection: this.collection + })); + } this.collection.each(function(doc) { this.rows[doc.id] = this.insertView("table.all-docs tbody", new this.nestedView({ model: doc @@ -470,6 +489,9 @@ function(app, FauxtonAPI, Codemirror, JSHint) { $form.find("select[name='"+key+"']").val(val); break; case "include_docs": + case "stale": + case "descending": + case "inclusive_end": $form.find("input[name='"+key+"']").prop('checked', true); break; case "reduce": @@ -619,9 +641,11 @@ function(app, FauxtonAPI, Codemirror, JSHint) { Views.ViewEditor = FauxtonAPI.View.extend({ template: "templates/documents/view_editor", + builtinReduces: ['_sum', '_count', '_stats'], events: { "click button.save": "saveView", + "click button.preview": "previewView", "change select#reduce-function-selector": "updateReduce" }, @@ -636,6 +660,9 @@ function(app, FauxtonAPI, Codemirror, JSHint) { initialize: function(options) { this.ddocs = options.ddocs; + this.viewCollection = options.viewCollection; + this.reduceFunStr = this.model.viewHasReduce(this.viewCollection.view); + this.newView = false; }, updateValues: function() { @@ -661,7 +688,22 @@ function(app, FauxtonAPI, Codemirror, JSHint) { }, establish: function() { - return [this.ddocs.fetch(), this.model.fetch()]; + //return [this.ddocs.fetch(), this.model.fetch()]; + return []; + }, + + previewView: function(event) { + FauxtonAPI.addNotification({ + msg: "<strong>Warning!</strong> Preview executes the Map/Reduce functions in your browser, and may behave differently from CouchDB.", + type: "warning", + selector: "#define-view .errors-container", + fade: false + }); + FauxtonAPI.addNotification({ + msg: "Preview Functionality Coming Soon", + type: "warning", + selector: "#define-view .errors-container" + }); }, saveView: function(event) { @@ -669,10 +711,17 @@ function(app, FauxtonAPI, Codemirror, JSHint) { if (this.hasValidCode()) { var mapVal = this.mapEditor.getValue(); var reduceVal = this.reduceEditor.getValue(); + /* notification = FauxtonAPI.addNotification({ msg: "Saving document.", selector: "#define-view .errors-container" }); + */ + FauxtonAPI.addNotification({ + msg: "Save Functionality Coming Soon", + type: "warning", + selector: "#define-view .errors-container" + }); /* this.model.save().error(function(xhr) { var responseText = JSON.parse(xhr.responseText).reason; @@ -745,18 +794,28 @@ function(app, FauxtonAPI, Codemirror, JSHint) { serialize: function() { return { - database: this.model, - ddocs: this.ddocs + //database: this.model, + ddocs: this.ddocs, + ddoc: this.model, + viewCollection: this.viewCollection, + reduceFunStr: this.reduceFunStr, + isCustomReduce: this.hasCustomReduce(), + newView: this.newView }; }, + hasCustomReduce: function() { + return this.reduceFunStr && ! _.contains(this.builtinReduces, this.reduceFunStr); + }, + 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); + if (this.newView) { + mapFun.val(this.langTemplates[this.defaultLang].map); + reduceFun.val(this.langTemplates[this.defaultLang].reduce); + } this.mapEditor = Codemirror.fromTextArea(mapFun.get()[0], { mode: "javascript", lineNumbers: true, @@ -766,7 +825,7 @@ function(app, FauxtonAPI, Codemirror, JSHint) { that.runJSHint("mapEditor"); }, extraKeys: { - "Ctrl-S": function(instance) { that.saveDoc(); }, + "Ctrl-S": function(instance) { that.saveView(); }, "Ctrl-/": "undo" } }); @@ -779,14 +838,16 @@ function(app, FauxtonAPI, Codemirror, JSHint) { that.runJSHint("reduceEditor"); }, extraKeys: { - "Ctrl-S": function(instance) { that.saveDoc(); }, + "Ctrl-S": function(instance) { that.saveView(); }, "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(); + if ( ! this.hasCustomReduce()) { + $(".control-group.reduce-function").hide(); + } } }); @@ -799,6 +860,13 @@ function(app, FauxtonAPI, Codemirror, JSHint) { "click .nav-list a.toggle-view#design-docs": "toggleView" }, + initialize: function(options) { + if (options.ddocInfo) { + this.ddocID = options.ddocInfo.id; + this.currView = options.ddocInfo.currView; + } + }, + establish: function() { if (this.collection) { return [this.collection.fetch()]; @@ -836,10 +904,12 @@ function(app, FauxtonAPI, Codemirror, JSHint) { buildIndexList: function(collection, selector, design){ _.each(_.keys(collection), function(key){ + var selected = this.ddocID == "_design/"+design; this.insertView("ul.nav." + selector, new Views.IndexItem({ ddoc: design, index: key, - database: this.collection.database.id + database: this.collection.database.id, + selected: selected && key == this.currView })); }, this); }, diff --git a/src/fauxton/app/modules/pouchdb/base.js b/src/fauxton/app/modules/pouchdb/base.js new file mode 100644 index 000000000..2b7cfc927 --- /dev/null +++ b/src/fauxton/app/modules/pouchdb/base.js @@ -0,0 +1,57 @@ +// 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. + + +/* + * NOTE: + * This temporarily uses the PouchDB map reduce implementation + * These files are modified locally until we make a more general version and + * push it back upstream. + */ + +define([ + "app", + + "api", + + // Modules + "modules/pouchdb/pouchdb.mapreduce.js" +], + +function(app, FauxtonAPI, MapReduce) { + var Pouch = {}; + Pouch.MapReduce = MapReduce; + + Pouch.runViewQuery = function(fun, docs) { + docs = [ + {_id: 'test_doc_1', foo: 'bar-1'}, + {_id: 'test_doc_2', foo: 'bar-2'}, + {_id: 'test_doc_3', foo: 'bar-3'}, + {_id: 'test_doc_4', foo: 'bar-4'}, + {_id: 'test_doc_5', foo: 'bar-5'}, + {_id: 'test_doc_6', foo: 'bar-6'}, + {_id: 'test_doc_7', foo: 'bar-7'}, + {_id: 'test_doc_8', foo: 'bar-8'}, + {_id: 'test_doc_9', foo: 'bar-9'}, + {_id: 'test_doc_10', foo: 'bar-10'} + ]; + + var deferred = FauxtonAPI.Deferred(); + var complete = function(resp) { + console.log("COMPLETE TRIGGERED", arguments); + }; + + return Pouch.MapReduce.query(fun, {docs: docs, complete:complete}); + }; + + return Pouch; +}); diff --git a/src/fauxton/app/modules/pouchdb/pouch.collate.js b/src/fauxton/app/modules/pouchdb/pouch.collate.js new file mode 100644 index 000000000..7cc5f9cfe --- /dev/null +++ b/src/fauxton/app/modules/pouchdb/pouch.collate.js @@ -0,0 +1,115 @@ +/* + * NOTE: + * This temporarily uses the PouchDB map reduce implementation + * These files are modified locally until we make a more general version and + * push it back upstream. + * Original file: + * https://github.com/daleharvey/pouchdb/blob/master/src/pouch.collate.js + */ + +/* +(function() { + // a few hacks to get things in the right place for node.js + if (typeof module !== 'undefined' && module.exports) { + module.exports = Pouch; + } +*/ + +define([ + "app", + + "api", + + // Modules + "modules/pouchdb/pouch.collate.js" +], + +function(app, FauxtonAPI, Collate) { + var Pouch = {}; + + Pouch.collate = function(a, b) { + var ai = collationIndex(a); + var bi = collationIndex(b); + if ((ai - bi) !== 0) { + return ai - bi; + } + if (a === null) { + return 0; + } + if (typeof a === 'number') { + return a - b; + } + if (typeof a === 'boolean') { + return a < b ? -1 : 1; + } + if (typeof a === 'string') { + return stringCollate(a, b); + } + if (Array.isArray(a)) { + return arrayCollate(a, b); + } + if (typeof a === 'object') { + return objectCollate(a, b); + } + }; + + var stringCollate = function(a, b) { + // See: https://github.com/daleharvey/pouchdb/issues/40 + // This is incompatible with the CouchDB implementation, but its the + // best we can do for now + return (a === b) ? 0 : ((a > b) ? 1 : -1); + }; + + var objectCollate = function(a, b) { + var ak = Object.keys(a), bk = Object.keys(b); + var len = Math.min(ak.length, bk.length); + for (var i = 0; i < len; i++) { + // First sort the keys + var sort = Pouch.collate(ak[i], bk[i]); + if (sort !== 0) { + return sort; + } + // if the keys are equal sort the values + sort = Pouch.collate(a[ak[i]], b[bk[i]]); + if (sort !== 0) { + return sort; + } + + } + return (ak.length === bk.length) ? 0 : + (ak.length > bk.length) ? 1 : -1; + }; + + var arrayCollate = function(a, b) { + var len = Math.min(a.length, b.length); + for (var i = 0; i < len; i++) { + var sort = Pouch.collate(a[i], b[i]); + if (sort !== 0) { + return sort; + } + } + return (a.length === b.length) ? 0 : + (a.length > b.length) ? 1 : -1; + }; + + // The collation is defined by erlangs ordered terms + // the atoms null, true, false come first, then numbers, strings, + // arrays, then objects + var collationIndex = function(x) { + var id = ['boolean', 'number', 'string', 'object']; + if (id.indexOf(typeof x) !== -1) { + if (x === null) { + return 1; + } + return id.indexOf(typeof x) + 2; + } + if (Array.isArray(x)) { + return 4.5; + } + }; + + return Pouch; + +//}).call(this); + +}); diff --git a/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js b/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js new file mode 100644 index 000000000..b97eb7fc3 --- /dev/null +++ b/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js @@ -0,0 +1,286 @@ +/* + * NOTE: + * This temporarily uses the PouchDB map reduce implementation + * These files are modified locally until we make a more general version and + * push it back upstream. + * Original file: + * https://github.com/daleharvey/pouchdb/blob/master/src/plugins/pouchdb.mapreduce.js + */ + +/*global Pouch: true */ + +//"use strict"; + +// This is the first implementation of a basic plugin, we register the +// plugin object with pouch and it is mixin'd to each database created +// (regardless of adapter), adapters can override plugins by providing +// their own implementation. functions on the plugin object that start +// with _ are reserved function that are called by pouchdb for special +// notifications. + +// If we wanted to store incremental views we can do it here by listening +// to the changes feed (keeping track of our last update_seq between page loads) +// and storing the result of the map function (possibly using the upcoming +// extracted adapter functions) + +define([ + "app", + + "api", + + // Modules + "modules/pouchdb/pouch.collate.js" +], + +function(app, FauxtonAPI, Collate) { + var Pouch = {}; + Pouch.collate = Collate.collate; + + //var MapReduce = function(db) { + var MapReduce = function() { + + function viewQuery(fun, options) { + console.log("IN VIEW QUERY"); + if (!options.complete) { + return; + } + + function sum(values) { + return values.reduce(function(a, b) { return a + b; }, 0); + } + + var results = []; + var current = null; + var num_started= 0; + var completed= false; + + var emit = function(key, val) { + console.log("IN EMIT: ", key, val, current); + var viewRow = { + id: current.doc._id, + key: key, + value: val + }; + console.log("VIEW ROW: ", viewRow); + + if (options.startkey && Pouch.collate(key, options.startkey) < 0) return; + if (options.endkey && Pouch.collate(key, options.endkey) > 0) return; + if (options.key && Pouch.collate(key, options.key) !== 0) return; + num_started++; + if (options.include_docs) { + // TODO:: FIX + throw({error: "Include Docs not supported"}); + /* + + //in this special case, join on _id (issue #106) + if (val && typeof val === 'object' && val._id){ + db.get(val._id, + function(_, joined_doc){ + if (joined_doc) { + viewRow.doc = joined_doc; + } + results.push(viewRow); + checkComplete(); + }); + return; + } else { + viewRow.doc = current.doc; + } + */ + } + console.log("EMITTING: ", viewRow); + results.push(viewRow); + }; + + // ugly way to make sure references to 'emit' in map/reduce bind to the + // above emit + eval('fun.map = ' + fun.map.toString() + ';'); + if (fun.reduce) { + eval('fun.reduce = ' + fun.reduce.toString() + ';'); + } + + // exclude _conflicts key by default + // or to use options.conflicts if it's set when called by db.query + var conflicts = ('conflicts' in options ? options.conflicts : false); + + //only proceed once all documents are mapped and joined + var checkComplete= function(){ + if (completed && results.length == num_started){ + results.sort(function(a, b) { + return Pouch.collate(a.key, b.key); + }); + if (options.descending) { + results.reverse(); + } + if (options.reduce === false) { + return options.complete(null, {rows: results}); + } + + var groups = []; + results.forEach(function(e) { + var last = groups[groups.length-1] || null; + if (last && Pouch.collate(last.key[0][0], e.key) === 0) { + last.key.push([e.key, e.id]); + last.value.push(e.value); + return; + } + groups.push({key: [[e.key, e.id]], value: [e.value]}); + }); + groups.forEach(function(e) { + e.value = fun.reduce(e.key, e.value) || null; + e.key = e.key[0][0]; + }); + options.complete(null, {rows: groups}); + } + }; + + if (options.docs) { + console.log("RUNNING MR ON DOCS: ", options.docs); + _.each(options.docs, function(doc) { + current = {doc: doc}; + fun.map.call(this, doc); + }, this); + return options.complete(null, {rows: results}); + } else { + console.log("COULD NOT FIND DOCS"); + return false; + } + + /* + db.changes({ + conflicts: conflicts, + include_docs: true, + onChange: function(doc) { + if (!('deleted' in doc)) { + current = {doc: doc.doc}; + fun.map.call(this, doc.doc); + } + }, + complete: function() { + completed= true; + checkComplete(); + } + }); + */ + } + + /* + function httpQuery(fun, opts, callback) { + + // List of parameters to add to the PUT request + var params = []; + var body = undefined; + var method = 'GET'; + + // If opts.reduce exists and is defined, then add it to the list + // of parameters. + // If reduce=false then the results are that of only the map function + // not the final result of map and reduce. + if (typeof opts.reduce !== 'undefined') { + params.push('reduce=' + opts.reduce); + } + if (typeof opts.include_docs !== 'undefined') { + params.push('include_docs=' + opts.include_docs); + } + if (typeof opts.limit !== 'undefined') { + params.push('limit=' + opts.limit); + } + if (typeof opts.descending !== 'undefined') { + params.push('descending=' + opts.descending); + } + if (typeof opts.startkey !== 'undefined') { + params.push('startkey=' + encodeURIComponent(JSON.stringify(opts.startkey))); + } + if (typeof opts.endkey !== 'undefined') { + params.push('endkey=' + encodeURIComponent(JSON.stringify(opts.endkey))); + } + if (typeof opts.key !== 'undefined') { + params.push('key=' + encodeURIComponent(JSON.stringify(opts.key))); + } + + // If keys are supplied, issue a POST request to circumvent GET query string limits + // see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options + if (typeof opts.keys !== 'undefined') { + method = 'POST'; + body = JSON.stringify({keys:opts.keys}); + } + + // Format the list of parameters into a valid URI query string + params = params.join('&'); + params = params === '' ? '' : '?' + params; + + // We are referencing a query defined in the design doc + if (typeof fun === 'string') { + var parts = fun.split('/'); + db.request({ + method: method, + url: '_design/' + parts[0] + '/_view/' + parts[1] + params, + body: body + }, callback); + return; + } + + // We are using a temporary view, terrible for performance but good for testing + var queryObject = JSON.parse(JSON.stringify(fun, function(key, val) { + if (typeof val === 'function') { + return val + ''; // implicitly `toString` it + } + return val; + })); + + db.request({ + method:'POST', + url: '_temp_view' + params, + body: queryObject + }, callback); + } + */ + + function query(fun, opts, callback) { + if (typeof opts === 'function') { + callback = opts; + opts = {}; + } + + if (callback) { + opts.complete = callback; + } + + /* + if (db.type() === 'http') { + return httpQuery(fun, opts, callback); + } + */ + + if (typeof fun === 'object') { + console.log("RUNNING VIEW QUERY", fun, opts, arguments); + return viewQuery(fun, opts); + } + + throw({error: "Shouldn't have gotten here"}); + + /* + var parts = fun.split('/'); + db.get('_design/' + parts[0], function(err, doc) { + if (err) { + if (callback) callback(err); + return; + } + viewQuery({ + map: doc.views[parts[1]].map, + reduce: doc.views[parts[1]].reduce + }, opts); + }); + */ + } + + return {'query': query}; + }; + + // Deletion is a noop since we dont store the results of the view + MapReduce._delete = function() { }; + + //Pouch.plugin('mapreduce', MapReduce); + + return MapReduce(); +}); diff --git a/src/fauxton/app/router.js b/src/fauxton/app/router.js index adfa96e75..67743a47e 100644 --- a/src/fauxton/app/router.js +++ b/src/fauxton/app/router.js @@ -32,6 +32,7 @@ define([ // Routes return the module that they define routes for "modules/databases/base", "modules/documents/base", + "modules/pouchdb/base", // this needs to be added as a plugin later diff --git a/src/fauxton/app/templates/documents/all_docs_list.html b/src/fauxton/app/templates/documents/all_docs_list.html index 715c9795e..423a80d03 100644 --- a/src/fauxton/app/templates/documents/all_docs_list.html +++ b/src/fauxton/app/templates/documents/all_docs_list.html @@ -13,88 +13,127 @@ the License. --> <div class="view show"> - <div class="row"> - <div class="btn-toolbar span6"> - <button type="button" class="btn all" data-toggle="button">✓ All</button> - <button class="btn btn-small disabled bulk-delete"><i class="icon-trash"></i></button> - </div> - <div class="btn-toolbar pull-right"> - <a href="#new-view-index" class="btn btn-small toggle-edit disabled"><i class="icon-wrench"></i> Edit index</a> - <a href="#params" class="btn btn-small toggle-params"><i class="icon-plus"></i> API preview</a> + <% if (!viewList) { %> + <div class="row"> + <div class="btn-toolbar span6"> + <button type="button" class="btn all" data-toggle="button">✓ All</button> + <button class="btn btn-small disabled bulk-delete"><i class="icon-trash"></i></button> + </div> + <div class="btn-toolbar pull-right"> + <a href="#new-view-index" class="btn btn-small toggle-edit disabled"><i class="icon-wrench"></i> Edit index</a> + <a href="#params" class="btn btn-small toggle-params"><i class="icon-plus"></i> API preview</a> + </div> </div> - </div> + <% } %> <div class="row"> - <div class="errors-container"></div> - <div class="accordion" id="advanced-options-accordion"> - <div class="accordion-group"> - <div class="accordion-heading"> - <a class="accordion-toggle" data-bypass="true" data-toggle="collapse" data-parent="#advanced-options-accordion" href="#collapse-advanced-options"> - Advanced Options - </a> - </div> - <div id="collapse-advanced-options" class="accordion-body collapse in"> - <div class="accordion-inner"> - <form class="view-query-update"> - <div class="controls controls-row"> - <label class="span3 inline"> - Limit: - <select name="limit" class="input-small"> - <option>5</option> - <option selected="selected">10</option> - <option>25</option> - <option>50</option> - <option>100</option> - </select> - </label> - <label class="span3 checkbox inline"> - <input name="include_docs" type="checkbox" value="true"> Include Docs - </label> - <% if (hasReduce) { %> + <div class="all-docs-list errors-container"></div> + <div id="edit-index-container"></div> + <% if (viewList) { %> + <div class="accordion" id="advanced-options-accordion"> + <div class="accordion-group"> + <div class="accordion-heading"> + <a class="accordion-toggle" data-bypass="true" data-toggle="collapse" data-parent="#advanced-options-accordion" href="#collapse-advanced-options"> + <i class="icon-plus"></i> Advanced Options + </a> + </div> + <div id="collapse-advanced-options" class="accordion-body collapse"> + <div class="accordion-inner"> + <form class="view-query-update"> + <div class="controls controls-row"> + <label class="span3 inline"> + Limit: + <select name="limit" class="input-small"> + <option>5</option> + <option selected="selected">10</option> + <option>25</option> + <option>50</option> + <option>100</option> + </select> + </label> + <label class="span3 checkbox inline"> + <input name="include_docs" type="checkbox" value="true"> Include Docs + </label> + <% if (hasReduce) { %> + <label class="span2 checkbox inline"> + <input name="reduce" type="checkbox" value="true"> Reduce + </label> + <label class="span4 inline"> + Group Level: + <select disabled name="group_level" class="input-small"> + <option value="0">None</option> + <option value="1">1</option> + <option value="2">2</option> + <option value="3">3</option> + <option value="4">4</option> + <option value="5">5</option> + <option value="6">6</option> + <option value="7">7</option> + <option value="8">8</option> + <option value="9">9</option> + <option value="999" selected="selected">exact</option> + </select> + </label> + <% } %> + </div> + + <div class="controls controls-row"> + <input name="key" class="span4" type="text" placeholder="Key"> + <input name="keys" class="span8" type="text" placeholder="Keys"> + </div> + <div class="controls controls-row"> + <input name="startkey" class="span6" type="text" placeholder="Start Key"> + <input name="endkey" class="span6" type="text" placeholder="End Key"> + </div> + <div class="controls controls-row"> <label class="span2 checkbox inline"> - <input name="reduce" type="checkbox" value="true"> Reduce + <input name="stale" type="checkbox" value="ok"> Stale </label> - <label class="span4 inline"> - Group Level: - <select disabled name="group_level" class="input-small"> - <option value="0">None</option> - <option value="1">1</option> - <option value="2">2</option> - <option value="3">3</option> - <option value="4">4</option> - <option value="5">5</option> - <option value="6">6</option> - <option value="7">7</option> - <option value="8">8</option> - <option value="9">9</option> - <option value="999" selected="selected">exact</option> - </select> + <label class="span2 checkbox inline"> + <input name="descending" type="checkbox" value="true"> Descending + </label> + <label class="span4 checkbox inline"> + <input name="inclusive_end" type="checkbox" value="false"> Disable Inclusive End + </label> + <label class="span4 checkbox inline"> + <input name="update_seq" type="checkbox" value="true"> Include Update Sequence </label> - <% } %> - </div> + </div> + <div class="controls controls-row"> + <button type="submit" class="btn btn-primary">Query</button> + </div> + </form> - <div class="controls controls-row"> - <input name="key" class="span4" type="text" placeholder="Key"> - <input name="keys" class="span8" type="text" placeholder="Keys"> - </div> - <div class="controls controls-row"> - <input name="startkey" class="span6" type="text" placeholder="Start Key"> - <input name="endkey" class="span6" type="text" placeholder="End Key"> - </div> - <div class="controls controls-row"> - <button type="submit" class="btn btn-primary">Query</button> - </div> - </form> - + </div> </div> - </div> + </div> </div> - </div> + <% } %> </div> + <p> + Showing 1-<%= database.models.length %> of <%= database.totalRows() %> rows + <% if (database.updateSeq()) { %> + -- Update Sequence: <%= database.updateSeq() %> + <% } %> + </p> <table class="all-docs table table-striped table-condensed"> <tbody></tbody> </table> + <!-- + <div class="pagination pagination-centered"> + <ul> + <li class="disabled"><a href="#">«</a></li> + <li class="active"><a href="#">1</a></li> + <li><a href="#">2</a></li> + <li><a href="#">3</a></li> + <li><a href="#">4</a></li> + <li><a href="#">5</a></li> + <li><a href="#">»</a></li> + </ul> + </div> + --> + </div> diff --git a/src/fauxton/app/templates/documents/view_editor.html b/src/fauxton/app/templates/documents/view_editor.html index 635d85f6a..4a8668e29 100644 --- a/src/fauxton/app/templates/documents/view_editor.html +++ b/src/fauxton/app/templates/documents/view_editor.html @@ -12,68 +12,81 @@ License for the specific language governing permissions and limitations under the License. --> -<div id="define-view" class="ddoc-alert well"> - <div class="errors-container"> - <div class="alert"> - <button type="button" class="close" data-dismiss="alert">×</button> - <strong>Warning!</strong> Preview executes the Map/Reduce functions in your browser, and may behave differently from CouchDB. +<div class="accordion" id="edit-index-accordion"> + <div class="accordion-group"> + <div class="accordion-heading"> + <a class="accordion-toggle" data-bypass="true" data-toggle="collapse" data-parent="#edit-index-accordion" href="#collapse-edit-index"> + <i class="icon-wrench"></i> Edit Index + </a> </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 id="collapse-edit-index" class="accordion-body collapse"> + <div class="accordion-inner"> + <div id="define-view" class="ddoc-alert well"> + <div class="errors-container"></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) { %> + <% if (ddoc.id == "_design/"+viewCollection.design) { %> + <option selected="selected"><%= ddoc.id %></option> + <% } else { %> + <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="<%= viewCollection.view %>" 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"><%= ddoc.get('doc').views[viewCollection.view].map %></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="" <%= !reduceFunStr ? 'selected="selected"' : '' %>>None</option> + <% _.each(["_sum", "_count", "_stats"], function(reduce) { %> + <option value="<%= reduce %>" <% if (reduce == reduceFunStr) { %>selected<% } %>><%= reduce %></option> + <% }) %> + <option value="CUSTOM" <% if (isCustomReduce) { %>selected<% } %>>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"><%= ddoc.get('doc').views[viewCollection.view].reduce %></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> </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> </div> diff --git a/src/fauxton/grunt.js b/src/fauxton/grunt.js index 927f506d0..8abdd0200 100644 --- a/src/fauxton/grunt.js +++ b/src/fauxton/grunt.js @@ -98,7 +98,8 @@ module.exports = function(grunt) { // route. jshint: { options: { - scripturl: true + scripturl: true, + evil: true } }, |