Repository: couchdb-fauxton Updated Branches: refs/heads/master 4d863a8f2 -> 02a5ee3f0
Add built in React support for route objects This removes the need for a backbone wrapper view for any React components. React components can now be rendered directly in a RouteObject. Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/02a5ee3f Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/02a5ee3f Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/02a5ee3f Branch: refs/heads/master Commit: 02a5ee3f0067432d3abf4fd683397ed6d66137ab Parents: 4d863a8 Author: Garren Smith <[email protected]> Authored: Wed Apr 1 15:54:08 2015 +0200 Committer: Garren Smith <[email protected]> Committed: Mon Apr 13 13:03:06 2015 +0200 ---------------------------------------------------------------------- app/addons/config/routes.js | 13 +- app/addons/cors/actions.js | 12 ++ app/addons/cors/base.js | 6 +- app/addons/cors/components.react.jsx | 13 +- app/addons/cors/stores.js | 17 +-- app/addons/cors/views.js | 53 -------- app/addons/documents/changes/actions.js | 7 + app/addons/documents/index-editor/actions.js | 6 + .../documents/index-editor/components.react.jsx | 9 +- app/addons/documents/index-editor/stores.js | 11 ++ app/addons/documents/routes-documents.js | 63 +++------ app/addons/documents/routes-index-editor.js | 27 ++-- app/addons/documents/views-changes.js | 66 --------- app/addons/documents/views-index.js | 54 -------- app/core/auth.js | 1 - app/core/layout.js | 5 +- app/core/routeObject.js | 42 +++++- app/core/router.js | 1 - app/core/tests/routeObjectSpec.js | 133 +++++++++++++++++-- 19 files changed, 255 insertions(+), 284 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/config/routes.js ---------------------------------------------------------------------- diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js index f2cf2d0..77c5605 100644 --- a/app/addons/config/routes.js +++ b/app/addons/config/routes.js @@ -15,18 +15,17 @@ define([ 'api', 'addons/config/resources', 'addons/config/views', - 'addons/cors/views' + 'addons/cors/components.react', + 'addons/cors/actions' ], -function (app, FauxtonAPI, Config, Views, CORS) { +function (app, FauxtonAPI, Config, Views, CORSComponents, CORSActions) { var ConfigRouteObject = FauxtonAPI.RouteObject.extend({ layout: 'with_tabs_sidebar', initialize: function () { this.configs = new Config.Collection(); - this.cors = new CORS.Config(); - this.httpd = new CORS.Httpd(); this.sidebar = this.setView("#sidebar-content", new Views.Tabs({ sidebarItems: [ @@ -68,10 +67,8 @@ function (app, FauxtonAPI, Config, Views, CORS) { configCORS: function () { this.removeView('#right-header'); - this.newSection = this.setView('#dashboard-content', new CORS.Views.CORSWrapper({ - cors: this.cors, - httpd: this.httpd - })); + this.newSection = this.setComponent('#dashboard-content', CORSComponents.CORSController); + CORSActions.FetchAndEditCors(); this.sidebar.setSelectedTab("cors"); }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/cors/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/cors/actions.js b/app/addons/cors/actions.js index 071f2ed..cce1518 100644 --- a/app/addons/cors/actions.js +++ b/app/addons/cors/actions.js @@ -15,6 +15,18 @@ define([ 'addons/cors/resources' ], function (FauxtonAPI, ActionTypes, Resources) { return { + FetchAndEditCors: function () { + var cors = new Resources.Config(); + var httpd = new Resources.Httpd(); + + FauxtonAPI.when([cors.fetch(), httpd.fetch()]).then(function () { + this.editCors({ + origins: cors.get('origins'), + isEnabled: httpd.corsEnabled() + }); + }.bind(this)); + }, + editCors: function (options) { FauxtonAPI.dispatch({ type: ActionTypes.EDIT_CORS, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/cors/base.js ---------------------------------------------------------------------- diff --git a/app/addons/cors/base.js b/app/addons/cors/base.js index f4b9fac..bb98115 100644 --- a/app/addons/cors/base.js +++ b/app/addons/cors/base.js @@ -12,11 +12,11 @@ define([ "app", - "api", - "addons/cors/views" + "api" ], -function (app, FauxtonAPI, CORS) { +function (app, FauxtonAPI) { + var CORS = FauxtonAPI.addon(); CORS.initialize = function () {}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/cors/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/cors/components.react.jsx b/app/addons/cors/components.react.jsx index 9ea15d9..10727ff 100644 --- a/app/addons/cors/components.react.jsx +++ b/app/addons/cors/components.react.jsx @@ -225,7 +225,7 @@ define([ origins: corsStore.getOrigins(), isAllOrigins: corsStore.isAllOrigins(), configChanged: corsStore.hasConfigChanged(), - savingStatus: corsStore.getSavingStatus() + shouldSaveChange: corsStore.shouldSaveChange() }; }, @@ -242,7 +242,9 @@ define([ }, componentDidUpdate: function () { - this.save(); + if (this.state.shouldSaveChange) { + this.save(); + } }, onChange: function () { @@ -335,13 +337,6 @@ define([ }); return { - renderCORS: function (el) { - React.render(<CORSController />, el); - }, - - removeCORS: function (el) { - React.unmountComponentAtNode(el); - }, CORSController: CORSController, OriginInput: OriginInput, Origins: Origins, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/cors/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/cors/stores.js b/app/addons/cors/stores.js index 3e6ce16..1200053 100644 --- a/app/addons/cors/stores.js +++ b/app/addons/cors/stores.js @@ -21,19 +21,11 @@ define([ this._isEnabled = options.isEnabled; this._origins = options.origins; this._configChanged = false; - this.savingDone(); + this._shouldSaveChange = false; }, - saving: function () { - this._savingStatus = 'Saving'; - }, - - savingDone: function () { - this._savingStatus = 'Save'; - }, - - getSavingStatus: function () { - return this._savingStatus; + shouldSaveChange: function () { + return this._shouldSaveChange; }, hasConfigChanged: function () { @@ -96,6 +88,9 @@ define([ }, dispatch: function (action) { + // it should save after any change is triggered except for EDIT_CORS which is just to update + // cors after the first change + this._shouldSaveChange = true; switch (action.type) { case ActionTypes.EDIT_CORS: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/cors/views.js ---------------------------------------------------------------------- diff --git a/app/addons/cors/views.js b/app/addons/cors/views.js deleted file mode 100644 index 99a4d88..0000000 --- a/app/addons/cors/views.js +++ /dev/null @@ -1,53 +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", - "api", - "addons/cors/resources", - "addons/cors/components.react", - "addons/cors/actions" -], - - -function (app, FauxtonAPI, CORS, Components, Actions) { - var Views = {}; - - Views.CORSWrapper = FauxtonAPI.View.extend({ - className: 'list', - initialize: function (options) { - this.cors = options.cors; - this.httpd = options.httpd; - }, - - establish: function () { - return [this.cors.fetch(), this.httpd.fetch()]; - }, - - afterRender: function () { - Actions.editCors({ - origins: this.cors.get('origins'), - isEnabled: this.httpd.corsEnabled() - }); - Components.renderCORS(this.el); - }, - - cleanup: function () { - Components.removeCORS(this.el); - } - - }); - - CORS.Views = Views; - - return CORS; -}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/changes/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/changes/actions.js b/app/addons/documents/changes/actions.js index a62a6fe..d94da66 100644 --- a/app/addons/documents/changes/actions.js +++ b/app/addons/documents/changes/actions.js @@ -43,6 +43,13 @@ function (app, FauxtonAPI, ActionTypes) { type: ActionTypes.SET_CHANGES, options: options }); + }, + + fetchChanges: function (options) { + var changes = options.changes; + changes.fetch().then(function () { + this.setChanges(options); + }.bind(this)); } }; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/index-editor/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/actions.js b/app/addons/documents/index-editor/actions.js index 79ef149..a7b33c7 100644 --- a/app/addons/documents/index-editor/actions.js +++ b/app/addons/documents/index-editor/actions.js @@ -76,6 +76,12 @@ function (app, FauxtonAPI, Documents, ActionTypes, IndexResultsActions) { }); }, + fetchDesignDocsBeforeEdit: function (options) { + options.designDocs.fetch({reset: true}).then(function () { + this.editIndex(options); + }.bind(this)); + }, + saveView: function (viewInfo) { var designDoc; var designDocs = viewInfo.designDocs; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/index-editor/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/components.react.jsx b/app/addons/documents/index-editor/components.react.jsx index 0887bac..c38be29 100644 --- a/app/addons/documents/index-editor/components.react.jsx +++ b/app/addons/documents/index-editor/components.react.jsx @@ -407,7 +407,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) } }); - var EditorWrapper = React.createClass({ + var EditorController = React.createClass({ render: function () { return ( <div className="editor-wrapper span5 scrollable"> @@ -418,12 +418,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) }); var Views = { - renderEditor: function (el) { - React.render(<EditorWrapper/>, el); - }, - removeEditor: function (el) { - React.unmountComponentAtNode(el); - }, + EditorController: EditorController, ReduceEditor: ReduceEditor, Editor: Editor, DesignDocSelector: DesignDocSelector, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/index-editor/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/stores.js b/app/addons/documents/index-editor/stores.js index f9a3606..9839001 100644 --- a/app/addons/documents/index-editor/stores.js +++ b/app/addons/documents/index-editor/stores.js @@ -23,6 +23,17 @@ function (FauxtonAPI, ActionTypes) { defaultMap: 'function (doc) {\n emit(doc._id, 1);\n}', defaultReduce: 'function (keys, values, rereduce) {\n if (rereduce) {\n return sum(values);\n } else {\n return values.length;\n }\n}', + initialize: function () { + this._designDocs = []; + this._view = { + reduce: this.defaultMap, + map: this.defaultReduce + }; + this._database = { + id: '0' + }; + }, + editIndex: function (options) { this._database = options.database; this._newView = options.newView; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/routes-documents.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js index 39617e3..652025e 100644 --- a/app/addons/documents/routes-documents.js +++ b/app/addons/documents/routes-documents.js @@ -17,8 +17,8 @@ define([ // Modules 'addons/documents/shared-routes', 'addons/documents/views', - 'addons/documents/views-changes', - 'addons/documents/views-index', + 'addons/documents/changes/components.react', + 'addons/documents/changes/actions', 'addons/documents/views-doceditor', 'addons/documents/views-mango', @@ -26,11 +26,12 @@ define([ 'addons/documents/resources', 'addons/fauxton/components', 'addons/documents/pagination/stores', - 'addons/documents/index-results/actions' + 'addons/documents/index-results/actions', + 'addons/documents/index-results/index-results.components.react' ], -function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mango, - Databases, Resources, Components, PaginationStores, IndexResultsActions) { +function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, DocEditor, Mango, + Databases, Resources, Components, PaginationStores, IndexResultsActions, IndexResultsComponents) { var DocumentsRouteObject = BaseRoute.extend({ @@ -82,7 +83,8 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mang designDocMetadata: function (database, ddoc) { this.footer && this.footer.remove(); this.toolsView && this.toolsView.remove(); - this.viewEditor && this.viewEditor.remove(); + + this.removeComponent('#dashboard-upper-content'); var designDocInfo = new Resources.DdocInfo({ _id: "_design/" + ddoc }, { database: this.database }); this.setView("#dashboard-lower-content", new Documents.Views.DdocInfo({ @@ -108,11 +110,6 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mang docParams = params.docParams, collection; - if (this.eventAllDocs) { - this.eventAllDocs = false; - return; - } - this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar()); this.footer = this.setView('#footer', new Documents.Views.Footer()); @@ -128,9 +125,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mang this.sidebar.setSelectedTab("all-docs"); } - this.viewEditor && this.viewEditor.remove(); - this.headerView && this.headerView.remove(); - + this.removeComponent('#dashboard-upper-content'); if (!docParams) { docParams = {}; @@ -144,7 +139,8 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mang this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage(); - this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({})); + //this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({})); + this.setComponent('#dashboard-lower-content', IndexResultsComponents.List); // this used to be a function that returned the object, but be warned: it caused a closure with a reference to // the initial this.database object which can change @@ -167,11 +163,14 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mang var docParams = app.getParams(); this.database.buildChanges(docParams); - this.changesView = this.setView("#dashboard-lower-content", new Changes.ChangesReactWrapper({ - model: this.database - })); + ChangesActions.fetchChanges({ + changes: this.database.changes, + filters: [], + databaseName: this.database.id + }); - this.headerView = this.setView('#dashboard-upper-content', new Changes.ChangesHeaderReactWrapper()); + this.setComponent("#dashboard-lower-content", Changes.ChangesController); + this.setComponent('#dashboard-upper-content', Changes.ChangesHeaderController); this.footer && this.footer.remove(); this.toolsView && this.toolsView.remove(); @@ -188,33 +187,9 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mang }, cleanup: function () { - if (this.reactHeader) { - this.removeView('#react-headerbar'); - } - if (this.viewEditor) { - this.removeView('#dashboard-upper-content'); - } - if (this.documentsView) { - this.removeView('#dashboard-lower-content'); - } - if (this.rightHeader) { - this.removeView('#right-header'); - } - if (this.leftheader) { - this.removeView('#breadcrumbs'); - } - if (this.sidebar) { - this.removeView('#sidebar'); - } - if (this.footer) { - this.removeView('#footer'); - } - if (this.headerView) { - this.removeView('#dashboard-upper-content'); - } - // we're no longer interested in listening to the lookahead tray event on this route object this.stopListening(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase); + FauxtonAPI.RouteObject.prototype.cleanup.apply(this); } }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/routes-index-editor.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js index 4d1e3b3..e04b35b 100644 --- a/app/addons/documents/routes-index-editor.js +++ b/app/addons/documents/routes-index-editor.js @@ -18,16 +18,18 @@ define([ "addons/documents/helpers", 'addons/documents/shared-routes', 'addons/documents/views', - 'addons/documents/views-index', + 'addons/documents/index-editor/components.react', + 'addons/documents/index-editor/actions', 'addons/databases/base', 'addons/fauxton/components', 'addons/documents/pagination/stores', - 'addons/documents/index-results/actions' + 'addons/documents/index-results/actions', + 'addons/documents/index-results/index-results.components.react' ], -function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index, - Databases, Components, PaginationStores, IndexResultsActions) { +function (app, FauxtonAPI, Helpers, BaseRoute, Documents, IndexEditorComponents, ActionsIndexEditor, + Databases, Components, PaginationStores, IndexResultsActions, IndexResultsComponents) { var IndexEditorAndResults = BaseRoute.extend({ @@ -52,7 +54,6 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index, establish: function () { return [ - this.designDocs.fetch({reset: true}), this.allDatabases.fetchOnce() ]; }, @@ -63,7 +64,6 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index, docParams = params.docParams, decodeDdoc = decodeURIComponent(ddoc); - this.rightHeader = this.setView('#right-header', new Documents.Views.RightAllDocsHeader({ database: this.database })); @@ -95,15 +95,16 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index, isListDeletable: false }); - this.viewEditor = this.setView('#left-content', new Index.ViewEditorReact({ + ActionsIndexEditor.fetchDesignDocsBeforeEdit({ viewName: viewName, newView: false, database: this.database, designDocs: this.designDocs, designDocId: '_design/' + decodeDdoc - })); + }); - this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({})); + this.setComponent('#left-content', IndexEditorComponents.EditorController); + this.setComponent('#dashboard-lower-content', IndexResultsComponents.List); this.apiUrl = function () { return [this.indexedDocs.urlRef(urlParams), FauxtonAPI.constants.DOC_URLS.GENERAL]; @@ -130,16 +131,18 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index, ] })); - this.viewEditor = this.setView('#left-content', new Index.ViewEditorReact({ + ActionsIndexEditor.fetchDesignDocsBeforeEdit({ viewName: 'new-view', newView: true, database: this.database, designDocs: this.designDocs, designDocId: designDoc, newDesignDoc: newDesignDoc - })); + }); + + this.setComponent('#left-content', IndexEditorComponents.EditorController); + this.setComponent('#dashboard-lower-content', IndexResultsComponents.List); - this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({})); IndexResultsActions.newResultsList({ collection: [], isListDeletable: false http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/views-changes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/views-changes.js b/app/addons/documents/views-changes.js deleted file mode 100644 index 416270d..0000000 --- a/app/addons/documents/views-changes.js +++ /dev/null @@ -1,66 +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", - - "api", - - // Libs - "addons/fauxton/components", - 'addons/documents/changes/components.react', - 'addons/documents/changes/actions' -], - -function (app, FauxtonAPI, Components, Changes, ChangesActions) { - - var Views = {}; - - - // wrappers for React components. The wrapper allows us to tie the React component into the Fauxton - // page load lifecycle - Views.ChangesHeaderReactWrapper = FauxtonAPI.View.extend({ - afterRender: function () { - Changes.renderHeader(this.el); - }, - cleanup: function () { - Changes.remove(this.el); - } - }); - - - Views.ChangesReactWrapper = FauxtonAPI.View.extend({ - initialize: function () { - this.filters = []; - }, - - afterRender: function () { - ChangesActions.setChanges({ - changes: this.model.changes, - filters: this.filters, - databaseName: this.model.id - }); - Changes.renderChanges(this.el); - }, - - establish: function () { - return [this.model.changes.fetchOnce({ prefill: true })]; - }, - - cleanup: function () { - Changes.remove(this.el); - } - }); - - - return Views; -}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/addons/documents/views-index.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/views-index.js b/app/addons/documents/views-index.js deleted file mode 100644 index e64963f..0000000 --- a/app/addons/documents/views-index.js +++ /dev/null @@ -1,54 +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([ - "api", - "addons/documents/index-editor/components.react", - "addons/documents/index-editor/actions", - 'addons/documents/index-results/index-results.components.react' -], - -function (FauxtonAPI, ViewEditor, ActionsIndexEditor, ViewResultList) { - - var Views = {}; - - Views.ViewEditorReact = FauxtonAPI.View.extend({ - initialize: function (options) { - this.options = options; - }, - - afterRender: function () { - ActionsIndexEditor.editIndex(this.options); - ViewEditor.renderEditor(this.el); - }, - - cleanup: function () { - ViewEditor.removeEditor(this.el); - } - }); - - Views.ViewResultListReact = FauxtonAPI.View.extend({ - initialize: function (options) { - this.options = options; - }, - - afterRender: function () { - ViewResultList.renderViewResultList(this.el); - }, - - cleanup: function () { - ViewResultList.removeViewResultList(this.el); - } - }); - - return Views; -}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/core/auth.js ---------------------------------------------------------------------- diff --git a/app/core/auth.js b/app/core/auth.js index af2e377..d31ef5e 100644 --- a/app/core/auth.js +++ b/app/core/auth.js @@ -62,4 +62,3 @@ function (FauxtonAPI, Backbone) { return Auth; }); - http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/core/layout.js ---------------------------------------------------------------------- diff --git a/app/core/layout.js b/app/core/layout.js index 14d1518..7a4d6f0 100644 --- a/app/core/layout.js +++ b/app/core/layout.js @@ -11,8 +11,8 @@ // the License. define([ - "backbone", - "plugins/backbone.layoutmanager" + 'backbone', + 'plugins/backbone.layoutmanager' ], function (Backbone) { // A wrapper of the main Backbone.layoutmanager @@ -24,6 +24,7 @@ define([ }); this.layoutViews = {}; + this.reactComponents = {}; //this views don't ever get removed. An example of this is the main navigation sidebar this.permanentViews = {}; this.el = this.layout.el; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/core/routeObject.js ---------------------------------------------------------------------- diff --git a/app/core/routeObject.js b/app/core/routeObject.js index 6dae4a4..0af072a 100644 --- a/app/core/routeObject.js +++ b/app/core/routeObject.js @@ -11,13 +11,15 @@ // the License. define([ - "core/base", - "backbone" + 'core/base', + 'react', + 'backbone' ], -function (FauxtonAPI, Backbone) { +function (FauxtonAPI, React, Backbone) { var RouteObject = function (options) { this._options = options; + this.reactComponents = {}; this._configure(options || {}); this.initialize.apply(this, arguments); @@ -101,11 +103,13 @@ function (FauxtonAPI, Backbone) { establishError = _.bind(this.establishError, this), renderComplete = _.bind(this.renderComplete, this), callEstablish = _.bind(this.callEstablish, this), + renderReactComponents = _.bind(this.renderReactComponents, this), promise = this.establish(); // Only start the view rendering process once the template has been rendered // otherwise we get double renders promiseLayout.then(function () { + renderReactComponents(); callEstablish(promise) .then(renderAllViews, establishError) .then(renderComplete); @@ -127,6 +131,12 @@ function (FauxtonAPI, Backbone) { return promise; }, + renderReactComponents: function () { + _.each(this.reactComponents, function (component, selector) { + React.render(React.createElement(component, null), $(selector)[0]); + }); + }, + callEstablish: function (establishPromise) { this.addPromise(establishPromise); return FauxtonAPI.when(establishPromise); @@ -229,10 +239,34 @@ function (FauxtonAPI, Backbone) { }, setView: function (selector, view) { + this.removeView(selector); + this.removeComponent(selector); this.views[selector] = view; return view; }, + setComponent: function (selector, component) { + this.removeView(selector); + this.removeComponent(selector); + this.reactComponents[selector] = component; + }, + + removeComponent: function (selector) { + if (_.has(this.reactComponents, selector)) { + React.unmountComponentAtNode($(selector)[0]); + this.reactComponents[selector] = null; + delete this.reactComponents[selector]; + } + }, + + removeComponents: function () { + _.each(this.reactComponents, function (component, selector) { + this.removeComponent(selector); + }, this); + + this.reactComponents = {}; + }, + getViews: function () { return this.views; }, @@ -246,6 +280,7 @@ function (FauxtonAPI, Backbone) { }, removeViews: function () { + this.reactComponents = {}; _.each(this.views, function (view, selector) { view.remove(); delete this.views[selector]; @@ -267,6 +302,7 @@ function (FauxtonAPI, Backbone) { cleanup: function () { this.removeViews(); + this.removeComponents(); this.rejectPromises(); }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/core/router.js ---------------------------------------------------------------------- diff --git a/app/core/router.js b/app/core/router.js index f6143b2..db37fd5 100644 --- a/app/core/router.js +++ b/app/core/router.js @@ -119,4 +119,3 @@ function (FauxtonAPI, Auth, Backbone) { return Router; }); - http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/02a5ee3f/app/core/tests/routeObjectSpec.js ---------------------------------------------------------------------- diff --git a/app/core/tests/routeObjectSpec.js b/app/core/tests/routeObjectSpec.js index b454d14..e267f65 100644 --- a/app/core/tests/routeObjectSpec.js +++ b/app/core/tests/routeObjectSpec.js @@ -10,11 +10,13 @@ // License for the specific language governing permissions and limitations under // the License. define([ - 'api', - 'testUtils' -], function (FauxtonAPI, testUtils) { + 'api', + 'react', + 'testUtils' +], function (FauxtonAPI, React, testUtils) { var assert = testUtils.assert, - RouteObject = FauxtonAPI.RouteObject; + restore = testUtils.restore, + RouteObject = FauxtonAPI.RouteObject; describe('RouteObjects', function () { @@ -49,7 +51,7 @@ define([ it('Should set template for first render ', function () { var setTemplateSpy = sinon.stub(mockLayout, 'setTemplate'), - promise = $.Deferred(); + promise = $.Deferred(); promise.resolve(); setTemplateSpy.returns(promise); @@ -60,7 +62,7 @@ define([ it('Should not set template after first render', function () { var setTemplateSpy = sinon.stub(mockLayout, 'setTemplate'), - promise = $.Deferred(); + promise = $.Deferred(); promise.resolve(); setTemplateSpy.returns(promise); @@ -71,6 +73,12 @@ define([ assert.ok(setTemplateSpy.calledOnce, 'SetTemplate not meant to be called'); }); + it('Should call renderReactComponents', function () { + var renderSpy = sinon.spy(testRouteObject, "renderReactComponents"); + + testRouteObject.renderWith('the-route', mockLayout, 'args'); + assert.ok(renderSpy.calledOnce); + }); it("Should call establish of routeObject", function () { var establishSpy = sinon.spy(testRouteObject, "establish"); @@ -81,8 +89,8 @@ define([ it("Should render views", function () { var view = new FauxtonAPI.View(), - getViewsSpy = sinon.stub(testRouteObject, "getViews"), - viewSpy = sinon.stub(view, "establish"); + getViewsSpy = sinon.stub(testRouteObject, "getViews"), + viewSpy = sinon.stub(view, "establish"); view.hasRendered = false; view.promise = function () { @@ -99,8 +107,8 @@ define([ it("Should not re-render a view", function () { var view = new FauxtonAPI.View(), - getViewsSpy = sinon.stub(testRouteObject, "getViews"), - viewSpy = sinon.stub(view, "establish"); + getViewsSpy = sinon.stub(testRouteObject, "getViews"), + viewSpy = sinon.stub(view, "establish"); view.hasRendered = true; getViewsSpy.returns({'#view': view}); @@ -110,6 +118,111 @@ define([ }); }); + describe('React Integration', function () { + var testRouteObject; + + beforeEach(function () { + var TestRouteObject = RouteObject.extend({ + crumbs: ['mycrumbs'] + }); + + testRouteObject = new TestRouteObject(); + var apiBar = {}; + //apiBar.hide = sinon.spy(); + + var mockLayout = { + setTemplate: function () { + var promise = $.Deferred(); + promise.resolve(); + return promise; + }, + clearBreadcrumbs: sinon.spy(), + setView: sinon.spy(), + renderView: sinon.spy(), + hooks: [], + setBreadcrumbs: sinon.spy(), + apiBar: apiBar + }; + + }); + + describe('setComponent', function () { + + afterEach(function () { + restore(testRouteObject.removeComponent); + restore(testRouteObject.removeView); + }); + + it('removes existing view for selector', function () { + var fakeReactComponent = React.createElement('div'); + var fakeSelector = '.fake-selector'; + var spy = sinon.spy(testRouteObject, 'removeView'); + + testRouteObject.setComponent(fakeSelector, fakeReactComponent); + + assert.ok(spy.calledWith(fakeSelector)); + }); + + it('removes existing component for selector', function () { + var fakeReactComponent = React.createElement('div'); + var fakeSelector = '.fake-selector'; + var spy = sinon.spy(testRouteObject, 'removeComponent'); + + testRouteObject.setComponent(fakeSelector, fakeReactComponent); + + assert.ok(spy.calledWith(fakeSelector)); + }); + + it('sets component for selector', function () { + var fakeReactComponent = React.createElement('div'); + var fakeSelector = '.fake-selector'; + + testRouteObject.setComponent(fakeSelector, fakeReactComponent); + assert.deepEqual(fakeReactComponent, testRouteObject.reactComponents[fakeSelector]); + }); + + }); + + describe('removeComponent', function () { + + afterEach(function () { + restore(React.unmountComponentAtNode); + }); + + it('removes existing component via react', function () { + var spy = sinon.spy(React, 'unmountComponentAtNode'); + var fakeSelector = 'remove-selector'; + testRouteObject.reactComponents[fakeSelector] = React.createElement('div'); + + testRouteObject.removeComponent(fakeSelector); + + assert.ok(spy.calledOnce); + + }); + + it('removes existing components key', function () { + var fakeSelector = 'remove-selector'; + testRouteObject.reactComponents[fakeSelector] = React.createElement('div'); + + testRouteObject.removeComponent(fakeSelector); + + assert.ok(_.isUndefined(testRouteObject.reactComponents[fakeSelector])); + + }); + + it('does nothing for non existing component', function () { + var spy = sinon.spy(React, 'unmountComponentAtNode'); + var fakeSelector = 'remove-selector'; + + testRouteObject.removeComponent(fakeSelector); + + assert.notOk(spy.calledOnce); + + }); + + }); + }); + });
