You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2016/05/31 07:58:36 UTC

[08/27] fauxton commit: updated refs/heads/master to 0ca35da

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/resourcesSpec.js b/app/addons/documents/tests/resourcesSpec.js
index 176d726..661209b 100644
--- a/app/addons/documents/tests/resourcesSpec.js
+++ b/app/addons/documents/tests/resourcesSpec.js
@@ -9,459 +9,456 @@
 // 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([
-  '../../../core/api',
-  '../resources',
-  '../../../../test/mocha/testUtils',
-  '../base'
-], function (FauxtonAPI, 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'
-      });
+import FauxtonAPI from "../../../core/api";
+import Models from "../resources";
+import testUtils from "../../../../test/mocha/testUtils";
+import "../base";
+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('creates the right api-url with an absolute url', function () {
-      assert.ok(/file:/.test(collection.urlRef('apiurl')));
-    });
+  it('creates the right api-url with an absolute url', function () {
+    assert.ok(/file:/.test(collection.urlRef('apiurl')));
+  });
+
+});
 
+describe('Document', function () {
+  var doc;
+  beforeEach(function () {
+    doc = new Models.Doc({}, {});
   });
 
-  describe('Document', function () {
-    var doc;
-    beforeEach(function () {
-      doc = new Models.Doc({}, {});
+  it('does not remove an id attribute', function () {
+    var res = doc.parse({
+      _id: 'be31e531fe131bdf416b479ac1000484',
+      _rev: '4-3a1b9f4b988b413e9245cd250769da72',
+      id: 'foo'
     });
+    assert.equal(res.id, 'foo');
+  });
 
-    it('does not remove an id attribute', function () {
-      var res = doc.parse({
-        _id: 'be31e531fe131bdf416b479ac1000484',
-        _rev: '4-3a1b9f4b988b413e9245cd250769da72',
-        id: 'foo'
-      });
-      assert.equal(res.id, 'foo');
+  it('removes the id, if we create a document and get back an "id" instead of "_id"', function () {
+    // if we take the document {"_id": "mycustomid", "_rev": "18-9cdeb1b121137233e3466b06a1780c29", id: "foo"}
+    // and do a PUT request for an update, CouchDB will return:
+    // {"ok":true,"id":"mycustomid","rev":"18-9cdeb1b121137233e3466b06a1780c29"}
+    // and our Model will think it has the id "mycustomid" instead of "foo"
+    var res = doc.parse({
+      id: 'be31e531fe131bdf416b479ac1000484',
+      _rev: '4-3a1b9f4b988b413e9245cd250769da72',
+      ok: true
     });
+    assert.notOk(res.id);
+  });
 
-    it('removes the id, if we create a document and get back an "id" instead of "_id"', function () {
-      // if we take the document {"_id": "mycustomid", "_rev": "18-9cdeb1b121137233e3466b06a1780c29", id: "foo"}
-      // and do a PUT request for an update, CouchDB will return:
-      // {"ok":true,"id":"mycustomid","rev":"18-9cdeb1b121137233e3466b06a1780c29"}
-      // and our Model will think it has the id "mycustomid" instead of "foo"
-      var res = doc.parse({
-        id: 'be31e531fe131bdf416b479ac1000484',
-        _rev: '4-3a1b9f4b988b413e9245cd250769da72',
-        ok: true
-      });
-      assert.notOk(res.id);
+  it('can return the doc url, if id given', function () {
+    doc = new Models.Doc({_id: 'scholle'}, {
+      database: {id: 'blerg', safeID: function () { return this.id; }}
     });
 
-    it('can return the doc url, if id given', function () {
-      doc = new Models.Doc({_id: 'scholle'}, {
-        database: {id: 'blerg', safeID: function () { return this.id; }}
-      });
+    assert.ok(/\/blerg/.test(doc.url('apiurl')));
+  });
 
-      assert.ok(/\/blerg/.test(doc.url('apiurl')));
+  it('will return the API url to create a new doc, if no doc exists yet', function () {
+    doc = new Models.Doc({}, {
+      database: {id: 'blerg', safeID: function () { return this.id; }}
     });
 
-    it('will return the API url to create a new doc, if no doc exists yet', function () {
-      doc = new Models.Doc({}, {
-        database: {id: 'blerg', safeID: function () { return this.id; }}
-      });
-
-      assert.ok(/\/blerg/.test(doc.url('apiurl')));
-    });
+    assert.ok(/\/blerg/.test(doc.url('apiurl')));
   });
+});
 
-  describe('MangoIndex', function () {
-    var doc;
+describe('MangoIndex', function () {
+  var doc;
 
-    it('is deleteable', function () {
-      var index = {
-        ddoc: null,
-        name: '_all_docs',
-        type: 'json',
-        def: {fields: [{_id: 'asc'}]}
-      };
-      doc = new Models.MangoIndex(index, {});
+  it('is deleteable', function () {
+    var index = {
+      ddoc: null,
+      name: '_all_docs',
+      type: 'json',
+      def: {fields: [{_id: 'asc'}]}
+    };
+    doc = new Models.MangoIndex(index, {});
 
-      assert.ok(doc.isDeletable());
-    });
+    assert.ok(doc.isDeletable());
+  });
 
-    it('special docs are not deleteable', function () {
-      var index = {
-        ddoc: null,
-        name: '_all_docs',
-        type: 'special',
-        def: {fields: [{_id: 'asc'}]}
-      };
-      doc = new Models.MangoIndex(index, {});
+  it('special docs are not deleteable', function () {
+    var index = {
+      ddoc: null,
+      name: '_all_docs',
+      type: 'special',
+      def: {fields: [{_id: 'asc'}]}
+    };
+    doc = new Models.MangoIndex(index, {});
 
-      assert.notOk(doc.isDeletable());
-    });
+    assert.notOk(doc.isDeletable());
   });
+});
 
-  describe('MangoDocumentCollection', function () {
-    var collection;
-
-    it('gets 1 doc more to know if there are more than 20', function () {
-      collection = new Models.MangoDocumentCollection([{
-        name: 'myId1',
-        doc: 'num1'
-      },
-      {
-        name: 'myId2',
-        doc: 'num2'
-      }], {
-        database: {id: 'databaseId', safeID: function () { return this.id; }},
-        params: {limit: 20}
-      });
-      collection.setQuery({
-        selector: '$foo',
-        fields: 'bla'
-      });
+describe('MangoDocumentCollection', function () {
+  var collection;
 
-      assert.deepEqual({
-        selector: '$foo',
-        fields: 'bla',
-        limit: 21,
-        skip: undefined
-      }, collection.getPaginatedQuery());
+  it('gets 1 doc more to know if there are more than 20', function () {
+    collection = new Models.MangoDocumentCollection([{
+      name: 'myId1',
+      doc: 'num1'
+    },
+    {
+      name: 'myId2',
+      doc: 'num2'
+    }], {
+      database: {id: 'databaseId', safeID: function () { return this.id; }},
+      params: {limit: 20}
     });
-
-    it('on next page, skips first 20', function () {
-      collection = new Models.MangoDocumentCollection([{
-        name: 'myId1',
-        doc: 'num1'
-      },
-      {
-        name: 'myId2',
-        doc: 'num2'
-      }], {
-        database: {id: 'databaseId', safeID: function () { return this.id; }},
-        params: {limit: 20}
-      });
-      collection.setQuery({
-        selector: '$foo',
-        fields: 'bla'
-      });
-      collection.next();
-      assert.deepEqual({
-        selector: '$foo',
-        fields: 'bla',
-        limit: 21,
-        skip: 20
-      }, collection.getPaginatedQuery());
+    collection.setQuery({
+      selector: '$foo',
+      fields: 'bla'
     });
 
+    assert.deepEqual({
+      selector: '$foo',
+      fields: 'bla',
+      limit: 21,
+      skip: undefined
+    }, collection.getPaginatedQuery());
   });
 
-  describe('MangoDocumentCollection', function () {
-    var collection;
-
-    it('is not editable', function () {
-      collection = new Models.MangoIndexCollection([{
-        name: 'myId1',
-        doc: 'num1'
-      },
-      {
-        name: 'myId2',
-        doc: 'num2'
-      }], {
-        database: {id: 'databaseId', safeID: function () { return this.id; }},
-        params: {limit: 20}
-      });
+  it('on next page, skips first 20', function () {
+    collection = new Models.MangoDocumentCollection([{
+      name: 'myId1',
+      doc: 'num1'
+    },
+    {
+      name: 'myId2',
+      doc: 'num2'
+    }], {
+      database: {id: 'databaseId', safeID: function () { return this.id; }},
+      params: {limit: 20}
+    });
+    collection.setQuery({
+      selector: '$foo',
+      fields: 'bla'
+    });
+    collection.next();
+    assert.deepEqual({
+      selector: '$foo',
+      fields: 'bla',
+      limit: 21,
+      skip: 20
+    }, collection.getPaginatedQuery());
+  });
 
-      assert.notOk(collection.isEditable());
+});
+
+describe('MangoDocumentCollection', function () {
+  var collection;
+
+  it('is not editable', function () {
+    collection = new Models.MangoIndexCollection([{
+      name: 'myId1',
+      doc: 'num1'
+    },
+    {
+      name: 'myId2',
+      doc: 'num2'
+    }], {
+      database: {id: 'databaseId', safeID: function () { return this.id; }},
+      params: {limit: 20}
     });
+
+    assert.notOk(collection.isEditable());
   });
+});
 
-  describe('IndexCollection', function () {
-    var collection;
-
-    it('design docs are editable', function () {
-      collection = new Models.IndexCollection([{
-        _id: 'myId1',
-        doc: 'num1'
-      },
-      {
-        _id: 'myId2',
-        doc: 'num2'
-      }], {
-        database: {id: 'databaseId', safeID: function () { return this.id; }},
-        params: {limit: 20},
-        design: '_design/foobar'
-      });
+describe('IndexCollection', function () {
+  var collection;
 
-      assert.ok(collection.isEditable());
+  it('design docs are editable', function () {
+    collection = new Models.IndexCollection([{
+      _id: 'myId1',
+      doc: 'num1'
+    },
+    {
+      _id: 'myId2',
+      doc: 'num2'
+    }], {
+      database: {id: 'databaseId', safeID: function () { return this.id; }},
+      params: {limit: 20},
+      design: '_design/foobar'
     });
 
-    it('reduced design docs are NOT editable', function () {
-      collection = new Models.IndexCollection([{
-        _id: 'myId1',
-        doc: 'num1'
-      },
-      {
-        _id: 'myId2',
-        doc: 'num2'
-      }], {
-        database: {id: 'databaseId', safeID: function () { return this.id; }},
-        params: {limit: 20, reduce: true},
-        design: '_design/foobar'
-      });
+    assert.ok(collection.isEditable());
+  });
 
-      assert.notOk(collection.isEditable());
+  it('reduced design docs are NOT editable', function () {
+    collection = new Models.IndexCollection([{
+      _id: 'myId1',
+      doc: 'num1'
+    },
+    {
+      _id: 'myId2',
+      doc: 'num2'
+    }], {
+      database: {id: 'databaseId', safeID: function () { return this.id; }},
+      params: {limit: 20, reduce: true},
+      design: '_design/foobar'
     });
+
+    assert.notOk(collection.isEditable());
   });
+});
 
-  describe('AllDocs', function () {
-    var collection;
-
-    it('all-docs-list documents are always editable', function () {
-      collection = new Models.AllDocs([{
-        _id: 'myId1',
-        doc: 'num1'
-      },
-      {
-        _id: 'myId2',
-        doc: 'num2'
-      }], {
-        database: {id: 'databaseId', safeID: function () { return this.id; }},
-        params: {limit: 20}
-      });
+describe('AllDocs', function () {
+  var collection;
 
-      assert.ok(collection.isEditable());
+  it('all-docs-list documents are always editable', function () {
+    collection = new Models.AllDocs([{
+      _id: 'myId1',
+      doc: 'num1'
+    },
+    {
+      _id: 'myId2',
+      doc: 'num2'
+    }], {
+      database: {id: 'databaseId', safeID: function () { return this.id; }},
+      params: {limit: 20}
     });
+
+    assert.ok(collection.isEditable());
   });
+});
 
-  describe('QueryParams', function () {
-    describe('parse', function () {
-      it('should not parse arbitrary parameters', function () {
-        var params = {'foo': '[1]]'};
-        var result = Models.QueryParams.parse(params);
+describe('QueryParams', function () {
+  describe('parse', function () {
+    it('should not parse arbitrary parameters', function () {
+      var params = {'foo': '[1]]'};
+      var result = Models.QueryParams.parse(params);
 
-        assert.deepEqual(result, params);
-      });
+      assert.deepEqual(result, params);
+    });
+
+    it('parses startkey, endkey', function () {
+      var params = {
+        'startkey':'[\"a\",\"b\"]',
+        'endkey':'[\"c\",\"d\"]'
+      };
+      var result = Models.QueryParams.parse(params);
 
-      it('parses startkey, endkey', function () {
-        var params = {
-          'startkey':'[\"a\",\"b\"]',
-          'endkey':'[\"c\",\"d\"]'
-        };
-        var result = Models.QueryParams.parse(params);
-
-        assert.deepEqual(result, {
-          'startkey': ['a', 'b'],
-          'endkey': ['c', 'd']
-        });
+      assert.deepEqual(result, {
+        'startkey': ['a', 'b'],
+        'endkey': ['c', 'd']
       });
+    });
 
-      it('parses key', function () {
-        var params = {
-          key:'[1,2]'
-        };
-        var result = Models.QueryParams.parse(params);
+    it('parses key', function () {
+      var params = {
+        key:'[1,2]'
+      };
+      var result = Models.QueryParams.parse(params);
 
-        assert.deepEqual(result, {'key': [1, 2]});
-      });
+      assert.deepEqual(result, {'key': [1, 2]});
+    });
 
-      it('does not modify input', function () {
-        var params = {
-          key:'[\"a\",\"b\"]'
-        };
-        var clone = _.clone(params);
-        var result = Models.QueryParams.parse(params);
+    it('does not modify input', function () {
+      var params = {
+        key:'[\"a\",\"b\"]'
+      };
+      var clone = _.clone(params);
+      var result = Models.QueryParams.parse(params);
 
-        assert.deepEqual(params, clone);
-      });
+      assert.deepEqual(params, clone);
     });
+  });
 
-    describe('stringify', function () {
-      it('should not stringify arbitrary parameters', function () {
-        var params = {'foo': [1, 2, 3]};
-        var result = Models.QueryParams.stringify(params);
+  describe('stringify', function () {
+    it('should not stringify arbitrary parameters', function () {
+      var params = {'foo': [1, 2, 3]};
+      var result = Models.QueryParams.stringify(params);
 
-        assert.deepEqual(result, params);
-      });
+      assert.deepEqual(result, params);
+    });
 
-      it('stringifies startkey, endkey', function () {
-        var params = {
-          'startkey': ['a', 'b'],
-          'endkey': ['c', 'd']
-        };
+    it('stringifies startkey, endkey', function () {
+      var params = {
+        'startkey': ['a', 'b'],
+        'endkey': ['c', 'd']
+      };
 
-        var result = Models.QueryParams.stringify(params);
+      var result = Models.QueryParams.stringify(params);
 
-        assert.deepEqual(result, {
-          'startkey':'[\"a\",\"b\"]',
-          'endkey':'[\"c\",\"d\"]'
-        });
+      assert.deepEqual(result, {
+        'startkey':'[\"a\",\"b\"]',
+        'endkey':'[\"c\",\"d\"]'
       });
+    });
 
-      it('stringifies key', function () {
-        var params = {'key':['a', 'b']};
-        var result = Models.QueryParams.stringify(params);
+    it('stringifies key', function () {
+      var params = {'key':['a', 'b']};
+      var result = Models.QueryParams.stringify(params);
 
-        assert.deepEqual(result, { 'key': '[\"a\",\"b\"]' });
-      });
+      assert.deepEqual(result, { 'key': '[\"a\",\"b\"]' });
+    });
 
-      it('does not modify input', function () {
-        var params = {'key': ['a', 'b']};
-        var clone = _.clone(params);
-        var result = Models.QueryParams.stringify(params);
+    it('does not modify input', function () {
+      var params = {'key': ['a', 'b']};
+      var clone = _.clone(params);
+      var result = Models.QueryParams.stringify(params);
 
-        assert.deepEqual(params, clone);
-      });
+      assert.deepEqual(params, clone);
+    });
 
-      it('is symmetrical with parse', function () {
-        var params = {
-          'startkey': ['a', 'b'],
-          'endkey': ['c', 'd'],
-          'foo': '[1,2]',
-          'bar': 'abc'
-        };
+    it('is symmetrical with parse', function () {
+      var params = {
+        'startkey': ['a', 'b'],
+        'endkey': ['c', 'd'],
+        'foo': '[1,2]',
+        'bar': 'abc'
+      };
 
-        var clone = _.clone(params);
-        var json = Models.QueryParams.stringify(params);
-        var result = Models.QueryParams.parse(json);
+      var clone = _.clone(params);
+      var json = Models.QueryParams.stringify(params);
+      var result = Models.QueryParams.parse(json);
 
-        assert.deepEqual(result, clone);
-      });
+      assert.deepEqual(result, clone);
     });
   });
+});
 
-  describe('Bulk Delete', function () {
-    var databaseId = 'ente',
-        collection,
-        promise,
-        values;
-
-    values = [{
-      _id: '1',
-      _rev: '1234561',
-      _deleted: true
-    },
-    {
-      _id: '2',
-      _rev: '1234562',
-      _deleted: true
-    },
-    {
-      _id: '3',
-      _rev: '1234563',
-      _deleted: true
-    }];
-
-    beforeEach(function () {
-      collection = new Models.BulkDeleteDocCollection(values, {
-        databaseId: databaseId
-      });
-
-      promise = FauxtonAPI.Deferred();
+describe('Bulk Delete', function () {
+  var databaseId = 'ente',
+      collection,
+      promise,
+      values;
+
+  values = [{
+    _id: '1',
+    _rev: '1234561',
+    _deleted: true
+  },
+  {
+    _id: '2',
+    _rev: '1234562',
+    _deleted: true
+  },
+  {
+    _id: '3',
+    _rev: '1234563',
+    _deleted: true
+  }];
+
+  beforeEach(function () {
+    collection = new Models.BulkDeleteDocCollection(values, {
+      databaseId: databaseId
     });
 
-    it('contains the models', function () {
-      collection = new Models.BulkDeleteDocCollection(values, {
-        databaseId: databaseId
-      });
+    promise = FauxtonAPI.Deferred();
+  });
 
-      assert.equal(collection.length, 3);
+  it('contains the models', function () {
+    collection = new Models.BulkDeleteDocCollection(values, {
+      databaseId: databaseId
     });
 
-    it('clears the memory if no errors happened', function () {
-      collection.handleResponse([
-        {'ok': true, 'id': '1', 'rev': '10-72cd2edbcc0d197ce96188a229a7af01'},
-        {'ok': true, 'id': '2', 'rev': '6-da537822b9672a4b2f42adb1be04a5b1'}
-      ], promise);
+    assert.equal(collection.length, 3);
+  });
 
-      assert.equal(collection.length, 1);
-    });
+  it('clears the memory if no errors happened', function () {
+    collection.handleResponse([
+      {'ok': true, 'id': '1', 'rev': '10-72cd2edbcc0d197ce96188a229a7af01'},
+      {'ok': true, 'id': '2', 'rev': '6-da537822b9672a4b2f42adb1be04a5b1'}
+    ], promise);
 
-    it('triggers a removed event with all ids', function () {
-      collection.listenToOnce(collection, 'removed', function (ids) {
-        assert.deepEqual(ids, ['Deferred', 'DeskSet']);
-      });
+    assert.equal(collection.length, 1);
+  });
 
-      collection.handleResponse([
-        {'ok': true, 'id': 'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
-        {'ok': true, 'id': 'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
-      ], promise);
+  it('triggers a removed event with all ids', function () {
+    collection.listenToOnce(collection, 'removed', function (ids) {
+      assert.deepEqual(ids, ['Deferred', 'DeskSet']);
     });
 
-    it('triggers a error event with all errored ids', function () {
-      collection.listenToOnce(collection, 'error', function (ids) {
-        assert.deepEqual(ids, ['Deferred']);
-      });
-      collection.handleResponse([
-        {'error':'conflict', 'id':'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
-        {'ok':true, 'id':'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
-      ], promise);
-    });
+    collection.handleResponse([
+      {'ok': true, 'id': 'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
+      {'ok': true, 'id': 'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
+    ], promise);
+  });
 
-    it('removes successfull deleted from the collection but keeps one with errors', function () {
-      collection.handleResponse([
-        {'error':'conflict', 'id':'1', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
-        {'ok':true, 'id':'2', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'},
-        {'error':'conflict', 'id':'3', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
-      ], promise);
-      assert.ok(collection.get('1'));
-      assert.ok(collection.get('3'));
-      assert.notOk(collection.get('2'));
+  it('triggers a error event with all errored ids', function () {
+    collection.listenToOnce(collection, 'error', function (ids) {
+      assert.deepEqual(ids, ['Deferred']);
     });
+    collection.handleResponse([
+      {'error':'conflict', 'id':'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
+      {'ok':true, 'id':'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
+    ], promise);
+  });
 
-    it('triggers resolve for successful delete', function () {
-      var spy = sinon.spy();
-      promise.then(spy);
-
-      collection.handleResponse([
-        {'ok':true, 'id':'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
-        {'ok':true, 'id':'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
-      ], promise);
+  it('removes successfull deleted from the collection but keeps one with errors', function () {
+    collection.handleResponse([
+      {'error':'conflict', 'id':'1', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
+      {'ok':true, 'id':'2', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'},
+      {'error':'conflict', 'id':'3', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
+    ], promise);
+    assert.ok(collection.get('1'));
+    assert.ok(collection.get('3'));
+    assert.notOk(collection.get('2'));
+  });
 
-      assert.ok(spy.calledOnce);
+  it('triggers resolve for successful delete', function () {
+    var spy = sinon.spy();
+    promise.then(spy);
 
-    });
+    collection.handleResponse([
+      {'ok':true, 'id':'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
+      {'ok':true, 'id':'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'}
+    ], promise);
 
-    it('triggers resolve for successful delete with errors as well', function () {
-      var spy = sinon.spy();
-      promise.then(spy);
-      var ids = {
-        errorIds: ['1'],
-        successIds: ['Deferred', 'DeskSet']
-      };
+    assert.ok(spy.calledOnce);
 
-      collection.handleResponse([
-        {'ok':true, 'id':'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
-        {'ok':true, 'id':'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'},
-        {'error':'conflict', 'id':'1', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
-      ], promise);
+  });
 
-      assert.ok(spy.calledWith(ids));
-    });
+  it('triggers resolve for successful delete with errors as well', function () {
+    var spy = sinon.spy();
+    promise.then(spy);
+    var ids = {
+      errorIds: ['1'],
+      successIds: ['Deferred', 'DeskSet']
+    };
+
+    collection.handleResponse([
+      {'ok':true, 'id':'Deferred', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
+      {'ok':true, 'id':'DeskSet', 'rev':'6-da537822b9672a4b2f42adb1be04a5b1'},
+      {'error':'conflict', 'id':'1', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'},
+    ], promise);
+
+    assert.ok(spy.calledWith(ids));
+  });
 
-    it('triggers reject for failed delete', function () {
-      var spy = sinon.spy();
-      promise.fail(spy);
+  it('triggers reject for failed delete', function () {
+    var spy = sinon.spy();
+    promise.fail(spy);
 
-      collection.handleResponse([
-        {'error':'conflict', 'id':'1', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'}
-      ], promise);
+    collection.handleResponse([
+      {'error':'conflict', 'id':'1', 'rev':'10-72cd2edbcc0d197ce96188a229a7af01'}
+    ], promise);
 
-      assert.ok(spy.calledWith(['1']));
+    assert.ok(spy.calledWith(['1']));
 
-    });
+  });
 
 
-  });
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/tests/routeSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/routeSpec.js b/app/addons/documents/tests/routeSpec.js
index 5807b02..57bf9aa 100644
--- a/app/addons/documents/tests/routeSpec.js
+++ b/app/addons/documents/tests/routeSpec.js
@@ -10,39 +10,36 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  '../../../core/api',
-  '../base',
-  '../routes',
-  '../header/header.actions',
-  '../index-results/actions',
-  '../../../../test/mocha/testUtils',
-], function (FauxtonAPI, Base, Documents, HeaderActions, IndexResultsActions, testUtils) {
-  var assert = testUtils.assert;
-  var DocumentRoute = Documents.RouteObjects[2];
-
-  //commenting out for now. This test adds little value and is breaking the routeObjectSpecs
-  describe('Documents Route', function () {
-
-    /*it('the all-documents-list has a right header', function () {
-       var routeObj = new DocumentRoute(null, null, ['test']);
-
-       routeObj.allDocs('newdatabase', null);
-      assert.equal(typeof routeObj.rightHeader, 'object');
-
-    });*/
-
-  });
-
-  //    until there is consensus on how to encode json responses
-  //    https://issues.apache.org/jira/browse/COUCHDB-2748
-  //    taking out this test for https://github.com/apache/couchdb-fauxton/pull/489
-
-  //describe('Fauxton Urls', function () {
-    // it('document app encodes document id', function () {
-    //   var id = "\foo";
-    //   var url = FauxtonAPI.urls('document', 'app', 'fake-db', id);
-    //   assert.deepEqual("/database/fake-db/%0Coo", url);
-    // });
-  //});
+import FauxtonAPI from "../../../core/api";
+import Base from "../base";
+import Documents from "../routes";
+import HeaderActions from "../header/header.actions";
+import IndexResultsActions from "../index-results/actions";
+import testUtils from "../../../../test/mocha/testUtils";
+var assert = testUtils.assert;
+var DocumentRoute = Documents.RouteObjects[2];
+
+//commenting out for now. This test adds little value and is breaking the routeObjectSpecs
+describe('Documents Route', function () {
+
+  /*it('the all-documents-list has a right header', function () {
+     var routeObj = new DocumentRoute(null, null, ['test']);
+
+     routeObj.allDocs('newdatabase', null);
+    assert.equal(typeof routeObj.rightHeader, 'object');
+
+  });*/
+
 });
+
+//    until there is consensus on how to encode json responses
+//    https://issues.apache.org/jira/browse/COUCHDB-2748
+//    taking out this test for https://github.com/apache/couchdb-fauxton/pull/489
+
+//describe('Fauxton Urls', function () {
+// it('document app encodes document id', function () {
+//   var id = "\foo";
+//   var url = FauxtonAPI.urls('document', 'app', 'fake-db', id);
+//   assert.deepEqual("/database/fake-db/%0Coo", url);
+// });
+//});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js
index 6c46076..3b1ff67 100644
--- a/app/addons/documents/views.js
+++ b/app/addons/documents/views.js
@@ -10,95 +10,86 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  "../../app",
-  "../../core/api",
-  "../fauxton/components",
-  "./resources",
-  "../databases/resources",
-
-  // Views
-  "./queryoptions/queryoptions.react",
-  "./queryoptions/actions",
-  './jumptodoc.react',
-
-  //plugins
-  "../../../assets/js/plugins/prettify"
-],
-
-function (app, FauxtonAPI, Components, Documents, Databases, QueryOptions, QueryActions, JumpToDoc) {
-
-  var Views = {};
-
-  function showError (msg) {
-    FauxtonAPI.addNotification({
-      msg: msg,
-      type: 'error',
-      clear:  true
-    });
-  }
+import app from "../../app";
+import FauxtonAPI from "../../core/api";
+import Components from "../fauxton/components";
+import Documents from "./resources";
+import Databases from "../databases/resources";
+import QueryOptions from "./queryoptions/queryoptions.react";
+import QueryActions from "./queryoptions/actions";
+import JumpToDoc from "./jumptodoc.react";
+import "../../../assets/js/plugins/prettify";
+
+var Views = {};
+
+function showError (msg) {
+  FauxtonAPI.addNotification({
+    msg: msg,
+    type: 'error',
+    clear:  true
+  });
+}
+
+Views.RightAllDocsHeader = FauxtonAPI.View.extend({
+  className: "header-right right-db-header flex-layout flex-row",
+  template: "addons/documents/templates/all_docs_header",
+  events: {
+    'click .toggle-select-menu': 'selectAllMenu'
+  },
+
+  initialize: function (options) {
+    this.database = options.database;
+    this.params = options.params;
+
+    _.bindAll(this);
+    this.selectVisible = false;
+    FauxtonAPI.Events.on('success:bulkDelete', this.selectAllMenu);
+  },
+
+  afterRender: function () {
+    QueryOptions.render('#query-options');
+    JumpToDoc.render('#header-search', this.database, this.database.allDocs);
+    this.toggleQueryOptionsHeader(this.isHidden);
+  },
+
+  cleanup: function () {
+    FauxtonAPI.Events.unbind('success:bulkDelete');
+  },
+
+  selectAllMenu: function (e) {
+    FauxtonAPI.triggerRouteEvent("toggleSelectHeader");
+    FauxtonAPI.Events.trigger("documents:showSelectAll", this.selectVisible);
+  },
+
+  resetQueryOptions: function (options) {
+    QueryActions.reset(options);
+  },
+
+  hideQueryOptions: function () {
+    this.isHidden = true;
+    if (this.hasRendered) {
+      this.toggleQueryOptionsHeader(this.isHidden);
+    }
+  },
 
-  Views.RightAllDocsHeader = FauxtonAPI.View.extend({
-    className: "header-right right-db-header flex-layout flex-row",
-    template: "addons/documents/templates/all_docs_header",
-    events: {
-      'click .toggle-select-menu': 'selectAllMenu'
-    },
-
-    initialize: function (options) {
-      this.database = options.database;
-      this.params = options.params;
-
-      _.bindAll(this);
-      this.selectVisible = false;
-      FauxtonAPI.Events.on('success:bulkDelete', this.selectAllMenu);
-    },
-
-    afterRender: function () {
-      QueryOptions.render('#query-options');
-      JumpToDoc.render('#header-search', this.database, this.database.allDocs);
+  showQueryOptions: function () {
+    this.isHidden = false;
+    if (this.hasRendered) {
       this.toggleQueryOptionsHeader(this.isHidden);
-    },
-
-    cleanup: function () {
-      FauxtonAPI.Events.unbind('success:bulkDelete');
-    },
-
-    selectAllMenu: function (e) {
-      FauxtonAPI.triggerRouteEvent("toggleSelectHeader");
-      FauxtonAPI.Events.trigger("documents:showSelectAll", this.selectVisible);
-    },
-
-    resetQueryOptions: function (options) {
-      QueryActions.reset(options);
-    },
-
-    hideQueryOptions: function () {
-      this.isHidden = true;
-      if (this.hasRendered) {
-        this.toggleQueryOptionsHeader(this.isHidden);
-      }
-    },
-
-    showQueryOptions: function () {
-      this.isHidden = false;
-      if (this.hasRendered) {
-        this.toggleQueryOptionsHeader(this.isHidden);
-      }
-    },
-
-    toggleQueryOptionsHeader: function (hide) {
-      $("#header-query-options").toggleClass("hide", hide);
-    },
-
-    serialize: function () {
-      return {
-        database: this.database.get('id')
-      };
     }
-  });
+  },
 
-  Documents.Views = Views;
+  toggleQueryOptionsHeader: function (hide) {
+    $("#header-query-options").toggleClass("hide", hide);
+  },
 
-  return Documents;
+  serialize: function () {
+    return {
+      database: this.database.get('id')
+    };
+  }
 });
+
+Documents.Views = Views;
+
+export default Documents;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index 7d22bb1..a907c03 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -10,112 +10,106 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  "../../app",
-  "../../core/api",
-  "./components",
-  './notifications/notifications.react',
-  './notifications/actions',
-  "./navigation/components.react",
-  "./navigation/actions",
-  '../components/react-components.react',
-  '../components/actions',
-  './assets/less/fauxton.less'
-],
-
-function (app, FauxtonAPI, Components, NotificationComponents, Actions, NavbarReactComponents, NavigationActions,
-          ReactComponents, ComponentActions) {
-
-  var Fauxton = FauxtonAPI.addon();
-  FauxtonAPI.addNotification = function (options) {
-    options = _.extend({
-      msg: 'Notification Event Triggered!',
-      type: 'info',
-      escape: true,
-      clear: false
-    }, options);
-
-    // log all notifications in a store
-    Actions.addNotification(options);
-  };
-
-  FauxtonAPI.UUID = FauxtonAPI.Model.extend({
-    initialize: function (options) {
-      options = _.extend({count: 1}, options);
-      this.count = options.count;
-    },
-
-    url: function () {
-      return app.host + "/_uuids?count=" + this.count;
-    },
-
-    next: function () {
-      return this.get("uuids").pop();
-    }
-  });
-
+import app from "../../app";
+import FauxtonAPI from "../../core/api";
+import Components from "./components";
+import NotificationComponents from "./notifications/notifications.react";
+import Actions from "./notifications/actions";
+import NavbarReactComponents from "./navigation/components.react";
+import NavigationActions from "./navigation/actions";
+import ReactComponents from "../components/react-components.react";
+import ComponentActions from "../components/actions";
+import "./assets/less/fauxton.less";
+
+var Fauxton = FauxtonAPI.addon();
+FauxtonAPI.addNotification = function (options) {
+  options = _.extend({
+    msg: 'Notification Event Triggered!',
+    type: 'info',
+    escape: true,
+    clear: false
+  }, options);
+
+  // log all notifications in a store
+  Actions.addNotification(options);
+};
+
+FauxtonAPI.UUID = FauxtonAPI.Model.extend({
+  initialize: function (options) {
+    options = _.extend({count: 1}, options);
+    this.count = options.count;
+  },
+
+  url: function () {
+    return app.host + "/_uuids?count=" + this.count;
+  },
+
+  next: function () {
+    return this.get("uuids").pop();
+  }
+});
 
-  Fauxton.initialize = function () {
 
-    FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
-      NavigationActions.setNavbarActiveLink(_.result(routeObject, 'selectedHeader'));
+Fauxton.initialize = function () {
 
-      // always attempt to render the API Bar. Even if it's hidden on initial load, it may be enabled later
-      routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
-        buttonVisible: true,
-        contentVisible: false
-      });
+  FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
+    NavigationActions.setNavbarActiveLink(_.result(routeObject, 'selectedHeader'));
 
-      if (routeObject.get('apiUrl')) {
-        var apiAndDocs = routeObject.get('apiUrl');
-
-        ComponentActions.updateAPIBar({
-          buttonVisible: true,
-          contentVisible: false,
-          endpoint: apiAndDocs[0],
-          docURL: apiAndDocs[1]
-        });
-      } else {
-        ComponentActions.hideAPIBarButton();
-      }
-
-      if (!routeObject.get('hideNotificationCenter')) {
-        routeObject.setComponent('#notification-center-btn', NotificationComponents.NotificationCenterButton);
-      }
-
-      if (routeObject.overrideBreadcrumbs) { return; }
-
-      FauxtonAPI.masterLayout.removeView('#breadcrumbs');
-      var crumbs = routeObject.get('crumbs');
-
-      if (crumbs.length) {
-        FauxtonAPI.masterLayout.setView('#breadcrumbs', new Components.Breadcrumbs({
-          crumbs: crumbs
-        }), true).render();
-      }
+    // always attempt to render the API Bar. Even if it's hidden on initial load, it may be enabled later
+    routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
+      buttonVisible: true,
+      contentVisible: false
     });
 
-    var primaryNavBarEl = $('#primary-navbar')[0];
-    if (primaryNavBarEl) {
-      NavbarReactComponents.renderNavBar(primaryNavBarEl);
+    if (routeObject.get('apiUrl')) {
+      var apiAndDocs = routeObject.get('apiUrl');
+
+      ComponentActions.updateAPIBar({
+        buttonVisible: true,
+        contentVisible: false,
+        endpoint: apiAndDocs[0],
+        docURL: apiAndDocs[1]
+      });
+    } else {
+      ComponentActions.hideAPIBarButton();
     }
 
-    var notificationEl = $('#notifications')[0];
-    if (notificationEl) {
-      NotificationComponents.renderNotificationController(notificationEl);
+    if (!routeObject.get('hideNotificationCenter')) {
+      routeObject.setComponent('#notification-center-btn', NotificationComponents.NotificationCenterButton);
     }
-    var versionInfo = new Fauxton.VersionInfo();
 
-    versionInfo.fetch().then(function () {
-      NavigationActions.setNavbarVersionInfo(versionInfo.get("version"));
-    });
-  };
+    if (routeObject.overrideBreadcrumbs) { return; }
 
-  Fauxton.VersionInfo = Backbone.Model.extend({
-    url: function () {
-      return app.host;
+    FauxtonAPI.masterLayout.removeView('#breadcrumbs');
+    var crumbs = routeObject.get('crumbs');
+
+    if (crumbs.length) {
+      FauxtonAPI.masterLayout.setView('#breadcrumbs', new Components.Breadcrumbs({
+        crumbs: crumbs
+      }), true).render();
     }
   });
 
-  return Fauxton;
+  var primaryNavBarEl = $('#primary-navbar')[0];
+  if (primaryNavBarEl) {
+    NavbarReactComponents.renderNavBar(primaryNavBarEl);
+  }
+
+  var notificationEl = $('#notifications')[0];
+  if (notificationEl) {
+    NotificationComponents.renderNotificationController(notificationEl);
+  }
+  var versionInfo = new Fauxton.VersionInfo();
+
+  versionInfo.fetch().then(function () {
+    NavigationActions.setNavbarVersionInfo(versionInfo.get("version"));
+  });
+};
+
+Fauxton.VersionInfo = Backbone.Model.extend({
+  url: function () {
+    return app.host;
+  }
 });
+
+export default Fauxton;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/0ca35da7/app/addons/fauxton/components.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/components.js b/app/addons/fauxton/components.js
index 73402be..97db63a 100644
--- a/app/addons/fauxton/components.js
+++ b/app/addons/fauxton/components.js
@@ -10,580 +10,573 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([
-  "../../app",
-  // Libs
-  "../../core/api",
-  "../../../assets/js/libs/spin.min",
-  '../components/react-components.react',
-  '../components/actions',
-  '../documents/helpers',
-
-  "velocity-animate/velocity.ui"
-],
-
-function (app, FauxtonAPI, spin, ReactComponents, ComponentsActions, Helpers) {
-  var Components = FauxtonAPI.addon();
-
-  // XXX: move to /addons/documents - component is tightly coupled to documents/alldocs
-  Components.LeftHeader = FauxtonAPI.View.extend({
-    className: "header-left",
-    template: "addons/fauxton/templates/header_left",
+import app from "../../app";
+import FauxtonAPI from "../../core/api";
+import spin from "../../../assets/js/libs/spin.min";
+import ReactComponents from "../components/react-components.react";
+import ComponentsActions from "../components/actions";
+import Helpers from "../documents/helpers";
+import "velocity-animate/velocity.ui";
+var Components = FauxtonAPI.addon();
+
+// XXX: move to /addons/documents - component is tightly coupled to documents/alldocs
+Components.LeftHeader = FauxtonAPI.View.extend({
+  className: "header-left",
+  template: "addons/fauxton/templates/header_left",
+
+  initialize: function (options) {
+    this.lookaheadTrayOptions = options.lookaheadTrayOptions || null;
+    this.crumbs = options.crumbs || [];
+
+    this.dbName = options.databaseName;
+
+    // listen for breadcrumb clicks
+    this.listenTo(FauxtonAPI.Events, 'breadcrumb:click', this.toggleTray);
+    this.listenTo(FauxtonAPI.Events, 'lookaheadTray:close', this.unselectLastBreadcrumb);
+  },
+
+  updateCrumbs: function (crumbs) {
+
+    // if the breadcrumbs haven't changed, don't bother re-rendering the component
+    if (_.isEqual(this.crumbs, crumbs)) {
+      return;
+    }
 
-    initialize: function (options) {
-      this.lookaheadTrayOptions = options.lookaheadTrayOptions || null;
-      this.crumbs = options.crumbs || [];
+    this.crumbs = crumbs;
+    this.breadcrumbs && this.breadcrumbs.update(crumbs);
+  },
 
-      this.dbName = options.databaseName;
+  unselectLastBreadcrumb: function () {
+    this.breadcrumbs.unselectLastBreadcrumb();
+  },
 
-      // listen for breadcrumb clicks
-      this.listenTo(FauxtonAPI.Events, 'breadcrumb:click', this.toggleTray);
-      this.listenTo(FauxtonAPI.Events, 'lookaheadTray:close', this.unselectLastBreadcrumb);
-    },
+  toggleTray: function () {
+    if (this.lookaheadTray !== null) {
+      this.lookaheadTray.toggleTray();
+    }
+  },
 
-    updateCrumbs: function (crumbs) {
+  beforeRender: function () {
+    this.setUpCrumbs();
+    this.setUpDropDownMenu();
 
-      // if the breadcrumbs haven't changed, don't bother re-rendering the component
-      if (_.isEqual(this.crumbs, crumbs)) {
-        return;
-      }
+    if (this.lookaheadTray !== null) {
+      this.setUpLookaheadTray();
+    }
+  },
+
+  setUpCrumbs: function () {
+    this.breadcrumbs = this.insertView("#header-breadcrumbs", new Components.Breadcrumbs({
+      crumbs: this.crumbs
+    }));
+  },
+
+  getModififyDbLinks: function () {
+    var onClickDelete = ComponentsActions.showDeleteDatabaseModal;
+    return Helpers.getModifyDatabaseLinks(this.dbName, onClickDelete);
+  },
+
+  setUpDropDownMenu: function () {
+    var dropdownMenuLinks = Helpers.getNewButtonLinks(this.dbName);
+
+    dropdownMenuLinks = this.getModififyDbLinks().concat(dropdownMenuLinks);
+
+    this.dropdown = this.insertView("#header-dropdown-menu", new Components.MenuDropDownReact({
+      links: dropdownMenuLinks,
+    }));
+  },
+
+  setUpLookaheadTray: function () {
+    var options = this.lookaheadTrayOptions,
+        dbNames = options.databaseCollection.getDatabaseNames(),
+        currentDBName = this.crumbs[1].name;
+
+    // remove the current database name from the list
+    dbNames = _.without(dbNames, currentDBName);
+
+    this.lookaheadTray = this.insertView("#header-lookahead", new Components.LookaheadTray({
+      data: dbNames,
+      toggleEventName: options.toggleEventName,
+      onUpdateEventName: options.onUpdateEventName,
+      placeholder: options.placeholder
+    }));
+  }
+});
 
-      this.crumbs = crumbs;
-      this.breadcrumbs && this.breadcrumbs.update(crumbs);
-    },
+Components.MenuDropDownReact = FauxtonAPI.View.extend({
+  initialize: function (options) {
+    this.options = options;
+  },
 
-    unselectLastBreadcrumb: function () {
-      this.breadcrumbs.unselectLastBreadcrumb();
-    },
+  afterRender: function () {
+    ReactComponents.renderMenuDropDown(this.el, this.options);
+  },
 
-    toggleTray: function () {
-      if (this.lookaheadTray !== null) {
-        this.lookaheadTray.toggleTray();
-      }
-    },
+  cleanup: function () {
+    ReactComponents.removeMenuDropDown(this.el);
+  }
+});
+Components.Breadcrumbs = FauxtonAPI.View.extend({
+  className: "breadcrumb pull-left",
+  tagName: "ul",
+  template: "addons/fauxton/templates/breadcrumbs",
+
+  events:  {
+    "click .js-lastelement": "toggleLastElement"
+  },
+
+  serialize: function () {
+    var crumbs = _.clone(this.crumbs);
+
+    // helper template function to determine when to insert a delimiter char
+    var nextCrumbHasLabel = function (crumb, index) {
+      var nextHasLabel = crumbs[index + 1].name !== "";
+      return index < crumbs.length && crumb.name && nextHasLabel;
+    };
+
+    return {
+      toggleDisabled: this.toggleDisabled,
+      crumbs: crumbs,
+      nextCrumbHasLabel: nextCrumbHasLabel
+    };
+  },
+
+  toggleLastElement: function (event) {
+    if (this.toggleDisabled) {
+      return;
+    }
+    this.$(event.currentTarget).toggleClass('js-enabled');
+    FauxtonAPI.Events.trigger('breadcrumb:click');
+  },
 
-    beforeRender: function () {
-      this.setUpCrumbs();
-      this.setUpDropDownMenu();
+  unselectLastBreadcrumb: function () {
+    if (this.toggleDisabled) {
+      return;
+    }
+    this.$('.js-enabled').removeClass('js-enabled');
+  },
+
+  update: function (crumbs) {
+    this.crumbs = crumbs;
+    this.render();
+  },
+
+  initialize: function (options) {
+    this.crumbs = options.crumbs;
+    this.toggleDisabled = options.toggleDisabled || false;
+  }
+});
 
-      if (this.lookaheadTray !== null) {
-        this.setUpLookaheadTray();
-      }
-    },
-
-    setUpCrumbs: function () {
-      this.breadcrumbs = this.insertView("#header-breadcrumbs", new Components.Breadcrumbs({
-        crumbs: this.crumbs
-      }));
-    },
-
-    getModififyDbLinks: function () {
-      var onClickDelete = ComponentsActions.showDeleteDatabaseModal;
-      return Helpers.getModifyDatabaseLinks(this.dbName, onClickDelete);
-    },
-
-    setUpDropDownMenu: function () {
-      var dropdownMenuLinks = Helpers.getNewButtonLinks(this.dbName);
-
-      dropdownMenuLinks = this.getModififyDbLinks().concat(dropdownMenuLinks);
-
-      this.dropdown = this.insertView("#header-dropdown-menu", new Components.MenuDropDownReact({
-        links: dropdownMenuLinks,
-      }));
-    },
-
-    setUpLookaheadTray: function () {
-      var options = this.lookaheadTrayOptions,
-          dbNames = options.databaseCollection.getDatabaseNames(),
-          currentDBName = this.crumbs[1].name;
-
-      // remove the current database name from the list
-      dbNames = _.without(dbNames, currentDBName);
-
-      this.lookaheadTray = this.insertView("#header-lookahead", new Components.LookaheadTray({
-        data: dbNames,
-        toggleEventName: options.toggleEventName,
-        onUpdateEventName: options.onUpdateEventName,
-        placeholder: options.placeholder
-      }));
+/**
+ * Our generic Tray component. All trays should extend this guy - it offers some convenient boilerplate code for
+ * hiding/showing, event publishing and so on. The important functions that can be called on the child Views are:
+ * - hideTray
+ * - showTray
+ * - toggleTray
+ */
+Components.Tray = FauxtonAPI.View.extend({
+
+  // populated dynamically
+  events: {},
+
+  initTray: function (opts) {
+    this.toggleTrayBtnSelector = (_.has(opts, 'toggleTrayBtnSelector')) ? opts.toggleTrayBtnSelector : null;
+    this.onShowTray = (_.has(opts, 'onShowTray')) ? opts.onShowTray : null;
+
+    // if the component extending this one passed along the selector of the element that toggles the tray,
+    // add the appropriate events
+    if (!_.isNull(this.toggleTrayBtnSelector)) {
+      this.events['click ' + this.toggleTrayBtnSelector] = 'toggleTray';
     }
-  });
 
-  Components.MenuDropDownReact = FauxtonAPI.View.extend({
-    initialize: function (options) {
-      this.options = options;
-    },
+    _.bind(this.toggleTray, this);
+    _.bind(this.trayVisible, this);
+    _.bind(this.hideTray, this);
+    _.bind(this.showTray, this);
 
-    afterRender: function () {
-      ReactComponents.renderMenuDropDown(this.el, this.options);
-    },
+    // a unique identifier for this tray
+    this.trayId = 'tray-' + this.cid;
 
-    cleanup: function () {
-      ReactComponents.removeMenuDropDown(this.el);
-    }
-  });
-  Components.Breadcrumbs = FauxtonAPI.View.extend({
-    className: "breadcrumb pull-left",
-    tagName: "ul",
-    template: "addons/fauxton/templates/breadcrumbs",
-
-    events:  {
-      "click .js-lastelement": "toggleLastElement"
-    },
-
-    serialize: function () {
-      var crumbs = _.clone(this.crumbs);
-
-      // helper template function to determine when to insert a delimiter char
-      var nextCrumbHasLabel = function (crumb, index) {
-        var nextHasLabel = crumbs[index + 1].name !== "";
-        return index < crumbs.length && crumb.name && nextHasLabel;
-      };
-
-      return {
-        toggleDisabled: this.toggleDisabled,
-        crumbs: crumbs,
-        nextCrumbHasLabel: nextCrumbHasLabel
-      };
-    },
-
-    toggleLastElement: function (event) {
-      if (this.toggleDisabled) {
+    var that = this;
+    $('body').on('click.' + this.trayId, function (e) {
+      var $clickEl = $(e.target);
+
+      if (!that.trayVisible()) {
         return;
       }
-      this.$(event.currentTarget).toggleClass('js-enabled');
-      FauxtonAPI.Events.trigger('breadcrumb:click');
-    },
-
-    unselectLastBreadcrumb: function () {
-      if (this.toggleDisabled) {
+      if (!_.isNull(that.toggleTrayBtnSelector) && $clickEl.closest(that.toggleTrayBtnSelector).length) {
         return;
       }
-      this.$('.js-enabled').removeClass('js-enabled');
-    },
+      if (!$clickEl.closest('.tray').length) {
+        that.hideTray();
+      }
+    });
+
+    FauxtonAPI.Events.on(FauxtonAPI.constants.EVENTS.TRAY_OPENED, this.onTrayOpenEvent, this);
+  },
 
-    update: function (crumbs) {
-      this.crumbs = crumbs;
-      this.render();
-    },
+  cleanup: function () {
+    $('body').off('click.' + this.trayId);
+  },
 
-    initialize: function (options) {
-      this.crumbs = options.crumbs;
-      this.toggleDisabled = options.toggleDisabled || false;
+  // all trays publish a EVENTS.TRAY_OPENED event containing their unique ID. This listens for those events and
+  // closes the current tray if it's already open
+  onTrayOpenEvent: function (msg) {
+    if (!_.has(msg, 'trayId')) {
+      return;
     }
-  });
-
-  /**
-   * Our generic Tray component. All trays should extend this guy - it offers some convenient boilerplate code for
-   * hiding/showing, event publishing and so on. The important functions that can be called on the child Views are:
-   * - hideTray
-   * - showTray
-   * - toggleTray
-   */
-  Components.Tray = FauxtonAPI.View.extend({
-
-    // populated dynamically
-    events: {},
-
-    initTray: function (opts) {
-      this.toggleTrayBtnSelector = (_.has(opts, 'toggleTrayBtnSelector')) ? opts.toggleTrayBtnSelector : null;
-      this.onShowTray = (_.has(opts, 'onShowTray')) ? opts.onShowTray : null;
-
-      // if the component extending this one passed along the selector of the element that toggles the tray,
-      // add the appropriate events
-      if (!_.isNull(this.toggleTrayBtnSelector)) {
-        this.events['click ' + this.toggleTrayBtnSelector] = 'toggleTray';
-      }
+    if (msg.trayId !== this.trayId && this.trayVisible()) {
+      this.hideTray();
+    }
+  },
 
-      _.bind(this.toggleTray, this);
-      _.bind(this.trayVisible, this);
-      _.bind(this.hideTray, this);
-      _.bind(this.showTray, this);
+  toggleTray: function (e) {
+    e.preventDefault();
 
-      // a unique identifier for this tray
-      this.trayId = 'tray-' + this.cid;
+    if (this.trayVisible()) {
+      this.hideTray();
+    } else {
+      this.showTray();
+    }
+  },
 
-      var that = this;
-      $('body').on('click.' + this.trayId, function (e) {
-        var $clickEl = $(e.target);
+  hideTray: function () {
+    var $tray = this.$('.tray');
+    $tray.velocity('reverse', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
+      $tray.hide();
+    });
 
-        if (!that.trayVisible()) {
-          return;
-        }
-        if (!_.isNull(that.toggleTrayBtnSelector) && $clickEl.closest(that.toggleTrayBtnSelector).length) {
-          return;
-        }
-        if (!$clickEl.closest('.tray').length) {
-          that.hideTray();
-        }
-      });
+    if (!_.isNull(this.toggleTrayBtnSelector)) {
+      this.$(this.toggleTrayBtnSelector).removeClass('enabled');
+    }
+  },
 
-      FauxtonAPI.Events.on(FauxtonAPI.constants.EVENTS.TRAY_OPENED, this.onTrayOpenEvent, this);
-    },
+  showTray: function () {
+    this.$('.tray').velocity('transition.slideDownIn', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED);
+    if (!_.isNull(this.toggleTrayBtnSelector)) {
+      this.$(this.toggleTrayBtnSelector).addClass('enabled');
+    }
 
-    cleanup: function () {
-      $('body').off('click.' + this.trayId);
-    },
+    if (!_.isNull(this.onShowTray)) {
+      this.onShowTray();
+    }
 
-    // all trays publish a EVENTS.TRAY_OPENED event containing their unique ID. This listens for those events and
-    // closes the current tray if it's already open
-    onTrayOpenEvent: function (msg) {
-      if (!_.has(msg, 'trayId')) {
-        return;
-      }
-      if (msg.trayId !== this.trayId && this.trayVisible()) {
-        this.hideTray();
-      }
-    },
+    FauxtonAPI.Events.trigger(FauxtonAPI.constants.EVENTS.TRAY_OPENED, { trayId: this.trayId });
+  },
 
-    toggleTray: function (e) {
-      e.preventDefault();
+  trayVisible: function () {
+    return this.$('.tray').is(':visible');
+  }
+});
 
-      if (this.trayVisible()) {
-        this.hideTray();
-      } else {
-        this.showTray();
-      }
-    },
 
-    hideTray: function () {
-      var $tray = this.$('.tray');
-      $tray.velocity('reverse', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
-        $tray.hide();
-      });
+Components.ModalView = FauxtonAPI.View.extend({
+  disableLoader: true,
 
-      if (!_.isNull(this.toggleTrayBtnSelector)) {
-        this.$(this.toggleTrayBtnSelector).removeClass('enabled');
-      }
-    },
+  initialize: function (options) {
+    _.bindAll(this);
+  },
 
-    showTray: function () {
-      this.$('.tray').velocity('transition.slideDownIn', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED);
-      if (!_.isNull(this.toggleTrayBtnSelector)) {
-        this.$(this.toggleTrayBtnSelector).addClass('enabled');
-      }
+  afterRender: function () {
+    var that = this;
+    this.$('.modal').on('shown', function () {
+      that.$('input:text:visible:first').focus();
+    });
+  },
 
-      if (!_.isNull(this.onShowTray)) {
-        this.onShowTray();
-      }
+  showModal: function () {
+    if (this._showModal) { this._showModal();}
+    this.clear_error_msg();
+    this.$('.modal').modal();
 
-      FauxtonAPI.Events.trigger(FauxtonAPI.constants.EVENTS.TRAY_OPENED, { trayId: this.trayId });
-    },
+    // hack to get modal visible
+    $('.modal-backdrop').css('z-index', FauxtonAPI.constants.MISC.MODAL_BACKDROP_Z_INDEX);
+  },
 
-    trayVisible: function () {
-      return this.$('.tray').is(':visible');
+  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;
     }
-  });
-
-
-  Components.ModalView = FauxtonAPI.View.extend({
-    disableLoader: true,
-
-    initialize: function (options) {
-      _.bindAll(this);
-    },
-
-    afterRender: function () {
-      var that = this;
-      this.$('.modal').on('shown', function () {
-        that.$('input:text:visible:first').focus();
-      });
-    },
-
-    showModal: function () {
-      if (this._showModal) { this._showModal();}
-      this.clear_error_msg();
-      this.$('.modal').modal();
-
-      // hack to get modal visible
-      $('.modal-backdrop').css('z-index', FauxtonAPI.constants.MISC.MODAL_BACKDROP_Z_INDEX);
-    },
-
-    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');
-    },
+    this.$('#modal-error').text(text).removeClass('hide');
+  },
 
-    clear_error_msg: function () {
-      this.$('#modal-error').text(' ').addClass('hide');
-    },
+  clear_error_msg: function () {
+    this.$('#modal-error').text(' ').addClass('hide');
+  },
 
-    serialize: function () {
-      if (this.model) {
-        return this.model.toJSON();
-      }
-      return {};
+  serialize: function () {
+    if (this.model) {
+      return this.model.toJSON();
     }
-  });
+    return {};
+  }
+});
 
-  Components.Typeahead = FauxtonAPI.View.extend({
+Components.Typeahead = FauxtonAPI.View.extend({
 
-    initialize: function (options) {
-      this.source = options.source;
-      this.onUpdateEventName = options.onUpdateEventName;
-    },
+  initialize: function (options) {
+    this.source = options.source;
+    this.onUpdateEventName = options.onUpdateEventName;
+  },
 
-    afterRender: function () {
-      var onUpdateEventName = this.onUpdateEventName;
+  afterRender: function () {
+    var onUpdateEventName = this.onUpdateEventName;
 
-      this.$el.typeahead({
-        source: this.source,
-        updater: function (item) {
-          FauxtonAPI.Events.trigger(onUpdateEventName, item);
-          return item;
-        }
-      });
-    }
-  });
-
-  Components.DbSearchTypeahead = Components.Typeahead.extend({
-    initialize: function (options) {
-      this.dbLimit = options.dbLimit || 30;
-      if (options.filter) {
-        this.resultFilter = options.resultFilter;
+    this.$el.typeahead({
+      source: this.source,
+      updater: function (item) {
+        FauxtonAPI.Events.trigger(onUpdateEventName, item);
+        return item;
       }
-      _.bindAll(this);
-    },
-
-    getURL: function (query, dbLimit) {
-      query = encodeURIComponent(query);
-      return [
-        app.host,
-        "/_all_dbs?startkey=%22",
-        query,
-        "%22&endkey=%22",
-        query,
-        encodeURIComponent("\u9999"),
-        "%22&limit=",
-        dbLimit
-      ].join('');
-    },
-
-    source: function (query, process) {
-      var url = this.getURL(query, this.dbLimit);
-      var resultFilter = this.resultFilter;
-
-      if (this.ajaxReq) { this.ajaxReq.abort(); }
-
-      this.ajaxReq = $.ajax({
-        cache: false,
-        url: url,
-        dataType: 'json',
-        success: function (data) {
-          if (resultFilter) {
-            data = resultFilter(data);
-          }
-          process(data);
-        }
-      });
-    }
-  });
-
-  Components.DocSearchTypeahead = Components.Typeahead.extend({
-    initialize: function (options) {
-      this.docLimit = options.docLimit || 30;
-      this.database = options.database;
-      _.bindAll(this);
-    },
-    source: function (id, process) {
-      var query = '?' + $.param({
-        startkey: JSON.stringify(id),
-        endkey: JSON.stringify(id + "\u9999"),
-        limit: this.docLimit
-      });
-
-      var url = FauxtonAPI.urls('allDocs', 'server', this.database.safeID(), query);
-
-      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.DbSearchTypeahead = Components.Typeahead.extend({
+  initialize: function (options) {
+    this.dbLimit = options.dbLimit || 30;
+    if (options.filter) {
+      this.resultFilter = options.resultFilter;
     }
-  });
-
-  Components.LookaheadTray = FauxtonAPI.View.extend({
-    className: "lookahead-tray tray",
-    template: "addons/fauxton/templates/lookahead_tray",
-    placeholder: "Enter to search",
-
-    events: {
-      'click #js-close-tray': 'closeTray',
-      'keyup': 'onKeyup'
-    },
-
-    serialize: function () {
-      return {
-        placeholder: this.placeholder
-      };
-    },
-
-    initialize: function (opts) {
-      this.data = opts.data;
-      this.toggleEventName = opts.toggleEventName;
-      this.onUpdateEventName = opts.onUpdateEventName;
-
-      var trayIsVisible = _.bind(this.trayIsVisible, this);
-      var closeTray = _.bind(this.closeTray, this);
-      $("body").on("click.lookaheadTray", function (e) {
-        if (!trayIsVisible()) { return; }
-        if ($(e.target).closest(".lookahead-tray").length === 0 &&
-            $(e.target).closest('.lookahead-tray-link').length === 0) {
-          closeTray();
+    _.bindAll(this);
+  },
+
+  getURL: function (query, dbLimit) {
+    query = encodeURIComponent(query);
+    return [
+      app.host,
+      "/_all_dbs?startkey=%22",
+      query,
+      "%22&endkey=%22",
+      query,
+      encodeURIComponent("\u9999"),
+      "%22&limit=",
+      dbLimit
+    ].join('');
+  },
+
+  source: function (query, process) {
+    var url = this.getURL(query, this.dbLimit);
+    var resultFilter = this.resultFilter;
+
+    if (this.ajaxReq) { this.ajaxReq.abort(); }
+
+    this.ajaxReq = $.ajax({
+      cache: false,
+      url: url,
+      dataType: 'json',
+      success: function (data) {
+        if (resultFilter) {
+          data = resultFilter(data);
         }
-      });
-    },
-
-    afterRender: function () {
-      var that = this;
-      this.dbSearchTypeahead = new Components.Typeahead({
-        el: 'input.search-autocomplete',
-        source: that.data,
-        onUpdateEventName: that.onUpdateEventName
-      });
-      this.dbSearchTypeahead.render();
-    },
-
-    clearValue: function () {
-      this.$('.search-autocomplete').val('');
-    },
-
-    cleanup: function () {
-      $("body").off("click.lookaheadTray");
-    },
-
-    trayIsVisible: function () {
-      return this.$el.is(":visible");
-    },
-
-    toggleTray: function () {
-      if (this.trayIsVisible()) {
-        this.closeTray();
-      } else {
-        this.openTray();
+        process(data);
       }
-    },
-
-    openTray: function () {
-      var speed = FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED;
-      this.$el.velocity('transition.slideDownIn', speed, function () {
-        this.$el.find('input').focus();
-      }.bind(this));
-    },
-
-    closeTray: function () {
-      var $tray = this.$el;
-      $tray.velocity("reverse", FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
-        $tray.hide();
-      });
-      FauxtonAPI.Events.trigger('lookaheadTray:close');
-    },
-
-    onKeyup: function (e) {
-      if (e.which === 27) {
-        this.closeTray();
-      }
-    }
-  });
-
-
-  //need to make this into a backbone view...
-  var routeObjectSpinner;
-
-  FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
-    if (!routeObject.disableLoader) {
-      var opts = {
-        lines: 16, // The number of lines to draw
-        length: 8, // The length of each line
-        width: 4, // The line thickness
-        radius: 12, // The radius of the inner circle
-        color: '#333', // #rbg or #rrggbb
-        speed: 1, // Rounds per second
-        trail: 10, // Afterglow percentage
-        shadow: false // Whether to render a shadow
-      };
-
-      if (routeObjectSpinner) { return; }
-
-      if (!$('.spinner').length) {
-        $('<div class="spinner"></div>')
-          .appendTo('#app-container');
+    });
+  }
+});
+
+Components.DocSearchTypeahead = Components.Typeahead.extend({
+  initialize: function (options) {
+    this.docLimit = options.docLimit || 30;
+    this.database = options.database;
+    _.bindAll(this);
+  },
+  source: function (id, process) {
+    var query = '?' + $.param({
+      startkey: JSON.stringify(id),
+      endkey: JSON.stringify(id + "\u9999"),
+      limit: this.docLimit
+    });
+
+    var url = FauxtonAPI.urls('allDocs', 'server', this.database.safeID(), query);
+
+    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);
       }
+    });
+  }
+});
 
-      routeObjectSpinner = new Spinner(opts).spin();
-      $('.spinner').append(routeObjectSpinner.el);
+Components.LookaheadTray = FauxtonAPI.View.extend({
+  className: "lookahead-tray tray",
+  template: "addons/fauxton/templates/lookahead_tray",
+  placeholder: "Enter to search",
+
+  events: {
+    'click #js-close-tray': 'closeTray',
+    'keyup': 'onKeyup'
+  },
+
+  serialize: function () {
+    return {
+      placeholder: this.placeholder
+    };
+  },
+
+  initialize: function (opts) {
+    this.data = opts.data;
+    this.toggleEventName = opts.toggleEventName;
+    this.onUpdateEventName = opts.onUpdateEventName;
+
+    var trayIsVisible = _.bind(this.trayIsVisible, this);
+    var closeTray = _.bind(this.closeTray, this);
+    $("body").on("click.lookaheadTray", function (e) {
+      if (!trayIsVisible()) { return; }
+      if ($(e.target).closest(".lookahead-tray").length === 0 &&
+          $(e.target).closest('.lookahead-tray-link').length === 0) {
+        closeTray();
+      }
+    });
+  },
+
+  afterRender: function () {
+    var that = this;
+    this.dbSearchTypeahead = new Components.Typeahead({
+      el: 'input.search-autocomplete',
+      source: that.data,
+      onUpdateEventName: that.onUpdateEventName
+    });
+    this.dbSearchTypeahead.render();
+  },
+
+  clearValue: function () {
+    this.$('.search-autocomplete').val('');
+  },
+
+  cleanup: function () {
+    $("body").off("click.lookaheadTray");
+  },
+
+  trayIsVisible: function () {
+    return this.$el.is(":visible");
+  },
+
+  toggleTray: function () {
+    if (this.trayIsVisible()) {
+      this.closeTray();
+    } else {
+      this.openTray();
     }
-  });
-
-  var removeRouteObjectSpinner = function () {
-    if (routeObjectSpinner) {
-      routeObjectSpinner.stop();
-      routeObjectSpinner = null;
-      $('.spinner').remove();
+  },
+
+  openTray: function () {
+    var speed = FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED;
+    this.$el.velocity('transition.slideDownIn', speed, function () {
+      this.$el.find('input').focus();
+    }.bind(this));
+  },
+
+  closeTray: function () {
+    var $tray = this.$el;
+    $tray.velocity("reverse", FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
+      $tray.hide();
+    });
+    FauxtonAPI.Events.trigger('lookaheadTray:close');
+  },
+
+  onKeyup: function (e) {
+    if (e.which === 27) {
+      this.closeTray();
     }
-  };
+  }
+});
 
-  var removeViewSpinner = function (selector) {
-    var viewSpinner = viewSpinners[selector];
 
-    if (viewSpinner) {
-      viewSpinner.stop();
-      $(selector).find('.spinner').remove();
-      delete viewSpinners[selector];
-    }
-  };
-
-  var viewSpinners = {};
-  FauxtonAPI.RouteObject.on('beforeRender', function (routeObject, view, selector) {
-    removeRouteObjectSpinner();
-
-    if (!view.disableLoader) {
-      var opts = _.extend({
-        lines: 16, // The number of lines to draw
-        length: 8, // The length of each line
-        width: 4, // The line thickness
-        radius: 12, // The radius of the inner circle
-        color: '#333', // #rbg or #rrggbb
-        speed: 1, // Rounds per second
-        trail: 10, // Afterglow percentage
-        shadow: false // Whether to render a shadow
-      }, view.loaderStyles);
-
-      var viewSpinner = new Spinner(opts).spin();
-      $('<div class="spinner"></div>')
-        .appendTo(selector)
-        .append(viewSpinner.el);
+//need to make this into a backbone view...
+var routeObjectSpinner;
 
-      viewSpinners[selector] = viewSpinner;
+FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
+  if (!routeObject.disableLoader) {
+    var opts = {
+      lines: 16, // The number of lines to draw
+      length: 8, // The length of each line
+      width: 4, // The line thickness
+      radius: 12, // The radius of the inner circle
+      color: '#333', // #rbg or #rrggbb
+      speed: 1, // Rounds per second
+      trail: 10, // Afterglow percentage
+      shadow: false // Whether to render a shadow
+    };
+
+    if (routeObjectSpinner) { return; }
+
+    if (!$('.spinner').length) {
+      $('<div class="spinner"></div>')
+        .appendTo('#app-container');
     }
-  });
 
-  FauxtonAPI.RouteObject.on('afterRender', function (routeObject, view, selector) {
-    removeViewSpinner(selector);
-  });
+    routeObjectSpinner = new Spinner(opts).spin();
+    $('.spinner').append(routeObjectSpinner.el);
+  }
+});
 
-  FauxtonAPI.RouteObject.on('viewHasRendered', function (view, selector) {
-    removeViewSpinner(selector);
-    removeRouteObjectSpinner();
-  });
+var removeRouteObjectSpinner = function () {
+  if (routeObjectSpinner) {
+    routeObjectSpinner.stop();
+    routeObjectSpinner = null;
+    $('.spinner').remove();
+  }
+};
+
+var removeViewSpinner = function (selector) {
+  var viewSpinner = viewSpinners[selector];
+
+  if (viewSpinner) {
+    viewSpinner.stop();
+    $(selector).find('.spinner').remove();
+    delete viewSpinners[selector];
+  }
+};
+
+var viewSpinners = {};
+FauxtonAPI.RouteObject.on('beforeRender', function (routeObject, view, selector) {
+  removeRouteObjectSpinner();
+
+  if (!view.disableLoader) {
+    var opts = _.extend({
+      lines: 16, // The number of lines to draw
+      length: 8, // The length of each line
+      width: 4, // The line thickness
+      radius: 12, // The radius of the inner circle
+      color: '#333', // #rbg or #rrggbb
+      speed: 1, // Rounds per second
+      trail: 10, // Afterglow percentage
+      shadow: false // Whether to render a shadow
+    }, view.loaderStyles);
+
+    var viewSpinner = new Spinner(opts).spin();
+    $('<div class="spinner"></div>')
+      .appendTo(selector)
+      .append(viewSpinner.el);
+
+    viewSpinners[selector] = viewSpinner;
+  }
+});
 
+FauxtonAPI.RouteObject.on('afterRender', function (routeObject, view, selector) {
+  removeViewSpinner(selector);
+});
 
-  return Components;
+FauxtonAPI.RouteObject.on('viewHasRendered', function (view, selector) {
+  removeViewSpinner(selector);
+  removeRouteObjectSpinner();
 });
+
+
+export default Components;