diff options
author | Garren Smith <garren.smith@gmail.com> | 2013-05-16 08:34:54 +0200 |
---|---|---|
committer | Garren Smith <garren.smith@gmail.com> | 2013-06-04 15:46:38 +0200 |
commit | 90e4da6ce6bda6cb72b325d2702fe9832e20ff59 (patch) | |
tree | d0fe10f41faba1d1595eeb688d15df9e4287ba2a | |
parent | eb364ff3a2a85bdc15aaa4f5b8c69aa987aea6f8 (diff) | |
download | couchdb-90e4da6ce6bda6cb72b325d2702fe9832e20ff59.tar.gz |
Create/edit and query views.
It is now also possible to preview views with pouch. Issue #1806
-rw-r--r-- | src/fauxton/app/api.js | 8 | ||||
-rw-r--r-- | src/fauxton/app/modules/databases/resources.js | 2 | ||||
-rw-r--r-- | src/fauxton/app/modules/documents/resources.js | 114 | ||||
-rw-r--r-- | src/fauxton/app/modules/documents/routes.js | 186 | ||||
-rw-r--r-- | src/fauxton/app/modules/documents/views.js | 591 | ||||
-rw-r--r-- | src/fauxton/app/modules/pouchdb/base.js | 17 | ||||
-rw-r--r-- | src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js | 50 | ||||
-rw-r--r-- | src/fauxton/app/templates/documents/all_docs_list.html | 93 | ||||
-rw-r--r-- | src/fauxton/app/templates/documents/changes.html | 4 | ||||
-rw-r--r-- | src/fauxton/app/templates/documents/view_editor.html | 238 | ||||
-rw-r--r-- | src/fauxton/app/templates/layouts/with_tabs_sidebar.html | 5 |
11 files changed, 790 insertions, 518 deletions
diff --git a/src/fauxton/app/api.js b/src/fauxton/app/api.js index c506b1aa0..e13a41dcc 100644 --- a/src/fauxton/app/api.js +++ b/src/fauxton/app/api.js @@ -32,7 +32,8 @@ function(app, Fauxton) { // List of JSHINT errors to ignore // Gets around problem of anonymous functions not being a valid statement FauxtonAPI.excludedViewErrors = [ - "Missing name in function declaration." + "Missing name in function declaration.", + "['{a}'] is better written in dot notation." ]; FauxtonAPI.isIgnorableError = function(msg) { @@ -54,8 +55,9 @@ function(app, Fauxton) { } }); - FauxtonAPI.navigate = function(url) { - Backbone.history.navigate(url, true); + FauxtonAPI.navigate = function(url, _opts) { + var options = _.extend({trigger: true}, _opts ); + app.router.navigate(url,options); }; FauxtonAPI.addHeaderLink = function(link) { diff --git a/src/fauxton/app/modules/databases/resources.js b/src/fauxton/app/modules/databases/resources.js index 6927fd57e..04e6c1ecc 100644 --- a/src/fauxton/app/modules/databases/resources.js +++ b/src/fauxton/app/modules/databases/resources.js @@ -47,7 +47,7 @@ function(app, FauxtonAPI, Documents) { if (context === "index") { return "/database/" + this.id + "/_all_docs"; } else if (context === "changes") { - return "/database/" + this.id + "/_changes?descending=true&limit=100"; + return "/database/" + this.id + "/_changes?descending=true&limit=100&include_docs=true"; } else if (context === "app") { return "/database/" + this.id; } else { diff --git a/src/fauxton/app/modules/documents/resources.js b/src/fauxton/app/modules/documents/resources.js index f1a07c32e..e31337887 100644 --- a/src/fauxton/app/modules/documents/resources.js +++ b/src/fauxton/app/modules/documents/resources.js @@ -13,15 +13,10 @@ define([ "app", - "api", - - // Views - "modules/documents/views" - - // Plugins + "api" ], -function(app, FauxtonAPI, Views) { +function(app, FauxtonAPI) { var Documents = app.module(); Documents.Doc = Backbone.Model.extend({ @@ -64,14 +59,61 @@ function(app, FauxtonAPI, Views) { hasViews: function() { if (!this.isDdoc()) return false; var doc = this.get('doc'); - return doc && doc.views && _.keys(doc.views).length > 0; + if (doc) { + return doc && doc.views && _.keys(doc.views).length > 0; + } + + var views = this.get('views'); + return views && _.keys(views).length > 0; }, getDdocView: function(view) { if (!this.isDdoc() || !this.hasViews()) return false; var doc = this.get('doc'); - return doc.views[view]; + if (doc) { + return doc.views[view]; + } + + return this.get('views')[view]; + }, + + setDdocView: function (view, map, reduce) { + if (!this.isDdoc()) return false; + var views = this.get('views'); + + if (reduce) { + views[view] = { + map: map, + reduce: reduce + }; + } else { + views[view].map = map; + } + + this.set({views: views}); + + return true; + }, + + removeDdocView: function (viewName) { + if (!this.isDdoc()) return false; + var views = this.get('views'); + + delete views[viewName]; + this.set({views: views}); + }, + + dDocModel: function () { + if (!this.isDdoc()) return false; + var doc = this.get('doc'); + + if (doc) { + console.log('DOC', doc); + return new Documents.Doc(doc, {database: this.database}); + } + + return this; }, viewHasReduce: function(viewName) { @@ -208,10 +250,10 @@ function(app, FauxtonAPI, Views) { initialize: function(_models, options) { this.database = options.database; - this.view = options.view; - this.design = options.design; this.params = _.extend({limit: 10, reduce: false}, options.params); this.idxType = "_view"; + this.view = options.view; + this.design = options.design.replace('_design/',''); }, url: function() { @@ -256,8 +298,56 @@ function(app, FauxtonAPI, Views) { return this.models; } }); + + Documents.PouchIndexCollection = Backbone.Collection.extend({ + model: Documents.ViewRow, + + initialize: function(_models, options) { + this.database = options.database; + this.rows = options.rows; + this.view = options.view; + this.design = options.design.replace('_design/',''); + this.params = _.extend({limit: 10, reduce: false}, options.params); + this.idxType = "_view"; + }, + + url: function () { + return ''; + }, + + fetch: function() { + var deferred = FauxtonAPI.Deferred(); + this.reset(this.rows, {silent: true}); + + this.viewMeta = { + total_rows: this.rows.length, + offest: 0, + update_seq: false + }; + + deferred.resolve(); + return deferred; + }, + + totalRows: function() { + console.log('rows'); + console.log(this); + return this.viewMeta.total_rows || "unknown"; + }, + + updateSeq: function() { + return this.viewMeta.update_seq || false; + }, + + buildAllDocs: function(){ + this.fetch(); + }, + + allDocs: function(){ + return this.models; + } + }); - Documents.Views = Views; return Documents; }); diff --git a/src/fauxton/app/modules/documents/routes.js b/src/fauxton/app/modules/documents/routes.js index 1a755591f..e02ac936f 100644 --- a/src/fauxton/app/modules/documents/routes.js +++ b/src/fauxton/app/modules/documents/routes.js @@ -16,7 +16,7 @@ define([ "api", // Modules - "modules/documents/resources", + "modules/documents/views", "modules/databases/base" ], @@ -32,7 +32,7 @@ function(app, FauxtonAPI, Documents, Databases) { var databaseName = options[0], docID = options[1]; this.database = this.database || new Databases.Model({id: databaseName}); - this.doc = this.doc || new Documents.Doc({ + this.doc = new Documents.Doc({ _id: docID }, { database: this.database @@ -62,7 +62,8 @@ function(app, FauxtonAPI, Documents, Databases) { code_editor: function (event) { this.tabsView.updateSelected('code_editor'); this.docView = this.setView("#dashboard-content", new Documents.Views.Doc({ - model: this.doc + model: this.doc, + database: this.database })); }, @@ -78,96 +79,6 @@ 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": data.database.url('app')} - ], - - views: { - "#sidebar-content": new Documents.Views.Sidebar({ - collection: data.designDocs - }), - - "#tabs": new Documents.Views.Tabs({ - collection: data.designDocs, - database: data.database - }), - - "#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 - // between code and field editors - /*var newDocCodeEditorCallback = function(databaseName) { - var data = { - database: new Databases.Model({id:databaseName}), - doc: new Documents.NewDoc(), - selected: "code_editor" - }; - data.doc.database = data.database; - data.designDocs = new Documents.AllDocs(null, { - database: data.database, - params: {startkey: '"_design"', - endkey: '"_design1"', - include_docs: true} - }); - - var options = app.getParams(); - options.include_docs = true; - data.database.buildAllDocs(options); - - return { - layout: "one_pane", - - data: data, - - crumbs: [ - {"name": "Databases", "link": "/_all_dbs"}, - {"name": data.database.id, "link": Databases.databaseUrl(data.database)}, - {"name": "new", "link": "#"} - ], - - views: { - "#dashboard-content": new Documents.Views.Doc({ - model: data.doc - }), - - "#tabs": new Documents.Views.FieldEditorTabs({ - selected: data.selected, - model: data.doc - }) - }, - - apiUrl: data.doc.url() - }; - };*/ - - var DocumentsRouteObject = FauxtonAPI.RouteObject.extend({ layout: "with_tabs_sidebar", @@ -180,6 +91,12 @@ function(app, FauxtonAPI, Documents, Databases) { "database/:database/new_view": "newViewEditor" }, + events: { + "route:updateAllDocs": "updateAllDocsFromView", + "route:updatePreviewDocs": "updateAllDocsFromPreview", + "route:reloadDesignDocs": "reloadDesignDocs" + }, + initialize: function (route, masterLayout, options) { var docOptions = app.getParams(); docOptions.include_docs = true; @@ -207,6 +124,9 @@ function(app, FauxtonAPI, Documents, Databases) { })); }, + establish: function () { + return this.data.designDocs.fetch(); + }, allDocs: function(databaseName, options) { var docOptions = app.getParams(options); @@ -220,7 +140,9 @@ function(app, FauxtonAPI, Documents, Databases) { this.sidebar.setSelectedTab('all-docs'); } - this.documentsView = this.setView("#dashboard-content", new Documents.Views.AllDocsList({ + if (this.viewEditor) { this.viewEditor.remove(); } + + this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({ collection: this.data.database.allDocs })); @@ -250,12 +172,22 @@ function(app, FauxtonAPI, Documents, Databases) { designDocs: this.data.designDocs }; - this.setView("#dashboard-content", new Documents.Views.AllDocsList({ + this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({ + model: this.data.database, + ddocs: this.data.designDocs, + viewName: view, + params: params, + newView: false, + database: this.data.database, + ddocInfo: ddocInfo + })); + + this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({ + database: this.data.database, collection: this.data.indexedDocs, nestedView: Documents.Views.Row, viewList: true, - ddocInfo: ddocInfo, - params: params + ddocInfo: ddocInfo })); this.sidebar.setSelectedTab(ddoc + '_' + view); @@ -271,17 +203,65 @@ function(app, FauxtonAPI, Documents, Databases) { this.apiUrl = this.data.indexedDocs.url(); }, - newViewEditor: function (event) { - // TODO: Get this working - this.setView("#dashboard-content", new Documents.Views.ViewEditor({ - model: this.data.database, - ddocs: this.data.designDocs + newViewEditor: function () { + var params = app.getParams(); + + this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({ + ddocs: this.data.designDocs, + params: params, + database: this.data.database, + newView: true })); this.sidebar.setSelectedTab('new-view'); + }, - } + updateAllDocsFromView: function (event) { + var view = event.view, + ddoc = event.ddoc; + + this.data.indexedDocs = new Documents.IndexCollection(null, { + database: this.data.database, + design: ddoc, + view: view, + params: app.getParams() + }); + + this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({ + database: this.data.database, + collection: this.data.indexedDocs, + nestedView: Documents.Views.Row, + viewList: true + })); + }, + + updateAllDocsFromPreview: function (event) { + var view = event.view, + rows = event.rows, + ddoc = event.ddoc; + this.data.indexedDocs = new Documents.PouchIndexCollection(null, { + database: this.data.database, + design: ddoc, + view: view, + rows: rows + }); + + this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({ + database: this.data.database, + collection: this.data.indexedDocs, + nestedView: Documents.Views.Row, + viewList: true + })); + }, + + reloadDesignDocs: function (event) { + this.sidebar.forceRender(); + + if (event && event.selectedTab) { + this.sidebar.setSelectedTab(event.selectedTab); + } + } }); var ChangesRouteObject = FauxtonAPI.RouteObject.extend({ diff --git a/src/fauxton/app/modules/documents/views.js b/src/fauxton/app/modules/documents/views.js index 17bf9af9d..f5cd88298 100644 --- a/src/fauxton/app/modules/documents/views.js +++ b/src/fauxton/app/modules/documents/views.js @@ -11,20 +11,24 @@ // the License. define([ - "app", + "app", - "api", + "api", - // Libs - "codemirror", - "jshint", + "modules/documents/resources", + "modules/pouchdb/base", + + // Libs + "codemirror", + "jshint", + + // Plugins + "plugins/codemirror-javascript", + "plugins/prettify" - // Plugins - "plugins/codemirror-javascript", - "plugins/prettify" ], -function(app, FauxtonAPI, Codemirror, JSHint) { +function(app, FauxtonAPI, Documents, pouchdb, Codemirror, JSHint) { var Views = {}; Views.Tabs = FauxtonAPI.View.extend({ @@ -276,161 +280,50 @@ function(app, FauxtonAPI, Codemirror, JSHint) { template: "templates/documents/all_docs_list", events: { "click button.all": "selectAll", - "click button.bulk-delete": "bulkDelete", - "change form.view-query-update input": "updateFilters", - "change form.view-query-update select": "updateFilters", - "submit form.view-query-update": "updateView" + "click button.bulk-delete": "bulkDelete" }, initialize: function(options){ this.nestedView = options.nestedView || Views.Document; this.rows = {}; this.viewList = !! options.viewList; - this.params = options.params; + this.database = options.database; if (options.ddocInfo) { this.designDocs = options.ddocInfo.designDocs; this.ddocID = options.ddocInfo.id; } + this.newView = options.newView || false; }, establish: function() { - var deferreds = [ - this.collection.fetch().error(function() { - // TODO: handle error requests that slip through - // This should just throw a notification, not break the page - console.log("ERROR: ", arguments); - }) - ]; - if (this.designDocs) { - deferreds.push(this.designDocs.fetch()); - } - return deferreds; + if (this.newView) { return null; } + + return this.collection.fetch().fail(function() { + // TODO: handle error requests that slip through + // This should just throw a notification, not break the page + console.log("ERROR: ", arguments); + }); }, selectAll: function(evt){ $("input:checkbox").attr('checked', !$(evt.target).hasClass('active')); }, - // TODO:: HACK:: - // Hack to grab info about the ddoc and current view to determine whether - // or not the view has a reduce function so we can display the advanced - // options appropriately. - // - // NOTE: we have this here temporarily because we have to wait for the - // design docs to be present. - // - // NOTE: We should probably refactor this View out into a separate View - // dedicated to displaying view query results. - // If nothing else, we should at least switch to something along the lines - // of fetchOnce to ensure we're not reloading the ddocs here in addition to - // the sidebar. - setDdocInfo: function() { - if (!this.ddoc && this.designDocs) { - this.ddoc = this.designDocs.get(this.ddocID); - } - }, - serialize: function() { - this.setDdocInfo(); - var data = { - database: this.collection, - viewList: this.viewList, - hasReduce: false, - params: this.params, - ddocs: this.designDocs - }; - if (this.ddoc) { - data.ddoc = this.ddoc; - data.hasReduce = this.ddoc.viewHasReduce(this.collection.view); - } - return data; - }, - - updateView: function(event) { - event.preventDefault(); - var $form = $(event.currentTarget); + var totalRows = 0, + updateSeq = false; - // Ignore params without a value - var params = _.filter($form.serializeArray(), function(param) { - return param.value; - }); - - // Validate *key* params to ensure they're valid JSON - var keyParams = ["key","keys","startkey","endkey"]; - var errorParams = _.filter(params, function(param) { - if (_.contains(keyParams, param.name)) { - try { - JSON.parse(param.value); - return false; - } catch(e) { - return true; - } - } else { - return false; - } - }); - - if (_.any(errorParams)) { - _.map(errorParams, function(param) { - - // TODO: Where to add this error? - // bootstrap wants the error on a control-group div, but we're not using that - //$('form.view-query-update input[name='+param+'], form.view-query-update select[name='+param+']').addClass('error'); - - return FauxtonAPI.addNotification({ - msg: "JSON Parse Error on field: "+param.name, - type: "error", - 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 .all-docs-list.errors-container" - }); - - return false; + if (!this.newView) { + totalRows = this.collection.totalRows(); + updateSeq = this.collection.updateSeq(); } - var fragment = window.location.hash.replace(/\?.*$/, ''); - fragment = fragment + '?' + $.param(params); - FauxtonAPI.navigate(fragment); - }, - - updateFilters: function(event) { - event.preventDefault(); - var $ele = $(event.currentTarget); - var name = $ele.attr('name'); - this.updateFiltersFor(name, $ele); - }, - - updateFiltersFor: function(name, $ele) { - var $form = $ele.parents("form.view-query-update:first"); - switch (name) { - // Reduce constraints - // - Can't include_docs for reduce=true - // - can't include group_level for reduce=false - case "reduce": - if ($ele.prop('checked') === true) { - if ($form.find("input[name=include_docs]").prop("checked") === true) { - $form.find("input[name=include_docs]").prop("checked", false); - var notification = FauxtonAPI.addNotification({ - msg: "include_docs has been disabled as you cannot include docs on a reduced view", - type: "warn", - selector: ".view.show .all-docs-list.errors-container" - }); - } - $form.find("input[name=include_docs]").prop("disabled", true); - $form.find("select[name=group_level]").prop("disabled", false); - } else { - $form.find("select[name=group_level]").prop("disabled", true); - $form.find("input[name=include_docs]").prop("disabled", false); - } - break; - case "include_docs": - break; - } + return { + updateSeq: updateSeq, + totalRows: totalRows, + numModels: this.collection.models.length, + viewList: this.viewList + }; }, /* @@ -468,14 +361,6 @@ 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 @@ -485,34 +370,6 @@ function(app, FauxtonAPI, Codemirror, JSHint) { afterRender: function(){ prettyPrint(); - if (this.params) { - var $form = this.$el.find("form.view-query-update"); - _.each(this.params, function(val, key) { - var $ele; - switch (key) { - case "limit": - case "group_level": - $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": - $ele = $form.find("input[name='"+key+"']"); - if (val == "true") { - $ele.prop('checked', true); - } - this.updateFiltersFor(key, $ele); - break; - default: - $form.find("input[name='"+key+"']").val(val); - break; - } - }, this); - } } }); @@ -523,6 +380,10 @@ function(app, FauxtonAPI, Codemirror, JSHint) { "click button.save-doc": "saveDoc" }, + initialize: function (options) { + this.database = options.database; + }, + updateValues: function() { var notification; if (this.model.changedAttributes()) { @@ -540,13 +401,15 @@ function(app, FauxtonAPI, Codemirror, JSHint) { }, saveDoc: function(event) { - var json, notification; + var json, notification, that = this; if (this.hasValidCode()) { json = JSON.parse(this.editor.getValue()); this.model.clear({silent:true}); this.model.set(json); notification = FauxtonAPI.addNotification({msg: "Saving document."}); - this.model.save().error(function(xhr) { + this.model.save().then(function () { + FauxtonAPI.navigate('/database/' + that.database.id + '/' + that.model.id); + }).fail(function(xhr) { var responseText = JSON.parse(xhr.responseText).reason; notification = FauxtonAPI.addNotification({ msg: "Save failed: " + responseText, @@ -607,7 +470,8 @@ function(app, FauxtonAPI, Codemirror, JSHint) { matchBrackets: true, lineWrapping: true, onChange: function() { - that.runJSHint(); + //throwing errors at the moment + //that.runJSHint(); }, extraKeys: { "Ctrl-S": function(instance) { that.saveDoc(); }, @@ -646,6 +510,7 @@ function(app, FauxtonAPI, Codemirror, JSHint) { } }); + //TODO split this into two smaller views, one for advance query options and other for index editing Views.ViewEditor = FauxtonAPI.View.extend({ template: "templates/documents/view_editor", builtinReduces: ['_sum', '_count', '_stats'], @@ -653,12 +518,17 @@ function(app, FauxtonAPI, Codemirror, JSHint) { events: { "click button.save": "saveView", "click button.preview": "previewView", - "change select#reduce-function-selector": "updateReduce" + "click button.delete": "deleteView", + "change select#reduce-function-selector": "updateReduce", + "change form.view-query-update input": "updateFilters", + "change form.view-query-update select": "updateFilters", + "change select#ddoc": "updateDesignDoc", + "submit form.view-query-update": "updateView" }, langTemplates: { "javascript": { - map: "function(doc) {\n emit(null, doc);\n}", + map: "function(doc) {\n emit(doc.id, 1);\n}", reduce: "function(keys, values, rereduce){\n if (rereduce){\n return sum(values);\n } else {\n return values.length;\n }\n}" } }, @@ -666,10 +536,27 @@ function(app, FauxtonAPI, Codemirror, JSHint) { defaultLang: "javascript", initialize: function(options) { + this.newView = options.newView || false; this.ddocs = options.ddocs; - this.viewCollection = options.viewCollection; - this.reduceFunStr = this.model.viewHasReduce(this.viewCollection.view); - this.newView = false; + this.params = options.params; + this.database = options.database; + if (this.newView) { + this.viewName = 'newView'; + } else { + this.ddocID = options.ddocInfo.id; + this.viewName = options.viewName; + } + }, + + updateDesignDoc: function () { + + if (this.$('#ddoc :selected').prop('id') === 'new-doc') { + this.$('#new-ddoc-section').show(); + + } else { + this.$('#new-ddoc-section').hide(); + } + }, updateValues: function() { @@ -694,43 +581,211 @@ function(app, FauxtonAPI, Codemirror, JSHint) { } }, - establish: function() { - //return [this.ddocs.fetch(), this.model.fetch()]; - return []; + queryParams: function () { + var $form = $(".view-query-update"); + // Ignore params without a value + var params = _.filter($form.serializeArray(), function(param) { + return param.value; + }); + + // Validate *key* params to ensure they're valid JSON + var keyParams = ["key","keys","startkey","endkey"]; + var errorParams = _.filter(params, function(param) { + if (_.contains(keyParams, param.name)) { + try { + JSON.parse(param.value); + return false; + } catch(e) { + return true; + } + } else { + return false; + } + }); + + return {params: params, errorParams: errorParams}; + }, + + deleteView: function (event) { + event.preventDefault(); + + if (this.newView) { return alert('Cannot delete a new view.'); } + if (!confirm('Are you sure you want to delete this view?')) {return;} + + var that = this, + promise, + viewName = this.$('#index-name').val(); + ddocName = this.$('#ddoc :selected').val(), + ddoc = this.getCurrentDesignDoc(); + + ddoc.removeDdocView(viewName); + + if (ddoc.hasViews()) { + promise = ddoc.save(); + } else { + promise = ddoc.destroy(); + } + + promise.then(function () { + FauxtonAPI.navigate('/database/' + that.database.id + '/_all_docs?limit=100'); + FauxtonAPI.triggerRouteEvent('reloadDesignDocs'); + }); + }, + + updateView: function(event) { + event.preventDefault(); + + if (this.newView) { return alert('Please save this new view before querying it.'); } + + var paramInfo = this.queryParams(), + errorParams = paramInfo.errorParams, + params = paramInfo.params; + + if (_.any(errorParams)) { + _.map(errorParams, function(param) { + + // TODO: Where to add this error? + // bootstrap wants the error on a control-group div, but we're not using that + //$('form.view-query-update input[name='+param+'], form.view-query-update select[name='+param+']').addClass('error'); + + return FauxtonAPI.addNotification({ + msg: "JSON Parse Error on field: "+param.name, + type: "error", + 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 .all-docs-list.errors-container" + }); + + return false; + } + + var fragment = window.location.hash.replace(/\?.*$/, ''); + fragment = fragment + '?' + $.param(params); + FauxtonAPI.navigate(fragment, {trigger: false}); + + FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: this.ddocID, view: this.viewName}); + }, + + updateFilters: function(event) { + event.preventDefault(); + var $ele = $(event.currentTarget); + var name = $ele.attr('name'); + this.updateFiltersFor(name, $ele); + }, + + updateFiltersFor: function(name, $ele) { + var $form = $ele.parents("form.view-query-update:first"); + switch (name) { + // Reduce constraints + // - Can't include_docs for reduce=true + // - can't include group_level for reduce=false + case "reduce": + if ($ele.prop('checked') === true) { + if ($form.find("input[name=include_docs]").prop("checked") === true) { + $form.find("input[name=include_docs]").prop("checked", false); + var notification = FauxtonAPI.addNotification({ + msg: "include_docs has been disabled as you cannot include docs on a reduced view", + type: "warn", + selector: ".view.show .all-docs-list.errors-container" + }); + } + $form.find("input[name=include_docs]").prop("disabled", true); + $form.find("select[name=group_level]").prop("disabled", false); + } else { + $form.find("select[name=group_level]").prop("disabled", true); + $form.find("input[name=include_docs]").prop("disabled", false); + } + break; + case "include_docs": + break; + } }, previewView: function(event) { + var that = this, + mapVal = this.mapEditor.getValue(), + reduceVal = this.reduceVal(), + paramsArr = this.queryParams().params; + + var params = _.reduce(paramsArr, function (params, param) { + params[param.name] = param.value; + return params; + }, {reduce: false}); + + event.preventDefault(); + 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 + fade: true }); - FauxtonAPI.addNotification({ - msg: "Preview Functionality Coming Soon", - type: "warning", - selector: "#define-view .errors-container" + + var promise = FauxtonAPI.Deferred(); + + if (!this.database.allDocs) { + this.database.buildAllDocs({limit: "100", include_docs: true}); + promise = this.database.allDocs.fetch(); + } else { + promise.resolve(); + } + + promise.then(function () { + params.docs = that.database.allDocs.map(function (model) { return model.get('doc');}); + + var queryPromise = pouchdb.runViewQuery({map: mapVal, reduce: reduceVal}, params); + queryPromise.then(function (results) { + FauxtonAPI.triggerRouteEvent('updatePreviewDocs', {rows: results.rows, ddoc: that.getCurrentDesignDoc().id, view: that.viewName}); + }); }); }, saveView: function(event) { - var json, notification; + var json, notification, + that = this; + + event.preventDefault(); + if (this.hasValidCode()) { - var mapVal = this.mapEditor.getValue(); - var reduceVal = this.reduceEditor.getValue(); - /* + var mapVal = this.mapEditor.getValue(), + reduceVal = this.reduceVal(), + viewName = this.$('#index-name').val(), + ddoc = this.getCurrentDesignDoc(), + ddocName = ddoc.id; + + this.viewName = viewName; + 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) { + + ddoc.setDdocView(viewName, mapVal, reduceVal); + + ddoc.save().then(function () { + FauxtonAPI.addNotification({ + msg: "View has been saved.", + type: "success", + selector: "#define-view .errors-container" + }); + + if (that.newView) { + var fragment = '/database/' + that.database.id +'/' + ddocName + '/_view/' + viewName; + + FauxtonAPI.navigate(fragment, {trigger: false}); + FauxtonAPI.triggerRouteEvent('reloadDesignDocs',{selectedTab: ddocName.replace('_design/','') + '_' + viewName}); + + that.newView = false; + } + + FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: ddocName, view: viewName}); + + }, function(xhr) { var responseText = JSON.parse(xhr.responseText).reason; notification = FauxtonAPI.addNotification({ msg: "Save failed: " + responseText, @@ -738,21 +793,51 @@ function(app, FauxtonAPI, Codemirror, JSHint) { clear: true }); }); - */ } else { notification = FauxtonAPI.addNotification({ - msg: "Please fix the JSON errors and try again.", + msg: "Please fix the Javascript errors and try again.", type: "error", selector: "#define-view .errors-container" }); } }, + getCurrentDesignDoc: function () { + if (this.newDesignDoc()) { + var doc = { + _id: '_design/' + this.$('#new-ddoc').val(), + views: {}, + language: "javascript" + }; + return new Documents.Doc(doc, {database: this.database}); + } else { + var ddocName = this.$('#ddoc').val(); + return this.ddocs.find(function (ddoc) { + return ddoc.id === ddocName; + }).dDocModel(); + } + + }, + + newDesignDoc: function () { + return this.$('#ddoc :selected').prop('id') === 'new-doc'; + }, + isCustomReduceEnabled: function() { return $("#reduce-function-selector").val() == "CUSTOM"; }, reduceVal: function() { + var reduceOption = this.$('#reduce-function-selector :selected').val(), + reduceVal = ""; + + if (reduceOption === 'CUSTOM') { + reduceVal = this.reduceEditor.getValue(); + } else if ( reduceOption !== 'NONE') { + reduceVal = reduceOption; + } + + return reduceVal; }, hasValidCode: function() { @@ -765,7 +850,7 @@ function(app, FauxtonAPI, Codemirror, JSHint) { } else { // By default CouchDB view functions don't pass lint return _.every(JSHINT.errors, function(error) { - return FauxtonAPI.isIgnorableError(error.reason); + return FauxtonAPI.isIgnorableError(error.raw); }); } }, this); @@ -801,13 +886,15 @@ function(app, FauxtonAPI, Codemirror, JSHint) { serialize: function() { return { - //database: this.model, ddocs: this.ddocs, ddoc: this.model, - viewCollection: this.viewCollection, + ddocName: this.model.id, + viewName: this.viewName, reduceFunStr: this.reduceFunStr, + hasReduce: this.reduceFunStr, isCustomReduce: this.hasCustomReduce(), - newView: this.newView + newView: this.newView, + langTemplates: this.langTemplates.javascript }; }, @@ -815,6 +902,22 @@ function(app, FauxtonAPI, Codemirror, JSHint) { return this.reduceFunStr && ! _.contains(this.builtinReduces, this.reduceFunStr); }, + beforeRender: function () { + + if (this.newView) { + this.reduceFunStr = '_sum'; + if (this.ddocs.length === 0) { + this.model = new Documents.Doc(null, {database: this.database}); + } else { + this.model = this.ddocs.first().dDocModel(); + } + this.ddocID = this.model.id; + } else { + this.model = this.ddocs.get(this.ddocID).dDocModel(); + this.reduceFunStr = this.model.viewHasReduce(this.viewName); + } + }, + afterRender: function() { var that = this; var mapFun = $("#map-function"); @@ -823,13 +926,16 @@ function(app, FauxtonAPI, Codemirror, JSHint) { mapFun.val(this.langTemplates[this.defaultLang].map); reduceFun.val(this.langTemplates[this.defaultLang].reduce); } + + this.updateDesignDoc(); + this.mapEditor = Codemirror.fromTextArea(mapFun.get()[0], { mode: "javascript", lineNumbers: true, matchBrackets: true, lineWrapping: true, onChange: function() { - that.runJSHint("mapEditor"); + //that.runJSHint("mapEditor"); }, extraKeys: { "Ctrl-S": function(instance) { that.saveView(); }, @@ -842,7 +948,7 @@ function(app, FauxtonAPI, Codemirror, JSHint) { matchBrackets: true, lineWrapping: true, onChange: function() { - that.runJSHint("reduceEditor"); + //that.runJSHint("reduceEditor"); }, extraKeys: { "Ctrl-S": function(instance) { that.saveView(); }, @@ -855,6 +961,36 @@ function(app, FauxtonAPI, Codemirror, JSHint) { if ( ! this.hasCustomReduce()) { $(".control-group.reduce-function").hide(); } + + if (this.params) { + var $form = this.$el.find("form.view-query-update"); + _.each(this.params, function(val, key) { + var $ele; + switch (key) { + case "limit": + case "group_level": + $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": + $ele = $form.find("input[name='"+key+"']"); + if (val == "true") { + $ele.prop('checked', true); + } + this.updateFiltersFor(key, $ele); + break; + default: + $form.find("input[name='"+key+"']").val(val); + break; + } + }, this); + } + } }); @@ -871,14 +1007,6 @@ function(app, FauxtonAPI, Codemirror, JSHint) { } }, - establish: function() { - if (this.collection) { - return [this.collection.fetch()]; - } else { - return null; - } - }, - serialize: function() { return { index: [1,2,3], @@ -929,7 +1057,14 @@ function(app, FauxtonAPI, Codemirror, JSHint) { }, this); }, + afterRender: function () { + if (this.selectedTab) { + this.setSelectedTab(this.selectedTab); + } + }, + setSelectedTab: function (selectedTab) { + this.selectedTab = selectedTab; this.$('li').removeClass('active'); this.$('#' + selectedTab).parent().addClass('active'); } @@ -942,20 +1077,24 @@ function(app, FauxtonAPI, Codemirror, JSHint) { template: "templates/documents/changes", establish: function() { - return [ - this.model.changes.fetch() - ]; + return [ this.model.changes.fetch()]; }, serialize: function () { + console.log('c', this.model.changes.toJSON()); return { changes: this.model.changes.toJSON(), database: this.model }; + }, + + afterRender: function(){ + prettyPrint(); } - }); + }); - return Views; + Documents.Views = Views; + return Documents; }); diff --git a/src/fauxton/app/modules/pouchdb/base.js b/src/fauxton/app/modules/pouchdb/base.js index 2b7cfc927..ddaf06d5c 100644 --- a/src/fauxton/app/modules/pouchdb/base.js +++ b/src/fauxton/app/modules/pouchdb/base.js @@ -31,8 +31,8 @@ function(app, FauxtonAPI, MapReduce) { var Pouch = {}; Pouch.MapReduce = MapReduce; - Pouch.runViewQuery = function(fun, docs) { - docs = [ + Pouch.runViewQuery = function(fun, opts) { + /*docs = [ {_id: 'test_doc_1', foo: 'bar-1'}, {_id: 'test_doc_2', foo: 'bar-2'}, {_id: 'test_doc_3', foo: 'bar-3'}, @@ -43,15 +43,18 @@ function(app, FauxtonAPI, MapReduce) { {_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); + var complete = function(resp, rows) { + deferred.resolve(rows); }; - return Pouch.MapReduce.query(fun, {docs: docs, complete:complete}); - }; + var options = _.extend(opts, {complete: complete}); + Pouch.MapReduce.query(fun, options); + return deferred; + }; + //pdb.runViewQuery({map:function(doc) { emit(doc._id, doc.foo) }}) return Pouch; }); diff --git a/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js b/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js index b97eb7fc3..a2d0b91b5 100644 --- a/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js +++ b/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js @@ -39,6 +39,36 @@ function(app, FauxtonAPI, Collate) { //var MapReduce = function(db) { var MapReduce = function() { + var builtInReduce = { + "_sum": function(keys, values){ + return sum(values); + }, + + "_count": function(keys, values, rereduce){ + if (rereduce){ + return sum(values); + } else { + return values.length; + } + }, + + "_stats": function(keys, values, rereduce){ + return { + 'sum': sum(values), + 'min': Math.min.apply(null, values), + 'max': Math.max.apply(null, values), + 'count': values.length, + 'sumsqr': (function(){ + _sumsqr = 0; + for(var idx in values){ + _sumsqr += values[idx] * values[idx]; + } + return _sumsqr; + })() + }; + } + }; + function viewQuery(fun, options) { console.log("IN VIEW QUERY"); if (!options.complete) { @@ -55,13 +85,13 @@ function(app, FauxtonAPI, Collate) { var completed= false; var emit = function(key, val) { - console.log("IN EMIT: ", key, val, current); + //console.log("IN EMIT: ", key, val, current); var viewRow = { id: current.doc._id, key: key, value: val }; - console.log("VIEW ROW: ", viewRow); + //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; @@ -95,7 +125,11 @@ function(app, FauxtonAPI, Collate) { // 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) { + if (fun.reduce && options.reduce) { + if (builtInReduce[fun.reduce]) { + console.log('built in reduce'); + fun.reduce = builtInReduce[fun.reduce]; + } eval('fun.reduce = ' + fun.reduce.toString() + ';'); } @@ -105,6 +139,7 @@ function(app, FauxtonAPI, Collate) { //only proceed once all documents are mapped and joined var checkComplete= function(){ + console.log('check'); if (completed && results.length == num_started){ results.sort(function(a, b) { return Pouch.collate(a.key, b.key); @@ -116,6 +151,7 @@ function(app, FauxtonAPI, Collate) { return options.complete(null, {rows: results}); } + console.log('reducing', options); var groups = []; results.forEach(function(e) { var last = groups[groups.length-1] || null; @@ -130,19 +166,21 @@ function(app, FauxtonAPI, Collate) { e.value = fun.reduce(e.key, e.value) || null; e.key = e.key[0][0]; }); + console.log('GROUPs', groups); options.complete(null, {rows: groups}); } }; if (options.docs) { - console.log("RUNNING MR ON DOCS: ", 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}); + completed = true; + return checkComplete();//options.complete(null, {rows: results}); } else { - console.log("COULD NOT FIND DOCS"); + //console.log("COULD NOT FIND DOCS"); return false; } diff --git a/src/fauxton/app/templates/documents/all_docs_list.html b/src/fauxton/app/templates/documents/all_docs_list.html index 2f63af038..9f4ffddfb 100644 --- a/src/fauxton/app/templates/documents/all_docs_list.html +++ b/src/fauxton/app/templates/documents/all_docs_list.html @@ -28,97 +28,10 @@ the License. </div> <% } %> - <div class="row"> - <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="stale" type="checkbox" value="ok"> Stale - </label> - <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 class="controls controls-row"> - <button type="submit" class="btn btn-primary">Query</button> - </div> - </form> - - </div> - </div> - - </div> - </div> - <% } %> - </div> - - <p> - Showing 1-<%= database.models.length %> of <%= database.totalRows() %> rows - <% if (database.updateSeq()) { %> - -- Update Sequence: <%= database.updateSeq() %> + Showing 1-<%= numModels %> of <%= totalRows %> rows + <% if (updateSeq) { %> + -- Update Sequence: <%= updateSeq %> <% } %> </p> <table class="all-docs table table-striped table-condensed"> diff --git a/src/fauxton/app/templates/documents/changes.html b/src/fauxton/app/templates/documents/changes.html index 3e5009c0f..e528a1c4a 100644 --- a/src/fauxton/app/templates/documents/changes.html +++ b/src/fauxton/app/templates/documents/changes.html @@ -28,7 +28,9 @@ the License. <% } else { %> <td> <a href="#<%= database.url('app') %>/<%= change.id %>"><%= change.id %></a> </td> <% } %> - <td> <%= JSON.stringify(change.changes) %> </td> + <td> + <pre class="prettyprint"> <%= JSON.stringify({changes: change.changes, doc: change.doc}, null, " ") %> </pre> + </td> <td><%= change.deleted ? "true" : "false" %></td> </tr> <% }); %> diff --git a/src/fauxton/app/templates/documents/view_editor.html b/src/fauxton/app/templates/documents/view_editor.html index 4a8668e29..a34ff0dc3 100644 --- a/src/fauxton/app/templates/documents/view_editor.html +++ b/src/fauxton/app/templates/documents/view_editor.html @@ -11,82 +11,184 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> +<div class="row"> + <div class="all-docs-list errors-container"></div> + <div id="edit-index-container"> -<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 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 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> <% if (newView) { %> Create Index <% } else { %> Edit Index <% } %> + </a> + </div> + <div id="collapse-edit-index" class="accordion-body <% if (!newView) { %> 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"> + <div class="row" style="margin-left:10px"> + <div class="span3"> + <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 id="new-doc">New document</option> + <% ddocs.each(function(ddoc) { %> + <% if (ddoc.id === ddocName) { %> + <option selected="selected"><%= ddoc.id %></option> + <% } else { %> + <option><%= ddoc.id %></option> + <% } %> + <% }); %> + </optgroup> + </select> + </div> + </div> + <div id="new-ddoc-section" class="span5" style="display:none"> + <label class="control-label" for="new-ddoc"> _design/ </label> + <div class="controls"> + <input type="text" id="new-ddoc" placeholder="newDesignDoc"> + </div> + </div> + </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="<%= viewName %>" 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"> + <% if (newView) { %> + <textarea class="js-editor" id="map-function"><%= langTemplates.map %></textarea> + <% } else { %> + <textarea class="js-editor" id="map-function"><%= ddoc.get('views')[viewName].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.html#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"> + <% if (newView) { %> + <textarea class="js-editor" id="reduce-function"><%= langTemplates.reduce %></textarea> + <% } else { %> + <textarea class="js-editor" id="reduce-function"><%= ddoc.get('views')[viewName].reduce %></textarea> + <% } %> + </div> + </div> + <div class="control-group"> + <hr /> + <div class="controls"> + <% if (!this.newView) { %> + <button class="btn btn-small btn-danger delete">Delete</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 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> + </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"> + <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> - <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 class="controls controls-row"> + <input name="key" class="span4" type="text" placeholder="Key"> + <input name="keys" class="span8" type="text" placeholder="Keys"> </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 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> - <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 class="controls controls-row"> + <label class="span2 checkbox inline"> + <input name="stale" type="checkbox" value="ok"> Stale + </label> + <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="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 class="controls controls-row"> + <button type="submit" class="btn btn-primary">Query</button> </div> - </div> - <div class="clearfix"></div> - </form> + </form> + + </div> </div> + </div> </div> - </div> -</div> diff --git a/src/fauxton/app/templates/layouts/with_tabs_sidebar.html b/src/fauxton/app/templates/layouts/with_tabs_sidebar.html index f78832fe4..0b5f2c7a1 100644 --- a/src/fauxton/app/templates/layouts/with_tabs_sidebar.html +++ b/src/fauxton/app/templates/layouts/with_tabs_sidebar.html @@ -21,7 +21,10 @@ the License. <div class="row-fluid"> <div id="sidebar-content" class="sidebar span4"></div> - <div id="dashboard-content" class="list span8 pull-right"></div> + <div id="dashboard-content" class="list span8 pull-right"> + <div id="dashboard-upper-content"></div> + <div id="dashboard-lower-content"></div> + </div> </div> </div> |