couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gar...@apache.org
Subject [4/7] Fauxton: move modules to addons
Date Mon, 06 Jan 2014 14:16:33 GMT
http://git-wip-us.apache.org/repos/asf/couchdb/blob/fae1de24/src/fauxton/app/addons/documents/templates/index_row_docular.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/index_row_docular.html b/src/fauxton/app/addons/documents/templates/index_row_docular.html
new file mode 100644
index 0000000..3835453
--- /dev/null
+++ b/src/fauxton/app/addons/documents/templates/index_row_docular.html
@@ -0,0 +1,26 @@
+<!--
+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"><%- doc.prettyJSON() %></pre>
+    <% if (doc.isEditable()) { %>
+      <div class="btn-group">
+        <a href="#<%= doc.url('app') %>" class="btn btn-small edits">Edit <%= doc.docType() %></a>
+        <button href="#" class="btn btn-small btn-danger delete" title="Delete this document."><i class="icon icon-trash"></i></button>
+      </div>
+    <% } %>
+  </div>
+</td>

http://git-wip-us.apache.org/repos/asf/couchdb/blob/fae1de24/src/fauxton/app/addons/documents/templates/index_row_tabular.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/index_row_tabular.html b/src/fauxton/app/addons/documents/templates/index_row_tabular.html
new file mode 100644
index 0000000..f5f68fa
--- /dev/null
+++ b/src/fauxton/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/blob/fae1de24/src/fauxton/app/addons/documents/templates/jumpdoc.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/jumpdoc.html b/src/fauxton/app/addons/documents/templates/jumpdoc.html
new file mode 100644
index 0000000..43fdb9c
--- /dev/null
+++ b/src/fauxton/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/blob/fae1de24/src/fauxton/app/addons/documents/templates/search.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/search.html b/src/fauxton/app/addons/documents/templates/search.html
new file mode 100644
index 0000000..bb84891
--- /dev/null
+++ b/src/fauxton/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/blob/fae1de24/src/fauxton/app/addons/documents/templates/sidebar.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/sidebar.html b/src/fauxton/app/addons/documents/templates/sidebar.html
new file mode 100644
index 0000000..8a73ae9
--- /dev/null
+++ b/src/fauxton/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/blob/fae1de24/src/fauxton/app/addons/documents/templates/tabs.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/tabs.html b/src/fauxton/app/addons/documents/templates/tabs.html
new file mode 100644
index 0000000..f8b0c4b
--- /dev/null
+++ b/src/fauxton/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/blob/fae1de24/src/fauxton/app/addons/documents/templates/upload_modal.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/upload_modal.html b/src/fauxton/app/addons/documents/templates/upload_modal.html
new file mode 100644
index 0000000..9a5c5cd
--- /dev/null
+++ b/src/fauxton/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">&times;</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/blob/fae1de24/src/fauxton/app/addons/documents/templates/view_editor.html
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/templates/view_editor.html b/src/fauxton/app/addons/documents/templates/view_editor.html
new file mode 100644
index 0000000..ddb5a0c
--- /dev/null
+++ b/src/fauxton/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 &amp; 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/blob/fae1de24/src/fauxton/app/addons/documents/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/tests/resourcesSpec.js b/src/fauxton/app/addons/documents/tests/resourcesSpec.js
new file mode 100644
index 0000000..6d28052
--- /dev/null
+++ b/src/fauxton/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'},
+        design: '_design/myDoc'
+      });
+
+    });
+
+    it('Should return urlNext', function () {
+      var url = collection.urlNextPage(20);
+
+      assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=20&reduce=false&startkey_docid=%22myId2%22&startkey=%22myId2%22');
+
+    });
+
+    it('Should return urlPrevious', function () {
+      var url = collection.urlPreviousPage(20, 'myId1');
+
+      assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=20&reduce=false&startkey_docid=%22myId1%22&startkey=%22myId1%22');
+
+    });
+
+  });
+
+  describe('AllDocs', function () {
+    var collection;
+    beforeEach(function () {
+      collection = new Models.AllDocs([{
+        _id:'myId1',
+        doc: 'num1'
+      },
+      {
+        _id:'myId2',
+        doc: 'num2'
+      }], {
+        database: {id: 'databaseId'},
+        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, 'myId1');
+      assert.equal(url, 'database/databaseId/_all_docs?limit=20&startkey_docid=%22myId1%22&startkey=%22myId1%22');
+    });
+
+
+  });
+
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb/blob/fae1de24/src/fauxton/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js
new file mode 100644
index 0000000..52070af
--- /dev/null
+++ b/src/fauxton/app/addons/documents/views.js
@@ -0,0 +1,1805 @@
+// 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.listenTo(this.collection, 'totalRows:decrement', this.render);
+    },
+
+    serialize: function () {
+       var totalRows = 0,
+          recordStart = 0,
+          updateSeq = false;
+
+      if (!this.newView) {
+        totalRows = this.collection.totalRows();
+        updateSeq = this.collection.updateSeq();
+      }
+
+      recordStart = this.collection.recordStart();
+
+      return {
+        database: app.mixins.safeURLName(this.collection.database.id),
+        updateSeq: updateSeq,
+        offset: recordStart,
+        totalRows: totalRows,
+        numModels: this.collection.models.length + recordStart - 1,
+      };
+    }
+
+  });
+
+  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) {
+      this.$('#query').toggle('fast');
+    },
+
+    beforeRender: function () {
+      this.advancedOptions = this.insertView('#query', new Views.AdvancedOptions({
+        updateViewFn: this.updateView,
+        previewFn: this.previewView,
+        hasReduce: false,
+        showPreview: false,
+        database: this.database
+      }));
+
+      this.$('#query').hide();
+    },
+
+    afterRender: function () {
+      if (this.params) {
+        this.advancedOptions.updateFromParams(this.params);
+      }
+
+    },
+
+    updateView: 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;
+      this.addPagination();
+    },
+
+    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){
+      $("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);
+          console.log(model.id.match('_design'), !!model.id.match('_design'));
+          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;
+
+      this.pagination = new Components.IndexPagination({
+        collection: this.collection,
+        scrollToSelector: '#dashboard-content',
+        previousUrlfn: function () {
+          return collection.urlPreviousPage(20, this.previousIds.pop());
+        },
+        canShowPreviousfn: function () {
+          if (collection.viewMeta.offset === 0) {
+            return false;
+          }
+
+          return true;
+        },
+        canShowNextfn: function () {
+          if (collection.length === 0 || (collection.viewMeta.offset + collection.length + 2) >= collection.viewMeta.total_rows) {
+            return false;
+          }
+
+          return true;
+        },
+        
+        nextUrlfn: function () {
+          return collection.urlNextPage(20);
+        }
+      });
+    },
+
+    beforeRender: function() {
+      this.allDocsNumber = this.setView('#item-numbers', new Views.AllDocsNumber({
+        collection: this.collection,
+        newView: this.newView
+      }));
+
+      this.insertView('#documents-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();}
+
+      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"
+        });
+
+        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"
+          });
+
+          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"
+        });
+      }
+    },
+
+    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"
+           });
+         });
+         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', {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;
+        $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 {
+        this.model = this.ddocs.get(this.ddocID).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/blob/fae1de24/src/fauxton/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/base.js b/src/fauxton/app/addons/fauxton/base.js
new file mode 100644
index 0000000..544ba36
--- /dev/null
+++ b/src/fauxton/app/addons/fauxton/base.js
@@ -0,0 +1,266 @@
+// 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",
+       // Libs
+       "backbone"
+],
+
+function(app, FauxtonAPI, Backbone) {
+
+  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;
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/fae1de24/src/fauxton/app/addons/fauxton/components.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/components.js b/src/fauxton/app/addons/fauxton/components.js
new file mode 100644
index 0000000..6afe046
--- /dev/null
+++ b/src/fauxton/app/addons/fauxton/components.js
@@ -0,0 +1,316 @@
+// 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('ace_configuration', ["app", "ace/ace"], function (app, ace) {
+  var path = app.host + app.root + 'js/ace';
+  var config = require("ace/config");
+  config.set("packaged", true);
+  config.set("workerPath",path);
+  config.set("modePath",path);
+  config.set("themePath", path);
+  return ace;
+});
+
+define([
+  "app",
+  // Libs
+  "api",
+  "ace_configuration",
+],
+
+function(app, FauxtonAPI, ace) {
+  var Components = app.module();
+
+  Components.Pagination = FauxtonAPI.View.extend({
+    template: "templates/fauxton/pagination",
+
+    initialize: function(options) {
+      this.page = parseInt(options.page, 10);
+      this.perPage = options.perPage;
+      this.total = options.total;
+      this.totalPages = Math.ceil(this.total / this.perPage);
+      this.urlFun = options.urlFun;
+    },
+
+    serialize: function() {
+      return {
+        page: this.page,
+        perPage: this.perPage,
+        total: this.total,
+        totalPages: this.totalPages,
+        urlFun: this.urlFun
+      };
+    }
+  });
+
+  Components.IndexPagination = FauxtonAPI.View.extend({
+    template: "templates/fauxton/index_pagination",
+    events: {
+      "click a": 'scrollTo',
+      "click a#next": 'nextClicked',
+      "click a#previous": 'previousClicked'
+    },
+
+    previousIds: [],
+
+    scrollTo: function () {
+      if (!this.scrollToSelector) { return; }
+      $(this.scrollToSelector).animate({ scrollTop: 0 }, 'slow');
+    },
+
+    initialize: function (options) {
+      this.previousUrlfn = options.previousUrlfn;
+      this.nextUrlfn = options.nextUrlfn;
+      this.canShowPreviousfn = options.canShowPreviousfn;
+      this.canShowNextfn = options.canShowNextfn;
+      this.scrollToSelector = options.scrollToSelector;
+      _.bindAll(this);
+    },
+
+    previousClicked: function (event) {
+      event.preventDefault();
+      event.stopPropagation();
+      if (!this.canShowPreviousfn()) { return; }
+      FauxtonAPI.navigate(this.previousUrlfn(), {trigger: false});
+      FauxtonAPI.triggerRouteEvent('paginate', 'previous');
+    },
+
+    nextClicked: function (event) {
+      event.preventDefault();
+      event.stopPropagation();
+      if (!this.canShowNextfn()) { return; }
+      var doc = this.collection.first();
+
+      if (doc) {
+        this.previousIds.push(doc.id);
+      }
+
+      FauxtonAPI.navigate(this.nextUrlfn(), {trigger: false});
+      FauxtonAPI.triggerRouteEvent('paginate', 'next');
+    },
+
+    serialize: function () {
+      return {
+        canShowNextfn: this.canShowNextfn,
+        canShowPreviousfn: this.canShowPreviousfn,
+      };
+    }
+
+  });
+
+  //TODO allow more of the typeahead options.
+  //Current this just does what we need but we
+  //need to support the other typeahead options.
+  Components.Typeahead = FauxtonAPI.View.extend({
+
+    initialize: function (options) {
+      this.source = options.source;
+      _.bindAll(this);
+    },
+
+    afterRender: function () {
+      var onUpdate = this.onUpdate;
+
+      this.$el.typeahead({
+        source: this.source,
+        updater: function (item) {
+          if (onUpdate) {
+            onUpdate(item);
+          }
+
+          return item;
+        }
+      });
+    }
+
+  });
+
+
+  Components.DbSearchTypeahead = Components.Typeahead.extend({
+    initialize: function (options) {
+      this.dbLimit = options.dbLimit || 30;
+      this.onUpdate = options.onUpdate;
+      _.bindAll(this);
+    },
+    source: function(query, process) {
+      var url = [
+        app.host,
+        "/_all_dbs?startkey=%22",
+        query,
+        "%22&endkey=%22",
+        query,
+        "\u9999",
+        "%22&limit=",
+        this.dbLimit
+      ].join('');
+
+      if (this.ajaxReq) { this.ajaxReq.abort(); }
+
+      this.ajaxReq = $.ajax({
+        cache: false,
+        url: url,
+        dataType: 'json',
+        success: function(data) {
+          process(data);
+        }
+      });
+    }
+  });
+
+  Components.DocSearchTypeahead = Components.Typeahead.extend({
+    initialize: function (options) {
+      this.docLimit = options.docLimit || 30;
+      this.database = options.database;
+      _.bindAll(this);
+    },
+    source: function(query, process) {
+      var url = [
+        app.host,
+        "/",
+        this.database.id,
+        "/_all_docs?startkey=%22",
+        query,
+        "%22&endkey=%22",
+        query,
+        "\u9999",
+        "%22&limit=",
+        this.docLimit
+      ].join('');
+
+      if (this.ajaxReq) { this.ajaxReq.abort(); }
+
+      this.ajaxReq = $.ajax({
+        cache: false,
+        url: url,
+        dataType: 'json',
+        success: function(data) {
+          var ids = _.map(data.rows, function (row) {
+            return row.id;
+          });
+          process(ids);
+        }
+      });
+    }
+  });
+
+  Components.Editor = FauxtonAPI.View.extend({
+    initialize: function (options) {
+      this.editorId = options.editorId;
+      this.mode = options.mode || "json";
+      this.commands = options.commands;
+      this.theme = options.theme || 'crimson_editor';
+      this.couchJSHINT = options.couchJSHINT;
+      this.edited = false;
+    },
+
+    afterRender: function () {
+      this.editor = ace.edit(this.editorId);
+      this.setHeightToLineCount();
+      this.editor.setTheme("ace/theme/" + this.theme);
+      this.editor.getSession().setMode("ace/mode/" + this.mode);
+      this.editor.getSession().setUseWrapMode(true);
+      this.editor.setShowPrintMargin(false);
+      this.editor.gotoLine(2);
+      this.addCommands();
+
+      if (this.couchJSHINT) {
+        this.removeIncorrectAnnotations();
+      }
+
+      var that = this;
+      this.editor.getSession().on('change', function () {
+        that.setHeightToLineCount();
+        that.edited = true;
+      });
+
+      $(window).on('beforeunload.editor', function() {
+        if (that.edited) {
+          return 'Your changes have not been saved. Click cancel to return to the document.';
+        }
+      });
+
+      FauxtonAPI.beforeUnload("editor", function (deferred) {
+        if (that.edited) {
+          return 'Your changes have not been saved. Click cancel to return to the document.';
+        }
+      });
+    },
+
+    cleanup: function () {
+      $(window).off('beforeunload.editor');
+      FauxtonAPI.removeBeforeUnload("editor");
+    },
+
+    setHeightToLineCount: function () {
+      var lines = this.editor.getSession().getDocument().getLength();
+      this.editor.setOptions({
+        maxLines: lines
+      });
+
+      this.editor.resize();
+    },
+
+    addCommands: function () {
+      _.each(this.commands, function (command) {
+        this.editor.commands.addCommand(command);
+      }, this);
+    },
+
+    removeIncorrectAnnotations: function () {
+      var editor = this.editor;
+
+      this.editor.getSession().on("changeAnnotation", function(){
+        var annotations = editor.getSession().getAnnotations();
+
+        var newAnnotations = _.reduce(annotations, function (annotations, error) {
+          if (!FauxtonAPI.isIgnorableError(error.raw)) {
+            annotations.push(error);
+          }
+          return annotations;
+        }, []);
+
+        if (annotations.length !== newAnnotations.length) {
+          editor.getSession().setAnnotations(newAnnotations);
+        }
+      });
+    },
+
+    editSaved: function () {
+      this.edited = false;
+    },
+
+    setValue: function (data, lineNumber) {
+      lineNumber = lineNumber ? lineNumber : -1;
+      this.editor.setValue(data, lineNumber);
+    },
+
+    getValue: function () {
+      return this.editor.getValue();
+    },
+
+    getAnnotations: function () {
+      return this.editor.getSession().getAnnotations();
+    },
+
+    hadValidCode: function () {
+     var errors = this.getAnnotations();
+     // By default CouchDB view functions don't pass lint
+     return _.every(errors, function(error) {
+      return FauxtonAPI.isIgnorableError(error.raw);
+      },this);
+    }
+
+  });
+
+  return Components;
+});
+


Mime
View raw message