diff --git a/lib/assets/javascripts/cartodb/new_common/views/base_dialog/view.js b/lib/assets/javascripts/cartodb/new_common/views/base_dialog/view.js index 9d16624ef9..9e2a0949a3 100644 --- a/lib/assets/javascripts/cartodb/new_common/views/base_dialog/view.js +++ b/lib/assets/javascripts/cartodb/new_common/views/base_dialog/view.js @@ -1,5 +1,7 @@ var cdb = require('cartodb.js'); +var BaseDialog = cdb.ui.common.Dialog; + /** * Abstract view for a dialog, a kind of view that takes up the full screen overlaying any previous content. * @@ -20,7 +22,7 @@ var cdb = require('cartodb.js'); * // To render & show initially (only to be called once): * dialog.appendToBody(); */ -module.exports = cdb.ui.common.Dialog.extend({ +module.exports = BaseDialog.extend({ className: 'Dialog', overrideDefaults: { @@ -46,7 +48,7 @@ module.exports = cdb.ui.common.Dialog.extend({ // The timeout should match the .Dialog--closing animation duration. var self = this; setTimeout(function() { - cdb.ui.common.Dialog.prototype._cancel.apply(self, arguments); + BaseDialog.prototype._cancel.apply(self, arguments); }, 80); //ms } }); diff --git a/lib/assets/javascripts/cartodb/new_dashboard/content_controller_view.js b/lib/assets/javascripts/cartodb/new_dashboard/content_controller_view.js index a07b686461..78c053d80f 100644 --- a/lib/assets/javascripts/cartodb/new_dashboard/content_controller_view.js +++ b/lib/assets/javascripts/cartodb/new_dashboard/content_controller_view.js @@ -137,7 +137,11 @@ module.exports = cdb.core.View.extend({ this._scrollToTop(); }, - _onDataFetched: function(coll, opts) { + /** + * Arguments may vary, depending on if it's the collection or a model that triggers the event callback. + * @private + */ + _onDataFetched: function() { var activeViews = [ 'filters', 'content_footer' ]; var tag = this.router.model.get('tag'); var q = this.router.model.get('q'); @@ -146,7 +150,7 @@ module.exports = cdb.core.View.extend({ var locked = this.router.model.get('locked'); var library = this.router.model.get('library'); - if (coll.size() === 0) { + if (this.collection.size() === 0) { if (!tag && !q && !shared && !locked && !liked) { if (this.router.model.get('content_type') === "maps") { diff --git a/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/template.jst.ejs b/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/template.jst.ejs index b728569415..270c12d6ff 100644 --- a/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/template.jst.ejs +++ b/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/template.jst.ejs @@ -14,7 +14,7 @@ - diff --git a/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/view.js b/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/view.js index c583866e04..ed855586f9 100644 --- a/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/view.js +++ b/lib/assets/javascripts/cartodb/new_dashboard/dialogs/delete_items/view.js @@ -1,11 +1,18 @@ var BaseDialog = require('new_common/views/base_dialog/view'); var pluralizeString = require('new_common/view_helpers/pluralize_string'); +var queue = require('queue-async'); /** * Delete items dialog */ module.exports = BaseDialog.extend({ + events: function() { + return _.extend({}, BaseDialog.prototype.events, { + 'click .js-ok' : '_deleteSelected' + }); + }, + initialize: function(args) { this.elder('initialize'); this.collection = args.collection; @@ -23,5 +30,32 @@ module.exports = BaseDialog.extend({ totalCount: totalCount, pluralizedContentType: pluralizeString(this.router.model.get('content_type') === 'datasets' ? 'dataset' : 'map', totalCount) }) + }, + + _deleteSelected: function(e) { + this.killEvent(e); + + var q = queue(5); // # items to destroy in parallel + _.each(this.collection.where({ selected: true }), function(m) { + q.defer(function(callback) { + m.destroy({ wait: true }) + .done(function() { + callback(null, arguments); + }) + .fail(function() { + callback(arguments) + }); + }); + }); + + var self = this; + q.awaitAll(function(error, results) { + // error and results contains outcome of the jqXHR requests above, see http://api.jquery.com/jQuery.ajax/#jqXHR + if (error) { + // TODO: How should errors be handled? + } else { + self.hide(); + } + }) } }); diff --git a/lib/assets/test/spec/cartodb/new_dashboard/dialogs/delete_items/view.spec.js b/lib/assets/test/spec/cartodb/new_dashboard/dialogs/delete_items/view.spec.js index b4b7cf5891..00632d1c38 100644 --- a/lib/assets/test/spec/cartodb/new_dashboard/dialogs/delete_items/view.spec.js +++ b/lib/assets/test/spec/cartodb/new_dashboard/dialogs/delete_items/view.spec.js @@ -1,4 +1,5 @@ var DeleteItems = require('new_dashboard/dialogs/delete_items/view'); +var $ = require('jquery'); var Router = require('new_dashboard/router'); describe('new_dashboard/dialogs/delete_items/view', function() { @@ -37,6 +38,52 @@ describe('new_dashboard/dialogs/delete_items/view', function() { expect(this.html).toContain('delete 2 datasets'); expect(this.html).toContain('them'); // the object pronoun of the sentence }); + + describe('and OK button is clicked', function() { + beforeEach(function() { + this.deferreds = []; + this.collection.each(function(m, i) { + this.deferreds[i] = $.Deferred(); + spyOn(m, 'destroy').and.returnValue(this.deferreds[i].promise()); + }, this); + + spyOn(this.view, 'hide'); + + this.view.$('.js-ok').click(); + }); + + it('should destroy selected items', function() { + expect(this.collection.at(0).destroy).toHaveBeenCalled(); + expect(this.collection.at(2).destroy).toHaveBeenCalled(); + }); + + it('should not remove items from collection until DELETE response comes back successfully', function() { + expect(this.collection.at(0).destroy).toHaveBeenCalledWith(jasmine.objectContaining({ wait: true })); + expect(this.collection.at(2).destroy).toHaveBeenCalledWith(jasmine.objectContaining({ wait: true })); + }); + + it('should NOT destroy unselected items', function() { + expect(this.collection.at(1).destroy).not.toHaveBeenCalled(); + }); + + it('should hide dialog but not until all items deleted', function() { + // Still one pending after 1st resolve + this.deferreds[0].resolve(); + expect(this.view.hide).not.toHaveBeenCalled(); + + // 2nd resolve, all should be done + this.deferreds[2].resolve(); + expect(this.view.hide).toHaveBeenCalled(); + }); + + it('should TBD if any item cannot be deleted', function() { + // 1st fails, so even if 2nd resolves should not hide view + // TODO: How should errors be handled? + this.deferreds[0].fail(); + this.deferreds[2].resolve(); + expect(this.view.hide).not.toHaveBeenCalled(); + }); + }); }); afterEach(function() { diff --git a/package.json b/package.json index bab7b38974..02c6b8d1a5 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "load-grunt-tasks": "~0.2.0", "moment": "^2.8.4", "open": "0.0.5", + "queue-async": "^1.0.7", "remapify": "^1.3.0", "rewireify": "0.0.13", "shelljs": "~0.2.6",