var getCardsBetween = function(idA, idB) { var pluckId = function(doc) { return doc._id; }; var getListsStrictlyBetween = function(id1, id2) { return Lists.find({ $and: [ { sort: { $gt: Lists.findOne(id1).sort } }, { sort: { $lt: Lists.findOne(id2).sort } } ], archived: false }).map(pluckId); }; var cards = _.sortBy([Cards.findOne(idA), Cards.findOne(idB)], function(c) { return c.sort; }); var selector; if (cards[0].listId === cards[1].listId) { selector = { listId: cards[0].listId, sort: { $gte: cards[0].sort, $lte: cards[1].sort }, archived: false }; } else { selector = { $or: [{ listId: cards[0].listId, sort: { $lte: cards[0].sort } }, { listId: { $in: getListsStrictlyBetween(cards[0].listId, cards[1].listId) } }, { listId: cards[1].listId, sort: { $gte: cards[1].sort } }], archived: false }; } return Cards.find(Filter.mongoSelector(selector)).map(pluckId); }; MultiSelection = { sidebarView: 'multiselection', _selectedCards: new ReactiveVar([]), _isActive: new ReactiveVar(false), startRangeCardId: null, reset: function() { this._selectedCards.set([]); }, getMongoSelector: function() { return Filter.mongoSelector({ _id: { $in: this._selectedCards.get() } }); }, isActive: function() { return this._isActive.get(); }, count: function() { return Cards.find(this.getMongoSelector()).count(); }, isEmpty: function() { return this.count() === 0; }, activate: function() { if (! this.isActive()) { EscapeActions.executeUpTo('detailsPane'); this._isActive.set(true); Tracker.flush(); } Sidebar.setView(this.sidebarView); }, disable: function() { if (this.isActive()) { this._isActive.set(false); if (Sidebar && Sidebar.getView() === this.sidebarView) { Sidebar.setView(); } this.reset(); } }, add: function(cardIds) { return this.toogle(cardIds, { add: true, remove: false }); }, remove: function(cardIds) { return this.toogle(cardIds, { add: false, remove: true }); }, toogleRange: function(cardId) { var selectedCards = this._selectedCards.get(); var startRange; this.reset(); if (! this.isActive() || selectedCards.length === 0) { this.toogle(cardId); } else { startRange = selectedCards[selectedCards.length - 1]; this.toogle(getCardsBetween(startRange, cardId)); } }, toogle: function(cardIds, options) { var self = this; cardIds = _.isString(cardIds) ? [cardIds] : cardIds; options = _.extend({ add: true, remove: true }, options || {}); if (! self.isActive()) { self.reset(); self.activate(); } var selectedCards = self._selectedCards.get(); _.each(cardIds, function(cardId) { var indexOfCard = selectedCards.indexOf(cardId); if (options.remove && indexOfCard > -1) selectedCards.splice(indexOfCard, 1); else if (options.add) selectedCards.push(cardId); }); self._selectedCards.set(selectedCards); }, isSelected: function(cardId) { return this._selectedCards.get().indexOf(cardId) > -1; } }; Blaze.registerHelper('MultiSelection', MultiSelection); EscapeActions.register('multiselection', function() { MultiSelection.disable(); }, function() { return MultiSelection.isActive(); }, { noClickEscapeOn: '.js-minicard,.js-board-sidebar-content' } );