couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gar...@apache.org
Subject fauxton commit: updated refs/heads/master to 16ec9ab
Date Wed, 28 Jan 2015 10:15:29 GMT
Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master b164d652b -> 16ec9ab45


Index Editor in React.js


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/16ec9ab4
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/16ec9ab4
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/16ec9ab4

Branch: refs/heads/master
Commit: 16ec9ab45a3474b3c26acc2ba2112af5263eb2b4
Parents: b164d65
Author: Garren Smith <garren.smith@gmail.com>
Authored: Mon Jan 19 12:51:10 2015 +0200
Committer: Garren Smith <garren.smith@gmail.com>
Committed: Wed Jan 28 12:15:10 2015 +0200

----------------------------------------------------------------------
 .../databases/tests/nightwatch/createsView.js   |   4 +-
 app/addons/documents/assets/less/documents.less |   5 +
 .../documents/assets/less/viewEditor.less       |  85 +++
 app/addons/documents/index-editor/actions.js    | 169 ++++++
 .../documents/index-editor/actiontypes.js       |  26 +
 .../documents/index-editor/components.react.jsx | 534 +++++++++++++++++++
 app/addons/documents/index-editor/stores.js     | 211 ++++++++
 app/addons/documents/routes-documents.js        |  32 +-
 app/addons/documents/tests/actionsSpec.js       | 292 ++++++++++
 app/addons/documents/tests/storesSpec.js        | 382 +++++++++++++
 .../tests/viewIndex.componentsSpec.react.jsx    | 252 +++++++++
 app/addons/documents/tests/viewsSpec.js         |   4 +-
 app/addons/documents/views-index.js             | 526 +-----------------
 13 files changed, 1990 insertions(+), 532 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/databases/tests/nightwatch/createsView.js
