You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2014/02/12 07:19:58 UTC
[04/52] [abbrv] Fauxton: move modules to addons
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/templates/index_row_tabular.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/index_row_tabular.html b/app/addons/documents/templates/index_row_tabular.html
new file mode 100644
index 0000000..f5f68fa
--- /dev/null
+++ b/app/addons/documents/templates/index_row_tabular.html
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+
+<td class="select"><input type="checkbox"></td>
+<td>
+ <div>
+ <pre class="prettyprint"><%- JSON.stringify(doc.get("key")) %></pre>
+ </div>
+</td>
+<td>
+ <div>
+ <pre class="prettyprint"><%- JSON.stringify(doc.get("value")) %></pre>
+ </div>
+</td>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/templates/jumpdoc.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/jumpdoc.html b/app/addons/documents/templates/jumpdoc.html
new file mode 100644
index 0000000..43fdb9c
--- /dev/null
+++ b/app/addons/documents/templates/jumpdoc.html
@@ -0,0 +1,19 @@
+<!--
+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.
+-->
+
+<form id="jump-to-doc" class="form-inline input-append" >
+ <input type="text" id="jump-to-doc-id" class="input-large" placeholder="Document ID"></input>
+
+ <button class="fonticon-search btn button red " type="submit"></button>
+</form>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/templates/search.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/search.html b/app/addons/documents/templates/search.html
new file mode 100644
index 0000000..bb84891
--- /dev/null
+++ b/app/addons/documents/templates/search.html
@@ -0,0 +1,15 @@
+<!--
+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.
+-->
+
+<input id="searchbox" type="text" class="span12" placeholder="Search by doc id, view key or search index">
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/templates/sidebar.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/sidebar.html b/app/addons/documents/templates/sidebar.html
new file mode 100644
index 0000000..8a73ae9
--- /dev/null
+++ b/app/addons/documents/templates/sidebar.html
@@ -0,0 +1,67 @@
+<!--
+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.
+-->
+
+<div id="sidenav">
+ <header class="row-fluid">
+ <div class="span12">
+ <div class="btn-group">
+ <button class="btn dropdown-toggle dropdown-toggle-btn" data-toggle="dropdown">
+ Docs
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <!-- dropdown menu links -->
+ <li><a class="icon-file" href="<%= db_url %>">Docs</a></li>
+ <li><a class="icon-lock" href="<%= permissions_url %>">Permissions</a></li>
+ <li><a class="icon-forward" href="<%= changes_url %>">Changes</a></li>
+ <% _.each(docLinks, function (link) { %>
+ <li><a class="<%= link.icon %>" href="<%= database_url + '/' + link.url %>"><%= link.title %></a></li>
+ <% }); %>
+ </ul>
+ </div>
+
+ <div class="btn-group">
+ <button class="btn dropdown-toggle dropdown-toggle-btn" data-toggle="dropdown">
+ New
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <!-- dropdown menu links -->
+ <li>
+ <a id="doc" href="#<%= database.url('app') %>/new">Document</a>
+ </li>
+ <li>
+ <a href="#<%= database.url('app') %>/new_view">Secondary Index</a>
+ <% _.each(newLinks, function (item) { %>
+ <a href="#<%= database.url('app') %>/<%=item.url%>"> <%= item.name %></a>
+ <% }); %>
+ </li>
+ </ul>
+ </div>
+ <button id="delete-database" class="btn"><i class="icon-trash"></i> Database</button>
+ </div>
+ </header>
+
+ <nav>
+ <ul class="nav nav-list">
+ <li class="active"><a id="all-docs" href="#<%= database.url('index') %>?limit=<%= docLimit %>" class="toggle-view"> All documents</a></li>
+ <li><a id="design-docs" href='#<%= database.url("index") %>?limit=<%= docLimit %>&startkey="_design"&endkey="_e"' class="toggle-view"> All design docs</a></li>
+ </ul>
+ <ul class="nav nav-list views">
+ <li class="nav-header">Secondary Indexes</li>
+ <li><a id="new-view" href="#<%= database.url('app') %>/new_view" class="new"><i class="icon-plus"></i> New</a></li>
+ </ul>
+ <div id="extension-navs"></div>
+ </nav>
+</div>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/templates/tabs.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/tabs.html b/app/addons/documents/templates/tabs.html
new file mode 100644
index 0000000..f8b0c4b
--- /dev/null
+++ b/app/addons/documents/templates/tabs.html
@@ -0,0 +1,18 @@
+<!--
+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.
+-->
+
+<ul class="nav nav-tabs">
+ <li class="active"><a href="<%= db_url %>">Docs</a></li>
+ <li id="changes"><a href="<%= changes_url %>">Changes</a></li>
+</ul>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/templates/upload_modal.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/upload_modal.html b/app/addons/documents/templates/upload_modal.html
new file mode 100644
index 0000000..9a5c5cd
--- /dev/null
+++ b/app/addons/documents/templates/upload_modal.html
@@ -0,0 +1,42 @@
+<!--
+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.
+-->
+
+<div class="modal hide fade">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+ <h3>Upload an Attachment</h3>
+ </div>
+ <div class="modal-body">
+ <div id="modal-error" class="alert alert-error hide" style="font-size: 16px;"> </div>
+ <form id="file-upload" class="form" method="post">
+ <p class="help-block">
+ Please select the file you want to upload as an attachment to this document.
+ Please note that this will result in the immediate creation of a new revision of the document,
+ so it's not necessary to save the document after the upload.
+ </p>
+ <input id="_attachments" type="file" name="_attachments">
+ <input id="_rev" type="hidden" name="_rev" value="" >
+ <br/>
+ </form>
+
+ <div class="progress progress-info">
+ <div class="bar" style="width: 0%"></div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <a href="#" data-dismiss="modal" data-bypass="true" class="btn button cancel-button outlineGray fonticon-circle-x">Cancel</a>
+ <a href="#" id="upload-btn" data-bypass="true" class="btn btn-primary button green save fonticon-circle-check">Upload</a>
+ </div>
+</div>
+
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/templates/view_editor.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/view_editor.html b/app/addons/documents/templates/view_editor.html
new file mode 100644
index 0000000..ddb5a0c
--- /dev/null
+++ b/app/addons/documents/templates/view_editor.html
@@ -0,0 +1,87 @@
+<!--
+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.
+-->
+<div class="row">
+ <ul class="nav nav-tabs" id="db-views-tabs-nav">
+ <li class="active"> <a data-bypass="true" id="index-nav" class="fonticon-wrench fonticon" data-toggle="tab" href="#index"><% if (newView) { %>Create Index <% } else { %>Edit Index <% } %></a></li>
+ <% if (!newView) { %>
+ <li><a data-bypass="true" id="query-nav" class="fonticon-plus fonticon" href="#query" data-toggle="tab">Query Options</a></li>
+ <li><a data-bypass="true" id="meta-nav" href="#metadata" data-toggle="tab">Design Doc Metadata</a></li>
+ <% } %>
+ </ul>
+ <div class="all-docs-list errors-container"></div>
+ <div class="tab-content">
+ <div class="tab-pane active" id="index">
+ <div id="define-view" class="ddoc-alert well">
+ <div class="errors-container"></div>
+ <form class="form-horizontal view-query-save">
+
+ <div class="control-group design-doc-group">
+ </div>
+
+ <div class="control-group">
+ <label for="index-name">Index name <a href="<%=getDocUrl('view_functions')%>" target="_blank"><i class="icon-question-sign"></i></a></label>
+ <input type="text" id="index-name" value="<%= viewName %>" placeholder="Index name" />
+ </div>
+
+
+ <div class="control-group">
+ <label for="map-function">Map function <a href="<%=getDocUrl('map_functions')%>" target="_blank"><i class="icon-question-sign"></i></a></label>
+ <% if (newView) { %>
+ <div class="js-editor" id="map-function"><%= langTemplates.map %></div>
+ <% } else { %>
+ <div class="js-editor" id="map-function"><%- ddoc.get('views')[viewName].map %></div>
+ <% } %>
+ </div>
+
+
+ <div class="control-group">
+ <label for="reduce-function-selector">Reduce (optional) <a href="<%=getDocUrl('reduce_functions')%>" target="_blank"><i class="icon-question-sign"></i></a></label>
+
+ <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 function</option>
+ </select>
+ </div>
+
+ <div class="control-group reduce-function">
+ <label for="reduce-function">Custom Reduce function</label>
+ <% if (newView) { %>
+ <div class="js-editor" id="reduce-function"><%= langTemplates.reduce %></div>
+ <% } else { %>
+ <div class="js-editor" id="reduce-function"><%- ddoc.get('views')[viewName].reduce %></div>
+ <% } %>
+ </div>
+
+ <div class="control-group">
+ <button class="button green save fonticon-circle-check">Save & Build Index</button>
+ <button class="button btn-info preview">Preview</button>
+ <% if (!newView) { %>
+ <button class="button delete outlineGray fonticon-circle-x">Delete</button>
+ <% } %>
+ </div>
+ <div class="clearfix"></div>
+ </form>
+ </div>
+ </div>
+ <div class="tab-pane" id="metadata">
+ <div id="ddoc-info" class="well"> </div>
+ </div>
+ <div class="tab-pane" id="query">
+ </div>
+ </div>
+</div>
+
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/resourcesSpec.js b/app/addons/documents/tests/resourcesSpec.js
new file mode 100644
index 0000000..380a4e4
--- /dev/null
+++ b/app/addons/documents/tests/resourcesSpec.js
@@ -0,0 +1,84 @@
+// 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.
+define([
+ 'addons/documents/resources',
+ 'testUtils'
+], function (Models, testUtils) {
+ var assert = testUtils.assert;
+
+ describe('IndexCollection', function () {
+ var collection;
+ beforeEach(function () {
+ collection = new Models.IndexCollection([{
+ id:'myId1',
+ doc: 'num1'
+ },
+ {
+ id:'myId2',
+ doc: 'num2'
+ }], {
+ database: {id: 'databaseId', safeID: function () { return this.id; }},
+ design: '_design/myDoc'
+ });
+
+ });
+
+ it('Should return urlNext', function () {
+ var url = collection.urlNextPage(20);
+
+ assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=21&reduce=false&startkey_docid=myId2&startkey=');
+
+ });
+
+ it('Should return urlPrevious', function () {
+ var url = collection.urlPreviousPage(20, {limit: 21, reduce: false, startkey_docid: "myId1",startkey:"myId1"} );
+
+ assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=20&reduce=false&startkey_docid=myId1&startkey=myId1');
+
+ });
+
+ });
+
+ describe('AllDocs', function () {
+ var collection;
+ beforeEach(function () {
+ collection = new Models.AllDocs([{
+ _id:'myId1',
+ doc: 'num1'
+ },
+ {
+ _id:'myId2',
+ doc: 'num2'
+ }], {
+ database: {id: 'databaseId', safeID: function () { return this.id; }},
+ params: {limit: 20}
+ });
+
+ });
+
+ it('Should return urlNext', function () {
+ var url = collection.urlNextPage(20);
+
+ assert.equal(url, 'database/databaseId/_all_docs?limit=21&startkey_docid=%22myId2%22&startkey=%22myId2%22');
+
+ });
+
+ it('Should return urlPrevious', function () {
+ var url = collection.urlPreviousPage(20, {limit: 21, startkey_docid: "myId1",startkey:"myId1"} );
+ assert.equal(url, 'database/databaseId/_all_docs?limit=20&startkey_docid=myId1&startkey=myId1');
+ });
+
+
+ });
+
+});
+
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js
new file mode 100644
index 0000000..97d58e3
--- /dev/null
+++ b/app/addons/documents/views.js
@@ -0,0 +1,1855 @@
+// 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.
+
+define([
+ "app",
+
+ "api",
+ "addons/fauxton/components",
+
+ "addons/documents/resources",
+ "addons/databases/resources",
+ "addons/pouchdb/base",
+
+ // Libs
+ "resizeColumns",
+
+ // Plugins
+ "plugins/prettify"
+
+],
+
+function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColumns) {
+ var Views = {};
+ Views.Tabs = FauxtonAPI.View.extend({
+ template: "templates/documents/tabs",
+ initialize: function(options){
+ this.collection = options.collection;
+ this.database = options.database;
+ this.active_id = options.active_id;
+ },
+
+ events: {
+ "click #delete-database": "delete_database"
+ },
+
+ serialize: function () {
+ return {
+ // TODO make this not hard coded here
+ changes_url: '#' + this.database.url('changes'),
+ db_url: '#' + this.database.url('index') + '?limit=' + Databases.DocLimit,
+ };
+ },
+
+ beforeRender: function(manage) {
+ this.insertView("#search", new Views.SearchBox({
+ collection: this.collection,
+ database: this.database.id
+ }));
+ },
+
+ afterRender: function () {
+ if (this.active_id) {
+ this.$('.active').removeClass('active');
+ this.$('#'+this.active_id).addClass('active');
+ }
+ },
+
+ delete_database: function (event) {
+ event.preventDefault();
+
+ var result = confirm("Are you sure you want to delete this database?");
+
+ if (!result) { return; }
+ FauxtonAPI.addNotification({
+ msg: "Deleting your database...",
+ type: "error"
+ });
+ return this.database.destroy().done(function () {
+ app.router.navigate('#/_all_dbs', {trigger: true});
+ });
+ }
+ });
+
+ Views.SearchBox = FauxtonAPI.View.extend({
+ template: "templates/documents/search",
+ tagName: "form",
+ initialize: function(options){
+ this.collection = options.collection;
+ this.database = options.database;
+ },
+ afterRender: function(){
+ var collection = this.collection;
+ var form = this.$el;
+ var searchbox = form.find("input#searchbox");
+ var database = this.database;
+
+ form.submit(function(evt){
+ evt.preventDefault();
+ var viewname = form.find("input#view").val().split('/');
+ var url = "#database/" + database + "/_design/";
+ url += viewname[0] + "/_view/" + viewname[1];
+ if (searchbox.val() !== ""){
+ // TODO: this'll need to work when val() is a number etc.
+ url += '?startkey="' + searchbox.val() + '"';
+ }
+ FauxtonAPI.navigate(url);
+ });
+
+ searchbox.typeahead({
+ source: function(query, process) {
+ // TODO: include _all_docs and view keys somehow
+ var views = _.map(collection.pluck('doc'), function(d){
+ return _.map(_.keys(d.views), function(view){
+ return d._id.split('/')[1] + "/" + view;
+ });
+ });
+ return _.flatten(views);
+ },
+ minLength: 3,
+ updater: function(item){
+ // TODO: some way to return the original search box
+ this.$element.removeClass('span12');
+ this.$element.addClass('span6');
+ this.$element.attr('placeholder', 'Search by view key');
+ $('<span class="add-on span6">' + item +'</span>').insertBefore(this.$element);
+ $('<input type="hidden" id="view" value="' + item +'"/>').insertBefore(this.$element);
+ // Remove the type ahead for now
+ $('.typehead').unbind();
+ }
+ });
+ }
+ });
+
+ Views.UploadModal = FauxtonAPI.View.extend({
+ template: "templates/documents/upload_modal",
+
+ disableLoader: true,
+
+ initialize: function (options) {
+ _.bindAll(this);
+ },
+
+ events: {
+ "click a#upload-btn": "uploadFile"
+ },
+
+ uploadFile: function (event) {
+ event.preventDefault();
+
+ var docRev = this.model.get('_rev'),
+ that = this,
+ $form = this.$('#file-upload');
+
+ if (!docRev) {
+ return this.set_error_msg('The document needs to be saved before adding an attachment.');
+ }
+
+ if ($('input[type="file"]')[0].files.length === 0) {
+ return this.set_error_msg('Selected a file to be uploaded.');
+ }
+
+ this.$('#_rev').val(docRev);
+
+ $form.ajaxSubmit({
+ url: this.model.url(),
+ type: 'POST',
+ beforeSend: this.beforeSend,
+ uploadProgress: this.uploadProgress,
+ success: this.success,
+ error: function (resp) {
+ console.log('ERR on upload', resp);
+ return that.set_error_msg('Could not upload document: ' + JSON.parse(resp.responseText).reason);
+ }
+ });
+ },
+
+ success: function (resp) {
+ var hideModal = this.hideModal,
+ $form = this.$('#file-upload');
+
+ FauxtonAPI.triggerRouteEvent('reRenderDoc');
+ //slight delay to make this transistion a little more fluid and less jumpy
+ setTimeout(function () {
+ $form.clearForm();
+ hideModal();
+ $('.modal-backdrop').remove();
+ }, 1000);
+ },
+
+ uploadProgress: function(event, position, total, percentComplete) {
+ this.$('.bar').css({width: percentComplete + '%'});
+ },
+
+ beforeSend: function () {
+ this.$('.progress').removeClass('hide');
+ },
+
+ showModal: function () {
+ this.$('.bar').css({width: '0%'});
+ this.$('.progress').addClass('hide');
+ this.clear_error_msg();
+ this.$('.modal').modal();
+ // hack to get modal visible
+ $('.modal-backdrop').css('z-index',1025);
+ },
+
+ hideModal: function () {
+ this.$('.modal').modal('hide');
+ },
+
+ set_error_msg: function (msg) {
+ var text;
+ if (typeof(msg) == 'string') {
+ text = msg;
+ } else {
+ text = JSON.parse(msg.responseText).reason;
+ }
+ this.$('#modal-error').text(text).removeClass('hide');
+ },
+
+ clear_error_msg: function () {
+ this.$('#modal-error').text(' ').addClass('hide');
+ },
+
+ serialize: function () {
+ return this.model.toJSON();
+ }
+ });
+
+ Views.DuplicateDocModal = FauxtonAPI.View.extend({
+ template: "templates/documents/duplicate_doc_modal",
+
+ initialize: function () {
+ _.bindAll(this);
+ },
+
+ events: {
+ "click #duplicate-btn":"duplicate"
+
+ },
+
+ duplicate: function (event) {
+ event.preventDefault();
+ var newId = this.$('#dup-id').val(),
+ encodedID = app.mixins.safeURLName(newId);
+
+ this.hideModal();
+ FauxtonAPI.triggerRouteEvent('duplicateDoc', encodedID);
+ },
+
+ _showModal: function () {
+ this.$('.bar').css({width: '0%'});
+ this.$('.progress').addClass('hide');
+ this.clear_error_msg();
+ this.$('.modal').modal();
+ // hack to get modal visible
+ $('.modal-backdrop').css('z-index',1025);
+ },
+
+ showModal: function () {
+ var showModal = this._showModal,
+ setDefaultIdValue = this.setDefaultIdValue,
+ uuid = new FauxtonAPI.UUID();
+
+ uuid.fetch().then(function () {
+ setDefaultIdValue(uuid.next());
+ showModal();
+ });
+ },
+
+ setDefaultIdValue: function (id) {
+ this.$('#dup-id').val(id);
+ },
+
+ hideModal: function () {
+ this.$('.modal').modal('hide');
+ },
+
+ set_error_msg: function (msg) {
+ var text;
+ if (typeof(msg) == 'string') {
+ text = msg;
+ } else {
+ text = JSON.parse(msg.responseText).reason;
+ }
+ this.$('#modal-error').text(text).removeClass('hide');
+ },
+
+ clear_error_msg: function () {
+ this.$('#modal-error').text(' ').addClass('hide');
+ },
+
+ serialize: function () {
+ return this.model.toJSON();
+ }
+
+ });
+
+ Views.FieldEditorTabs = FauxtonAPI.View.extend({
+ template: "templates/documents/doc_field_editor_tabs",
+ disableLoader: true,
+ initialize: function(options) {
+ this.selected = options.selected;
+ },
+
+ events: {
+ },
+ updateSelected: function (selected) {
+ this.selected = selected;
+ this.$('.active').removeClass('active');
+ this.$('#'+this.selected).addClass('active');
+ },
+
+ serialize: function() {
+ var selected = this.selected;
+ return {
+ doc: this.model,
+ isNewDoc: this.model.isNewDoc(),
+ isSelectedClass: function(item) {
+ return item && item === selected ? "active" : "";
+ }
+ };
+ },
+
+ establish: function() {
+ return [this.model.fetch()];
+ }
+ });
+
+ Views.Document = FauxtonAPI.View.extend({
+ template: "templates/documents/all_docs_item",
+ tagName: "tr",
+ className: "all-docs-item",
+
+ events: {
+ "click button.delete": "destroy",
+ "dblclick pre.prettyprint": "edit"
+ },
+
+ attributes: function() {
+ return {
+ "data-id": this.model.id
+ };
+ },
+
+ serialize: function() {
+ return {
+ doc: this.model
+ };
+ },
+
+ establish: function() {
+ return [this.model.fetch()];
+ },
+
+ edit: function(event) {
+ event.preventDefault();
+ FauxtonAPI.navigate("#" + this.model.url('app'));
+ },
+
+ destroy: function(event) {
+ event.preventDefault();
+ var that = this;
+
+ if (!window.confirm("Are you sure you want to delete this doc?")) {
+ return false;
+ }
+
+ this.model.destroy().then(function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Succesfully destroyed your doc"
+ });
+ that.$el.fadeOut(function () {
+ that.remove();
+ });
+
+ that.model.collection.remove(that.model.id);
+ if (!!that.model.id.match('_design')) {
+ FauxtonAPI.triggerRouteEvent('reloadDesignDocs');
+ }
+ }, function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Failed to destroy your doc!",
+ type: "error"
+ });
+ });
+ }
+ });
+
+ Views.Row = FauxtonAPI.View.extend({
+ template: "templates/documents/index_row_docular",
+ tagName: "tr",
+
+ events: {
+ "click button.delete": "destroy"
+ },
+
+ destroy: function (event) {
+ event.preventDefault();
+
+ window.alert('Cannot delete a document generated from a view.');
+ },
+
+ serialize: function() {
+ return {
+ doc: this.model
+ };
+ }
+ });
+
+ 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,
+ index_clean: app.mixins.removeSpecialCharacters(this.index),
+ ddoc_clean: app.mixins.removeSpecialCharacters(this.ddoc),
+ index_encoded: app.mixins.safeURLName(this.index),
+ ddoc_encoded: app.mixins.safeURLName(this.ddoc),
+ database_encoded: app.mixins.safeURLName(this.database),
+ selected: this.selected
+ };
+ },
+
+ afterRender: function() {
+ if (this.selected) {
+ $("#sidenav ul.nav-list li").removeClass("active");
+ this.$el.addClass("active");
+ }
+ }
+ });
+
+ Views.AllDocsNumber = FauxtonAPI.View.extend({
+ template: "templates/documents/all_docs_number",
+
+ initialize: function (options) {
+ this.newView = options.newView || false;
+ this.showNumbers = options.showNumbers;
+ this.pagination = options.pagination;
+
+ this.listenTo(this.collection, 'totalRows:decrement', this.render);
+ },
+
+ serialize: function () {
+ var totalRows = 0,
+ recordStart = 0,
+ updateSeq = false,
+ pageStart = 0,
+ pageEnd = 20;
+
+ if (!this.newView) {
+ totalRows = this.collection.totalRows();
+ updateSeq = this.collection.updateSeq();
+ }
+
+ recordStart = this.collection.recordStart();
+ if (this.pagination) {
+ pageStart = this.pagination.pageStart();
+ pageEnd = this.pagination.pageEnd();
+ }
+
+ return {
+ database: app.mixins.safeURLName(this.collection.database.id),
+ updateSeq: updateSeq,
+ offset: recordStart,
+ totalRows: totalRows,
+ showNumbers: this.showNumbers,
+ numModels: this.collection.models.length + recordStart - 1,
+ pageStart: pageStart,
+ pageEnd: pageEnd
+ };
+ }
+
+ });
+
+ Views.AllDocsLayout = FauxtonAPI.View.extend({
+ template: "templates/documents/all_docs_layout",
+ className: "row",
+
+ initialize: function (options) {
+ this.database = options.database;
+ this.params = options.params;
+ },
+
+ events: {
+ 'click #toggle-query': "toggleQuery"
+ },
+
+ toggleQuery: function (event) {
+ $('#dashboard-content').scrollTop(0);
+ this.$('#query').toggle('fast');
+ },
+
+ beforeRender: function () {
+ this.advancedOptions = this.insertView('#query', new Views.AdvancedOptions({
+ updateViewFn: this.updateAllDocs,
+ previewFn: this.previewView,
+ hasReduce: false,
+ showPreview: false,
+ database: this.database
+ }));
+
+ this.$('#query').hide();
+ },
+
+ afterRender: function () {
+ if (this.params) {
+ this.advancedOptions.updateFromParams(this.params);
+ }
+
+ },
+
+ updateAllDocs: function (event, paramInfo) {
+ event.preventDefault();
+
+ var 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: ".advanced-options .errors-container"
+ });
+ });
+ FauxtonAPI.addNotification({
+ msg: "Make sure that strings are properly quoted and any other values are valid JSON structures",
+ type: "warning",
+ selector: ".advanced-options .errors-container"
+ });
+
+ return false;
+ }
+
+ var fragment = window.location.hash.replace(/\?.*$/, '');
+ fragment = fragment + '?' + $.param(params);
+ FauxtonAPI.navigate(fragment, {trigger: false});
+
+ FauxtonAPI.triggerRouteEvent('updateAllDocs', {allDocs: true});
+ },
+
+ previewView: function (event) {
+ event.preventDefault();
+ }
+
+ });
+
+ // TODO: Rename to reflect that this is a list of rows or documents
+ Views.AllDocsList = FauxtonAPI.View.extend({
+ template: "templates/documents/all_docs_list",
+ events: {
+ "click button.all": "selectAll",
+ "click button.bulk-delete": "bulkDelete",
+ "click #collapse": "collapse",
+ "change .row-select":"toggleTrash"
+ },
+
+ toggleTrash: function () {
+ if (this.$('.row-select:checked').length > 0) {
+ this.$('.bulk-delete').removeClass('disabled');
+ } else {
+ this.$('.bulk-delete').addClass('disabled');
+ }
+ },
+
+ initialize: function(options){
+ this.nestedView = options.nestedView || Views.Document;
+ this.rows = {};
+ this.viewList = !! options.viewList;
+ this.database = options.database;
+ if (options.ddocInfo) {
+ this.designDocs = options.ddocInfo.designDocs;
+ this.ddocID = options.ddocInfo.id;
+ }
+ this.newView = options.newView || false;
+ this.expandDocs = true;
+ },
+
+ establish: function() {
+ if (this.newView) { return null; }
+
+ return this.collection.fetch({reset: true}).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){
+ $('.all-docs').find("input:checkbox").prop('checked', !$(evt.target).hasClass('active')).trigger('change');
+ },
+
+ serialize: function() {
+ var requestDuration = false;
+
+ if (this.collection.requestDurationInString) {
+ requestDuration = this.collection.requestDurationInString();
+ }
+
+ return {
+ viewList: this.viewList,
+ requestDuration: requestDuration,
+ expandDocs: this.expandDocs
+ };
+ },
+
+ collapse: function (event) {
+ event.preventDefault();
+
+ if (this.expandDocs) {
+ this.expandDocs = false;
+ } else {
+ this.expandDocs = true;
+ }
+
+ this.render();
+ },
+
+ /*
+ * TODO: this should be reconsidered
+ * This currently performs delete operations on the model level,
+ * when we could be using bulk docs with _deleted = true. Using
+ * individual models is cleaner from a backbone standpoint, but
+ * not from the couchdb api.
+ * Also, the delete method is naive and leaves the body intact,
+ * when we should switch the doc to only having id/rev/deleted.
+ */
+ bulkDelete: function() {
+ var that = this;
+ // yuck, data binding ftw?
+ var eles = this.$el.find("input.row-select:checked")
+ .parents("tr.all-docs-item")
+ .map(function(e) { return $(this).attr("data-id"); })
+ .get();
+
+ if (eles.length === 0 || !window.confirm("Are you sure you want to delete these " + eles.length + " docs?")) {
+ return false;
+ }
+
+ _.each(eles, function(ele) {
+ var model = this.collection.get(ele);
+
+ model.destroy().then(function(resp) {
+ that.rows[ele].$el.fadeOut(function () {
+ $(this).remove();
+ });
+
+ model.collection.remove(model.id);
+ if (!!model.id.match('_design')) {
+ FauxtonAPI.triggerRouteEvent('reloadDesignDocs');
+ }
+ that.$('.bulk-delete').addClass('disabled');
+ }, function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Failed to destroy your doc!",
+ type: "error"
+ });
+ });
+ }, this);
+ },
+
+ addPagination: function () {
+ var collection = this.collection;
+ var perPage = function () {
+ if (collection.params.limit && collection.skipFirstItem) {
+ return parseInt(collection.params.limit, 10) - 1;
+ } else if (collection.params.limit) {
+ return parseInt(collection.params.limit, 10);
+ }
+
+ return 20;
+ };
+
+ this.pagination = new Components.IndexPagination({
+ collection: this.collection,
+ scrollToSelector: '#dashboard-content',
+ previousUrlfn: function () {
+ return collection.urlPreviousPage(perPage(), this.previousParams.pop());
+ },
+ canShowPreviousfn: function () {
+ if (this.previousParams.length === 0) {
+ return false;
+ }
+
+ return true;
+ },
+ canShowNextfn: function () {
+ if (collection.length < (perPage() -1)) {
+ return false;
+ }
+
+ return true;
+ },
+
+ nextUrlfn: function () {
+ return collection.urlNextPage(perPage());
+ }
+ });
+ },
+
+ cleanup: function () {
+ //if (!this.pagination) { return; }
+ this.pagination.remove();
+ //this.pagination = null;
+ this.allDocsNumber.remove();
+ _.each(this.rows, function (row) {row.remove();});
+ },
+
+ beforeRender: function() {
+ var showNumbers = true;
+
+ if (!this.pagination) {
+ this.addPagination();
+ }
+
+ this.insertView('#documents-pagination', this.pagination);
+
+ if (this.designDocs || this.collection.idxType === '_view' || this.collection.params.startkey === '"_design"') {
+ showNumbers = false;
+ }
+
+ this.allDocsNumber = this.setView('#item-numbers', new Views.AllDocsNumber({
+ collection: this.collection,
+ newView: this.newView,
+ showNumbers: showNumbers,
+ pagination: this.pagination
+ }));
+
+ var docs = this.expandDocs ? this.collection : this.collection.simple();
+
+ docs.each(function(doc) {
+ this.rows[doc.id] = this.insertView("table.all-docs tbody", new this.nestedView({
+ model: doc
+ }));
+ }, this);
+ },
+
+ afterRender: function(){
+ prettyPrint();
+ }
+ });
+
+ Views.Doc = FauxtonAPI.View.extend({
+ template: "templates/documents/doc",
+ events: {
+ "click button.save-doc": "saveDoc",
+ "click button.delete": "destroy",
+ "click button.duplicate": "duplicate",
+ "click button.upload": "upload",
+ "click button.cancel-button": "goback"
+ },
+ disableLoader: true,
+ initialize: function (options) {
+ this.database = options.database;
+ _.bindAll(this);
+ },
+ goback: function(){
+ FauxtonAPI.navigate(this.database.url("index") + "?limit=100");
+ },
+ destroy: function(event) {
+ if (this.model.isNewDoc()) {
+ FauxtonAPI.addNotification({
+ msg: 'This document has not been saved yet.',
+ type: 'warning'
+ });
+ return;
+ }
+
+ if (!window.confirm("Are you sure you want to delete this doc?")) {
+ return false;
+ }
+
+ var database = this.model.database;
+
+ this.model.destroy().then(function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Succesfully destroyed your doc"
+ });
+ FauxtonAPI.navigate(database.url("index"));
+ }, function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Failed to destroy your doc!",
+ type: "error"
+ });
+ });
+ },
+
+ beforeRender: function () {
+ this.uploadModal = this.setView('#upload-modal', new Views.UploadModal({model: this.model}));
+ this.uploadModal.render();
+
+ this.duplicateModal = this.setView('#duplicate-modal', new Views.DuplicateDocModal({model: this.model}));
+ this.duplicateModal.render();
+ },
+
+ upload: function (event) {
+ event.preventDefault();
+ if (this.model.isNewDoc()) {
+ FauxtonAPI.addNotification({
+ msg: 'Please save the document before uploading an attachment.',
+ type: 'warning'
+ });
+ return;
+ }
+ this.uploadModal.showModal();
+ },
+
+ duplicate: function(event) {
+ if (this.model.isNewDoc()) {
+ FauxtonAPI.addNotification({
+ msg: 'Please save the document before duplicating it.',
+ type: 'warning'
+ });
+ return;
+ }
+ event.preventDefault();
+ this.duplicateModal.showModal();
+ },
+
+ 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());
+ }
+ },
+
+ establish: function() {
+ var promise = this.model.fetch(),
+ databaseId = this.database.safeID(),
+ deferred = $.Deferred(),
+ that = this;
+
+ promise.then(function () {
+ deferred.resolve();
+ }, function (xhr, reason, msg) {
+ if (xhr.status === 404) {
+ FauxtonAPI.addNotification({
+ msg: 'The document does not exist',
+ type: 'error',
+ clear: true
+ });
+ that.goback();
+ }
+ deferred.reject();
+ });
+
+ return deferred;
+ },
+
+ saveDoc: function(event) {
+ var json, notification,
+ that = this,
+ editor = this.editor,
+ validDoc = this.getDocFromEditor();
+
+ if (validDoc) {
+ this.getDocFromEditor();
+
+ notification = FauxtonAPI.addNotification({msg: "Saving document."});
+
+ this.model.save().then(function () {
+ editor.editSaved();
+ FauxtonAPI.navigate('/database/' + that.database.safeID() + '/' + that.model.id);
+ }).fail(function(xhr) {
+
+ var responseText = JSON.parse(xhr.responseText).reason;
+ notification = FauxtonAPI.addNotification({
+ msg: "Save failed: " + responseText,
+ type: "error",
+ clear: true,
+ selector: "#doc .errors-container"
+ });
+ });
+ } else if(this.model.validationError && this.model.validationError === 'Cannot change a documents id.') {
+ notification = FauxtonAPI.addNotification({
+ msg: "Cannot save: " + 'Cannot change a documents _id, try Duplicate doc instead!',
+ type: "error",
+ selector: "#doc .errors-container"
+ });
+ delete this.model.validationError;
+ } else {
+ notification = FauxtonAPI.addNotification({
+ msg: "Please fix the JSON errors and try again.",
+ type: "error",
+ selector: "#doc .errors-container"
+ });
+ }
+ },
+
+ getDocFromEditor: function () {
+ if (!this.hasValidCode()) {
+ return false;
+ }
+
+ json = JSON.parse(this.editor.getValue());
+
+ this.model.clear().set(json, {validate: true});
+ if (this.model.validationError) {
+ return false;
+ }
+
+ return this.model;
+ },
+
+ hasValidCode: function() {
+ var errors = this.editor.getAnnotations();
+ return errors.length === 0;
+ },
+
+ serialize: function() {
+ return {
+ doc: this.model,
+ attachments: this.getAttachments()
+ };
+ },
+
+ getAttachments: function () {
+ var attachments = this.model.get('_attachments');
+
+ if (!attachments) { return false; }
+
+ return _.map(attachments, function (att, key) {
+ return {
+ fileName: key,
+ size: att.length,
+ contentType: att.content_type,
+ url: this.model.url() + '/' + key
+ };
+ }, this);
+ },
+
+ afterRender: function() {
+ var saveDoc = this.saveDoc;
+
+ this.editor = new Components.Editor({
+ editorId: "editor-container",
+ commands: [{
+ name: 'save',
+ bindKey: {win: 'Ctrl-S', mac: 'Ctrl-S'},
+ exec: function(editor) {
+ saveDoc();
+ },
+ readOnly: true // false if this command should not apply in readOnly mode
+ }]
+ });
+ this.editor.render();
+ this.model.on("sync", this.updateValues, this);
+ },
+
+ cleanup: function () {
+ if (this.editor) this.editor.remove();
+ }
+ });
+
+ Views.DocFieldEditor = FauxtonAPI.View.extend({
+ template: "templates/documents/doc_field_editor",
+ disableLoader: true,
+ events: {
+ "click button.save": "saveDoc"
+ },
+
+ saveDoc: function(event) {
+ FauxtonAPI.addNotification({
+ type: "warning",
+ msg: "Save functionality coming soon."
+ });
+ },
+
+ serialize: function() {
+ return {
+ doc: this.getModelWithoutAttachments(),
+ attachments: this.getAttachments()
+ };
+ },
+
+ getModelWithoutAttachments: function() {
+ var model = this.model.toJSON();
+ delete model._attachments;
+ return model;
+ },
+
+ getAttachments: function () {
+ var attachments = this.model.get('_attachments');
+
+ if (!attachments) { return []; }
+
+ return _.map(attachments, function (att, key) {
+ return {
+ fileName: key,
+ size: att.length,
+ contentType: att.content_type,
+ url: this.model.url() + '/' + key
+ };
+ }, this);
+ },
+
+ establish: function() {
+ return [this.model.fetch()];
+ }
+ });
+
+ Views.AdvancedOptions = FauxtonAPI.View.extend({
+ template: "templates/documents/advanced_options",
+ className: "advanced-options well",
+
+ initialize: function (options) {
+ this.database = options.database;
+ this.ddocName = options.ddocName;
+ this.viewName = options.viewName;
+ this.updateViewFn = options.updateViewFn;
+ this.previewFn = options.previewFn;
+ //this.hadReduce = options.hasReduce || true;
+
+ if (typeof(options.hasReduce) === 'undefined') {
+ this.hasReduce = true;
+ } else {
+ this.hasReduce = options.hasReduce;
+ }
+
+ if (typeof(options.showPreview) === 'undefined') {
+ this.showPreview = true;
+ } else {
+ this.showPreview = options.showPreview;
+ }
+ },
+
+ events: {
+ "change form.view-query-update input": "updateFilters",
+ "change form.view-query-update select": "updateFilters",
+ "submit form.view-query-update": "updateView",
+ "click button.preview": "previewView"
+ },
+
+ beforeRender: function () {
+ if (this.viewName && this.ddocName) {
+ var buttonViews = FauxtonAPI.getExtensions('advancedOptions:ViewButton');
+ _.each(buttonViews, function (view) {
+ this.insertView('#button-options', view);
+ view.update(this.database, this.ddocName, this.viewName);
+ }, this);
+ }
+ },
+
+ renderOnUpdatehasReduce: function (hasReduce) {
+ this.hasReduce = hasReduce;
+ this.render();
+ },
+
+ queryParams: function () {
+ var $form = this.$(".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};
+ },
+
+ 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;
+ }
+ },
+
+ updateFromParams: function (params) {
+ var $form = this.$el.find("form.view-query-update");
+ _.each(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);
+ },
+
+ updateView: function (event) {
+ this.updateViewFn(event, this.queryParams());
+ },
+
+ previewView: function (event) {
+ this.previewFn(event, this.queryParams());
+ },
+
+ serialize: function () {
+ return {
+ hasReduce: this.hasReduce,
+ showPreview: this.showPreview
+ };
+ }
+ });
+
+ Views.DesignDocSelector = FauxtonAPI.View.extend({
+ template: "templates/documents/design_doc_selector",
+
+ events: {
+ "change select#ddoc": "updateDesignDoc"
+ },
+
+ initialize: function (options) {
+ this.ddocName = options.ddocName;
+ this.database = options.database;
+ this.listenTo(this.collection, 'add', this.ddocAdded);
+ this.DocModel = options.DocModel || Documents.Doc;
+ },
+
+ ddocAdded: function (ddoc) {
+ this.ddocName = ddoc.id;
+ this.render();
+ },
+
+ serialize: function () {
+ return {
+ ddocName: this.ddocName,
+ ddocs: this.collection
+ };
+ },
+
+ updateDesignDoc: function () {
+ if (this.newDesignDoc()) {
+ this.$('#new-ddoc-section').show();
+ } else {
+ this.$('#new-ddoc-section').hide();
+ }
+ },
+
+ newDesignDoc: function () {
+
+ return this.$('#ddoc').val() === 'new-doc';
+ },
+
+ newDocValidation: function(){
+ return this.newDesignDoc() && this.$('#new-ddoc').val()==="";
+ },
+ getCurrentDesignDoc: function () {
+ if (this.newDesignDoc()) {
+ var doc = {
+ _id: '_design/' + this.$('#new-ddoc').val(),
+ views: {},
+ language: "javascript"
+ };
+ var ddoc = new this.DocModel(doc, {database: this.database});
+ //this.collection.add(ddoc);
+ return ddoc;
+ } else if ( !this.newDesignDoc() ) {
+ var ddocName = this.$('#ddoc').val();
+ return this.collection.find(function (ddoc) {
+ return ddoc.id === ddocName;
+ }).dDocModel();
+ }
+ }
+ });
+
+ Views.ViewEditor = FauxtonAPI.View.extend({
+ template: "templates/documents/view_editor",
+ builtinReduces: ['_sum', '_count', '_stats'],
+
+ events: {
+ "click button.save": "saveView",
+ "click button.delete": "deleteView",
+ "change select#reduce-function-selector": "updateReduce",
+ "click button.preview": "previewView",
+ "click #db-views-tabs-nav": 'toggleIndexNav'
+ },
+
+ langTemplates: {
+ "javascript": {
+ 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}"
+ }
+ },
+
+ defaultLang: "javascript",
+
+ initialize: function(options) {
+ this.newView = options.newView || false;
+ this.ddocs = options.ddocs;
+ this.params = options.params;
+ this.database = options.database;
+ if (this.newView) {
+ this.viewName = 'newView';
+ } else {
+ this.ddocID = options.ddocInfo.id;
+ this.viewName = options.viewName;
+ this.ddocInfo = new Documents.DdocInfo({_id: this.ddocID},{database: this.database});
+ }
+
+ this.showIndex = false;
+ _.bindAll(this);
+ },
+
+ establish: function () {
+ if (this.ddocInfo) {
+ return this.ddocInfo.fetch();
+ }
+ },
+
+ 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") {
+ this.createReduceEditor();
+ this.reduceEditor.setValue(this.langTemplates.javascript.reduce);
+ $reduceContainer.show();
+ } else {
+ $reduceContainer.hide();
+ }
+ },
+
+ 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.safeID() + '/_all_docs?limit=' + Databases.DocLimit);
+ FauxtonAPI.triggerRouteEvent('reloadDesignDocs');
+ });
+ },
+
+ saveView: function(event) {
+ var json, notification,
+ that = this;
+
+ if (event) { event.preventDefault();}
+
+ $('#dashboard-content').scrollTop(0); //scroll up
+
+ if (this.hasValidCode() && this.$('#new-ddoc:visible').val() !=="") {
+ 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",
+ clear: true
+ });
+
+ ddoc.setDdocView(viewName, mapVal, reduceVal);
+
+ ddoc.save().then(function () {
+ that.ddocs.add(ddoc);
+
+ that.mapEditor.editSaved();
+ that.reduceEditor && that.reduceEditor.editSaved();
+
+ FauxtonAPI.addNotification({
+ msg: "View has been saved.",
+ type: "success",
+ selector: "#define-view .errors-container",
+ clear: true
+ });
+
+ if (that.newView) {
+ var fragment = '/database/' + that.database.safeID() +'/' + ddoc.safeID() + '/_view/' + app.mixins.safeURLName(viewName);
+
+ FauxtonAPI.navigate(fragment, {trigger: false});
+ that.newView = false;
+ that.ddocID = ddoc.safeID();
+ that.viewName = viewName;
+ that.ddocInfo = ddoc;
+ that.showIndex = true;
+ that.render();
+ FauxtonAPI.triggerRouteEvent('reloadDesignDocs', {
+ selectedTab: app.mixins.removeSpecialCharacters(ddocName.replace(/_design\//,'')) + '_' + app.mixins.removeSpecialCharacters(viewName)
+ });
+ }
+
+ if (that.reduceFunStr !== reduceVal) {
+ that.reduceFunStr = reduceVal;
+ that.advancedOptions.renderOnUpdatehasReduce(that.hasReduce());
+ }
+
+ FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: ddocName, view: viewName});
+
+ }, function(xhr) {
+ var responseText = JSON.parse(xhr.responseText).reason;
+ notification = FauxtonAPI.addNotification({
+ msg: "Save failed: " + responseText,
+ type: "error",
+ clear: true
+ });
+ });
+ } else {
+ var errormessage = (this.$('#new-ddoc:visible').val() ==="")?"Enter a design doc name":"Please fix the Javascript errors and try again.";
+ notification = FauxtonAPI.addNotification({
+ msg: errormessage,
+ type: "error",
+ selector: "#define-view .errors-container",
+ clear: true
+ });
+ }
+ },
+
+ updateView: function(event, paramInfo) {
+ event.preventDefault();
+
+ if (this.newView) { return alert('Please save this new view before querying it.'); }
+
+ var 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: ".advanced-options .errors-container",
+ clear: true
+ });
+ });
+ FauxtonAPI.addNotification({
+ msg: "Make sure that strings are properly quoted and any other values are valid JSON structures",
+ type: "warning",
+ selector: ".advanced-options .errors-container",
+ clear: true
+ });
+
+ 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});
+ },
+
+
+ previewView: function(event, paramsInfo) {
+ event.preventDefault();
+ var that = this,
+ mapVal = this.mapVal(),
+ reduceVal = this.reduceVal(),
+ paramsArr = [];
+
+ if (paramsInfo && paramsInfo.params) {
+ paramsArr = paramsInfo.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: ".advanced-options .errors-container",
+ fade: true
+ });
+
+ var promise = FauxtonAPI.Deferred();
+
+ if (!this.database.allDocs) {
+ this.database.buildAllDocs({limit: Databases.DocLimit.toString(), 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});
+ });
+ });
+ },
+
+ getCurrentDesignDoc: function () {
+ return this.designDocSelector.getCurrentDesignDoc();
+ },
+
+ isCustomReduceEnabled: function() {
+ return $("#reduce-function-selector").val() == "CUSTOM";
+ },
+
+ mapVal: function () {
+ if (this.mapEditor) {
+ return this.mapEditor.getValue();
+ }
+
+ return this.$('#map-function').text();
+ },
+
+ 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() {
+ return _.every(["mapEditor", "reduceEditor"], function(editorName) {
+ var editor = this[editorName];
+ if (editorName === "reduceEditor" && ! this.isCustomReduceEnabled()) {
+ return true;
+ }
+ return editor.hadValidCode();
+ }, this);
+ },
+
+ toggleIndexNav: function (event) {
+ var $targetId = this.$(event.target).attr('id'),
+ $previousTab = this.$(this.$('li.active a').attr('href')),
+ $targetTab = this.$(this.$(event.target).attr('href'));
+
+ if ($targetTab.attr('id') !== $previousTab.attr('id')) {
+ $previousTab.removeAttr('style');
+ }
+
+ if ($targetId === 'index-nav') {
+ if (this.newView) { return; }
+ var that = this;
+ $('#dashboard-content').scrollTop(0); //scroll up
+ $targetTab.toggle('slow', function(){
+ that.showEditors();
+ });
+ } else {
+ $targetTab.toggle('slow');
+ }
+ },
+
+ serialize: function() {
+ return {
+ ddocs: this.ddocs,
+ ddoc: this.model,
+ ddocName: this.model.id,
+ viewName: this.viewName,
+ reduceFunStr: this.reduceFunStr,
+ isCustomReduce: this.hasCustomReduce(),
+ newView: this.newView,
+ langTemplates: this.langTemplates.javascript
+ };
+ },
+
+ hasCustomReduce: function() {
+ return this.reduceFunStr && ! _.contains(this.builtinReduces, this.reduceFunStr);
+ },
+
+ hasReduce: function () {
+ return this.reduceFunStr || false;
+ },
+
+ createReduceEditor: function () {
+ if (this.reduceEditor) {
+ this.reduceEditor.remove();
+ }
+
+ this.reduceEditor = new Components.Editor({
+ editorId: "reduce-function",
+ mode: "javascript",
+ couchJSHINT: true
+ });
+ this.reduceEditor.render();
+ },
+
+ 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 {
+ var ddocDecode = decodeURIComponent(this.ddocID);
+ this.model = this.ddocs.get(ddocDecode).dDocModel();
+ this.reduceFunStr = this.model.viewHasReduce(this.viewName);
+ this.setView('#ddoc-info', new Views.DdocInfo({model: this.ddocInfo }));
+ }
+
+ this.designDocSelector = this.setView('.design-doc-group', new Views.DesignDocSelector({
+ collection: this.ddocs,
+ ddocName: this.model.id,
+ database: this.database
+ }));
+
+ this.advancedOptions = this.insertView('#query', new Views.AdvancedOptions({
+ updateViewFn: this.updateView,
+ previewFn: this.previewView,
+ database: this.database,
+ viewName: this.viewName,
+ ddocName: this.model.id,
+ hasReduce: this.hasReduce()
+ }));
+ },
+
+ afterRender: function() {
+ if (this.params) {
+ this.advancedOptions.updateFromParams(this.params);
+ }
+
+ this.designDocSelector.updateDesignDoc();
+ if (this.newView || this.showIndex) {
+ this.showEditors();
+ this.showIndex = false;
+ } else {
+ this.$('#index').hide();
+ this.$('#index-nav').parent().removeClass('active');
+ }
+ },
+
+ showEditors: function () {
+ this.mapEditor = new Components.Editor({
+ editorId: "map-function",
+ mode: "javascript",
+ couchJSHINT: true
+ });
+ this.mapEditor.render();
+
+ if (this.hasCustomReduce()) {
+ this.createReduceEditor();
+ } else {
+ $(".control-group.reduce-function").hide();
+ }
+
+ if (this.newView) {
+ this.mapEditor.setValue(this.langTemplates[this.defaultLang].map);
+ //Use a built in view by default
+ //this.reduceEditor.setValue(this.langTemplates[this.defaultLang].reduce);
+ }
+
+ this.mapEditor.editSaved();
+ this.reduceEditor && this.reduceEditor.editSaved();
+ },
+
+ cleanup: function () {
+ this.mapEditor && this.mapEditor.remove();
+ this.reduceEditor && this.reduceEditor.remove();
+ }
+ });
+
+ Views.JumpToDoc = FauxtonAPI.View.extend({
+ template: "templates/documents/jumpdoc",
+
+ initialize: function (options) {
+ this.database = options.database;
+ },
+
+ events: {
+ "submit #jump-to-doc": "jumpToDoc"
+ },
+
+ jumpToDoc: function (event) {
+ event.preventDefault();
+ var docId = this.$('#jump-to-doc-id').val().trim();
+ FauxtonAPI.navigate('/database/' + app.mixins.safeURLName(this.database.id) +'/' + app.mixins.safeURLName(docId), {trigger: true});
+ },
+
+ afterRender: function () {
+ this.typeAhead = new Components.DocSearchTypeahead({el: '#jump-to-doc-id', database: this.database});
+ this.typeAhead.render();
+ }
+ });
+
+ Views.Sidebar = FauxtonAPI.View.extend({
+ template: "templates/documents/sidebar",
+ events: {
+ "click button#delete-database": "deleteDatabase"
+ },
+
+ initialize: function(options) {
+ this.database = options.database;
+ if (options.ddocInfo) {
+ this.ddocID = options.ddocInfo.id;
+ this.currView = options.ddocInfo.currView;
+ }
+ },
+
+ deleteDatabase: function (event) {
+ event.preventDefault();
+
+ var result = confirm('Are you sure you want to delete this database?');
+
+ if (!result) { return; }
+ var databaseName = this.database.id;
+ FauxtonAPI.addNotification({
+ msg: "Deleting your database...",
+ type: "error",
+ clear: true
+ });
+
+ this.database.destroy().then(function () {
+ FauxtonAPI.navigate('#/_all_dbs');
+ FauxtonAPI.addNotification({
+ msg: 'The database ' + databaseName + ' has been deleted.',
+ clear: true
+ });
+ }).fail(function (rsp, error, msg) {
+ FauxtonAPI.addNotification({
+ msg: 'Could not delete the database, reason ' + msg + '.',
+ type: 'error',
+ clear: true
+ });
+ });
+ },
+
+ serialize: function() {
+ var docLinks = FauxtonAPI.getExtensions('docLinks'),
+ newLinks = FauxtonAPI.getExtensions('sidebar:newLinks'),
+ addLinks = FauxtonAPI.getExtensions('sidebar:links'),
+ extensionList = FauxtonAPI.getExtensions('sidebar:list');
+ return {
+ changes_url: '#' + this.database.url('changes'),
+ permissions_url: '#' + this.database.url('app') + '/permissions',
+ db_url: '#' + this.database.url('index') + '?limit=' + Databases.DocLimit,
+ database: this.collection.database,
+ database_url: '#' + this.database.url('app'),
+ docLinks: docLinks,
+ docLimit: Databases.DocLimit,
+ addLinks: addLinks,
+ newLinks: newLinks,
+ extensionList: extensionList > 0
+ };
+ },
+
+ 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,
+ selected: selected && key == this.currView
+ }));
+ }, this);
+ },
+
+ beforeRender: function(manage) {
+
+ var sidebarListViews = FauxtonAPI.getExtensions('sidebar:list');
+ _.each(sidebarListViews, function (view) {
+ var extension = this.insertView('#extension-navs', view);
+ extension.update(this.database, this.collection, this.viewName);
+ extension.render();
+ }, this);
+
+
+ this.collection.each(function(design) {
+ if (design.has('doc')){
+ var ddoc = design.id.replace(/^_design\//,"");
+ if (design.get('doc').views){
+ this.buildIndexList(design.get('doc').views, "views", ddoc);
+ }
+ }
+ }, 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');
+ }
+ });
+
+ Views.Indexed = FauxtonAPI.View.extend({});
+
+ Views.Changes = FauxtonAPI.View.extend({
+ template: "templates/documents/changes",
+
+ establish: function() {
+ return [ this.model.changes.fetch()];
+ },
+
+ serialize: function () {
+ return {
+ changes: this.model.changes.toJSON(),
+ database: this.model
+ };
+ },
+
+ afterRender: function(){
+ prettyPrint();
+ }
+ });
+
+ Views.DdocInfo = FauxtonAPI.View.extend({
+ template: "templates/documents/ddoc_info",
+
+ initialize: function (options) {
+ this.refreshTime = options.refreshTime || 5000;
+ this.listenTo(this.model, 'change', this.render);
+ },
+
+ serialize: function () {
+ return {
+ view_index: this.model.get('view_index')
+ };
+ },
+
+ afterRender: function () {
+ this.startRefreshInterval();
+ },
+
+ startRefreshInterval: function () {
+ var model = this.model;
+
+ // Interval already set
+ if (this.intervalId) { return ; }
+
+ this.intervalId = setInterval(function () {
+ model.fetch();
+ }, this.refreshTime);
+ },
+
+ stopRefreshInterval: function () {
+ clearInterval(this.intervalId);
+ },
+
+ cleanup: function () {
+ this.stopRefreshInterval();
+ }
+ });
+
+ Documents.Views = Views;
+ return Documents;
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5f9a88f6/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
new file mode 100644
index 0000000..aa4c3d4
--- /dev/null
+++ b/app/addons/fauxton/base.js
@@ -0,0 +1,271 @@
+// 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.
+
+define([
+ "app",
+ // Libs
+ "backbone",
+ "resizeColumns",
+],
+
+function(app, Backbone, resizeColumns) {
+
+
+ //resizeAnimation
+ app.resizeColumns = new resizeColumns({});
+ app.resizeColumns.onResizeHandler();
+
+ var Fauxton = {};
+
+ Fauxton.Breadcrumbs = Backbone.View.extend({
+ template: "templates/fauxton/breadcrumbs",
+
+ serialize: function() {
+ var crumbs = _.clone(this.crumbs);
+ return {
+ crumbs: crumbs
+ };
+ },
+
+ initialize: function(options) {
+ this.crumbs = options.crumbs;
+ }
+ });
+
+ Fauxton.VersionInfo = Backbone.Model.extend({
+ url: app.host
+ });
+
+ // TODO: this View should extend from FauxtonApi.View.
+ // Chicken and egg problem, api.js extends fauxton/base.js.
+ // Need to sort the loading order.
+ Fauxton.Footer = Backbone.View.extend({
+ template: "templates/fauxton/footer",
+
+ initialize: function() {
+ this.versionInfo = new Fauxton.VersionInfo();
+ },
+
+ establish: function() {
+ return [this.versionInfo.fetch()];
+ },
+
+ serialize: function() {
+ return {
+ version: this.versionInfo.get("version")
+ };
+ }
+ });
+
+ Fauxton.NavBar = Backbone.View.extend({
+ className:"navbar",
+ template: "templates/fauxton/nav_bar",
+ // TODO: can we generate this list from the router?
+ navLinks: [
+ {href:"#/_all_dbs", title:"Databases", icon: "fonticon-database", className: 'databases'}
+ ],
+
+ bottomNavLinks: [],
+ footerNavLinks: [],
+
+ serialize: function() {
+ return {
+ navLinks: this.navLinks,
+ bottomNavLinks: this.bottomNavLinks,
+ footerNavLinks: this.footerNavLinks
+ };
+ },
+
+ addLink: function(link) {
+ // link.top means it gets pushed to the top of the array,
+ // link.bottomNav means it goes to the additional bottom nav
+ // link.footerNav means goes to the footer nav
+ if (link.top && !link.bottomNav){
+ this.navLinks.unshift(link);
+ } else if (link.top && link.bottomNav){
+ this.bottomNavLinks.unshift(link);
+ } else if (link.bottomNav) {
+ this.bottomNavLinks.push(link);
+ } else if (link.footerNav) {
+ this.footerNavLinks.push(link);
+ } else {
+ this.navLinks.push(link);
+ }
+ },
+
+ removeLink: function (removeLink) {
+ var links = this.navlinks;
+
+ if (removeLink.bottomNav) {
+ links = this.bottomNavLinks;
+ } else if (removeLink.footerNav) {
+ links = this.footerNavLinks;
+ }
+
+ var foundIndex = -1;
+
+ _.each(links, function (link, index) {
+ if (link.title === removeLink.title) {
+ foundIndex = index;
+ }
+ });
+
+ if (foundIndex === -1) {return;}
+ links.splice(foundIndex, 1);
+ this.render();
+ },
+
+ afterRender: function(){
+
+ $('#primary-navbar li[data-nav-name="' + app.selectedHeader + '"]').addClass('active');
+
+ var menuOpen = true;
+ var $selectorList = $('body');
+ $('.brand').off();
+ $('.brand').on({
+ click: function(e){
+ if(!$(e.target).is('a')){
+ toggleMenu();
+ }
+ }
+ });
+
+ function toggleMenu(){
+ $selectorList.toggleClass('closeMenu');
+ menuOpen = $selectorList.hasClass('closeMenu');
+ app.resizeColumns.onResizeHandler();
+ }
+
+ $('#primary-navbar').on("click", ".nav a", function(){
+ if (!($selectorList.hasClass('closeMenu'))){
+ setTimeout(
+ function(){
+ $selectorList.addClass('closeMenu');
+ app.resizeColumns.onResizeHandler();
+ },3000);
+
+ }
+ });
+
+ app.resizeColumns.initialize();
+ },
+
+ beforeRender: function () {
+ this.addLinkViews();
+ },
+
+ addLinkViews: function () {
+ var that = this;
+
+ _.each(_.union(this.navLinks, this.bottomNavLinks), function (link) {
+ if (!link.view) { return; }
+
+ //TODO check if establish is a function
+ var establish = link.establish || [];
+ $.when.apply(null, establish).then( function () {
+ var selector = link.bottomNav ? '#bottom-nav-links' : '#nav-links';
+ that.insertView(selector, link.view).render();
+ });
+ }, this);
+ }
+
+ // TODO: ADD ACTIVE CLASS
+ });
+
+ Fauxton.ApiBar = Backbone.View.extend({
+ template: "templates/fauxton/api_bar",
+ endpoint: '_all_docs',
+
+ documentation: 'docs',
+
+ events: {
+ "click .api-url-btn" : "toggleAPIbar"
+ },
+
+ toggleAPIbar: function(e){
+ var $currentTarget = $(e.currentTarget).find('span');
+ if ($currentTarget.hasClass("fonticon-plus")){
+ $currentTarget.removeClass("fonticon-plus").addClass("fonticon-minus");
+ }else{
+ $currentTarget.removeClass("fonticon-minus").addClass("fonticon-plus");
+ }
+
+ $('.api-navbar').toggle();
+
+ },
+
+ serialize: function() {
+ return {
+ endpoint: this.endpoint,
+ documentation: this.documentation
+ };
+ },
+
+ hide: function(){
+ this.$el.addClass('hide');
+ },
+ show: function(){
+ this.$el.removeClass('hide');
+ },
+ update: function(endpoint) {
+ this.show();
+ this.endpoint = endpoint[0];
+ this.documentation = endpoint[1];
+ this.render();
+ }
+
+ });
+
+ Fauxton.Notification = Backbone.View.extend({
+ fadeTimer: 5000,
+
+ initialize: function(options) {
+ this.msg = options.msg;
+ this.type = options.type || "info";
+ this.selector = options.selector;
+ this.fade = options.fade === undefined ? true : options.fade;
+ this.clear = options.clear;
+ this.data = options.data || "";
+ this.template = options.template || "templates/fauxton/notification";
+ },
+
+ serialize: function() {
+ return {
+ data: this.data,
+ msg: this.msg,
+ type: this.type
+ };
+ },
+
+ delayedFade: function() {
+ var that = this;
+ if (this.fade) {
+ setTimeout(function() {
+ that.$el.fadeOut();
+ }, this.fadeTimer);
+ }
+ },
+
+ renderNotification: function(selector) {
+ selector = selector || this.selector;
+ if (this.clear) {
+ $(selector).html('');
+ }
+ this.render().$el.appendTo(selector);
+ this.delayedFade();
+ return this;
+ }
+ });
+
+
+ return Fauxton;
+});