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');
+ });
+ });
+
+ });
+});