From b88b2932388cd9b5ad3bb384f353d941a8806b53 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Fri, 2 May 2014 21:33:46 +0200 Subject: [PATCH 01/14] Fauxton: Inject database into view it's handled in the options and used, but was never passed --- src/fauxton/app/addons/documents/routes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fauxton/app/addons/documents/routes.js b/src/fauxton/app/addons/documents/routes.js index 5a2a04f137d..5f8659778bf 100644 --- a/src/fauxton/app/addons/documents/routes.js +++ b/src/fauxton/app/addons/documents/routes.js @@ -241,6 +241,7 @@ function(app, FauxtonAPI, Documents, Databases) { })); this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({ + database: this.data.database, collection: this.data.database.allDocs, docParams: docParams, params: urlParams From 7c6e84eedc92de9503dfd52afa79b1401f9732d0 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Fri, 2 May 2014 21:39:21 +0200 Subject: [PATCH 02/14] Implement bulk deletion for all-docs-listing Introduce a collection which keeps track of documents that will deleted using the CouchDB Bulk-update API, backed by a Session- Storage. The collection fires events, so the view is noticed. Closes COUCHDB-2153 --- src/fauxton/app/addons/documents/resources.js | 116 +++++++++++++- src/fauxton/app/addons/documents/routes.js | 3 +- .../documents/templates/all_docs_item.html | 4 +- .../documents/templates/all_docs_list.html | 6 +- .../addons/documents/tests/resourcesSpec.js | 120 ++++++++++++++- src/fauxton/app/addons/documents/views.js | 143 +++++++++++------- 6 files changed, 324 insertions(+), 68 deletions(-) diff --git a/src/fauxton/app/addons/documents/resources.js b/src/fauxton/app/addons/documents/resources.js index 99e79a2ab59..2a44c8044cf 100644 --- a/src/fauxton/app/addons/documents/resources.js +++ b/src/fauxton/app/addons/documents/resources.js @@ -41,7 +41,7 @@ function(app, FauxtonAPI, PagingCollection) { }; })(); - + Documents.Doc = FauxtonAPI.Model.extend({ idAttribute: "_id", documentation: function(){ @@ -295,6 +295,120 @@ function(app, FauxtonAPI, PagingCollection) { }); + Documents.BulkDeleteDoc = FauxtonAPI.Model.extend({ + idAttribute: "_id" + }); + + Documents.BulkDeleteDocCollection = FauxtonAPI.Collection.extend({ + + model: Documents.BulkDeleteDoc, + + initialize: function (models, options) { + this.databaseId = options.databaseId; + }, + + bulkDelete: function () { + var payload = this.createPayload(this.toJSON()), + that = this; + + $.ajax({ + type: 'POST', + url: app.host + '/' + this.databaseId + '/_bulk_docs', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify(payload), + }) + .then(function (res) { + that.handleResponse(res); + }) + .fail(function () { + var ids = _.reduce(this.toArray(), function (acc, doc) { + acc.push(doc.id); + return acc; + }, []); + that.trigger('error', ids); + }); + }, + + handleResponse: function (res) { + var errorIds = [], + successIds = [], + doc; + + for (doc in res) { + if (res[doc].error) { + errorIds.push(res[doc].id); + } + if (res[doc].ok === true) { + successIds.push(res[doc].id); + } + } + + this.removeDocuments(successIds); + + if (!errorIds.length) { + this.clear(); + } else { + this.trigger('error', errorIds); + this.save(); + } + + this.trigger('updated'); + }, + + removeDocuments: function (ids) { + _.each(ids, function (id) { + if (/_design/.test(id)) { + FauxtonAPI.triggerRouteEvent('reloadDesignDocs'); + } + + this.remove(this.get(id)); + }, this); + + this.trigger('removed', ids); + }, + + createList: function (documents) { + var documentList = [], + id; + + for (id in documents) { + documentList.push(documents[id]); + } + + return documentList; + }, + + createPayload: function (documents) { + var documentList = this.createList(documents); + + return { + docs: documentList + }; + }, + + parse: function (resp) { + return this.createList(resp); + }, + + clear: function () { + window.sessionStorage.removeItem('couchdb:docsToDelete:' + this.databaseId); + }, + + save: function () { + var data = JSON.stringify(this.toJSON()); + window.sessionStorage.setItem('couchdb:docsToDelete:' + this.databaseId, data); + }, + + sync: function (method, model, options) { + var storedData = window.sessionStorage.getItem('couchdb:docsToDelete:' + this.databaseId), + documents = JSON.parse(storedData) || {}; + + options.success(documents); + } + }); + + Documents.AllDocs = PagingCollection.extend({ model: Documents.Doc, documentation: function(){ diff --git a/src/fauxton/app/addons/documents/routes.js b/src/fauxton/app/addons/documents/routes.js index 5f8659778bf..cd036a53306 100644 --- a/src/fauxton/app/addons/documents/routes.js +++ b/src/fauxton/app/addons/documents/routes.js @@ -244,7 +244,8 @@ function(app, FauxtonAPI, Documents, Databases) { database: this.data.database, collection: this.data.database.allDocs, docParams: docParams, - params: urlParams + params: urlParams, + bulkDeleteDocsCollection: new Documents.BulkDeleteDocCollection([], {databaseId: this.data.database.get('id')}) })); this.crumbs = [ diff --git a/src/fauxton/app/addons/documents/templates/all_docs_item.html b/src/fauxton/app/addons/documents/templates/all_docs_item.html index bfedaaa0833..a8ef20f4442 100644 --- a/src/fauxton/app/addons/documents/templates/all_docs_item.html +++ b/src/fauxton/app/addons/documents/templates/all_docs_item.html @@ -12,13 +12,13 @@ the License. --> - + type="checkbox" class="js-row-select">
<%- doc.prettyJSON() %>
<% if (doc.isEditable()) { %> <% } %> 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 a521ff969b7..27ddf411f24 100644 --- a/src/fauxton/app/addons/documents/templates/all_docs_list.html +++ b/src/fauxton/app/addons/documents/templates/all_docs_list.html @@ -17,7 +17,7 @@
- + <% if (expandDocs) { %> <% } else { %> @@ -32,8 +32,8 @@
- - <% if (endOfResults) { %> + + <% if (endOfResults) { %>

End of results - edit query diff --git a/src/fauxton/app/addons/documents/tests/resourcesSpec.js b/src/fauxton/app/addons/documents/tests/resourcesSpec.js index 62506e6e4d4..02962360ead 100644 --- a/src/fauxton/app/addons/documents/tests/resourcesSpec.js +++ b/src/fauxton/app/addons/documents/tests/resourcesSpec.js @@ -10,8 +10,8 @@ // License for the specific language governing permissions and limitations under // the License. define([ - 'addons/documents/resources', - 'testUtils' + 'addons/documents/resources', + 'testUtils' ], function (Models, testUtils) { var assert = testUtils.assert; @@ -50,8 +50,122 @@ define([ }); }); - }); + describe('Bulk Delete', function () { + var databaseId = 'ente', + collection, + values; + + values = [{ + _id: '1', + _rev: '1234561', + _deleted: true + }, + { + _id: '2', + _rev: '1234562', + _deleted: true + }, + { + _id: '3', + _rev: '1234563', + _deleted: true + }]; + + beforeEach(function () { + window.sessionStorage.removeItem('couchdb:docsToDelete:' + databaseId); + collection = new Models.BulkDeleteDocCollection(values, { + databaseId: databaseId + }); + }); + + it("saves the models", function () { + collection.save(); + collection = collection = new Models.BulkDeleteDocCollection([], { + databaseId: databaseId + }); + + collection.sync(null, null, { + success: function (data) { + assert.deepEqual(data, values); + } + }); + }); + + it("clears the memory if no errors happened", function () { + collection.save(); + collection.handleResponse([ + {"ok":true,"id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, + {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} + ]); + + collection.sync(null, null, { + success: function (data) { + assert.deepEqual(data, {}); + } + }); + }); + + it("clears the storage if no errors happened", function () { + collection.save(); + collection.handleResponse([ + {"ok":true,"id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, + {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} + ]); + + collection.sync(null, null, { + success: function (data) { + assert.deepEqual(data, {}); + } + }); + }); + + it("triggers a removed event with all ids", function () { + collection.listenTo(collection, 'removed', function (ids) { + assert.deepEqual(ids, ['Deferred', 'DeskSet']); + }); + collection.save(); + collection.handleResponse([ + {"ok":true,"id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, + {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} + ]); + }); + + it("triggers a error event with all errored ids", function () { + collection.listenTo(collection, 'error', function (ids) { + assert.deepEqual(ids, ['Deferred']); + }); + collection.save(); + collection.handleResponse([ + {"error":"confclict","id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, + {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} + ]); + }); + + it("removes successfull deleted from the collection but keeps one with errors", function () { + collection.save(); + collection.handleResponse([ + {"error":"confclict","id":"1","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, + {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}, + {"error":"conflict","id":"3","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} + ]); + assert.ok(collection.get('1')); + assert.ok(collection.get('3')); + assert.notOk(collection.get('2')); + }); + + it("removes successfull deleted from the storage but keeps one with errors", function () { + collection.save(); + collection.handleResponse([ + {"error":"confclict","id":"1","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, + {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}, + {"error":"conflict","id":"3","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} + ]); + var data = window.sessionStorage.getItem('couchdb:docsToDelete:' + databaseId); + + assert.equal(data, '[{"_id":"1","_rev":"1234561","_deleted":true},{"_id":"3","_rev":"1234563","_deleted":true}]'); + }); + }); }); diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index c58241c988a..93c63a9a5cb 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -33,6 +33,15 @@ define([ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, resizeColumns, beautify, prettify, ZeroClipboard) { + + function showError (msg) { + FauxtonAPI.addNotification({ + msg: msg, + type: 'error', + clear: true + }); + } + var Views = {}; Views.SearchBox = FauxtonAPI.View.extend({ @@ -274,6 +283,10 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, tagName: "tr", className: "all-docs-item", + initialize: function(options) { + this.checked = options.checked; + }, + events: { "click button.delete": "destroy", "dblclick pre.prettyprint": "edit" @@ -287,7 +300,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, serialize: function() { return { - doc: this.model + doc: this.model, + checked: this.checked }; }, @@ -530,29 +544,16 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, template: "addons/documents/templates/all_docs_list", events: { "click button.all": "selectAll", - "click button.bulk-delete": "bulkDelete", + "click button.js-bulk-delete": "bulkDelete", "click #collapse": "collapse", - "change .row-select":"toggleTrash", + "change .js-row-select": "toggleDocument", "click #js-end-results": "scrollToQuery" }, - toggleTrash: function () { - if (this.$('.row-select:checked').length > 0) { - this.$('.bulk-delete').removeClass('disabled'); - } else { - this.$('.bulk-delete').addClass('disabled'); - } - }, - - scrollToQuery: function () { - $('#dashboard-content').animate({ scrollTop: 0 }, 'slow'); - }, - - initialize: function(options){ + initialize: function (options) { this.nestedView = options.nestedView || Views.Document; this.rows = {}; - this.viewList = !! options.viewList; - this.database = options.database; + this.viewList = !!options.viewList; if (options.ddocInfo) { this.designDocs = options.ddocInfo.designDocs; @@ -563,6 +564,59 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, this.params = options.params || {}; this.expandDocs = true; this.perPageDefault = this.docParams.limit || 20; + + this.bulkDeleteDocsCollection = options.bulkDeleteDocsCollection; + this.bulkDeleteDocsCollection.fetch({reset: true}); + }, + + removeDocuments: function (ids) { + _.each(ids, function (id) { + this.removeDocument(id); + }, this); + }, + + removeDocument: function (id) { + this.rows[id].$el.fadeOut(function () { + $(this).remove(); + }); + }, + + showError: function (ids) { + if (ids) { + showError('Failed to destroy: ' + ids.join(', ')); + return; + } + + showError('Failed to destroy your doc!'); + }, + + toggleDocument: function (event) { + this.toggleTrash(); + + var docId = this.$(event.target).closest('tr').attr('data-id'), + db = this.database.get('id'), + rev = this.collection.get(docId).get('_rev'), + data = {_id: docId, _rev: rev, _deleted: true}; + + if (this.$(event.target).is(':checked')) { + this.bulkDeleteDocsCollection.add(new Documents.BulkDeleteDoc(data)); + } else { + this.bulkDeleteDocsCollection.remove(this.bulkDeleteDocsCollection.get(docId)); + } + + this.bulkDeleteDocsCollection.save(); + }, + + toggleTrash: function () { + if (this.$('.js-row-select:checked').length > 0) { + this.$('.js-bulk-delete').removeClass('disabled'); + } else { + this.$('.js-bulk-delete').addClass('disabled'); + } + }, + + scrollToQuery: function () { + $('#dashboard-content').animate({ scrollTop: 0 }, 'slow'); }, establish: function() { @@ -582,7 +636,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, //now redirect back to alldocs FauxtonAPI.navigate(model.database.url("index") + "?limit=100"); - console.log("ERROR: ", arguments); } }); }, @@ -611,48 +664,17 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, this.render(); }, - /* - * TODO: this should be reconsidered - * This currently performs delete operations on the model level, - * when we could be using bulk docs with _deleted = true. Using - * individual models is cleaner from a backbone standpoint, but - * not from the couchdb api. - * Also, the delete method is naive and leaves the body intact, - * when we should switch the doc to only having id/rev/deleted. - */ bulkDelete: function() { - var that = this; - // yuck, data binding ftw? - var eles = this.$el.find("input.row-select:checked") - .parents("tr.all-docs-item") - .map(function(e) { return $(this).attr("data-id"); }) - .get(); + var that = this, + documents = this.bulkDeleteDocsCollection.toArray(), + msg; - if (eles.length === 0 || !window.confirm("Are you sure you want to delete these " + eles.length + " docs?")) { + msg = "Are you sure you want to delete these " + documents.length + " docs?"; + if (documents.length === 0 || !window.confirm(msg)) { return false; } - _.each(eles, function(ele) { - var model = this.collection.get(ele); - - model.destroy().then(function(resp) { - that.rows[ele].$el.fadeOut(function () { - $(this).remove(); - }); - - model.collection.remove(model.id); - if (!!model.id.match('_design')) { - FauxtonAPI.triggerRouteEvent('reloadDesignDocs'); - } - that.$('.bulk-delete').addClass('disabled'); - }, function(resp) { - FauxtonAPI.addNotification({ - msg: "Failed to destroy your doc!", - type: "error", - clear: true - }); - }); - }, this); + this.bulkDeleteDocsCollection.bulkDelete(); }, addPagination: function () { @@ -671,6 +693,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, }, beforeRender: function() { + var docs; if (!this.pagination) { this.addPagination(); @@ -689,11 +712,12 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, this.setView('#item-numbers', this.allDocsNumber); - var docs = this.expandDocs ? this.collection : this.collection.simple(); + docs = this.expandDocs ? this.collection : this.collection.simple(); docs.each(function(doc) { this.rows[doc.id] = this.insertView("table.all-docs tbody", new this.nestedView({ - model: doc + model: doc, + checked: this.bulkDeleteDocsCollection.get(doc.id) })); }, this); }, @@ -716,6 +740,9 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, afterRender: function(){ prettyPrint(); + this.listenTo(this.bulkDeleteDocsCollection, 'error', this.showError); + this.listenTo(this.bulkDeleteDocsCollection, 'removed', this.removeDocuments); + this.listenTo(this.bulkDeleteDocsCollection, 'updated', this.toggleTrash); }, perPage: function () { From e0394f16edbd82cdb96c386af2975f1b57f8b89e Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Sun, 18 May 2014 14:35:53 +0200 Subject: [PATCH 03/14] review --- src/fauxton/app/addons/documents/resources.js | 70 ++++++------------- .../addons/documents/tests/resourcesSpec.js | 57 +++------------ src/fauxton/app/addons/documents/views.js | 13 ++-- 3 files changed, 37 insertions(+), 103 deletions(-) diff --git a/src/fauxton/app/addons/documents/resources.js b/src/fauxton/app/addons/documents/resources.js index 2a44c8044cf..e64af76e81c 100644 --- a/src/fauxton/app/addons/documents/resources.js +++ b/src/fauxton/app/addons/documents/resources.js @@ -303,6 +303,8 @@ function(app, FauxtonAPI, PagingCollection) { model: Documents.BulkDeleteDoc, + sync: function() {}, + initialize: function (models, options) { this.databaseId = options.databaseId; }, @@ -322,7 +324,7 @@ function(app, FauxtonAPI, PagingCollection) { that.handleResponse(res); }) .fail(function () { - var ids = _.reduce(this.toArray(), function (acc, doc) { + var ids = _.reduce(that.toArray(), function (acc, doc) { acc.push(doc.id); return acc; }, []); @@ -331,80 +333,50 @@ function(app, FauxtonAPI, PagingCollection) { }, handleResponse: function (res) { - var errorIds = [], - successIds = [], - doc; - - for (doc in res) { - if (res[doc].error) { - errorIds.push(res[doc].id); + var ids = _.reduce(res, function (ids, doc) { + if (doc.error) { + ids.errorIds.push(doc.id); } - if (res[doc].ok === true) { - successIds.push(res[doc].id); + + if (doc.ok === true) { + ids.successIds.push(doc.id); } - } - this.removeDocuments(successIds); + return ids; + }, {errorIds: [], successIds: []}); - if (!errorIds.length) { - this.clear(); - } else { - this.trigger('error', errorIds); - this.save(); + this.removeDocuments(ids.successIds); + + if (ids.errorIds.length) { + this.trigger('error', ids.errorIds); } this.trigger('updated'); }, removeDocuments: function (ids) { + var reloadDesignDocs = false; _.each(ids, function (id) { if (/_design/.test(id)) { - FauxtonAPI.triggerRouteEvent('reloadDesignDocs'); + reloadDesignDocs = true; } this.remove(this.get(id)); }, this); - this.trigger('removed', ids); - }, - - createList: function (documents) { - var documentList = [], - id; - - for (id in documents) { - documentList.push(documents[id]); + if (reloadDesignDocs) { + FauxtonAPI.triggerRouteEvent('reloadDesignDocs'); } - return documentList; + this.trigger('removed', ids); }, createPayload: function (documents) { - var documentList = this.createList(documents); + var documentList = documents; return { docs: documentList }; - }, - - parse: function (resp) { - return this.createList(resp); - }, - - clear: function () { - window.sessionStorage.removeItem('couchdb:docsToDelete:' + this.databaseId); - }, - - save: function () { - var data = JSON.stringify(this.toJSON()); - window.sessionStorage.setItem('couchdb:docsToDelete:' + this.databaseId, data); - }, - - sync: function (method, model, options) { - var storedData = window.sessionStorage.getItem('couchdb:docsToDelete:' + this.databaseId), - documents = JSON.parse(storedData) || {}; - - options.success(documents); } }); diff --git a/src/fauxton/app/addons/documents/tests/resourcesSpec.js b/src/fauxton/app/addons/documents/tests/resourcesSpec.js index 02962360ead..655fe273486 100644 --- a/src/fauxton/app/addons/documents/tests/resourcesSpec.js +++ b/src/fauxton/app/addons/documents/tests/resourcesSpec.js @@ -74,58 +74,33 @@ define([ }]; beforeEach(function () { - window.sessionStorage.removeItem('couchdb:docsToDelete:' + databaseId); collection = new Models.BulkDeleteDocCollection(values, { databaseId: databaseId }); }); - it("saves the models", function () { - collection.save(); - collection = collection = new Models.BulkDeleteDocCollection([], { + it("contains the models", function () { + collection = new Models.BulkDeleteDocCollection(values, { databaseId: databaseId }); - collection.sync(null, null, { - success: function (data) { - assert.deepEqual(data, values); - } - }); + assert.equal(collection.length, 3); }); it("clears the memory if no errors happened", function () { - collection.save(); collection.handleResponse([ - {"ok":true,"id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, - {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} - ]); - - collection.sync(null, null, { - success: function (data) { - assert.deepEqual(data, {}); - } - }); - }); - - it("clears the storage if no errors happened", function () { - collection.save(); - collection.handleResponse([ - {"ok":true,"id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, - {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} + {"ok":true,"id":"1","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, + {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} ]); - collection.sync(null, null, { - success: function (data) { - assert.deepEqual(data, {}); - } - }); + assert.equal(collection.length, 1) }); it("triggers a removed event with all ids", function () { - collection.listenTo(collection, 'removed', function (ids) { + collection.listenToOnce(collection, 'removed', function (ids) { assert.deepEqual(ids, ['Deferred', 'DeskSet']); }); - collection.save(); + collection.handleResponse([ {"ok":true,"id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} @@ -133,10 +108,9 @@ define([ }); it("triggers a error event with all errored ids", function () { - collection.listenTo(collection, 'error', function (ids) { + collection.listenToOnce(collection, 'error', function (ids) { assert.deepEqual(ids, ['Deferred']); }); - collection.save(); collection.handleResponse([ {"error":"confclict","id":"Deferred","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, {"ok":true,"id":"DeskSet","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} @@ -144,7 +118,6 @@ define([ }); it("removes successfull deleted from the collection but keeps one with errors", function () { - collection.save(); collection.handleResponse([ {"error":"confclict","id":"1","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}, @@ -154,18 +127,6 @@ define([ assert.ok(collection.get('3')); assert.notOk(collection.get('2')); }); - - it("removes successfull deleted from the storage but keeps one with errors", function () { - collection.save(); - collection.handleResponse([ - {"error":"confclict","id":"1","rev":"10-72cd2edbcc0d197ce96188a229a7af01"}, - {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"}, - {"error":"conflict","id":"3","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} - ]); - var data = window.sessionStorage.getItem('couchdb:docsToDelete:' + databaseId); - - assert.equal(data, '[{"_id":"1","_rev":"1234561","_deleted":true},{"_id":"3","_rev":"1234563","_deleted":true}]'); - }); }); }); diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 93c63a9a5cb..5fc3dc7da26 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -576,6 +576,9 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, }, removeDocument: function (id) { + if (!this.rows[id]) { + return; + } this.rows[id].$el.fadeOut(function () { $(this).remove(); }); @@ -599,12 +602,10 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, data = {_id: docId, _rev: rev, _deleted: true}; if (this.$(event.target).is(':checked')) { - this.bulkDeleteDocsCollection.add(new Documents.BulkDeleteDoc(data)); + this.bulkDeleteDocsCollection.add(data); } else { this.bulkDeleteDocsCollection.remove(this.bulkDeleteDocsCollection.get(docId)); } - - this.bulkDeleteDocsCollection.save(); }, toggleTrash: function () { @@ -666,11 +667,11 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, bulkDelete: function() { var that = this, - documents = this.bulkDeleteDocsCollection.toArray(), + documentsLength = this.bulkDeleteDocsCollection.length, msg; - msg = "Are you sure you want to delete these " + documents.length + " docs?"; - if (documents.length === 0 || !window.confirm(msg)) { + msg = "Are you sure you want to delete these " + documentsLength + " docs?"; + if (documentsLength === 0 || !window.confirm(msg)) { return false; } From 948da8eef6e22c368ea5fcf3b5b11918ca31db32 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Wed, 21 May 2014 20:01:42 +0200 Subject: [PATCH 04/14] add missing semicolon --- src/fauxton/app/addons/documents/tests/resourcesSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fauxton/app/addons/documents/tests/resourcesSpec.js b/src/fauxton/app/addons/documents/tests/resourcesSpec.js index 655fe273486..17d1eb933d5 100644 --- a/src/fauxton/app/addons/documents/tests/resourcesSpec.js +++ b/src/fauxton/app/addons/documents/tests/resourcesSpec.js @@ -93,7 +93,7 @@ define([ {"ok":true,"id":"2","rev":"6-da537822b9672a4b2f42adb1be04a5b1"} ]); - assert.equal(collection.length, 1) + assert.equal(collection.length, 1); }); it("triggers a removed event with all ids", function () { From 741794bc3d0279e53160644b4ea5f7f59694ab35 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Wed, 21 May 2014 20:36:29 +0200 Subject: [PATCH 05/14] fix secondary indexes --- src/fauxton/app/addons/documents/views.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 5fc3dc7da26..323fa3911bf 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -565,8 +565,11 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, this.expandDocs = true; this.perPageDefault = this.docParams.limit || 20; - this.bulkDeleteDocsCollection = options.bulkDeleteDocsCollection; - this.bulkDeleteDocsCollection.fetch({reset: true}); + // some doclists don't have an option to delete + if (!this.viewList) { + this.bulkDeleteDocsCollection = options.bulkDeleteDocsCollection; + this.bulkDeleteDocsCollection.fetch({reset: true}); + } }, removeDocuments: function (ids) { @@ -716,9 +719,13 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, docs = this.expandDocs ? this.collection : this.collection.simple(); docs.each(function(doc) { + var isChecked; + if (this.bulkDeleteDocsCollection) { + isChecked = this.bulkDeleteDocsCollection.get(doc.id); + } this.rows[doc.id] = this.insertView("table.all-docs tbody", new this.nestedView({ model: doc, - checked: this.bulkDeleteDocsCollection.get(doc.id) + checked: isChecked })); }, this); }, @@ -741,9 +748,12 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, afterRender: function(){ prettyPrint(); - this.listenTo(this.bulkDeleteDocsCollection, 'error', this.showError); - this.listenTo(this.bulkDeleteDocsCollection, 'removed', this.removeDocuments); - this.listenTo(this.bulkDeleteDocsCollection, 'updated', this.toggleTrash); + + if (this.bulkDeleteDocsCollection) { + this.listenTo(this.bulkDeleteDocsCollection, 'error', this.showError); + this.listenTo(this.bulkDeleteDocsCollection, 'removed', this.removeDocuments); + this.listenTo(this.bulkDeleteDocsCollection, 'updated', this.toggleTrash); + } }, perPage: function () { From 077387efb364313fe5aab4588661704d305834e1 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Wed, 21 May 2014 21:00:19 +0200 Subject: [PATCH 06/14] add click handler for selection --- .../app/addons/documents/templates/all_docs_item.html | 2 +- src/fauxton/app/addons/documents/views.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/fauxton/app/addons/documents/templates/all_docs_item.html b/src/fauxton/app/addons/documents/templates/all_docs_item.html index a8ef20f4442..7e0a08ea725 100644 --- a/src/fauxton/app/addons/documents/templates/all_docs_item.html +++ b/src/fauxton/app/addons/documents/templates/all_docs_item.html @@ -13,7 +13,7 @@ --> type="checkbox" class="js-row-select"> - +

<%- doc.prettyJSON() %>
<% if (doc.isEditable()) { %> diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 323fa3911bf..9738c646f7c 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -547,6 +547,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, "click button.js-bulk-delete": "bulkDelete", "click #collapse": "collapse", "change .js-row-select": "toggleDocument", + "click .js-row-document-container": "toggleDocument", "click #js-end-results": "scrollToQuery" }, @@ -599,20 +600,24 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, toggleDocument: function (event) { this.toggleTrash(); - var docId = this.$(event.target).closest('tr').attr('data-id'), + var $row = this.$(event.target).closest('tr'), + docId = $row.attr('data-id'), db = this.database.get('id'), rev = this.collection.get(docId).get('_rev'), data = {_id: docId, _rev: rev, _deleted: true}; - if (this.$(event.target).is(':checked')) { + if (!$row.hasClass('js-to-delete')) { this.bulkDeleteDocsCollection.add(data); } else { this.bulkDeleteDocsCollection.remove(this.bulkDeleteDocsCollection.get(docId)); } + + $row.find('.js-row-select').prop('checked', !$row.hasClass('js-to-delete')); + $row.toggleClass('js-to-delete'); }, toggleTrash: function () { - if (this.$('.js-row-select:checked').length > 0) { + if (this.$('.js-to-delete').length > 0) { this.$('.js-bulk-delete').removeClass('disabled'); } else { this.$('.js-bulk-delete').addClass('disabled'); From 731706c53c2ea09b66bb719b5bde1c85a28e170a Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Wed, 21 May 2014 21:08:04 +0200 Subject: [PATCH 07/14] remove fetch call --- src/fauxton/app/addons/documents/views.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 9738c646f7c..e0f08209907 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -569,7 +569,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, // some doclists don't have an option to delete if (!this.viewList) { this.bulkDeleteDocsCollection = options.bulkDeleteDocsCollection; - this.bulkDeleteDocsCollection.fetch({reset: true}); } }, From 4bdd8601a22eeac65ba853da9c46e66753693b03 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Wed, 21 May 2014 23:27:00 +0200 Subject: [PATCH 08/14] fetch the amount of documents for the page again --- src/fauxton/app/addons/documents/views.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index e0f08209907..72769d1ac46 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -576,6 +576,9 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, _.each(ids, function (id) { this.removeDocument(id); }, this); + + this.pagination.updatePerPage(parseInt(this.$('#select-per-page :selected').val(), 10)); + FauxtonAPI.triggerRouteEvent('perPageChange', this.pagination.documentsLeftToFetch()); }, removeDocument: function (id) { From 20b53d7baf1e161acf90a67414e63eca34f26575 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Thu, 22 May 2014 19:18:45 +0200 Subject: [PATCH 09/14] wording: destroy to delete --- src/fauxton/app/addons/documents/views.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 72769d1ac46..20955492f89 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -324,7 +324,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, this.model.destroy().then(function(resp) { FauxtonAPI.addNotification({ - msg: "Succesfully destroyed your doc", + msg: "Succesfully deleted your doc", clear: true }); that.$el.fadeOut(function () { @@ -337,7 +337,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, } }, function(resp) { FauxtonAPI.addNotification({ - msg: "Failed to destroy your doc!", + msg: "Failed to deleted your doc!", type: "error", clear: true }); @@ -592,11 +592,11 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, showError: function (ids) { if (ids) { - showError('Failed to destroy: ' + ids.join(', ')); + showError('Failed to delete: ' + ids.join(', ')); return; } - showError('Failed to destroy your doc!'); + showError('Failed to delete your doc!'); }, toggleDocument: function (event) { @@ -803,13 +803,13 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, this.model.destroy().then(function(resp) { FauxtonAPI.addNotification({ - msg: "Succesfully destroyed your doc", + msg: "Succesfully deleted your doc", clear: true }); FauxtonAPI.navigate(database.url("index")); }, function(resp) { FauxtonAPI.addNotification({ - msg: "Failed to destroy your doc!", + msg: "Failed to delete your doc!", type: "error", clear: true }); From 49ab527f8a481c7ca693446915377a92b53b6d0f Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Thu, 22 May 2014 19:31:05 +0200 Subject: [PATCH 10/14] click on the whole box toggles --- src/fauxton/app/addons/documents/templates/all_docs_item.html | 2 +- src/fauxton/app/addons/documents/views.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fauxton/app/addons/documents/templates/all_docs_item.html b/src/fauxton/app/addons/documents/templates/all_docs_item.html index 7e0a08ea725..a8ef20f4442 100644 --- a/src/fauxton/app/addons/documents/templates/all_docs_item.html +++ b/src/fauxton/app/addons/documents/templates/all_docs_item.html @@ -13,7 +13,7 @@ --> type="checkbox" class="js-row-select"> - +
<%- doc.prettyJSON() %>
<% if (doc.isEditable()) { %> diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 20955492f89..3e95969f43e 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -546,8 +546,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, "click button.all": "selectAll", "click button.js-bulk-delete": "bulkDelete", "click #collapse": "collapse", - "change .js-row-select": "toggleDocument", - "click .js-row-document-container": "toggleDocument", + "click .all-docs-item": "toggleDocument", "click #js-end-results": "scrollToQuery" }, From d9d9f17388c0cff977273d5023e0f0d7bbcd90ef Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Tue, 27 May 2014 21:31:06 +0200 Subject: [PATCH 11/14] fix event binding `afterRender` is called twice, so we had the same event bindings twice --- src/fauxton/app/addons/documents/views.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 3e95969f43e..1c432994b14 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -752,14 +752,17 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, } }, - afterRender: function(){ + afterRender: function () { prettyPrint(); if (this.bulkDeleteDocsCollection) { + this.stopListening(this.bulkDeleteDocsCollection); this.listenTo(this.bulkDeleteDocsCollection, 'error', this.showError); this.listenTo(this.bulkDeleteDocsCollection, 'removed', this.removeDocuments); this.listenTo(this.bulkDeleteDocsCollection, 'updated', this.toggleTrash); } + + this.toggleTrash(); }, perPage: function () { From 934d445d6e2b6a977f2347c31fd64697f5d8776d Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Tue, 27 May 2014 21:34:12 +0200 Subject: [PATCH 12/14] remove the view --- src/fauxton/app/addons/documents/views.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 1c432994b14..567fd844b9d 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -581,11 +581,14 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, }, removeDocument: function (id) { + var that = this; + if (!this.rows[id]) { return; } - this.rows[id].$el.fadeOut(function () { - $(this).remove(); + + this.rows[id].$el.fadeOut('slow', function () { + that.rows[id].remove(); }); }, From 92f9dd16ba7193d2c3c78617b0406d79f17f08b7 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Tue, 27 May 2014 21:42:07 +0200 Subject: [PATCH 13/14] fix trash button .disabled comes from bootstrap fix execution order --- .../app/addons/documents/templates/all_docs_list.html | 2 +- src/fauxton/app/addons/documents/views.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) 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 27ddf411f24..a64342779d2 100644 --- a/src/fauxton/app/addons/documents/templates/all_docs_list.html +++ b/src/fauxton/app/addons/documents/templates/all_docs_list.html @@ -17,7 +17,7 @@
- + <% if (expandDocs) { %> <% } else { %> diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 567fd844b9d..60c6f612a93 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -602,8 +602,6 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, }, toggleDocument: function (event) { - this.toggleTrash(); - var $row = this.$(event.target).closest('tr'), docId = $row.attr('data-id'), db = this.database.get('id'), @@ -618,13 +616,17 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, $row.find('.js-row-select').prop('checked', !$row.hasClass('js-to-delete')); $row.toggleClass('js-to-delete'); + + this.toggleTrash(); }, toggleTrash: function () { + var $bulkdDeleteButton = this.$('.js-bulk-delete'); + if (this.$('.js-to-delete').length > 0) { - this.$('.js-bulk-delete').removeClass('disabled'); + $bulkdDeleteButton.removeClass('disabled'); } else { - this.$('.js-bulk-delete').addClass('disabled'); + $bulkdDeleteButton.addClass('disabled'); } }, From 688323812290d04683e893410559b0d03c369141 Mon Sep 17 00:00:00 2001 From: Robert Kowalski Date: Sat, 31 May 2014 17:06:29 +0200 Subject: [PATCH 14/14] toggle trash after switching page back&forward --- src/fauxton/app/addons/documents/views.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fauxton/app/addons/documents/views.js b/src/fauxton/app/addons/documents/views.js index 60c6f612a93..42b405b32ab 100644 --- a/src/fauxton/app/addons/documents/views.js +++ b/src/fauxton/app/addons/documents/views.js @@ -623,7 +623,7 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, toggleTrash: function () { var $bulkdDeleteButton = this.$('.js-bulk-delete'); - if (this.$('.js-to-delete').length > 0) { + if (this.bulkDeleteDocsCollection.length > 0) { $bulkdDeleteButton.removeClass('disabled'); } else { $bulkdDeleteButton.addClass('disabled');