You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by se...@apache.org on 2015/05/07 21:23:35 UTC
fauxton commit: updated refs/heads/master to 187745c
Repository: couchdb-fauxton
Updated Branches:
refs/heads/master 4730b1144 -> 187745ca9
databases in 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/187745ca
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/187745ca
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/187745ca
Branch: refs/heads/master
Commit: 187745ca91910ba41c12d7d8846af6f09a8cfa00
Parents: 4730b11
Author: sebastianrothbucher <se...@googlemail.com>
Authored: Thu Apr 9 16:35:39 2015 +0200
Committer: sebastianrothbucher <se...@googlemail.com>
Committed: Thu May 7 20:46:22 2015 +0200
----------------------------------------------------------------------
app/addons/databases/actions.js | 123 +++++++
app/addons/databases/actiontypes.js | 20 ++
app/addons/databases/assets/less/databases.less | 2 +-
app/addons/databases/base.js | 11 +-
app/addons/databases/components.react.jsx | 319 +++++++++++++++++++
app/addons/databases/resources.js | 6 +
app/addons/databases/routes.js | 47 +--
app/addons/databases/stores.js | 128 ++++++++
.../databases/templates/footer_alldbs.html | 17 -
.../databases/templates/header_alldbs.html | 21 --
app/addons/databases/templates/item.html | 30 --
app/addons/databases/templates/jump_to_db.html | 19 --
app/addons/databases/templates/list.html | 26 --
app/addons/databases/templates/newdatabase.html | 20 --
app/addons/databases/tests/actionsSpec.js | 251 +++++++++++++++
.../databases/tests/componentsSpec.react.jsx | 190 +++++++++++
app/addons/databases/tests/resourcesSpec.js | 3 +-
app/addons/databases/tests/storesSpec.js | 74 +++++
app/addons/databases/views.js | 242 --------------
app/addons/fauxton/components.react.jsx | 141 +++++++-
.../fauxton/tests/componentsSpec.react.jsx | 197 ++++++++++++
21 files changed, 1459 insertions(+), 428 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/actions.js b/app/addons/databases/actions.js
new file mode 100644
index 0000000..af67d25
--- /dev/null
+++ b/app/addons/databases/actions.js
@@ -0,0 +1,123 @@
+// 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/databases/stores',
+ 'addons/databases/actiontypes',
+ 'addons/databases/resources'
+],
+function (app, FauxtonAPI, Stores, ActionTypes, Resources) {
+ return {
+
+ init: function (databases) {
+ var params = app.getParams();
+ var page = params.page ? parseInt(params.page, 10) : 1;
+ var perPage = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE;
+
+ this.setStartLoading();
+ FauxtonAPI.when(databases.fetch({ cache: false })).then(function () {
+ FauxtonAPI.when(databases.paginated(page, perPage).map(function (database) {
+ return database.status.fetchOnce();
+ })).always(function () {
+ //make this always so that even if a user is not allowed access to a database
+ //they will still see a list of all databases
+ FauxtonAPI.dispatch({
+ type: ActionTypes.DATABASES_INIT,
+ options: {
+ collection: databases.paginated(page, perPage),
+ backboneCollection: databases,
+ page: page
+ }
+ });
+ }.bind(this));
+ }.bind(this));
+ },
+
+ setPage: function (page) {
+ FauxtonAPI.dispatch({
+ type: ActionTypes.DATABASES_SETPAGE,
+ options: {
+ page: page
+ }
+ });
+ },
+
+ setStartLoading: function () {
+ FauxtonAPI.dispatch({
+ type: ActionTypes.DATABASES_STARTLOADING
+ });
+ },
+
+ setLoadComplete: function () {
+ FauxtonAPI.dispatch({
+ type: ActionTypes.DATABASES_LOADCOMPLETE
+ });
+ },
+
+ createNewDatabase: function (databaseName) {
+ if (_.isNull(databaseName) || databaseName.trim().length === 0) {
+ FauxtonAPI.addNotification({
+ msg: 'Please enter a valid database name',
+ type: 'error',
+ clear: true
+ });
+ return;
+ }
+ databaseName = databaseName.trim();
+ // name accepted, make sure prompt can be removed
+ FauxtonAPI.dispatch({
+ type: ActionTypes.DATABASES_SET_PROMPT_VISIBLE,
+ options: {
+ visible: false
+ }
+ });
+
+ var db = Stores.databasesStore.obtainNewDatabaseModel(databaseName);
+ FauxtonAPI.addNotification({ msg: 'Creating database.' });
+ db.save().done(function () {
+ FauxtonAPI.addNotification({
+ msg: 'Database created successfully',
+ type: 'success',
+ clear: true
+ });
+ var route = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(databaseName), '?limit=' + Resources.DocLimit);
+ app.router.navigate(route, { trigger: true });
+ }
+ ).error(function (xhr) {
+ var responseText = JSON.parse(xhr.responseText).reason;
+ FauxtonAPI.addNotification({
+ msg: 'Create database failed: ' + responseText,
+ type: 'error',
+ clear: true
+ });
+ }
+ );
+ },
+
+ jumpToDatabase: function (databaseName) {
+ if (_.isNull(databaseName) || databaseName.trim().length === 0) {
+ return;
+ }
+ databaseName = databaseName.trim();
+ if (Stores.databasesStore.doesDatabaseExist(databaseName)) {
+ var url = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(databaseName), "");
+ FauxtonAPI.navigate(url);
+ } else {
+ FauxtonAPI.addNotification({
+ msg: 'Database does not exist.',
+ type: 'error'
+ });
+ }
+ }
+ };
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/actiontypes.js b/app/addons/databases/actiontypes.js
new file mode 100644
index 0000000..7be561e
--- /dev/null
+++ b/app/addons/databases/actiontypes.js
@@ -0,0 +1,20 @@
+// 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 {
+ DATABASES_INIT: 'DATABASES_INIT',
+ DATABASES_SETPAGE: 'DATABASES_SETPAGE',
+ DATABASES_SET_PROMPT_VISIBLE: 'DATABASES_SET_PROMPT_VISIBLE',
+ DATABASES_STARTLOADING: 'DATABASES_STARTLOADING',
+ DATABASES_LOADCOMPLETE: 'DATABASES_LOADCOMPLETE'
+ };
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/assets/less/databases.less
----------------------------------------------------------------------
diff --git a/app/addons/databases/assets/less/databases.less b/app/addons/databases/assets/less/databases.less
index ca3a666..b6986ed 100644
--- a/app/addons/databases/assets/less/databases.less
+++ b/app/addons/databases/assets/less/databases.less
@@ -55,7 +55,7 @@
a.btn {
color: white;
background-color: #af2d24;
- margin-left: -4px;
+ margin-left: 0;
line-height: 1.5em;
border: 0px;
padding: 10px 10px 9px;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/base.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/base.js b/app/addons/databases/base.js
index ad07c42..56a76b0 100644
--- a/app/addons/databases/base.js
+++ b/app/addons/databases/base.js
@@ -12,18 +12,11 @@
define([
"app",
-
"api",
-
- // Modules
- "addons/databases/routes",
- // Views
- "addons/databases/views"
-
+ "addons/databases/routes"
],
-function (app, FauxtonAPI, Databases, Views) {
- Databases.Views = Views;
+function (app, FauxtonAPI, Databases) {
Databases.initialize = function () {
FauxtonAPI.addHeaderLink({
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/databases/components.react.jsx b/app/addons/databases/components.react.jsx
new file mode 100644
index 0000000..0bf290c
--- /dev/null
+++ b/app/addons/databases/components.react.jsx
@@ -0,0 +1,319 @@
+// 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',
+ 'react',
+ 'addons/components/react-components.react',
+ 'addons/fauxton/components.react',
+ 'addons/databases/stores',
+ 'addons/databases/resources',
+ 'addons/databases/actions',
+ 'helpers'
+], function (app, FauxtonAPI, React, Components, ComponentsReact, Stores, Resources, Actions, Helpers) {
+
+ var databasesStore = Stores.databasesStore;
+
+ var DatabasesController = React.createClass({
+
+ getStoreState: function () {
+ return {
+ collection: databasesStore.getCollection(),
+ loading: databasesStore.isLoading()
+ };
+ },
+
+ getInitialState: function () {
+ return this.getStoreState();
+ },
+
+ componentDidMount: function () {
+ databasesStore.on('change', this.onChange, this);
+ },
+
+ componentWillUnmount: function () {
+ databasesStore.off('change', this.onChange, this);
+ },
+
+ onChange: function () {
+ this.setState(this.getStoreState());
+ },
+
+ render: function () {
+ var collection = this.state.collection;
+ var loading = this.state.loading;
+ return (
+ <DatabaseTable body={collection} loading={loading} />
+ );
+ }
+ });
+
+ var DatabaseTable = React.createClass({
+
+ createRows: function () {
+ return _.map(this.props.body, function (item, iteration) {
+ return (
+ <DatabaseRow row={item} key={iteration} />
+ );
+ });
+ },
+
+ render: function () {
+ if (this.props.loading) {
+ return (
+ <div className="view">
+ <Components.LoadLines />
+ </div>
+ );
+ }
+ var rows = this.createRows();
+ return (
+ <div className="view">
+ <table className="databases table table-striped">
+ <thead>
+ <th>Name</th>
+ <th>Size</th>
+ <th># of Docs</th>
+ <th>Update Seq</th>
+ <th>Actions</th>
+ </thead>
+ <tbody>
+ {rows}
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+ });
+
+ var DatabaseRow = React.createClass({
+
+ renderGraveyard: function (row) {
+ if (row.status.isGraveYard()) {
+ return (
+ <GraveyardInfo row={row} />
+ );
+ } else {
+ return null;
+ }
+ },
+
+ render: function () {
+ var row = this.props.row;
+ var name = row.get("name");
+ var encoded = app.utils.safeURLName(name);
+ var size = Helpers.formatSize(row.status.dataSize());
+ return (
+ <tr>
+ <td>
+ <a href={"#/database/"+encoded+"/_all_docs"}>{name}</a>
+ </td>
+ <td>{size}</td>
+ <td>{row.status.numDocs()} {this.renderGraveyard(row)}</td>
+ <td>{row.status.updateSeq()}</td>
+ <td>
+ <a className="db-actions btn fonticon-replicate set-replication-start" title={"Replicate "+name} href={"#/replication/"+encoded}></a> 
+ <a className="db-actions btn icon-lock set-permissions" title={"Set permissions for "+name} href={"#/database/"+encoded+"/permissions"}></a>
+ </td>
+ </tr>
+ );
+ }
+ });
+
+ var GraveyardInfo = React.createClass({
+
+ componentDidMount: function () {
+ $(this.refs.myself.getDOMNode()).tooltip();
+ },
+
+ render: function () {
+ var row = this.props.row;
+ var graveyardTitle = "This database has just " + row.status.numDocs() +
+ " docs and " + row.status.numDeletedDocs() + " deleted docs";
+ return (
+ <i className="js-db-graveyard icon icon-exclamation-sign" ref="myself" title={graveyardTitle}></i>
+ );
+ }
+ });
+
+ var RightDatabasesHeader = React.createClass({
+
+ render: function () {
+ return (
+ <div className="header-right">
+ <AddDatabaseWidget />
+ <JumpToDatabaseWidget />
+ </div>
+ );
+ }
+ });
+
+ var AddDatabaseWidget = React.createClass({
+
+ onTrayToggle: function (e) {
+ e.preventDefault();
+ this.refs.newDbTray.toggle(function (shown) {
+ if (shown) {
+ this.refs.newDbName.getDOMNode().focus();
+ }
+ }.bind(this));
+ },
+
+ onKeyUpInInput: function (e) {
+ if (e.which === 13) {
+ this.onAddDatabase();
+ }
+ },
+
+ componentDidMount: function () {
+ databasesStore.on('change', this.onChange, this);
+ },
+
+ componentWillUnmount: function () {
+ databasesStore.off('change', this.onChange, this);
+ },
+
+ onChange: function () {
+ if (this.isMounted()) {
+ this.refs.newDbTray.setVisible(databasesStore.isPromptVisible());
+ }
+ },
+
+ onAddDatabase: function () {
+ var databaseName = this.refs.newDbName.getDOMNode().value;
+ Actions.createNewDatabase(databaseName);
+ },
+
+ render: function () {
+
+ return (
+ <div className="button" id="add-db-button">
+ <a id="add-new-database" href="#" className="add-new-database-btn" onClick={this.onTrayToggle} data-bypass="true">
+ <i className="header-icon fonticon-new-database"></i>
+ Add New Database
+ </a>
+ <ComponentsReact.Tray ref="newDbTray" className="new-database-tray">
+ <span className="add-on">Add New Database</span>
+ <input id="js-new-database-name" type="text" onKeyUp={this.onKeyUpInInput} ref="newDbName" className="input-xxlarge" placeholder="Name of database" />
+ <a className="btn" id="js-create-database" onClick={this.onAddDatabase}>Create</a>
+ </ComponentsReact.Tray>
+ </div>
+ );
+ }
+ });
+
+ var JumpToDatabaseWidget = React.createClass({
+
+ getStoreState: function () {
+ return {
+ databaseNames: databasesStore.getDatabaseNames()
+ };
+ },
+
+ getInitialState: function () {
+ return this.getStoreState();
+ },
+
+ componentDidMount: function () {
+ databasesStore.on('change', this.onChange, this);
+ $(this.refs.searchDbName.getDOMNode()).typeahead({
+ source: this.state.databaseNames,
+ updater: function (item) {
+ this.jumpToDb(item);
+ }.bind(this)
+ });
+ },
+
+ componentWillUnmount: function () {
+ databasesStore.off('change', this.onChange, this);
+ },
+
+ onChange: function () {
+ this.setState(this.getStoreState());
+ },
+
+ jumpToDb: function (databaseName) {
+ databaseName = databaseName || this.refs.searchDbName.getDOMNode().value;
+ Actions.jumpToDatabase(databaseName);
+ },
+
+ jumpToDbHandler: function (e) {
+ e.preventDefault();
+ this.jumpToDb();
+ },
+
+ render: function () {
+ return (
+ <div className="searchbox-wrapper">
+ <div id="header-search" className="js-search searchbox-container">
+ <form onSubmit={this.jumpToDbHandler} id="jump-to-db" className="navbar-form pull-right database-search">
+ <div className="input-append">
+ <input type="text" className="search-autocomplete" ref="searchDbName" name="search-query" placeholder="Database name" autoComplete="off" />
+ <button className="btn btn-primary" type="submit"><i className="icon icon-search"></i></button>
+ </div>
+ </form>
+ </div>
+ </div>
+ );
+ }
+ });
+
+ var DatabasePagination = React.createClass({
+
+ getStoreState: function () {
+ return {
+ databaseNames: databasesStore.getDatabaseNames(),
+ page: databasesStore.getPage()
+ };
+ },
+
+ getInitialState: function () {
+ return this.getStoreState();
+ },
+
+ componentDidMount: function () {
+ databasesStore.on('change', this.onChange, this);
+ },
+
+ componentWillUnmount: function () {
+ databasesStore.off('change', this.onChange, this);
+ },
+
+ onChange: function () {
+ this.setState(this.getStoreState());
+ },
+
+ render: function () {
+ var page = this.state.page;
+ var total = this.props.total || this.state.databaseNames.length;
+ return (
+ <footer className="all-db-footer pagination-footer">
+ <div id="database-pagination">
+ <ComponentsReact.Pagination page={page} total={total} urlPrefix="#/_all_dbs?page=" />
+ </div>
+ </footer>
+ );
+ }
+ });
+
+ return {
+ DatabasesController: DatabasesController,
+ DatabaseTable: DatabaseTable,
+ DatabaseRow: DatabaseRow,
+ RightDatabasesHeader: RightDatabasesHeader,
+ GraveyardInfo: GraveyardInfo,
+ AddDatabaseWidget: AddDatabaseWidget,
+ JumpToDatabaseWidget: JumpToDatabaseWidget,
+ DatabasePagination: DatabasePagination
+ };
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/resources.js b/app/addons/databases/resources.js
index 882f89d..2d277de 100644
--- a/app/addons/databases/resources.js
+++ b/app/addons/databases/resources.js
@@ -200,6 +200,12 @@ function (app, FauxtonAPI, Documents) {
name: database
};
});
+ },
+
+ paginated: function (page, perPage) {
+ var start = (page - 1) * perPage;
+ var end = page * perPage;
+ return this.slice(start, end);
}
});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/routes.js b/app/addons/databases/routes.js
index 53cf52b..b65c49d 100644
--- a/app/addons/databases/routes.js
+++ b/app/addons/databases/routes.js
@@ -12,18 +12,13 @@
define([
"app",
-
"api",
-
- // Modules
"addons/databases/resources",
- // TODO:: fix the include flow modules so we don't have to require views here
- 'addons/databases/views',
- 'addons/fauxton/components'
-
+ "addons/databases/actions",
+ 'addons/databases/components.react'
],
-function (app, FauxtonAPI, Databases, Views, Components) {
+function (app, FauxtonAPI, Databases, Actions, Components) {
var AllDbsRouteObject = FauxtonAPI.RouteObject.extend({
layout: 'one_pane',
@@ -47,42 +42,14 @@ function (app, FauxtonAPI, Databases, Views, Components) {
},
allDatabases: function () {
- var params = app.getParams(),
- dbPage = params.page ? parseInt(params.page, 10) : 1,
- perPage = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE,
- pagination;
-
- pagination = new Components.Pagination({
- page: dbPage,
- perPage: perPage,
- collection: this.databases,
- urlFun: function (page) {
- return '#/_all_dbs?page=' + page;
- }
- });
-
- this.footer = this.setView('#footer', new Views.Footer());
- this.setView('#database-pagination', pagination);
-
- this.databasesView = this.setView("#dashboard-content", new Views.List({
- collection: this.databases,
- perPage: perPage,
- page: dbPage
- }));
-
- this.rightHeader = this.setView("#right-header", new Views.RightAllDBsHeader({
- collection: this.databases,
- }));
-
- this.databasesView.setPage(dbPage);
+ Actions.init(this.databases);
+ this.setComponent("#right-header", Components.RightDatabasesHeader);
+ this.setComponent("#dashboard-content", Components.DatabasesController);
+ this.setComponent("#footer", Components.DatabasePagination);
},
apiUrl: function () {
return [this.databases.url("apiurl"), this.databases.documentation()];
- },
-
- establish: function () {
- return [this.databases.fetch({ cache: false })];
}
});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/stores.js b/app/addons/databases/stores.js
new file mode 100644
index 0000000..4da6593
--- /dev/null
+++ b/app/addons/databases/stores.js
@@ -0,0 +1,128 @@
+// 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/databases/actiontypes',
+ 'addons/databases/resources'
+], function (app, FauxtonAPI, ActionTypes, Resources) {
+
+ var DatabasesStore = FauxtonAPI.Store.extend({
+
+ initialize: function () {
+ this._collection = {};
+ this._loading = false;
+ this._promptVisible = false;
+ },
+
+ init: function (collection, backboneCollection) {
+ this._collection = collection;
+ this._backboneCollection = backboneCollection;
+ },
+
+ setPage: function (page) {
+ this._page = page;
+ },
+
+ getPage: function () {
+ if (this._page) {
+ return this._page;
+ } else {
+ return 1;
+ }
+ },
+
+ isLoading: function () {
+ return this._loading;
+ },
+
+ setLoading: function (loading) {
+ this._loading = loading;
+ },
+
+ isPromptVisible: function () {
+ return this._promptVisible;
+ },
+
+ setPromptVisible: function (promptVisible) {
+ this._promptVisible = promptVisible;
+ },
+
+ obtainNewDatabaseModel: function (databaseName, nameAccCallback) {
+ return new this._backboneCollection.model({
+ id: databaseName,
+ name: databaseName
+ });
+ },
+
+ getCollection: function () {
+ return this._collection;
+ },
+
+ getDatabaseNames: function () {
+ if (this._backboneCollection) {
+ return _.map(this._backboneCollection.toJSON(), function (item, key) {
+ return item.name;
+ });
+ } else {
+ return [];
+ }
+ },
+
+ doesDatabaseExist: function (databaseName) {
+ return this.getDatabaseNames().indexOf(databaseName) >= 0;
+ },
+
+ dispatch: function (action) {
+ switch (action.type) {
+
+ case ActionTypes.DATABASES_INIT:
+ this.init(action.options.collection, action.options.backboneCollection);
+ this.setPage(action.options.page);
+ this.setLoading(false);
+ this.triggerChange();
+ break;
+
+ case ActionTypes.DATABASES_SETPAGE:
+ this.setPage(action.options.page);
+ this.triggerChange();
+ break;
+
+ case ActionTypes.DATABASES_SET_PROMPT_VISIBLE:
+ this.setPromptVisible(action.options.visible);
+ this.triggerChange();
+ break;
+
+ case ActionTypes.DATABASES_STARTLOADING:
+ this.setLoading(true);
+ this.triggerChange();
+ break;
+
+ case ActionTypes.DATABASES_LOADCOMPLETE:
+ this.setLoading(false);
+ this.triggerChange();
+ break;
+
+ default:
+ return;
+ }
+ }
+ });
+
+ var databasesStore = new DatabasesStore();
+ databasesStore.dispatchToken = FauxtonAPI.dispatcher.register(databasesStore.dispatch.bind(databasesStore));
+ return {
+ databasesStore: databasesStore
+ };
+
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/templates/footer_alldbs.html
----------------------------------------------------------------------
diff --git a/app/addons/databases/templates/footer_alldbs.html b/app/addons/databases/templates/footer_alldbs.html
deleted file mode 100644
index 9afcaad..0000000
--- a/app/addons/databases/templates/footer_alldbs.html
+++ /dev/null
@@ -1,17 +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.
-*/ %>
-
-<footer class="all-db-footer pagination-footer">
- <div id="database-pagination"></div>
-</footer>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/templates/header_alldbs.html
----------------------------------------------------------------------
diff --git a/app/addons/databases/templates/header_alldbs.html b/app/addons/databases/templates/header_alldbs.html
deleted file mode 100644
index 375745e..0000000
--- a/app/addons/databases/templates/header_alldbs.html
+++ /dev/null
@@ -1,21 +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.
-*/ %>
-
-<!-- add database-->
-<div class="button" id="add-db-button"></div>
-
-<!-- search (jump to doc)-->
-<div class="searchbox-wrapper">
- <div id="header-search" class="js-search searchbox-container"></div>
-</div>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/templates/item.html
----------------------------------------------------------------------
diff --git a/app/addons/databases/templates/item.html b/app/addons/databases/templates/item.html
deleted file mode 100644
index 610c8c6..0000000
--- a/app/addons/databases/templates/item.html
+++ /dev/null
@@ -1,30 +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.
-*/%>
-
-<td>
- <a href="#/database/<%=encoded%>/_all_docs"><%= database.get("name") %></a>
-</td>
-<td><%= formatSize(database.status.dataSize()) %></td>
-<td>
- <%= database.status.numDocs() %>
- <% if (database.status.isGraveYard()) { %>
- <i class="js-db-graveyard icon icon-exclamation-sign" data-toggle="tooltip"
- title="This database has just <%= database.status.numDocs() %> docs and <%= database.status.numDeletedDocs() %> deleted docs"></i>
- <% } %>
-</td>
-<td><%= database.status.updateSeq() %></td>
-<td>
- <a class="db-actions btn fonticon-replicate set-replication-start" title="Replicate <%-database.get("name")%>" href="#/replication/<%-encoded%>"></a>
- <a class="db-actions btn icon-lock set-permissions" title="Set permissions for <%-database.get("name")%>" href="#/database/<%-encoded%>/permissions"></a>
-</td>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/templates/jump_to_db.html
----------------------------------------------------------------------
diff --git a/app/addons/databases/templates/jump_to_db.html b/app/addons/databases/templates/jump_to_db.html
deleted file mode 100644
index e3e7912..0000000
--- a/app/addons/databases/templates/jump_to_db.html
+++ /dev/null
@@ -1,19 +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.
--->
-<form id="jump-to-db" class="navbar-form pull-right database-search">
- <div class="input-append">
- <input type="text" class="search-autocomplete" autocomplete="off" name="search-query" placeholder="Database name" />
- <button class="btn btn-primary" type="submit"><i class="icon icon-search"></i></button>
- </div>
-</form>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/templates/list.html
----------------------------------------------------------------------
diff --git a/app/addons/databases/templates/list.html b/app/addons/databases/templates/list.html
deleted file mode 100644
index c1b625e..0000000
--- a/app/addons/databases/templates/list.html
+++ /dev/null
@@ -1,26 +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.
--->
-<div class="view">
- <table class="databases table table-striped">
- <thead>
- <th>Name</th>
- <th>Size</th>
- <th># of Docs</th>
- <th>Update Seq</th>
- <th>Actions</th>
- </thead>
- <tbody>
- </tbody>
- </table>
-</div>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/templates/newdatabase.html
----------------------------------------------------------------------
diff --git a/app/addons/databases/templates/newdatabase.html b/app/addons/databases/templates/newdatabase.html
deleted file mode 100644
index bda32b8..0000000
--- a/app/addons/databases/templates/newdatabase.html
+++ /dev/null
@@ -1,20 +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.
-*/%>
-<a id="add-new-database" class="add-new-database-btn" href="#"><i class="header-icon fonticon-new-database"></i> Add New Database</a>
-
-<div class="new-database-tray tray">
- <span class="add-on">Add New Database</span>
- <input id="js-new-database-name" type="text" class="input-xxlarge" placeholder="Name of database">
- <a class="btn" id="js-create-database">Create</a>
-</div>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/tests/actionsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/tests/actionsSpec.js b/app/addons/databases/tests/actionsSpec.js
new file mode 100644
index 0000000..cd69116
--- /dev/null
+++ b/app/addons/databases/tests/actionsSpec.js
@@ -0,0 +1,251 @@
+// 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',
+ 'testUtils',
+ 'addons/databases/stores',
+ 'addons/databases/actions',
+ 'addons/databases/actiontypes',
+ 'addons/databases/resources'
+], function (app, FauxtonAPI, utils, Stores, Actions, ActionTypes, Resources) {
+
+ var assert = utils.assert;
+
+ describe('Databases Actions', function () {
+
+ describe('Initialization', function () {
+
+ var oldDispatch, oldWhen, oldGetParams;
+ var dispatchEvents, thenCallback, alwaysCallback;
+ var databasesMock;
+
+ beforeEach(function () {
+ oldDispatch = FauxtonAPI.dispatch;
+ dispatchEvents = [];
+ FauxtonAPI.dispatch = function (what) {
+ dispatchEvents.push(what);
+ };
+ oldWhen = FauxtonAPI.when;
+ FauxtonAPI.when = function () {
+ return {
+ then: function (callback) {
+ thenCallback = callback;
+ callback();
+ },
+ always: function (callback) {
+ alwaysCallback = callback;
+ callback();
+ }
+ };
+ };
+ // (replace on demand)
+ oldGetParams = app.getParams;
+ databasesMock = {
+ fetch: function () {
+ },
+ paginated: function () {
+ return [];
+ }
+ };
+ });
+
+ afterEach(function () {
+ FauxtonAPI.dispatch = oldDispatch;
+ FauxtonAPI.when = oldWhen;
+ app.getParams = oldGetParams;
+ });
+
+ it('Starts loading first', function () {
+ app.getParams = function () {
+ return {};
+ };
+ Actions.init(databasesMock);
+ assert(!!thenCallback || !!alwaysCallback);
+ // now we should have resolved it all
+ assert.equal(2, dispatchEvents.length);
+ assert.equal(ActionTypes.DATABASES_STARTLOADING, dispatchEvents[0].type);
+ assert.equal(ActionTypes.DATABASES_INIT, dispatchEvents[1].type);
+ assert.equal(1, dispatchEvents[1].options.page);
+ });
+
+ it('Accepts page params', function () {
+ app.getParams = function () {
+ return {
+ page: 33
+ };
+ };
+ Actions.init(databasesMock);
+ // now we should have resolved it all
+ assert.equal(2, dispatchEvents.length);
+ assert.equal(ActionTypes.DATABASES_INIT, dispatchEvents[1].type);
+ assert.equal(33, dispatchEvents[1].options.page);
+ });
+
+ });
+
+ describe('Add database', function () {
+
+ var oldColl, oldBackbone, oldRouter, oldNotification, oldDispatch;
+ var passedId, doneCallback, errorCallback, navigationTarget, notificationText, dispatchEvents;
+
+ beforeEach(function () {
+ oldColl = Stores.databasesStore._collection;
+ oldBackbone = Stores.databasesStore._backboneCollection;
+ passedId = null;
+ Stores.databasesStore._backboneCollection = {};
+ Stores.databasesStore._backboneCollection.model = function (options) {
+ passedId = options.id;
+ return {
+ "save": function () {
+ var res = {
+ "done": function (callback) {
+ doneCallback = callback;
+ return res;
+ },
+ "error": function (callback) {
+ errorCallback = callback;
+ return res;
+ }
+ };
+ return res;
+ }
+ };
+ };
+ oldRouter = app.router;
+ navigationTarget = null;
+ app.router = {
+ "navigate": function (target) {
+ navigationTarget = target;
+ }
+ };
+ oldNotification = FauxtonAPI.addNotification;
+ notificationText = [];
+ FauxtonAPI.addNotification = function (options) {
+ notificationText.push(options.msg);
+ };
+ oldDispatch = FauxtonAPI.dispatch;
+ dispatchEvents = [];
+ FauxtonAPI.dispatch = function (what) {
+ dispatchEvents.push(what);
+ };
+ });
+
+ afterEach(function () {
+ Stores.databasesStore._collection = oldColl;
+ Stores.databasesStore._backboneCollection = oldBackbone;
+ app.router = oldRouter;
+ FauxtonAPI.addNotification = oldNotification;
+ FauxtonAPI.dispatch = oldDispatch;
+ });
+
+ it("Creates database in backend", function () {
+ Actions.createNewDatabase("testdb");
+ doneCallback();
+ assert.equal("testdb", passedId);
+ assert.equal(1, _.map(dispatchEvents, function (item) {
+ if (item.type === ActionTypes.DATABASES_SET_PROMPT_VISIBLE) {
+ return item;
+ }
+ }).length);
+ assert.equal(2, notificationText.length);
+ assert(notificationText[0].indexOf("Creating") >= 0);
+ assert(notificationText[1].indexOf("success") >= 0);
+ assert(navigationTarget.indexOf("testdb") >= 0);
+ });
+
+ it("Creates no database without name", function () {
+ Actions.createNewDatabase(" ");
+ assert(passedId === null);
+ assert.equal(0, _.map(dispatchEvents, function (item) {
+ if (item.type === ActionTypes.DATABASES_SET_PROMPT_VISIBLE) {
+ return item;
+ }
+ }).length);
+ assert.equal(1, notificationText.length);
+ assert(notificationText[0].indexOf("valid database name") >= 0);
+ });
+
+ it("Shows error message on create fail", function () {
+ Actions.createNewDatabase("testdb");
+ errorCallback({"responseText": JSON.stringify({"reason": "testerror"})});
+ assert.equal("testdb", passedId);
+ assert.equal(2, notificationText.length);
+ assert(notificationText[0].indexOf("Creating") >= 0);
+ assert(notificationText[1].indexOf("failed") >= 0);
+ assert(notificationText[1].indexOf("testerror") >= 0);
+ assert(navigationTarget === null);
+ });
+
+ });
+
+ describe('Jump to database', function () {
+
+ var container, jumpEl, oldNavigate, oldAddNotification, oldGetDatabaseNames, old$;
+ var navigationTarget, notificationText;
+
+ beforeEach(function () {
+ old$ = $;
+ // simulate typeahead
+ $ = function (selector) {
+ var res = old$(selector);
+ res.typeahead = function () {};
+ return res;
+ };
+ oldNavigate = FauxtonAPI.navigate;
+ navigationTarget = null;
+ FauxtonAPI.navigate = function (url) {
+ navigationTarget = url;
+ };
+ oldAddNotification = FauxtonAPI.addNotification;
+ notificationText = [];
+ FauxtonAPI.addNotification = function (options) {
+ notificationText.push(options.msg);
+ };
+ oldGetDatabaseNames = Stores.databasesStore.getDatabaseNames;
+ Stores.databasesStore.getDatabaseNames = function () {
+ return ["db1", "db2"];
+ };
+ });
+
+ afterEach(function () {
+ $ = old$;
+ FauxtonAPI.navigate = oldNavigate;
+ FauxtonAPI.addNotification = oldAddNotification;
+ Stores.databasesStore.getDatabaseNames = oldGetDatabaseNames;
+ });
+
+ it("jumps to an existing DB", function () {
+ Actions.jumpToDatabase("db1");
+ assert(navigationTarget.indexOf("db1") >= 0);
+ assert.equal(0, notificationText.length);
+ });
+
+ it("does nothing on empty name", function () {
+ Actions.jumpToDatabase(" ");
+ assert(navigationTarget === null);
+ assert.equal(0, notificationText.length);
+ });
+
+ it("shows a message on non-existent DB", function () {
+ Actions.jumpToDatabase("db3");
+ assert(navigationTarget === null);
+ assert.equal(1, notificationText.length);
+ assert(notificationText[0].indexOf("not exist") >= 0);
+ });
+
+ });
+
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/tests/componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/databases/tests/componentsSpec.react.jsx b/app/addons/databases/tests/componentsSpec.react.jsx
new file mode 100644
index 0000000..5a790a4
--- /dev/null
+++ b/app/addons/databases/tests/componentsSpec.react.jsx
@@ -0,0 +1,190 @@
+// 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/databases/components.react',
+ 'addons/databases/actions',
+ 'addons/databases/stores',
+ 'testUtils',
+ "react"
+], function (FauxtonAPI, Views, Actions, Stores, utils, React) {
+
+ var assert = utils.assert;
+ var TestUtils = React.addons.TestUtils;
+
+ describe('DatabasesController', function () {
+
+ var container, dbEl, oldGetCollection;
+
+ beforeEach(function () {
+ // define our own collection
+ oldGetCollection = Stores.databasesStore.getCollection;
+ Stores.databasesStore.getCollection = function () {
+ return [
+ {
+ "get": function (what) {
+ if ("name" === what) {
+ return "db1";
+ } else {
+ throw "Unknown get('" + what + "')";
+ }
+ },
+ "status": {
+ "dataSize": function () {
+ return 2 * 1024 * 1024;
+ },
+ "numDocs": function () {
+ return 88;
+ },
+ "isGraveYard": function () {
+ return false;
+ },
+ "updateSeq": function () {
+ return 99;
+ }
+ }
+ },
+ {
+ "get": function (what) {
+ if ("name" === what) {
+ return "db2";
+ } else {
+ throw "Unknown get('" + what + "')";
+ }
+ },
+ "status": {
+ "dataSize": function () {
+ return 1024;
+ },
+ "numDocs": function () {
+ return 188;
+ },
+ "numDeletedDocs": function () {
+ return 222;
+ },
+ "isGraveYard": function () {
+ return true;
+ },
+ "updateSeq": function () {
+ return 399;
+ }
+ }
+ }
+ ];
+ };
+ container = document.createElement('div');
+ dbEl = React.renderComponent(React.createElement(Views.DatabasesController, {}), container);
+ });
+
+ afterEach(function () {
+ Stores.databasesStore.getCollection = oldGetCollection;
+ React.unmountComponentAtNode(container);
+ });
+
+ it('renders base data of DBs', function () {
+ assert.equal(1 + 2, dbEl.getDOMNode().getElementsByTagName('tr').length);
+ assert.equal("db1", dbEl.getDOMNode().getElementsByTagName('tr')[1].getElementsByTagName('td')[0].innerText.trim());
+ assert.equal("2.0 MB", dbEl.getDOMNode().getElementsByTagName('tr')[1].getElementsByTagName('td')[1].innerText.trim());
+ assert.equal("88", dbEl.getDOMNode().getElementsByTagName('tr')[1].getElementsByTagName('td')[2].innerText.trim());
+ assert.equal(0, dbEl.getDOMNode().getElementsByTagName('tr')[1].getElementsByTagName('td')[2].getElementsByTagName("i").length);
+ assert.equal(2, dbEl.getDOMNode().getElementsByTagName('tr')[1].getElementsByTagName('td')[4].getElementsByTagName("a").length);
+ assert.equal("db2", dbEl.getDOMNode().getElementsByTagName('tr')[2].getElementsByTagName('td')[0].innerText.trim());
+ assert.equal(1, dbEl.getDOMNode().getElementsByTagName('tr')[2].getElementsByTagName('td')[2].getElementsByTagName("i").length);
+ });
+
+ });
+
+ describe('AddDatabaseWidget', function () {
+
+ var container, addEl, oldCreateNewDatabase;
+ var createCalled, passedDbName;
+
+ beforeEach(function () {
+ oldCreateNewDatabase = Actions.createNewDatabase;
+ Actions.createNewDatabase = function (dbName) {
+ createCalled = true;
+ passedDbName = dbName;
+ };
+ container = document.createElement('div');
+ addEl = React.renderComponent(React.createElement(Views.AddDatabaseWidget, {}), container);
+ });
+
+ afterEach(function () {
+ Actions.createNewDatabase = oldCreateNewDatabase;
+ React.unmountComponentAtNode(container);
+ });
+
+ it("Creates a database with given name", function () {
+ createCalled = false;
+ passedDbName = null;
+ TestUtils.findRenderedDOMComponentWithTag(addEl, 'input').getDOMNode().value = "testdb";
+ addEl.onAddDatabase();
+ assert.equal(true, createCalled);
+ assert.equal("testdb", passedDbName);
+ });
+
+ });
+
+ describe('JumpToDatabaseWidget', function () {
+
+ var container, jumpEl, oldJumpToDatabase, oldGetDatabaseNames, old$;
+ var jumpCalled, passedDbName;
+
+ beforeEach(function () {
+ old$ = $;
+ // simulate typeahead
+ $ = function (selector) {
+ var res = old$(selector);
+ res.typeahead = function () {};
+ return res;
+ };
+ oldJumpToDatabase = Actions.jumpToDatabase;
+ Actions.jumpToDatabase = function (dbName) {
+ jumpCalled = true;
+ passedDbName = dbName;
+ };
+ oldGetDatabaseNames = Stores.databasesStore.getDatabaseNames;
+ Stores.databasesStore.getDatabaseNames = function () {
+ return ["db1", "db2"];
+ };
+ container = document.createElement('div');
+ jumpEl = React.renderComponent(React.createElement(Views.JumpToDatabaseWidget, {}), container);
+ });
+
+ afterEach(function () {
+ $ = old$;
+ Actions.jumpToDatabase = oldJumpToDatabase;
+ Stores.databasesStore.getDatabaseNames = oldGetDatabaseNames;
+ React.unmountComponentAtNode(container);
+ });
+
+ it("Jumps to a database with given name", function () {
+ jumpCalled = false;
+ passedDbName = null;
+ jumpEl.jumpToDb("db1");
+ assert.equal(true, jumpCalled);
+ assert.equal("db1", passedDbName);
+ });
+
+ it("jumps to an existing DB from input", function () {
+ jumpCalled = false;
+ passedDbName = null;
+ TestUtils.findRenderedDOMComponentWithTag(jumpEl, 'input').getDOMNode().value = "db2";
+ jumpEl.jumpToDb();
+ assert.equal(true, jumpCalled);
+ assert.equal("db2", passedDbName);
+ });
+
+ });
+
+});
+
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/tests/resourcesSpec.js b/app/addons/databases/tests/resourcesSpec.js
index cf3289d..d3c0bab 100644
--- a/app/addons/databases/tests/resourcesSpec.js
+++ b/app/addons/databases/tests/resourcesSpec.js
@@ -12,9 +12,8 @@
define([
'api',
'addons/databases/resources',
- 'addons/databases/views',
'testUtils'
-], function (FauxtonAPI, Resources, Views, testUtils) {
+], function (FauxtonAPI, Resources, testUtils) {
var assert = testUtils.assert,
ViewSandbox = testUtils.ViewSandbox;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/tests/storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/tests/storesSpec.js b/app/addons/databases/tests/storesSpec.js
new file mode 100644
index 0000000..2eb9fbc
--- /dev/null
+++ b/app/addons/databases/tests/storesSpec.js
@@ -0,0 +1,74 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ 'app',
+ 'api',
+ 'testUtils',
+ 'addons/databases/stores',
+ 'addons/databases/actiontypes',
+ 'addons/databases/resources'
+], function (app, FauxtonAPI, utils, Stores, ActionTypes, Resources) {
+
+ var assert = utils.assert;
+
+ describe('Databases Store', function () {
+
+ var oldColl, oldBackbone;
+ var passedId, doneCallback, errorCallback, navigationTarget;
+
+ beforeEach(function () {
+ oldColl = Stores.databasesStore._collection;
+ oldBackbone = Stores.databasesStore._backboneCollection;
+ Stores.databasesStore._backboneCollection = {};
+ });
+
+ afterEach(function () {
+ Stores.databasesStore._collection = oldColl;
+ Stores.databasesStore._backboneCollection = oldBackbone;
+ });
+
+ it("inits based on what we pass", function () {
+ Stores.databasesStore.init({"name": "col1"}, {"name": "col2"});
+ assert.equal("col1", Stores.databasesStore.getCollection().name);
+ assert.equal("col2", Stores.databasesStore._backboneCollection.name);
+ });
+
+ describe("database collection info", function () {
+
+ beforeEach(function () {
+ Stores.databasesStore._backboneCollection.toJSON = function () {
+ return {
+ "db1": {
+ "name": "db1"
+ },
+ "db2": {
+ "name": "db2"
+ }
+ };
+ };
+ });
+
+ it("determines database names", function () {
+ assert.ok(JSON.stringify(["db1", "db2"]) == JSON.stringify(Stores.databasesStore.getDatabaseNames().sort()));
+ });
+
+ it("determines database availability", function () {
+ assert(Stores.databasesStore.doesDatabaseExist("db1"));
+ assert(!Stores.databasesStore.doesDatabaseExist("db3"));
+ });
+
+ });
+
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/databases/views.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/views.js b/app/addons/databases/views.js
deleted file mode 100644
index cbbebb1..0000000
--- a/app/addons/databases/views.js
+++ /dev/null
@@ -1,242 +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',
- 'addons/fauxton/components',
- 'api',
- 'addons/databases/resources'
-],
-
-function (app, Components, FauxtonAPI, Databases) {
-
- var Views = {};
-
- Views.Footer = FauxtonAPI.View.extend({
- template: 'addons/databases/templates/footer_alldbs',
- });
-
- Views.RightAllDBsHeader = FauxtonAPI.View.extend({
- className: 'header-right',
- template: 'addons/databases/templates/header_alldbs',
-
- beforeRender: function () {
- this.headerSearch = this.insertView('#header-search', new JumpToDBView({
- collection: this.collection
- }));
-
- this.newbutton = this.insertView('#add-db-button', new NewDatabaseView({
- collection: this.collection
- }));
- }
- });
-
- Views.Item = FauxtonAPI.View.extend({
- template: 'addons/databases/templates/item',
- tagName: 'tr',
-
- establish: function () {
- return [this.model.fetch()];
- },
-
- serialize: function () {
- return {
- encoded: app.utils.safeURLName(this.model.get('name')),
- database: this.model
- };
- },
-
- afterRender: function () {
- this.$el.find('.js-db-graveyard').tooltip();
- }
- });
-
- Views.List = FauxtonAPI.View.extend({
- template: 'addons/databases/templates/list',
- events: {
- 'click button.all': 'selectAll'
- },
-
- initialize: function (options) {
- var params = app.getParams();
- },
-
- serialize: function () {
- return {
- databases: this.collection
- };
- },
- establish: function () {
- var currentDBs = this.paginated();
- var deferred = FauxtonAPI.Deferred();
-
- FauxtonAPI.when(currentDBs.map(function (database) {
- return database.status.fetchOnce();
- })).always(function (resp) {
- //make this always so that even if a user is not allowed access to a database
- //they will still see a list of all databases
- deferred.resolve();
- });
- return [deferred];
- },
-
- paginated: function () {
- var start = (this.page - 1) * this.perPage;
- var end = this.page * this.perPage;
- return this.collection.slice(start, end);
- },
-
- beforeRender: function () {
- _.each(this.paginated(), function (database) {
- this.insertView('table.databases tbody', new Views.Item({
- model: database
- }));
- }, this);
- },
-
- setPage: function (page) {
- this.page = page || 1;
- },
-
- selectAll: function (event) {
- $('input:checkbox').attr('checked', !$(event.target).hasClass('active'));
- }
- });
-
-
- // private Views
-
- var JumpToDBView = FauxtonAPI.View.extend({
- template: 'addons/databases/templates/jump_to_db',
- events: {
- 'submit form#jump-to-db': 'switchDatabaseHandler'
- },
-
- initialize: function () {
- var params = app.getParams();
- this.page = params.page ? parseInt(params.page, 10) : 1;
- this.listenTo(FauxtonAPI.Events, 'jumptodb:update', this.switchDatabase);
- },
-
- establish: function () {
- var currentDBs = this.paginated();
- var deferred = FauxtonAPI.Deferred();
-
- FauxtonAPI.when(currentDBs.map(function (database) {
- return database.status.fetchOnce();
- })).always(function (resp) {
- // make this always so that even if a user is not allowed access to a database
- // they will still see a list of all databases
- deferred.resolve();
- });
- return [deferred];
- },
-
- switchDatabase: function (selectedName) {
- var dbname = this.$el.find('[name="search-query"]').val().trim();
-
- if (selectedName) {
- dbname = selectedName;
- }
- if (dbname && this.collection.where({ id: app.utils.safeURLName(dbname) }).length > 0) {
- // TODO: switch to using a model, or Databases.databaseUrl()
- // Neither of which are in scope right now
- // var db = new Database.Model({id: dbname});
- var url = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(dbname), '');
- FauxtonAPI.navigate(url);
- } else {
- FauxtonAPI.addNotification({
- msg: 'Database does not exist.',
- type: 'error'
- });
- }
- },
-
- switchDatabaseHandler: function (event) {
- event.preventDefault();
- this.switchDatabase();
- },
-
- afterRender: function () {
- var AllDBsArray = _.map(this.collection.toJSON(), function (item, key) {
- return item.name;
- });
-
- this.dbSearchTypeahead = new Components.Typeahead({
- el: 'input.search-autocomplete',
- source: AllDBsArray,
- onUpdateEventName: 'jumptodb:update'
- });
- this.dbSearchTypeahead.render();
- }
- });
-
- var NewDatabaseView = Components.Tray.extend({
- template: 'addons/databases/templates/newdatabase',
- events: {
- 'click #js-create-database': 'createDatabase',
- 'keyup #js-new-database-name': 'processKey'
- },
-
- initialize: function () {
- this.initTray({ toggleTrayBtnSelector: '#add-new-database' });
- },
-
- processKey: function (e) {
- if (e.which === 13) {
- this.createDatabase(e);
- }
- },
-
- createDatabase: function (e) {
- e.preventDefault();
-
- var databaseName = $.trim(this.$('#js-new-database-name').val());
- if (databaseName.length === 0) {
- FauxtonAPI.addNotification({
- msg: 'Please enter a valid database name',
- type: 'error',
- clear: true
- });
- return;
- }
- this.hideTray();
-
- var db = new this.collection.model({
- id: databaseName,
- name: databaseName
- });
- FauxtonAPI.addNotification({ msg: 'Creating database.' });
-
- db.save().done(function () {
- FauxtonAPI.addNotification({
- msg: 'Database created successfully',
- type: 'success',
- clear: true
- });
- var route = '#/database/' + app.utils.safeURLName(databaseName) + '/_all_docs?limit=' + Databases.DocLimit;
- app.router.navigate(route, { trigger: true });
- }
- ).error(function (xhr) {
- var responseText = JSON.parse(xhr.responseText).reason;
- FauxtonAPI.addNotification({
- msg: 'Create database failed: ' + responseText,
- type: 'error',
- clear: true
- });
- }
- );
- }
- });
-
- return Views;
-});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/fauxton/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/components.react.jsx b/app/addons/fauxton/components.react.jsx
index 57ced03..5e7e891 100644
--- a/app/addons/fauxton/components.react.jsx
+++ b/app/addons/fauxton/components.react.jsx
@@ -83,10 +83,149 @@ function (app, FauxtonAPI, React, ZeroClipboard) {
}
});
+ var _NextTrayInternalId = 0;
+ var Tray = React.createClass({
+
+ getInitialState: function () {
+ return {
+ show: false,
+ internalid: (_NextTrayInternalId++)
+ };
+ },
+
+ toggle: function (done) {
+ if (this.state.show) {
+ this.hide(done);
+ } else {
+ this.show(done);
+ }
+ },
+
+ setVisible: function (visible, done) {
+ if (this.state.show && !visible) {
+ this.hide(done);
+ } else if (!this.state.show && visible) {
+ this.show(done);
+ }
+ },
+
+ componentDidMount: function () {
+ $('body').on('click.Tray-' + this.state.internalid, function (e) {
+ var tgt = $(e.target);
+ if (this.state.show && tgt.closest('.tray').length === 0) {
+ this.hide();
+ }
+ }.bind(this));
+ },
+
+ componentWillUnmount: function () {
+ $('body').off('click.Tray-' + this.state.internalid);
+ },
+
+ show: function (done) {
+ this.setState({show: true});
+ $(this.refs.myself.getDOMNode()).velocity('transition.slideDownIn', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
+ if (done) {
+ done(true);
+ }
+ });
+ },
+
+ hide: function (done) {
+ $(this.refs.myself.getDOMNode()).velocity('reverse', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
+ this.setState({show: false});
+ if (done) {
+ done(false);
+ }
+ }.bind(this));
+ },
+
+ render: function () {
+ var styleSpec = this.state.show ? {"display": "block", "opacity": 1} : {"display": "none", "opacity": 0};
+ var classSpec = this.props.className || "";
+ classSpec += " tray";
+ return (
+ <div ref="myself" style={styleSpec} className={classSpec}>{this.props.children}</div>
+ );
+ }
+ });
+
+ var Pagination = React.createClass({
+
+ getInitialState: function () {
+ return {};
+ },
+
+ getDefaultProps: function () {
+ return {
+ perPage: FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE,
+ page: 1,
+ total: 0
+ };
+ },
+
+ getVisiblePages: function (page, totalPages) {
+ var from, to;
+ if (totalPages < 10) {
+ from = 1;
+ to = totalPages + 1;
+ } else {
+ from = page - 5;
+ to = page + 5;
+ if (from <= 1) {
+ from = 1;
+ to = 11;
+ }
+ if (to > totalPages + 1) {
+ from = totalPages - 9;
+ to = totalPages + 1;
+ }
+ }
+ return {
+ from: from,
+ to: to
+ };
+ },
+
+ createItemsForPage: function (visiblePages, page, prefix, suffix) {
+ return _.range(visiblePages.from, visiblePages.to).map(function (i) {
+ return (
+ <li key={i} className={(page === i ? "active" : null)}>
+ <a href={prefix + i + suffix}>{i}</a>
+ </li>
+ );
+ });
+ },
+
+ render: function () {
+ var page = this.state.page || this.props.page;
+ var total = this.state.total || this.props.total;
+ var perPage = this.props.perPage;
+ var prefix = this.props.urlPrefix || "";
+ var suffix = this.props.urlSuffix || "";
+ var totalPages = total === 0 ? 1 : Math.ceil(total / perPage);
+ var visiblePages = this.getVisiblePages(page, totalPages);
+ var rangeItems = this.createItemsForPage(visiblePages, page, prefix, suffix);
+ return (
+ <ul className="pagination">
+ <li className={(page === 1 ? "disabled" : null)}>
+ <a href={prefix + Math.max(page - 1, 1) + suffix}>«</a>
+ </li>
+ {rangeItems}
+ <li className={(page < totalPages ? null : "disabled")}>
+ <a href={prefix + Math.min(page + 1, totalPages) + suffix}>»</a>
+ </li>
+ </ul>
+ );
+ }
+ });
+
return {
Clipboard: Clipboard,
- CodeFormat: CodeFormat
+ CodeFormat: CodeFormat,
+ Tray: Tray,
+ Pagination: Pagination
};
});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/187745ca/app/addons/fauxton/tests/componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/tests/componentsSpec.react.jsx b/app/addons/fauxton/tests/componentsSpec.react.jsx
new file mode 100644
index 0000000..52a00d0
--- /dev/null
+++ b/app/addons/fauxton/tests/componentsSpec.react.jsx
@@ -0,0 +1,197 @@
+// 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/fauxton/components.react',
+ 'testUtils',
+ "react"
+], function (FauxtonAPI, Views, utils, React) {
+
+ var assert = utils.assert;
+ var TestUtils = React.addons.TestUtils;
+
+ describe('Tray', function () {
+
+ var container, anotherContainer, trayEl, done, old$;
+ // trace registrations
+ var handlersOn, handlersOff, velocities;
+
+ beforeEach(function () {
+ handlersOn = [];
+ handlersOff = [];
+ velocities = [];
+ done = sinon.spy();
+ // simulate $ to test registration
+ old$ = $;
+ $ = function (what) {
+ this.on = function (id, handler) {
+ handlersOn.push([what, id]);
+ };
+ this.off = function (id) {
+ handlersOff.push([what, id]);
+ };
+ this.velocity = function (trans, speed, callback) {
+ velocities.push([what, trans]);
+ callback();
+ };
+ return this;
+ };
+ container = document.createElement('div');
+ // when we want to control the diff, we have to render directly
+ trayEl = React.renderComponent(React.createElement(Views.Tray, {className: "traytest"}), container);
+ });
+
+ afterEach(function () {
+ $ = old$;
+ React.unmountComponentAtNode(container);
+ if (anotherContainer) {
+ React.unmountComponentAtNode(anotherContainer);
+ }
+ });
+
+ it('renders trayid and custom classes', function () {
+ assert(trayEl.getDOMNode().getAttribute("class").indexOf("traytest") >= 0);
+ });
+
+ it('registers handler with body', function () {
+ assert.equal(1, handlersOn.length);
+ assert.equal("body", handlersOn[0][0]);
+ assert.equal("click.Tray-", handlersOn[0][1].substring(0, "click.Tray-".length));
+ assert.equal(0, handlersOff.length);
+ // also a 2nd time
+ anotherContainer = document.createElement('div');
+ React.renderComponent(React.createElement(Views.Tray, {className: "traytest"}), anotherContainer);
+ assert.equal(2, handlersOn.length);
+ assert.equal("body", handlersOn[1][0]);
+ assert.equal("click.Tray-", handlersOn[1][1].substring(0, "click.Tray-".length));
+ assert.equal(0, handlersOff.length);
+ // (we have different IDs)
+ assert(handlersOn[0][1] != handlersOn[1][1]);
+ // and we also unregister properly
+ var unmountSuccess = React.unmountComponentAtNode(anotherContainer);
+ assert(unmountSuccess);
+ assert.equal(1, handlersOff.length);
+ assert.equal("body", handlersOff[0][0]);
+ assert.equal("click.Tray-", handlersOff[0][1].substring(0, "click.Tray-".length));
+ // (the ID of the 2nd element)
+ assert(handlersOn[1][1] == handlersOff[0][1]);
+ });
+
+ it('is initially closed', function () {
+ assert.equal("none", trayEl.getDOMNode().style.display);
+ });
+
+ it('shows when requested', function () {
+ trayEl.setVisible(true);
+ assert.equal(1, velocities.length);
+ assert.equal("block", trayEl.getDOMNode().style.display);
+ });
+
+ it('hides when requested', function () {
+ trayEl.show();
+ trayEl.setVisible(false);
+ assert.equal(2, velocities.length);
+ assert.equal("none", trayEl.getDOMNode().style.display);
+ });
+
+ it('does nothing when already hidden', function () {
+ trayEl.setVisible(false);
+ assert.equal(0, velocities.length);
+ });
+
+ it('toggles open with callback', function () {
+ trayEl.toggle(done);
+ assert.ok(done.calledOnce);
+ assert.equal(1, velocities.length);
+ assert.equal("block", trayEl.getDOMNode().style.display);
+ });
+
+ it('toggles close again with callback', function () {
+ trayEl.show();
+ trayEl.toggle(done);
+ assert.ok(done.calledOnce);
+ assert.equal(2, velocities.length);
+ assert.equal("none", trayEl.getDOMNode().style.display);
+ });
+
+ });
+
+ describe('Pagination', function () {
+
+ var nvl, container;
+
+ beforeEach(function () {
+ // helper for empty strings
+ nvl = function (str) {
+ return str === null ? "" : str;
+ };
+ container = document.createElement('div');
+ // create element individually to parameterize
+ });
+
+ afterEach(function () {
+ React.unmountComponentAtNode(container);
+ });
+
+ it("renders 20-wise pages per default", function () {
+ var pageEl = React.renderComponent(React.createElement(Views.Pagination, {page: 3, total: 55, urlPrefix: "?prefix=", urlSuffix: "&suffix=88"}), container);
+ var lis = pageEl.getDOMNode().getElementsByTagName("li");
+ assert.equal(1 + 3 + 1, lis.length);
+ assert(nvl(lis[0].getAttribute("class")).indexOf("disabled") < 0);
+ assert(nvl(lis[1].getAttribute("class")).indexOf("active") < 0);
+ assert(nvl(lis[2].getAttribute("class")).indexOf("active") < 0);
+ assert(nvl(lis[3].getAttribute("class")).indexOf("active") >= 0);
+ assert(nvl(lis[4].getAttribute("class")).indexOf("disabled") >= 0);
+ assert.equal("2", lis[2].innerText);
+ assert.equal("?prefix=2&suffix=88", lis[2].getElementsByTagName("a")[0].getAttribute("href"));
+ });
+
+ it("can overwrite collection size", function () {
+ var pageEl = React.renderComponent(React.createElement(Views.Pagination, {perPage: 10, page: 3, total: 55}), container);
+ var lis = pageEl.getDOMNode().getElementsByTagName("li");
+ assert.equal(1 + 6 + 1, lis.length);
+ });
+
+ it("handles large collections properly - beginning", function () {
+ var pageEl = React.renderComponent(React.createElement(Views.Pagination, {page: 3, total: 600}), container);
+ var lis = pageEl.getDOMNode().getElementsByTagName("li");
+ assert.equal(1 + 10 + 1, lis.length);
+ assert(nvl(lis[3].getAttribute("class")).indexOf("active") >= 0);
+ assert.equal("3", lis[3].innerText);
+ assert.equal("7", lis[7].innerText);
+ assert.equal("10", lis[10].innerText);
+ });
+
+ it("handles large collections properly - middle", function () {
+ var pageEl = React.renderComponent(React.createElement(Views.Pagination, {page: 10, total: 600}), container);
+ var lis = pageEl.getDOMNode().getElementsByTagName("li");
+ assert.equal(1 + 10 + 1, lis.length);
+ assert(nvl(lis[6].getAttribute("class")).indexOf("active") >= 0);
+ assert.equal("7", lis[3].innerText);
+ assert.equal("11", lis[7].innerText);
+ assert.equal("14", lis[10].innerText);
+ });
+
+ it("handles large collections properly - end", function () {
+ var pageEl = React.renderComponent(React.createElement(Views.Pagination, {page: 29, total: 600}), container);
+ var lis = pageEl.getDOMNode().getElementsByTagName("li");
+ assert.equal(1 + 10 + 1, lis.length);
+ assert(nvl(lis[9].getAttribute("class")).indexOf("active") >= 0);
+ assert.equal("23", lis[3].innerText);
+ assert.equal("27", lis[7].innerText);
+ assert.equal("30", lis[10].innerText);
+ });
+
+ });
+
+});
+