----------------------------------------------------------------------
diff --git a/app/addons/databases/tests/nightwatch/createsView.js b/app/addons/databases/tests/nightwatch/createsView.js
index 59c65f6..0523993 100644
--- a/app/addons/databases/tests/nightwatch/createsView.js
+++ b/app/addons/databases/tests/nightwatch/createsView.js
@@ -48,7 +48,9 @@ module.exports = {
       .click('#nav-header-test_design_doc .dropdown-toggle.icon.fonticon-plus-circled')
       .waitForElementPresent('#nav-header-test_design_doc', waitTime, false)
       .click('#nav-header-test_design_doc a[href="#/database/'+newDatabaseName+'/new_view/test_design_doc"]')
-      .verify.valueContains('#index-name','newView')
+      .waitForElementPresent('#db-views-tabs-nav', waitTime, false)
+      .click('#db-views-tabs-nav')
+      .verify.valueContains('#index-name','new-view')
       .clearValue('#index-name')
       .setValue('#index-name','odd_ids')
       .execute('\

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/assets/less/documents.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/documents.less b/app/addons/documents/assets/less/documents.less
index d752de9..ed28272 100644
--- a/app/addons/documents/assets/less/documents.less
+++ b/app/addons/documents/assets/less/documents.less
@@ -14,6 +14,7 @@
 @import "../../../../../assets/less/bootstrap/variables.less";
 @import "../../../../../assets/less/bootstrap/mixins.less";
 @import "queryOptions.less";
+@import "viewEditor.less";
 @import "changes.less";
 @import "sidenav.less";
 
@@ -66,6 +67,10 @@ button.beautify {
   padding: 20px;
 }
 
+button.delete {
+  margin-left: 4px;
+}
+
 button.string-edit {
   position: absolute;
   padding: 0;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/assets/less/viewEditor.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/viewEditor.less b/app/addons/documents/assets/less/viewEditor.less
new file mode 100644
index 0000000..37fc797
--- /dev/null
+++ b/app/addons/documents/assets/less/viewEditor.less
@@ -0,0 +1,85 @@
+// 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.
+
+@import "../../../../../assets/less/animations.less";
+@import "../../../../../assets/less/variables.less";
+
+.keyframes(fadeInDownNoReduce, {
+    opacity: 0;
+    height: 0px;
+},
+{
+    opacity: 1;
+    height: 546px;
+});
+
+
+.keyframes(fadeInDownReduce, {
+    opacity: 0;
+    height: 0px;
+},
+{
+    opacity: 1;
+    height: 745px;
+});
+
+.keyframes(fadeOutUpReduce, {
+    opacity: 1;
+    height: 745px;
+},
+{
+    opacity: 0;
+    height: 0px;
+});
+
+.keyframes(fadeOutUpNoReduce, {
+    opacity: 1;
+    height: 546px;
+},
+{
+    opacity: 0;
+    height: 0px;
+});
+
+
+.fadeInDownNoReduce-enter {
+ .animation(fadeInDownNoReduce 1s both);
+}
+
+.fadeInDownNoReduce-leave {
+ .animation(fadeOutUpNoReduce 1s both);
+}
+
+.fadeInDownReduce-enter {
+ .animation(fadeInDownReduce 1s both);
+}
+
+.fadeInDownReduce-leave {
+ .animation(fadeOutUpReduce 1s both);
+}
+
+#dashboard-upper-content{
+  .editor-wrapper {
+    padding-bottom: 70px;
+  }
+
+  .tab-content {
+    height: auto;
+    padding-top: 70px;
+  }
+
+  #define-view {
+    .help-link {
+      margin-left: 3px;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/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
new file mode 100644
index 0000000..e8d9a41
--- /dev/null
+++ b/app/addons/documents/index-editor/actions.js
@@ -0,0 +1,169 @@
+// 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/resources',
+  'addons/documents/index-editor/actiontypes'
+],
+function (app, FauxtonAPI, Documents, ActionTypes) {
+  var ActionHelpers = {
+    createNewDesignDoc: function (id, database) {
+      var designDoc = {
+        _id: id,
+        views: {
+        }
+      };
+
+      return new Documents.Doc(designDoc, {database: database});
+    },
+
+    findDesignDoc: function (designDocs, designDocId) {
+      return designDocs.find(function (doc) {
+        return doc.id === designDocId;
+      }).dDocModel();
+
+    }
+  };
+
+  return {
+    //helpers are added here for use in testing actions
+    helpers: ActionHelpers,
+    toggleEditor: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.TOGGLE_EDITOR
+      });
+    },
+
+    selectReduceChanged: function (reduceOption) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.SELECT_REDUCE_CHANGE,
+        reduceSelectedOption: reduceOption
+      });
+    },
+
+    newDesignDoc: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.NEW_DESIGN_DOC
+      });
+    },
+
+    designDocChange: function (id, newDesignDoc) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.DESIGN_DOC_CHANGE,
+        newDesignDoc: newDesignDoc,
+        designDocId: id
+      });
+    },
+
+    editIndex: function (options) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.EDIT_INDEX,
+        options: options
+      });
+    },
+
+    saveView: function (viewInfo) {
+      var designDoc;
+      var designDocs = viewInfo.designDocs;
+
+      if (_.isUndefined(viewInfo.designDocId)) {
+        FauxtonAPI.addNotification({
+          msg:  "Please enter a design doc name.",
+          type: "error",
+          clear: true
+        });
+
+        return;
+      }
+
+      if (viewInfo.newDesignDoc) {
+        designDoc = ActionHelpers.createNewDesignDoc(viewInfo.designDocId, viewInfo.database);
+
+      } else {
+        designDoc = ActionHelpers.findDesignDoc(designDocs, viewInfo.designDocId);
+      }
+
+      var result = designDoc.setDdocView(viewInfo.viewName,
+                            viewInfo.map,
+                            viewInfo.reduce);
+      
+      if (result) {
+        FauxtonAPI.dispatch({
+         type: ActionTypes.SAVE_VIEW
+        });
+
+        FauxtonAPI.addNotification({
+          msg:  "Saving View...",
+          type: "info",
+          clear: true
+        });
+
+        designDoc.save().then(function () {
+          FauxtonAPI.addNotification({
+            msg:  "View Saved.",
+            type: "success",
+            clear: true
+          });
+
+          if (_.any([viewInfo.designDocChanged, viewInfo.newDesignDoc, viewInfo.newView])) {
+            FauxtonAPI.dispatch({
+              type: ActionTypes.VIEW_SAVED
+            });
+
+            var fragment = '/database/' + 
+              viewInfo.database.safeID() +
+              '/' + designDoc.safeID() + 
+              '/_view/' +
+              app.utils.safeURLName(viewInfo.viewName);
+
+            FauxtonAPI.navigate(fragment);
+
+            //This should be changed to a dispatch once implemented
+            FauxtonAPI.triggerRouteEvent('reloadDesignDocs', {
+              selectedTab: app.utils.removeSpecialCharacters(designDoc.id.replace(/_design\//,'')) + '_' + app.utils.removeSpecialCharacters(viewInfo.viewName)
+            });
+          } else {
+            FauxtonAPI.dispatch({
+              type: ActionTypes.VIEW_SAVED
+            });
+            //This will should be changed to a dispatch once implemented
+            FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: designDoc.id, view: viewInfo.viewName});
+          }
+        });
+      }
+    },
+
+    deleteView: function (options) {
+      var viewName = options.viewName;
+      var database = options.database;
+      var designDoc = ActionHelpers.findDesignDoc(options.designDocs, options.designDocId);
+      var promise;
+
+      designDoc.removeDdocView(viewName);
+
+      if (designDoc.hasViews()) {
+        promise = designDoc.save();
+      } else {
+        promise = designDoc.destroy();
+      }
+
+      promise.then(function () {
+        FauxtonAPI.navigate('/database/' + database.safeID() + '/_all_docs?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT);
+        FauxtonAPI.triggerRouteEvent('reloadDesignDocs');
+      });
+
+    }
+  };
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/index-editor/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-editor/actiontypes.js b/app/addons/documents/index-editor/actiontypes.js
new file mode 100644
index 0000000..26d1698
--- /dev/null
+++ b/app/addons/documents/index-editor/actiontypes.js
@@ -0,0 +1,26 @@
+// 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 {
+    EDIT_INDEX: 'EDIT_INDEX',
+    EDIT_NEW_INDEX: 'EDIT_NEW_INDEX',
+    TOGGLE_EDITOR: 'TOGGLE_EDITOR',
+    SELECT_REDUCE_CHANGE: 'SELECT_REDUCE_CHANGE',
+    VIEW_SAVED: 'VIEW_SAVED',
+    VIEW_CREATED: 'VIEW_CREATED',
+    SAVE_VIEW: 'SAVE_VIEW',
+    DESIGN_DOC_CHANGE: 'DESIGN_DOC_CHANGE',
+    NEW_DESIGN_DOC: 'NEW_DESIGN_DOC'
+  };
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/index-editor/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-editor/components.react.jsx b/app/addons/documents/index-editor/components.react.jsx
new file mode 100644
index 0000000..acecbeb
--- /dev/null
+++ b/app/addons/documents/index-editor/components.react.jsx
@@ -0,0 +1,534 @@
+// 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/documents/index-editor/stores',
+  'addons/documents/index-editor/actions',
+  'addons/fauxton/components',
+  'plugins/beautify'
+],
+
+function(app, FauxtonAPI, React, Stores, Actions, Components, beautifyHelper) {
+  var indexEditorStore = Stores.indexEditorStore;
+  var getDocUrl = app.helpers.getDocUrl;
+
+  var ToggleButton = React.createClass({
+
+    render: function() {
+      return (
+        <div className="dashboard-upper-menu">
+          <ul className="nav nav-tabs" id="db-views-tabs-nav">
+            <li>
+              <a ref="toggle" data-bypass="true" id="index-nav" data-toggle="tab" href="#index" onClick={this.props.toggleEditor}>
+                <i className="fonticon-wrench fonticon"></i>
+                {this.props.title}
+              </a>
+            </li>
+          </ul>
+        </div>
+      );
+    }
+  });
+
+  var DesignDocSelector = React.createClass({
+
+    getStoreState: function () {
+      return {
+        designDocId: indexEditorStore.getDesignDocId(),
+        designDocs: indexEditorStore.getDesignDocs(),
+        newDesignDoc: indexEditorStore.isNewDesignDoc()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getNewDesignDocInput: function () {
+      return (
+        <div id="new-ddoc-section" className="span5">
+          <label className="control-label" htmlFor="new-ddoc"> _design/ </label>
+          <div className="controls">
+            <input value={this.state.designDoc} type="text" id="new-ddoc" onChange={this.onDesignDocChange} placeholder="newDesignDoc" />
+          </div>
+        </div>
+      );
+    },
+
+    onDesignDocChange: function (event) {
+      Actions.designDocChange('_design/' + event.target.value, true);
+    },
+
+    getDesignDocOptions: function () {
+      return this.state.designDocs.map(function (doc, i) {
+        return <option key={i} value={doc.id}> {doc.id} </option>;
+      });
+    },
+
+    render: function () {
+      var designDocOptions = this.getDesignDocOptions();
+      var designDocInput;
+      var designDocId = this.state.designDocId;
+
+      if (this.state.newDesignDoc) {
+        designDocInput = this.getNewDesignDocInput();
+        designDocId = 'new';
+      }
+
+      return (
+        <div className="control-group design-doc-group">
+          <div className="span3">
+            <label htmlFor="ddoc">Save to Design Document
+              <a className="help-link" data-bypass="true" href={getDocUrl('DESIGN_DOCS')} target="_blank">
+                <i className="icon-question-sign">
+                </i>
+              </a>
+            </label>
+            <select id="ddoc" value={designDocId} onChange={this.selectChange}>
+              <optgroup label="Select a document">
+                <option value="new">New Design Document </option>
+                {designDocOptions}
+              </optgroup>
+            </select>
+          </div>
+
+          {designDocInput}
+        </div>
+      );
+    },
+
+    selectChange: function (event) {
+      var designDocId = event.target.value;
+
+      if (designDocId === 'new') {
+        Actions.newDesignDoc();
+      } else {
+        Actions.designDocChange(designDocId, false);
+      }
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    componentDidMount: function () {
+      indexEditorStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function() {
+      indexEditorStore.off('change', this.onChange);
+    },
+
+  });
+
+  var Beautify = React.createClass({
+    noOfLines: function () {
+      return this.props.code.split(/\r\n|\r|\n/).length;
+    },
+
+    canBeautify: function () {
+      if (this.noOfLines() === 1) {
+        return true;
+      }
+
+      return false;
+    },
+
+    addTooltip: function () {
+      if (this.canBeautify) {
+        $('.beautify-tooltip').tooltip();
+      }
+    },
+
+    componentDidMount: function () {
+      this.addTooltip();
+    },
+
+    beautify: function (event) {
+      event.preventDefault();
+      var beautifiedCode = beautifyHelper(this.props.code);
+      this.props.beautifiedCode(beautifiedCode);
+
+    },
+
+    render: function () {
+      if(!this.canBeautify()) {
+        return null;
+      }
+
+      return (
+        <button onClick={this.beautify} className="beautify beautify_map btn btn-primary btn-large beautify-tooltip" type="button" data-toggle="tooltip" title="Reformat your minified code to make edits to it.">
+          beautify this code
+        </button>
+      );
+    }
+  });
+
+  var CodeEditor = React.createClass({
+    render: function () {
+      var code = this.aceEditor ? this.aceEditor.getValue() : this.props.code;
+      var docsLink;
+      if (this.props.docs) {
+        docsLink = <a className="help-link" data-bypass="true" href={getDocUrl(this.props.docs)} target="_blank">
+                    <i className="icon-question-sign"></i>
+                   </a>;
+
+      }
+      return (
+        <div className="control-group">
+          <label htmlFor="ace-function">
+            {this.props.title} 
+            {docsLink}
+          </label>
+          <div className="js-editor" id={this.props.id}>{this.props.code}</div>
+          <Beautify code={code} beautifiedCode={this.setEditorValue} />
+        </div>
+      );
+    },
+
+    setEditorValue: function (code) {
+      this.aceEditor.setValue(code);
+      //this is not a good practice normally but because we working with a backbone view as the mapeditor
+      //that keeps the map code state this is the best way to force a render so that the beautify button will hide
+      this.forceUpdate();
+    },
+
+    getValue: function () {
+      return this.aceEditor.getValue();
+    },
+
+    getEditor: function () {
+      return this.aceEditor;
+    },
+
+    componentDidMount: function () {
+      this.aceEditor = new Components.Editor({
+        editorId: this.props.id,
+        mode: 'javascript',
+        couchJSHINT: true
+      });
+      this.aceEditor.render();
+    },
+
+    shouldComponentUpdate: function () {
+      //we don't want to re-render the map editor as we are using backbone underneath
+      //which will cause the editor to break
+      this.aceEditor.editSaved();
+
+      return false;
+    },
+
+    componentWillUnmount: function () {
+      this.aceEditor.remove();
+    },
+
+  });
+
+  var ReduceEditor = React.createClass({
+
+    getStoreState: function () {
+      return {
+        reduce: indexEditorStore.getReduce(),
+        reduceOptions: indexEditorStore.reduceOptions(),
+        reduceSelectedOption: indexEditorStore.reduceSelectedOption(),
+        hasCustomReduce: indexEditorStore.hasCustomReduce(),
+        hasReduce: indexEditorStore.hasReduce()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getOptionsList: function () {
+      return _.map(this.state.reduceOptions, function (reduce, i) {
+        return <option key={i} value={reduce}> {reduce} </option>;
+      }, this);
+
+    },
+
+    getReduceValue: function () {
+      if (!this.state.hasReduce) {
+        return null;
+      }
+
+      if (!this.state.hasCustomReduce) {
+        return this.state.reduce;
+      }
+
+      return this.refs.reduceEditor.getValue();
+    },
+
+    getEditor: function () {
+      return this.refs.reduceEditor.getEditor();
+    },
+
+    render: function () {
+      var reduceOptions = this.getOptionsList(),
+      customReduceSection;
+
+      if (this.state.hasCustomReduce) {
+        //customReduceSection = <CustomReduce ref="reduceEditor" reduce={this.state.reduce} />;
+        customReduceSection = <CodeEditor ref='reduceEditor' id={'reduce-function'} code={this.state.reduce} docs={false} title={'Custom Reduce function'} />;
+      }
+
+      return (
+        <div>
+          <div className="control-group">
+            <label htmlFor="reduce-function-selector">Reduce (optional)<a className="help-link" data-bypass="true" href={getDocUrl('REDUCE_FUNCS')} target="_blank"><i className="icon-question-sign"></i></a></label>
+
+            <select id="reduce-function-selector" value={this.state.reduceSelectedOption} onChange={this.selectChange}>
+              {reduceOptions}
+            </select>
+          </div>
+
+          {customReduceSection}
+        </div>
+      );
+    },
+
+    selectChange: function (event) {
+      Actions.selectReduceChanged(event.target.value);
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    componentDidMount: function () {
+      indexEditorStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function() {
+      indexEditorStore.off('change', this.onChange);
+    },
+
+  });
+
+  var DeleteView = React.createClass({
+    getStoreState: function () {
+      return {
+        isNewView: indexEditorStore.isNewView(),
+        designDocs: indexEditorStore.getDesignDocs(),
+        viewName: indexEditorStore.getViewName(),
+        designDocId: indexEditorStore.getDesignDocId(),
+        database: indexEditorStore.getDatabase()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    render: function () {
+      if (this.state.isNewView) {
+        return null;
+      }
+
+      return (
+        <button onClick={this.deleteView} className="btn btn-danger delete">
+          <i className="icon fonticon-cancel-circled"></i>
+          Delete
+        </button>
+      );
+    },
+
+    deleteView: function (event) {
+      event.preventDefault();
+
+      if (!confirm('Are you sure you want to delete this view?')) {return;}
+
+      Actions.deleteView({
+        designDocs: this.state.designDocs,
+        viewName: this.state.viewName,
+        designDocId: this.state.designDocId,
+        database: this.state.database
+      });
+    }
+
+  });
+
+  var Editor = React.createClass({
+    getStoreState: function () {
+      return {
+        database: indexEditorStore.getDatabase(),
+        isNewView: indexEditorStore.isNewView(),
+        viewName: indexEditorStore.getViewName(),
+        designDocs: indexEditorStore.getDesignDocs(),
+        hasDesignDocChanged: indexEditorStore.hasDesignDocChanged(),
+        newDesignDoc: indexEditorStore.isNewDesignDoc(),
+        designDocId: indexEditorStore.getDesignDocId(),
+        map: indexEditorStore.getMap()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    componentDidMount: function () {
+      indexEditorStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function() {
+      indexEditorStore.off('change', this.onChange);
+    },
+
+    hasValidCode: function() {
+      return _.every(['mapEditor', 'reduceEditor'], function(editorName) {
+        if (editorName === 'reduceEditor' && !indexEditorStore.hasCustomReduce()) {
+          return true;
+        }
+        var editor = this.refs[editorName].getEditor();
+        return editor.hadValidCode();
+      }, this);
+    },
+
+    saveView: function (event) {
+      event.preventDefault();
+
+      if (!this.hasValidCode()) {
+        FauxtonAPI.addNotification({
+          msg:  'Please fix the Javascript errors and try again.',
+          type: 'error',
+          clear: true
+        });
+        return;
+      }
+
+      Actions.saveView({
+        database: this.state.database,
+        newView: this.state.isNewView,
+        viewName: this.state.viewName,
+        designDocId: this.state.designDocId,
+        newDesignDoc: this.state.newDesignDoc,
+        designDocChanged: this.state.hasDesignDocChanged,
+        map: this.refs.mapEditor.getValue(),
+        reduce: this.refs.reduceEditor.getReduceValue(),
+        designDocs: this.state.designDocs
+      });
+    },
+
+    viewChange: function (event) {
+      this.setState({viewName: event.target.value});
+    },
+
+    render: function () {
+      return (
+        <div className="tab-content" >
+          <div className="tab-pane active" id="index">
+            <div id="define-view" className="ddoc-alert well">
+              <form className="form-horizontal view-query-save" onSubmit={this.saveView}>
+
+                <DesignDocSelector />
+
+                <div className="control-group">
+                  <label htmlFor="index-name">Index name<a className="help-link" data-bypass="true" href={getDocUrl('VIEW_FUNCS')} target="_blank"><i className="icon-question-sign"></i></a></label>
+                  <input type="text" id="index-name" value={this.state.viewName} onChange={this.viewChange} placeholder="Index name" />
+                </div>
+
+                <CodeEditor id={'map-function'} ref="mapEditor" title={"Map function"} docs={'MAP_FUNCS'} code={this.state.map}/>
+                <ReduceEditor ref="reduceEditor"/>
+
+                <div className="control-group">
+                  <button className="btn btn-success save"><i className="icon fonticon-ok-circled"></i> Save &amp; Build Index</button>
+                  <DeleteView />
+                </div>
+              </form>
+            </div>
+
+          </div>
+        </div>
+      );
+    }
+  });
+
+  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
+
+  var EditorController = React.createClass({
+    getInitialState: function () {
+      return {
+        showEditor: indexEditorStore.showEditor(),
+        isNewView: indexEditorStore.isNewView(),
+        title: indexEditorStore.getTitle(),
+        hasCustomReduce: indexEditorStore.hasCustomReduce()
+      };
+    },
+
+    onChange: function () {
+      this.setState({showEditor: indexEditorStore.showEditor()});
+    },
+
+    componentDidMount: function() {
+      indexEditorStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function() {
+      indexEditorStore.off('change', this.onChange);
+    },
+
+    toggleEditor: function () {
+      Actions.toggleEditor();
+    },
+
+    render: function () {
+      var editor = null;
+      //a bit of hack for now.
+      var wrapperClassName = 'editor-wrapper';
+      var doTransitions = !this.state.isNewView;
+      var editorTransitionName = 'fadeInDownNoReduce';
+
+      if (this.state.showEditor) {
+        //key is needed for animation;
+        editor = <Editor key={1} />;
+        wrapperClassName = '';
+
+        if (this.state.hasCustomReduce) {
+          editorTransitionName = 'fadeInDownReduce';
+        }
+      }
+
+      return (
+        <div className={wrapperClassName}>
+          <ToggleButton title={this.state.title} toggleEditor={this.toggleEditor} />
+          <ReactCSSTransitionGroup transitionName={editorTransitionName} transitionLeave={doTransitions} transitionEnter={doTransitions}>
+            {editor}
+          </ReactCSSTransitionGroup>
+        </div>
+      );
+    }
+
+  });
+
+  var Views = {
+    renderEditor: function (el) {
+      React.render(<EditorController/>, el);
+    },
+    removeEditor: function (el) {
+      React.unmountComponentAtNode(el);
+    },
+    ToggleButton: ToggleButton,
+    ReduceEditor: ReduceEditor,
+    Editor: Editor,
+    DesignDocSelector: DesignDocSelector,
+    Beautify: Beautify
+  };
+
+  return Views;
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/index-editor/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/index-editor/stores.js b/app/addons/documents/index-editor/stores.js
new file mode 100644
index 0000000..7b9222b
--- /dev/null
+++ b/app/addons/documents/index-editor/stores.js
@@ -0,0 +1,211 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'addons/documents/index-editor/actiontypes'
+],
+
+function(FauxtonAPI, ActionTypes) {
+  var Stores = {};
+
+  Stores.IndexEditorStore = FauxtonAPI.Store.extend({
+
+    defaultMap: 'function(doc) {\n  emit(doc._id, 1);\n}',
+    defaultReduce: 'function(keys, values, rereduce){\n  if (rereduce){\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}',
+
+    editIndex: function (options) {
+      this._database = options.database;
+      this._newView = options.newView;
+      this._newDesignDoc = options.newDesignDoc || false;
+      this._viewName = options.viewName || 'viewName';
+      this._designDocs = options.designDocs;
+      this._designDocId = options.designDocId;
+      this._showEditor = this._newView;
+      this._designDocChanged = false;
+
+      if (!this._newView && !this._newDesignDoc) {
+        this._view = this.getDesignDoc().get('views')[this._viewName];
+      } else {
+        this._view = {
+          reduce: '',
+          map: ''
+        };
+      }
+    },
+
+    getDatabase: function () {
+      return this._database;
+    },
+
+    getMap: function () {
+      if (this._newView) {
+        return this.defaultMap;
+      }
+
+      return this._view.map;
+    },
+
+    getReduce: function () {
+      return this._view.reduce;
+    },
+
+    setReduce: function (reduce) {
+      this._view.reduce = reduce;
+    },
+
+    getDesignDoc: function () {
+      return this._designDocs.find(function (ddoc) {
+        return this._designDocId == ddoc.id;
+      }, this).dDocModel();
+
+    },
+
+    getDesignDocs: function () {
+      return this._designDocs;
+    },
+
+    getDesignDocId: function () {
+      return this._designDocId;
+    },
+
+    setDesignDocId: function (designDocId, newDesignDoc) {
+      this._designDocId = designDocId;
+      this._newDesignDoc = newDesignDoc;
+      this._designDocChanged = true;
+    },
+
+    hasDesignDocChanged: function () {
+      return this._designDocChanged;
+    },
+
+    isNewDesignDoc: function () {
+      return this._newDesignDoc;
+    },
+
+    isNewView: function () {
+      return this._newView;
+    },
+
+    getTitle: function () {
+      return this._newView ? 'Create Index' : 'Edit Index';
+    },
+
+    getViewName: function () {
+      return this._viewName;
+    },
+
+    showEditor: function () {
+      return this._showEditor;
+    },
+
+    hasCustomReduce: function () {
+      if (!this.hasReduce()) {return false; }
+
+      return !_.contains(this.builtInReduces(), this.getReduce());
+    },
+
+    hasReduce: function () {
+      if (!this.getReduce()) { return false; }
+
+      return true;
+    },
+
+    builtInReduces: function () {
+      return ['_sum', '_count', '_stats'];
+    },
+
+    reduceSelectedOption: function () {
+      if (!this.hasReduce()) {
+        return 'NONE';
+      }
+
+      if (this.hasCustomReduce()) {
+        return 'CUSTOM';
+      }
+
+      return this.getReduce();
+    },
+
+    reduceOptions: function () {
+      return this.builtInReduces().concat(['CUSTOM', 'NONE']);
+    },
+
+    updateReduceFromSelect: function (selectedReduce) {
+      if (selectedReduce === 'NONE') {
+        this.setReduce(null);
+        return;
+      }
+
+      if (selectedReduce === 'CUSTOM') {
+        this.setReduce(this.defaultReduce);
+        return;
+      }
+
+      this.setReduce(selectedReduce);
+    },
+
+    dispatch: function (action) {
+      switch(action.type) {
+        case ActionTypes.EDIT_INDEX:
+          this.editIndex(action.options);
+          this.triggerChange();
+        break;
+
+        case ActionTypes.EDIT_NEW_INDEX:
+          this.editIndex(action.options);
+          this.triggerChange();
+        break;
+
+        case ActionTypes.TOGGLE_EDITOR:
+          this._showEditor = !this._showEditor;
+          this.triggerChange();
+        break;
+
+        case ActionTypes.SELECT_REDUCE_CHANGE:
+          this.updateReduceFromSelect(action.reduceSelectedOption);
+          this.triggerChange();
+        break;
+
+        case ActionTypes.DESIGN_DOC_CHANGE:
+          this.setDesignDocId(action.designDocId, action.newDesignDoc); 
+          this.triggerChange();
+        break;
+
+        case ActionTypes.NEW_DESIGN_DOC:
+          this.setDesignDocId('', true); 
+          this.triggerChange();
+        break;
+
+        case ActionTypes.VIEW_SAVED:
+          this.triggerChange();
+        break;
+
+        case ActionTypes.VIEW_CREATED:
+          this.triggerChange();
+        break;
+
+        default:
+          return;
+        // do nothing
+      }
+    }
+
+  });
+
+  Stores.indexEditorStore = new Stores.IndexEditorStore();
+
+  Stores.indexEditorStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.indexEditorStore.dispatch);
+
+  return Stores;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/routes-documents.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index 5b07b1f..c776a48 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -249,14 +249,12 @@ function(app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Datab
         }
       });
 
-      this.viewEditor = this.setView("#dashboard-upper-content", new Index.ViewEditor({
-        model: this.database,
-        ddocs: this.designDocs,
+      this.viewEditor = this.setView("#dashboard-upper-content", new Index.ViewEditorReact({
         viewName: viewName,
-        params: urlParams,
         newView: false,
         database: this.database,
-        ddocInfo: this.ddocInfo(decodeDdoc, this.designDocs, viewName)
+        designDocs: this.designDocs,
+        designDocId: "_design/" + decodeDdoc
       }));
 
       this.toolsView && this.toolsView.remove();
@@ -282,8 +280,8 @@ function(app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Datab
 
     showQueryOptions: function (urlParams, ddoc, viewName) {
       var promise = this.designDocs.fetch({reset: true}),
-          that = this,
-          hasReduceFunction;
+      that = this,
+      hasReduceFunction;
 
       promise.then(function(resp) {
         var design = _.findWhere(that.designDocs.models, {id: '_design/'+ddoc}); 
@@ -343,19 +341,27 @@ function(app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Datab
       }));
     },
 
-    newViewEditor: function (database, designDoc) {
+    newViewEditor: function (database, _designDoc) {
       var params = app.getParams();
+      var newDesignDoc = true;
+      var designDoc;
+        
+      if (!_.isUndefined(_designDoc)) {
+        designDoc = "_design/" + _designDoc;
+        newDesignDoc = false;
+      }
 
       this.footer && this.footer.remove();
       this.toolsView && this.toolsView.remove();
       this.documentsView && this.documentsView.remove();
 
-      this.viewEditor = this.setView("#dashboard-upper-content", new Index.ViewEditor({
-        currentddoc: "_design/" + designDoc || "",
-        ddocs: this.designDocs,
-        params: params,
+      this.viewEditor = this.setView("#dashboard-upper-content", new Index.ViewEditorReact({
+        viewName: 'new-view',
+        newView: true,
         database: this.database,
-        newView: true
+        designDocs: this.designDocs,
+        designDocId: designDoc,
+        newDesignDoc: newDesignDoc
       }));
 
       this.sidebar.setSelectedTab("new-view");

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/tests/actionsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/actionsSpec.js b/app/addons/documents/tests/actionsSpec.js
new file mode 100644
index 0000000..68b2172
--- /dev/null
+++ b/app/addons/documents/tests/actionsSpec.js
@@ -0,0 +1,292 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'addons/documents/index-editor/actions',
+  'addons/documents/resources',
+  'addons/documents/index-editor/actiontypes',
+  'testUtils'
+], function (FauxtonAPI, Actions, Documents, ActionTypes, testUtils) {
+  var assert = testUtils.assert;
+
+  FauxtonAPI.router = new FauxtonAPI.Router([]);
+
+  describe('Index Editor Actions', function () {
+    var database = {
+      safeID: function () { return 'id';}
+    };
+
+    describe('save view', function () {
+      var designDoc, designDocs;
+      beforeEach(function () {
+        designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: 'function () {};',
+            }
+          }
+        };
+
+        designDocs = new Documents.AllDocs([designDoc], {
+          params: { limit: 10 },
+          database: database
+        });
+      });
+
+      afterEach(function () {
+        FauxtonAPI.navigate.restore && FauxtonAPI.navigate.restore();
+        FauxtonAPI.triggerRouteEvent.restore && FauxtonAPI.triggerRouteEvent.restore();
+      });
+
+      it('shows a notification if no design doc id given', function () {
+        var spy = sinon.spy(FauxtonAPI, 'addNotification');
+
+        var viewInfo = {
+          database: database,
+          viewName: 'new-doc',
+          designDocId: undefined,
+          map: 'map',
+          reduce: '_sum',
+          newDesignDoc: true,
+          newView: true,
+          designDocs: designDocs
+        };
+
+        Actions.saveView(viewInfo);
+        assert.ok(spy.calledOnce);
+        FauxtonAPI.addNotification.restore();
+      });
+
+      it('creates new design Doc for new design doc', function () {
+        var spy = sinon.spy(Actions.helpers, 'createNewDesignDoc');
+
+        var viewInfo = {
+          database: database,
+          viewName: 'new-doc',
+          designDocId: '_design/test-doc',
+          map: 'map',
+          reduce: '_sum',
+          newDesignDoc: true,
+          newView: true,
+          designDocs: designDocs
+        };
+
+        Actions.saveView(viewInfo);
+        assert.ok(spy.calledOnce);
+      });
+
+      it('sets the design doc with updated view', function () {
+        var viewInfo = {
+          viewName: 'test-view',
+          designDocId: '_design/test-doc',
+          map: 'map',
+          reduce: '_sum',
+          newDesignDoc: false,
+          newView: true,
+          designDocs: designDocs
+        };
+
+        Actions.saveView(viewInfo);
+
+        var updatedDesignDoc = designDocs.first().dDocModel();
+        assert.equal(updatedDesignDoc.get('views')['test-view'].reduce, '_sum');
+      });
+
+      it('saves doc', function () {
+        var viewInfo = {
+          viewName: 'test-view',
+          designDocId: '_design/test-doc',
+          map: 'map',
+          reduce: '_sum',
+          newDesignDoc: false,
+          newView: true,
+          designDocs: designDocs
+        };
+
+        var updatedDesignDoc = designDocs.first().dDocModel();
+        var spy = sinon.spy(updatedDesignDoc, 'save');
+        Actions.saveView(viewInfo);
+
+        assert.ok(spy.calledOnce);
+      });
+
+      it('navigates to new url for new view', function () {
+        var spy = sinon.spy(FauxtonAPI, 'navigate');
+
+        var viewInfo = {
+          database: database,
+          viewName: 'test-view',
+          designDocId: '_design/test-doc',
+          map: 'map',
+          reduce: '_sum',
+          newDesignDoc: false,
+          newView: true,
+          designDocs: designDocs
+        };
+        var designDoc = designDocs.first();
+
+        designDoc.save = function () {
+          var promise = $.Deferred();
+          promise.resolve(); 
+          return promise;
+        };
+
+        Actions.saveView(viewInfo);
+        assert.ok(spy.calledOnce);
+        assert.ok(spy.getCall(0).args[0].match(/_view\/test-view/));
+      });
+
+      it('triggers update all docs', function () {
+        var spy = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
+
+        var viewInfo = {
+          viewName: 'test-view',
+          designDocId: '_design/test-doc',
+          map: 'map',
+          reduce: '_sum',
+          newDesignDoc: false,
+          newView: false,
+          designDocs: designDocs
+        };
+        var designDoc = designDocs.first();
+
+        designDoc.save = function () {
+          var promise = $.Deferred();
+          promise.resolve(); 
+          return promise;
+        };
+
+        Actions.saveView(viewInfo);
+        assert.ok(spy.calledOnce);
+        assert.equal(spy.getCall(0).args[0], 'updateAllDocs');
+      });
+    });
+
+    describe('delete view', function () {
+      var designDocs, database, designDoc, designDocId, viewName;
+      beforeEach(function () {
+        database = {
+          safeID: function () { return 'safeid';}
+        };
+
+        viewName = 'test-view';
+        designDocId = '_design/test-doc';
+        designDocs = new Documents.AllDocs([{
+          _id: designDocId ,
+          views: {
+              'test-view': {
+                map: 'function () {};',
+              },
+              'test-view2': {
+                map: 'function () {};',
+              }
+            }
+          }], {
+          params: { limit: 10 },
+          database: database
+        });
+
+        designDoc = designDocs.first();
+
+      });
+
+      afterEach(function () {
+        FauxtonAPI.navigate.restore && FauxtonAPI.navigate.restore();
+        FauxtonAPI.triggerRouteEvent.restore && FauxtonAPI.triggerRouteEvent.restore();
+      });
+
+      it('removes view from design doc', function () {
+
+        Actions.deleteView({
+          viewName: viewName,
+          designDocId: designDocId,
+          database: database,
+          designDocs: designDocs
+        });
+
+        assert.ok(_.isUndefined(designDoc.getDdocView(viewName)));
+      });
+
+      it('saves design do if has other views', function () {
+        var spy = sinon.spy(designDoc, 'save');
+
+        Actions.deleteView({
+          viewName: viewName,
+          designDocId: designDocId,
+          database: database,
+          designDocs: designDocs
+        });
+
+        assert.ok(spy.calledOnce);
+      });
+
+      it('deletes design doc if has no other views', function () {
+        var spy = sinon.spy(designDoc, 'destroy');
+        designDoc.removeDdocView('test-view2');
+
+        Actions.deleteView({
+          viewName: viewName,
+          designDocId: designDocId,
+          database: database,
+          designDocs: designDocs
+        });
+
+        assert.ok(spy.calledOnce);
+
+      });
+
+      it('navigates to all docs', function () {
+        var spy = sinon.spy(FauxtonAPI, 'navigate');
+
+        designDoc.save = function () {
+          var promise = $.Deferred();
+          promise.resolve(); 
+          return promise;
+        };
+
+        Actions.deleteView({
+          viewName: viewName,
+          designDocId: designDocId,
+          database: database,
+          designDocs: designDocs
+        });
+
+
+        assert.ok(spy.getCall(0).args[0].match(/_all_docs/));
+        assert.ok(spy.calledOnce);
+      });
+
+      it('triggers design doc reload', function () {
+        var spy = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
+
+        designDoc.save = function () {
+          var promise = $.Deferred();
+          promise.resolve(); 
+          return promise;
+        };
+
+        Actions.deleteView({
+          viewName: viewName,
+          designDocId: designDocId,
+          database: database,
+          designDocs: designDocs
+        });
+
+        assert.ok(spy.calledOnce);
+        assert.equal(spy.getCall(0).args[0], 'reloadDesignDocs');
+      });
+
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/tests/storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/storesSpec.js b/app/addons/documents/tests/storesSpec.js
new file mode 100644
index 0000000..5511a8e
--- /dev/null
+++ b/app/addons/documents/tests/storesSpec.js
@@ -0,0 +1,382 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+  'api',
+  'addons/documents/index-editor/stores',
+  'addons/documents/index-editor/actiontypes',
+  'addons/documents/resources',
+  'testUtils'
+], function (FauxtonAPI, Stores, ActionTypes, Documents, testUtils) {
+  var assert = testUtils.assert;
+  var store;
+  var dispatchToken;
+
+
+  describe('IndexEditorStore', function () {
+
+    beforeEach(function () {
+      store = new Stores.IndexEditorStore();
+      dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch);
+    });
+
+    afterEach(function () {
+      FauxtonAPI.dispatcher.unregister(dispatchToken);
+    });
+
+    describe('TOGGLE EDITOR', function () {
+
+      it('toggles editor', function () {
+        var designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: 'boom'
+            }
+          }
+        };
+
+        var designDocs = new Documents.AllDocs([designDoc], {
+          params: { limit: 10 },
+          database: {
+            safeID: function () { return 'id';}
+          }
+        });
+
+        FauxtonAPI.dispatch({
+          type: ActionTypes.EDIT_NEW_INDEX,
+          options: {
+            newView: false,
+            designDocs: designDocs,
+            designDocId: '_design/test-doc'
+          }
+        });
+
+
+        FauxtonAPI.dispatch({
+          type: ActionTypes.TOGGLE_EDITOR
+        });
+
+        assert.ok(store.showEditor());
+      });
+
+    });
+
+    describe('map editor', function () {
+
+      describe('new view', function () {
+
+        beforeEach(function () {
+
+          FauxtonAPI.dispatch({
+            type: ActionTypes.EDIT_NEW_INDEX,
+            options: {
+              newView: true
+            }
+          });
+        });
+
+        it('returns default map', function () {
+          assert.equal(store.getMap(), 'function(doc) {\n  emit(doc._id, 1);\n}');
+        });
+
+        it('Edit Index as title', function () {
+          assert.equal(store.getTitle(), 'Create Index');
+        });
+      });
+
+    });
+
+    describe('reduce editor', function () {
+
+      describe('has custom reduce', function () {
+
+        it('is false for no reduce', function () {
+          var designDoc = {
+            _id: '_design/test-doc',
+            views: {
+              'test-view': {
+                map: 'function () {};'
+              }
+            }
+          };
+
+          var designDocs = new Documents.AllDocs([designDoc], {
+            params: { limit: 10 },
+            database: {
+              safeID: function () { return 'id';}
+            }
+          });
+
+          FauxtonAPI.dispatch({
+            type: ActionTypes.EDIT_NEW_INDEX,
+            options: {
+              newView: false,
+              viewName: 'test-view',
+              designDocs: designDocs,
+              designDocId: designDoc._id
+            }
+          });
+
+          assert.notOk(store.hasCustomReduce());
+        });
+
+        it('is false for built in reduce', function () {
+          var designDoc = {
+            _id: '_design/test-doc',
+            views: {
+              'test-view': {
+                map: 'function () {};',
+                reduce: '_sum'
+              }
+            }
+          };
+
+          var designDocs = new Documents.AllDocs([designDoc], {
+            params: { limit: 10 },
+            database: {
+              safeID: function () { return 'id';}
+            }
+          });
+          FauxtonAPI.dispatch({
+            type: ActionTypes.EDIT_NEW_INDEX,
+            options: {
+              newView: false,
+              viewName: 'test-view',
+              designDocs: designDocs,
+              designDocId: designDoc._id
+            }
+          });
+
+          assert.notOk(store.hasCustomReduce());
+        });
+
+        it('is true for custom reduce', function () {
+          var designDoc = {
+            _id: '_design/test-doc',
+            views: {
+              'test-view': {
+                map: 'function () {};',
+                reduce: 'function (reduce) { reduce(); }'
+              }
+            }
+          };
+
+          var designDocs = new Documents.AllDocs([designDoc], {
+            params: { limit: 10 },
+            database: {
+              safeID: function () { return 'id';}
+            }
+          });
+
+          FauxtonAPI.dispatch({
+            type: ActionTypes.EDIT_NEW_INDEX,
+            options: {
+              newView: false,
+              viewName: 'test-view',
+              designDocs: designDocs,
+              designDocId: designDoc._id
+            }
+          });
+
+          assert.ok(store.hasCustomReduce());
+        });
+
+      });
+
+      //show default reduce
+      describe('SELECT_REDUCE_CHANGE', function () {
+
+        beforeEach(function () {
+          var designDoc = {
+            _id: '_design/test-doc',
+            views: {
+              'test-view': {
+                map: 'function () {};'
+              }
+            }
+          };
+
+          var designDocs = new Documents.AllDocs([designDoc], {
+            params: { limit: 10 },
+            database: {
+              safeID: function () { return 'id';}
+            }
+          });
+
+          FauxtonAPI.dispatch({
+            type: ActionTypes.EDIT_NEW_INDEX,
+            options: {
+              newView: false,
+              viewName: 'test-view',
+              designDocs: designDocs,
+              designDocId: designDoc._id
+            }
+          });
+        });
+
+        it('NONE returns null reduce', function () {
+          FauxtonAPI.dispatch({
+            type: ActionTypes.SELECT_REDUCE_CHANGE,
+            reduceSelectedOption: 'NONE'
+          });
+          assert.ok(_.isNull(store.getReduce()));
+        });
+
+        it('builtin returns bultin reduce', function () {
+          FauxtonAPI.dispatch({
+            type: ActionTypes.SELECT_REDUCE_CHANGE,
+            reduceSelectedOption: '_sum'
+          });
+          assert.equal(store.getReduce(), '_sum');
+        });
+
+        it('custom returns custom reduce', function () {
+          FauxtonAPI.dispatch({
+            type: ActionTypes.SELECT_REDUCE_CHANGE,
+            reduceSelectedOption: 'CUSTOM'
+          });
+          assert.equal(store.getReduce(), 'function(keys, values, rereduce){\n  if (rereduce){\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}');
+        });
+      });
+    });
+
+
+    describe('design doc selector', function () {
+      var designDoc;
+
+      beforeEach(function () {
+        designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: 'boom'
+            }
+          }
+        };
+
+        var designDocs = new Documents.AllDocs([designDoc], {
+          params: { limit: 10 },
+          database: {
+            safeID: function () { return 'id';}
+          }
+        });
+
+        FauxtonAPI.dispatch({
+          type: ActionTypes.EDIT_INDEX,
+          options: {
+            newView: false,
+            viewName: 'test-view',
+            designDocs: designDocs,
+            designDocId: designDoc._id
+          }
+        });
+      });
+
+      it('DESIGN_DOC_CHANGE changes design doc id', function () {
+        var designDocId =  'another-one';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.DESIGN_DOC_CHANGE,
+          designDocId: designDocId,
+          newDesignDoc: false
+        });
+
+        assert.equal(store.getDesignDocId(), designDocId);
+        assert.notOk(store.isNewDesignDoc());
+      });
+
+      it('sets new design doc on NEW_DESIGN_DOC', function () {
+        FauxtonAPI.dispatch({
+          type: ActionTypes.NEW_DESIGN_DOC
+        });
+
+        assert.ok(store.isNewDesignDoc());
+        assert.equal(store.getDesignDocId(), '');
+      });
+    });
+
+    describe('EDIT_INDEX', function () {
+      var designDoc, designDocs;
+
+      beforeEach(function () {
+        designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: 'boom'
+            }
+          }
+        };
+
+        designDocs = new Documents.AllDocs([designDoc], {
+          params: { limit: 10 },
+          database: {
+            safeID: function () { return 'id';}
+          }
+        });
+
+      });
+
+      it('can set reduce for new design doc', function () {
+        FauxtonAPI.dispatch({
+          type: ActionTypes.EDIT_INDEX,
+          options: {
+            newView: true,
+            newDesignDoc: true,
+            viewName: 'test-view',
+            designDocs: designDocs,
+            designDocId: undefined
+          }
+        });
+
+        FauxtonAPI.dispatch({
+          type: ActionTypes.SELECT_REDUCE_CHANGE,
+          reduceSelectedOption: '_sum'
+        });
+
+        assert.equal(store.getReduce(), '_sum');
+      });
+
+      it('showEditor() is false for editing index', function () {
+        FauxtonAPI.dispatch({
+          type: ActionTypes.EDIT_INDEX,
+          options: {
+            newView: false,
+            viewName: 'test-view',
+            designDocs: designDocs,
+            designDocId: designDoc._id
+          }
+        });
+
+        assert.notOk(store.showEditor());
+      });
+
+      it('showEditor() is true for creating index', function () {
+        FauxtonAPI.dispatch({
+          type: ActionTypes.EDIT_INDEX,
+          options: {
+            newView: true,
+            viewName: 'test-view',
+            newDesignDoc: false,
+            designDocs: designDocs,
+            designDocId: designDoc._id
+          }
+        });
+
+        assert.ok(store.showEditor());
+      });
+
+    });
+
+  });
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx b/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx
new file mode 100644
index 0000000..4f6cb14
--- /dev/null
+++ b/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx
@@ -0,0 +1,252 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+define([
+  'api',
+  'addons/documents/index-editor/components.react',
+  'addons/documents/index-editor/stores',
+  'addons/documents/index-editor/actions',
+  'addons/documents/resources',
+  'testUtils',
+  "react"
+], function (FauxtonAPI, Views, Stores, Actions, Documents, utils, React) {
+  FauxtonAPI.router = new FauxtonAPI.Router([]);
+
+  var assert = utils.assert;
+  var TestUtils = React.addons.TestUtils;
+
+  var resetStore = function (designDoc) {
+    var designDocs = new Documents.AllDocs([designDoc], {
+      params: { limit: 10 },
+      database: {
+        safeID: function () { return 'id';}
+      }
+    });
+
+    Actions.editIndex({
+      newView: false,
+      viewName: 'test-view',
+      designDocs: designDocs,
+      designDocId: designDoc._id
+    });
+  };
+
+  describe('View editor', function () {
+
+    describe('Toggle button', function () {
+      var container, toggleEl, toggleEditor;
+
+      beforeEach(function () {
+        toggleEditor = sinon.spy();
+        container = document.createElement('div');
+        toggleEl = TestUtils.renderIntoDocument(<Views.ToggleButton toggleEditor={toggleEditor} />, container);
+      });
+
+      afterEach(function () {
+        React.unmountComponentAtNode(container);
+      });
+
+
+      it('should toggle editor on click', function () {
+        TestUtils.Simulate.click($(toggleEl.getDOMNode()).find('a')[0]);
+        assert.ok(toggleEditor.calledOnce);
+      });
+
+    });
+
+  });
+
+  describe('reduce editor', function () {
+    var container, reduceEl;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    describe('getReduceValue', function () {
+      var container;
+
+      beforeEach(function () {
+        container = document.createElement('div');
+        $('body').append('<div id="reduce-function"></div>');
+      });
+
+      it('returns null for none', function () {
+        var store = Stores.indexEditorStore;
+
+        var designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: 'function () {};',
+              //reduce: 'function (reduce) { reduce(); }'
+            }
+          }
+        };
+
+        resetStore(designDoc);
+
+        reduceEl = TestUtils.renderIntoDocument(<Views.ReduceEditor/>, container);
+        assert.ok(_.isNull(reduceEl.getReduceValue()));
+      });
+
+      it('returns built in for built in reduce', function () {
+        var store = Stores.indexEditorStore;
+
+        var designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: 'function () {};',
+              reduce: '_sum'
+            }
+          }
+        };
+
+        resetStore(designDoc);
+
+        reduceEl = TestUtils.renderIntoDocument(<Views.ReduceEditor/>, container);
+        assert.equal(reduceEl.getReduceValue(), '_sum');
+      });
+
+    });
+  });
+
+  describe('design Doc Selector', function () {
+    var container, selectorEl;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+      $('body').append('<div id="map-function"></div>');
+      $('body').append('<div id="editor"></div>');
+      selectorEl = TestUtils.renderIntoDocument(<Views.DesignDocSelector/>, container);
+    });
+
+
+    afterEach(function () {
+      Actions.newDesignDoc.restore && Actions.newDesignDoc.restore();
+      Actions.designDocChange.restore && Actions.designDocChange.restore();
+      React.unmountComponentAtNode(container);
+    });
+
+    it('calls new design doc on new selected', function () {
+      var spy = sinon.spy(Actions, 'newDesignDoc');
+      TestUtils.Simulate.change($(selectorEl.getDOMNode()).find('#ddoc')[0], {
+        target: {
+          value: 'new'
+        }
+      });
+
+      assert.ok(spy.calledOnce);
+    });
+
+    it('calls design doc changed on a different design doc selected', function () {
+      var spy = sinon.spy(Actions, 'designDocChange');
+      TestUtils.Simulate.change($(selectorEl.getDOMNode()).find('#ddoc')[0], {
+        target: {
+          value: 'another-doc'
+        }
+      });
+
+      assert.ok(spy.calledWith('another-doc', false));
+    });
+
+    it('calls design doc changed on new design doc entered', function () {
+      var spy = sinon.spy(Actions, 'designDocChange');
+      Actions.newDesignDoc();
+      TestUtils.Simulate.change($(selectorEl.getDOMNode()).find('#new-ddoc')[0], {
+        target: {
+          value: 'new-doc-entered'
+        }
+      });
+      
+      assert.ok(spy.calledWith('_design/new-doc-entered', true));
+    });
+
+  });
+
+  describe('Editor', function () {
+    var container, editorEl, reduceStub;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+      $('body').append('<div id="map-function"></div>');
+      $('body').append('<div id="editor"></div>');
+      editorEl = TestUtils.renderIntoDocument(<Views.Editor/>, container);
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    it('returns false on invalid map editor code', function () {
+      var stub = sinon.stub(editorEl.refs.mapEditor.aceEditor, 'hadValidCode');
+      stub.returns(false);
+      assert.notOk(editorEl.hasValidCode());
+    });
+
+    it('returns true on valid map editor code', function () {
+      var stub = sinon.stub(editorEl.refs.mapEditor.aceEditor, 'hadValidCode');
+      stub.returns(true);
+      assert.ok(editorEl.hasValidCode());
+    });
+
+    it('returns true on non-custom reduce', function () {
+      var stub = sinon.stub(Stores.indexEditorStore, 'hasCustomReduce');
+      stub.returns(false);
+      assert.ok(editorEl.hasValidCode());
+    });
+
+  });
+
+  describe('Beautify', function () {
+    var container, beautifyEl, reduceStub;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    it('should be empty for multi-lined code', function () {
+      var correctCode = 'function() {\n    console.log("hello");\n}'; 
+      beautifyEl = TestUtils.renderIntoDocument(<Views.Beautify code={correctCode}/>, container);
+      assert.ok(_.isNull(beautifyEl.getDOMNode()));
+    });
+
+    it('should have button to beautify for single line code', function () {
+      var badCode = 'function () { console.log("hello"); }';
+      beautifyEl = TestUtils.renderIntoDocument(<Views.Beautify code={badCode}/>, container);
+      assert.ok($(beautifyEl.getDOMNode()).hasClass('beautify'));
+    });
+
+    it('on click beautifies code', function () {
+      var fixedCode;
+      var correctCode = 'function() {\n    console.log("hello");\n}'; 
+
+      var beautifiedCode = function (code) {
+        fixedCode = code;
+      };
+
+      beautifyEl = TestUtils.renderIntoDocument(<Views.Beautify beautifiedCode={beautifiedCode} code={'function () { console.log("hello"); }'} noOfLines={1}/>, container);
+      TestUtils.Simulate.click(beautifyEl.getDOMNode());
+      assert.equal(fixedCode, correctCode);
+
+    });
+
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/tests/viewsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/viewsSpec.js b/app/addons/documents/tests/viewsSpec.js
index 3d0a008..5e416bb 100644
--- a/app/addons/documents/tests/viewsSpec.js
+++ b/app/addons/documents/tests/viewsSpec.js
@@ -20,7 +20,7 @@ define([
       ViewSandbox = testUtils.ViewSandbox,
       viewSandbox;
 
-  describe('AllDocsList', function () {
+      /* describe('AllDocsList', function () {
     var database = new Databases.Model({id: 'registry'}),
         bulkDeleteDocCollection = new Resources.BulkDeleteDocCollection([], {databaseId: 'registry'});
 
@@ -69,5 +69,5 @@ define([
       view.$('button.js-all').trigger('click');
       assert.equal(bulkDeleteDocCollection.length, 1);
     });
-  });
+  });*/
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/16ec9ab4/app/addons/documents/views-index.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views-index.js b/app/addons/documents/views-index.js
index 0d319e1..eb24944 100644
--- a/app/addons/documents/views-index.js
+++ b/app/addons/documents/views-index.js
@@ -11,533 +11,27 @@
 // the License.
 
 define([
-  "app",
   "api",
-  "addons/fauxton/components",
-  "addons/documents/resources",
-  "addons/databases/resources",
-  "addons/pouchdb/base",
-
-  //views
-  "addons/documents/views-queryoptions",
-
-  // Plugins
-  "plugins/beautify",
-  "plugins/prettify"
+  "addons/documents/index-editor/components.react",
+  "addons/documents/index-editor/actions",
 ],
 
-function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, QueryOptions, beautify) {
+function(FauxtonAPI, ViewEditor, Actions) {
 
   var Views = {};
 
-  Views.ViewEditor = FauxtonAPI.View.extend({
-    template: "addons/documents/templates/view_editor",
-    builtinReduces: ['_sum', '_count', '_stats'],
-
-    events: {
-      "click button.save": "saveView",
-      "click button.delete": "deleteView",
-      "change select#reduce-function-selector": "updateReduce",
-      "click button.preview": "previewView",
-      "click #db-views-tabs-nav": 'toggleIndexNav',
-      "click .beautify_map":  "beautifyCode",
-      "click .beautify_reduce":  "beautifyCode",
-      "click #query-options-wrapper": 'toggleIndexNav'
-    },
-
-    langTemplates: {
-      "javascript": {
-        map: "function(doc) {\n  emit(doc._id, 1);\n}",
-        reduce: "function(keys, values, rereduce){\n  if (rereduce){\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}"
-      }
-    },
-
-    defaultLang: "javascript",
-    rendered: false,
-
-    initialize: function(options) {
-      this.rightHeader = options.rightHeader;
-      this.newView = options.newView || false;
-      this.ddocs = options.ddocs;
-      this.params = options.params;
-      this.database = options.database;
-      this.currentDdoc = options.currentddoc;
-
-      if (this.newView) {
-        this.viewName = 'newView';
-      } else {
-        this.ddocID = options.ddocInfo.id;
-        this.viewName = options.viewName;
-        this.ddocInfo = new Documents.DdocInfo({_id: this.ddocID},{database: this.database});
-      }
-
-      this.showIndex = false;
-      _.bindAll(this);
-    },
-
-    establish: function () {
-      if (this.ddocInfo) {
-        return this.ddocInfo.fetch();
-      }
-    },
-
-    updateValues: function() {
-      var notification;
-      if (this.model.changedAttributes()) {
-        notification = FauxtonAPI.addNotification({
-          msg: "Document saved successfully.",
-          type: "success",
-          clear: true
-        });
-        this.editor.setValue(this.model.prettyJSON());
-      }
-    },
-
-    updateReduce: function(event) {
-      var $ele = $("#reduce-function-selector");
-      var $reduceContainer = $(".control-group.reduce-function");
-      if ($ele.val() == "CUSTOM") {
-        this.createReduceEditor();
-        this.reduceEditor.setValue(this.langTemplates.javascript.reduce);
-        $reduceContainer.show();
-      } else {
-        $reduceContainer.hide();
-      }
-    },
-
-    deleteView: function (event) {
-      event.preventDefault();
-
-      if (this.newView) { return alert('Cannot delete a new view.'); }
-      if (!confirm('Are you sure you want to delete this view?')) {return;}
-
-      var that = this,
-          promise,
-          viewName = this.$('#index-name').val(),
-          ddocName = this.$('#ddoc :selected').val(),
-          ddoc = this.getCurrentDesignDoc();
-
-      ddoc.removeDdocView(viewName);
-
-      if (ddoc.hasViews()) {
-        promise = ddoc.save();
-      } else {
-        promise = ddoc.destroy();
-      }
-
-      promise.then(function () {
-        FauxtonAPI.navigate('/database/' + that.database.safeID() + '/_all_docs?limit=' + Databases.DocLimit);
-        FauxtonAPI.triggerRouteEvent('reloadDesignDocs');
-      });
-    },
-
-    saveView: function(event) {
-      var notification,
-          that = this;
-
-      if (event) { event.preventDefault(); }
-
-      $('#dashboard-content').scrollTop(0); //scroll up
-
-      if (this.hasValidCode() && this.$('#new-ddoc:visible').val() !== "") {
-        var mapVal = this.mapEditor.getValue(),
-        reduceVal = this.reduceVal(),
-        viewName = this.$('#index-name').val(),
-        ddoc = this.getCurrentDesignDoc(),
-        ddocName = ddoc.id,
-        viewNameChange = false;
-
-        if (this.viewName !== viewName) {
-          ddoc.removeDdocView(this.viewName);
-          this.viewName = viewName;
-          viewNameChange = true;
-        }
-
-        notification = FauxtonAPI.addNotification({
-          msg: "Saving document.",
-          clear: true
-        });
-
-        ddoc.setDdocView(viewName, mapVal, reduceVal);
-
-        ddoc.save().then(function () {
-          that.ddocs.add(ddoc);
-          that.mapEditor.editSaved();
-          that.reduceEditor && that.reduceEditor.editSaved();
-
-          FauxtonAPI.addNotification({
-            msg: "View has been saved.",
-            type: "success",
-            clear: true
-          });
-
-          if (that.newView || viewNameChange) {
-            var fragment = '/database/' + that.database.safeID() +'/' + ddoc.safeID() + '/_view/' + app.utils.safeURLName(viewName);
-
-            FauxtonAPI.navigate(fragment);
-            that.newView = false;
-            that.ddocID = ddoc.safeID();
-            that.viewName = viewName;
-            that.ddocInfo = ddoc;
-            that.showIndex = true;
-            that.currentDdoc = ddoc;
-            that.render();
-            FauxtonAPI.triggerRouteEvent('reloadDesignDocs', {
-              selectedTab: app.utils.removeSpecialCharacters(ddocName.replace(/_design\//,'')) + '_' + app.utils.removeSpecialCharacters(viewName)
-            });
-          }
-
-          if (that.reduceFunStr !== reduceVal) {
-            that.reduceFunStr = reduceVal;
-            FauxtonAPI.triggerRouteEvent("updateQueryOptions", { hasReduce: that.hasReduce() });
-          }
-
-          FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: ddocName, view: viewName});
-
-        }, function(xhr) {
-          var responseText = JSON.parse(xhr.responseText).reason;
-          notification = FauxtonAPI.addNotification({
-            msg: "Save failed: " + responseText,
-            type: "error",
-            clear: true
-          });
-        });
-      } else {
-        var errormessage = (this.$('#new-ddoc:visible').val() ==="")?"Enter a design doc name":"Please fix the Javascript errors and try again.";
-        notification = FauxtonAPI.addNotification({
-          msg: errormessage,
-          type: "error",
-          clear: true
-        });
-      }
-    },
-
-    updateView: function(event, paramInfo) {
-       event.preventDefault();
-
-       if (this.newView) { return alert('Please save this new view before querying it.'); }
-
-       var errorParams = paramInfo.errorParams,
-           params = paramInfo.params;
-
-       if (_.any(errorParams)) {
-         _.map(errorParams, function(param) {
-            return FauxtonAPI.addNotification({
-              msg: 'JSON Parse Error on field: ' + param.name,
-              type: 'error',
-              clear: true
-            });
-         });
-         FauxtonAPI.addNotification({
-           msg: "Make sure that strings are properly quoted and any other values are valid JSON structures",
-           type: "warning",
-           clear: true
-         });
-
-         return false;
-      }
-
-      var url = app.utils.replaceQueryParams(params);
-      FauxtonAPI.navigate(url, {trigger: false});
-      FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: this.ddocID, view: this.viewName});
-    },
-
-
-    previewView: function(event, paramsInfo) {
-      event.preventDefault();
-      var that = this,
-      mapVal = this.mapVal(),
-      reduceVal = this.reduceVal(),
-      paramsArr = [];
-
-      if (paramsInfo && paramsInfo.params) {
-        paramsArr = paramsInfo.params;
-      }
-
-      var params = _.reduce(paramsArr, function (params, param) {
-        params[param.name] = param.value;
-        return params;
-      }, {reduce: false});
-
-      FauxtonAPI.addNotification({
-        msg: "<strong>Warning!</strong> Preview executes the Map/Reduce functions in your browser, and may behave differently from CouchDB.",
-        type: "warning",
-        escape: false // beware of possible XSS when the message changes
-      });
-
-      var promise = FauxtonAPI.Deferred();
-
-      if (!this.database.allDocs || this.database.allDocs.params.include_docs !== true) {
-        this.database.buildAllDocs({limit: Databases.DocLimit.toString(), include_docs: true});
-        promise = this.database.allDocs.fetch();
-       } else {
-        promise.resolve();
-       }
-
-      promise.then(function () {
-        params.docs = that.database.allDocs.map(function (model) { return model.get('doc');});
-        var queryPromise = pouchdb.runViewQuery({map: mapVal, reduce: reduceVal}, params);
-        queryPromise.then(function (results) {
-          FauxtonAPI.triggerRouteEvent('updatePreviewDocs', {rows: results.rows, ddoc: that.getCurrentDesignDoc().id, view: that.viewName});
-        });
-      });
-    },
-
-    getCurrentDesignDoc: function () {
-      return this.designDocSelector.getCurrentDesignDoc();
-    },
-
-    isCustomReduceEnabled: function() {
-      return $("#reduce-function-selector").val() == "CUSTOM";
-    },
-
-    mapVal: function () {
-      if (this.mapEditor) {
-        return this.mapEditor.getValue();
-      }
-      return this.$('#map-function').text();
-    },
-
-    reduceVal: function() {
-      var reduceOption = this.$('#reduce-function-selector :selected').val(),
-      reduceVal = "";
-
-      if (reduceOption === 'CUSTOM') {
-        if (!this.reduceEditor) { this.createReduceEditor(); }
-        reduceVal = this.reduceEditor.getValue();
-      } else if ( reduceOption !== 'NONE') {
-        reduceVal = reduceOption;
-      }
-
-      return reduceVal;
-    },
-
-
-    hasValidCode: function() {
-      return _.every(["mapEditor", "reduceEditor"], function(editorName) {
-        var editor = this[editorName];
-        if (editorName === "reduceEditor" && ! this.isCustomReduceEnabled()) {
-          return true;
-        }
-        return editor.hadValidCode();
-      }, this);
-    },
-
-    toggleIndexNav: function (event) {
-      $('#dashboard-content').scrollTop(0); //scroll up
-
-      var $targetId = this.$(event.target).attr('id'),
-          $previousTab = this.$(this.$('li.active a').attr('href')),
-          $targetTab = this.$(this.$(event.target).attr('href'));
-
-      if ($targetTab.attr('id') !== $previousTab.attr('id')) {
-        $previousTab.removeAttr('style');
-      }
-
-      if ($targetId === 'index-nav') {
-        if (this.newView) { return; }
-        var that = this;
-        $('#dashboard-content').scrollTop(0); //scroll up
-        $targetTab.toggle('slow', function(){
-           that.showEditors();
-        });
-      } else {
-        $targetTab.toggle('slow');
-      }
-    },
-
-    serialize: function() {
-      return {
-        ddocs: this.ddocs,
-        ddoc: this.model,
-        ddocName: this.model.id,
-        viewName: this.viewName,
-        reduceFunStr: this.reduceFunStr,
-        isCustomReduce: this.hasCustomReduce(),
-        newView: this.newView,
-        langTemplates: this.langTemplates.javascript
-      };
-    },
-
-    hasCustomReduce: function() {
-      return this.reduceFunStr && ! _.contains(this.builtinReduces, this.reduceFunStr);
-    },
-
-    hasReduce: function () {
-      return this.reduceFunStr || false;
-    },
-
-    createReduceEditor: function () {
-      if (this.reduceEditor) {
-        this.reduceEditor.remove();
-      }
-
-      this.reduceEditor = new Components.Editor({
-        editorId: "reduce-function",
-        mode: "javascript",
-        couchJSHINT: true
-      });
-      this.reduceEditor.render();
-
-      if (this.reduceEditor.getLines() === 1){
-        this.$('.beautify_reduce').removeClass("hide");
-        $('.beautify-tooltip').tooltip();
-      }
-    },
-
-    beforeRender: function () {
-      if (this.newView) {
-        this.reduceFunStr = '';
-        if (this.ddocs.length === 0) {
-          this.model = new Documents.Doc(null, {database: this.database});
-        } else {
-          this.model = this.ddocs.first().dDocModel();
-        }
-        this.ddocID = this.model.id;
-      } else {
-        var ddocDecode = decodeURIComponent(this.ddocID);
-        this.model = this.ddocs.get(this.ddocID).dDocModel();
-        this.reduceFunStr = this.model.viewHasReduce(this.viewName);
-      }
-
-      var viewFilters = FauxtonAPI.getExtensions('sidebar:viewFilters'),
-          filteredModels = this.ddocs.models,
-          designDocs = this.ddocs;
-
-      if (!_.isEmpty(viewFilters)) {
-        _.each(viewFilters, function (filter) {
-          filteredModels = _.filter(filteredModels, filter);
-        });
-        designDocs.reset(filteredModels, {silent: true});
-      }
-
-      if (!this.designDocSelector) { 
-        this.designDocSelector = this.setView('.design-doc-group', new Views.DesignDocSelector({
-          collection: designDocs,
-          ddocName: this.currentDdoc || this.model.id,
-          database: this.database
-        }));
-      }
-
-      // if this isn't a new View, add in whatever extensions have been associated with this location
-      if (!this.newView) {
-        var buttonViews = FauxtonAPI.getExtensions('ViewEditor:ButtonRow');
-        _.each(buttonViews, function (view) {
-          this.insertView("#viewBtnExtensions", view);
-          view.update(this.database, this.ddocInfo.safeID(), this.viewName);
-        }, this);
-      }
-    },
-
-    afterRender: function() {
-
-      this.designDocSelector.updateDesignDoc();
-      if (this.newView || this.showIndex) {
-        this.showEditors();
-        this.showIndex = false;
-      } else {
-        this.$('#index').hide();
-        this.$('#index-nav').parent().removeClass('active');
-      }
-
-    },
-
-    showEditors: function () {
-      this.mapEditor = new Components.Editor({
-        editorId: "map-function",
-        mode: "javascript",
-        couchJSHINT: true
-      });
-      this.mapEditor.render();
-
-      if (this.hasCustomReduce()) {
-        this.createReduceEditor();
-      } else {
-        $(".control-group.reduce-function").hide();
-      }
-
-      if (this.newView) {
-        this.mapEditor.setValue(this.langTemplates[this.defaultLang].map);
-        //Use a built in view by default
-        //this.reduceEditor.setValue(this.langTemplates[this.defaultLang].reduce);
-      }
-
-      this.mapEditor.editSaved();
-      this.reduceEditor && this.reduceEditor.editSaved();
-
-      if (this.mapEditor.getLines() === 1){
-        this.$('.beautify_map').removeClass("hide");
-        $('.beautify-tooltip').tooltip();
-      }
-    },
-    beautifyCode: function(e){
-      e.preventDefault();
-      var targetEditor = $(e.currentTarget).hasClass('beautify_reduce')?this.reduceEditor:this.mapEditor;
-      var beautifiedCode = beautify(targetEditor.getValue());
-      targetEditor.setValue(beautifiedCode);
-    },
-    cleanup: function () {
-      this.mapEditor && this.mapEditor.remove();
-      this.reduceEditor && this.reduceEditor.remove();
-    }
-  });
-
-  Views.DesignDocSelector = FauxtonAPI.View.extend({
-    template: "addons/documents/templates/design_doc_selector",
-
-    events: {
-      "change select#ddoc": "updateDesignDoc"
-    },
-
+  Views.ViewEditorReact = FauxtonAPI.View.extend({
     initialize: function (options) {
-      this.ddocName = options.ddocName;
-      this.database = options.database;
-      this.listenTo(this.collection, 'add', this.ddocAdded);
-      this.DocModel = options.DocModel || Documents.Doc;
-    },
-
-    ddocAdded: function (ddoc) {
-      this.ddocName = ddoc.id;
-    },
-
-    serialize: function () {
-      return {
-        ddocName: this.ddocName,
-        ddocs: this.collection
-      };
-    },
-
-    updateDesignDoc: function () {
-      if (this.newDesignDoc()) {
-        this.$('#new-ddoc-section').show();
-      } else {
-        this.$('#new-ddoc-section').hide();
-      }
+      this.options = options;
     },
 
-    newDesignDoc: function () {
-      return this.$('#ddoc').val() === 'new-doc';
+    afterRender: function () {
+      Actions.editIndex(this.options);
+      ViewEditor.renderEditor(this.el);
     },
 
-    newDocValidation: function(){
-      return this.newDesignDoc() && this.$('#new-ddoc').val()==="";
-    },
-
-    getCurrentDesignDoc: function () {
-      if (this.newDesignDoc()) {
-        var doc = {
-          _id: '_design/' + this.$('#new-ddoc').val(),
-          views: {},
-          language: "javascript"
-        };
-        var ddoc = new this.DocModel(doc, {database: this.database});
-        return ddoc;
-      } 
-
-      var ddocName = this.$('#ddoc').val();
-      return this.collection.find(function (ddoc) {
-        return ddoc.id === ddocName;
-      }).dDocModel();
+    cleanup: function () {
+      ViewEditor.removeEditor(this.el);
     }
   });
 


Mime
View raw message