summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarren Smith <garren.smith@gmail.com>2014-01-28 16:30:38 +0200
committerGarren Smith <garren.smith@gmail.com>2014-03-06 11:48:09 +0200
commit4e60f0b7a7b26662dde0ebc0a73c70df54e0f309 (patch)
treef4267162072e1ffcdb84613ffcd64e70198e2526
parent0782a44ce6f54aa553e014acfa3bb11448c0682e (diff)
downloadcouchdb-4e60f0b7a7b26662dde0ebc0a73c70df54e0f309.tar.gz
Fauxton: Improved pagination
This is an improvement and fix on the current pagination. This fixes pagination so that it works with all query options. Pagination in Couchdb is quite complex and there are plenty of situations to cater for. This new pagination works quite differently to the previous way we had it working for a user. A user can set the number of documents they want to view on a page. This is not related to limit options in the query options - the limit option is an overall cap of how many documents to paginate too. A limit of none is possible and is the default. If the limit option is set to 50 and a user wants 10 docs per page, the would then be able to paginate 3 pages before hitting the end. Another change is that the api url and browser url does not change when we paginate. That happens internally and hence the new addition of urlParams and docParams. This allows Fauxton to keep track of the parameters that the user sees and the parameters that the document needs to paginate. Fixes COUCHDB-2067
-rw-r--r--src/fauxton/app/addons/databases/resources.js2
-rw-r--r--src/fauxton/app/addons/databases/templates/item.html2
-rw-r--r--src/fauxton/app/addons/databases/views.js3
-rw-r--r--src/fauxton/app/addons/documents/assets/less/documents.less8
-rw-r--r--src/fauxton/app/addons/documents/resources.js211
-rw-r--r--src/fauxton/app/addons/documents/routes.js192
-rw-r--r--src/fauxton/app/addons/documents/templates/advanced_options.html8
-rw-r--r--src/fauxton/app/addons/documents/templates/all_docs_list.html15
-rw-r--r--src/fauxton/app/addons/documents/templates/all_docs_number.html24
-rw-r--r--src/fauxton/app/addons/documents/templates/sidebar.html4
-rw-r--r--src/fauxton/app/addons/documents/templates/view_editor.html4
-rw-r--r--src/fauxton/app/addons/documents/tests/resourcesSpec.js27
-rw-r--r--src/fauxton/app/addons/documents/views.js241
-rw-r--r--src/fauxton/app/addons/fauxton/base.js14
-rw-r--r--src/fauxton/app/addons/fauxton/components.js127
-rw-r--r--src/fauxton/app/addons/fauxton/tests/paginateSpec.js18
-rw-r--r--src/fauxton/test/mocha/chai.js804
-rw-r--r--src/fauxton/test/test.config.underscore4
18 files changed, 1153 insertions, 555 deletions
diff --git a/src/fauxton/app/addons/databases/resources.js b/src/fauxton/app/addons/databases/resources.js
index ea1aed243..80cd533f8 100644
--- a/src/fauxton/app/addons/databases/resources.js
+++ b/src/fauxton/app/addons/databases/resources.js
@@ -22,7 +22,7 @@ define([
function(app, FauxtonAPI, Documents) {
var Databases = FauxtonAPI.addon();
- Databases.DocLimit = 20;
+ Databases.DocLimit = 100;
Databases.Model = FauxtonAPI.Model.extend({
initialize: function(options) {
diff --git a/src/fauxton/app/addons/databases/templates/item.html b/src/fauxton/app/addons/databases/templates/item.html
index e2f80712f..549f42157 100644
--- a/src/fauxton/app/addons/databases/templates/item.html
+++ b/src/fauxton/app/addons/databases/templates/item.html
@@ -13,7 +13,7 @@ the License.
-->
<td>
- <a href="#/database/<%=encoded%>/_all_docs?limit=<%=docLimit%>"><%= database.get("name") %></a>
+ <a href="#/database/<%=encoded%>/_all_docs"><%= database.get("name") %></a>
</td>
<td><%= database.status.humanSize() %></td>
<td><%= database.status.numDocs() %></td>
diff --git a/src/fauxton/app/addons/databases/views.js b/src/fauxton/app/addons/databases/views.js
index 7f23d6598..a56267fbb 100644
--- a/src/fauxton/app/addons/databases/views.js
+++ b/src/fauxton/app/addons/databases/views.js
@@ -31,8 +31,7 @@ function(app, Components, FauxtonAPI, Databases) {
return {
encoded: app.utils.safeURLName(this.model.get("name")),
- database: this.model,
- docLimit: Databases.DocLimit
+ database: this.model
};
}
});
diff --git a/src/fauxton/app/addons/documents/assets/less/documents.less b/src/fauxton/app/addons/documents/assets/less/documents.less
index 9dee85e7b..c30a9afae 100644
--- a/src/fauxton/app/addons/documents/assets/less/documents.less
+++ b/src/fauxton/app/addons/documents/assets/less/documents.less
@@ -23,6 +23,14 @@ button.beautify {
margin-top: 20px;
}
+#per-page {
+ float: right;
+
+ #select-per-page {
+ margin-top: 10px;
+ }
+
+}
/** used in all_docs_list.html **/
.view {
diff --git a/src/fauxton/app/addons/documents/resources.js b/src/fauxton/app/addons/documents/resources.js
index adfee1f16..c0b736f35 100644
--- a/src/fauxton/app/addons/documents/resources.js
+++ b/src/fauxton/app/addons/documents/resources.js
@@ -18,6 +18,92 @@ define([
function(app, FauxtonAPI) {
var Documents = FauxtonAPI.addon();
+ Documents.QueryParams = (function () {
+ var _eachParams = function (params, action) {
+ _.each(['startkey', 'endkey', 'key'], function (key) {
+ if (_.has(params, key)) {
+ params[key] = action(params[key]);
+ }
+ });
+
+ return params;
+ };
+
+ return {
+ parse: function (params) {
+ return _eachParams(params, JSON.parse);
+ },
+
+ stringify: function (params) {
+ return _eachParams(params, JSON.stringify);
+ }
+ };
+ })();
+
+ Documents.paginate = {
+ history: [],
+ calculate: function (doc, defaultParams, currentParams, _isAllDocs) {
+ var docId = '',
+ lastId = '',
+ isView = !!!_isAllDocs,
+ key;
+
+ if (currentParams.keys) {
+ throw "Cannot paginate when keys is specfied";
+ }
+
+ if (_.isUndefined(doc)) {
+ throw "Require docs to paginate";
+ }
+
+ // defaultParams should always override the user-specified parameters
+ _.extend(currentParams, defaultParams);
+
+ lastId = doc.id || doc._id;
+
+ // If we are paginating on a view, we need to set a ``key`` and a ``docId``
+ // and expect that they are different values.
+ if (isView) {
+ key = doc.key;
+ docId = lastId;
+ } else {
+ docId = key = lastId;
+ }
+
+ // Set parameters to paginate
+ if (isView) {
+ currentParams.startkey_docid = docId;
+ currentParams.startkey = key;
+ } else if (currentParams.startkey) {
+ currentParams.startkey = key;
+ } else {
+ currentParams.startkey_docid = docId;
+ }
+
+ return currentParams;
+ },
+
+ next: function (docs, currentParams, perPage, _isAllDocs) {
+ var params = {limit: perPage, skip: 1},
+ doc = _.last(docs);
+
+ this.history.push(_.clone(currentParams));
+ return this.calculate(doc, params, currentParams, _isAllDocs);
+ },
+
+ previous: function (docs, currentParams, perPage, _isAllDocs) {
+ var params = this.history.pop(),
+ doc = _.first(docs);
+
+ params.limit = perPage;
+ return params;
+ },
+
+ reset: function () {
+ this.history = [];
+ }
+ };
+
Documents.Doc = FauxtonAPI.Model.extend({
idAttribute: "_id",
documentation: function(){
@@ -274,20 +360,31 @@ function(app, FauxtonAPI) {
Documents.AllDocs = FauxtonAPI.Collection.extend({
model: Documents.Doc,
+ isAllDocs: true,
documentation: function(){
return "docs";
},
initialize: function(_models, options) {
this.database = options.database;
- this.params = options.params;
- this.skipFirstItem = false;
-
+ this.params = _.clone(options.params);
this.on("remove",this.decrementTotalRows , this);
+ this.perPageLimit = options.perPageLimit || 20;
+
+ if (!this.params.limit) {
+ this.params.limit = this.perPageLimit;
+ }
},
- url: function(context) {
+ url: function(context, params) {
var query = "";
- if (this.params) {
+
+ if (params) {
+ if (!_.isEmpty(params)) {
+ query = "?" + $.param(params);
+ } else {
+ query = '';
+ }
+ } else if (this.params) {
query = "?" + $.param(this.params);
}
@@ -314,34 +411,13 @@ function(app, FauxtonAPI) {
});
},
- urlNextPage: function (num, lastId) {
- if (!lastId) {
- var doc = this.last();
-
- if (doc) {
- lastId = doc.id;
- } else {
- lastId = '';
- }
- }
-
- this.params.startkey_docid = '"' + lastId + '"';
- this.params.startkey = '"' + lastId + '"';
- // when paginating forward, fetch 21 and don't show
- // the first item as it was the last item in the previous list
- this.params.limit = num + 1;
- return this.url('app');
+ updateLimit: function (limit) {
+ this.perPageLimit = limit;
+ this.params.limit = limit;
},
- urlPreviousPage: function (num, params) {
- if (params) {
- this.params = params;
- } else {
- this.params = {reduce: false};
- }
-
- this.params.limit = num;
- return this.url('app');
+ updateParams: function (params) {
+ this.params = params;
},
totalRows: function() {
@@ -359,18 +435,6 @@ function(app, FauxtonAPI) {
return this.viewMeta.update_seq || false;
},
- recordStart: function () {
- if (this.viewMeta.offset === 0) {
- return 1;
- }
-
- if (this.skipFirstItem) {
- return this.viewMeta.offset + 2;
- }
-
- return this.viewMeta.offset + 1;
- },
-
parse: function(resp) {
var rows = resp.rows;
@@ -409,11 +473,23 @@ function(app, FauxtonAPI) {
this.view = options.view;
this.design = options.design.replace('_design/','');
this.skipFirstItem = false;
+ this.perPageLimit = options.perPageLimit || 20;
+
+ if (!this.params.limit) {
+ this.params.limit = this.perPageLimit;
+ }
+
},
- url: function(context) {
+ url: function(context, params) {
var query = "";
- if (this.params) {
+ if (params) {
+ if (!_.isEmpty(params)) {
+ query = "?" + $.param(params);
+ } else {
+ query = '';
+ }
+ } else if (this.params) {
query = "?" + $.param(this.params);
}
@@ -430,42 +506,18 @@ function(app, FauxtonAPI) {
return url.join("/") + query;
},
- urlNextPage: function (num, lastId) {
- if (!lastId) {
- lastDoc = this.last();
- }
-
- var id = lastDoc.get("id");
- if (id) {
- this.params.startkey_docid = id;
- }
-
- this.params.startkey = JSON.stringify(lastDoc.get('key'));
- this.params.limit = num + 1;
- return this.url('app');
+ updateParams: function (params) {
+ this.params = params;
},
- urlPreviousPage: function (num, params) {
- if (params) {
- this.params = params;
- } else {
- this.params = {reduce: false};
+ updateLimit: function (limit) {
+ if (this.params.startkey_docid && this.params.startkey) {
+ //we are paginating so set limit + 1
+ this.params.limit = limit + 1;
+ return;
}
- this.params.limit = num;
- return this.url('app');
- },
-
- recordStart: function () {
- if (this.viewMeta.offset === 0) {
- return 1;
- }
-
- if (this.skipFirstItem) {
- return this.viewMeta.offset + 2;
- }
-
- return this.viewMeta.offset + 1;
+ this.params.limit = limit;
},
totalRows: function() {
@@ -565,6 +617,7 @@ function(app, FauxtonAPI) {
return timeString;
}
+
});
@@ -619,10 +672,6 @@ function(app, FauxtonAPI) {
return deferred;
},
- recordStart: function () {
- return 1;
- },
-
totalRows: function() {
return this.viewMeta.total_rows || "unknown";
},
diff --git a/src/fauxton/app/addons/documents/routes.js b/src/fauxton/app/addons/documents/routes.js
index 1510485ee..f34019578 100644
--- a/src/fauxton/app/addons/documents/routes.js
+++ b/src/fauxton/app/addons/documents/routes.js
@@ -155,13 +155,11 @@ function(app, FauxtonAPI, Documents, Databases) {
"route:updateAllDocs": "updateAllDocsFromView",
"route:updatePreviewDocs": "updateAllDocsFromPreview",
"route:reloadDesignDocs": "reloadDesignDocs",
- "route:paginate": "paginate"
+ "route:paginate": "paginate",
+ "route:perPageChange": "perPageChange"
},
initialize: function (route, masterLayout, options) {
- var docOptions = app.getParams();
- docOptions.include_docs = true;
-
this.databaseName = options[0];
this.data = {
@@ -170,9 +168,11 @@ function(app, FauxtonAPI, Documents, Databases) {
this.data.designDocs = new Documents.AllDocs(null, {
database: this.data.database,
- params: {startkey: '"_design"',
+ params: {
+ startkey: '"_design"',
endkey: '"_design1"',
- include_docs: true}
+ include_docs: true
+ }
});
this.sidebar = this.setView("#sidebar-content", new Documents.Views.Sidebar({
@@ -185,12 +185,32 @@ function(app, FauxtonAPI, Documents, Databases) {
return this.data.designDocs.fetch();
},
+ createParams: function (options) {
+ var urlParams = app.getParams(options);
+ return {
+ urlParams: urlParams,
+ docParams: _.extend(_.clone(urlParams), {limit: this.getDocPerPageLimit(urlParams, 20)})
+ };
+ },
+
+ /*
+ * docParams are the options collection uses to fetch from the server
+ * urlParams are what are shown in the url and to the user
+ * They are not the same when paginating
+ */
allDocs: function(databaseName, options) {
- var docOptions = app.getParams(options);
+ var params = this.createParams(options),
+ urlParams = params.urlParams,
+ docParams = params.docParams;
+
+ if (this.eventAllDocs) {
+ this.eventAllDocs = false;
+ return;
+ }
- this.data.database.buildAllDocs(docOptions);
+ this.data.database.buildAllDocs(docParams);
- if (docOptions.startkey && docOptions.startkey.indexOf('_design') > -1) {
+ if (docParams.startkey && docParams.startkey.indexOf('_design') > -1) {
this.sidebar.setSelectedTab('design-docs');
} else {
this.sidebar.setSelectedTab('all-docs');
@@ -206,22 +226,29 @@ function(app, FauxtonAPI, Documents, Databases) {
this.setView("#dashboard-upper-content", new Documents.Views.AllDocsLayout({
database: this.data.database,
collection: this.data.database.allDocs,
- params: docOptions
+ params: urlParams,
+ docParams: docParams
}));
this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
- collection: this.data.database.allDocs
+ collection: this.data.database.allDocs,
+ docParams: docParams,
+ params: urlParams
}));
this.crumbs = [
{"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)}
];
- this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
+ this.apiUrl = [this.data.database.allDocs.url("apiurl", urlParams), this.data.database.allDocs.documentation() ];
+ //reset the pagination history - the history is used for pagination.previous
+ Documents.paginate.reset();
},
viewFn: function (databaseName, ddoc, view) {
- var params = app.getParams(),
+ var params = this.createParams(),
+ urlParams = params.urlParams,
+ docParams = params.docParams;
decodeDdoc = decodeURIComponent(ddoc);
view = view.replace(/\?.*$/,'');
@@ -230,7 +257,7 @@ function(app, FauxtonAPI, Documents, Databases) {
database: this.data.database,
design: decodeDdoc,
view: view,
- params: params
+ params: docParams
});
var ddocInfo = {
@@ -243,7 +270,7 @@ function(app, FauxtonAPI, Documents, Databases) {
model: this.data.database,
ddocs: this.data.designDocs,
viewName: view,
- params: params,
+ params: urlParams,
newView: false,
database: this.data.database,
ddocInfo: ddocInfo
@@ -256,7 +283,9 @@ function(app, FauxtonAPI, Documents, Databases) {
collection: this.data.indexedDocs,
nestedView: Documents.Views.Row,
viewList: true,
- ddocInfo: ddocInfo
+ ddocInfo: ddocInfo,
+ docParams: docParams,
+ params: urlParams
}));
this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + '_' + app.utils.removeSpecialCharacters(view));
@@ -267,15 +296,15 @@ function(app, FauxtonAPI, Documents, Databases) {
];
};
- this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
+ this.apiUrl = [this.data.indexedDocs.url("apiurl", urlParams), "docs"];
+ Documents.paginate.reset();
},
newViewEditor: function () {
var params = app.getParams();
- if (this.toolsView) {
- this.toolsView.remove();
- }
+ this.toolsView && this.toolsView.remove();
+ this.documentsView && this.documentsView.remove();
this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({
ddocs: this.data.designDocs,
@@ -290,39 +319,41 @@ function(app, FauxtonAPI, Documents, Databases) {
{"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)},
];
};
+
+ Documents.paginate.reset();
},
updateAllDocsFromView: function (event) {
var view = event.view,
- docOptions = app.getParams(),
- ddoc = event.ddoc;
+ params = this.createParams(),
+ urlParams = params.urlParams,
+ docParams = params.docParams,
+ ddoc = event.ddoc,
+ collection;
- this.documentsView && this.documentsView.remove();
+ docParams.limit = this.getDocPerPageLimit(urlParams, this.documentsView.perPage());
+ this.documentsView.forceRender();
if (event.allDocs) {
- this.data.database.buildAllDocs(docOptions);
- this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
- collection: this.data.database.allDocs
- }));
- //this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
- return;
- }
+ this.eventAllDocs = true; // this is horrible. But I cannot get the trigger not to fire the route!
+ this.data.database.buildAllDocs(docParams);
+ collection = this.data.database.allDocs;
- this.data.indexedDocs = new Documents.IndexCollection(null, {
- database: this.data.database,
- design: ddoc,
- view: view,
- params: app.getParams()
- });
+ } else {
+ collection = this.data.indexedDocs = new Documents.IndexCollection(null, {
+ database: this.data.database,
+ design: ddoc,
+ view: view,
+ params: docParams
+ });
- this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
- database: this.data.database,
- collection: this.data.indexedDocs,
- nestedView: Documents.Views.Row,
- viewList: true
- }));
+ }
+
+ this.documentsView.setCollection(collection);
+ this.documentsView.setParams(docParams, urlParams);
- this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
+ this.apiUrl = [collection.url("apiurl", urlParams), "docs"];
+ Documents.paginate.reset();
},
updateAllDocsFromPreview: function (event) {
@@ -345,14 +376,49 @@ function(app, FauxtonAPI, Documents, Databases) {
}));
},
- paginate: function (direction) {
- _.extend(this.documentsView.collection.params, app.getParams());
+ perPageChange: function (perPage) {
+ var params = app.getParams();
+ this.perPage = perPage;
+ this.documentsView.updatePerPage(perPage);
+ this.documentsView.forceRender();
+ params.limit = perPage;
+ this.documentsView.collection.params = params;
+ this.setDocPerPageLimit(perPage);
+ },
+
+ paginate: function (options) {
+ var params = {},
+ urlParams = app.getParams(),
+ collection = this.documentsView.collection;
+
this.documentsView.forceRender();
- if (direction === 'next') {
- this.documentsView.collection.skipFirstItem = true;
+
+ // this is really ugly. But we basically need to make sure that
+ // all parameters are in the correct state and have been parsed before we
+ // calculate how to paginate the collection
+ collection.params = Documents.QueryParams.parse(collection.params);
+ urlParams = Documents.QueryParams.parse(urlParams);
+
+ if (options.direction === 'next') {
+ params = Documents.paginate.next(collection.toJSON(),
+ collection.params,
+ options.perPage,
+ !!collection.isAllDocs);
} else {
- this.documentsView.collection.skipFirstItem = false;
+ params = Documents.paginate.previous(collection.toJSON(),
+ collection.params,
+ options.perPage,
+ !!collection.isAllDocs);
}
+
+ // use the perPage sent from IndexPagination as it calculates how many
+ // docs to fetch for next page
+ params.limit = options.perPage;
+
+ // again not pretty but need to make sure all the parameters can be correctly
+ // built into a query
+ params = Documents.QueryParams.stringify(params);
+ collection.updateParams(params);
},
reloadDesignDocs: function (event) {
@@ -361,6 +427,32 @@ function(app, FauxtonAPI, Documents, Databases) {
if (event && event.selectedTab) {
this.sidebar.setSelectedTab(event.selectedTab);
}
+ },
+
+ setDocPerPageLimit: function (perPage) {
+ window.localStorage.setItem('fauxton:perpage', perPage);
+ },
+
+
+ getDocPerPageLimit: function (urlParams, perPage) {
+ var storedPerPage = perPage;
+
+ if (window.localStorage) {
+ storedPerPage = window.localStorage.getItem('fauxton:perpage');
+
+ if (!storedPerPage) {
+ this.setDocPerPageLimit(perPage);
+ storedPerPage = perPage;
+ } else {
+ storedPerPage = parseInt(storedPerPage, 10);
+ }
+ }
+
+ if (!urlParams.limit || urlParams.limit > storedPerPage) {
+ return storedPerPage;
+ } else {
+ return urlParams.limit;
+ }
}
});
@@ -383,9 +475,9 @@ function(app, FauxtonAPI, Documents, Databases) {
this.databaseName = options[0];
this.database = new Databases.Model({id: this.databaseName});
- var docOptions = app.getParams();
+ var docParams = app.getParams();
- this.database.buildChanges(docOptions);
+ this.database.buildChanges(docParams);
this.setView("#tabs", new Documents.Views.Tabs({
collection: this.designDocs,
diff --git a/src/fauxton/app/addons/documents/templates/advanced_options.html b/src/fauxton/app/addons/documents/templates/advanced_options.html
index bea256a93..e282c6254 100644
--- a/src/fauxton/app/addons/documents/templates/advanced_options.html
+++ b/src/fauxton/app/addons/documents/templates/advanced_options.html
@@ -12,7 +12,7 @@ License for the specific language governing permissions and limitations under
the License.
-->
<div class="errors-container"></div>
-<form class="view-query-update custom-inputs">
+<form class="js-view-query-update custom-inputs">
<div class="controls-group">
<div class="row-fluid">
<div class="controls controls-row">
@@ -68,10 +68,12 @@ the License.
<select name="limit" class="input-small">
<option>5</option>
<option>10</option>
- <option selected="selected">20</option>
+ <option >20</option>
<option>30</option>
<option>50</option>
- <option>100</option>
+ <option >100</option>
+ <option>500</option>
+ <option selected="selected">None</option>
</select>
</label>
<div class="checkbox inline">
diff --git a/src/fauxton/app/addons/documents/templates/all_docs_list.html b/src/fauxton/app/addons/documents/templates/all_docs_list.html
index cdec81e32..a521ff969 100644
--- a/src/fauxton/app/addons/documents/templates/all_docs_list.html
+++ b/src/fauxton/app/addons/documents/templates/all_docs_list.html
@@ -29,15 +29,16 @@ the License.
<p>
<div id="item-numbers"> </div>
-
- <% if (requestDuration) { %>
- <span class="view-request-duration pull-right">
- View request duration: <strong> <%= requestDuration %> </strong>
- </span>
- <% } %>
- </p>
<table class="all-docs table table-striped table-condensed">
<tbody></tbody>
</table>
+
+ <% if (endOfResults) { %>
+ <div class="text-center well">
+ <p class="muted">
+ End of results - <a id="js-end-results" href="#query" data-bypass="true" data-toggle="tab">edit query</a>
+ </p>
+ </div>
+ <% } %>
<div id="documents-pagination"></div>
</div>
diff --git a/src/fauxton/app/addons/documents/templates/all_docs_number.html b/src/fauxton/app/addons/documents/templates/all_docs_number.html
index df8fe07b6..0461a4bf1 100644
--- a/src/fauxton/app/addons/documents/templates/all_docs_number.html
+++ b/src/fauxton/app/addons/documents/templates/all_docs_number.html
@@ -11,13 +11,25 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
-->
-<% if (totalRows === "unknown"){ %>
- Showing 0 documents. <a href="#/database/<%=database%>/new"> Create your first document.</a>
-<% } else if (showNumbers) { %>
- Showing <%=offset%> - <%= numModels %> of <%= totalRows %> rows
+<% if (totalRows === "unknown" || totalRows === 0){ %>
+Showing 0 documents. <a href="#/database/<%=database%>/new"> Create your first document.</a>
<% } else { %>
- Showing <%=pageStart%> - <%= pageEnd %>
+Showing <%=pageStart%> - <%= pageEnd %>
<%}%>
<% if (updateSeq) { %>
- -- Update Sequence: <%= updateSeq %>
+-- Update Sequence: <%= updateSeq %>
<% } %>
+
+<div id="per-page">
+ <label id="per-page" class="drop-down inline">
+ Per page:
+ <select id="select-per-page" name="per-page" class="input-small">
+ <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>
+ </select>
+ </label>
+</div>
diff --git a/src/fauxton/app/addons/documents/templates/sidebar.html b/src/fauxton/app/addons/documents/templates/sidebar.html
index 73589604e..750cd3004 100644
--- a/src/fauxton/app/addons/documents/templates/sidebar.html
+++ b/src/fauxton/app/addons/documents/templates/sidebar.html
@@ -55,8 +55,8 @@ the License.
<nav>
<ul class="nav nav-list">
- <li class="active"><a id="all-docs" href="#<%= database.url('index') %>?limit=<%= docLimit %>" class="toggle-view"> All documents</a></li>
- <li><a id="design-docs" href='#<%= database.url("index") %>?limit=<%= docLimit %>&startkey="_design"&endkey="_e"' class="toggle-view"> All design docs</a></li>
+ <li class="active"><a id="all-docs" href="#<%= database.url('index') %>" class="toggle-view"> All documents</a></li>
+ <li><a id="design-docs" href='#<%= database.url("index") %>?startkey="_design"&endkey="_e"' class="toggle-view"> All design docs</a></li>
</ul>
<ul class="nav nav-list views">
<li class="nav-header">Secondary Indexes</li>
diff --git a/src/fauxton/app/addons/documents/templates/view_editor.html b/src/fauxton/app/addons/documents/templates/view_editor.html
index 6a2084923..e08e36e67 100644
--- a/src/fauxton/app/addons/documents/templates/view_editor.html
+++ b/src/fauxton/app/addons/documents/templates/view_editor.html
@@ -17,12 +17,14 @@ the License.
<% if (newView) { %>Create Index <% } else { %>Edit Index <% } %></a></li>
<% if (!newView) { %>
<li><a data-bypass="true" id="query-nav" href="#query" data-toggle="tab">
- <i class="fonticon-plus fonticon"></i> Query Options</a></li>
+ <i class="fonticon-plus fonticon"></i> Query Options</a>
+ </li>
<li><a data-bypass="true" id="meta-nav" href="#metadata" data-toggle="tab">Design Doc Metadata</a></li>
<% } %>
</ul>
<div class="all-docs-list errors-container"></div>
<div class="tab-content">
+ <div id="query-options-wrapper"></div>
<div class="tab-pane active" id="index">
<div id="define-view" class="ddoc-alert well">
<div class="errors-container"></div>
diff --git a/src/fauxton/app/addons/documents/tests/resourcesSpec.js b/src/fauxton/app/addons/documents/tests/resourcesSpec.js
index 380a4e4fc..62506e6e4 100644
--- a/src/fauxton/app/addons/documents/tests/resourcesSpec.js
+++ b/src/fauxton/app/addons/documents/tests/resourcesSpec.js
@@ -32,20 +32,6 @@ define([
});
- it('Should return urlNext', function () {
- var url = collection.urlNextPage(20);
-
- assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=21&reduce=false&startkey_docid=myId2&startkey=');
-
- });
-
- it('Should return urlPrevious', function () {
- var url = collection.urlPreviousPage(20, {limit: 21, reduce: false, startkey_docid: "myId1",startkey:"myId1"} );
-
- assert.equal(url, 'database/databaseId/_design/myDoc/_view/?limit=20&reduce=false&startkey_docid=myId1&startkey=myId1');
-
- });
-
});
describe('AllDocs', function () {
@@ -65,19 +51,6 @@ define([
});
- it('Should return urlNext', function () {
- var url = collection.urlNextPage(20);
-
- assert.equal(url, 'database/databaseId/_all_docs?limit=21&startkey_docid=%22myId2%22&startkey=%22myId2%22');
-
- });
-
- it('Should return urlPrevious', function () {
- var url = collection.urlPreviousPage(20, {limit: 21, startkey_docid: "myId1",startkey:"myId1"} );
- assert.equal(url, 'database/databaseId/_all_docs?limit=20&startkey_docid=myId1&startkey=myId1');
- });
-
-
});
});
diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js
index 7282ed797..54ec261f0 100644
--- a/src/fauxton/app/addons/documents/views.js
+++ b/src/fauxton/app/addons/documents/views.js
@@ -26,8 +26,6 @@ define([
// Plugins
"plugins/beautify",
"plugins/prettify",
-
-
],
function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColumns, beautify) {
@@ -416,25 +414,38 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
initialize: function (options) {
this.newView = options.newView || false;
- this.showNumbers = options.showNumbers;
this.pagination = options.pagination;
-
+ _.bindAll(this);
+
+ this._perPage = options.perPageDefault || 20;
this.listenTo(this.collection, 'totalRows:decrement', this.render);
},
+ events: {
+ 'change #select-per-page': 'updatePerPage'
+ },
+
+ updatePerPage: function (event) {
+ this._perPage = parseInt(this.$('#select-per-page :selected').val(), 10);
+ this.pagination.updatePerPage(this.perPage());
+ FauxtonAPI.triggerRouteEvent('perPageChange', this.pagination.documentsLeftToFetch());
+ },
+
+ afterRender: function () {
+ this.$('option[value="' + this.perPage() + '"]').attr('selected', "selected");
+ },
+
serialize: function () {
var totalRows = 0,
- recordStart = 0,
updateSeq = false,
pageStart = 0,
pageEnd = 20;
if (!this.newView) {
- totalRows = this.collection.totalRows();
+ totalRows = this.collection.length;
updateSeq = this.collection.updateSeq();
}
- recordStart = this.collection.recordStart();
if (this.pagination) {
pageStart = this.pagination.pageStart();
pageEnd = this.pagination.pageEnd();
@@ -443,13 +454,18 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
return {
database: app.utils.safeURLName(this.collection.database.id),
updateSeq: updateSeq,
- offset: recordStart,
totalRows: totalRows,
- showNumbers: this.showNumbers,
- numModels: this.collection.models.length + recordStart - 1,
pageStart: pageStart,
pageEnd: pageEnd
};
+ },
+
+ perPage: function () {
+ return this._perPage;
+ },
+
+ setCollection: function (collection) {
+ this.collection = collection;
}
});
@@ -468,7 +484,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
toggleQuery: function (event) {
$('#dashboard-content').scrollTop(0);
- this.$('#query').toggle('fast');
+ this.$('#query').toggle('slow');
},
beforeRender: function () {
@@ -477,17 +493,14 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
previewFn: this.previewView,
hasReduce: false,
showPreview: false,
- database: this.database
+ database: this.database,
}));
-
- this.$('#query').hide();
},
afterRender: function () {
if (this.params) {
this.advancedOptions.updateFromParams(this.params);
}
-
},
updateAllDocs: function (event, paramInfo) {
@@ -518,9 +531,12 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
}
var fragment = window.location.hash.replace(/\?.*$/, '');
- fragment = fragment + '?' + $.param(params);
- FauxtonAPI.navigate(fragment, {trigger: false});
+ if (!_.isEmpty(params)) {
+ fragment = fragment + '?' + $.param(params);
+ }
+
+ FauxtonAPI.navigate(fragment, {trigger: false});
FauxtonAPI.triggerRouteEvent('updateAllDocs', {allDocs: true});
},
@@ -537,7 +553,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
"click button.all": "selectAll",
"click button.bulk-delete": "bulkDelete",
"click #collapse": "collapse",
- "change .row-select":"toggleTrash"
+ "change .row-select":"toggleTrash",
+ "click #js-end-results": "scrollToQuery"
},
toggleTrash: function () {
@@ -548,26 +565,45 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
}
},
+ scrollToQuery: function () {
+ $('#dashboard-content').animate({ scrollTop: 0 }, 'slow');
+ },
+
initialize: function(options){
this.nestedView = options.nestedView || Views.Document;
this.rows = {};
this.viewList = !! options.viewList;
this.database = options.database;
+
if (options.ddocInfo) {
this.designDocs = options.ddocInfo.designDocs;
this.ddocID = options.ddocInfo.id;
}
this.newView = options.newView || false;
+ this.docParams = options.docParams;
+ this.params = options.params || {};
this.expandDocs = true;
+ this.perPageDefault = this.docParams.limit || 20;
},
establish: function() {
if (this.newView) { return null; }
- return this.collection.fetch({reset: true}).fail(function() {
- // TODO: handle error requests that slip through
- // This should just throw a notification, not break the page
- console.log("ERROR: ", arguments);
+ return this.collection.fetch({
+ reset: true,
+ success: function() { },
+ error: function(model, xhr, options){
+ // TODO: handle error requests that slip through
+ // This should just throw a notification, not break the page
+ FauxtonAPI.addNotification({
+ msg: "Bad Request",
+ type: "error"
+ });
+
+ //now redirect back to alldocs
+ FauxtonAPI.navigate(model.database.url("index") + "?limit=100");
+ console.log("ERROR: ", arguments);
+ }
});
},
@@ -576,16 +612,10 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
},
serialize: function() {
- var requestDuration = false;
-
- if (this.collection.requestDurationInString) {
- requestDuration = this.collection.requestDurationInString();
- }
-
return {
viewList: this.viewList,
- requestDuration: requestDuration,
- expandDocs: this.expandDocs
+ expandDocs: this.expandDocs,
+ endOfResults: !this.pagination.canShowNextfn()
};
},
@@ -646,70 +676,41 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
addPagination: function () {
var collection = this.collection;
- var perPage = function () {
- if (collection.params.limit && collection.skipFirstItem) {
- return parseInt(collection.params.limit, 10) - 1;
- } else if (collection.params.limit) {
- return parseInt(collection.params.limit, 10);
- }
-
- return 20;
- };
this.pagination = new Components.IndexPagination({
collection: this.collection,
scrollToSelector: '#dashboard-content',
- previousUrlfn: function () {
- return collection.urlPreviousPage(perPage(), this.previousParams.pop());
- },
- canShowPreviousfn: function () {
- if (this.previousParams.length === 0) {
- return false;
- }
-
- return true;
- },
- canShowNextfn: function () {
- if (collection.length < (perPage() -1)) {
- return false;
- }
-
- return true;
- },
-
- nextUrlfn: function () {
- return collection.urlNextPage(perPage());
- }
+ docLimit: this.params.limit,
+ perPage: this.perPageDefault
});
},
cleanup: function () {
- this.allDocsNumber.remove();
+ this.pagination && this.pagination.remove();
+ this.allDocsNumber && this.allDocsNumber.remove();
_.each(this.rows, function (row) {row.remove();});
-
- if (!this.pagination) { return; }
- this.pagination.remove();
},
beforeRender: function() {
- var showNumbers = true;
if (!this.pagination) {
this.addPagination();
}
- this.insertView('#documents-pagination', this.pagination);
+ if (!this.params.keys) { //cannot paginate with keys
+ this.insertView('#documents-pagination', this.pagination);
+ }
- if (this.designDocs || this.collection.idxType === '_view' || this.collection.params.startkey === '"_design"') {
- showNumbers = false;
+ if (!this.allDocsNumber) {
+ this.allDocsNumber = new Views.AllDocsNumber({
+ collection: this.collection,
+ newView: this.newView,
+ pagination: this.pagination,
+ perPageDefault: this.perPageDefault
+ });
}
- this.allDocsNumber = this.setView('#item-numbers', new Views.AllDocsNumber({
- collection: this.collection,
- newView: this.newView,
- showNumbers: showNumbers,
- pagination: this.pagination
- }));
+ this.setView('#item-numbers', this.allDocsNumber);
var docs = this.expandDocs ? this.collection : this.collection.simple();
@@ -720,8 +721,32 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
}, this);
},
+ setCollection: function (collection) {
+ this.collection = collection;
+ this.pagination.setCollection(collection);
+ this.allDocsNumber.setCollection(collection);
+ },
+
+ setParams: function (docParams, urlParams) {
+ this.docParams = docParams;
+ this.params = urlParams;
+ this.perPageDefault = this.docParams.limit;
+
+ if (this.params.limit) {
+ this.pagination.docLimit = this.params.limit;
+ }
+ },
+
afterRender: function(){
prettyPrint();
+ },
+
+ perPage: function () {
+ return this.allDocsNumber.perPage();
+ },
+
+ updatePerPage: function (newPerPage) {
+ this.collection.updateLimit(newPerPage);
}
});
@@ -942,9 +967,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
model = this.model;
editor.editor.on("change", function (event) {
- //if (event.data.action !== 'removeText') { return; }
- //if (!event.data.text.match(/_id/) && !event.data.text.match(/_rev/)) { return; }
-
var changedDoc;
try {
changedDoc = JSON.parse(editor.getValue());
@@ -1033,7 +1055,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
this.viewName = options.viewName;
this.updateViewFn = options.updateViewFn;
this.previewFn = options.previewFn;
- //this.hadReduce = options.hasReduce || true;
if (typeof(options.hasReduce) === 'undefined') {
this.hasReduce = true;
@@ -1049,9 +1070,9 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
},
events: {
- "change form.view-query-update input": "updateFilters",
- "change form.view-query-update select": "updateFilters",
- "submit form.view-query-update": "updateView",
+ "change form.js-view-query-update input": "updateFilters",
+ "change form.js-view-query-update select": "updateFilters",
+ "submit form.js-view-query-update": "updateView",
"click button.preview": "previewView"
},
@@ -1071,11 +1092,15 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
},
queryParams: function () {
- var $form = this.$(".view-query-update");
+ var $form = this.$(".js-view-query-update");
// Ignore params without a value
- var params = _.filter($form.serializeArray(), function(param) {
- return param.value;
- });
+ var params = _.reduce($form.serializeArray(), function(params, param) {
+ if (!param.value) { return params; }
+ if (param.name === "limit" && param.value === 'None') { return params; }
+
+ params.push(param);
+ return params;
+ }, []);
// Validate *key* params to ensure they're valid JSON
var keyParams = ["key","keys","startkey","endkey"];
@@ -1103,7 +1128,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
},
updateFiltersFor: function(name, $ele) {
- var $form = $ele.parents("form.view-query-update:first");
+ var $form = $ele.parents("form.js-view-query-update:first");
switch (name) {
// Reduce constraints
// - Can't include_docs for reduce=true
@@ -1131,12 +1156,13 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
},
updateFromParams: function (params) {
- var $form = this.$el.find("form.view-query-update");
+ var $form = this.$el.find("form.js-view-query-update");
_.each(params, function(val, key) {
var $ele;
switch (key) {
case "limit":
- case "group_level":
+ case "group_level":
+ if (!val) { return; }
$form.find("select[name='"+key+"']").val(val);
break;
case "include_docs":
@@ -1247,7 +1273,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
"click button.preview": "previewView",
"click #db-views-tabs-nav": 'toggleIndexNav',
"click .beautify_map": "beautifyCode",
- "click .beautify_reduce": "beautifyCode"
+ "click .beautify_reduce": "beautifyCode",
+ "click #query-options-wrapper": 'toggleIndexNav'
},
langTemplates: {
@@ -1368,6 +1395,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
that.mapEditor.editSaved();
that.reduceEditor && that.reduceEditor.editSaved();
+
FauxtonAPI.addNotification({
msg: "View has been saved.",
type: "success",
@@ -1448,9 +1476,11 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
}
var fragment = window.location.hash.replace(/\?.*$/, '');
- fragment = fragment + '?' + $.param(params);
- FauxtonAPI.navigate(fragment, {trigger: false});
+ if (!_.isEmpty(params)) {
+ fragment = fragment + '?' + $.param(params);
+ }
+ FauxtonAPI.navigate(fragment, {trigger: false});
FauxtonAPI.triggerRouteEvent('updateAllDocs', {ddoc: this.ddocID, view: this.viewName});
},
@@ -1622,18 +1652,25 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
database: this.database
}));
- this.advancedOptions = this.insertView('#query', new Views.AdvancedOptions({
- updateViewFn: this.updateView,
- previewFn: this.previewView,
- database: this.database,
- viewName: this.viewName,
- ddocName: this.model.id,
- hasReduce: this.hasReduce()
- }));
+
+ if (!this.newView) {
+ this.eventer = _.extend({}, Backbone.Events);
+
+ this.advancedOptions = this.insertView('#query', new Views.AdvancedOptions({
+ updateViewFn: this.updateView,
+ previewFn: this.previewView,
+ database: this.database,
+ viewName: this.viewName,
+ ddocName: this.model.id,
+ hasReduce: this.hasReduce(),
+ eventer: this.eventer
+ }));
+ }
+
},
afterRender: function() {
- if (this.params) {
+ if (this.params && !this.newView) {
this.advancedOptions.updateFromParams(this.params);
}
@@ -1646,7 +1683,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
this.$('#index-nav').parent().removeClass('active');
}
-
},
showEditors: function () {
@@ -1737,11 +1773,10 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColum
return {
changes_url: '#' + this.database.url('changes'),
permissions_url: '#' + this.database.url('app') + '/permissions',
- db_url: '#' + this.database.url('index') + '?limit=' + Databases.DocLimit,
+ db_url: '#' + this.database.url('index'),
database: this.collection.database,
database_url: '#' + this.database.url('app'),
docLinks: docLinks,
- docLimit: Databases.DocLimit,
addLinks: addLinks,
newLinks: newLinks,
extensionList: extensionList > 0
diff --git a/src/fauxton/app/addons/fauxton/base.js b/src/fauxton/app/addons/fauxton/base.js
index 35babb5f6..a6e462a28 100644
--- a/src/fauxton/app/addons/fauxton/base.js
+++ b/src/fauxton/app/addons/fauxton/base.js
@@ -45,7 +45,6 @@ function(app, FauxtonAPI, resizeColumns) {
}
});
-
Fauxton.initialize = function () {
app.footer = new Fauxton.Footer({el: "#footer-content"}),
app.navBar = new Fauxton.NavBar();
@@ -93,7 +92,7 @@ function(app, FauxtonAPI, resizeColumns) {
});
};
- Fauxton.Breadcrumbs = Backbone.View.extend({
+ Fauxton.Breadcrumbs = FauxtonAPI.View.extend({
template: "templates/fauxton/breadcrumbs",
serialize: function() {
@@ -114,10 +113,7 @@ function(app, FauxtonAPI, resizeColumns) {
}
});
- // TODO: this View should extend from FauxtonApi.View.
- // Chicken and egg problem, api.js extends fauxton/base.js.
- // Need to sort the loading order.
- Fauxton.Footer = Backbone.View.extend({
+ Fauxton.Footer = FauxtonAPI.View.extend({
template: "templates/fauxton/footer",
initialize: function() {
@@ -135,7 +131,7 @@ function(app, FauxtonAPI, resizeColumns) {
}
});
- Fauxton.NavBar = Backbone.View.extend({
+ Fauxton.NavBar = FauxtonAPI.View.extend({
className:"navbar",
template: "templates/fauxton/nav_bar",
// TODO: can we generate this list from the router?
@@ -260,7 +256,7 @@ function(app, FauxtonAPI, resizeColumns) {
// TODO: ADD ACTIVE CLASS
});
- Fauxton.ApiBar = Backbone.View.extend({
+ Fauxton.ApiBar = FauxtonAPI.View.extend({
template: "templates/fauxton/api_bar",
endpoint: '_all_docs',
@@ -304,7 +300,7 @@ function(app, FauxtonAPI, resizeColumns) {
});
- Fauxton.Notification = Backbone.View.extend({
+ Fauxton.Notification = FauxtonAPI.View.extend({
fadeTimer: 5000,
initialize: function(options) {
diff --git a/src/fauxton/app/addons/fauxton/components.js b/src/fauxton/app/addons/fauxton/components.js
index 0422b5a07..7dcf2d7d0 100644
--- a/src/fauxton/app/addons/fauxton/components.js
+++ b/src/fauxton/app/addons/fauxton/components.js
@@ -69,19 +69,68 @@ function(app, FauxtonAPI, ace, spin) {
initialize: function (options) {
this.previousUrlfn = options.previousUrlfn;
this.nextUrlfn = options.nextUrlfn;
- this.canShowPreviousfn = options.canShowPreviousfn;
- this.canShowNextfn = options.canShowNextfn;
this.scrollToSelector = options.scrollToSelector;
_.bindAll(this);
- this.previousParams = [];
+ this.docLimit = options.docLimit || 1000000;
+ this.perPage = options.perPage || 20;
+ this.setDefaults();
+ },
+
+ setDefaults: function () {
+ this._pageNumber = [];
+ this._pageStart = 1;
+ this.enabled = true;
+ this.currentPage = 1;
+ },
+
+ canShowPreviousfn: function () {
+ if (this._pageStart === 1 || !this.enabled) {
+ return false;
+ }
+ return true;
+ },
+
+ canShowNextfn: function () {
+ if (!this.enabled) { return this.enabled; }
+
+ if (this.collection.length < (this.perPage -1)) {
+ return false;
+ }
+
+ if ((this.pageStart() + this.perPage) >= this.docLimit) {
+ return false;
+ }
+
+ if (this.collection.viewMeta && this.collection.viewMeta.total_rows <= this.pageStart() + this.perPage) {
+ return false;
+ }
+
+ return true;
},
previousClicked: function (event) {
event.preventDefault();
event.stopPropagation();
if (!this.canShowPreviousfn()) { return; }
- FauxtonAPI.navigate(this.previousUrlfn(), {trigger: false});
- FauxtonAPI.triggerRouteEvent('paginate', 'previous');
+
+ this.decPageNumber();
+
+ FauxtonAPI.triggerRouteEvent('paginate', {
+ direction: 'previous',
+ perPage: this.perPage,
+ currentPage: this.currentPage
+ });
+ },
+
+ documentsLeftToFetch: function () {
+ var documentsLeftToFetch = this.docLimit - this.totalDocsViewed(),
+ limit = this.perPage;
+
+ if (documentsLeftToFetch < this.perPage ) {
+ limit = documentsLeftToFetch;
+ }
+
+ return limit;
},
nextClicked: function (event) {
@@ -89,14 +138,14 @@ function(app, FauxtonAPI, ace, spin) {
event.stopPropagation();
if (!this.canShowNextfn()) { return; }
- var params = _.clone(this.collection.params);
+ this.incPageNumber();
- if (params) {
- this.previousParams.push(params);
- }
+ FauxtonAPI.triggerRouteEvent('paginate', {
+ direction: 'next',
+ perPage: this.documentsLeftToFetch(),
+ currentPage: this.currentPage
+ });
- FauxtonAPI.navigate(this.nextUrlfn(), {trigger: false});
- FauxtonAPI.triggerRouteEvent('paginate', 'next');
},
serialize: function () {
@@ -106,30 +155,58 @@ function(app, FauxtonAPI, ace, spin) {
};
},
- pageLimit: function () {
- var limit = 20;
+ updatePerPage: function (newPerPage) {
+ this.setDefaults();
+ this.perPage = newPerPage;
+ },
+
+ page: function () {
+ return this._pageStart - 1;
+ },
+
+ incPageNumber: function () {
+ this.currentPage = this.currentPage + 1;
+ this._pageNumber.push({perPage: this.perPage});
+ this._pageStart = this._pageStart + this.perPage;
+ },
+
+ totalDocsViewed: function () {
+ return _.reduce(this._pageNumber, function (total, value) {
+ return total + value.perPage;
+ }, 0);
+ },
- if (this.collection.params.limit && this.collection.skipFirstItem) {
- limit = parseInt(this.collection.params.limit, 10) - 1;
- } else if (this.collection.params.limit) {
- limit = parseInt(this.collection.params.limit, 10);
+ decPageNumber: function () {
+ this.currentPage = this.currentPage - 1;
+ this._pageNumber.pop();
+ var val = this._pageStart - this.perPage;
+ if (val < 1) {
+ val = 1;
}
- return limit;
+ this._pageStart = val;
},
pageStart: function () {
- return (this.previousParams.length * this.pageLimit()) + 1;
-
+ return this._pageStart;
},
pageEnd: function () {
- if (this.collection.length < this.pageLimit()) {
- return (this.previousParams.length * this.pageLimit()) + this.collection.length;
- }
+ return this.page() + this.collection.length;
+ },
- return (this.previousParams.length * this.pageLimit()) + this.pageLimit();
- }
+ disable: function () {
+ this.enabled = false;
+ },
+
+ enable: function () {
+ this.enabled = true;
+ },
+
+ setCollection: function (collection) {
+ this.collection = collection;
+ this.setDefaults();
+ },
});
diff --git a/src/fauxton/app/addons/fauxton/tests/paginateSpec.js b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
index 535e26f4a..8fc409a5d 100644
--- a/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
+++ b/src/fauxton/app/addons/fauxton/tests/paginateSpec.js
@@ -58,15 +58,6 @@ define([
//FauxtonAPI.navigate.restore && FauxtonAPI.navigate.restore();
});
- it('Should navigate', function () {
- var navigateMock = sinon.spy(FauxtonAPI, 'navigate');
-
- paginate.$('a#next').click();
-
- assert.ok(navigateMock.calledOnce);
- FauxtonAPI.navigate.restore();
- });
-
it('Should trigger routeEvent', function () {
var navigateMock = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
@@ -81,15 +72,6 @@ define([
describe('#previous', function () {
- it('Should navigate', function () {
- var navigateMock = sinon.spy(FauxtonAPI, 'navigate');
-
- paginate.$('a#previous').click();
-
- assert.ok(navigateMock.calledOnce);
- FauxtonAPI.navigate.restore();
- });
-
it('Should trigger routeEvent', function () {
var navigateMock = sinon.spy(FauxtonAPI, 'triggerRouteEvent');
diff --git a/src/fauxton/test/mocha/chai.js b/src/fauxton/test/mocha/chai.js
index 2a67f982b..9dd7b0a78 100644
--- a/src/fauxton/test/mocha/chai.js
+++ b/src/fauxton/test/mocha/chai.js
@@ -27,10 +27,14 @@ function require(path, parent, orig) {
// perform real require()
// by invoking the module's
// registered function
- if (!module.exports) {
- module.exports = {};
- module.client = module.component = true;
- module.call(this, module.exports, require.relative(resolved), module);
+ if (!module._resolving && !module.exports) {
+ var mod = {};
+ mod.exports = {};
+ mod.client = mod.component = true;
+ module._resolving = true;
+ module.call(this, mod.exports, require.relative(resolved), mod);
+ delete module._resolving;
+ module.exports = mod.exports;
}
return module.exports;
@@ -309,6 +313,411 @@ AssertionError.prototype.toJSON = function (stack) {
};
});
+require.register("chaijs-type-detect/lib/type.js", function(exports, require, module){
+/*!
+ * type-detect
+ * Copyright(c) 2013 jake luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Primary Exports
+ */
+
+var exports = module.exports = getType;
+
+/*!
+ * Detectable javascript natives
+ */
+
+var natives = {
+ '[object Array]': 'array'
+ , '[object RegExp]': 'regexp'
+ , '[object Function]': 'function'
+ , '[object Arguments]': 'arguments'
+ , '[object Date]': 'date'
+};
+
+/**
+ * ### typeOf (obj)
+ *
+ * Use several different techniques to determine
+ * the type of object being tested.
+ *
+ *
+ * @param {Mixed} object
+ * @return {String} object type
+ * @api public
+ */
+
+function getType (obj) {
+ var str = Object.prototype.toString.call(obj);
+ if (natives[str]) return natives[str];
+ if (obj === null) return 'null';
+ if (obj === undefined) return 'undefined';
+ if (obj === Object(obj)) return 'object';
+ return typeof obj;
+}
+
+exports.Library = Library;
+
+/**
+ * ### Library
+ *
+ * Create a repository for custom type detection.
+ *
+ * ```js
+ * var lib = new type.Library;
+ * ```
+ *
+ */
+
+function Library () {
+ this.tests = {};
+}
+
+/**
+ * #### .of (obj)
+ *
+ * Expose replacement `typeof` detection to the library.
+ *
+ * ```js
+ * if ('string' === lib.of('hello world')) {
+ * // ...
+ * }
+ * ```
+ *
+ * @param {Mixed} object to test
+ * @return {String} type
+ */
+
+Library.prototype.of = getType;
+
+/**
+ * #### .define (type, test)
+ *
+ * Add a test to for the `.test()` assertion.
+ *
+ * Can be defined as a regular expression:
+ *
+ * ```js
+ * lib.define('int', /^[0-9]+$/);
+ * ```
+ *
+ * ... or as a function:
+ *
+ * ```js
+ * lib.define('bln', function (obj) {
+ * if ('boolean' === lib.of(obj)) return true;
+ * var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ];
+ * if ('string' === lib.of(obj)) obj = obj.toLowerCase();
+ * return !! ~blns.indexOf(obj);
+ * });
+ * ```
+ *
+ * @param {String} type
+ * @param {RegExp|Function} test
+ * @api public
+ */
+
+Library.prototype.define = function (type, test) {
+ if (arguments.length === 1) return this.tests[type];
+ this.tests[type] = test;
+ return this;
+};
+
+/**
+ * #### .test (obj, test)
+ *
+ * Assert that an object is of type. Will first
+ * check natives, and if that does not pass it will
+ * use the user defined custom tests.
+ *
+ * ```js
+ * assert(lib.test('1', 'int'));
+ * assert(lib.test('yes', 'bln'));
+ * ```
+ *
+ * @param {Mixed} object
+ * @param {String} type
+ * @return {Boolean} result
+ * @api public
+ */
+
+Library.prototype.test = function (obj, type) {
+ if (type === getType(obj)) return true;
+ var test = this.tests[type];
+
+ if (test && 'regexp' === getType(test)) {
+ return test.test(obj);
+ } else if (test && 'function' === getType(test)) {
+ return test(obj);
+ } else {
+ throw new ReferenceError('Type test "' + type + '" not defined or invalid.');
+ }
+};
+
+});
+require.register("chaijs-deep-eql/lib/eql.js", function(exports, require, module){
+/*!
+ * deep-eql
+ * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/*!
+ * Module dependencies
+ */
+
+var type = require('type-detect');
+
+/*!
+ * Buffer.isBuffer browser shim
+ */
+
+var Buffer;
+try { Buffer = require('buffer').Buffer; }
+catch(ex) {
+ Buffer = {};
+ Buffer.isBuffer = function() { return false; }
+}
+
+/*!
+ * Primary Export
+ */
+
+module.exports = deepEqual;
+
+/**
+ * Assert super-strict (egal) equality between
+ * two objects of any type.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @param {Array} memoised (optional)
+ * @return {Boolean} equal match
+ */
+
+function deepEqual(a, b, m) {
+ if (sameValue(a, b)) {
+ return true;
+ } else if ('date' === type(a)) {
+ return dateEqual(a, b);
+ } else if ('regexp' === type(a)) {
+ return regexpEqual(a, b);
+ } else if (Buffer.isBuffer(a)) {
+ return bufferEqual(a, b);
+ } else if ('arguments' === type(a)) {
+ return argumentsEqual(a, b, m);
+ } else if (!typeEqual(a, b)) {
+ return false;
+ } else if (('object' !== type(a) && 'object' !== type(b))
+ && ('array' !== type(a) && 'array' !== type(b))) {
+ return sameValue(a, b);
+ } else {
+ return objectEqual(a, b, m);
+ }
+}
+
+/*!
+ * Strict (egal) equality test. Ensures that NaN always
+ * equals NaN and `-0` does not equal `+0`.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} equal match
+ */
+
+function sameValue(a, b) {
+ if (a === b) return a !== 0 || 1 / a === 1 / b;
+ return a !== a && b !== b;
+}
+
+/*!
+ * Compare the types of two given objects and
+ * return if they are equal. Note that an Array
+ * has a type of `array` (not `object`) and arguments
+ * have a type of `arguments` (not `array`/`object`).
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function typeEqual(a, b) {
+ return type(a) === type(b);
+}
+
+/*!
+ * Compare two Date objects by asserting that
+ * the time values are equal using `saveValue`.
+ *
+ * @param {Date} a
+ * @param {Date} b
+ * @return {Boolean} result
+ */
+
+function dateEqual(a, b) {
+ if ('date' !== type(b)) return false;
+ return sameValue(a.getTime(), b.getTime());
+}
+
+/*!
+ * Compare two regular expressions by converting them
+ * to string and checking for `sameValue`.
+ *
+ * @param {RegExp} a
+ * @param {RegExp} b
+ * @return {Boolean} result
+ */
+
+function regexpEqual(a, b) {
+ if ('regexp' !== type(b)) return false;
+ return sameValue(a.toString(), b.toString());
+}
+
+/*!
+ * Assert deep equality of two `arguments` objects.
+ * Unfortunately, these must be sliced to arrays
+ * prior to test to ensure no bad behavior.
+ *
+ * @param {Arguments} a
+ * @param {Arguments} b
+ * @param {Array} memoize (optional)
+ * @return {Boolean} result
+ */
+
+function argumentsEqual(a, b, m) {
+ if ('arguments' !== type(b)) return false;
+ a = [].slice.call(a);
+ b = [].slice.call(b);
+ return deepEqual(a, b, m);
+}
+
+/*!
+ * Get enumerable properties of a given object.
+ *
+ * @param {Object} a
+ * @return {Array} property names
+ */
+
+function enumerable(a) {
+ var res = [];
+ for (var key in a) res.push(key);
+ return res;
+}
+
+/*!
+ * Simple equality for flat iterable objects
+ * such as Arrays or Node.js buffers.
+ *
+ * @param {Iterable} a
+ * @param {Iterable} b
+ * @return {Boolean} result
+ */
+
+function iterableEqual(a, b) {
+ if (a.length !== b.length) return false;
+
+ var i = 0;
+ var match = true;
+
+ for (; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ match = false;
+ break;
+ }
+ }
+
+ return match;
+}
+
+/*!
+ * Extension to `iterableEqual` specifically
+ * for Node.js Buffers.
+ *
+ * @param {Buffer} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function bufferEqual(a, b) {
+ if (!Buffer.isBuffer(b)) return false;
+ return iterableEqual(a, b);
+}
+
+/*!
+ * Block for `objectEqual` ensuring non-existing
+ * values don't get in.
+ *
+ * @param {Mixed} object
+ * @return {Boolean} result
+ */
+
+function isValue(a) {
+ return a !== null && a !== undefined;
+}
+
+/*!
+ * Recursively check the equality of two objects.
+ * Once basic sameness has been established it will
+ * defer to `deepEqual` for each enumerable key
+ * in the object.
+ *
+ * @param {Mixed} a
+ * @param {Mixed} b
+ * @return {Boolean} result
+ */
+
+function objectEqual(a, b, m) {
+ if (!isValue(a) || !isValue(b)) {
+ return false;
+ }
+
+ if (a.prototype !== b.prototype) {
+ return false;
+ }
+
+ var i;
+ if (m) {
+ for (i = 0; i < m.length; i++) {
+ if ((m[i][0] === a && m[i][1] === b)
+ || (m[i][0] === b && m[i][1] === a)) {
+ return true;
+ }
+ }
+ } else {
+ m = [];
+ }
+
+ try {
+ var ka = enumerable(a);
+ var kb = enumerable(b);
+ } catch (ex) {
+ return false;
+ }
+
+ ka.sort();
+ kb.sort();
+
+ if (!iterableEqual(ka, kb)) {
+ return false;
+ }
+
+ m.push([ a, b ]);
+
+ var key;
+ for (i = ka.length - 1; i >= 0; i--) {
+ key = ka[i];
+ if (!deepEqual(a[key], b[key], m)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+});
require.register("chai/index.js", function(exports, require, module){
module.exports = require('./lib/chai');
@@ -316,7 +725,7 @@ module.exports = require('./lib/chai');
require.register("chai/lib/chai.js", function(exports, require, module){
/*!
* chai
- * Copyright(c) 2011-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -327,7 +736,7 @@ var used = []
* Chai version
*/
-exports.version = '1.7.2';
+exports.version = '1.8.1';
/*!
* Assertion Error
@@ -400,7 +809,7 @@ require.register("chai/lib/chai/assertion.js", function(exports, require, module
/*!
* chai
* http://chaijs.com
- * Copyright(c) 2011-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -480,6 +889,10 @@ module.exports = function (_chai, util) {
util.overwriteMethod(this.prototype, name, fn);
};
+ Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) {
+ util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior);
+ };
+
/*!
* ### .assert(expression, message, negateMessage, expected, actual)
*
@@ -533,7 +946,7 @@ require.register("chai/lib/chai/core/assertions.js", function(exports, require,
/*!
* chai
* http://chaijs.com
- * Copyright(c) 2011-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -545,7 +958,7 @@ module.exports = function (chai, _) {
/**
* ### Language Chains
*
- * The following are provide as chainable getters to
+ * The following are provided as chainable getters to
* improve the readability of your assertions. They
* do not provide an testing capability unless they
* have been overwritten by a plugin.
@@ -558,6 +971,7 @@ module.exports = function (chai, _) {
* - is
* - that
* - and
+ * - has
* - have
* - with
* - at
@@ -569,7 +983,7 @@ module.exports = function (chai, _) {
*/
[ 'to', 'be', 'been'
- , 'is', 'and', 'have'
+ , 'is', 'and', 'has', 'have'
, 'with', 'that', 'at'
, 'of', 'same' ].forEach(function (chain) {
Assertion.addProperty(chain, function () {
@@ -677,9 +1091,21 @@ module.exports = function (chai, _) {
function include (val, msg) {
if (msg) flag(this, 'message', msg);
- var obj = flag(this, 'object')
+ var obj = flag(this, 'object');
+
+ if (_.type(val) === 'object') {
+ if (!flag(this, 'negate')) {
+ for (var k in val) new Assertion(obj).property(k, val[k]);
+ return;
+ }
+ var subset = {}
+ for (var k in val) subset[k] = obj[k]
+ var expected = _.eql(subset, val);
+ } else {
+ var expected = obj && ~obj.indexOf(val)
+ }
this.assert(
- ~obj.indexOf(val)
+ expected
, 'expected #{this} to include ' + _.inspect(val)
, 'expected #{this} to not include ' + _.inspect(val));
}
@@ -776,8 +1202,8 @@ module.exports = function (chai, _) {
*
* Asserts that the target is `undefined`.
*
- * expect(undefined).to.be.undefined;
- * expect(null).to.not.be.undefined;
+ * expect(undefined).to.be.undefined;
+ * expect(null).to.not.be.undefined;
*
* @name undefined
* @api public
@@ -1534,6 +1960,7 @@ module.exports = function (chai, _) {
* @param {String|RegExp} expected error message
* @param {String} message _optional_
* @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
+ * @returns error for chaining (null if no error)
* @api public
*/
@@ -1558,7 +1985,10 @@ module.exports = function (chai, _) {
constructor = null;
errMsg = null;
} else if (typeof constructor === 'function') {
- name = (new constructor()).name;
+ name = constructor.prototype.name || constructor.name;
+ if (name === 'Error' && constructor !== Error) {
+ name = (new constructor()).name;
+ }
} else {
constructor = null;
}
@@ -1572,12 +2002,14 @@ module.exports = function (chai, _) {
err === desiredError
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}'
- , desiredError
- , err
+ , (desiredError instanceof Error ? desiredError.toString() : desiredError)
+ , (err instanceof Error ? err.toString() : err)
);
+ flag(this, 'object', err);
return this;
}
+
// next, check constructor
if (constructor) {
this.assert(
@@ -1585,11 +2017,15 @@ module.exports = function (chai, _) {
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp} but #{act} was thrown'
, name
- , err
+ , (err instanceof Error ? err.toString() : err)
);
- if (!errMsg) return this;
+ if (!errMsg) {
+ flag(this, 'object', err);
+ return this;
+ }
}
+
// next, check message
var message = 'object' === _.type(err) && "message" in err
? err.message
@@ -1604,6 +2040,7 @@ module.exports = function (chai, _) {
, message
);
+ flag(this, 'object', err);
return this;
} else if ((message != null) && errMsg && 'string' === typeof errMsg) {
this.assert(
@@ -1614,6 +2051,7 @@ module.exports = function (chai, _) {
, message
);
+ flag(this, 'object', err);
return this;
} else {
thrown = true;
@@ -1636,9 +2074,11 @@ module.exports = function (chai, _) {
thrown === true
, 'expected #{this} to throw ' + expectedThrown + actuallyGot
, 'expected #{this} to not throw ' + expectedThrown + actuallyGot
- , desiredError
- , thrownError
+ , (desiredError instanceof Error ? desiredError.toString() : desiredError)
+ , (thrownError instanceof Error ? thrownError.toString() : thrownError)
);
+
+ flag(this, 'object', thrownError);
};
Assertion.addMethod('throw', assertThrows);
@@ -1657,8 +2097,8 @@ module.exports = function (chai, _) {
* To check if a constructor will respond to a static function,
* set the `itself` flag.
*
- * Klass.baz = function(){};
- * expect(Klass).itself.to.respondTo('baz');
+ * Klass.baz = function(){};
+ * expect(Klass).itself.to.respondTo('baz');
*
* @name respondTo
* @param {String} method
@@ -1686,12 +2126,12 @@ module.exports = function (chai, _) {
*
* Sets the `itself` flag, later used by the `respondTo` assertion.
*
- * function Foo() {}
- * Foo.bar = function() {}
- * Foo.prototype.baz = function() {}
+ * function Foo() {}
+ * Foo.bar = function() {}
+ * Foo.prototype.baz = function() {}
*
- * expect(Foo).itself.to.respondTo('bar');
- * expect(Foo).itself.not.to.respondTo('baz');
+ * expect(Foo).itself.to.respondTo('bar');
+ * expect(Foo).itself.not.to.respondTo('baz');
*
* @name itself
* @api public
@@ -1805,7 +2245,7 @@ module.exports = function (chai, _) {
require.register("chai/lib/chai/interface/assert.js", function(exports, require, module){
/*!
* chai
- * Copyright(c) 2011-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -1860,13 +2300,12 @@ module.exports = function (chai, util) {
*/
assert.fail = function (actual, expected, message, operator) {
- throw new chai.AssertionError({
+ message = message || 'assert.fail()';
+ throw new chai.AssertionError(message, {
actual: actual
, expected: expected
- , message: message
, operator: operator
- , stackStartFunction: assert.fail
- });
+ }, assert.fail);
};
/**
@@ -2462,19 +2901,7 @@ module.exports = function (chai, util) {
*/
assert.include = function (exp, inc, msg) {
- var obj = new Assertion(exp, msg);
-
- if (Array.isArray(exp)) {
- obj.to.include(inc);
- } else if ('string' === typeof exp) {
- obj.to.contain.string(inc);
- } else {
- throw new chai.AssertionError(
- 'expected an array or string'
- , null
- , assert.include
- );
- }
+ new Assertion(exp, msg).include(inc);
};
/**
@@ -2494,19 +2921,7 @@ module.exports = function (chai, util) {
*/
assert.notInclude = function (exp, inc, msg) {
- var obj = new Assertion(exp, msg);
-
- if (Array.isArray(exp)) {
- obj.to.not.include(inc);
- } else if ('string' === typeof exp) {
- obj.to.not.contain.string(inc);
- } else {
- throw new chai.AssertionError(
- 'expected an array or string'
- , null
- , assert.notInclude
- );
- }
+ new Assertion(exp, msg).not.include(inc);
};
/**
@@ -2750,7 +3165,8 @@ module.exports = function (chai, util) {
errt = null;
}
- new Assertion(fn, msg).to.Throw(errt, errs);
+ var assertErr = new Assertion(fn, msg).to.Throw(errt, errs);
+ return flag(assertErr, 'object');
};
/**
@@ -2888,7 +3304,7 @@ module.exports = function (chai, util) {
require.register("chai/lib/chai/interface/expect.js", function(exports, require, module){
/*!
* chai
- * Copyright(c) 2011-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -2903,7 +3319,7 @@ module.exports = function (chai, util) {
require.register("chai/lib/chai/interface/should.js", function(exports, require, module){
/*!
* chai
- * Copyright(c) 2011-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -2982,7 +3398,7 @@ module.exports = function (chai, util) {
require.register("chai/lib/chai/utils/addChainableMethod.js", function(exports, require, module){
/*!
* Chai - addChainingMethod utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3037,15 +3453,27 @@ var call = Function.prototype.call,
*/
module.exports = function (ctx, name, method, chainingBehavior) {
- if (typeof chainingBehavior !== 'function')
+ if (typeof chainingBehavior !== 'function') {
chainingBehavior = function () { };
+ }
+
+ var chainableBehavior = {
+ method: method
+ , chainingBehavior: chainingBehavior
+ };
+
+ // save the methods so we can overwrite them later, if we need to.
+ if (!ctx.__methods) {
+ ctx.__methods = {};
+ }
+ ctx.__methods[name] = chainableBehavior;
Object.defineProperty(ctx, name,
{ get: function () {
- chainingBehavior.call(this);
+ chainableBehavior.chainingBehavior.call(this);
var assert = function () {
- var result = method.apply(this, arguments);
+ var result = chainableBehavior.method.apply(this, arguments);
return result === undefined ? this : result;
};
@@ -3079,7 +3507,7 @@ module.exports = function (ctx, name, method, chainingBehavior) {
require.register("chai/lib/chai/utils/addMethod.js", function(exports, require, module){
/*!
* Chai - addMethod utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3119,7 +3547,7 @@ module.exports = function (ctx, name, method) {
require.register("chai/lib/chai/utils/addProperty.js", function(exports, require, module){
/*!
* Chai - addProperty utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3159,142 +3587,10 @@ module.exports = function (ctx, name, getter) {
};
});
-require.register("chai/lib/chai/utils/eql.js", function(exports, require, module){
-// This is (almost) directly from Node.js assert
-// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/assert.js
-
-module.exports = _deepEqual;
-
-var getEnumerableProperties = require('./getEnumerableProperties');
-
-// for the browser
-var Buffer;
-try {
- Buffer = require('buffer').Buffer;
-} catch (ex) {
- Buffer = {
- isBuffer: function () { return false; }
- };
-}
-
-function _deepEqual(actual, expected, memos) {
-
- // 7.1. All identical values are equivalent, as determined by ===.
- if (actual === expected) {
- return true;
-
- } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
- if (actual.length != expected.length) return false;
-
- for (var i = 0; i < actual.length; i++) {
- if (actual[i] !== expected[i]) return false;
- }
-
- return true;
-
- // 7.2. If the expected value is a Date object, the actual value is
- // equivalent if it is also a Date object that refers to the same time.
- } else if (expected instanceof Date) {
- if (!(actual instanceof Date)) return false;
- return actual.getTime() === expected.getTime();
-
- // 7.3. Other pairs that do not both pass typeof value == 'object',
- // equivalence is determined by ==.
- } else if (typeof actual != 'object' && typeof expected != 'object') {
- return actual === expected;
-
- } else if (expected instanceof RegExp) {
- if (!(actual instanceof RegExp)) return false;
- return actual.toString() === expected.toString();
-
- // 7.4. For all other Object pairs, including Array objects, equivalence is
- // determined by having the same number of owned properties (as verified
- // with Object.prototype.hasOwnProperty.call), the same set of keys
- // (although not necessarily the same order), equivalent values for every
- // corresponding key, and an identical 'prototype' property. Note: this
- // accounts for both named and indexed properties on Arrays.
- } else {
- return objEquiv(actual, expected, memos);
- }
-}
-
-function isUndefinedOrNull(value) {
- return value === null || value === undefined;
-}
-
-function isArguments(object) {
- return Object.prototype.toString.call(object) == '[object Arguments]';
-}
-
-function objEquiv(a, b, memos) {
- if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
- return false;
-
- // an identical 'prototype' property.
- if (a.prototype !== b.prototype) return false;
-
- // check if we have already compared a and b
- var i;
- if (memos) {
- for(i = 0; i < memos.length; i++) {
- if ((memos[i][0] === a && memos[i][1] === b) ||
- (memos[i][0] === b && memos[i][1] === a))
- return true;
- }
- } else {
- memos = [];
- }
-
- //~~~I've managed to break Object.keys through screwy arguments passing.
- // Converting to array solves the problem.
- if (isArguments(a)) {
- if (!isArguments(b)) {
- return false;
- }
- a = pSlice.call(a);
- b = pSlice.call(b);
- return _deepEqual(a, b, memos);
- }
- try {
- var ka = getEnumerableProperties(a),
- kb = getEnumerableProperties(b),
- key;
- } catch (e) {//happens when one is a string literal and the other isn't
- return false;
- }
-
- // having the same number of owned properties (keys incorporates
- // hasOwnProperty)
- if (ka.length != kb.length)
- return false;
-
- //the same set of keys (although not necessarily the same order),
- ka.sort();
- kb.sort();
- //~~~cheap key test
- for (i = ka.length - 1; i >= 0; i--) {
- if (ka[i] != kb[i])
- return false;
- }
-
- // remember objects we have compared to guard against circular references
- memos.push([ a, b ]);
-
- //equivalent values for every corresponding key, and
- //~~~possibly expensive deep test
- for (i = ka.length - 1; i >= 0; i--) {
- key = ka[i];
- if (!_deepEqual(a[key], b[key], memos)) return false;
- }
-
- return true;
-}
-
-});
require.register("chai/lib/chai/utils/flag.js", function(exports, require, module){
/*!
* Chai - flag utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3329,7 +3625,7 @@ module.exports = function (obj, key, value) {
require.register("chai/lib/chai/utils/getActual.js", function(exports, require, module){
/*!
* Chai - getActual utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3351,7 +3647,7 @@ module.exports = function (obj, args) {
require.register("chai/lib/chai/utils/getEnumerableProperties.js", function(exports, require, module){
/*!
* Chai - getEnumerableProperties utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3379,7 +3675,7 @@ module.exports = function getEnumerableProperties(object) {
require.register("chai/lib/chai/utils/getMessage.js", function(exports, require, module){
/*!
* Chai - message composition utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3431,7 +3727,7 @@ module.exports = function (obj, args) {
require.register("chai/lib/chai/utils/getName.js", function(exports, require, module){
/*!
* Chai - getName utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3454,7 +3750,7 @@ module.exports = function (func) {
require.register("chai/lib/chai/utils/getPathValue.js", function(exports, require, module){
/*!
* Chai - getPathValue utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* @see https://github.com/logicalparadox/filtr
* MIT Licensed
*/
@@ -3559,7 +3855,7 @@ function _getPathValue (parsed, obj) {
require.register("chai/lib/chai/utils/getProperties.js", function(exports, require, module){
/*!
* Chai - getProperties utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -3659,7 +3955,7 @@ exports.transferFlags = require('./transferFlags');
* Deep equal utility
*/
-exports.eql = require('./eql');
+exports.eql = require('deep-eql');
/*!
* Deep path value
@@ -3703,6 +3999,12 @@ exports.overwriteMethod = require('./overwriteMethod');
exports.addChainableMethod = require('./addChainableMethod');
+/*!
+ * Overwrite chainable method
+ */
+
+exports.overwriteChainableMethod = require('./overwriteChainableMethod');
+
});
require.register("chai/lib/chai/utils/inspect.js", function(exports, require, module){
@@ -4031,7 +4333,7 @@ function objectToString(o) {
require.register("chai/lib/chai/utils/objDisplay.js", function(exports, require, module){
/*!
* Chai - flag utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -4082,7 +4384,7 @@ module.exports = function (obj) {
require.register("chai/lib/chai/utils/overwriteMethod.js", function(exports, require, module){
/*!
* Chai - overwriteMethod utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -4136,7 +4438,7 @@ module.exports = function (ctx, name, method) {
require.register("chai/lib/chai/utils/overwriteProperty.js", function(exports, require, module){
/*!
* Chai - overwriteProperty utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -4190,10 +4492,66 @@ module.exports = function (ctx, name, getter) {
};
});
+require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function(exports, require, module){
+/*!
+ * Chai - overwriteChainableMethod utility
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
+ * MIT Licensed
+ */
+
+/**
+ * ### overwriteChainableMethod (ctx, name, fn)
+ *
+ * Overwites an already existing chainable method
+ * and provides access to the previous function or
+ * property. Must return functions to be used for
+ * name.
+ *
+ * utils.overwriteChainableMethod(chai.Assertion.prototype, 'length',
+ * function (_super) {
+ * }
+ * , function (_super) {
+ * }
+ * );
+ *
+ * Can also be accessed directly from `chai.Assertion`.
+ *
+ * chai.Assertion.overwriteChainableMethod('foo', fn, fn);
+ *
+ * Then can be used as any other assertion.
+ *
+ * expect(myFoo).to.have.length(3);
+ * expect(myFoo).to.have.length.above(3);
+ *
+ * @param {Object} ctx object whose method / property is to be overwritten
+ * @param {String} name of method / property to overwrite
+ * @param {Function} method function that returns a function to be used for name
+ * @param {Function} chainingBehavior function that returns a function to be used for property
+ * @name overwriteChainableMethod
+ * @api public
+ */
+
+module.exports = function (ctx, name, method, chainingBehavior) {
+ var chainableBehavior = ctx.__methods[name];
+
+ var _chainingBehavior = chainableBehavior.chainingBehavior;
+ chainableBehavior.chainingBehavior = function () {
+ var result = chainingBehavior(_chainingBehavior).call(this);
+ return result === undefined ? this : result;
+ };
+
+ var _method = chainableBehavior.method;
+ chainableBehavior.method = function () {
+ var result = method(_method).apply(this, arguments);
+ return result === undefined ? this : result;
+ };
+};
+
+});
require.register("chai/lib/chai/utils/test.js", function(exports, require, module){
/*!
* Chai - test utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -4222,7 +4580,7 @@ module.exports = function (obj, args) {
require.register("chai/lib/chai/utils/transferFlags.js", function(exports, require, module){
/*!
* Chai - transferFlags utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -4269,7 +4627,7 @@ module.exports = function (assertion, object, includeAll) {
require.register("chai/lib/chai/utils/type.js", function(exports, require, module){
/*!
* Chai - type utility
- * Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
+ * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
@@ -4314,17 +4672,25 @@ module.exports = function (obj) {
};
});
+
+
+
+
require.alias("chaijs-assertion-error/index.js", "chai/deps/assertion-error/index.js");
require.alias("chaijs-assertion-error/index.js", "chai/deps/assertion-error/index.js");
require.alias("chaijs-assertion-error/index.js", "assertion-error/index.js");
require.alias("chaijs-assertion-error/index.js", "chaijs-assertion-error/index.js");
-
-require.alias("chai/index.js", "chai/index.js");
-
-if (typeof exports == "object") {
+require.alias("chaijs-deep-eql/lib/eql.js", "chai/deps/deep-eql/lib/eql.js");
+require.alias("chaijs-deep-eql/lib/eql.js", "chai/deps/deep-eql/index.js");
+require.alias("chaijs-deep-eql/lib/eql.js", "deep-eql/index.js");
+require.alias("chaijs-type-detect/lib/type.js", "chaijs-deep-eql/deps/type-detect/lib/type.js");
+require.alias("chaijs-type-detect/lib/type.js", "chaijs-deep-eql/deps/type-detect/index.js");
+require.alias("chaijs-type-detect/lib/type.js", "chaijs-type-detect/index.js");
+require.alias("chaijs-deep-eql/lib/eql.js", "chaijs-deep-eql/index.js");
+require.alias("chai/index.js", "chai/index.js");if (typeof exports == "object") {
module.exports = require("chai");
} else if (typeof define == "function" && define.amd) {
- define(function(){ return require("chai"); });
+ define([], function(){ return require("chai"); });
} else {
this["chai"] = require("chai");
}})();
diff --git a/src/fauxton/test/test.config.underscore b/src/fauxton/test/test.config.underscore
index 5cebe7837..95494a4ed 100644
--- a/src/fauxton/test/test.config.underscore
+++ b/src/fauxton/test/test.config.underscore
@@ -7,7 +7,11 @@ require.config(
require([
"app",
<% _.each(testFiles, function (test) {%>
+ <% if (test[0] === '.') { %>
'../<%= test %>',
+ <% } else { %>
+ '<%= test %>',
+ <% } %>
<% }) %>
], function() {
if (window.mochaPhantomJS) { mochaPhantomJS.run(); }