Repository: couchdb-fauxton Updated Branches: refs/heads/master f61efde4b -> 8a7a0f9d2
Sidebar component updates This makes a couple of changes to the DesignDoc and IndexSection components used in the sidebar. 1. `<IndexSection />` knew too much! It had some hardcoded references to the structure (prop names) of the design docs and how it was being called. Now itâs more generic and passed everything it needs to know via props, so itâs a little bit dumber + will work for any new type of design doc contents. 2. The design doc item in the sidebar will now only list Search Indexes or other subsections if they have content. This wonât impact Fauxton as is, but for script that wrap this code, itâll cut down on visual clutter in the UI. [This is mostly for UX.] Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/8a7a0f9d Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/8a7a0f9d Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/8a7a0f9d Branch: refs/heads/master Commit: 8a7a0f9d209310a5520cfc48a138a7a3ae357735 Parents: f61efde Author: Ben Keen <ben.k...@gmail.com> Authored: Fri Jan 8 17:34:01 2016 -0800 Committer: Ben Keen <ben.k...@gmail.com> Committed: Tue Jan 12 09:24:28 2016 -0800 ---------------------------------------------------------------------- app/addons/documents/sidebar/sidebar.react.jsx | 71 ++++++++++----- .../tests/sidebar.componentsSpec.react.jsx | 93 ++++++++++++++++++++ 2 files changed, 142 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/8a7a0f9d/app/addons/documents/sidebar/sidebar.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/sidebar/sidebar.react.jsx b/app/addons/documents/sidebar/sidebar.react.jsx index fba0acd..7a7abd7 100644 --- a/app/addons/documents/sidebar/sidebar.react.jsx +++ b/app/addons/documents/sidebar/sidebar.react.jsx @@ -142,18 +142,18 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, DocumentViews) { var IndexSection = React.createClass({ - getDefaultProps: function () { - return { - indexTypeMap: { - views: { icon: 'fonticon-sidenav-map-reduce', urlFolder: '_view', type: 'view' }, - indexes: { icon: 'fonticon-sidenav-search', urlFolder: '_indexes', type: 'search' } - } - }; + propTypes: { + urlNamespace: React.PropTypes.string.isRequired, + icon: React.PropTypes.string.isRequired, + databaseName: React.PropTypes.string.isRequired, + designDocName: React.PropTypes.string.isRequired, + items: React.PropTypes.array.isRequired }, createItems: function () { return _.map(this.props.items, function (index, key) { - var href = FauxtonAPI.urls(this.props.indexTypeMap[this.props.selector].type, 'app', this.props.databaseName, this.props.designDocName); + var href = FauxtonAPI.urls(this.props.urlNamespace, 'app', this.props.databaseName, this.props.designDocName); + return ( <li key={key}> <a @@ -176,6 +176,13 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, DocumentViews) { }, render: function () { + + // if this section has no content, omit it to prevent clutter. Otherwise it would show a toggle option that + // would hide/show nothing + if (this.props.items.length === 0) { + return null; + } + var toggleClassNames = 'accordion-header'; var toggleBodyClassNames = 'accordion-body collapse'; if (this.props.contentVisible) { @@ -184,7 +191,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, DocumentViews) { } var title = this.props.title; - var icon = this.props.indexTypeMap[this.props.selector].icon; + var icon = this.props.icon; var designDocName = this.props.designDocName; var linkId = "nav-design-function-" + designDocName + this.props.selector; return ( @@ -205,19 +212,36 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, DocumentViews) { var DesignDoc = React.createClass({ - createIndexList: function () { - var sidebarListTypes = FauxtonAPI.getExtensions('sidebar:list'); + propTypes: { + sidebarListTypes: React.PropTypes.array.isRequired + }, - if (_.isEmpty(sidebarListTypes) || - (_.has(sidebarListTypes[0], 'selector') && sidebarListTypes[0].selector !== 'views')) { - sidebarListTypes.unshift({ + getInitialState: function () { + return { + updatedSidebarListTypes: this.props.sidebarListTypes + }; + }, + + componentWillMount: function () { + if (_.isEmpty(this.state.updatedSidebarListTypes) || + (_.has(this.state.updatedSidebarListTypes[0], 'selector') && this.state.updatedSidebarListTypes[0].selector !== 'views')) { + + var newList = this.state.updatedSidebarListTypes; + newList.unshift({ selector: 'views', - name: 'Views' + name: 'Views', + icon: 'fonticon-sidenav-map-reduce', + urlNamespace: 'view' }); + this.setState({ updatedSidebarListTypes: newList }); } + }, - return _.map(sidebarListTypes, function (index, key) { + createIndexList: function () { + return _.map(this.state.updatedSidebarListTypes, function (index, key) { return <IndexSection + icon={index.icon} + urlNamespace={index.urlNamespace} contentVisible={this.props.isVisible(this.props.designDocName, index.name)} toggle={this.props.toggle} databaseName={this.props.databaseName} @@ -303,10 +327,16 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, DocumentViews) { }); var DesignDocList = React.createClass({ + componentWillMount: function () { + var list = FauxtonAPI.getExtensions('sidebar:list'); + this.sidebarListTypes = _.isUndefined(list) ? [] : list; + }, + createDesignDocs: function () { return _.map(this.props.designDocs, function (designDoc, key) { return <DesignDoc toggle={this.props.toggle} + sidebarListTypes={this.sidebarListTypes} contentVisible={this.props.isVisible(designDoc.safeId)} isVisible={this.props.isVisible} key={key} @@ -317,9 +347,6 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, DocumentViews) { }, render: function () { - var designDocName = this.props.designDocName; - var designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', this.props.databaseName, designDocName); - return ( <ul className="nav nav-list"> {this.createDesignDocs()} @@ -412,9 +439,9 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, DocumentViews) { } }); - var Views = { - SidebarController: SidebarController + return { + SidebarController: SidebarController, + DesignDoc: DesignDoc }; - return Views; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/8a7a0f9d/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx b/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx new file mode 100644 index 0000000..ec3a046 --- /dev/null +++ b/app/addons/documents/sidebar/tests/sidebar.componentsSpec.react.jsx @@ -0,0 +1,93 @@ +// 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', + 'react', + 'testUtils', + 'addons/documents/sidebar/sidebar.react' +], function (FauxtonAPI, React, utils, Components) { + var assert = utils.assert; + var TestUtils = React.addons.TestUtils; + var DesignDoc = Components.DesignDoc; + + + describe('DesignDoc', function () { + var container; + + beforeEach(function () { + container = document.createElement('div'); + }); + + afterEach(function () { + React.unmountComponentAtNode(container); + }); + + it('confirm only single sub-option is shown by default (metadata link)', function () { + var stub = function () { return true; }; + var el = TestUtils.renderIntoDocument(<DesignDoc + toggle={stub} + sidebarListTypes={[]} + contentVisible={true} + isVisible={stub} + designDoc={{}} + designDocName="id" + databaseName="db-name" />, container); + var subOptions = $(React.findDOMNode(el)).find('.accordion-body li'); + assert.equal(subOptions.length, 1); + }); + + it('confirm design doc sidebar extensions appear', function () { + var stub = function () { return true; }; + var el = TestUtils.renderIntoDocument(<DesignDoc + toggle={stub} + contentVisible={true} + isVisible={stub} + sidebarListTypes={[{ + selector: 'customProp', + name: 'Search Indexes', + icon: 'icon-here', + urlNamespace: 'whatever' + }]} + designDoc={{ + customProp: { + one: 'something' + } + }} + designDocName="id" + databaseName="db-name" />, container); + var subOptions = $(React.findDOMNode(el)).find('.accordion-body li'); + assert.equal(subOptions.length, 3); // 1 for "Metadata" row, 1 for Type List row ("search indexes") and one for the index itself + }); + + it('confirm design doc sidebar extensions do not appear when they have no content', function () { + var stub = function () { return true; }; + var el = TestUtils.renderIntoDocument(<DesignDoc + toggle={stub} + sidebarListTypes={[{ + selector: 'customProp', + name: 'Search Indexes', + icon: 'icon-here', + urlNamespace: 'whatever' + }]} + contentVisible={true} + isVisible={stub} + designDoc={{}} // note that this is empty + designDocName="id" + databaseName="db-name" />, container); + var subOptions = $(React.findDOMNode(el)).find('.accordion-body li'); + assert.equal(subOptions.length, 1); + }); + + }); + +}); +