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 d8c28d9
Date Tue, 11 Aug 2015 09:16:38 GMT
Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master f772160aa -> d8c28d98f


Convert Query Options module to React.js

This converts all the query options to use React. It also adds a new
React.js component for trays


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

Branch: refs/heads/master
Commit: d8c28d98f26f1274f60f6c0bb5694894bd61cb4e
Parents: f772160
Author: Garren Smith <garren.smith@gmail.com>
Authored: Mon Jun 22 10:04:14 2015 +0200
Committer: Garren Smith <garren.smith@gmail.com>
Committed: Mon Aug 10 17:23:24 2015 +0200

----------------------------------------------------------------------
 .../components/react-components.react.jsx       | 112 ++++-
 .../documents/assets/less/queryOptions.less     |  56 ++-
 app/addons/documents/queryoptions/actions.js    | 119 +++++
 .../documents/queryoptions/actiontypes.js       |  29 ++
 .../queryoptions/queryoptions.react.jsx         | 403 +++++++++++++++
 app/addons/documents/queryoptions/stores.js     | 314 ++++++++++++
 .../tests/queryoptions.componentsSpec.react.jsx | 180 +++++++
 .../tests/queryoptions.storesSpec.js            | 135 ++++++
 app/addons/documents/shared-routes.js           |   1 +
 app/addons/documents/templates/edit_tools.html  |  44 --
 .../documents/templates/query_options.html      |  38 --
 .../query_options_additional_params.html        |  61 ---
 .../templates/query_options_key_search.html     |  50 --
 .../templates/query_options_main_fields.html    |  53 --
 .../tests/nightwatch/viewQueryOptions.js        |   2 +-
 app/addons/documents/views-queryoptions.js      | 485 -------------------
 app/addons/documents/views.js                   |  20 +-
 app/addons/fauxton/components.js                |   1 -
 app/constants.js                                |   1 +
 assets/less/trays.less                          |   8 +
 20 files changed, 1361 insertions(+), 751 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/components/react-components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/components/react-components.react.jsx b/app/addons/components/react-components.react.jsx
index 9ff9b45..b712e72 100644
--- a/app/addons/components/react-components.react.jsx
+++ b/app/addons/components/react-components.react.jsx
@@ -20,7 +20,7 @@ define([
 ],
 
 function (app, FauxtonAPI, React, Components, ace, beautifyHelper) {
-
+  var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
 
   var ToggleHeaderButton = React.createClass({
     render: function () {
@@ -731,9 +731,114 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) {
         </div>
       );
     }
+  });
+
+  var TrayLink = React.createClass({
+
+    onClick: function (e) {
+      e.preventDefault();
+      this.props.toggleTray();
+    },
 
+    render: function () {
+      return (
+        <a
+          onClick={this.onClick}
+          id={this.props.id}
+          data-bypass="true"
+          data-toggle="tab"
+          className={this.props.className}
+        >
+          <i className={this.props.icon} />
+          {this.props.text}
+        </a>
+      );
+    }
   });
 
+  var TrayContents = React.createClass({
+    getChildren: function () {
+      if (!this.props.trayVisible) {
+        return null;
+      }
+
+      var className = "tray show-tray " + this.props.className;
+      return (
+        <div key={1} id={this.props.id} className={className}>
+          {this.props.children}
+        </div>);
+    },
+
+    render: function () {
+      return (
+        <ReactCSSTransitionGroup transitionName="tray" transitionAppear={true}>
+          {this.getChildren()}
+        </ReactCSSTransitionGroup>
+      );
+    }
+  });
+
+  // The tray components work as follows:
+  // <Tray> Outer wrapper for all components in the tray
+  // <TrayLink></TrayLink> The tray button to activate the tray
+  // <TrayContents> </TrayContents> What is displayed when the tray is active
+  // </Tray>
+  // See documents/queryoptions/queryoptions.react.jsx for a complete example
+
+  var Tray = React.createClass({
+
+    propTypes: {
+      id: React.PropTypes.string.isRequired
+    },
+
+    componentDidMount: function () {
+      $('body').on('click.' + this.props.id, _.bind(this.closeIfOpen, this));
+      FauxtonAPI.Events.on(FauxtonAPI.constants.EVENTS.TRAY_HIDE, this.closeIfOpen, this);
+    },
+
+    componentWillUnmount: function () {
+      FauxtonAPI.Events.off(FauxtonAPI.constants.EVENTS.TRAY_HIDE);
+      $('body').off('click.' + this.props.id);
+    },
+
+    getInitialState: function () {
+      return {
+        trayVisible: false
+      };
+    },
+
+    toggleTray: function () {
+      this.setState({trayVisible: !this.state.trayVisible});
+    },
+
+    renderChildren: function () {
+      return React.Children.map(this.props.children, function (child, key) {
+        return React.addons.cloneWithProps(child, {
+          trayVisible: this.state.trayVisible,
+          toggleTray: this.toggleTray,
+          key: key
+        });
+      }.bind(this));
+    },
+
+    render: function () {
+      return (
+        <div>
+            {this.renderChildren()}
+        </div>
+      );
+    },
+
+    closeIfOpen: function (e) {
+      if (!this.state.trayVisible) { return; }
+      var trayEl = $(this.getDOMNode());
+
+      if (!trayEl.is(e.target) && trayEl.has(e.target).length === 0) {
+        this.toggleTray();
+      }
+    },
+
+  });
 
   return {
     ConfirmButton: ConfirmButton,
@@ -746,7 +851,10 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) {
     PaddedBorderedBox: PaddedBorderedBox,
     Document: Document,
     LoadLines: LoadLines,
-    MenuDropDown: MenuDropDown
+    MenuDropDown: MenuDropDown,
+    Tray: Tray,
+    TrayContents: TrayContents,
+    TrayLink: TrayLink
   };
 
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/assets/less/queryOptions.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/queryOptions.less b/app/addons/documents/assets/less/queryOptions.less
index ed852de..f1b8e0f 100644
--- a/app/addons/documents/assets/less/queryOptions.less
+++ b/app/addons/documents/assets/less/queryOptions.less
@@ -10,6 +10,52 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+.tray-enter,
+.tray-appear {
+  opacity: 0.01;
+  animation: slidefadeIn .2s ease-in;
+  -webkit-animation: slidefadeIn .2s ease-in;
+}
+
+.tray-enter.tray-enter-active,
+.tray-appear.tray-appear-active {
+  opacity: 1;
+}
+
+@keyframes slidefadeIn {
+  from {
+    opacity: 0.01;
+    top: 30px
+  }
+
+  to {
+    opacity: 1;
+    top: 55px;
+  }
+}
+
+@keyframes slidefadeOut {
+  from {
+    opacity: 1;
+    top: 55px;
+  }
+
+  to {
+    opacity: 0.01;
+    top: 30px
+  }
+}
+
+.tray-leave {
+  opacity: 1;
+  animation: slidefadeOut .2s ease-out;
+  -webkit-animation: slidefadeOut .2s ease-out;
+
+  &.tray-leave-active {
+    opacity: 0;
+  }
+}
+
 #query-options-tray:before {
   right: 140px;
 }
