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 2014/02/03 11:12:28 UTC

[04/22] couchdb commit: updated refs/heads/paginate-api-options to 5d2a6f9

Fauxton: Split up api.js

Split up api to core/*. To seperate out the Framework from the UI.
Move tests to fit with this refactor.
Change addons/Fauxton with work with api extraction.


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/96be583d
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/96be583d
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/96be583d

Branch: refs/heads/paginate-api-options
Commit: 96be583d8566536b0356296d4f9b01164e31f4fc
Parents: 5c9f9a9
Author: Garren Smith <ga...@gmail.com>
Authored: Wed Jan 8 15:26:12 2014 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Wed Jan 29 16:53:46 2014 +0200

----------------------------------------------------------------------
 src/Makefile.am                                 |  22 +-
 src/fauxton/Gruntfile.js                        |   4 +-
 .../app/addons/activetasks/tests/viewsSpec.js   |   3 -
 src/fauxton/app/addons/auth/base.js             |   1 +
 src/fauxton/app/addons/auth/resources.js        |   7 +-
 src/fauxton/app/addons/documents/views.js       |   8 +-
 src/fauxton/app/addons/fauxton/base.js          | 109 +++-
 src/fauxton/app/addons/fauxton/components.js    | 100 +++-
 src/fauxton/app/addons/fauxton/layout.js        |  98 ---
 src/fauxton/app/addons/fauxton/resizeColumns.js |  87 +++
 .../app/addons/fauxton/tests/baseSpec.js        |  79 +++
 .../app/addons/fauxton/tests/navbarSpec.js      | 107 ++++
 .../app/addons/fauxton/tests/paginateSpec.js    | 105 ++++
 src/fauxton/app/api.js                          | 593 -------------------
 src/fauxton/app/app.js                          |  85 ++-
 src/fauxton/app/config.js                       |   5 +-
 src/fauxton/app/core/api.js                     |  53 ++
 src/fauxton/app/core/auth.js                    |  67 +++
 src/fauxton/app/core/base.js                    | 137 +++++
 src/fauxton/app/core/couchdbSession.js          |  54 ++
 src/fauxton/app/core/layout.js                  |  91 +++
 src/fauxton/app/core/routeObject.js             | 296 +++++++++
 src/fauxton/app/core/router.js                  | 113 ++++
 src/fauxton/app/core/tests/layoutSpec.js        |  92 +++
 src/fauxton/app/core/tests/routeObjectSpec.js   |  96 +++
 src/fauxton/app/core/utils.js                   |  94 +++
 src/fauxton/app/main.js                         |  13 +-
 src/fauxton/app/resizeColumns.js                |  87 ---
 src/fauxton/app/router.js                       | 142 -----
 src/fauxton/app/utils.js                        |  66 ---
 src/fauxton/settings.json.default               |   1 +
 src/fauxton/tasks/couchserver.js                |   2 +-
 src/fauxton/test/core/layoutSpec.js             |  94 ---
 src/fauxton/test/core/navbarSpec.js             | 107 ----
 src/fauxton/test/core/paginateSpec.js           | 109 ----
 src/fauxton/test/core/routeObjectSpec.js        | 105 ----
 src/fauxton/test/mocha/testUtils.js             |   3 +-
 src/fauxton/test/test.config.underscore         |   1 +
 38 files changed, 1731 insertions(+), 1505 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index ba160b9..0f5c491 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -111,14 +111,22 @@ FAUXTON_FILES = \
     fauxton/app/addons/verifyinstall/routes.js \
     fauxton/app/addons/verifyinstall/templates/main.html \
     fauxton/app/addons/verifyinstall/assets/less/verifyinstall.less \
-    fauxton/app/api.js \
+    fauxton/app/core/api.js \
+    fauxton/app/core/auth.js \
+    fauxton/app/core/base.js \
+    fauxton/app/core/couchdbSession.js \
+    fauxton/app/core/layout.js \
+    fauxton/app/core/routeObject.js \
+    fauxton/app/core/router.js \
+    fauxton/app/core/utils.js \
+    fauxton/app/core/tests/layoutSpec.js \
+    fauxton/app/core/tests/routeObjectSpec.js \
     fauxton/app/app.js \
     fauxton/app/config.js \
     fauxton/app/helpers.js \
     fauxton/app/initialize.js.underscore \
     fauxton/app/load_addons.js.underscore \
     fauxton/app/main.js \
-    fauxton/app/utils.js \
     fauxton/app/addons/databases/base.js \
     fauxton/app/addons/databases/resources.js \
     fauxton/app/addons/databases/routes.js \
@@ -131,11 +139,13 @@ FAUXTON_FILES = \
     fauxton/app/addons/fauxton/base.js \
     fauxton/app/addons/fauxton/components.js \
     fauxton/app/addons/fauxton/layout.js \
+    fauxton/app/addons/fauxton/resizeColumns.js \
+    fauxton/app/addons/fauxton/tests/baseSpec.js \
+    fauxton/app/addons/fauxton/tests/navbarSpec.js \
+    fauxton/app/addons/fauxton/tests/paginateSpec.js \
     fauxton/app/addons/pouchdb/base.js \
     fauxton/app/addons/pouchdb/pouch.collate.js \
     fauxton/app/addons/pouchdb/pouchdb.mapreduce.js \
-    fauxton/app/resizeColumns.js \
-    fauxton/app/router.js \
     fauxton/app/addons/databases/templates/item.html \
     fauxton/app/addons/databases/templates/list.html \
     fauxton/app/addons/databases/templates/newdatabase.html \
@@ -309,10 +319,6 @@ FAUXTON_FILES = \
     fauxton/tasks/couchserver.js \
     fauxton/tasks/fauxton.js \
     fauxton/tasks/helper.js \
-    fauxton/test/core/layoutSpec.js \
-    fauxton/test/core/navbarSpec.js \
-    fauxton/test/core/paginateSpec.js \
-    fauxton/test/core/routeObjectSpec.js \
     fauxton/test/mocha/chai.js \
     fauxton/test/mocha/mocha.css \
     fauxton/test/mocha/mocha.js \

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/Gruntfile.js
----------------------------------------------------------------------
diff --git a/src/fauxton/Gruntfile.js b/src/fauxton/Gruntfile.js
index 32065b6..2c5a249 100644
--- a/src/fauxton/Gruntfile.js
+++ b/src/fauxton/Gruntfile.js
@@ -357,7 +357,7 @@ module.exports = function(grunt) {
 
     mochaSetup: {
       default: {
-        files: { src: helper.watchFiles(['[Ss]pec.js'], ['./test/core/**/*[Ss]pec.js', './app/**/*[Ss]pec.js'])},
+        files: { src: helper.watchFiles(['[Ss]pec.js'], ['./app/**/*[Ss]pec.js'])},
         template: 'test/test.config.underscore',
         config: './app/config.js'
       }
@@ -424,7 +424,7 @@ module.exports = function(grunt) {
   grunt.registerTask('lint', ['clean', 'jshint']);
   grunt.registerTask('test', ['lint', 'dependencies', 'test_inline']);
   // lighter weight test task for use inside dev/watch
-  grunt.registerTask('test_inline', ['mochaSetup','jst', 'concat:test_config_js', 'mocha_phantomjs']);
+  grunt.registerTask('test_inline', ['mochaSetup','jst', 'concat:test_config_js','mocha_phantomjs']);
   // Fetch dependencies (from git or local dir), lint them and make load_addons
   grunt.registerTask('dependencies', ['get_deps', 'gen_load_addons:default']);
   // build templates, js and css

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/activetasks/tests/viewsSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/activetasks/tests/viewsSpec.js b/src/fauxton/app/addons/activetasks/tests/viewsSpec.js
index 395b60a..13c9049 100644
--- a/src/fauxton/app/addons/activetasks/tests/viewsSpec.js
+++ b/src/fauxton/app/addons/activetasks/tests/viewsSpec.js
@@ -132,8 +132,5 @@ define([
       });
 
     });
