diff options
author | Garren Smith <garren.smith@gmail.com> | 2014-01-20 15:11:13 +0200 |
---|---|---|
committer | Garren Smith <garren.smith@gmail.com> | 2014-01-29 15:48:54 +0200 |
commit | 4c7c1ef5f137d71af12707bf0de3e85c69c145dc (patch) | |
tree | 3d6c64cc40925a78204ce2d5ff2c7f445910ffa6 | |
parent | 81f93d303129248121623a7bde52a3a320210382 (diff) | |
download | couchdb-4c7c1ef5f137d71af12707bf0de3e85c69c145dc.tar.gz |
Fauxton: Split up api.js
Split up api to core/*. Move tests to fit with this refactor.
Change addons/Fauxton with work with api extraction.
22 files changed, 496 insertions, 261 deletions
diff --git a/src/fauxton/Gruntfile.js b/src/fauxton/Gruntfile.js index 32065b67d..2c5a24906 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 diff --git a/src/fauxton/app/addons/activetasks/tests/viewsSpec.js b/src/fauxton/app/addons/activetasks/tests/viewsSpec.js index 395b60a79..13c904974 100644 --- a/src/fauxton/app/addons/activetasks/tests/viewsSpec.js +++ b/src/fauxton/app/addons/activetasks/tests/viewsSpec.js @@ -132,8 +132,5 @@ define([ }); }); - - - }); }); diff --git a/src/fauxton/app/addons/auth/resources.js b/src/fauxton/app/addons/auth/resources.js index 970d55bb9..321d3026d 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) { diff --git a/src/fauxton/app/addons/fauxton/base.js b/src/fauxton/app/addons/fauxton/base.js index ee6b7944a..2fee68070 100644 --- a/src/fauxton/app/addons/fauxton/base.js +++ b/src/fauxton/app/addons/fauxton/base.js @@ -66,6 +66,15 @@ function(app, FauxtonAPI, resizeColumns) { })); } }); + + 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({ diff --git a/src/fauxton/app/addons/fauxton/layout.js b/src/fauxton/app/addons/fauxton/layout.js index 3810c84c3..baac86411 100644 --- a/src/fauxton/app/addons/fauxton/layout.js +++ b/src/fauxton/app/addons/fauxton/layout.js @@ -29,6 +29,10 @@ function(Backbone) { 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 () { 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 000000000..644002cdf --- /dev/null +++ b/src/fauxton/app/addons/fauxton/tests/baseSpec.js @@ -0,0 +1,74 @@ +// 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(); + + // Need to find a better way of doing this + mockLayout = { + setTemplate: sinon.spy(), + clearBreadcrumbs: sinon.spy(), + setView: sinon.spy(), + renderView: 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.clearBreadcrumbs.calledOnce, 'Clear Breadcrumbs called'); + }); + + it('Should set breadcrumbs when breadcrumbs exist', function () { + FauxtonAPI.masterLayout = mockLayout; + testRouteObject.renderWith('the-route', mockLayout, 'args'); + assert.ok(mockLayout.setBreadcrumbs.calledOnce, 'Set Breadcrumbs was called'); + }); + + }); + + +}); diff --git a/src/fauxton/test/core/navbarSpec.js b/src/fauxton/app/addons/fauxton/tests/navbarSpec.js index 3eca6f6ce..3eca6f6ce 100644 --- a/src/fauxton/test/core/navbarSpec.js +++ b/src/fauxton/app/addons/fauxton/tests/navbarSpec.js diff --git a/src/fauxton/test/core/paginateSpec.js b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js index d05b322a6..535e26f4a 100644 --- a/src/fauxton/test/core/paginateSpec.js +++ b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js @@ -10,12 +10,12 @@ // License for the specific language governing permissions and limitations under // the License. define([ - 'api', + 'app', 'addons/fauxton/components', 'addons/documents/resources', 'testUtils', - 'app' -], function (FauxtonAPI, Views, Models, testUtils, app) { + 'api' +], function (app, Views, Models, testUtils, FauxtonAPI) { var assert = testUtils.assert, ViewSandbox = testUtils.ViewSandbox; @@ -23,10 +23,6 @@ define([ describe('IndexPaginate', function () { var viewSandbox, paginate, collection, navigateMock; beforeEach(function () { - app.router = { - navigate: function () {} - }; - collection = new Models.IndexCollection([{ id:'myId1', doc: 'num1' diff --git a/src/fauxton/app/app.js b/src/fauxton/app/app.js index 23a72c78e..74980ea89 100644 --- a/src/fauxton/app/app.js +++ b/src/fauxton/app/app.js @@ -24,14 +24,14 @@ define([ "core/utils", // Modules "core/api", - "addons/Fauxton/layout", + "core/couchdbSession", // Plugins. "plugins/backbone.layoutmanager", "plugins/jquery.form" ], -function(app, $, _, Backbone, Bootstrap, Helpers, Utils, FauxtonAPI, Layout, LoadAddons) { +function(app, $, _, Backbone, Bootstrap, Helpers, Utils, FauxtonAPI, Couchdb) { // Make sure we have a console.log if (typeof console == "undefined") { console = { @@ -52,7 +52,7 @@ function(app, $, _, Backbone, Bootstrap, Helpers, Utils, FauxtonAPI, Layout, Loa 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, @@ -84,11 +84,13 @@ function(app, $, _, Backbone, Bootstrap, Helpers, Utils, FauxtonAPI, Layout, Loa } }); + 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 Layout(), + masterLayout: new FauxtonAPI.Layout(), addHeaderLink: function(link) { FauxtonAPI.registerExtension('navbar:addHeaderLink', link); diff --git a/src/fauxton/app/config.js b/src/fauxton/app/config.js index 2f1433e54..98be9c6a8 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", diff --git a/src/fauxton/app/core/api.js b/src/fauxton/app/core/api.js index 81e9aa495..5483f446f 100644 --- a/src/fauxton/app/core/api.js +++ b/src/fauxton/app/core/api.js @@ -11,12 +11,22 @@ // the License. define([ - "core/resources", + "core/base", + "core/layout", + "core/router", + "core/routeObject", + "core/couchdbSession", "core/utils" ], -function(FauxtonAPI, utils) { - FauxtonAPI.utils = utils; +function(FauxtonAPI, Layout, Router, RouteObject, CouchdbSession, 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); @@ -38,97 +48,7 @@ function(FauxtonAPI, utils) { FauxtonAPI.router.triggerRouteEvent("route:"+routeEvent, args); }; - var beforeUnloads = {}; - - FauxtonAPI.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.removeViews(); - } - 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; - // 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 FauxtonAPI; }); diff --git a/src/fauxton/app/core/auth.js b/src/fauxton/app/core/auth.js new file mode 100644 index 000000000..15cf5667f --- /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; +}); + diff --git a/src/fauxton/app/core/base.js b/src/fauxton/app/core/base.js index 46013863f..e98bdf867 100644 --- a/src/fauxton/app/core/base.js +++ b/src/fauxton/app/core/base.js @@ -11,26 +11,19 @@ // the License. define([ + "backbone" ], -function() { +function(Backbone) { var FauxtonAPI = { //add default objects router: { navigate: function () {} }, - masterLayout: { - // remove these by converting to extensions - navBar: { - addLink: function () {}, - removeLink: function () {} - } - }, - - addNotification: function () { + masterLayout: {}, - }, + addNotification: function () {}, config: function (options) { return _.extend(this, options); @@ -109,6 +102,11 @@ function() { FauxtonAPI.extensions = extensions; + FauxtonAPI.setSession = function (newSession) { + FauxtonAPI.session = newSession; + return FauxtonAPI.session.fetchUser(); + }; + return FauxtonAPI; }); diff --git a/src/fauxton/app/core/couchdbSession.js b/src/fauxton/app/core/couchdbSession.js index 532f3eacd..6f13677a6 100644 --- a/src/fauxton/app/core/couchdbSession.js +++ b/src/fauxton/app/core/couchdbSession.js @@ -12,69 +12,62 @@ define([ "core/base" ], function (FauxtonAPI) { - //this later needs to be dynamically loaded - var app = { - host: "" - }; - - FauxtonAPI.UUID = FauxtonAPI.Model.extend({ - initialize: function(options) { - options = _.extend({count: 1}, options); - this.count = options.count; - }, + var CouchdbSession = { + 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; - }, + url: function() { + return "/_uuids?count=" + this.count; + }, - next: function() { - return this.get("uuids").pop(); - } - }); + next: function() { + return this.get("uuids").pop(); + } + }), - FauxtonAPI.Session = FauxtonAPI.Model.extend({ - url: app.host + '/_session', + Session: FauxtonAPI.Model.extend({ + url: '/_session', - user: function () { - var userCtx = this.get('userCtx'); + user: function () { + var userCtx = this.get('userCtx'); - if (!userCtx || !userCtx.name) { return null; } + if (!userCtx || !userCtx.name) { return null; } - return { - name: userCtx.name, - roles: userCtx.roles - }; - }, + return { + name: userCtx.name, + roles: userCtx.roles + }; + }, - fetchUser: function (opt) { - var that = this, - currentUser = this.user(); + fetchUser: function (opt) { + var that = this, + currentUser = this.user(); - return this.fetchOnce(opt).then(function () { - var user = that.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'); - } + // 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) { - FauxtonAPI.session = newSession; - return FauxtonAPI.session.fetchUser(); + // 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; + }); + } + }) }; + //set default session - FauxtonAPI.setSession(new FauxtonAPI.Session()); + //FauxtonAPI.setSession(new FauxtonAPI.Session()); - return FauxtonAPI; + return CouchdbSession; }); diff --git a/src/fauxton/app/core/layout.js b/src/fauxton/app/core/layout.js new file mode 100644 index 000000000..a5ed88c36 --- /dev/null +++ b/src/fauxton/app/core/layout.js @@ -0,0 +1,90 @@ +// 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. + // Exposes the different views: + // navBar -> the top navigation bar + // dashboardContent -> Main display view + // breadcrumbs -> Breadcrumbs navigation section + 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(); + }, + + 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; + +}); diff --git a/src/fauxton/app/core/resources.js b/src/fauxton/app/core/routeObject.js index 3a2907788..9fe8f6975 100644 --- a/src/fauxton/app/core/resources.js +++ b/src/fauxton/app/core/routeObject.js @@ -11,59 +11,12 @@ // the License. define([ - "app", - "core/couchdbSession", + "core/base", "backbone" ], -function(app, FauxtonAPI) { +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(); - - FauxtonAPI.RouteObject = function(options) { + var RouteObject = function(options) { this._options = options; this._configure(options || {}); @@ -74,7 +27,7 @@ function(app, FauxtonAPI) { var broadcaster = {}; _.extend(broadcaster, Backbone.Events); - FauxtonAPI.RouteObject.on = function (eventName, fn) { + RouteObject.on = function (eventName, fn) { broadcaster.on(eventName, fn); }; @@ -109,11 +62,11 @@ function(app, FauxtonAPI) { */ // Piggy-back on Backbone's self-propagating extend function - FauxtonAPI.RouteObject.extend = Backbone.Model.extend; + RouteObject.extend = Backbone.Model.extend; var routeObjectOptions = ["views", "routes", "events", "roles", "crumbs", "layout", "apiUrl", "establish"]; - _.extend(FauxtonAPI.RouteObject.prototype, Backbone.Events, { + _.extend(RouteObject.prototype, Backbone.Events, { // Should these be default vals or empty funcs? views: {}, routes: {}, @@ -127,6 +80,7 @@ function(app, FauxtonAPI) { establish: function() {}, route: function() {}, roles: [], + _promises: [], initialize: function() {} }, { @@ -141,7 +95,9 @@ function(app, FauxtonAPI) { } triggerBroadcast('beforeEstablish'); - FauxtonAPI.when(this.establish()).then(function(resp) { + var establishPromise = this.establish(); + this.addPromise(establishPromise); + FauxtonAPI.when(establishPromise).then(function(resp) { triggerBroadcast('afterEstablish'); _.each(routeObject.getViews(), function(view, selector) { if(view.hasRendered) { @@ -150,7 +106,9 @@ function(app, FauxtonAPI) { } triggerBroadcast('beforeRender', view, selector); - FauxtonAPI.when(view.establish()).then(function(resp) { + var viewPromise = view.establish(); + routeObject.addPromise(viewPromise); + FauxtonAPI.when(viewPromise).then(function(resp) { masterLayout.setView(selector, view); masterLayout.renderView(selector); @@ -161,7 +119,7 @@ function(app, FauxtonAPI) { reason: resp }; - if (resp) { + if (resp && resp.responseText) { var errorText = JSON.parse(resp.responseText).reason; FauxtonAPI.addNotification({ msg: 'An Error occurred: ' + errorText, @@ -169,13 +127,12 @@ function(app, FauxtonAPI) { clear: true }); } - masterLayout.renderView(selector); }); }); }.bind(this), function (resp) { - if (!resp) { return; } + if (!resp || !resp.responseText) { return; } FauxtonAPI.addNotification({ msg: 'An Error occurred' + JSON.parse(resp.responseText).reason, type: 'error', @@ -183,12 +140,6 @@ function(app, FauxtonAPI) { }); }); - 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'); @@ -244,6 +195,36 @@ function(app, FauxtonAPI) { }, 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')); }, @@ -280,6 +261,5 @@ function(app, FauxtonAPI) { } }); - - return FauxtonAPI; + return RouteObject; }); diff --git a/src/fauxton/app/core/router.js b/src/fauxton/app/core/router.js new file mode 100644 index 000000000..966e171f4 --- /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.removeViews(); + } + 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; +}); + diff --git a/src/fauxton/test/core/layoutSpec.js b/src/fauxton/app/core/tests/layoutSpec.js index 4167100d7..b58966bc8 100644 --- a/src/fauxton/test/core/layoutSpec.js +++ b/src/fauxton/app/core/tests/layoutSpec.js @@ -10,18 +10,16 @@ // License for the specific language governing permissions and limitations under // the License. define([ - 'addons/fauxton/layout', - 'testUtils' -], function (Layout, testUtils) { + 'api', + 'testUtils' +], function (FauxtonAPI, testUtils) { var assert = testUtils.assert; describe("Faxuton Layout", function () { var layout; beforeEach(function () { - var navBar = new Backbone.View(); - var apiBar = new Backbone.View(); - layout = new Layout(navBar, apiBar); + layout = new FauxtonAPI.Layout(); }); describe('#setTemplate', function () { diff --git a/src/fauxton/test/core/routeObjectSpec.js b/src/fauxton/app/core/tests/routeObjectSpec.js index 987d5b7e4..2fca94d45 100644 --- a/src/fauxton/test/core/routeObjectSpec.js +++ b/src/fauxton/app/core/tests/routeObjectSpec.js @@ -57,16 +57,7 @@ define([ assert.ok(mockLayout.setTemplate.calledOnce, 'SetTemplate not meant to be called'); }); - it('Should clear breadcrumbs', function () { - testRouteObject.renderWith('the-route', mockLayout, 'args'); - assert.ok(mockLayout.clearBreadcrumbs.calledOnce, 'Clear Breadcrumbs called'); - }); - - it('Should set breadcrumbs when breadcrumbs exist', function () { - testRouteObject.renderWith('the-route', mockLayout, 'args'); - assert.ok(mockLayout.setBreadcrumbs.calledOnce, 'Set Breadcrumbs was called'); - }); - + it("Should call establish of routeObject", function () { var establishSpy = sinon.spy(testRouteObject,"establish"); diff --git a/src/fauxton/tasks/couchserver.js b/src/fauxton/tasks/couchserver.js index 5ccbfe11e..21c2fcb23 100644 --- a/src/fauxton/tasks/couchserver.js +++ b/src/fauxton/tasks/couchserver.js @@ -61,7 +61,7 @@ module.exports = function (grunt) { // server js from app directory filePath = path.join(app_dir, url.replace('/_utils/fauxton/','')); } else if (!!url.match(/testrunner/)) { - var testSetup = grunt.util.spawn({cmd: 'grunt', grunt: true, args: ['mochaSetup']}, function (error, result, code) {/* log.writeln(String(result));*/ }); + var testSetup = grunt.util.spawn({cmd: 'grunt', grunt: true, args: ['test_inline']}, function (error, result, code) {/* log.writeln(String(result));*/ }); testSetup.stdout.pipe(process.stdout); testSetup.stderr.pipe(process.stderr); filePath = path.join('./test/runner.html'); diff --git a/src/fauxton/test/mocha/testUtils.js b/src/fauxton/test/mocha/testUtils.js index f9643e8df..08b80c70d 100644 --- a/src/fauxton/test/mocha/testUtils.js +++ b/src/fauxton/test/mocha/testUtils.js @@ -11,11 +11,12 @@ // the License. define([ + "api", "chai", "sinon-chai", "underscore" ], -function(chai, sinonChai) { +function(FauxtonAPI,chai, sinonChai) { chai.use(sinonChai); var ViewSandbox = function () { @@ -24,7 +25,7 @@ function(chai, sinonChai) { _.extend(ViewSandbox.prototype, { initialize: function () { - this.$el = $('<div style="display:none"></div>').appendTo('body'); + this.$el = $('<div style="display:no1ne"></div>').appendTo('body'); this.$ = this.$el.find; }, views: [], diff --git a/src/fauxton/test/test.config.underscore b/src/fauxton/test/test.config.underscore index dda16f275..5cebe7837 100644 --- a/src/fauxton/test/test.config.underscore +++ b/src/fauxton/test/test.config.underscore @@ -5,6 +5,7 @@ require.config( ); require([ + "app", <% _.each(testFiles, function (test) {%> '../<%= test %>', <% }) %> |