@@ -44,12 +90,20 @@
   }
 
   form {
-    overflow: scroll;
+    overflow: auto;
     margin-bottom: 0;
     .dropdown.inline {
       display: inline-block;
     }
 
+    #keys-input {
+      resize: none;
+    }
+
+    #qoGroupLevelGroup {
+      width: 34%;
+    }
+
     .input-small {
       width: 74px;
       margin-left: 5px;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/queryoptions/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/queryoptions/actions.js b/app/addons/documents/queryoptions/actions.js
new file mode 100644
index 0000000..861214a
--- /dev/null
+++ b/app/addons/documents/queryoptions/actions.js
@@ -0,0 +1,119 @@
+// 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/queryoptions/actiontypes',
+  'addons/documents/queryoptions/stores'
+],
+function (app, FauxtonAPI, ActionTypes, Stores) {
+  var store = Stores.queryOptionsStore;
+  return {
+
+    toggleIncludeDocs: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_TOGGLE_INCLUDE_DOCS
+      });
+    },
+
+    toggleByKeys: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_TOGGLE_BY_KEYS
+      });
+    },
+
+    toggleBetweenKeys: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_TOGGLE_BETWEEN_KEYS
+      });
+    },
+
+    toggleUpdateSeq: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_TOGGLE_UPDATE_SEQ
+      });
+    },
+
+    toggleDescending: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_TOGGLE_DESCENDING
+      });
+    },
+
+    toggleReduce: function () {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_TOGGLE_REDUCE
+      });
+    },
+
+    updateBetweenKeys: function (betweenKeys) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_UPDATE_BETWEEN_KEYS,
+        betweenKeys: betweenKeys
+      });
+    },
+
+    updateByKeys: function (byKeys) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_UPDATE_BY_KEYS,
+        byKeys: byKeys
+      });
+    },
+
+    updateSkip: function (skip) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_UPDATE_SKIP,
+        skip: skip
+      });
+    },
+
+    updateLimit: function (limit) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_UPDATE_LIMIT,
+        limit: limit
+      });
+    },
+
+    runQuery: function (params) {
+      var url = app.utils.replaceQueryParams(params);
+      FauxtonAPI.navigate(url);
+    },
+
+    updateGroupLevel: function (groupLevel) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_UPDATE_GROUP_LEVEL,
+        groupLevel: groupLevel
+      });
+    },
+
+    reset: function (options) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.QUERY_RESET,
+        params: options.queryParams
+      });
+
+      if (options.showReduce) {
+        FauxtonAPI.dispatch({
+          type: ActionTypes.QUERY_SHOW_REDUCE
+        });
+      }
+
+      if (options.showStale) {
+        FauxtonAPI.dispatch({
+          type: ActionTypes.QUERY_SHOW_STALE
+        });
+      }
+    }
+
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/queryoptions/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/queryoptions/actiontypes.js b/app/addons/documents/queryoptions/actiontypes.js
new file mode 100644
index 0000000..23e1fce
--- /dev/null
+++ b/app/addons/documents/queryoptions/actiontypes.js
@@ -0,0 +1,29 @@
+// 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 {
+    QUERY_TOGGLE_INCLUDE_DOCS: 'QUERY_TOGGLE_INCLUDE_DOCS',
+    QUERY_TOGGLE_BY_KEYS: 'QUERY_TOGGLE_BY_KEYS',
+    QUERY_TOGGLE_BETWEEN_KEYS: 'QUERY_TOGGLE_BETWEEN_KEYS',
+    QUERY_UPDATE_BETWEEN_KEYS: 'QUERY_UPDATE_BETWEEN_KEYS',
+    QUERY_UPDATE_BY_KEYS: 'QUERY_UPDATE_BY_KEYS',
+    QUERY_TOGGLE_UPDATE_SEQ: 'QUERY_TOGGLE_UPDATE_SEQ',
+    QUERY_TOGGLE_DESCENDING: 'QUERY_TOGGLE_DESCENDING',
+    QUERY_UPDATE_SKIP: 'QUERY_UPDATE_SKIP',
+    QUERY_UPDATE_LIMIT: 'QUERY_UPDATE_LIMIT',
+    QUERY_RESET: 'QUERY_RESET',
+    QUERY_SHOW_REDUCE: 'QUERY_SHOW_REDUCE',
+    QUERY_SHOW_STALE: 'QUERY_SHOW_STALE',
+    QUERY_UPDATE_GROUP_LEVEL: 'QUERY_UPDATE_GROUP_LEVEL'
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/queryoptions/queryoptions.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/queryoptions/queryoptions.react.jsx b/app/addons/documents/queryoptions/queryoptions.react.jsx
new file mode 100644
index 0000000..6d16364
--- /dev/null
+++ b/app/addons/documents/queryoptions/queryoptions.react.jsx
@@ -0,0 +1,403 @@
+// 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/queryoptions/stores',
+  'addons/documents/queryoptions/actions',
+  'addons/components/react-components.react',
+],
+
+function (app, FauxtonAPI, React, Stores, Actions, Components) {
+  var store = Stores.queryOptionsStore;
+  var LoadLines = Components.LoadLines;
+  var Tray = Components.Tray;
+  var TrayContents = Components.TrayContents;
+  var TrayLink = Components.TrayLink;
+
+  var MainFieldsView = React.createClass({
+
+    toggleIncludeDocs: function (e) {
+      this.props.toggleIncludeDocs();
+    },
+
+    groupLevelChange: function (e) {
+      this.props.updateGroupLevel(e.target.value);
+    },
+
+    groupLevel: function () {
+      if (!this.props.reduce) {
+        return null;
+      }
+
+      return (
+        <label className="drop-down inline" id="qoGroupLevelGroup">
+          Group Level
+          <select onChange={this.groupLevelChange} id="qoGroupLevel" value={this.props.groupLevel} name="group_level" className="input-small">
+            <option value="0">None</option>
+            <option value="1">1</option>
+            <option value="2">2</option>
+            <option value="3">3</option>
+            <option value="4">4</option>
+            <option value="5">5</option>
+            <option value="6">6</option>
+            <option value="7">7</option>
+            <option value="8">8</option>
+            <option value="9">9</option>
+            <option value="exact">Exact</option>
+          </select>
+        </label>
+      );
+    },
+
+    reduce: function () {
+      if (!this.props.showReduce) {
+        return null;
+      }
+
+      return (
+        <span>
+          <div className="checkbox inline">
+            <input id="qoReduce" name="reduce" onChange={this.props.toggleReduce} type="checkbox" checked={this.props.reduce} />
+            <label htmlFor="qoReduce">Reduce</label>
+          </div>
+          {this.groupLevel()}
+        </span>
+      );
+    },
+
+    render: function () {
+      var includeDocs = this.props.includeDocs;
+      return (
+        <div className="query-group" id="query-options-main-fields">
+          <span className="add-on">
+            Query Options
+            <a className="help-link" href={FauxtonAPI.constants.DOC_URLS.GENERAL} target="_blank" data-bypass="true">
+              <i className="icon-question-sign" />
+            </a>
+          </span>
+          <div className="controls-group qo-main-fields-row">
+            <div className="row-fluid fieldsets">
+              <div className="checkbox inline">
+                <input disabled={this.props.reduce} onChange={this.toggleIncludeDocs} className="boom" id="qoIncludeDocs" name="include_docs" type="checkbox" checked={includeDocs} />
+                <label className={this.props.reduce ? 'disabled' : ''} htmlFor="qoIncludeDocs" id="qoIncludeDocsLabel">Include Docs</label>
+              </div>
+              {this.reduce()}
+            </div>
+          </div>
+        </div>
+      );
+    }
+
+  });
+
+  var KeySearchFields = React.createClass({
+    toggleByKeys: function () {
+      this.props.toggleByKeys();
+    },
+
+    toggleBetweenKeys: function () {
+      this.props.toggleBetweenKeys();
+    },
+
+    updateBetweenKeys: function () {
+      this.props.updateBetweenKeys({
+        startkey: this.refs.startkey.getDOMNode().value,
+        endkey: this.refs.endkey.getDOMNode().value,
+        include: this.props.betweenKeys.include
+      });
+    },
+
+    updateInclusiveEnd: function () {
+      this.props.updateBetweenKeys({
+        include: !this.props.betweenKeys.include,
+        startkey: this.props.betweenKeys.startkey,
+        endkey: this.props.betweenKeys.endkey
+      });
+    },
+
+    updateByKeys: function (e) {
+      this.props.updateByKeys(e.target.value);
+    },
+
+    render: function () {
+      var keysGroupClass = 'controls-group well js-query-keys-wrapper ';
+      var byKeysClass = 'row-fluid js-keys-section ';
+      var betweenKeysClass = byKeysClass;
+      var byKeysButtonClass = 'drop-down btn ';
+      var betweenKeysButtonClass = byKeysButtonClass;
+
+      if (!this.props.showByKeys && !this.props.showBetweenKeys) {
+        keysGroupClass += 'hide';
+      }
+
+      if (!this.props.showByKeys) {
+        byKeysClass += 'hide';
+      } else {
+        byKeysButtonClass += 'active';
+      }
+
+      if (!this.props.showBetweenKeys) {
+        betweenKeysClass += 'hide';
+      } else {
+        betweenKeysButtonClass += 'active';
+      }
+
+      return (
+        <div className="query-group" id="query-options-key-search">
+          <div className="add-on">Keys</div>
+          <div className="btn-group toggle-btns row-fluid">
+            <label id="byKeys" onClick={this.toggleByKeys} className={byKeysButtonClass}>By Key(s)</label>
+            <label id="betweenKeys" onClick={this.toggleBetweenKeys} className={betweenKeysButtonClass}>Between Keys</label>
+          </div>
+
+          <div className={keysGroupClass}>
+            <div className={byKeysClass} id="js-showKeys">
+              <div className="controls controls-row">
+                <label htmlFor="keys-input" className="drop-down">A key, or an array of keys.</label>
+                <textarea value={this.props.byKeys} onChange={this.updateByKeys} id="keys-input" className="input-xxlarge" rows="5" type="text"
+                  placeholder='Enter either a single key ["123"] or an array of keys ["123", "456"].
+A key value is the first parameter emitted in a map function. For example emit("123", 1) the key is "123".'></textarea>
+                <div id="keys-error" className="inline-block js-keys-error"></div>
+              </div>
+            </div>
+
+            <div className={betweenKeysClass} id="js-showStartEnd">
+              <div className="controls controls-row">
+                <div>
+                  <label htmlFor="startkey" className="drop-down">Start key</label>
+                  <input id="startkey" ref="startkey" type="text" onChange={this.updateBetweenKeys} value={this.props.betweenKeys.startkey} placeholder='e.g., "1234"' />
+                </div>
+                <div>
+                  <label htmlFor="endkey" className="drop-down">End key</label>
+                  <input id="endkey" ref="endkey" onChange={this.updateBetweenKeys} value={this.props.betweenKeys.endkey} type="text" placeholder='e.g., "1234"'/>
+                  <div className="controls include-end-key-row checkbox controls-row inline">
+                    <input id="qoIncludeEndKeyInResults" ref="inclusive_end" type="checkbox" onChange={this.updateInclusiveEnd} checked={this.props.betweenKeys.include}/>
+                    <label htmlFor="qoIncludeEndKeyInResults">Include End Key in results</label>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+
+        </div>
+      );
+    }
+  });
+
+  var AdditionalParams = React.createClass({
+    showStale: function () {
+      if (!this.props.showStale) {
+        return null;
+      }
+
+      return (
+        <div className="checkbox inline">
+          <input id="qoStale" name="stale" type="checkbox" defaultValue="ok" />
+          <label htmlFor="qoStale">Stale</label>
+        </div>
+      );
+    },
+
+    updateSkip: function (e) {
+      e.preventDefault();
+      var val = e.target.value;
+
+      //check skip is only numbers
+      if (!/^\d*$/.test(val)) {
+        FauxtonAPI.addNotification({
+          msg: 'Skip can only be a number',
+          type: 'error'
+        });
+        val = this.props.skip;
+      }
+
+      this.props.updateSkip(val);
+    },
+
+    updateLimit: function (e) {
+      e.preventDefault();
+      this.props.updateLimit(e.target.value);
+    },
+
+    render: function () {
+      return (
+        <div className="query-group" id="query-options-additional-params">
+          <div className="add-on additionalParams">Additional Parameters</div>
+          <div className="row-fluid fieldsets">
+            {this.showStale()}
+            <div className="checkbox inline">
+              <input id="qoUpdateSeq" type="checkbox" onChange={this.props.toggleUpdateSeq} checked={this.props.updateSeq} />
+              <label htmlFor="qoUpdateSeq">Update Sequence</label>
+            </div>
+            <div className="dropdown inline">
+              <label className="drop-down">
+                Limit
+                <select id="qoLimit" onChange={this.updateLimit} name="limit" value={this.props.limit} className="input-small">
+                  <option value="none">None</option>
+                  <option value={5}>5</option>
+                  <option value={10}>10</option>
+                  <option value={20}>20</option>
+                  <option value={30}>30</option>
+                  <option value={50}>50</option>
+                  <option value={100}>100</option>
+                  <option value={500}>500</option>
+                </select>
+              </label>
+            </div>
+          </div>
+          <div className="row-fluid fieldsets">
+            <div className="checkbox inline">
+              <input id="qoDescending" type="checkbox" onChange={this.props.toggleDescending} checked={this.props.descending} />
+              <label htmlFor="qoDescending">Descending</label>
+            </div>
+            <div className="dropdown inline">
+              <label htmlFor="qoSkip" className="drop-down">
+                Skip
+                <input value={this.props.skip} onChange={this.updateSkip} className="input-small" type="text" id="qoSkip" placeholder="# of rows" />
+              </label>
+            </div>
+          </div>
+        </div>
+      );
+    }
+  });
+
+  var QueryButtons = React.createClass({
+    hideTray: function (e) {
+      FauxtonAPI.Events.trigger(FauxtonAPI.constants.EVENTS.TRAY_HIDE, {});
+    },
+
+    render: function () {
+      return (
+        <div className="controls-group query-group">
+          <div id="button-options" className="controls controls-row">
+            <button type="submit" className="btn btn-success">
+              <i className="fonticon-play icon" />
+              Query
+            </button>
+            <a onClick={this.hideTray} className="btn btn-cancel">Cancel</a>
+          </div>
+        </div>
+      );
+    }
+  });
+
+  var QueryOptionsController = React.createClass({
+    getStoreState: function () {
+      return {
+        isVisible: store.isVisible(),
+        includeDocs: store.includeDocs(),
+        showBetweenKeys: store.showBetweenKeys(),
+        showByKeys: store.showByKeys(),
+        betweenKeys: store.betweenKeys(),
+        byKeys: store.byKeys(),
+        updateSeq: store.updateSeq(),
+        descending: store.descending(),
+        skip: store.skip(),
+        limit: store.limit(),
+        showReduce: store.showReduce(),
+        showStale: store.showStale(),
+        reduce: store.reduce(),
+        groupLevel: store.groupLevel()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    componentDidMount: function () {
+      store.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      store.off('change', this.onChange);
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    runQuery: function (e) {
+      e.preventDefault();
+
+      Actions.runQuery(store.getQueryParams());
+    },
+
+    render: function () {
+      return (
+        <Tray id="query-options-tray">
+          <TrayLink
+            id="toggle-query"
+            className="btn btn-primary pull-right query-options-btn"
+            text="Query Options"
+            icon="icon header-icon fonticon-gears"
+          />
+
+        <TrayContents
+          className="query-options"
+          id="query-options-tray">
+
+          <form onSubmit={this.runQuery} className="js-view-query-update custom-inputs">
+            <MainFieldsView
+              includeDocs={this.state.includeDocs}
+              toggleIncludeDocs={Actions.toggleIncludeDocs}
+              showReduce={this.state.showReduce}
+              reduce={this.state.reduce}
+              toggleReduce={Actions.toggleReduce}
+              groupLevel={this.state.groupLevel}
+              updateGroupLevel={Actions.updateGroupLevel}
+            />
+            <KeySearchFields
+              key={1}
+              showByKeys={this.state.showByKeys}
+              showBetweenKeys={this.state.showBetweenKeys}
+              toggleByKeys={Actions.toggleByKeys}
+              toggleBetweenKeys={Actions.toggleBetweenKeys}
+              betweenKeys={this.state.betweenKeys}
+              updateBetweenKeys={Actions.updateBetweenKeys}
+              byKeys={this.state.byKeys}
+              updateByKeys={Actions.updateByKeys}
+             />
+            <AdditionalParams
+              updateSeq={this.state.updateSeq}
+              toggleUpdateSeq={Actions.toggleUpdateSeq}
+              descending={this.state.descending}
+              toggleDescending={Actions.toggleDescending}
+              skip={this.state.skip}
+              updateSkip={Actions.updateSkip}
+              updateLimit={Actions.updateLimit}
+              limit={this.state.limit}
+            />
+            <QueryButtons />
+          </form>
+
+        </TrayContents>
+        </Tray>
+      );
+    }
+
+  });
+
+  return {
+    QueryOptionsController: QueryOptionsController,
+    MainFieldsView: MainFieldsView,
+    KeySearchFields: KeySearchFields,
+    AdditionalParams: AdditionalParams,
+    render: function (el) {
+      React.render(<QueryOptionsController />, $(el)[0]);
+    }
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/queryoptions/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/queryoptions/stores.js b/app/addons/documents/queryoptions/stores.js
new file mode 100644
index 0000000..71ca62d
--- /dev/null
+++ b/app/addons/documents/queryoptions/stores.js
@@ -0,0 +1,314 @@
+// 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/queryoptions/actiontypes'
+],
+
+function (app, FauxtonAPI, ActionTypes) {
+  var Stores = {};
+
+  Stores.QueryOptionsStore = FauxtonAPI.Store.extend({
+
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._loading = true;
+      this._showByKeys = false;
+      this._showBetweenKeys = false;
+      this._includeDocs = false;
+      this._betweenKeys = {
+        include: true,
+      };
+
+      this._byKeys = '';
+      this._updateSeq = false;
+      this._descending = false;
+      this._skip = '';
+      this._limit = "none";
+      this._reduce = false;
+      this._groupLevel = 'exact';
+
+      this._showReduce = false;
+      this._showStale = false;
+    },
+
+    isLoading: function () {
+      return this._loading;
+    },
+
+    isVisible: function () {
+      return true;
+    },
+
+    showReduce: function () {
+      return this._showReduce;
+    },
+
+    reduce: function () {
+      return this._reduce;
+    },
+
+    showStale: function () {
+      return this._showStale;
+    },
+
+    betweenKeys: function () {
+      return this._betweenKeys;
+    },
+
+    updateBetweenKeys: function (newBetweenKeys) {
+      this._betweenKeys = newBetweenKeys;
+    },
+
+    updateSkip: function (skip) {
+      this._skip = skip;
+    },
+
+    skip: function () {
+      return this._skip;
+    },
+
+    limit: function () {
+      return this._limit;
+    },
+
+    updateLimit: function (limit) {
+      this._limit = limit;
+    },
+
+    byKeys: function () {
+      return this._byKeys;
+    },
+
+    updateByKeys: function (keys) {
+      this._byKeys = keys;
+    },
+
+    includeDocs: function () {
+      return this._includeDocs;
+    },
+
+    updateSeq: function () {
+      return this._updateSeq;
+    },
+
+    descending: function () {
+      return this._descending;
+    },
+
+    groupLevel: function () {
+      return this._groupLevel;
+    },
+
+    toggleByKeys: function () {
+      this._showByKeys = !this._showByKeys;
+
+      if (this._showByKeys) {
+        this._showBetweenKeys = false;
+      }
+    },
+
+    toggleBetweenKeys: function () {
+      this._showBetweenKeys = !this._showBetweenKeys;
+
+      if (this._showBetweenKeys) {
+        this._showByKeys = false;
+      }
+    },
+
+    showByKeys: function () {
+      return this._showByKeys;
+    },
+
+    showBetweenKeys: function () {
+      return this._showBetweenKeys;
+    },
+
+    updateGroupLevel: function (groupLevel) {
+      this._groupLevel = groupLevel;
+    },
+
+    setQueryParams: function (params) {
+      this.reset();
+      if (params.include_docs) {
+        this._includeDocs = true;
+      }
+
+      if (params.start_key) {
+        var include = true;
+
+        if (params.inclusive_end) {
+          include = params.inclusive_end === "true" ? true : false;
+        }
+
+        this._betweenKeys = {
+          startkey: JSON.parse(params.start_key),
+          endkey: JSON.parse(params.end_key),
+          include: include
+        };
+
+        this._showBetweenKeys = true;
+      } else if (params.keys) {
+        this._byKeys = params.keys;
+        this._showByKeys = true;
+      }
+
+      if (params.update_seq) {
+        this._updateSeq = params.update_seq;
+      }
+
+      if (params.limit && params.limit !== 'none') {
+        this._limit = params.limit;
+      }
+
+      if (params.skip) {
+        this._skip = params.skip;
+      }
+
+      if (params.descending) {
+        this._descending = params.descending;
+      }
+
+      if (params.reduce) {
+        if (params.group) {
+          this._groupLevel = 'exact';
+        } else {
+          this._groupLevel = params.group_level;
+        }
+        this._reduce = true;
+      }
+    },
+
+    getQueryParams: function () {
+      var params = {};
+
+      if (this._includeDocs) {
+        params.include_docs = this._includeDocs;
+      }
+
+      if (this._showBetweenKeys) {
+        var betweenKeys = this._betweenKeys;
+        _.extend(params, {
+          inclusive_end: betweenKeys.include,
+          start_key: JSON.stringify(betweenKeys.startkey),
+          end_key: JSON.stringify(betweenKeys.endkey)
+        });
+
+      } else if (this._showByKeys) {
+        params.keys = this._byKeys;
+      }
+
+      if (this._updateSeq) {
+        params.update_seq = this._updateSeq;
+      }
+
+      if (this._limit !== 'none') {
+        params.limit = parseInt(this._limit, 10);
+      }
+
+      if (this._skip) {
+        params.skip = parseInt(this._skip, 10);
+      }
+
+      if (this._descending) {
+        params.descending = this._descending;
+      }
+
+      if (this._reduce) {
+        params.reduce = true;
+
+        if (this._groupLevel === 'exact') {
+          params.group = true;
+        } else {
+          params.group_level = this._groupLevel;
+        }
+      }
+
+      return params;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+        case ActionTypes.QUERY_RESET:
+          this.setQueryParams(action.params);
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_TOGGLE_INCLUDE_DOCS:
+          this._includeDocs = !this._includeDocs;
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_TOGGLE_UPDATE_SEQ:
+          this._updateSeq = !this._updateSeq;
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_TOGGLE_DESCENDING:
+          this._descending = !this._descending;
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_TOGGLE_BY_KEYS:
+          this.toggleByKeys();
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_TOGGLE_BETWEEN_KEYS:
+          this.toggleBetweenKeys();
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_UPDATE_BETWEEN_KEYS:
+          this.updateBetweenKeys(action.betweenKeys);
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_UPDATE_BY_KEYS:
+          this.updateByKeys(action.byKeys);
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_UPDATE_SKIP:
+          this.updateSkip(action.skip);
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_UPDATE_LIMIT:
+          this.updateLimit(action.limit);
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_SHOW_REDUCE:
+          this._showReduce = true;
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_TOGGLE_REDUCE:
+          this._reduce = !this._reduce;
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_SHOW_STALE:
+          this._showStale = true;
+          this.triggerChange();
+        break;
+        case ActionTypes.QUERY_UPDATE_GROUP_LEVEL:
+          this.updateGroupLevel(action.groupLevel);
+          this.triggerChange();
+        break;
+        default:
+        return;
+        // do nothing
+      }
+    }
+  });
+
+  Stores.queryOptionsStore = new Stores.QueryOptionsStore();
+  Stores.queryOptionsStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.queryOptionsStore.dispatch);
+
+  return Stores;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/queryoptions/tests/queryoptions.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/documents/queryoptions/tests/queryoptions.componentsSpec.react.jsx b/app/addons/documents/queryoptions/tests/queryoptions.componentsSpec.react.jsx
new file mode 100644
index 0000000..4810e97
--- /dev/null
+++ b/app/addons/documents/queryoptions/tests/queryoptions.componentsSpec.react.jsx
@@ -0,0 +1,180 @@
+// 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/queryoptions/queryoptions.react',
+  'addons/documents/queryoptions/stores',
+  'addons/documents/queryoptions/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 restore = utils.restore;
+
+  describe('Query Options', function () {
+    var container, El;
+
+    beforeEach(function () {
+      container = document.createElement('div');
+    });
+
+    afterEach(function () {
+      React.unmountComponentAtNode(container);
+    });
+
+    describe('MainFieldsView', function () {
+      var mainFieldsEl;
+
+      it('returns null no reduce', function () {
+
+        mainFieldsEl = TestUtils.renderIntoDocument(<Views.MainFieldsView reduce={false} includeDocs={false} showReduce={false}/>, container);
+        assert.ok(_.isNull(mainFieldsEl.reduce()));
+      });
+
+      it('shows reduce and group level', function () {
+        mainFieldsEl = TestUtils.renderIntoDocument(<Views.MainFieldsView reduce={true} includeDocs={false} showReduce={true}/>, container);
+        assert.ok(!_.isNull(mainFieldsEl.reduce()));
+        assert.ok(!_.isNull(mainFieldsEl.groupLevel()));
+      });
+
+      it('updates group level', function () {
+        var spy = sinon.spy();
+        mainFieldsEl = TestUtils.renderIntoDocument(<Views.MainFieldsView updateGroupLevel={spy} reduce={true} includeDocs={false} showReduce={true}/>, container);
+        var el = $(mainFieldsEl.getDOMNode()).find('#qoGroupLevel')[0];
+        TestUtils.Simulate.change(el, {target: {value: 'exact'}});
+
+        assert.ok(spy.calledOnce);
+      });
+
+      it('toggles include docs on change', function () {
+        var spy = sinon.spy();
+        mainFieldsEl = TestUtils.renderIntoDocument(<Views.MainFieldsView toggleIncludeDocs={spy} reduce={false} includeDocs={false} showReduce={false}/>, container);
+        var el = $(mainFieldsEl.getDOMNode()).find('#qoIncludeDocs')[0];
+        TestUtils.Simulate.change(el);
+
+        assert.ok(spy.calledOnce);
+      });
+    });
+
+    describe('KeySearchFields', function () {
+
+      it('toggles keys', function () {
+        var spy = sinon.spy();
+        var keysEl = TestUtils.renderIntoDocument(<Views.KeySearchFields
+          showByKeys={false}
+          showBetweenKeys={false}
+          betweenKeys={{}}
+          toggleByKeys={spy}
+          />, container);
+
+        var el = $(keysEl.getDOMNode()).find('#byKeys')[0];
+        TestUtils.Simulate.click(el);
+        assert.ok(spy.calledOnce);
+      });
+
+      it('toggles between keys', function () {
+        var spy = sinon.spy();
+        var keysEl = TestUtils.renderIntoDocument(<Views.KeySearchFields
+          showByKeys={false}
+          showBetweenKeys={false}
+          toggleBetweenKeys={spy}
+          betweenKeys={{}}
+          />, container);
+
+        var el = $(keysEl.getDOMNode()).find('#betweenKeys')[0];
+        TestUtils.Simulate.click(el);
+        assert.ok(spy.calledOnce);
+      });
+
+      it('updates byKeys', function () {
+        var spy = sinon.spy();
+        var keysEl = TestUtils.renderIntoDocument(<Views.KeySearchFields
+          showByKeys={false}
+          showBetweenKeys={false}
+          updateByKeys={spy}
+          betweenKeys={{}}
+          />, container);
+
+        var el = $(keysEl.getDOMNode()).find('#keys-input')[0];
+        TestUtils.Simulate.change(el, {target: {value: 'boom'}});
+        assert.ok(spy.calledWith('boom'));
+      });
+
+      it('updates betweenKeys', function () {
+        var spy = sinon.spy();
+        var betweenKeys = {
+          startkey: '',
+          endkey: '',
+          inclusive_end: true
+        };
+        var keysEl = TestUtils.renderIntoDocument(<Views.KeySearchFields
+          showByKeys={false}
+          showBetweenKeys={false}
+          updateBetweenKeys={spy}
+          betweenKeys={betweenKeys}
+          />, container);
+
+        var el = $(keysEl.getDOMNode()).find('#endkey')[0];
+        TestUtils.Simulate.change(el, {target: {value: 'boom'}});
+        assert.ok(spy.calledOnce);
+      });
+    });
+
+    describe('AdditionalParams', function () {
+      afterEach(function () {
+        restore(FauxtonAPI.addNotification);
+      });
+
+      it('only updates skip with numbers', function () {
+        var paramsEl = TestUtils.renderIntoDocument(<Views.AdditionalParams
+          updateSkip={function () {}}
+           />, container);
+
+        var spy = sinon.spy(FauxtonAPI, 'addNotification');
+        paramsEl.updateSkip({target: {value: 'b'}, preventDefault: function () {}});
+
+        assert.ok(spy.calledOnce);
+      });
+
+      it('updates skip if a number', function () {
+        var val = 0;
+        var paramsEl = TestUtils.renderIntoDocument(<Views.AdditionalParams
+          updateSkip={function (a) {
+            val = a;
+          }}
+           />, container);
+
+        paramsEl.updateSkip({target: {value: '3'}, preventDefault: function () {}});
+        assert.equal(val, '3');
+      });
+
+      it('does not show stale if showStale false', function () {
+        var paramsEl = TestUtils.renderIntoDocument(<Views.AdditionalParams
+          showStale={false} />, container);
+
+        assert.equal(paramsEl.showStale(), null);
+      });
+
+      it('show stale if showStale true', function () {
+        var paramsEl = TestUtils.renderIntoDocument(<Views.AdditionalParams
+          showStale={true} />, container);
+
+        assert.notEqual(paramsEl.showStale(), null);
+      });
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/queryoptions/tests/queryoptions.storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/queryoptions/tests/queryoptions.storesSpec.js b/app/addons/documents/queryoptions/tests/queryoptions.storesSpec.js
new file mode 100644
index 0000000..38e5700
--- /dev/null
+++ b/app/addons/documents/queryoptions/tests/queryoptions.storesSpec.js
@@ -0,0 +1,135 @@
+// 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/queryoptions/stores',
+  'addons/documents/queryoptions/actiontypes',
+  'testUtils'
+], function (FauxtonAPI, Stores, ActionTypes, testUtils) {
+  var assert = testUtils.assert;
+  var dispatchToken;
+  var store;
+  var opts;
+
+  describe('QueryOptions Store', function () {
+    beforeEach(function () {
+      store = new Stores.QueryOptionsStore();
+      dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch);
+    });
+
+    afterEach(function () {
+      FauxtonAPI.dispatcher.unregister(dispatchToken);
+    });
+
+    describe('Toggle By Keys and Between Keys', function () {
+
+      it('toggling by keys sets by keys to true', function () {
+
+        store.toggleByKeys();
+
+        assert.ok(store.showByKeys());
+      });
+
+      it('toggling between keys sets between keys to true', function () {
+
+        store.toggleBetweenKeys();
+        assert.ok(store.showBetweenKeys());
+      });
+
+      it('toggling between keys sets by keys to false', function () {
+        store._showByKeys = true;
+        store.toggleBetweenKeys();
+        assert.notOk(store.showByKeys());
+      });
+
+      it('toggling by keys sets between keys to false', function () {
+        store._showBetweenKeys = true;
+        store.toggleByKeys();
+        assert.notOk(store.showBetweenKeys());
+      });
+
+    });
+
+    describe('getQueryParams', function () {
+      it('returns params for default', function () {
+        assert.deepEqual(store.getQueryParams(), {});
+      });
+
+      it('with betweenKeys', function () {
+        store.toggleBetweenKeys();
+        store.updateBetweenKeys({
+          startkey:"a",
+          endkey: "z",
+          include: true
+        });
+
+        assert.deepEqual(store.getQueryParams(), {
+          inclusive_end: true,
+          start_key: JSON.stringify("a"),
+          end_key: JSON.stringify("z")
+        });
+      });
+
+      it('with byKeys', function () {
+        store.toggleByKeys();
+        store.updateByKeys("[1,2,3]");
+
+        assert.deepEqual(store.getQueryParams(), {
+          keys: "[1,2,3]"
+        });
+      });
+    });
+
+    describe('setQueryParams', function () {
+
+      it('sets all store values from given params', function () {
+
+        store.setQueryParams({
+          include_docs: true,
+          limit: 10,
+          skip: 5,
+          update_seq: true,
+          descending: true
+        });
+
+        assert.ok(store.includeDocs());
+        assert.ok(store.descending());
+        assert.equal(store.limit(), 10);
+        assert.equal(store.skip(), 5);
+        assert.equal(store.updateSeq(), 1);
+      });
+
+      it('sets between keys', function () {
+        store.setQueryParams({
+          start_key: 1,
+          end_key: 5
+        });
+
+        assert.equal(store.betweenKeys().startkey, 1);
+        assert.equal(store.betweenKeys().endkey, 5);
+        assert.ok(store.betweenKeys().include);
+        assert.ok(store.showBetweenKeys());
+      });
+
+      it('sets by keys', function () {
+        store.setQueryParams({
+          keys: [1, 2, 3]
+        });
+
+        assert.deepEqual(store.byKeys(), [1, 2, 3]);
+        assert.ok(store.showByKeys());
+      });
+
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/shared-routes.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/shared-routes.js b/app/addons/documents/shared-routes.js
index 2bf52b4..715b3ef 100644
--- a/app/addons/documents/shared-routes.js
+++ b/app/addons/documents/shared-routes.js
@@ -82,6 +82,7 @@ define([
           queryParams: urlParams,
           showStale: true,
           hasReduce: hasReduceFunction,
+          showReduce: !_.isUndefined(hasReduceFunction),
           viewName: viewName,
           ddocName: ddoc
         });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/templates/edit_tools.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/edit_tools.html b/app/addons/documents/templates/edit_tools.html
deleted file mode 100644
index 40c884d..0000000
--- a/app/addons/documents/templates/edit_tools.html
+++ /dev/null
@@ -1,44 +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 show">
-  <p>
-    Showing 1-<%= numModels %> of <%= totalRows %> rows
-    <% if (updateSeq) { %>
-      -- Update Sequence: <%= updateSeq %>
-    <% } %>
-    <% if (requestDuration) { %>
-  <span class="view-request-duration">
-    View request duration: <strong> <%= requestDuration %> </strong> 
-   </span>
-   <% } %>
-  </p>
-  <table class="all-docs table table-striped table-condensed">
-    <tbody></tbody>
-  </table>
-  <!--
-  <div class="pagination pagination-centered">
-    <ul>
-      <li class="disabled"><a href="#">&laquo;</a></li>
-      <li class="active"><a href="#">1</a></li>
-      <li><a href="#">2</a></li>
-      <li><a href="#">3</a></li>
-      <li><a href="#">4</a></li>
-      <li><a href="#">5</a></li>
-      <li><a href="#">&raquo;</a></li>
-    </ul>
-  </div>
-  -->
-
-</div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/templates/query_options.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/query_options.html b/app/addons/documents/templates/query_options.html
deleted file mode 100644
index 8126457..0000000
--- a/app/addons/documents/templates/query_options.html
+++ /dev/null
@@ -1,38 +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="toggle-query" href="#query-options-tray" data-bypass="true" data-toggle="tab"
-   class="btn btn-primary pull-right query-options-btn">
-  <i class="icon header-icon fonticon-gears"></i>
-  Query Options
-</a>
-
-<div id="query-options-tray" class="query-options tray">
-  <form class="js-view-query-update custom-inputs">
-
-    <div class="query-group" id="query-options-main-fields"></div>
-    <div class="query-group" id="query-options-key-search"></div>
-    <div class="controls-group query-group" id="query-options-additional-params"></div>
-
-    <div class="controls-group query-group">
-      <div id="button-options" class="controls controls-row">
-        <button type="submit" class="btn btn-success">
-          <i class="fonticon-play icon"></i>
-          Query
-        </button>
-        <a class="btn btn-cancel">Cancel</a>
-      </div>
-    </div>
-
-  </form>
-</div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/templates/query_options_additional_params.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/query_options_additional_params.html b/app/addons/documents/templates/query_options_additional_params.html
deleted file mode 100644
index 3784315..0000000
--- a/app/addons/documents/templates/query_options_additional_params.html
+++ /dev/null
@@ -1,61 +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.
-*/ %>
-
-<!-- Limit and Skip are conditional -->
-<div class="add-on additionalParams">Additional Parameters</div>
-
-<div class="row-fluid fieldsets">
-
-  <% if (showStale) { %>
-  <div class="checkbox inline">
-    <input id="qoStale" name="stale" type="checkbox" value="ok" />
-    <label for="qoStale">Stale</label>
-  </div>
-  <% } %>
-
-  <div class="checkbox inline">
-    <input id="qoUpdateSeq" name="update_seq" type="checkbox" value="true" />
-    <label for="qoUpdateSeq">Update Sequence</label>
-  </div>
-
-  <div class="dropdown inline">
-    <label class="drop-down">
-      Limit
-      <select id="qoLimit" name="limit" class="input-small">
-        <option value="" selected="selected">None</option>
-        <option value="5">5</option>
-        <option value="10">10</option>
-        <option value="20">20</option>
-        <option value="30">30</option>
-        <option value="50">50</option>
-        <option value="100">100</option>
-        <option value="500">500</option>
-      </select>
-    </label>
-  </div>
-</div>
-
-<div class="row-fluid fieldsets">
-  <div class="checkbox inline">
-    <input id="qoDescending" name="descending" type="checkbox" value="true" />
-    <label for="qoDescending">Descending</label>
-  </div>
-
-  <div class="dropdown inline">
-    <label for="qoSkip" class="drop-down">
-      Skip
-      <input name="skip" class="input-small" type="text" id="qoSkip" placeholder="# of rows" />
-    </label>
-  </div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/templates/query_options_key_search.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/query_options_key_search.html b/app/addons/documents/templates/query_options_key_search.html
deleted file mode 100644
index b288b4d..0000000
--- a/app/addons/documents/templates/query_options_key_search.html
+++ /dev/null
@@ -1,50 +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="add-on">Keys</div>
-
-<!-- tabs for choosing Keys or start & end -->
-<div class="btn-group toggle-btns row-fluid">
-  <label data-action="showByKeys" class="drop-down btn">By Key(s)</label>
-  <label data-action="showBetweenKeys" class="drop-down btn">Between Keys</label>
-</div>
-
-<div class="controls-group well hide js-query-keys-wrapper">
-  <div class="row-fluid js-keys-section" id="js-showKeys">
-    <div class="controls controls-row">
-      <label for="keys-input" class="drop-down">A key, or an array of keys.</label>
-      <textarea id="keys-input" name="keys" class="input-xxlarge" rows="5" type="text"
-        placeholder='Enter valid JSON; e.g., ["1234"] or ["1234","2345"]'></textarea>
-      <div id="keys-error" class="inline-block js-keys-error"></div>
-    </div>
-  </div>
-
-  <div class="row-fluid js-keys-section hide" id="js-showStartEnd">
-    <div class="controls controls-row">
-      <div>
-        <label for="startkey" class="drop-down">Start key</label>
-        <input name="startkey" id="startkey" type="text" placeholder='e.g., "1234"' disabled />
-      </div>
-      <div>
-        <label for="endkey" class="drop-down">End key</label>
-        <input id="endkey" name="endkey" type="text" placeholder='e.g., "1234"'>
-        <div class="controls include-end-key-row checkbox controls-row inline">
-          <input id="qoIncludeEndKeyInResults" name="inclusive_end" type="checkbox" value="true" checked disabled />
-          <label for="qoIncludeEndKeyInResults">Include End Key in results</label>
-        </div>
-      </div>
-    </div>
-  </div>
-</div>
-  

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/templates/query_options_main_fields.html
----------------------------------------------------------------------
diff --git a/app/addons/documents/templates/query_options_main_fields.html b/app/addons/documents/templates/query_options_main_fields.html
deleted file mode 100644
index 89c87cd..0000000
--- a/app/addons/documents/templates/query_options_main_fields.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<% /*
-Licensed under the Apache License, Version 2.0 (the "License"); you may not
-use this file except in compliance with the License. You may obtain a copy of
-the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-License for the specific language governing permissions and limitations under
-the License.
-*/ %>
-
-<span class="add-on">
-  Query Options
-  <a class="help-link" href="<%-documentation%>" target="_blank" data-bypass="true">
-    <i class="icon-question-sign"></i>
-  </a>
-</span>
-
-<div class="controls-group qo-main-fields-row">
-  <div class="row-fluid fieldsets">
-    <div class="checkbox inline">
-      <input id="qoIncludeDocs" name="include_docs" type="checkbox" value="true" />
-      <label for="qoIncludeDocs" id="qoIncludeDocsLabel">Include Docs</label>
-    </div>
-
-    <% if (hasReduce) { %>
-    <div class="checkbox inline">
-      <input id="qoReduce" name="reduce" type="checkbox" value="true" />
-      <label for="qoReduce">Reduce</label>
-    </div>
-
-    <label class="drop-down inline" id="qoGroupLevelGroup">
-      Group Level
-      <select id="qoGroupLevel" name="group_level" class="input-small">
-        <option value="0">None</option>
-        <option value="1">1</option>
-        <option value="2">2</option>
-        <option value="3">3</option>
-        <option value="4">4</option>
-        <option value="5">5</option>
-        <option value="6">6</option>
-        <option value="7">7</option>
-        <option value="8">8</option>
-        <option value="9">9</option>
-        <option value="exact" selected="selected">Exact</option>
-      </select>
-    </label>
-    <% } %>
-  </div>
-</div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/tests/nightwatch/viewQueryOptions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/tests/nightwatch/viewQueryOptions.js b/app/addons/documents/tests/nightwatch/viewQueryOptions.js
index 8839dd5..244c380 100644
--- a/app/addons/documents/tests/nightwatch/viewQueryOptions.js
+++ b/app/addons/documents/tests/nightwatch/viewQueryOptions.js
@@ -22,7 +22,7 @@ module.exports = {
       .loginToGUI()
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_design/keyview/_view/keyview')
       .clickWhenVisible('#toggle-query', waitTime, false)
-      .clickWhenVisible('[data-action="showByKeys"]', waitTime, false)
+      .clickWhenVisible('#byKeys', waitTime, false)
       .setValue('#keys-input', '["document_1"]')
       .clickWhenVisible('#query-options .btn-success')
       .waitForElementNotPresent('#right-content [data-id="document_2"]', waitTime, false)

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/views-queryoptions.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views-queryoptions.js b/app/addons/documents/views-queryoptions.js
deleted file mode 100644
index 3eb8eef..0000000
--- a/app/addons/documents/views-queryoptions.js
+++ /dev/null
@@ -1,485 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-define([
-  "app",
-  "api",
-
-  // libs
-  'addons/fauxton/components'
-],
-
-  function (app, FauxtonAPI, Components) {
-
-    // our default settings for the Query Options tray
-    var defaultOptions = {
-      showStale: false,
-      hasReduce: false,
-
-      // all the possible query search params. Ultimately these should probably be moved higher-up (route object?),
-      // because they also apply to the actual search results. Seems better to place them there, then use them in
-      // both places
-      queryParams: {
-        include_docs: "false",
-        keys: "",
-        limit: "",
-        descending: "false",
-        skip: "",
-        update_seq: "false",
-        startkey: "",
-        endkey: "",
-        inclusive_end: "true",
-        reduce: "false",
-        stale: "",
-        group_level: "exact"
-      }
-    };
-
-
-    var Views = {};
-
-    // our main View. This is the only View exposed externally
-    Views.QueryOptionsTray = Components.Tray.extend({
-      template: "addons/documents/templates/query_options",
-      className: "query-options",
-
-      initialize: function (options) {
-
-        // overlays whatever custom options were passed with defaultOptions above, so this.options
-        // always contains all options. [This does a deep copy: we never overwrite defaultOptions!]
-        this.options = $.extend(true, {}, defaultOptions, options);
-
-        $(window).on("resize", this.onResize);
-
-        // initialize the parent View
-        this.initTray({
-          toggleTrayBtnSelector: '#toggle-query',
-          onShowTray: function () {
-            this.$('#query-options-tray button[type="submit"]').removeAttr("disabled");
-          }
-        });
-
-        // add the sub-views
-        this.mainFieldsView       = this.setView("#query-options-main-fields", new MainFieldsView(this.options));
-        this.keySearchFieldsView  = this.setView("#query-options-key-search", new KeySearchFieldsView(this.options));
-        this.additionalParamsView = this.setView("#query-options-additional-params", new AdditionalParamsView(this.options));
-      },
-
-      afterRender: function () {
-        this.onResize();
-      },
-
-      cleanup: function () {
-        $(window).off("resize", this.onResize);
-      },
-
-      events: {
-        "submit form.js-view-query-update": "onSubmit",  // submits the form
-        "click .btn-cancel": "hideTray"                  // closes the tray (doesn't reset the fields)
-      },
-
-      // returns all applicable query parameters for the Query Options tray
-      getQueryParams: function () {
-        var mainFieldParams = this.mainFieldsView.getParams();
-        var keySearchParams = this.keySearchFieldsView.getParams();
-        var additionalParams = this.additionalParamsView.getParams();
-
-        // assumption: there aren't conflicting keys
-        return _.extend({}, mainFieldParams, keySearchParams, additionalParams);
-      },
-
-      onSubmit: function (e) {
-        e.preventDefault();
-
-        // validate the user-inputted fields. If anything is invalid, the sub-view will display the appropriate
-        // errors to the user
-        if (!this.keySearchFieldsView.hasValidInputs() || !this.additionalParamsView.hasValidInputs()) {
-          return;
-        }
-
-        // make sure we can not submit twice which results in chaos (and no result ever)
-        this.$('#query-options-tray button[type="submit"]').attr("disabled", "disabled");
-
-        this.hideTray();
-
-        // this may be empty. That's ok! Perhaps the user just did a search with params, then removed them & wants the default
-        var params = this.getQueryParams();
-
-        // all looks good! Use navigate() to update the page URL and trigger the appropriate route
-        var url = app.utils.replaceQueryParams(params);
-        FauxtonAPI.navigate(url);
-      },
-
-      // if the screen is so small there isn't space for the full tray height we manually shrink the height to allow scrolling.
-      // Technically this should handle width as well, but we won't bother because there are way bigger issues with a screen
-      // with such a small width!
-      onResize: function () {
-        var $tray = $("#query-options-tray");
-        var heightFromTop = parseInt($tray.css("top"), 10);
-        var windowHeight = $(window).height();
-
-        // we apply the max-height to the form rather than the entire tray to allow the little up arrow to appear normally
-        $tray.find("form").css("max-height", windowHeight - heightFromTop);
-      },
-
-      /*
-       * Updates specific query options, leaving any that have been set already intact.
-       */
-      updateQueryOptions: function (options) {
-        this.options = $.extend(this.options, options);
-        this.updateSubViews();
-      },
-
-      /*
-       * Reset the query options back to the defaults, then apply whatever new options are needed.
-       */
-      resetQueryOptions: function (options) {
-        this.options = $.extend(true, {}, defaultOptions, options);
-        this.updateSubViews();
-      },
-
-      // helper
-      updateSubViews: function () {
-        this.mainFieldsView.update(this.options);
-        this.keySearchFieldsView.update(this.options);
-        this.additionalParamsView.update(this.options);
-      }
-    });
-
-
-    // ------ "private" Views ------
-
-    var MainFieldsView = FauxtonAPI.View.extend({
-      template: "addons/documents/templates/query_options_main_fields",
-      events: {
-        "change #qoReduce": "onToggleReduceCheckbox"
-      },
-
-      initialize: function (options) {
-        this.queryParams = options.queryParams;
-        this.showStale = options.showStale;
-        this.hasReduce = options.hasReduce;
-      },
-
-      update: function (options) {
-        this.queryParams = options.queryParams;
-        this.showStale = options.showStale;
-        this.hasReduce = options.hasReduce;
-
-        // if the View hasn't already rendered we can rely on afterRender() to pre-fill the fields
-        if (this.hasRendered) {
-          this.render();
-        }
-      },
-
-      afterRender: function () {
-        $("#qoIncludeDocs").prop("checked", this.queryParams.include_docs === "true");
-        this.updateReduceSettings(this.queryParams.reduce === "true");
-      },
-
-      /*
-       * The "Reduce" option comes with baggage:
-       *   - we can't include_docs for reduce = true
-       *   - can't include group_level for reduce = false
-       */
-      onToggleReduceCheckbox: function (e) {
-        e.preventDefault();
-        var isChecked = $(e.currentTarget).prop("checked");
-        this.updateReduceSettings(isChecked);
-      },
-
-
-      // helper function to hide/show, disable/enable fields based on whether "Reduce" is an option and whether
-      // it's checked
-      updateReduceSettings: function (isChecked) {
-        $("#qoReduce").prop("checked", isChecked);
-
-        var $qoIncludeDocs = $("#qoIncludeDocs"),
-          $qoIncludeDocsLabel = $("#qoIncludeDocsLabel"),
-          $qoGroupLevelGroup = $("#qoGroupLevelGroup");
-
-        if (this.hasReduce) {
-          $("#qoGroupLevel").val(this.queryParams.group_level);
-
-          if (isChecked) {
-            $qoIncludeDocs.prop({ "checked": false, "disabled": true });
-            $qoIncludeDocsLabel.addClass("disabled");
-            $qoGroupLevelGroup.removeClass("hide");
-          } else {
-            $qoIncludeDocs.prop("disabled", false);
-            $qoIncludeDocsLabel.removeClass("disabled");
-            $qoGroupLevelGroup.addClass("hide");
-          }
-        } else {
-          $qoIncludeDocs.prop("disabled", false);
-          $qoIncludeDocsLabel.removeClass("disabled");
-          $qoGroupLevelGroup.addClass("hide");
-        }
-      },
-
-      getParams: function () {
-        var params = {};
-        this.$("input:checked,select").each(function () {
-
-          // this ensures that only settings that differ from the defaults are passed along. If we didn't do this,
-          // the query string would be loaded up with all possible vals for each search (which would work, but would be ugly)
-          if (this.value !== defaultOptions.queryParams[this.name]) {
-            params[this.name] = this.value;
-          }
-        });
-
-        // this is weird, but necessary. ?group_level=exact used to work in older version of couchDB but no longer.
-        // Instead, to specify exact, you need to pass group=true instead. Since group_level=999 (exact) was the default
-        // value, it won't be in params
-        if (_.has(params, 'reduce') && !_.has(params, 'group_level')) {
-          params.group = 'true';
-        }
-
-        return params;
-      },
-
-      serialize: function () {
-        return {
-          hasReduce: this.hasReduce,
-          showStale: this.showStale
-        };
-      }
-    });
-
-
-    var KeySearchFieldsView = FauxtonAPI.View.extend({
-      template: "addons/documents/templates/query_options_key_search",
-      events: {
-        "click .toggle-btns > label": "toggleKeysSection"
-      },
-
-      initialize: function (options) {
-        this.queryParams = options.queryParams;
-        this.hasReduce = options.hasReduce;
-      },
-
-      update: function (options) {
-        this.queryParams = options.queryParams;
-        this.hasReduce = options.hasReduce;
-
-        if (this.hasRendered) {
-          this.render();
-        }
-      },
-
-      // prefill the form fields
-      afterRender: function () {
-        if (this.queryParams.keys) {
-          this.$(".toggle-btns > label[data-action=showByKeys]").addClass("active");
-          this.$(".js-query-keys-wrapper").removeClass("hide");
-          this.showByKeysSection();
-          $("#keys-input").val(this.queryParams.keys);
-        } else {
-
-          // if the startKey, endKey or inclusive_end differs from the defaults, show the section. Meh, this sucks...
-          if (defaultOptions.queryParams.startkey !== this.queryParams.startkey ||
-            defaultOptions.queryParams.endkey !== this.queryParams.endkey ||
-            defaultOptions.queryParams.inclusive_end !== this.queryParams.inclusive_end) {
-            this.$(".toggle-btns > label[data-action=showBetweenKeys]").addClass("active");
-            this.$(".js-keys-section").addClass("hide");
-            this.$(".js-query-keys-wrapper").removeClass("hide");
-            this.showBetweenKeysSection();
-
-            $("#startkey").prop("disabled", false).val(this.queryParams.startkey);
-            $("#endkey").prop("disabled", false).val(this.queryParams.endkey);
-            $("#qoIncludeEndKeyInResults").prop("checked", this.queryParams.inclusive_end === "true");
-          }
-        }
-      },
-
-      toggleKeysSection: function (e) {
-        e.preventDefault();
-
-        var $clickedEl = $(e.currentTarget);
-        var $keyFieldsWrapper = this.$(".js-query-keys-wrapper");
-
-        if ($clickedEl.hasClass("active")) {
-          $clickedEl.removeClass("active");
-          $keyFieldsWrapper.addClass("hide");
-        } else {
-          this.$(".toggle-btns > label").removeClass("active");
-          this.$(".js-keys-section").hide();
-
-          $clickedEl.addClass("active");
-          $keyFieldsWrapper.removeClass("hide");
-
-          // show section and disable what needs to be disabled
-          var action = $clickedEl.data("action");
-          if (action === "showByKeys") {
-            this.showByKeysSection();
-          } else {
-            this.showBetweenKeysSection();
-          }
-        }
-      },
-
-      showByKeysSection: function () {
-        this.$("#js-showKeys, .js-disabled-message").show();
-        this.$('[name="startkey"],[name="endkey"],[name="inclusive_end"]').prop("disabled", true);
-        this.$('[name="keys"]').removeAttr("disabled");
-      },
-
-      showBetweenKeysSection: function () {
-        this.$("#js-showStartEnd").show();
-        this.$('[name="startkey"],[name="endkey"],[name="inclusive_end"]').removeAttr("disabled");
-        this.$('.js-disabled-message').hide();
-        this.$('[name="keys"]').prop("disabled", true);
-      },
-
-      // this assumes that hasValidInputs has been called. Otherwise the returned param data may be invalid
-      getParams: function () {
-        var params = {};
-        var selectedKeysSection = this.getSelectedKeysSection();
-
-        // basically the gist of this is that it only actually returns *relevant* key-value pairs. Defaults
-        // aren't included because they'd clutter up the URL
-        if (selectedKeysSection === "showByKeys") {
-          var keys = $.trim($("#keys-input").val());
-          if (keys !== "") {
-            params.keys = keys;
-          }
-        } else if (selectedKeysSection === "showBetweenKeys") {
-          var startKey = $.trim($("#startkey").val());
-          if (startKey !== defaultOptions.queryParams.startkey) {
-            params.startkey = startKey;
-          }
-          var endKey = $.trim($("#endkey").val());
-          if (endKey !== defaultOptions.queryParams.endkey) {
-            params.endkey = endKey;
-          }
-          var includeEndKeyVal = $("#qoIncludeEndKeyInResults").is(":checked");
-          params.inclusive_end = (includeEndKeyVal) ? "true" : "false";
-        }
-        return params;
-      },
-
-      /*
-       * Checks to see that the user-inputted values are valid. If not, it displays a message to the user.
-       * @returns {boolean} true if all valid; false otherwise
-       */
-      hasValidInputs: function () {
-        var selectedKeysSection = this.getSelectedKeysSection(),
-          errorMsg = null;
-
-        if (selectedKeysSection === "showByKeys") {
-          var keys = this.parseJSON($("#keys-input").val());
-          if (_.isUndefined(keys) || !_.isArray(keys)) {
-            errorMsg = "Keys values must be in an array, e.g [1,2,3]";
-          }
-        } else {
-          var startKey = $.trim($("#startkey").val()),
-            endKey = $.trim($("#endkey").val());
-
-          if (startKey !== "" && _.isUndefined(this.parseJSON(startKey))) {
-            errorMsg = "JSON Parse Error on the Start Key field";
-          } else if (endKey !== "" && _.isUndefined(this.parseJSON(endKey))) {
-            errorMsg = "JSON Parse Error on the End Key field";
-          }
-        }
-
-        if (errorMsg !== null) {
-          this.$(".js-keys-error").empty();
-
-          FauxtonAPI.addNotification({
-            type: "error",
-            msg: errorMsg,
-            clear:  false
-          });
-          return false;
-        }
-
-        return true;
-      },
-
-      parseJSON: function (value) {
-        try {
-          return JSON.parse(value);
-        } catch (e) {
-          return undefined;
-        }
-      },
-
-      getSelectedKeysSection: function () {
-        return this.$(".toggle-btns > label.active").data("action");
-      }
-    });
-
-
-    var AdditionalParamsView = FauxtonAPI.View.extend({
-      template: "addons/documents/templates/query_options_additional_params",
-
-      initialize: function (options) {
-        this.queryParams = options.queryParams;
-        this.showStale = options.showStale;
-      },
-
-      update: function (options) {
-        this.queryParams = options.queryParams;
-        this.showStale = options.showStale;
-        if (this.hasRendered) {
-          this.render();
-        }
-      },
-
-      afterRender: function () {
-        $("#qoUpdateSeq").prop("checked", this.queryParams.update_seq === "true");
-        $("#qoDescending").prop("checked", this.queryParams.descending === "true");
-        $("#qoLimit").val(this.queryParams.limit);
-        $("#qoSkip").val(this.queryParams.skip);
-        $("#qoStale").prop("checked", this.queryParams.stale === "ok");
-      },
-
-      getParams: function () {
-        var params = {};
-        this.$("input,select").each(function () {
-          if ($(this).is(":checkbox")) {
-            if (this.checked) {
-              params[this.name] = this.value;
-            }
-          } else {
-            var val = $.trim(this.value);
-            if (val !== "") {
-              params[this.name] = this.value;
-            }
-          }
-        });
-        return params;
-      },
-
-      hasValidInputs: function () {
-        var allValid = true;
-        var skipVal = $("#qoSkip").val();
-        if (skipVal !== "" && /\D/.test(skipVal)) {
-          FauxtonAPI.addNotification({
-            msg: "Please only enter numbers only for the Skip field.",
-            type: "error",
-            clear:  true
-          });
-          allValid = false;
-        }
-        return allValid;
-      },
-
-      serialize: function () {
-        return {
-          showStale: this.showStale
-        };
-      }
-    });
-
-    return Views;
-  });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/documents/views.js
----------------------------------------------------------------------
diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js
index 13aaeca..fa9214b 100644
--- a/app/addons/documents/views.js
+++ b/app/addons/documents/views.js
@@ -18,14 +18,15 @@ define([
   "addons/databases/resources",
 
   // Views
-  "addons/documents/views-queryoptions",
+  "addons/documents/queryoptions/queryoptions.react",
+  "addons/documents/queryoptions/actions",
 
   //plugins
   "plugins/prettify"
 ],
 
 function (app, FauxtonAPI, Components, Documents,
-  Databases, QueryOptions) {
+  Databases, QueryOptions, QueryActions) {
 
   var Views = {};
 
@@ -57,15 +58,10 @@ function (app, FauxtonAPI, Components, Documents,
         database: this.database,
         collection: this.database.allDocs
       }));
-
-      // add the Query Options modal + header link
-      this.queryOptions = this.insertView("#query-options", new QueryOptions.QueryOptionsTray({
-        hasReduce: false,
-        showStale: false
-      }));
     },
 
     afterRender: function () {
+      QueryOptions.render('#query-options');
       this.toggleQueryOptionsHeader(this.isHidden);
     },
 
@@ -83,14 +79,8 @@ function (app, FauxtonAPI, Components, Documents,
       this.apiBar && this.apiBar.update(api);
     },
 
-    // these are similar, but different! resetQueryOptions() completely resets the settings then overlays the new ones;
-    // updateQueryOptions() just updates the existing settings with whatever is specified. Between them, the
     resetQueryOptions: function (options) {
-      this.queryOptions.resetQueryOptions(options);
-    },
-
-    updateQueryOptions: function (options) {
-      this.queryOptions.updateQueryOptions(options);
+      QueryActions.reset(options);
     },
 
     hideQueryOptions: function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/addons/fauxton/components.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/components.js b/app/addons/fauxton/components.js
index ce58eb4..42c8102 100644
--- a/app/addons/fauxton/components.js
+++ b/app/addons/fauxton/components.js
@@ -231,7 +231,6 @@ function (app, FauxtonAPI, ace, spin, ZeroClipboard) {
 
     toggleTray: function (e) {
       e.preventDefault();
-      e.stopImmediatePropagation();
 
       if (this.trayVisible()) {
         this.hideTray();

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/app/constants.js
----------------------------------------------------------------------
diff --git a/app/constants.js b/app/constants.js
index 16c653a..f9a8123 100644
--- a/app/constants.js
+++ b/app/constants.js
@@ -28,6 +28,7 @@ define([], function () {
     EVENTS: {
       TRAY_CLOSED: 'tray:closed',
       TRAY_OPENED: 'tray:opened',
+      TRAY_HIDE: 'tray:hide',
       NAVBAR_SIZE_CHANGED: 'navbar:size_changed'
     },
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d8c28d98/assets/less/trays.less
----------------------------------------------------------------------
diff --git a/assets/less/trays.less b/assets/less/trays.less
index 982b52c..c9c6e74 100644
--- a/assets/less/trays.less
+++ b/assets/less/trays.less
@@ -12,6 +12,14 @@
 
 @import "bootstrap/mixins.less";
 
+.hide-tray {
+  display: none;
+}
+
+.show-tray {
+  display: block !important; //nasty hack until we convert all trays to React
+}
+
 .tray {
   .bottom-left-shadow-border;
   display: none;


Mime
View raw message