-
-
-
   });
 });

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/auth/base.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/auth/base.js b/src/fauxton/app/addons/auth/base.js
index 9f9a332..7f69a7f 100644
--- a/src/fauxton/app/addons/auth/base.js
+++ b/src/fauxton/app/addons/auth/base.js
@@ -20,6 +20,7 @@ function(app, FauxtonAPI, Auth) {
 
   Auth.session = new Auth.Session();
   FauxtonAPI.setSession(Auth.session);
+  app.session = Auth.session;
 
   Auth.initialize = function() {
     Auth.navLink = new Auth.NavLink({model: Auth.session});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/auth/resources.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/auth/resources.js b/src/fauxton/app/addons/auth/resources.js
index 970d55b..321d302 100644
--- a/src/fauxton/app/addons/auth/resources.js
+++ b/src/fauxton/app/addons/auth/resources.js
@@ -12,10 +12,11 @@
 
 define([
        "app",
-       "api"
+       "api",
+       "core/CouchdbSession"
 ],
 
-function (app, FauxtonAPI) {
+function (app, FauxtonAPI, CouchdbSession) {
 
   var Auth = new FauxtonAPI.addon();
 
@@ -46,7 +47,7 @@ function (app, FauxtonAPI) {
     }
   });
 
-  Auth.Session = FauxtonAPI.Session.extend({
+  Auth.Session = CouchdbSession.Session.extend({
     url: app.host + '/_session',
 
     initialize: function (options) {

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/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
index 9516355..aeb5983 100644
--- a/src/fauxton/app/addons/documents/views.js
+++ b/src/fauxton/app/addons/documents/views.js
@@ -21,7 +21,7 @@ define([
        "addons/pouchdb/base",
 
        // Libs
-       "resizeColumns",
+       "addons/Fauxton/resizeColumns",
 
        // Plugins
        "plugins/beautify",
@@ -720,11 +720,11 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
     },
     
     cleanup: function () {
-      //if (!this.pagination) { return; }
-      this.pagination.remove();
-      //this.pagination = null;
       this.allDocsNumber.remove();
       _.each(this.rows, function (row) {row.remove();});
+
+      if (!this.pagination) { return; }
+      this.pagination.remove();
     },
 
     beforeRender: function() {

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/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
index 1811e84..35babb5 100644
--- a/src/fauxton/app/addons/fauxton/base.js
+++ b/src/fauxton/app/addons/fauxton/base.js
@@ -12,18 +12,86 @@
 
 define([
   "app",
-  // Libs
-  "backbone",
-  "resizeColumns",
+  "api",
+  "addons/fauxton/resizeColumns"
 ],
 
-function(app, Backbone, resizeColumns) {
+function(app, FauxtonAPI, resizeColumns) {
 
-  //resizeAnimation
-  app.resizeColumns = new resizeColumns({});
-  app.resizeColumns.onResizeHandler();
+  var Fauxton = FauxtonAPI.addon();
+  FauxtonAPI.addNotification = function (options) {
+    options = _.extend({
+      msg: "Notification Event Triggered!",
+      type: "info",
+      selector: "#global-notifications"
+    }, options);
 
-  var Fauxton = {};
+    var view = new Fauxton.Notification(options);
+    return view.renderNotification();
+  };
+
+  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 () {
+    app.footer = new Fauxton.Footer({el: "#footer-content"}),
+    app.navBar = new Fauxton.NavBar();
+    app.apiBar = new Fauxton.ApiBar();
+
+    FauxtonAPI.when.apply(null, app.footer.establish()).done(function() {
+      FauxtonAPI.masterLayout.setView("#primary-navbar", app.navBar, true);
+      FauxtonAPI.masterLayout.setView("#api-navbar", app.apiBar, true);
+      app.navBar.render();
+      app.apiBar.render();
+
+      app.footer.render();
+    });
+
+    FauxtonAPI.masterLayout.navBar = app.navBar;
+    FauxtonAPI.masterLayout.apiBar = app.apiBar;
+
+    FauxtonAPI.RouteObject.on('beforeFullRender', function (routeObject) {
+      $('#primary-navbar li').removeClass('active');
+
+      if (routeObject.selectedHeader) {
+        app.selectedHeader = routeObject.selectedHeader;
+        $('#primary-navbar li[data-nav-name="' + routeObject.selectedHeader + '"]').addClass('active');
+      }
+    });
+
+    FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
+      FauxtonAPI.masterLayout.removeView('#breadcrumbs');
+      var crumbs = routeObject.get('crumbs');
+
+      if (crumbs.length) {
+        FauxtonAPI.masterLayout.setView('#breadcrumbs', new Fauxton.Breadcrumbs({
+          crumbs: crumbs
+        }), true).render();
+      }
+    });
+
+    FauxtonAPI.RouteObject.on('renderComplete', function (routeObject) {
+      var masterLayout = FauxtonAPI.masterLayout;
+      if (routeObject.get('apiUrl')){
+        masterLayout.apiBar.update(routeObject.get('apiUrl'));
+      } else {
+        masterLayout.apiBar.hide();
+      }
+    });
+  };
 
   Fauxton.Breadcrumbs = Backbone.View.extend({
     template: "templates/fauxton/breadcrumbs",
@@ -41,7 +109,9 @@ function(app, Backbone, resizeColumns) {
   });
 
   Fauxton.VersionInfo = Backbone.Model.extend({
-    url: app.host
+    url: function () {
+      return app.host;
+    }
   });
 
   // TODO: this View should extend from FauxtonApi.View.
@@ -76,6 +146,16 @@ function(app, Backbone, resizeColumns) {
     bottomNavLinks: [],
     footerNavLinks: [],
 
+    initialize: function () {
+      _.bindAll(this);
+      //resizeAnimation
+      this.resizeColumns = new resizeColumns({});
+      this.resizeColumns.onResizeHandler();
+      
+      FauxtonAPI.extensions.on('add:navbar:addHeaderLink', this.addLink);
+      FauxtonAPI.extensions.on('removeItem:navbar:addHeaderLink', this.removeLink);
+    },
+
     serialize: function() {
       return {
         navLinks: this.navLinks,
@@ -124,7 +204,6 @@ function(app, Backbone, resizeColumns) {
     },
 
     afterRender: function(){
-
       $('#primary-navbar li[data-nav-name="' + app.selectedHeader + '"]').addClass('active');
 
       var menuOpen = true;
@@ -141,21 +220,22 @@ function(app, Backbone, resizeColumns) {
       function toggleMenu(){
         $selectorList.toggleClass('closeMenu');
         menuOpen = $selectorList.hasClass('closeMenu');
-        app.resizeColumns.onResizeHandler();
+        this.resizeColumns.onResizeHandler();
       }
-
+      
+      var that = this;
       $('#primary-navbar').on("click", ".nav a", function(){
         if (!($selectorList.hasClass('closeMenu'))){
           setTimeout(
             function(){
             $selectorList.addClass('closeMenu');
-            app.resizeColumns.onResizeHandler();
+            that.resizeColumns.onResizeHandler();
           },3000);
 
         }
       });
 
-      app.resizeColumns.initialize();
+      this.resizeColumns.initialize();
     },
 
     beforeRender: function () {
@@ -265,6 +345,5 @@ function(app, Backbone, resizeColumns) {
     }
   });
 
-
   return Fauxton;
 });

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/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
index edde428..c26fc96 100644
--- a/src/fauxton/app/addons/fauxton/components.js
+++ b/src/fauxton/app/addons/fauxton/components.js
@@ -25,10 +25,11 @@ define([
   // Libs
   "api",
   "ace_configuration",
+  "spin"
 ],
 
-function(app, FauxtonAPI, ace) {
-  var Components = app.module();
+function(app, FauxtonAPI, ace, spin) {
+  var Components = FauxtonAPI.addon();
 
   Components.Pagination = FauxtonAPI.View.extend({
     template: "templates/fauxton/pagination",
@@ -124,7 +125,7 @@ function(app, FauxtonAPI, ace) {
 
     pageEnd: function () {
       if (this.collection.length < this.pageLimit()) {
-        return this.collection.length;
+        return (this.previousParams.length * this.pageLimit()) + this.collection.length;
       }
 
       return (this.previousParams.length * this.pageLimit()) + this.pageLimit();
@@ -235,6 +236,8 @@ function(app, FauxtonAPI, ace) {
       this.theme = options.theme || 'crimson_editor';
       this.couchJSHINT = options.couchJSHINT;
       this.edited = false;
+
+      _.bindAll(this);
     },
 
     afterRender: function () {
@@ -295,13 +298,14 @@ function(app, FauxtonAPI, ace) {
     },
 
     removeIncorrectAnnotations: function () {
-      var editor = this.editor;
+      var editor = this.editor,
+          isIgnorableError = this.isIgnorableError;
 
-      this.editor.getSession().on("changeAnnotation", function(){
+      this.editor.getSession().on("changeAnnotation", function () {
         var annotations = editor.getSession().getAnnotations();
 
         var newAnnotations = _.reduce(annotations, function (annotations, error) {
-          if (!FauxtonAPI.isIgnorableError(error.raw)) {
+          if (!isIgnorableError(error.raw)) {
             annotations.push(error);
           }
           return annotations;
@@ -338,12 +342,94 @@ function(app, FauxtonAPI, ace) {
      var errors = this.getAnnotations();
      // By default CouchDB view functions don't pass lint
      return _.every(errors, function(error) {
-      return FauxtonAPI.isIgnorableError(error.raw);
+      return this.isIgnorableError(error.raw);
       },this);
+    },
+
+    // List of JSHINT errors to ignore
+    // Gets around problem of anonymous functions not being a valid statement
+    excludedViewErrors: [
+      "Missing name in function declaration.",
+      "['{a}'] is better written in dot notation."
+    ],
+
+    isIgnorableError: function(msg) {
+      return _.contains(this.excludedViewErrors, msg);
     }
 
   });
 
+  //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 (!$('.spinner').length) {
+       $('<div class="spinner"></div>')
+        .appendTo('#app-container');
+     }
+
+     routeObjectSpinner = new Spinner(opts).spin();
+     $('.spinner').append(routeObjectSpinner.el);
+   }
+  });
+
+  var removeRouteObjectSpinner = function () {
+    if (routeObjectSpinner) {
+      routeObjectSpinner.stop();
+      $('.spinner').remove();
+    }
+  };
+
+  var removeViewSpinner = function () {
+    if (viewSpinner){
+      viewSpinner.stop();
+      $('.spinner').remove();
+    }
+  };
+
+  var viewSpinner;
+  FauxtonAPI.RouteObject.on('beforeRender', function (routeObject, view, selector) {
+    removeRouteObjectSpinner();
+
+    if (!view.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
+      };
+
+      viewSpinner = new Spinner(opts).spin();
+      $('<div class="spinner"></div>')
+        .appendTo(selector)
+        .append(viewSpinner.el);
+    }
+  });
+
+  FauxtonAPI.RouteObject.on('afterRender', function (routeObject, view, selector) {
+    removeViewSpinner();
+  });
+
+  FauxtonAPI.RouteObject.on('viewHasRendered', function () {
+    removeViewSpinner();
+    removeRouteObjectSpinner();
+  });
+
   return Components;
 });
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/fauxton/layout.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/layout.js b/src/fauxton/app/addons/fauxton/layout.js
deleted file mode 100644
index 1422241..0000000
--- a/src/fauxton/app/addons/fauxton/layout.js
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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(["backbone"],
-
-function(Backbone) {
-
-  // A wrapper of the main Backbone.layoutmanager
-  // Allows the main layout of the page to be changed by any plugin.
-  // Exposes the different views:
-  //    navBar -> the top navigation bar
-  //    dashboardContent -> Main display view
-  //    breadcrumbs -> Breadcrumbs navigation section
-  var Layout = function (navBar, apiBar) {
-    this.navBar = navBar;
-    this.apiBar = apiBar;
-
-    this.layout = new Backbone.Layout({
-      template: "templates/layouts/with_sidebar",
-
-      views: {
-        "#primary-navbar": this.navBar,
-        "#api-navbar": this.apiBar
-      },
-      afterRender: function(){
-
-      }
-    });
-
-    this.layoutViews = {};
-    //this.hooks = {};
-
-    this.el = this.layout.el;
-  };
-
-  // creatings the dashboard object same way backbone does
-  _.extend(Layout.prototype, {
-    render: function () {
-      return this.layout.render();
-    },
-
-    setTemplate: function(template) {
-      if (template.prefix){
-        this.layout.template = template.prefix + template.name;
-      } else{
-        this.layout.template = "templates/layouts/" + template;
-      }
-      // If we're changing layouts all bets are off, so kill off all the
-      // existing views in the layout.
-      _.each(this.layoutViews, function(view){view.remove();});
-      this.layoutViews = {};
-      this.render();
-    },
-
-    setTabs: function(view){
-      // TODO: Not sure I like this - seems fragile/repetitive
-      this.tabs = this.layout.setView("#tabs", view);
-      this.tabs.render();
-    },
-
-    setBreadcrumbs: function(view) {
-      this.breadcrumbs = this.layout.setView("#breadcrumbs", view);
-      this.breadcrumbs.render();
-    },
-
-    clearBreadcrumbs: function () {
-      if (!this.breadcrumbs) {return ;}
-
-      this.breadcrumbs.remove();
-    },
-
-    setView: function(selector, view) {
-      this.layoutViews[selector] = this.layout.setView(selector, view, false);
-    },
-
-    renderView: function(selector) {
-      var view = this.layoutViews[selector];
-      if (!view) {
-        return false;
-      } else {
-        return view.render();
-      }
-    }
-
-  });
-
-  return Layout;
-
-});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/fauxton/resizeColumns.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/resizeColumns.js b/src/fauxton/app/addons/fauxton/resizeColumns.js
new file mode 100644
index 0000000..abfcd2f
--- /dev/null
+++ b/src/fauxton/app/addons/fauxton/resizeColumns.js
@@ -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.
+
+
+// This file creates a set of helper functions that will be loaded for all html
+// templates. These functions should be self contained and not rely on any 
+// external dependencies as they are loaded prior to the application. We may
+// want to change this later, but for now this should be thought of as a
+// "purely functional" helper system.
+
+define([
+  "api"
+],
+
+function(FauxtonAPI) {
+
+  var Resize = function(options){
+    this.options = options;
+    this.options.selectorElements = options.selectorElements || ".window-resizeable";
+  };
+
+  Resize.prototype = {
+    getPrimaryNavWidth: function(){
+      var primaryNavWidth  = $('body').hasClass('closeMenu')? 64:224;
+      return primaryNavWidth;
+    },
+    getPanelWidth: function(){
+      var sidebarWidth = $('#sidebar-content').length > 0 ? $('#sidebar-content').width(): 0;
+      return (this.getPrimaryNavWidth() + sidebarWidth); 
+    },
+    initialize: function(){
+     // $(window).off('resize');
+      var that = this;
+      //add throttler :) 
+      this.lazyLayout = _.debounce(that.onResizeHandler, 300).bind(this);
+      FauxtonAPI.utils.addWindowResize(this.lazyLayout,"animation");
+      FauxtonAPI.utils.initWindowResize();
+      this.onResizeHandler();
+    },
+    updateOptions:function(options){
+      this.options = {};
+      this.options = options;
+      this.options.selectorElements = options.selectorElements || ".window-resizeable";
+    },
+    turnOff:function(){
+      FauxtonAPI.utils.removeWindowResize("animation");
+    },
+    cleanupCallback: function(){
+      this.callback = null;
+    },
+    onResizeHandler: function (){
+      //if there is an override, do that instead
+      if (this.options.onResizeHandler){
+        this.options.onResizeHandler();
+      } else {
+        var combinedWidth = window.innerWidth - this.getPanelWidth(),
+        smallWidthConstraint = ($('#sidebar-content').length > 0)? 470:800,
+        panelWidth; 
+
+        if( combinedWidth > smallWidthConstraint  && combinedWidth < 1400){
+          panelWidth = window.innerWidth - this.getPanelWidth();
+        } else if (combinedWidth < smallWidthConstraint){
+          panelWidth = smallWidthConstraint;
+        } else if(combinedWidth > 1400){
+          panelWidth = 1400;
+        }
+
+        $(this.options.selectorElements).innerWidth(panelWidth);
+      }
+      //if there is a callback, run that
+      if(this.options.callback) {
+        this.options.callback();
+      }
+    } 
+  };
+
+  return Resize;
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/fauxton/tests/baseSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/tests/baseSpec.js b/src/fauxton/app/addons/fauxton/tests/baseSpec.js
new file mode 100644
index 0000000..7df4dfc
--- /dev/null
+++ b/src/fauxton/app/addons/fauxton/tests/baseSpec.js
@@ -0,0 +1,79 @@
+// 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([
+  'testUtils',
+  'api',
+  'addons/fauxton/base',
+  "backbone"
+], function (testUtils, FauxtonAPI, Base) {
+  var assert = testUtils.assert;
+
+
+  describe('Fauxton RouteObject:beforeEstablish', function () {
+    var TestRouteObject, testRouteObject, mockLayout, _layout;
+
+    before(function () {
+      Base.initialize();
+      _layout = FauxtonAPI.masterLayout;
+    });
+
+    beforeEach(function () {
+      TestRouteObject = FauxtonAPI.RouteObject.extend({
+        crumbs: ['mycrumbs']
+      });
+
+      testRouteObject = new TestRouteObject();
+      var apiBar = {};
+      apiBar.hide = sinon.spy();
+      var setViewSpy = sinon.stub();
+      setViewSpy.returns({
+        render: function () {}
+      });
+
+      // Need to find a better way of doing this
+      mockLayout = {
+        setTemplate: sinon.spy(),
+        clearBreadcrumbs: sinon.spy(),
+        setView: setViewSpy,
+        renderView: sinon.spy(),
+        removeView: sinon.spy(),
+        hooks: [],
+        setBreadcrumbs: sinon.spy(),
+        apiBar: apiBar,
+        layout: { 
+          setView: function () {}
+        }
+      };
+
+
+    });
+
+    after(function () {
+      FauxtonAPI.masterLayout = _layout;
+    });
+
+    it('Should clear breadcrumbs', function () {
+      FauxtonAPI.masterLayout = mockLayout;
+      testRouteObject.renderWith('the-route', mockLayout, 'args');
+      assert.ok(mockLayout.removeView.calledWith('#breadcrumbs'), 'Clear Breadcrumbs called');
+    });
+
+    it('Should set breadcrumbs when breadcrumbs exist', function () {
+      FauxtonAPI.masterLayout = mockLayout;
+      testRouteObject.renderWith('the-route', mockLayout, 'args');
+      assert.ok(mockLayout.setView.calledOnce, 'Set Breadcrumbs was called');
+    });
+
+  });
+
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/fauxton/tests/navbarSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/tests/navbarSpec.js b/src/fauxton/app/addons/fauxton/tests/navbarSpec.js
new file mode 100644
index 0000000..3eca6f6
--- /dev/null
+++ b/src/fauxton/app/addons/fauxton/tests/navbarSpec.js
@@ -0,0 +1,107 @@
+// 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/fauxton/base',
+      'testUtils'
+], function (Fauxton, testUtils) {
+  var assert = testUtils.assert,
+      NavBar = Fauxton.NavBar;
+
+  describe('NavBar', function () {
+
+    describe('adding links', function () {
+      var navBar;
+
+      beforeEach(function () {
+        navBar = new NavBar();
+        navBar.navLinks = [];
+        navBar.bottomNavLinks = [];
+        navBar.footerNavLinks = [];
+      });
+
+      it('Should add link to navlinks', function () {
+        navBar.addLink({href: '#/test', title: 'Test Title'});
+
+        assert.equal(navBar.navLinks.length, 1);
+        assert.equal(navBar.footerNavLinks.length, 0);
+        assert.equal(navBar.bottomNavLinks.length, 0);
+      });
+
+      it('Should add link to bottom links', function () {
+        navBar.addLink({href: '#/test', bottomNav: true, title: 'Test Title'});
+
+        assert.equal(navBar.bottomNavLinks.length, 1);
+        assert.equal(navBar.navLinks.length, 0);
+        assert.equal(navBar.footerNavLinks.length, 0);
+      });
+
+      it('Should add link to footer links', function () {
+        navBar.addLink({href: '#/test', footerNav: true, title: 'Test Title'});
+
+        assert.equal(navBar.footerNavLinks.length, 1);
+        assert.equal(navBar.bottomNavLinks.length, 0);
+        assert.equal(navBar.navLinks.length, 0);
+      });
+    });
+
+    describe('removing links', function () {
+      var navBar;
+
+      beforeEach(function () {
+        navBar = new NavBar();
+        navBar.navLinks = [];
+        navBar.bottomNavLinks = [];
+        navBar.footerNavLinks = [];
+        navBar.addLink({
+          href: '#/test', 
+          footerNav: true, 
+          title: 'Test Title Footer'
+        });
+
+        navBar.addLink({
+          href: '#/test', 
+          bottomNav: true, 
+          title: 'Test Title Bottom'
+        });
+
+        navBar.addLink({
+          href: '#/test', 
+          title: 'Test Title'
+        });
+      });
+
+      it("should remove links from list", function () {
+        navBar.removeLink({
+          title: 'Test Title Footer',
+          footerNav: true
+        });
+
+        assert.equal(navBar.footerNavLinks.length, 0);
+        assert.equal(navBar.bottomNavLinks.length, 1);
+        assert.equal(navBar.navLinks.length, 1);
+      });
+
+      it("Should call render after removing links", function () {
+        var renderSpy = sinon.stub(navBar,'render');
+
+        navBar.removeLink({
+          title: 'Test Title Footer',
+          footerNav: true
+        });
+
+        assert.ok(renderSpy.calledOnce);
+      });
+
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/addons/fauxton/tests/paginateSpec.js b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
new file mode 100644
index 0000000..535e26f
--- /dev/null
+++ b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
@@ -0,0 +1,105 @@
+// 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',
+       'addons/fauxton/components',
+       'addons/documents/resources',
+       'testUtils',
+       'api'
+], function (app, Views, Models, testUtils, FauxtonAPI) {
+  var assert = testUtils.assert,
+  ViewSandbox = testUtils.ViewSandbox;
+
+
+  describe('IndexPaginate', function () {
+    var viewSandbox, paginate, collection, navigateMock;
+    beforeEach(function () {
+      collection = new Models.IndexCollection([{
+        id:'myId1',
+        doc: 'num1'
+      },
+      {
+        id:'myId2',
+        doc: 'num2'
+      }], {
+        database: {id: 'databaseId'},
+        design: '_design/myDoc'
+      });
+
+      paginate = new Views.IndexPagination({
+        collection: collection,
+        previousUrlfn: function () {},
+        nextUrlfn: function () {},
+        canShowPreviousfn: function () { return true; },
+        canShowNextfn: function () { return true;}
+      });
+      viewSandbox = new ViewSandbox();
+      viewSandbox.renderView(paginate); 
+    });
+
+    afterEach(function () {
+      viewSandbox.remove();
+    });
+
+    describe('#next', function () {
+      beforeEach(function () {
+        //do this so it doesn't throw an error on other unwired up components
+        FauxtonAPI.triggerRouteEvent = function () {};
+        //FauxtonAPI.triggerRouteEvent.restore && FauxtonAPI.triggerRouteEvent.restore();
+        //FauxtonAPI.navigate.restore && FauxtonAPI.navigate.restore(); 
+      });
+
+      it('Should navigate', function () {
+        var navigateMock = sinon.spy(FauxtonAPI, 'navigate');
+
+        paginate.$('a#next').click();
+
+        assert.ok(navigateMock.calledOnce);
+        FauxtonAPI.navigate.restore();
+      });
+
+      it('Should trigger routeEvent', function () {
+        var navigateMock = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
+
+        paginate.$('a#next').click();
+
+        assert.ok(navigateMock.calledOnce);
+        FauxtonAPI.triggerRouteEvent.restore();
+      });
+
+    });
+
+
+    describe('#previous', function () {
+
+      it('Should navigate', function () {
+        var navigateMock = sinon.spy(FauxtonAPI, 'navigate');
+
+        paginate.$('a#previous').click();
+
+        assert.ok(navigateMock.calledOnce);
+        FauxtonAPI.navigate.restore();
+      });
+
+      it('Should trigger routeEvent', function () {
+        var navigateMock = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
+
+        paginate.$('a#previous').click();
+
+        assert.ok(navigateMock.calledOnce);
+        FauxtonAPI.triggerRouteEvent.restore();
+      });
+
+    });
+
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/api.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/api.js b/src/fauxton/app/api.js
deleted file mode 100644
index 9ac895e..0000000
--- a/src/fauxton/app/api.js
+++ /dev/null
@@ -1,593 +0,0 @@
-// 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",
-
-       // Modules
-       "addons/fauxton/base",
-       "spin"
-],
-
-function(app, Fauxton) {
-  var FauxtonAPI = app.module();
-
-  FauxtonAPI.moduleExtensions = {
-    Routes: {
-    }
-  };
-
-  FauxtonAPI.addonExtensions = {
-    initialize: function() {}
-  };
-
-  // List of JSHINT errors to ignore
-  // Gets around problem of anonymous functions not being a valid statement
-  FauxtonAPI.excludedViewErrors = [
-    "Missing name in function declaration.",
-    "['{a}'] is better written in dot notation."
-  ];
-
-  FauxtonAPI.isIgnorableError = function(msg) {
-    return _.contains(FauxtonAPI.excludedViewErrors, msg);
-  };
-
-  FauxtonAPI.View = Backbone.View.extend({
-    // This should return an array of promises, an empty array, or null
-    establish: function() {
-      return null;
-    },
-
-    loaderClassname: 'loader',
-
-    disableLoader: false,
-
-    forceRender: function () {
-      this.hasRendered = false;
-    }
-  });
-
-  FauxtonAPI.navigate = function(url, _opts) {
-    var options = _.extend({trigger: true}, _opts );
-    app.router.navigate(url,options);
-  };
-
-  FauxtonAPI.beforeUnload = function () {
-    app.router.beforeUnload.apply(app.router, arguments);
-  };
-
-  FauxtonAPI.removeBeforeUnload = function () {
-    app.router.removeBeforeUnload.apply(app.router, arguments);
-  };
-
-  FauxtonAPI.addHeaderLink = function(link) {
-    app.masterLayout.navBar.addLink(link);
-  };
-
-  FauxtonAPI.removeHeaderLink = function(link) {
-    app.masterLayout.navBar.removeLink(link);
-  };
-
-  FauxtonAPI.Deferred = function() {
-    return $.Deferred();
-  };
-
-  FauxtonAPI.when = function (deferreds) {
-    if (deferreds instanceof Array) {
-      return $.when.apply(null, deferreds);
-    }
-
-    return $.when(deferreds);
-  };
-
-  FauxtonAPI.addRoute = function(route) {
-    app.router.route(route.route, route.name, route.callback);
-  };
-
-  FauxtonAPI.triggerRouteEvent = function (routeEvent, args) {
-    app.router.triggerRouteEvent("route:"+routeEvent, args);
-  };
-
-  FauxtonAPI.module = function(extra) {
-    return app.module(_.extend(FauxtonAPI.moduleExtensions, extra));
-  };
-
-  FauxtonAPI.addon = function(extra) {
-    return FauxtonAPI.module(FauxtonAPI.addonExtensions, extra);
-  };
-
-  FauxtonAPI.addNotification = function(options) {
-    options = _.extend({
-      msg: "Notification Event Triggered!",
-      type: "info",
-      selector: "#global-notifications"
-    }, options);
-    var view = new Fauxton.Notification(options);
-
-    return view.renderNotification();
-  };
-
-  FauxtonAPI.UUID = Backbone.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();
-    }
-  });
-
-  FauxtonAPI.Session = Backbone.Model.extend({
-    url: app.host + '/_session',
-
-    user: function () {
-      var userCtx = this.get('userCtx');
-
-      if (!userCtx || !userCtx.name) { return null; }
-
-      return {
-        name: userCtx.name,
-        roles: userCtx.roles
-      };
-    },
-
-    fetchOnce: function (opt) {
-      var options = _.extend({}, opt);
-
-      if (!this._deferred || this._deferred.state() === "rejected" || options.forceFetch ) {
-        this._deferred = this.fetch();
-      }
-
-      return this._deferred;
-    },
-
-    fetchUser: function (opt) {
-      var that = this,
-      currentUser = this.user();
-
-      return this.fetchOnce(opt).then(function () {
-        var user = that.user();
-
-        // Notify anyone listening on these events that either a user has changed
-        // or current user is the same
-        if (currentUser !== user) {
-          that.trigger('session:userChanged');
-        } else {
-          that.trigger('session:userFetched');
-        }
-
-        // this will return the user as a value to all function that calls done on this
-        // eg. session.fetchUser().done(user) { .. do something with user ..}
-        return user; 
-      });
-    }
-  });
-
-  FauxtonAPI.setSession = function (newSession) {
-    app.session = FauxtonAPI.session = newSession;
-    return FauxtonAPI.session.fetchUser();
-  };
-
-  FauxtonAPI.setSession(new FauxtonAPI.Session());
-
-  // This is not exposed externally as it should not need to be accessed or overridden
-  var Auth = function (options) {
-    this._options = options;
-    this.initialize.apply(this, arguments);
-  };
-
-  // Piggy-back on Backbone's self-propagating extend function,
-  Auth.extend = Backbone.Model.extend;
-
-  _.extend(Auth.prototype, Backbone.Events, {
-    authDeniedCb: function() {},
-
-    initialize: function() {
-      var that = this;
-    },
-
-    authHandlerCb : function (roles) {
-      var deferred = $.Deferred();
-      deferred.resolve();
-      return deferred;
-    },
-
-    registerAuth: function (authHandlerCb) {
-      this.authHandlerCb = authHandlerCb;
-    },
-
-    registerAuthDenied: function (authDeniedCb) {
-      this.authDeniedCb = authDeniedCb;
-    },
-
-    checkAccess: function (roles) {
-      var requiredRoles = roles || [],
-      that = this;
-
-      return FauxtonAPI.session.fetchUser().then(function (user) {
-        return FauxtonAPI.when(that.authHandlerCb(FauxtonAPI.session, requiredRoles));
-      });
-    }
-  });
-
-  FauxtonAPI.auth = new Auth();
-
-  FauxtonAPI.RouteObject = function(options) {
-    this._options = options;
-
-    this._configure(options || {});
-    this.initialize.apply(this, arguments);
-    this.addEvents();
-  };
-
-  var broadcaster = {};
-  _.extend(broadcaster, Backbone.Events);
-
-  FauxtonAPI.RouteObject.on = function (eventName, fn) {
-    broadcaster.on(eventName, fn); 
-  };
-  
-  /* How Route Object events work
-   To listen to a specific route objects events:
-
-   myRouteObject = FauxtonAPI.RouteObject.extend({
-    events: {
-      "beforeRender": "beforeRenderEvent"
-    },
-
-    beforeRenderEvent: function (view, selector) {
-      console.log('Hey, beforeRenderEvent triggered',arguments);
-    },
-   });
-
-    It is also possible to listen to events triggered from all Routeobjects. 
-    This is great for more general things like adding loaders, hooks.
-
-    FauxtonAPI.RouteObject.on('beforeRender', function (routeObject, view, selector) {
-      console.log('hey, this will trigger when any routeobject renders a view');
-    });
-
-   Current Events to subscribe to:
-    * beforeFullRender -- before a full render is being done
-    * beforeEstablish -- before the routeobject calls establish
-    * AfterEstablish -- after the routeobject has run establish
-    * beforeRender -- before a view is rendered
-    * afterRender -- a view is finished being rendered
-    * renderComplete -- all rendering is complete
-    
-  */
-
-  // Piggy-back on Backbone's self-propagating extend function
-  FauxtonAPI.RouteObject.extend = Backbone.Model.extend;
-
-  var routeObjectOptions = ["views", "routes", "events", "roles", "crumbs", "layout", "apiUrl", "establish"];
-
-  _.extend(FauxtonAPI.RouteObject.prototype, Backbone.Events, {
-    // Should these be default vals or empty funcs?
-    views: {},
-    routes: {},
-    events: {},
-    crumbs: [],
-    layout: "with_sidebar",
-    apiUrl: null,
-    disableLoader: false,
-    loaderClassname: 'loader',
-    renderedState: false,
-    establish: function() {},
-    route: function() {},
-    roles: [],
-    _promises: [],
-    initialize: function() {}
-  }, {
-
-    renderWith: function(route, masterLayout, args) {
-      var routeObject = this,
-          triggerBroadcast = _.bind(this.triggerBroadcast, this);
-
-      // Only want to redo the template if its a full render
-      if (!this.renderedState) {
-        masterLayout.setTemplate(this.layout);
-        triggerBroadcast('beforeFullRender');
-        $('#primary-navbar li').removeClass('active');
-
-        if (this.selectedHeader) {
-          app.selectedHeader = this.selectedHeader;
-          $('#primary-navbar li[data-nav-name="' + this.selectedHeader + '"]').addClass('active');
-        }
-      }
-
-      masterLayout.clearBreadcrumbs();
-      var crumbs = this.get('crumbs');
-
-      if (crumbs.length) {
-        masterLayout.setBreadcrumbs(new Fauxton.Breadcrumbs({
-          crumbs: crumbs
-        }));
-      }
-
-      triggerBroadcast('beforeEstablish');
-      var establishPromise = this.establish();
-      this.addPromise(establishPromise);
-      FauxtonAPI.when(establishPromise).then(function(resp) {
-        triggerBroadcast('afterEstablish');
-        _.each(routeObject.getViews(), function(view, selector) {
-          if(view.hasRendered) { 
-            triggerBroadcast('viewHasRendered', view, selector);
-            return;
-          }
-
-          triggerBroadcast('beforeRender', view, selector);
-          var viewPromise = view.establish();
-          routeObject.addPromise(viewPromise);
-          FauxtonAPI.when(viewPromise).then(function(resp) {
-            masterLayout.setView(selector, view);
-
-            masterLayout.renderView(selector);
-            triggerBroadcast('afterRender', view, selector);
-            }, function(resp) {
-              view.establishError = {
-                error: true,
-                reason: resp
-              };
-
-              if (resp && resp.responseText) { 
-                var errorText = JSON.parse(resp.responseText).reason;
-                FauxtonAPI.addNotification({
-                  msg: 'An Error occurred: ' + errorText,
-                  type: 'error',
-                  clear: true
-                });
-              }
-
-              masterLayout.renderView(selector);
-          });
-
-        });
-      }.bind(this), function (resp) {
-          if (!resp || !resp.responseText) { return; }
-          FauxtonAPI.addNotification({
-                msg: 'An Error occurred' + JSON.parse(resp.responseText).reason,
-                type: 'error',
-                clear: true
-          });
-      });
-
-      if (this.get('apiUrl')){
-        masterLayout.apiBar.update(this.get('apiUrl'));
-      } else {
-        masterLayout.apiBar.hide();
-      }
-
-      // Track that we've done a full initial render
-      this.renderedState = true;
-      triggerBroadcast('renderComplete');
-    },
-
-    triggerBroadcast: function (eventName) {
-      var args = Array.prototype.slice.call(arguments);
-      this.trigger.apply(this, args);
-
-      args.splice(0,1, eventName, this);
-      broadcaster.trigger.apply(broadcaster, args);
-    },
-
-    get: function(key) {
-      return _.isFunction(this[key]) ? this[key]() : this[key];
-    },
-
-    addEvents: function(events) {
-      events = events || this.get('events');
-      _.each(events, function(method, event) {
-        if (!_.isFunction(method) && !_.isFunction(this[method])) {
-          throw new Error("Invalid method: "+method);
-        }
-        method = _.isFunction(method) ? method : this[method];
-
-        this.on(event, method);
-      }, this);
-    },
-
-    _configure: function(options) {
-      _.each(_.intersection(_.keys(options), routeObjectOptions), function(key) {
-        this[key] = options[key];
-      }, this);
-    },
-
-    getView: function(selector) {
-      return this.views[selector];
-    },
-
-    setView: function(selector, view) {
-      this.views[selector] = view;
-      return view;
-    },
-
-    getViews: function() {
-      return this.views;
-    },
-
-    removeViews: function () {
-      _.each(this.views, function (view, selector) {
-        view.remove();
-        delete this.views[selector];
-      }, this);
-    },
-
-    addPromise: function (promise) {
-      if (_.isEmpty(promise)) { return; }
-
-      if (_.isArray(promise)) {
-        return _.each(promise, function (p) {
-          this._promises.push(p);
-        }, this);
-      }
-
-     this._promises.push(promise);
-    },
-
-    cleanup: function () {
-      this.removeViews();
-      this.rejectPromises();
-    },
-
-    rejectPromises: function () {
-      _.each(this._promises, function (promise) {
-        if (promise.state() === "resolved") { return; }
-        if (promise.abort) {
-          return promise.abort("Route change");
-        } 
-
-        promise.reject();
-      }, this);
-
-      this._promises = [];
-    },
-
-    getRouteUrls: function () {
-      return _.keys(this.get('routes'));
-    },
-
-    hasRoute: function (route) {
-      if (this.get('routes')[route]) {
-        return true;
-      }
-      return false;
-    },
-
-    routeCallback: function (route, args) {
-      var routes = this.get('routes'),
-      routeObj = routes[route],
-      routeCallback;
-
-      if (typeof routeObj === 'object') {
-        routeCallback = this[routeObj.route];
-      } else {
-        routeCallback = this[routeObj];
-      }
-
-      routeCallback.apply(this, args);
-    },
-
-    getRouteRoles: function (routeUrl) {
-      var route = this.get('routes')[routeUrl];
-
-      if ((typeof route === 'object') && route.roles) {
-        return route.roles; 
-      }
-
-      return this.roles;
-    }
-
-  });
-
-  // We could look at moving the spinner code out to its own module
-  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 (!$('.spinner').length) {
-       $('<div class="spinner"></div>')
-        .appendTo('#app-container');
-     }
-
-     routeObjectSpinner = new Spinner(opts).spin();
-     $('.spinner').append(routeObjectSpinner.el);
-   }
-  });
-
-  var removeRouteObjectSpinner = function () {
-    if (routeObjectSpinner) {
-      routeObjectSpinner.stop();
-      $('.spinner').remove();
-    }
-  };
-
-  var removeViewSpinner = function () {
-    if (viewSpinner){
-      viewSpinner.stop();
-      $('.spinner').remove();
-    }
-  };
-
-  var viewSpinner;
-  FauxtonAPI.RouteObject.on('beforeRender', function (routeObject, view, selector) {
-    removeRouteObjectSpinner();
-
-    if (!view.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
-      };
-
-      viewSpinner = new Spinner(opts).spin();
-      $('<div class="spinner"></div>')
-        .appendTo(selector)
-        .append(viewSpinner.el);
-    }
-  });
-
-  FauxtonAPI.RouteObject.on('afterRender', function (routeObject, view, selector) {
-    removeViewSpinner();
-  });
-
-  FauxtonAPI.RouteObject.on('viewHasRendered', function () {
-    removeViewSpinner();
-    removeRouteObjectSpinner();
-  });
-
-  var extensions = _.extend({}, Backbone.Events);
-  // Can look at a remove function later.
-  FauxtonAPI.registerExtension = function (name, view) {
-    if (!extensions[name]) {
-      extensions[name] = [];
-    }
-
-    extensions.trigger('add:' + name, view);
-    extensions[name].push(view);
-  };
-
-  FauxtonAPI.getExtensions = function (name) {
-    var views = extensions[name];
-
-    if (!views) {
-      views = [];
-    }
-
-    return views;
-  };
-
-  FauxtonAPI.extensions = extensions;
-
-  app.fauxtonAPI = FauxtonAPI;
-  return app.fauxtonAPI;
-});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/app.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/app.js b/src/fauxton/app/app.js
index 5325f77..521ad6c 100644
--- a/src/fauxton/app/app.js
+++ b/src/fauxton/app/app.js
@@ -21,77 +21,38 @@ define([
   "bootstrap",
 
   "helpers",
-  "utils",
+  "core/utils",
   // Modules
-  "resizeColumns",
-
-   // Plugins.
+  "core/api",
+  "core/couchdbsession",
+  // Plugins.
   "plugins/backbone.layoutmanager",
   "plugins/jquery.form"
 
 ],
 
-function(app, $, _, Backbone, Bootstrap, Helpers, Utils, resizeColumns) {
-
-   // Make sure we have a console.log
+function(app, $, _, Backbone, Bootstrap, Helpers, Utils, FauxtonAPI, Couchdb) {
+  // Make sure we have a console.log
   if (typeof console == "undefined") {
     console = {
-      log: function(){}
+      log: function(){},
+      trace: function(){},
+      debug: function(){}
     };
   }
 
   // Provide a global location to place configuration settings and module
   // creation also mix in Backbone.Events
-  _.extend(app, Backbone.Events, {
+  _.extend(app, {
     utils: Utils,
-
-    renderView: function(baseView, selector, view, options, callback) {
-      baseView.setView(selector, new view(options)).render().then(callback);
-    },
-
-    // Create a custom object with a nested Views object.
-    module: function(additionalProps) {
-      return _.extend({ Views: {} }, additionalProps);
-    },
-
-    // Thanks to: http://stackoverflow.com/a/2880929
-    getParams: function(queryString) {
-      if (queryString) {
-        // I think this could be combined into one if
-        if (queryString.substring(0,1) === "?") {
-          queryString = queryString.substring(1);
-        } else if (queryString.indexOf('?') > -1) {
-          queryString = queryString.split('?')[1];
-        }
-      }
-      var hash = window.location.hash.split('?')[1];
-      queryString = queryString || hash || window.location.search.substring(1);
-      var match,
-      urlParams = {},
-      pl     = /\+/g,  // Regex for replacing addition symbol with a space
-      search = /([^&=]+)=?([^&]*)/g,
-      decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
-      query  = queryString;
-
-      if (queryString) {
-        while ((match = search.exec(query))) {
-          urlParams[decode(match[1])] = decode(match[2]);
-        }
-      }
-
-      return urlParams;
-    }
+    getParams: FauxtonAPI.utils.getParams
   });
 
-  //resizeAnimation
-  app.resizeColumns = new resizeColumns({});
-  app.resizeColumns.onResizeHandler();
-
   // Localize or create a new JavaScript Template object.
   var JST = window.JST = window.JST || {};
 
   // Configure LayoutManager with Backbone Boilerplate defaults.
-  Backbone.Layout.configure({
+  FauxtonAPI.Layout.configure({
     // Allow LayoutManager to augment Backbone.View.prototype.
     manage: true,
 
@@ -123,6 +84,28 @@ function(app, $, _, Backbone, Bootstrap, Helpers, Utils, resizeColumns) {
     }
   });
 
+  FauxtonAPI.setSession(new Couchdb.Session());
+
+  // Define your master router on the application namespace and trigger all
+  // navigation from this instance.
+  FauxtonAPI.config({
+    el: "#app-container",
+    masterLayout: new FauxtonAPI.Layout(),
+    
+    addHeaderLink: function(link) {
+      FauxtonAPI.registerExtension('navbar:addHeaderLink', link);
+    },
+
+    removeHeaderLink: function(link) {
+      FauxtonAPI.removeExtensionItem('navbar:addHeaderLink', link, function (item) {
+        if (item.title === link.title) {
+          return true;
+        }
+
+        return false;
+      });
+    }
+  });
 
   return app;
 });

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/config.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/config.js b/src/fauxton/app/config.js
index 057523b..98be9c6 100644
--- a/src/fauxton/app/config.js
+++ b/src/fauxton/app/config.js
@@ -25,7 +25,7 @@ require.config({
     jquery: "../assets/js/libs/jquery",
     lodash: "../assets/js/libs/lodash",
     backbone: "../assets/js/libs/backbone",
-    "backbone.layoutmanger": "../assets/js/plugins/backbone.layoutmanager",
+    "backbone.layoutmanager": "../assets/js/plugins/backbone.layoutmanager",
     bootstrap: "../assets/js/libs/bootstrap",
     spin: "../assets/js/libs/spin.min",
     d3: "../assets/js/libs/d3",
@@ -37,7 +37,8 @@ require.config({
 
   map: {
     "*": {
-      'underscore': 'lodash'
+      'underscore': 'lodash',
+      'api':'core/api'
     }
   },
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/api.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/api.js b/src/fauxton/app/core/api.js
new file mode 100644
index 0000000..1b21dca
--- /dev/null
+++ b/src/fauxton/app/core/api.js
@@ -0,0 +1,53 @@
+// 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([
+       "core/base",
+       "core/layout",
+       "core/router",
+       "core/routeObject",
+       "core/utils"
+],
+
+function(FauxtonAPI, Layout, Router, RouteObject, utils) {
+  FauxtonAPI = _.extend(FauxtonAPI, {
+    Layout: Layout,
+    Router: Router,
+    RouteObject: RouteObject,
+    utils: utils
+  });
+
+  FauxtonAPI.navigate = function(url, _opts) {
+    var options = _.extend({trigger: true}, _opts );
+    FauxtonAPI.router.navigate(url,options);
+  };
+
+  FauxtonAPI.beforeUnload = function () {
+    FauxtonAPI.router.beforeUnload.apply(FauxtonAPI.router, arguments);
+  };
+
+  FauxtonAPI.removeBeforeUnload = function () {
+    FauxtonAPI.router.removeBeforeUnload.apply(FauxtonAPI.router, arguments);
+  };
+
+  FauxtonAPI.addRoute = function(route) {
+    FauxtonAPI.router.route(route.route, route.name, route.callback);
+  };
+
+  FauxtonAPI.triggerRouteEvent = function (routeEvent, args) {
+    FauxtonAPI.router.triggerRouteEvent("route:"+routeEvent, args);
+  };
+
+  
+  return FauxtonAPI;
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/auth.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/auth.js b/src/fauxton/app/core/auth.js
new file mode 100644
index 0000000..15cf566
--- /dev/null
+++ b/src/fauxton/app/core/auth.js
@@ -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.
+
+define([
+  "core/base",
+  "backbone"
+],
+function(FauxtonAPI, Backbone) {
+
+  // This is not exposed externally as it should not need to be accessed or overridden
+  var Auth = function (options) {
+    this._options = options;
+    this.initialize.apply(this, arguments);
+  };
+
+  // Piggy-back on Backbone's self-propagating extend function,
+  Auth.extend = Backbone.Model.extend;
+
+  _.extend(Auth.prototype, Backbone.Events, {
+    authDeniedCb: function() {},
+
+    initialize: function() {
+      var that = this;
+    },
+
+    authHandlerCb : function (roles) {
+      var deferred = $.Deferred();
+      deferred.resolve();
+      return deferred;
+    },
+
+    registerAuth: function (authHandlerCb) {
+      this.authHandlerCb = authHandlerCb;
+    },
+
+    registerAuthDenied: function (authDeniedCb) {
+      this.authDeniedCb = authDeniedCb;
+    },
+
+    checkAccess: function (roles) {
+      var requiredRoles = roles || [],
+      that = this;
+
+      if (!FauxtonAPI.session) {
+        throw new Error("Fauxton.session is not configured.");
+      }
+
+      return FauxtonAPI.session.fetchUser().then(function (user) {
+        return FauxtonAPI.when(that.authHandlerCb(FauxtonAPI.session, requiredRoles));
+      });
+    }
+  });
+
+//  FauxtonAPI.auth = new Auth();
+
+  return Auth;
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/base.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/base.js b/src/fauxton/app/core/base.js
new file mode 100644
index 0000000..55a8d87
--- /dev/null
+++ b/src/fauxton/app/core/base.js
@@ -0,0 +1,137 @@
+// 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([
+  "backbone"
+],
+
+function(Backbone) {
+  var FauxtonAPI = {
+    //add default objects
+    router: {
+      navigate: function () {}
+    },
+
+    masterLayout: {},
+
+    addNotification: function () {},
+
+    config: function (options) {
+      return _.extend(this, options);
+    }
+  };
+
+  FauxtonAPI.Deferred = function() {
+    return $.Deferred();
+  };
+
+  FauxtonAPI.when = function (deferreds) {
+    if (deferreds instanceof Array) {
+      return $.when.apply(null, deferreds);
+    }
+
+    return $.when(deferreds);
+  };
+
+  FauxtonAPI.addonExtensions = {
+    initialize: function() {},
+    RouteObjects: {},
+    Views: {}
+  };
+
+  FauxtonAPI.addon = function(extra) {
+    return _.extend(_.clone(FauxtonAPI.addonExtensions), extra);
+  };
+
+  FauxtonAPI.View = Backbone.View.extend({
+    // This should return an array of promises, an empty array, or null
+    establish: function() {
+      return null;
+    },
+
+    loaderClassname: 'loader',
+
+    disableLoader: false,
+
+    forceRender: function () {
+      this.hasRendered = false;
+    }
+  });
+
+  FauxtonAPI.Model = Backbone.Model.extend({
+    fetchOnce: function (opt) {
+      var options = _.extend({}, opt);
+
+      if (!this._deferred || this._deferred.state() === "rejected" || options.forceFetch ) {
+        this._deferred = this.fetch();
+      }
+
+      return this._deferred;
+    }
+  });
+
+  var extensions = _.extend({}, Backbone.Events);
+  // Can look at a remove function later.
+  FauxtonAPI.registerExtension = function (name, view) {
+    if (!extensions[name]) {
+      extensions[name] = [];
+    }
+
+    extensions.trigger('add:' + name, view);
+    extensions[name].push(view);
+  };
+
+  FauxtonAPI.unRegisterExtension = function (name) {
+    var views = extensions[name];
+    
+    if (!views) { return; }
+    extensions.trigger('remove:' + name, views);
+    delete extensions[name];
+  };
+
+  FauxtonAPI.getExtensions = function (name) {
+    var views = extensions[name];
+
+    if (!views) {
+      views = [];
+    }
+
+    return views;
+  };
+
+  FauxtonAPI.removeExtensionItem = function (name, view, cb) {
+    var views = extensions[name];
+    if (!views) { return; }
+
+    var _cb = arguments[arguments.length -1];
+    if (_.isObject(view) && !cb) {
+      _cb = function (item) { return _.isEqual(item, view);};
+    } 
+
+    views = _.filter(views, function (item) {
+      return !_cb(item);
+    });
+
+    extensions[name] = views;
+    extensions.trigger('removeItem:' + name, view);
+  };
+
+  FauxtonAPI.extensions = extensions;
+
+  FauxtonAPI.setSession = function (newSession) {
+    FauxtonAPI.session = newSession;
+    return FauxtonAPI.session.fetchUser();
+  };
+
+  return FauxtonAPI;
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/couchdbSession.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/couchdbSession.js b/src/fauxton/app/core/couchdbSession.js
new file mode 100644
index 0000000..93bfd8a
--- /dev/null
+++ b/src/fauxton/app/core/couchdbSession.js
@@ -0,0 +1,54 @@
+// 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([
+  "core/base"
+],
+function (FauxtonAPI) {
+  var CouchdbSession = {
+    Session: FauxtonAPI.Model.extend({
+      url: '/_session',
+
+      user: function () {
+        var userCtx = this.get('userCtx');
+
+        if (!userCtx || !userCtx.name) { return null; }
+
+        return {
+          name: userCtx.name,
+          roles: userCtx.roles
+        };
+      },
+
+      fetchUser: function (opt) {
+        var that = this,
+        currentUser = this.user();
+
+        return this.fetchOnce(opt).then(function () {
+          var user = that.user();
+
+          // Notify anyone listening on these events that either a user has changed
+          // or current user is the same
+          if (currentUser !== user) {
+            that.trigger('session:userChanged');
+          } else {
+            that.trigger('session:userFetched');
+          }
+
+          // this will return the user as a value to all function that calls done on this
+          // eg. session.fetchUser().done(user) { .. do something with user ..}
+          return user; 
+        });
+      }
+    })
+  };
+
+  return CouchdbSession;
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/layout.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/layout.js b/src/fauxton/app/core/layout.js
new file mode 100644
index 0000000..ff339c7
--- /dev/null
+++ b/src/fauxton/app/core/layout.js
@@ -0,0 +1,91 @@
+// 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([
+  "backbone", 
+  "plugins/backbone.layoutmanager"
+], function(Backbone) {
+
+  // A wrapper of the main Backbone.layoutmanager
+  // Allows the main layout of the page to be changed by any plugin.
+  var Layout = function () {
+    this.layout = new Backbone.Layout({
+      template: "templates/layouts/with_sidebar",
+    });
+
+    this.layoutViews = {};
+    this.el = this.layout.el;
+  };
+
+  Layout.configure = function (options) {
+    Backbone.Layout.configure(options);
+  };
+
+  // creatings the dashboard object same way backbone does
+  _.extend(Layout.prototype, {
+    render: function () {
+      return this.layout.render();
+    },
+
+    setTemplate: function(template) {
+      if (template.prefix){
+        this.layout.template = template.prefix + template.name;
+      } else{
+        this.layout.template = "templates/layouts/" + template;
+      }
+      // If we're changing layouts all bets are off, so kill off all the
+      // existing views in the layout.
+      _.each(this.layoutViews, function(view){view.remove();});
+      this.layoutViews = {};
+      this.render();
+    },
+
+    setView: function(selector, view, keep) {
+      this.layout.setView(selector, view, false);
+
+      if (!keep) {
+        this.layoutViews[selector] = view;
+      }
+
+      return view;
+    },
+
+    renderView: function(selector) {
+      var view = this.layoutViews[selector];
+      if (!view) {
+        return false;
+      } else {
+        return view.render();
+      }
+    },
+
+    removeView: function (selector) {
+      var view = this.layout.getView(selector);
+
+      if (!view) {
+        return false;
+      }
+
+      view.remove();
+      
+      if (this.layoutViews[selector]) {
+        delete this.layoutViews[selector];
+      }
+
+      return true;
+    }
+
+  });
+
+  return Layout;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/routeObject.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/routeObject.js b/src/fauxton/app/core/routeObject.js
new file mode 100644
index 0000000..f3b8672
--- /dev/null
+++ b/src/fauxton/app/core/routeObject.js
@@ -0,0 +1,296 @@
+// 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([
+  "core/base",
+  "backbone"
+],
+function(FauxtonAPI, Backbone) {
+
+  var RouteObject = function(options) {
+    this._options = options;
+
+    this._configure(options || {});
+    this.initialize.apply(this, arguments);
+    this.addEvents();
+  };
+
+  var broadcaster = {};
+  _.extend(broadcaster, Backbone.Events);
+
+  RouteObject.on = function (eventName, fn) {
+    broadcaster.on(eventName, fn); 
+  };
+  
+  /* How Route Object events work
+   To listen to a specific route objects events:
+
+   myRouteObject = FauxtonAPI.RouteObject.extend({
+    events: {
+      "beforeRender": "beforeRenderEvent"
+    },
+
+    beforeRenderEvent: function (view, selector) {
+      console.log('Hey, beforeRenderEvent triggered',arguments);
+    },
+   });
+
+    It is also possible to listen to events triggered from all Routeobjects. 
+    This is great for more general things like adding loaders, hooks.
+
+    FauxtonAPI.RouteObject.on('beforeRender', function (routeObject, view, selector) {
+      console.log('hey, this will trigger when any routeobject renders a view');
+    });
+
+   Current Events to subscribe to:
+    * beforeFullRender -- before a full render is being done
+    * beforeEstablish -- before the routeobject calls establish
+    * AfterEstablish -- after the routeobject has run establish
+    * beforeRender -- before a view is rendered
+    * afterRender -- a view is finished being rendered
+    * renderComplete -- all rendering is complete
+    
+  */
+
+  // Piggy-back on Backbone's self-propagating extend function
+  RouteObject.extend = Backbone.Model.extend;
+
+  var routeObjectOptions = ["views", "routes", "events", "roles", "crumbs", "layout", "apiUrl", "establish"];
+
+  _.extend(RouteObject.prototype, Backbone.Events, {
+    // Should these be default vals or empty funcs?
+    views: {},
+    routes: {},
+    events: {},
+    crumbs: [],
+    layout: "with_sidebar",
+    apiUrl: null,
+    disableLoader: false,
+    loaderClassname: 'loader',
+    renderedState: false,
+    establish: function() {},
+    route: function() {},
+    roles: [],
+    _promises: [],
+    initialize: function() {}
+  }, {
+
+    renderWith: function(route, masterLayout, args) {
+      //set the options for this render
+      var options = {
+        masterLayout: masterLayout,
+        route: route,
+        args: args
+      };
+
+      this.setTemplateOnFullRender(masterLayout);
+
+      this.triggerBroadcast('beforeEstablish');
+
+      var renderAllViews = _.bind(this.renderAllViews, this, options),
+          establishError = _.bind(this.establishError, this),
+          renderComplete = _.bind(this.renderComplete, this),
+          promise = this.establish();
+
+      this.callEstablish(promise)
+        .then(renderAllViews, establishError)
+        .then(renderComplete);
+    },
+
+    setTemplateOnFullRender: function(masterLayout){
+      // Only want to redo the template if its a full render
+      if (!this.renderedState) {
+        masterLayout.setTemplate(this.layout);
+        this.triggerBroadcast('beforeFullRender');
+      }
+    },
+
+    callEstablish: function(establishPromise) {
+      this.addPromise(establishPromise);
+      return FauxtonAPI.when(establishPromise);
+    },
+
+    renderAllViews: function(options, resp){
+      var routeObject = this,
+          renderView = _.bind(this.renderView, this, routeObject, options);
+
+      this.triggerBroadcast('afterEstablish');
+
+      var promises = _.map(routeObject.getViews(), renderView, this);
+      return FauxtonAPI.when(promises);
+    },
+    
+    renderView: function(routeObject, options, view, selector) {
+      var viewInfo = {
+        view: view, 
+        selector: selector,
+        masterLayout: options.masterLayout
+      };
+
+      var renderViewOnLayout = _.bind(this.renderViewOnLayout, this, viewInfo);
+
+      if(view.hasRendered) { 
+        this.triggerBroadcast('viewHasRendered', view, selector);
+        return;
+      }
+
+      this.triggerBroadcast('beforeRender', view, selector);
+      
+      return this.callEstablish(view.establish()).then(renderViewOnLayout, this.establishError);
+    },
+
+    renderViewOnLayout: function(viewInfo, resp, xhr){
+      var masterLayout = viewInfo.masterLayout;
+
+      masterLayout.setView(viewInfo.selector, viewInfo.view);
+      masterLayout.renderView(viewInfo.selector);
+
+      this.triggerBroadcast('afterRender', viewInfo.view, viewInfo.selector);
+    },
+
+    establishError: function(resp){
+      if (!resp || !resp.responseText) { return; }
+      FauxtonAPI.addNotification({
+            msg: 'An Error occurred' + JSON.parse(resp.responseText).reason,
+            type: 'error',
+            clear: true
+      });
+    },
+
+    renderComplete: function () {
+      // Track that we've done a full initial render
+      this.setRenderedState(true);
+      this.triggerBroadcast('renderComplete');
+    },
+
+    setRenderedState: function(bool){
+      this.renderedState = bool;
+    },
+    
+    triggerBroadcast: function (eventName) {
+      var args = Array.prototype.slice.call(arguments);
+      this.trigger.apply(this, args);
+
+      args.splice(0,1, eventName, this);
+      broadcaster.trigger.apply(broadcaster, args);
+    },
+
+    get: function(key) {
+      return _.isFunction(this[key]) ? this[key]() : this[key];
+    },
+
+    addEvents: function(events) {
+      events = events || this.get('events');
+      _.each(events, function(method, event) {
+        if (!_.isFunction(method) && !_.isFunction(this[method])) {
+          throw new Error("Invalid method: "+method);
+        }
+        method = _.isFunction(method) ? method : this[method];
+
+        this.on(event, method);
+      }, this);
+    },
+
+    _configure: function(options) {
+      _.each(_.intersection(_.keys(options), routeObjectOptions), function(key) {
+        this[key] = options[key];
+      }, this);
+    },
+
+    getView: function(selector) {
+      return this.views[selector];
+    },
+
+    setView: function(selector, view) {
+      this.views[selector] = view;
+      return view;
+    },
+
+    getViews: function() {
+      return this.views;
+    },
+
+    removeViews: function () {
+      _.each(this.views, function (view, selector) {
+        view.remove();
+        delete this.views[selector];
+      }, this);
+    },
+
+    addPromise: function (promise) {
+      if (_.isEmpty(promise)) { return; }
+
+      if (!_.isArray(promise)) {
+        return this._promises.push(promise);
+      }
+
+      _.each(promise, function (p) {
+          this._promises.push(p);
+      }, this);
+    },
+
+    cleanup: function () {
+      this.removeViews();
+      this.rejectPromises();
+    },
+
+    rejectPromises: function () {
+      _.each(this._promises, function (promise) {
+        if (promise.state() === "resolved") { return; }
+        if (promise.abort) {
+          return promise.abort("Route change");
+        } 
+
+        promise.reject();
+      }, this);
+
+      this._promises = [];
+    },
+
+    getRouteUrls: function () {
+      return _.keys(this.get('routes'));
+    },
+
+    hasRoute: function (route) {
+      if (this.get('routes')[route]) {
+        return true;
+      }
+      return false;
+    },
+
+    routeCallback: function (route, args) {
+      var routes = this.get('routes'),
+      routeObj = routes[route],
+      routeCallback;
+
+      if (typeof routeObj === 'object') {
+        routeCallback = this[routeObj.route];
+      } else {
+        routeCallback = this[routeObj];
+      }
+
+      routeCallback.apply(this, args);
+    },
+
+    getRouteRoles: function (routeUrl) {
+      var route = this.get('routes')[routeUrl];
+
+      if ((typeof route === 'object') && route.roles) {
+        return route.roles; 
+      }
+
+      return this.roles;
+    }
+
+  });
+  return RouteObject;
+});

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/router.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/router.js b/src/fauxton/app/core/router.js
new file mode 100644
index 0000000..cc1ca4f
--- /dev/null
+++ b/src/fauxton/app/core/router.js
@@ -0,0 +1,113 @@
+// 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([
+       "core/base",
+       "core/auth",
+       "backbone"
+],
+
+function(FauxtonAPI, Auth, Backbone) {
+
+  var beforeUnloads = {};
+
+  var Router = Backbone.Router.extend({
+    routes: {},
+
+    beforeUnload: function (name, fn) {
+      beforeUnloads[name] = fn;
+    },
+
+    removeBeforeUnload: function (name) {
+      delete beforeUnloads[name];
+    },
+
+    navigate: function (fragment, trigger) {
+      var continueNav  = true,
+          msg = _.find(_.map(beforeUnloads, function (fn) { return fn(); }), function (beforeReturn) {
+            if (beforeReturn) { return true; }
+          });
+
+      if (msg) {
+        continueNav = window.confirm(msg);
+      }
+
+      if (continueNav) {
+        Backbone.Router.prototype.navigate(fragment, trigger);
+      }
+    },
+
+    addModuleRouteObject: function(RouteObject) {
+      var that = this;
+      var masterLayout = FauxtonAPI.masterLayout,
+      routeUrls = RouteObject.prototype.getRouteUrls();
+
+      _.each(routeUrls, function(route) {
+        this.route(route, route.toString(), function() {
+          var args = Array.prototype.slice.call(arguments),
+          roles = RouteObject.prototype.getRouteRoles(route),
+          authPromise = FauxtonAPI.auth.checkAccess(roles);
+
+          authPromise.then(function () {
+            if (!that.activeRouteObject || !that.activeRouteObject.hasRoute(route)) {
+              if (that.activeRouteObject) {
+                that.activeRouteObject.cleanup();
+              }
+              that.activeRouteObject = new RouteObject(route, masterLayout, args);
+            }
+
+            var routeObject = that.activeRouteObject;
+            routeObject.routeCallback(route, args);
+            routeObject.renderWith(route, masterLayout, args);
+          }, function () {
+            FauxtonAPI.auth.authDeniedCb();
+          });
+
+        }); 
+      }, this);
+    },
+
+    setModuleRoutes: function(addons) {
+      _.each(addons, function(module) {
+        if (module){
+          module.initialize();
+          // This is pure routes the addon provides
+          if (module.RouteObjects) {
+            _.each(module.RouteObjects, this.addModuleRouteObject, this);
+          }
+        }
+      }, this);
+    },
+
+    initialize: function(addons) {
+      this.addons = addons;
+      this.auth = FauxtonAPI.auth = new Auth();
+      // NOTE: This must be below creation of the layout
+      // FauxtonAPI header links and others depend on existence of the layout
+      this.setModuleRoutes(addons);
+
+      $(FauxtonAPI.el).html(FauxtonAPI.masterLayout.el);
+      FauxtonAPI.masterLayout.render();
+    },
+
+    triggerRouteEvent: function(event, args) {
+      if (this.activeRouteObject) {
+        var eventArgs = [event].concat(args);
+        this.activeRouteObject.trigger.apply(this.activeRouteObject, eventArgs );
+        this.activeRouteObject.renderWith(eventArgs, FauxtonAPI.masterLayout, args);
+      }
+    }
+  });
+
+  return Router;
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb/blob/96be583d/src/fauxton/app/core/tests/layoutSpec.js
----------------------------------------------------------------------
diff --git a/src/fauxton/app/core/tests/layoutSpec.js b/src/fauxton/app/core/tests/layoutSpec.js
new file mode 100644
index 0000000..b58966b
--- /dev/null
+++ b/src/fauxton/app/core/tests/layoutSpec.js
@@ -0,0 +1,92 @@
+// 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([
+  'api',
+  'testUtils'
+], function (FauxtonAPI, testUtils) {
+  var assert = testUtils.assert;
+
+  describe("Faxuton Layout", function () {
+    var layout;
+
+    beforeEach(function () {
+      layout = new FauxtonAPI.Layout();
+    });
+
+    describe('#setTemplate', function () {
+
+      it("Should set template without prefix", function () {
+        layout.setTemplate('myTemplate');
+
+        assert.equal(layout.layout.template, 'templates/layouts/myTemplate');
+
+      });
+
+      it("Should set template with prefix", function () {
+        layout.setTemplate({name: 'myTemplate', prefix: 'myPrefix/'});
+
+        assert.equal(layout.layout.template, 'myPrefix/myTemplate');
+      });
+
+      it("Should remove old views", function () {
+        var view = {
+          remove: function () {}
+        };
+
+        layout.layoutViews = {
+          'selector': view
+        };
+
+        var mockRemove = sinon.spy(view, 'remove');
+        layout.setTemplate('myTemplate');
+        assert.ok(mockRemove.calledOnce);
+
+      });
+
+      it("Should render", function () {
+        var mockRender = sinon.spy(layout, 'render');
+
+        layout.setTemplate('myTemplate');
+
+        assert.ok(mockRender.calledOnce);
+
+      });
+
+    });
+
+    describe('#renderView', function () {
+
+      it('Should render existing view', function () {
+        var view = new Backbone.View();
+        var mockRender = sinon.spy(view, 'render');
+        layout.layoutViews = {
+          '#selector': view
+        };
+
+        var out = layout.renderView('#selector');
+
+        assert.ok(mockRender.calledOnce);
+      });
+
+      it('Should return false for non-existing view', function () {
+        var view = new Backbone.View();
+        layout.layoutViews = {
+          'selector': view
+        };
+
+        var out = layout.renderView('wrongSelector');
+        assert.notOk(out, 'No view found');
+      });
+    });
+
+  });
+});