Create Index Results This converts the AllDocsList backbone view to React.
Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/96d31bc7 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/96d31bc7 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/96d31bc7 Branch: refs/heads/master Commit: 96d31bc7d0e7101c5a364fbaab2524d1e626e3d5 Parents: 2bc72f2 Author: Garren Smith <[email protected]> Authored: Thu Mar 5 15:58:18 2015 +0200 Committer: Garren Smith <[email protected]> Committed: Thu Mar 26 12:39:57 2015 +0200 ---------------------------------------------------------------------- .travis.yml | 2 +- .../components/react-components.react.jsx | 17 +- app/addons/components/tests/docSpec.react.jsx | 8 +- .../tests/nightwatch/checkDatabaseTooltip.js | 2 +- app/addons/documents/header/header.actions.js | 26 +- .../documents/header/header.actiontypes.js | 6 +- app/addons/documents/header/header.react.jsx | 62 +-- app/addons/documents/header/header.stores.js | 75 +--- app/addons/documents/index-editor/actions.js | 7 +- app/addons/documents/index-results/actions.js | 151 ++++++++ .../documents/index-results/actiontypes.js | 21 ++ .../index-results.components.react.jsx | 120 +++++- app/addons/documents/index-results/stores.js | 270 +++++++++++++ .../tests/index-results.actionsSpec.js | 232 ++++++++++++ .../index-results.componentsSpec.react.jsx | 31 ++ .../tests/index-results.storesSpec.js | 372 ++++++++++++++++++ app/addons/documents/pagination/actions.js | 32 +- app/addons/documents/pagination/actiontypes.js | 2 - app/addons/documents/pagination/stores.js | 22 +- .../pagination/tests/pagination.actionsSpec.js | 75 ++-- .../pagination/tests/paginationStoreSpec.js | 68 +++- app/addons/documents/resources.js | 16 +- app/addons/documents/routes-documents.js | 375 +++++++++---------- app/addons/documents/routes-index-editor.js | 35 +- app/addons/documents/shared-routes.js | 71 ---- app/addons/documents/tests/actionsSpec.js | 14 +- app/addons/documents/tests/headerSpec.react.jsx | 39 +- .../tests/nightwatch/deletesDocument.js | 3 +- .../documents/tests/nightwatch/paginateView.js | 14 +- app/addons/documents/tests/resourcesSpec.js | 136 ++++--- app/addons/documents/tests/routeSpec.js | 11 - app/addons/documents/tests/viewsSpec.js | 29 -- app/addons/documents/views.js | 260 +------------ assets/less/animations.less | 1 - assets/less/react-animations.less | 18 + test/mocha/testUtils.js | 9 +- 36 files changed, 1781 insertions(+), 851 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/.travis.yml ---------------------------------------------------------------------- diff --git a/.travis.yml b/.travis.yml index 21e7bbe..0bab741 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ before_script: - npm install -g grunt-cli - grunt test - grunt dev & - - sleep 5 + - sleep 10 script: - grunt nightwatch notifications: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/components/react-components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/react-components.react.jsx b/app/addons/components/react-components.react.jsx index b5727f9..a21c794 100644 --- a/app/addons/components/react-components.react.jsx +++ b/app/addons/components/react-components.react.jsx @@ -191,7 +191,13 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) { var Document = React.createClass({ propTypes: { - docIdentifier: React.PropTypes.string.isRequired + docIdentifier: React.PropTypes.string.isRequired, + docChecked: React.PropTypes.func.isRequired + }, + + onChange: function (e) { + e.preventDefault(); + this.props.docChecked(this.props.docIdentifier, this.props.doc, e); }, getUrlFragment: function () { @@ -213,19 +219,22 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) { id={'checkbox-' + this.props.docIdentifier} checked={this.props.checked ? 'checked="checked"': null} type="checkbox" - onChange={this.props.onChange} + onChange={this.onChange} className="js-row-select" /> - <label + <label onClick={this.onChange} className="label-checkbox-doclist" htmlFor={'checkbox-' + this.props.docIdentifier} /> </div> ); }, + onDoubleClick: function (e) { + this.props.onDoubleClick(this.props.docIdentifier, this.props.doc, e); + }, render: function () { return ( - <div onDoubleClick={this.props.onDoubleClick} className="doc-row"> + <div data-id={this.props.docIdentifier} onDoubleClick={this.onDoubleClick} className="doc-row"> <div className="custom-inputs"> {this.getCheckbox()} </div> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/components/tests/docSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/tests/docSpec.react.jsx b/app/addons/components/tests/docSpec.react.jsx index ce3a94c..119abe6 100644 --- a/app/addons/components/tests/docSpec.react.jsx +++ b/app/addons/components/tests/docSpec.react.jsx @@ -54,7 +54,7 @@ define([ <ReactComponents.Document checked={true} docIdentifier="foo" />, container ); - assert.equal($(el.getDOMNode()).find('#checkbox-foo').attr('checked'), 'checked'); + assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), 'checked'); }); it('you can uncheck it', function () { @@ -62,17 +62,17 @@ define([ <ReactComponents.Document docIdentifier="foo" />, container ); - assert.equal($(el.getDOMNode()).find('#checkbox-foo').attr('checked'), undefined); + assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), undefined); }); it('it calls an onchange callback', function () { var spy = sinon.spy(); el = TestUtils.renderIntoDocument( - <ReactComponents.Document onChange={spy} docIdentifier="foo" />, + <ReactComponents.Document docChecked={spy} docIdentifier="foo" />, container ); - var testEl = $(el.getDOMNode()).find('#checkbox-foo')[0]; + var testEl = $(el.getDOMNode()).find('input[type="checkbox"]')[0]; React.addons.TestUtils.Simulate.change(testEl, {target: {value: 'Hello, world'}}); assert.ok(spy.calledOnce); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js ---------------------------------------------------------------------- diff --git a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js index 3808338..dc25f6a 100644 --- a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js +++ b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js @@ -34,7 +34,7 @@ module.exports = { .waitForElementPresent('.control-select-all', waitTime, false) .click('.control-delete') .acceptAlert() - .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false) + .waitForElementVisible('.alert.alert-info', waitTime, false) .click('#nav-links a[href="#/_all_dbs"]') // now let's look at the actual UI to confirm the tooltip appears http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.actions.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/header/header.actions.js b/app/addons/documents/header/header.actions.js index 7be2a05..5a6dfe0 100644 --- a/app/addons/documents/header/header.actions.js +++ b/app/addons/documents/header/header.actions.js @@ -18,31 +18,37 @@ define([ function (app, FauxtonAPI, ActionTypes) { return { - toggleCollapseDocuments: function () { + collapseDocuments: function () { FauxtonAPI.dispatch({ type: ActionTypes.COLLAPSE_DOCUMENTS }); - FauxtonAPI.Events.trigger('headerbar:collapse'); }, - toggleSelectAllDocuments: function (on) { - FauxtonAPI.Events.trigger('headerbar:selectall', on); + unCollapseDocuments: function () { + FauxtonAPI.dispatch({ + type: ActionTypes.EXPAND_DOCUMENTS + }); + }, - updateDocumentCount: function (options) { + selectAllDocuments: function () { FauxtonAPI.dispatch({ - type: ActionTypes.UPDATE_DOCUMENT_COUNT, - options: options + type: ActionTypes.SELECT_ALL_DOCUMENTS }); }, - deleteSelected: function () { + deSelectAllDocuments: function () { FauxtonAPI.dispatch({ - type: ActionTypes.DELETE_SELECTED + type: ActionTypes.DESELECT_ALL_DOCUMENTS }); + }, - FauxtonAPI.Events.trigger('headerbar:deleteselected'); + updateDocumentCount: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.UPDATE_DOCUMENT_COUNT, + options: options + }); }, toggleHeaderControls: function () { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.actiontypes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/header/header.actiontypes.js b/app/addons/documents/header/header.actiontypes.js index 180dabb..18cc41f 100644 --- a/app/addons/documents/header/header.actiontypes.js +++ b/app/addons/documents/header/header.actiontypes.js @@ -14,9 +14,11 @@ define([], function () { return { UPDATE_DOCUMENT_COUNT: 'UPDATE_DOCUMENT_COUNT', COLLAPSE_DOCUMENTS: 'COLLAPSE_DOCUMENTS', + EXPAND_DOCUMENTS: 'UNCOLLAPSE_DOCUMENTS', TOGGLE_HEADER_CONTROLS: 'TOGGLE_HEADER_CONTROLS', RESET_HEADER_BAR: 'RESET_HEADER_BAR', - DELETE_SELECTED_DOCUMENTS: 'DELETE_SELECTED_DOCUMENTS' + DELETE_SELECTED_DOCUMENTS: 'DELETE_SELECTED_DOCUMENTS', + SELECT_ALL_DOCUMENTS: 'SELECT_ALL_DOCUMENTS', + DESELECT_ALL_DOCUMENTS: 'DESELECT_ALL_DOCUMENTS' }; }); - http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/header/header.react.jsx b/app/addons/documents/header/header.react.jsx index 5906974..a5a1f5d 100644 --- a/app/addons/documents/header/header.react.jsx +++ b/app/addons/documents/header/header.react.jsx @@ -17,20 +17,24 @@ define([ 'addons/documents/header/header.stores', 'addons/documents/header/header.actions', 'addons/components/react-components.react', + 'addons/documents/index-results/stores', + 'addons/documents/index-results/actions', ], -function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { +function (app, FauxtonAPI, React, Stores, Actions, ReactComponents, IndexResultsStore, IndexResultsActions) { var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; var headerBarStore = Stores.headerBarStore; var bulkDocumentHeaderStore = Stores.bulkDocumentHeaderStore; + var indexResultsStore = IndexResultsStore.indexResultsStore; var ToggleHeaderButton = ReactComponents.ToggleHeaderButton; var BulkDocumentHeaderController = React.createClass({ getStoreState: function () { return { - areDocumentsCollapsed: bulkDocumentHeaderStore.getCollapsedState(), - isDeselectPossible: bulkDocumentHeaderStore.getIsDeselectPossible(), - isSelectAllPossible: bulkDocumentHeaderStore.getIsSelectAllPossible() + canCollapseDocs: indexResultsStore.canCollapseDocs(), + canUncollapseDocs: indexResultsStore.canUncollapseDocs(), + canDeselectAll: indexResultsStore.canDeselectAll(), + canSelectAll: indexResultsStore.canSelectAll() }; }, @@ -39,11 +43,11 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { }, componentDidMount: function () { - bulkDocumentHeaderStore.on('change', this.onChange, this); + indexResultsStore.on('change', this.onChange, this); }, componentWillUnmount: function () { - bulkDocumentHeaderStore.off('change', this.onChange); + indexResultsStore.off('change', this.onChange); }, onChange: function () { @@ -52,9 +56,10 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { render: function () { var baseClass = 'header-control-box header-control-square ', - isDeselectPossible = this.state.isDeselectPossible, - isSelectAllPossible = this.state.isSelectAllPossible, - areDocumentsCollapsed = this.state.areDocumentsCollapsed; + canDeselectAll = this.state.canDeselectAll, + canSelectAll = this.state.canSelectAll, + canCollapseDocs = this.state.canCollapseDocs, + canUncollapseDocs = this.state.canUncollapseDocs; return ( <div className='alternative-header'> @@ -64,8 +69,8 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { innerClasses={''} containerClasses={baseClass + 'control-select-all'} text={''} - setEnabledClass={!isSelectAllPossible} - disabled={!isSelectAllPossible} + setEnabledClass={!canSelectAll} + disabled={!canSelectAll} title={'Select all Documents'} /> <ToggleHeaderButton @@ -74,29 +79,30 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { innerClasses={''} containerClasses={baseClass + 'control-de-select-all'} text={''} - setEnabledClass={!isDeselectPossible} - disabled={!isDeselectPossible} + setEnabledClass={!canDeselectAll} + disabled={!canDeselectAll} title={'Deselect all Documents'} /> + <ToggleHeaderButton fonticon={'fonticon-collapse'} - toggleCallback={this.toggleCollapseDocuments} + toggleCallback={this.collapseDocuments} innerClasses={''} containerClasses={baseClass + 'control-collapse'} text={''} - setEnabledClass={areDocumentsCollapsed} - disabled={areDocumentsCollapsed} - title={'Collapse all'} /> + setEnabledClass={!canCollapseDocs} + disabled={!canCollapseDocs} + title={'Collapse Selected'} /> <ToggleHeaderButton fonticon={'fonticon-expand'} - toggleCallback={this.toggleCollapseDocuments} + toggleCallback={this.unCollapseDocuments} innerClasses={''} containerClasses={baseClass + 'control-expand'} text={''} - setEnabledClass={!areDocumentsCollapsed} - disabled={!areDocumentsCollapsed} - title={'Expand all'} /> + setEnabledClass={!canUncollapseDocs} + disabled={!canUncollapseDocs} + title={'Expand Selected'} /> <ToggleHeaderButton fonticon={'fonticon-trash'} @@ -117,16 +123,20 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { ); }, - toggleCollapseDocuments: function () { - Actions.toggleCollapseDocuments(); + collapseDocuments: function () { + Actions.collapseDocuments(); + }, + + unCollapseDocuments: function () { + Actions.unCollapseDocuments(); }, selectAllDocuments: function () { - Actions.toggleSelectAllDocuments(false); + Actions.selectAllDocuments(); }, deSelectAllDocuments: function () { - Actions.toggleSelectAllDocuments(true); + Actions.deSelectAllDocuments(); }, cancelView: function () { @@ -134,7 +144,7 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { }, deleteSelected: function () { - Actions.deleteSelected(); + IndexResultsActions.deleteSelected(); } }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/header/header.stores.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/header/header.stores.js b/app/addons/documents/header/header.stores.js index 4085b0e..bc57a34 100644 --- a/app/addons/documents/header/header.stores.js +++ b/app/addons/documents/header/header.stores.js @@ -12,79 +12,13 @@ define([ 'api', - 'addons/documents/header/header.actiontypes' + 'addons/documents/header/header.actiontypes', + 'addons/documents/index-results/actiontypes' ], -function (FauxtonAPI, ActionTypes) { +function (FauxtonAPI, ActionTypes, IndexResultsActions) { var Stores = {}; - Stores.BulkDocumentHeaderStore = FauxtonAPI.Store.extend({ - initialize: function () { - this.reset(); - }, - - reset: function () { - this._collapsedDocuments = false; - this._selectedDocumentsCount = 0; - this._documentsOnPageCount = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE; - }, - - toggleCollapse: function () { - this._collapsedDocuments = !this._collapsedDocuments; - }, - - getCollapsedState: function () { - return this._collapsedDocuments; - }, - - getSelectedAllState: function () { - return this._selectedAllDocuments; - }, - - getIsDeselectPossible: function () { - if (this._selectedDocumentsonPageCount > 0) { - return true; - } - return false; - }, - - getIsSelectAllPossible: function () { - if (this._selectedDocumentsonPageCount < this._documentsOnPageCount) { - return true; - } - return false; - }, - - setSelectedDocumentCount: function (options) { - this._selectedDocumentsonPageCount = options.selectedOnPage; - this._documentsOnPageCount = options.documentsOnPageCount; - }, - - dispatch: function (action) { - switch (action.type) { - - case ActionTypes.COLLAPSE_DOCUMENTS: - this.toggleCollapse(); - this.triggerChange(); - break; - - case ActionTypes.UPDATE_DOCUMENT_COUNT: - this.setSelectedDocumentCount(action.options); - this.triggerChange(); - break; - - case ActionTypes.RESET_HEADER_BAR: - this.reset(); - this.triggerChange(); - break; - - default: - return; - } - } - - }); - Stores.HeaderBarStore = FauxtonAPI.Store.extend({ initialize: function (options) { this.reset(); @@ -137,8 +71,5 @@ function (FauxtonAPI, ActionTypes) { Stores.headerBarStore = new Stores.HeaderBarStore(); Stores.headerBarStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.headerBarStore.dispatch); - Stores.bulkDocumentHeaderStore = new Stores.BulkDocumentHeaderStore(); - Stores.bulkDocumentHeaderStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.bulkDocumentHeaderStore.dispatch); - return Stores; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/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 263849b..84bf535 100644 --- a/app/addons/documents/index-editor/actions.js +++ b/app/addons/documents/index-editor/actions.js @@ -14,9 +14,10 @@ define([ 'app', 'api', 'addons/documents/resources', - 'addons/documents/index-editor/actiontypes' + 'addons/documents/index-editor/actiontypes', + 'addons/documents/index-results/actions' ], -function (app, FauxtonAPI, Documents, ActionTypes) { +function (app, FauxtonAPI, Documents, ActionTypes, IndexResultsActions) { var ActionHelpers = { createNewDesignDoc: function (id, database) { var designDoc = { @@ -125,7 +126,7 @@ function (app, FauxtonAPI, Documents, ActionTypes) { FauxtonAPI.navigate(fragment, {trigger: true}); } - FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: designDoc.id, view: viewInfo.viewName}); + IndexResultsActions.reloadResultsList(); }); } }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/actions.js b/app/addons/documents/index-results/actions.js new file mode 100644 index 0000000..3e882eb --- /dev/null +++ b/app/addons/documents/index-results/actions.js @@ -0,0 +1,151 @@ +// 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/documents/index-results/actiontypes', + 'addons/documents/index-results/stores', + 'addons/documents/header/header.stores', + 'addons/documents/header/header.actions', + 'addons/documents/resources' +], +function (app, FauxtonAPI, ActionTypes, Stores, HeaderStores, HeaderActions, Documents) { + var indexResultsStore = Stores.indexResultsStore; + var headerBarStore = HeaderStores.headerBarStore; + + var errorMessage = function (ids) { + var msg = 'Failed to delete your document!'; + + if (ids) { + msg = 'Failed to delete: ' + ids.join(', '); + } + + FauxtonAPI.addNotification({ + msg: msg, + type: 'error', + clear: true + }); + }; + + return { + newResultsList: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.INDEX_RESULTS_NEW_RESULTS, + options: options + }); + + if (!options.collection.fetch) { return; } + + return options.collection.fetch({reset: true}).then(function () { + this.resultsListReset(); + }.bind(this), function (xhr) { + // TODO: handle error requests that slip through + // This should just throw a notification, not break the page + var errorMsg = 'Bad Request'; + + try { + var responseText = JSON.parse(xhr.responseText); + if (responseText.reason) { + errorMsg = responseText.reason; + } + } catch (e) { + console.log(e); + } + + FauxtonAPI.addNotification({ + msg: errorMsg, + type: "error", + clear: true + }); + }); + }, + + reloadResultsList: function () { + return this.newResultsList({ + collection: indexResultsStore.getCollection(), + deleteable: indexResultsStore.isDeleteable() + }); + }, + + resultsListReset: function () { + FauxtonAPI.dispatch({ + type: ActionTypes.INDEX_RESULTS_RESET + }); + }, + + selectDoc: function (id) { + FauxtonAPI.dispatch({ + type: ActionTypes.INDEX_RESULTS_SELECT_DOC, + id: id + }); + + //show menu + if (!headerBarStore.getToggleStatus()) { + HeaderActions.toggleHeaderControls(); + return; + } + + //hide menu + if (headerBarStore.getToggleStatus() && indexResultsStore.getSelectedItemsLength() === 0) { + HeaderActions.toggleHeaderControls(); + } + }, + + selectListOfDocs: function (ids) { + FauxtonAPI.dispatch({ + type: ActionTypes.INDEX_RESULTS_SELECT_LIST_OF_DOCS, + ids: ids + }); + }, + + clearResults: function () { + FauxtonAPI.dispatch({ + type: ActionTypes.INDEX_RESULTS_CLEAR_RESULTS + }); + }, + + deleteSelected: function () { + var itemsLength = indexResultsStore.getSelectedItemsLength(); + var msg = "Are you sure you want to delete these " + itemsLength + " docs?"; + + if (itemsLength === 0 || !window.confirm(msg)) { + return false; + } + + var reloadResultsList = _.bind(this.reloadResultsList, this); + var selectListOfDocs = _.bind(this.selectListOfDocs, this); + var selectedIds = []; + + indexResultsStore.createBulkDeleteFromSelected().bulkDelete() + .then(function (ids) { + FauxtonAPI.addNotification({ + msg: 'Successfully deleted your docs', + clear: true + }); + + if (!_.isEmpty(ids.errorIds)) { + errorMessage(ids.errorIds); + selectedIds = ids.errorIds; + } + }, function (ids) { + errorMessage(ids); + selectedIds = ids; + }) + .always(function () { + reloadResultsList().then(function () { + selectListOfDocs(selectedIds); + }); + }); + } + }; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/actiontypes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/actiontypes.js b/app/addons/documents/index-results/actiontypes.js new file mode 100644 index 0000000..72157c7 --- /dev/null +++ b/app/addons/documents/index-results/actiontypes.js @@ -0,0 +1,21 @@ +// 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([], function () { + return { + INDEX_RESULTS_NEW_RESULTS: 'INDEX_RESULTS_NEW_RESULTS', + INDEX_RESULTS_RESET: 'INDEX_RESULTS_RESET', + INDEX_RESULTS_SELECT_DOC: 'INDEX_RESULTS_SELECT_DOC', + INDEX_RESULTS_SELECT_LIST_OF_DOCS: 'INDEX_RESULTS_SELECT_LIST_OF_DOCS', + INDEX_RESULTS_CLEAR_RESULTS: 'INDEX_RESULTS_CLEAR_RESULTS' + }; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/index-results.components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/index-results.components.react.jsx b/app/addons/documents/index-results/index-results.components.react.jsx index 8c77739..92fc018 100644 --- a/app/addons/documents/index-results/index-results.components.react.jsx +++ b/app/addons/documents/index-results/index-results.components.react.jsx @@ -13,10 +13,18 @@ define([ 'app', 'api', - 'react' + 'react', + 'addons/documents/index-results/stores', + 'addons/documents/index-results/actions', + 'addons/components/react-components.react', + 'addons/documents/resources', + + "plugins/prettify" ], -function (app, FauxtonAPI, React) { +function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { + var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; + var store = Stores.indexResultsStore; var NoResultScreen = React.createClass({ render: function () { @@ -28,10 +36,118 @@ function (app, FauxtonAPI, React) { } }); + var ResultsScreen = React.createClass({ + + onDoubleClick: function (id, doc) { + FauxtonAPI.navigate(doc.url); + }, + + getUrlFragment: function (url) { + if (this.props.hasReduce) { + return null; + } + + + return ( + <a href={url}> + <i className="fonticon-pencil"></i> + </a>); + }, + + getDocumentList: function () { + return _.map(this.props.results, function (doc) { + return ( + <Components.Document + key={doc.id} + doc={doc} + onDoubleClick={this.onDoubleClick} + keylabel={doc.keylabel} + docContent={doc.content} + checked={this.props.isSelected(doc.id)} + docChecked={this.props.docChecked} + docIdentifier={doc.id} > + {this.getUrlFragment('#' + doc.url)} + </Components.Document> + ); + }, this); + }, + + render: function () { + var classNames = 'view'; + + if (this.props.isDeleteable) { + classNames += ' show-select'; + } + + return ( + <div className={classNames}> + <div id="doc-list"> + <ReactCSSTransitionGroup transitionName={'slow-fade'}> + {this.getDocumentList()} + </ReactCSSTransitionGroup> + </div> + </div> + ); + }, + + componentDidMount: function () { + prettyPrint(); + }, + + componentDidUpdate: function () { + prettyPrint(); + }, + + }); + var ViewResultListController = React.createClass({ + getStoreState: function () { + return { + hasResults: store.hasResults(), + results: store.getResults(), + isDeleteable: store.isDeleteable(), + isSelected: store.isSelected, + hasReduce: store.hasReduce() + }; + }, + + isSelected: function (id) { + return this.state.isSelected(id); + }, + + getInitialState: function () { + return this.getStoreState(); + }, + + componentDidMount: function () { + store.on('change', this.onChange, this); + }, + + componentWillUnmount: function () { + store.off('change', this.onChange); + }, + + onChange: function () { + this.setState(this.getStoreState()); + }, + + docChecked: function (id) { + Actions.selectDoc(id); + }, + render: function () { var view = <NoResultScreen />; + if (this.state.hasResults) { + view = <ResultsScreen + isCollapsed={this.isCollapsed} + isSelected={this.isSelected} + hasReduce={this.state.hasReduce} + isDeleteable={this.state.isDeleteable} + docChecked={this.docChecked} + results={this.state.results} />; + } + return ( view ); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/stores.js b/app/addons/documents/index-results/stores.js new file mode 100644 index 0000000..1f2382d --- /dev/null +++ b/app/addons/documents/index-results/stores.js @@ -0,0 +1,270 @@ +// 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-results/actiontypes', + 'addons/documents/header/header.actiontypes', + "addons/documents/resources" +], + +function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { + var Stores = {}; + + /*TODO: + remove header code, add delete, clean up pagination tests + */ + + Stores.IndexResultsStore = FauxtonAPI.Store.extend({ + + initialize: function () { + this._deleteable = false; + this._collection = []; + this.clearSelectedItems(); + this.clearCollapsedDocs(); + this._isLoading = false; + }, + + clearSelectedItems: function () { + this._selectedItems = {}; + }, + + clearCollapsedDocs: function () { + this._collapsedDocs = {}; + }, + + newResults: function (options) { + this._collection = options.collection; + this._deleteable = options.deleteable; + this.clearSelectedItems(); + this.clearCollapsedDocs(); + }, + + hasReduce: function () { + if (!this._collection || !this._collection.params) { + return false; + } + return this._collection.params.reduce; + }, + + getCollection: function () { + return this._collection; + }, + + getDocContent: function (originalDoc) { + var doc = originalDoc.toJSON(); + + if (this.isCollapsed(doc._id)) { + doc = { + _id: _.isUndefined(doc._id), + _rev: doc._rev + }; + } + + return JSON.stringify(doc, null, " "); + }, + + getDocId: function (doc) { + + if (!_.isUndefined(doc.id)) { + return doc.id; + } + + if (!_.isNull(doc.get('key'))) { + return doc.get('key').toString(); + } + + return ''; + }, + + getResults: function () { + return this._collection.map(function (doc) { + return { + content: this.getDocContent(doc), + id: this.getDocId(doc), + keylabel: doc.isFromView() ? 'key' : 'id', + url: doc.isFromView() ? doc.url('app') : doc.url('web-index') + }; + }, this); + }, + + hasResults: function () { + if (this._isLoading) { return this._isLoading; } + return this._collection.length > 0; + }, + + isDeleteable: function () { + return this._deleteable; + }, + + selectDoc: function (id) { + if (!this._selectedItems[id]) { + this._selectedItems[id] = true; + } else { + delete this._selectedItems[id]; + } + }, + + selectListOfDocs: function (ids) { + this.clearSelectedItems(); + _.each(ids, function (id) { + this.selectDoc(id); + }, this); + }, + + selectAllDocuments: function () { + this.clearSelectedItems(); + this._collection.each(function (doc) { + this.selectDoc(doc.id); + }, this); + }, + + deSelectAllDocuments: function () { + this.clearSelectedItems(); + }, + + getSelectedItemsLength: function () { + return _.keys(this._selectedItems).length; + }, + + getCollapsedDocsLength: function () { + return _.keys(this._collapsedDocs).length; + }, + + getCollapsedDocs: function () { + return this._collapsedDocs; + }, + + getDatabase: function () { + return this._collection.database; + }, + + createBulkDeleteFromSelected: function () { + var items = _.map(_.keys(this._selectedItems), function (id) { + var doc = this._collection.get(id); + + return { + _id: doc.id, + _rev: doc.get('_rev'), + _deleted: true + }; + }, this); + + var bulkDelete = new Documents.BulkDeleteDocCollection(items, { + databaseId: this.getDatabase().safeID() + }); + + return bulkDelete; + }, + + canSelectAll: function () { + return this._collection.length > this.getSelectedItemsLength(); + }, + + canDeselectAll: function () { + return this.getSelectedItemsLength() > 0; + }, + + getSelectedItems: function () { + return this._selectedItems; + }, + + canCollapseDocs: function () { + return this._collection.length > this.getCollapsedDocsLength(); + }, + + canUncollapseDocs: function () { + return this.getCollapsedDocsLength() > 0; + }, + + isSelected: function (id) { + return !!this._selectedItems[id]; + }, + + isCollapsed: function (id) { + return !!this._collapsedDocs[id]; + }, + + collapseSelectedDocs: function () { + _.each(this._selectedItems, function (val, key) { + this._collapsedDocs[key] = true; + }, this); + }, + + unCollapseSelectedDocs: function () { + _.each(this._selectedItems, function (val, key) { + delete this._collapsedDocs[key]; + }, this); + }, + + clearResultsBeforeFetch: function () { + this.getCollection().reset(); + this._isLoading = true; + }, + + resultsResetFromFetch: function () { + this._isLoading = false; + }, + + dispatch: function (action) { + switch (action.type) { + case ActionTypes.INDEX_RESULTS_NEW_RESULTS: + this.newResults(action.options); + this.triggerChange(); + break; + case ActionTypes.INDEX_RESULTS_RESET: + this.resultsResetFromFetch(); + this.triggerChange(); + break; + case ActionTypes.INDEX_RESULTS_SELECT_DOC: + this.selectDoc(action.id); + this.triggerChange(); + break; + case ActionTypes.INDEX_RESULTS_SELECT_LIST_OF_DOCS: + this.selectListOfDocs(action.ids); + this.triggerChange(); + break; + case ActionTypes.INDEX_RESULTS_CLEAR_RESULTS: + this.clearResultsBeforeFetch(); + this.triggerChange(); + break; + case HeaderActionTypes.SELECT_ALL_DOCUMENTS: + this.selectAllDocuments(); + this.triggerChange(); + break; + case HeaderActionTypes.DESELECT_ALL_DOCUMENTS: + this.deSelectAllDocuments(); + this.triggerChange(); + break; + case HeaderActionTypes.COLLAPSE_DOCUMENTS: + this.collapseSelectedDocs(); + this.triggerChange(); + break; + case HeaderActionTypes.EXPAND_DOCUMENTS: + this.unCollapseSelectedDocs(); + this.triggerChange(); + break; + default: + return; + // do nothing + } + } + + }); + + Stores.indexResultsStore = new Stores.IndexResultsStore(); + + Stores.indexResultsStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.indexResultsStore.dispatch); + + return Stores; + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/tests/index-results.actionsSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/tests/index-results.actionsSpec.js b/app/addons/documents/index-results/tests/index-results.actionsSpec.js new file mode 100644 index 0000000..5ec9aab --- /dev/null +++ b/app/addons/documents/index-results/tests/index-results.actionsSpec.js @@ -0,0 +1,232 @@ +// 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-results/actions', + 'addons/documents/index-results/stores', + 'addons/documents/header/header.stores', + 'addons/documents/header/header.actions', + 'addons/documents/resources', + 'testUtils', +], function (FauxtonAPI, Actions, Stores, HeaderStores, HeaderActions, Documents, testUtils) { + var assert = testUtils.assert; + var restore = testUtils.restore; + var store = Stores.indexResultsStore; + + FauxtonAPI.router = new FauxtonAPI.Router([]); + + describe('Index Results Actions', function () { + + describe('#newResultsList', function () { + + it('sends results list reset', function () { + var collection = { + fetch: function () { + var promise = $.Deferred(); + promise.resolve(); + return promise; + } + }; + + var spy = sinon.spy(Actions, 'resultsListReset'); + + Actions.newResultsList({collection: collection}); + assert.ok(spy.calledOnce); + }); + + }); + + }); + + describe('#selectDoc', function () { + afterEach(function () { + restore(HeaderStores.headerBarStore.getToggleStatus); + restore(HeaderActions.toggleHeaderControls); + }); + + it('toggles header controls if not active', function () { + var stub = sinon.stub(HeaderStores.headerBarStore, 'getToggleStatus'); + stub.returns(false); + + var spy = sinon.spy(HeaderActions, 'toggleHeaderControls'); + + Actions.selectDoc('id'); + assert.ok(spy.calledOnce); + }); + + it('does not toggles header controls if active', function () { + store.clearSelectedItems(); + var stub = sinon.stub(HeaderStores.headerBarStore, 'getToggleStatus'); + stub.returns(true); + + var spy = sinon.spy(HeaderActions, 'toggleHeaderControls'); + + Actions.selectDoc('id'); + assert.notOk(spy.calledOnce); + }); + + it('hides header control if active and no items selected', function () { + var stub = sinon.stub(HeaderStores.headerBarStore, 'getToggleStatus'); + stub.returns(true); + store._selectedItems = {'id': true}; + + var spy = sinon.spy(HeaderActions, 'toggleHeaderControls'); + + Actions.selectDoc('id'); + assert.ok(spy.calledOnce); + + }); + + }); + + describe('#deleteSelected', function () { + var confirmStub; + + beforeEach(function () { + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store._selectedItems = { + 'testId1': true, + 'testId2': true + }; + + confirmStub = sinon.stub(window, 'confirm'); + confirmStub.returns(true); + + }); + + afterEach(function () { + restore(window.confirm); + restore(store.createBulkDeleteFromSelected); + restore(FauxtonAPI.addNotification); + restore(Actions.reloadResultsList); + restore(Actions.selectListOfDocs); + }); + + it('doesn\'t delete if user denies confirmation', function () { + window.confirm.restore(); + + var stub = sinon.stub(window, 'confirm'); + stub.returns(false); + + var spy = sinon.spy(store, 'createBulkDeleteFromSelected'); + + Actions.deleteSelected(); + + assert.notOk(spy.calledOnce); + }); + + it('creates bulk delete', function () { + var spy = sinon.spy(store, 'createBulkDeleteFromSelected'); + + Actions.deleteSelected(); + + assert.ok(spy.calledOnce); + }); + + it('on success notifies all deleted', function () { + var spy = sinon.spy(FauxtonAPI, 'addNotification'); + var promise = FauxtonAPI.Deferred(); + var ids = { + errorIds: [] + }; + var bulkDelete = { + bulkDelete: function () { + promise.resolve(ids); + return promise; + } + }; + var stub = sinon.stub(store, 'createBulkDeleteFromSelected'); + stub.returns(bulkDelete); + + Actions.deleteSelected(); + + assert.ok(spy.calledOnce); + }); + + it('on success with some failed ids, re-selects failed', function () { + var spy = sinon.spy(Actions, 'selectListOfDocs'); + + var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList'); + var stubPromise = FauxtonAPI.Deferred(); + stubPromise.resolve(); + reloadResultsListStub.returns(stubPromise); + + var promise = FauxtonAPI.Deferred(); + var ids = { + errorIds: ['1'] + }; + var bulkDelete = { + bulkDelete: function () { + promise.resolve(ids); + return promise; + } + }; + + var stub = sinon.stub(store, 'createBulkDeleteFromSelected'); + stub.returns(bulkDelete); + + Actions.deleteSelected(); + assert.ok(spy.calledWith(ids.errorIds)); + }); + + it('on failure notifies failed', function () { + var spy = sinon.spy(FauxtonAPI, 'addNotification'); + var promise = FauxtonAPI.Deferred(); + var bulkDelete = { + bulkDelete: function () { + promise.reject(); + return promise; + } + }; + var stub = sinon.stub(store, 'createBulkDeleteFromSelected'); + stub.returns(bulkDelete); + + Actions.deleteSelected(); + + assert.ok(spy.calledOnce); + }); + + it('on failure re-selects docs', function () { + var spy = sinon.spy(Actions, 'selectListOfDocs'); + + var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList'); + var stubPromise = FauxtonAPI.Deferred(); + stubPromise.resolve(); + reloadResultsListStub.returns(stubPromise); + + var promise = FauxtonAPI.Deferred(); + var errorIds = ['1']; + + var bulkDelete = { + bulkDelete: function () { + promise.reject(errorIds); + return promise; + } + }; + + var stub = sinon.stub(store, 'createBulkDeleteFromSelected'); + stub.returns(bulkDelete); + + Actions.deleteSelected(); + assert.ok(spy.calledWith(errorIds)); + }); + + }); + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx new file mode 100644 index 0000000..4502921 --- /dev/null +++ b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx @@ -0,0 +1,31 @@ +// 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-results/index-results.components.react', + 'testUtils', + "react" +], function (FauxtonAPI, Views, utils, React) { + FauxtonAPI.router = new FauxtonAPI.Router([]); + + var assert = utils.assert; + var TestUtils = React.addons.TestUtils; + + describe('Index Results', function () { + var container; + + afterEach(function () { + React.unmountComponentAtNode(container); + }); + + }); +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/index-results/tests/index-results.storesSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/tests/index-results.storesSpec.js b/app/addons/documents/index-results/tests/index-results.storesSpec.js new file mode 100644 index 0000000..574082f --- /dev/null +++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js @@ -0,0 +1,372 @@ +// 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-results/stores', + 'addons/documents/index-results/actiontypes', + 'addons/documents/shared-resources', + 'testUtils' +], function (FauxtonAPI, Stores, ActionTypes, Documents, testUtils) { + var assert = testUtils.assert; + var dispatchToken; + var store; + + describe('Index Results Store', function () { + + beforeEach(function () { + store = new Stores.IndexResultsStore(); + dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch); + }); + + describe('#hasResults', function () { + + it('returns true for collection', function () { + store._collection = [1, 2, 3]; + + assert.ok(store.hasResults()); + }); + + it('returns false for empty collection', function () { + store._collection = []; + + assert.notOk(store.hasResults()); + }); + + }); + + describe('#getResults', function () { + + it('has correct doc format', function () { + store._collection = new Documents.AllDocs([{_id: 'testId'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + var doc = store.getResults()[0]; + assert.equal(doc.id, 'testId'); + assert.equal(doc.keylabel, 'id'); + }); + + }); + + afterEach(function () { + FauxtonAPI.dispatcher.unregister(dispatchToken); + }); + }); + + describe('canSelectAll', function () { + + it('returns true for selected docs less than collection', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store._selectedItems = {'testId1': true}; + assert.ok(store.canSelectAll()); + }); + + it('returns false for selected docs same as collection', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store._selectedItems = { + 'testId1': true, + 'testId2': true + }; + + assert.notOk(store.canSelectAll()); + }); + + }); + + describe('canDeselectAll', function () { + + it('returns true for selected docs', function () { + store._selectedItems = {'testId1': true}; + assert.ok(store.canDeselectAll()); + }); + + it('returns false for no selected docs', function () { + store._selectedItems = {}; + + assert.notOk(store.canDeselectAll()); + }); + + }); + + describe('canCollapseDocs', function () { + + it('returns true for no collapsed docs', function () { + store._collapsedDocs = {}; + assert.ok(store.canCollapseDocs()); + }); + + it('returns false for all collapsed docs', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store._collapsedDocs = { + 'testId1': true, + 'testId2': true + }; + + assert.notOk(store.canCollapseDocs()); + }); + + }); + + describe('canUncollapseDocs', function () { + + it('returns true for collapsed docs', function () { + store._collapsedDocs = {'testId1': true}; + assert.ok(store.canUncollapseDocs()); + }); + + it('returns false for no collapsed docs', function () { + store.clearCollapsedDocs(); + + assert.notOk(store.canUncollapseDocs()); + }); + + }); + + describe('getDocContent', function () { + + it('returns full doc if not collapsed', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + var doc = store._collection.first(); + var result = store.getDocContent(doc); + + assert.equal(JSON.parse(result).value, 'one'); + }); + + it('returns collapsed doc if collapsed', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + var doc = store._collection.first(); + store._collapsedDocs = {'testId1': true}; + var result = store.getDocContent(doc); + + assert.ok(_.isUndefined(JSON.parse(result).value)); + }); + + }); + + describe('#selectDoc', function () { + + it('selects doc if not already selected', function () { + store._selectedItems = {}; + store.selectDoc('id'); + assert.equal(store.getSelectedItemsLength(), 1); + }); + + it('deselects doc if already selected', function () { + store._selectedItems = {'id': true}; + store.selectDoc('id'); + assert.equal(store.getSelectedItemsLength(), 0); + }); + }); + + describe('#selectAllDocuments', function () { + + it('selects all documents', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store.selectAllDocuments(); + assert.ok(store.getSelectedItems().testId1); + }); + + }); + + describe('#deSelectAllDocuments', function () { + + it('deselects all documents', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store.selectAllDocuments(); + assert.ok(store.getSelectedItems().testId1); + store.deSelectAllDocuments(); + assert.equal(store.getSelectedItemsLength(), 0); + }); + }); + + describe('#collapseSelectedDocs', function () { + + it('collapses all selected docs', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store.clearCollapsedDocs(); + + store._selectedItems = { + 'testId1': true, + 'testId2': true + }; + + store.collapseSelectedDocs(); + assert.equal(store.getCollapsedDocsLength(), 2); + }); + + }); + + describe('#unCollapseSelectedDocs', function () { + + it('uncollapses all selected docs', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store.clearCollapsedDocs(); + + store._selectedItems = { + 'testId1': true, + 'testId2': true + }; + + store.collapseSelectedDocs(); + assert.equal(store.getCollapsedDocsLength(), 2); + store.unCollapseSelectedDocs(); + assert.equal(store.getCollapsedDocsLength(), 0); + }); + }); + + describe('#createBulkDeleteFromSelected', function () { + + it('correctly creates BulkDeleteDocCollection', function () { + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + store._selectedItems = { + 'testId1': true, + 'testId2': true + }; + + var bulkDelete = store.createBulkDeleteFromSelected(); + + assert.equal(bulkDelete.length, 2); + assert.ok(bulkDelete.at(0).get('_deleted')); + }); + + }); + + describe('#getDocId', function () { + + it('returns id if it exists', function () { + var doc = new Documents.Doc({ + _id: 'doc-id' + }, { + database: { + safeID: function () { return '1';} + } + }); + + assert.equal(store.getDocId(doc), 'doc-id'); + + }); + + it('returns key if it exists', function () { + var doc = new Documents.Doc({ + key: 'doc-key' + }, { + database: { + safeID: function () { return '1';} + } + }); + + assert.equal(store.getDocId(doc), 'doc-key'); + + }); + + it('returns empty string if no key or id exists', function () { + var doc = new Documents.Doc({ + key: null, + value: 'the-value' + }, { + database: { + safeID: function () { return '1';} + } + }); + + assert.equal(store.getDocId(doc), ''); + + }); + }); + + describe('hasReduce', function () { + + it('returns false for no collection', function () { + store._collection = null; + assert.notOk(store.hasReduce()); + }); + + it('returns false for no params', function () { + store._collection = []; + assert.notOk(store.hasReduce()); + }); + + it('returns true for reduce param', function () { + store._collection = []; + store._collection.param = { + reduce: true + }; + assert.notOk(store.hasReduce()); + + }); + + }); +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/pagination/actions.js b/app/addons/documents/pagination/actions.js index c69087a..5705d90 100644 --- a/app/addons/documents/pagination/actions.js +++ b/app/addons/documents/pagination/actions.js @@ -14,9 +14,10 @@ define([ 'app', 'api', 'addons/documents/pagination/actiontypes', - 'addons/documents/pagination/stores' + 'addons/documents/pagination/stores', + 'addons/documents/index-results/actions' ], -function (app, FauxtonAPI, ActionTypes, Stores) { +function (app, FauxtonAPI, ActionTypes, Stores, IndexResultsActions) { var store = Stores.indexPaginationStore; @@ -27,13 +28,10 @@ function (app, FauxtonAPI, ActionTypes, Stores) { perPage: perPage }); - FauxtonAPI.triggerRouteEvent('perPageChange', store.documentsLeftToFetch()); - }, + IndexResultsActions.clearResults(); - newPagination: function (collection) { - FauxtonAPI.dispatch({ - type: ActionTypes.NEW_PAGINATION, - collection: collection + store.getCollection().fetch().then(function () { + IndexResultsActions.resultsListReset(); }); }, @@ -44,21 +42,13 @@ function (app, FauxtonAPI, ActionTypes, Stores) { }); }, - collectionReset: function () { - FauxtonAPI.dispatch({ - type: ActionTypes.PAGINATION_COLLECTION_RESET, - }); - }, - paginateNext: function () { FauxtonAPI.dispatch({ type: ActionTypes.PAGINATE_NEXT, }); - FauxtonAPI.triggerRouteEvent('paginate', { - direction: 'next', - perPage: store.documentsLeftToFetch(), - currentPage: store.getCurrentPage() + store.getCollection().next().then(function () { + IndexResultsActions.resultsListReset(); }); }, @@ -67,10 +57,8 @@ function (app, FauxtonAPI, ActionTypes, Stores) { type: ActionTypes.PAGINATE_PREVIOUS, }); - FauxtonAPI.triggerRouteEvent('paginate', { - direction: 'previous', - perPage: store.getPerPage(), - currentPage: store.getCurrentPage() + store.getCollection().previous().then(function () { + IndexResultsActions.resultsListReset(); }); }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/actiontypes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/pagination/actiontypes.js b/app/addons/documents/pagination/actiontypes.js index fe2fe93..199131e 100644 --- a/app/addons/documents/pagination/actiontypes.js +++ b/app/addons/documents/pagination/actiontypes.js @@ -14,8 +14,6 @@ define([], function () { return { COLLECTION_CHANGED: 'COLLECTION_CHANGED', PER_PAGE_CHANGE: 'PER_PAGE_CHANGE', - NEW_PAGINATION: 'NEW_PAGINATION', - COLLECTION_RESET: 'PAGINATION_COLLECTION_RESET', PAGINATE_NEXT: 'PAGINATE_NEXT', PAGINATE_PREVIOUS: 'PAGINATE_PREVIOUS', SET_PAGINATION_DOCUMENT_LIMIT: 'SET_PAGINATION_DOCUMENT_LIMIT' http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/pagination/stores.js b/app/addons/documents/pagination/stores.js index f63f766..731614a 100644 --- a/app/addons/documents/pagination/stores.js +++ b/app/addons/documents/pagination/stores.js @@ -13,8 +13,9 @@ define([ 'app', 'api', - 'addons/documents/pagination/actiontypes' -], function (app, FauxtonAPI, ActionTypes) { + 'addons/documents/pagination/actiontypes', + 'addons/documents/index-results/actiontypes' +], function (app, FauxtonAPI, ActionTypes, IndexResultsActionTypes) { var Stores = {}; var maxDocLimit = 10000; @@ -49,6 +50,10 @@ define([ this.reset(); }, + getCollection: function () { + return this._collection; + }, + canShowPrevious: function () { if (!this._enabled) { return false; } return this._collection.hasPrevious(); @@ -67,6 +72,7 @@ define([ paginateNext: function () { this._currentPage += 1; this._pageStart += this.getPerPage(); + this._collection.paging.pageSize = this.documentsLeftToFetch(); }, paginatePrevious: function () { @@ -76,6 +82,8 @@ define([ if (this._pageStart < 1) { this._pageStart = 1; } + + this._collection.paging.pageSize = this.getPerPage(); }, getCurrentPage: function () { @@ -121,6 +129,10 @@ define([ setPerPage: function (perPage) { this._perPage = perPage; app.utils.localStorageSet('fauxton:perpage', perPage); + + if (this._collection && this._collection.pageSizeReset) { + this._collection.pageSizeReset(perPage, {fetch: false}); + } }, getTotalRows: function () { @@ -142,15 +154,15 @@ define([ dispatch: function (action) { switch (action.type) { - case ActionTypes.NEW_PAGINATION: - this.newPagination(action.collection); + case IndexResultsActionTypes.INDEX_RESULTS_NEW_RESULTS: + this.newPagination(action.options.collection); this.triggerChange(); break; case ActionTypes.SET_PAGINATION_DOCUMENT_LIMIT: this.setDocumentLimit(action.docLimit); this.triggerChange(); break; - case ActionTypes.PAGINATION_COLLECTION_RESET: + case IndexResultsActionTypes.INDEX_RESULTS_RESET: this.triggerChange(); break; case ActionTypes.PAGINATE_NEXT: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/tests/pagination.actionsSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/pagination/tests/pagination.actionsSpec.js b/app/addons/documents/pagination/tests/pagination.actionsSpec.js index 8eab4b2..edeff05 100644 --- a/app/addons/documents/pagination/tests/pagination.actionsSpec.js +++ b/app/addons/documents/pagination/tests/pagination.actionsSpec.js @@ -14,8 +14,10 @@ define([ 'api', 'addons/documents/pagination/actions', 'addons/documents/pagination/stores', + 'addons/documents/index-results/actions', + 'addons/documents/shared-resources', 'testUtils', -], function (FauxtonAPI, Actions, Stores, testUtils) { +], function (FauxtonAPI, Actions, Stores, IndexResultsActions, Documents, testUtils) { var assert = testUtils.assert; FauxtonAPI.router = new FauxtonAPI.Router([]); @@ -26,47 +28,74 @@ define([ Stores.indexPaginationStore.documentsLeftToFetch.restore && Stores.indexPaginationStore.documentsLeftToFetch.restore(); Stores.indexPaginationStore.getCurrentPage.restore && Stores.indexPaginationStore.getCurrentPage.restore(); Stores.indexPaginationStore.getPerPage.restore && Stores.indexPaginationStore.getPerPage.restore(); - FauxtonAPI.triggerRouteEvent.restore(); + + IndexResultsActions.resultsListReset.restore && IndexResultsActions.resultsListReset.restore(); + Stores.indexPaginationStore.getCollection.restore && Stores.indexPaginationStore.getCollection.restore(); }); describe('updatePerPage', function () { - it('triggers routeEvent', function () { - var stub = sinon.stub(Stores.indexPaginationStore, 'documentsLeftToFetch'); - stub.returns(30); - var spy = sinon.spy(FauxtonAPI, 'triggerRouteEvent'); + beforeEach(function () { + Stores.indexPaginationStore._collection = new Documents.AllDocs([{id:1}, {id: 2}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + }); + + it('fetches collection', function () { + var spy = sinon.spy(Stores.indexPaginationStore, 'getCollection'); Actions.updatePerPage(30); - assert.ok(spy.calledWith('perPageChange', 30)); + assert.ok(spy.calledOnce); + }); + + it('sends results list reset', function () { + var promise = $.Deferred(); + promise.resolve(); + var stub = sinon.stub(Stores.indexPaginationStore, 'getCollection'); + var spy = sinon.spy(IndexResultsActions, 'resultsListReset'); + stub.returns({ + fetch: function () { return promise; } + }); + + Actions.updatePerPage(30); + assert.ok(spy.calledOnce); }); }); describe('paginateNext', function () { - it('triggers routeEvent', function () { - var spyEvent = sinon.spy(FauxtonAPI, 'triggerRouteEvent'); - var spyDocuments = sinon.spy(Stores.indexPaginationStore, 'documentsLeftToFetch'); - var spyPage = sinon.spy(Stores.indexPaginationStore, 'getCurrentPage'); - Actions.paginateNext(); + it('sends results list reset', function () { + var promise = $.Deferred(); + promise.resolve(); + var stub = sinon.stub(Stores.indexPaginationStore, 'getCollection'); + var spy = sinon.spy(IndexResultsActions, 'resultsListReset'); + stub.returns({ + next: function () { return promise; } + }); - assert.ok(spyEvent.calledOnce); - assert.ok(spyDocuments.calledOnce); - assert.ok(spyPage.calledOnce); + Actions.paginateNext(); + assert.ok(spy.calledOnce); }); }); describe('paginatePrevious', function () { - it('triggers routeEvent', function () { - var spyEvent = sinon.spy(FauxtonAPI, 'triggerRouteEvent'); - var spyPerPage = sinon.spy(Stores.indexPaginationStore, 'getPerPage'); - var spyPage = sinon.spy(Stores.indexPaginationStore, 'getCurrentPage'); - Actions.paginatePrevious(); + it('sends results list reset', function () { + var promise = $.Deferred(); + promise.resolve(); + var stub = sinon.stub(Stores.indexPaginationStore, 'getCollection'); + var spy = sinon.spy(IndexResultsActions, 'resultsListReset'); + stub.returns({ + previous: function () { return promise; } + }); - assert.ok(spyEvent.calledOnce); - assert.ok(spyPerPage.called); - assert.ok(spyPage.calledOnce); + Actions.paginatePrevious(); + assert.ok(spy.calledOnce); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/pagination/tests/paginationStoreSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/pagination/tests/paginationStoreSpec.js b/app/addons/documents/pagination/tests/paginationStoreSpec.js index 2230af5..f278c3d 100644 --- a/app/addons/documents/pagination/tests/paginationStoreSpec.js +++ b/app/addons/documents/pagination/tests/paginationStoreSpec.js @@ -14,8 +14,9 @@ define([ 'api', 'addons/documents/pagination/stores', 'addons/documents/pagination/actiontypes', + 'addons/documents/shared-resources', 'testUtils' -], function (FauxtonAPI, Stores, ActionTypes, testUtils) { +], function (FauxtonAPI, Stores, ActionTypes, Documents, testUtils) { var assert = testUtils.assert; var dispatchToken; var store; @@ -34,7 +35,12 @@ define([ describe('#collectionChanged', function () { var collection; beforeEach(function () { - collection = new Backbone.Collection([{id:1}, {id: 2}]); + collection = new Documents.AllDocs([{id:1}, {id: 2}], { + params: {}, + database: { + safeID: function () { return '1';} + } + }); collection.updateSeq = function () { return 'updateSeq';}; store.reset(); }); @@ -93,6 +99,12 @@ define([ describe('paginateNext', function () { beforeEach(function () { store.setPerPage(20); + store._collection = new Documents.AllDocs(null, { + params: {}, + database: { + safeID: function () { return '1';} + } + }); }); it('should increment page number', function () { @@ -112,18 +124,30 @@ define([ }); it('should set correct page end', function () { - store._collection = new Backbone.Collection(); store._collection.length = 20; store.reset(); store.paginateNext(); assert.equal(store.getPageEnd(), 40); }); + + it('should set collection pageSize', function () { + store.reset(); + store.paginateNext(); + + assert.equal(store.getCollection().paging.pageSize, 20); + }); }); describe('paginatePrevious', function () { beforeEach(function () { store.reset(); + store._collection = new Documents.AllDocs(null, { + params: {}, + database: { + safeID: function () { return '1';} + } + }); }); it('should decrement page number', function () { @@ -141,7 +165,6 @@ define([ }); it('should decrement page end', function () { - store._collection = new Backbone.Collection(); store._collection.length = 20; store.paginateNext(); store.paginatePrevious(); @@ -149,6 +172,14 @@ define([ assert.equal(store.getPageEnd(), 20); }); + it('should set collection pageSize', function () { + store.reset(); + store.paginateNext(); + store.paginatePrevious(); + + assert.equal(store.getCollection().paging.pageSize, 20); + }); + }); describe('totalDocsViewed', function () { @@ -226,7 +257,36 @@ define([ store.setDocumentLimit(NaN); assert.equal(store._docLimit, 10000); }); + }); + + describe('#setPerPage', function () { + beforeEach(function () { + store.reset(); + store._collection = new Documents.AllDocs(null, { + params: {}, + database: { + safeID: function () { return '1';} + } + }); + + }); + it('stores per page in local storage', function () { + var testPerPage = 111; + store.setPerPage(testPerPage); + var perPage = window.localStorage.getItem('fauxton:perpage'); + assert.equal(perPage, testPerPage ); + }); + + it('sets collections perPage', function () { + var spy = sinon.spy(store._collection, 'pageSizeReset'); + var testPerPage = 110; + + store.setPerPage(testPerPage); + assert.equal(spy.getCall(0).args[0], testPerPage); + + + }); }); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/resources.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js index 880cce1..e6ee676 100644 --- a/app/addons/documents/resources.js +++ b/app/addons/documents/resources.js @@ -106,6 +106,7 @@ function (app, FauxtonAPI, Documents, PagingCollection) { bulkDelete: function () { var payload = this.createPayload(this.toJSON()), + promise = FauxtonAPI.Deferred(), that = this; $.ajax({ @@ -116,7 +117,7 @@ function (app, FauxtonAPI, Documents, PagingCollection) { data: JSON.stringify(payload), }) .then(function (res) { - that.handleResponse(res); + that.handleResponse(res, promise); }) .fail(function () { var ids = _.reduce(that.toArray(), function (acc, doc) { @@ -124,10 +125,13 @@ function (app, FauxtonAPI, Documents, PagingCollection) { return acc; }, []); that.trigger('error', ids); + promise.reject(ids); }); + + return promise; }, - handleResponse: function (res) { + handleResponse: function (res, promise) { var ids = _.reduce(res, function (ids, doc) { if (doc.error) { ids.errorIds.push(doc.id); @@ -146,6 +150,14 @@ function (app, FauxtonAPI, Documents, PagingCollection) { this.trigger('error', ids.errorIds); } + // This is kind of tricky. If there are no documents deleted then rejects + // otherwise resolve with list of successful and failed documents + if (!_.isEmpty(ids.successIds)) { + promise.resolve(ids); + } else { + promise.reject(ids.errorIds); + } + this.trigger('updated'); }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/96d31bc7/app/addons/documents/routes-documents.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js index 0b607de..ff57dac 100644 --- a/app/addons/documents/routes-documents.js +++ b/app/addons/documents/routes-documents.js @@ -24,225 +24,220 @@ define([ 'addons/databases/base', 'addons/documents/resources', 'addons/fauxton/components', - 'addons/documents/pagination/actions', - 'addons/documents/pagination/stores' + 'addons/documents/pagination/stores', + 'addons/documents/index-results/actions' ], function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, - Databases, Resources, Components, PaginationActions, PaginationStores) { + Databases, Resources, Components, PaginationStores, IndexResultsActions) { + + + var DocumentsRouteObject = BaseRoute.extend({ + layout: "with_tabs_sidebar", + routes: { + "database/:database/_all_docs(:extra)": { + route: "allDocs", + roles: ["fx_loggedIn"] + }, + "database/:database/_design/:ddoc/_info": { + route: "designDocMetadata", + roles: ['fx_loggedIn'] + }, + 'database/:database/_changes': 'changes' + }, + events: { + "route:reloadDesignDocs": "reloadDesignDocs" + }, - var DocumentsRouteObject = BaseRoute.extend({ - layout: "with_tabs_sidebar", - routes: { - "database/:database/_all_docs(:extra)": { - route: "allDocs", - roles: ["fx_loggedIn"] + initialize: function (route, masterLayout, options) { + this.initViews(options[0]); + this.listenToLookaheadTray(); }, - "database/:database/_design/:ddoc/_info": { - route: "designDocMetadata", - roles: ['fx_loggedIn'] + + establish: function () { + return [ + this.designDocs.fetch({reset: true}), + this.allDatabases.fetchOnce() + ]; }, - 'database/:database/_changes': 'changes' - }, - - events: { - "route:reloadDesignDocs": "reloadDesignDocs", - 'route:updateAllDocs': 'updateAllDocsFromView', - 'route:paginate': 'paginate', - 'route:perPageChange': 'perPageChange', - }, - - initialize: function (route, masterLayout, options) { - this.initViews(options[0]); - this.listenToLookaheadTray(); - }, - - establish: function () { - return [ - this.designDocs.fetch({reset: true}), - this.allDatabases.fetchOnce() - ]; - }, - - initViews: function (dbName) { - this.databaseName = dbName; - this.database = new Databases.Model({id: this.databaseName}); - this.allDatabases = this.getAllDatabases(); - - this.createDesignDocsCollection(); - - this.rightHeader = this.setView("#right-header", new Documents.Views.RightAllDocsHeader({ - database: this.database - })); - - this.addLeftHeader(); - this.addSidebar(); - }, - - getAllDatabases: function () { - return new Databases.List(); //getAllDatabases() can be overwritten instead of hard coded into initViews - }, - - // this safely assumes the db name is valid - onSelectDatabase: function (dbName) { - this.cleanup(); - this.initViews(dbName); - - var url = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(dbName), ''); - FauxtonAPI.navigate(url, { - trigger: true - }); - - // we need to start listening again because cleanup() removed the listener, but in this case - // initialize() doesn't fire to re-set up the listener - this.listenToLookaheadTray(); - }, - - listenToLookaheadTray: function () { - this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase); - }, - - designDocMetadata: function (database, ddoc) { - this.footer && this.footer.remove(); - this.toolsView && this.toolsView.remove(); - this.viewEditor && this.viewEditor.remove(); - - var designDocInfo = new Resources.DdocInfo({ _id: "_design/" + ddoc }, { database: this.database }); - this.setView("#dashboard-lower-content", new Documents.Views.DdocInfo({ - ddocName: ddoc, - model: designDocInfo - })); - - this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + "_metadata"); - this.leftheader.updateCrumbs(this.getCrumbs(this.database)); - this.rightHeader.hideQueryOptions(); - - this.apiUrl = [designDocInfo.url('apiurl'), designDocInfo.documentation()]; - }, - - /* - * docParams are the options collection uses to fetch from the server - * urlParams are what are shown in the url and to the user - * They are not the same when paginating - */ - allDocs: function (databaseName, options) { - var params = this.createParams(options), - urlParams = params.urlParams, - docParams = params.docParams, - collection; - - if (this.eventAllDocs) { - this.eventAllDocs = false; - return; - } - this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar()); + initViews: function (dbName) { + this.databaseName = dbName; + this.database = new Databases.Model({id: this.databaseName}); + this.allDatabases = this.getAllDatabases(); + + this.createDesignDocsCollection(); - this.footer = this.setView('#footer', new Documents.Views.Footer()); + this.rightHeader = this.setView("#right-header", new Documents.Views.RightAllDocsHeader({ + database: this.database + })); - this.leftheader.updateCrumbs(this.getCrumbs(this.database)); + this.addLeftHeader(); + this.addSidebar(); + }, - this.database.buildAllDocs(docParams); - collection = this.database.allDocs; + getAllDatabases: function () { + return new Databases.List(); //getAllDatabases() can be overwritten instead of hard coded into initViews + }, - if (docParams.startkey && docParams.startkey.indexOf("_design") > -1) { - this.sidebar.setSelectedTab("design-docs"); - } else { - this.sidebar.setSelectedTab("all-docs"); - } + // this safely assumes the db name is valid + onSelectDatabase: function (dbName) { + this.cleanup(); + this.initViews(dbName); - this.viewEditor && this.viewEditor.remove(); - this.headerView && this.headerView.remove(); + var url = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(dbName), ''); + FauxtonAPI.navigate(url, { + trigger: true + }); + // we need to start listening again because cleanup() removed the listener, but in this case + // initialize() doesn't fire to re-set up the listener + this.listenToLookaheadTray(); + }, - if (!docParams) { - docParams = {}; - } + listenToLookaheadTray: function () { + this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase); + }, - PaginationActions.newPagination(collection); - this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage(); + designDocMetadata: function (database, ddoc) { + this.footer && this.footer.remove(); + this.toolsView && this.toolsView.remove(); + this.viewEditor && this.viewEditor.remove(); - // documentsView will populate the collection - this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({ - database: this.database, - collection: collection, - docParams: docParams, - bulkDeleteDocsCollection: new Documents.BulkDeleteDocCollection([], {databaseId: this.database.get('id')}) - })); + var designDocInfo = new Resources.DdocInfo({ _id: "_design/" + ddoc }, { database: this.database }); + this.setView("#dashboard-lower-content", new Documents.Views.DdocInfo({ + ddocName: ddoc, + model: designDocInfo + })); - // 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 - this.apiUrl = [this.database.allDocs.urlRef("apiurl", urlParams), this.database.allDocs.documentation()]; + this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + "_metadata"); + this.leftheader.updateCrumbs(this.getCrumbs(this.database)); + this.rightHeader.hideQueryOptions(); - // update the rightHeader with the latest & greatest info - this.rightHeader.resetQueryOptions({ queryParams: urlParams }); - this.rightHeader.showQueryOptions(); - }, + this.apiUrl = [designDocInfo.url('apiurl'), designDocInfo.documentation()]; + }, - reloadDesignDocs: function (event) { - this.sidebar.forceRender(); + /* + * docParams are the options collection uses to fetch from the server + * urlParams are what are shown in the url and to the user + * They are not the same when paginating + */ + allDocs: function (databaseName, options) { + var params = this.createParams(options), + urlParams = params.urlParams, + docParams = params.docParams, + collection; - if (event && event.selectedTab) { - this.sidebar.setSelectedTab(event.selectedTab); - } - }, + if (this.eventAllDocs) { + this.eventAllDocs = false; + return; + } - changes: function () { - var docParams = app.getParams(); - this.database.buildChanges(docParams); + this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar()); - this.changesView = this.setView("#dashboard-lower-content", new Changes.ChangesReactWrapper({ - model: this.database - })); + this.footer = this.setView('#footer', new Documents.Views.Footer()); - this.headerView = this.setView('#dashboard-upper-content', new Changes.ChangesHeaderReactWrapper()); + this.leftheader.updateCrumbs(this.getCrumbs(this.database)); - this.footer && this.footer.remove(); - this.toolsView && this.toolsView.remove(); - this.viewEditor && this.viewEditor.remove(); - this.reactHeader && this.reactHeader.remove(); + this.database.buildAllDocs(docParams); + collection = this.database.allDocs; - this.sidebar.setSelectedTab('changes'); - this.leftheader.updateCrumbs(this.getCrumbs(this.database)); - this.rightHeader.hideQueryOptions(); + if (docParams.startkey && docParams.startkey.indexOf("_design") > -1) { + this.sidebar.setSelectedTab("design-docs"); + } else { + this.sidebar.setSelectedTab("all-docs"); + } - this.apiUrl = function () { - return [FauxtonAPI.urls('changes', 'apiurl', this.database.id, ''), this.database.documentation()]; - }; - }, + this.viewEditor && this.viewEditor.remove(); + this.headerView && this.headerView.remove(); - 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'); + + if (!docParams) { + docParams = {}; + } + + IndexResultsActions.newResultsList({ + collection: collection, + deleteable: true + }); + + this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage(); + + this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({})); + + // 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 + this.apiUrl = [this.database.allDocs.urlRef("apiurl", urlParams), this.database.allDocs.documentation()]; + + // update the rightHeader with the latest & greatest info + this.rightHeader.resetQueryOptions({ queryParams: urlParams }); + this.rightHeader.showQueryOptions(); + }, + + reloadDesignDocs: function (event) { + this.sidebar.forceRender(); + + if (event && event.selectedTab) { + this.sidebar.setSelectedTab(event.selectedTab); + } + }, + + changes: function () { + var docParams = app.getParams(); + this.database.buildChanges(docParams); + + this.changesView = this.setView("#dashboard-lower-content", new Changes.ChangesReactWrapper({ + model: this.database + })); + + this.headerView = this.setView('#dashboard-upper-content', new Changes.ChangesHeaderReactWrapper()); + + this.footer && this.footer.remove(); + this.toolsView && this.toolsView.remove(); + this.viewEditor && this.viewEditor.remove(); + this.reactHeader && this.reactHeader.remove(); + + this.sidebar.setSelectedTab('changes'); + this.leftheader.updateCrumbs(this.getCrumbs(this.database)); + this.rightHeader.hideQueryOptions(); + + this.apiUrl = function () { + return [FauxtonAPI.urls('changes', 'apiurl', this.database.id, ''), this.database.documentation()]; + }; + }, + + 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); } - // we're no longer interested in listening to the lookahead tray event on this route object - this.stopListening(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase); - } + }); + return DocumentsRouteObject; }); - - return DocumentsRouteObject; -});
