function initSorting(items) { items.sortable({ tolerance: 'pointer', helper: 'clone', items: '.js-checklist-item:not(.placeholder)', axis: 'y', distance: 7, placeholder: 'placeholder', scroll: false, start(evt, ui) { ui.placeholder.height(ui.helper.height()); EscapeActions.executeUpTo('popup-close'); }, stop(evt, ui) { const parent = ui.item.parents('.js-checklist-items'); const orderedItems = []; parent.find('.js-checklist-item').each(function(i, item) { const checklistItem = Blaze.getData(item).item; orderedItems.push(checklistItem._id); }); items.sortable('cancel'); const formerParent = ui.item.parents('.js-checklist-items'); const checklist = Blaze.getData(parent.get(0)).checklist; const oldChecklist = Blaze.getData(formerParent.get(0)).checklist; if (oldChecklist._id !== checklist._id) { const currentItem = Blaze.getData(ui.item.get(0)).item; for (let i = 0; i < orderedItems.length; i++) { const itemId = orderedItems[i]; if (itemId !== currentItem._id) continue; const newItem = { _id: checklist.getNewItemId(), title: currentItem.title, sort: i, isFinished: currentItem.isFinished, }; checklist.addFullItem(newItem); orderedItems[i] = currentItem._id; oldChecklist.removeItem(itemId); } } else { checklist.sortItems(orderedItems); } }, }); } Template.checklists.onRendered(function () { const self = BlazeComponent.getComponentForElement(this.firstNode); self.itemsDom = this.$('.card-checklist-items'); initSorting(self.itemsDom); self.itemsDom.mousedown(function(evt) { evt.stopPropagation(); }); function userIsMember() { return Meteor.user() && Meteor.user().isBoardMember(); } // Disable sorting if the current user is not a board member self.autorun(() => { const $itemsDom = $(self.itemsDom); if ($itemsDom.data('sortable')) { $(self.itemsDom).sortable('option', 'disabled', !userIsMember()); } }); }); BlazeComponent.extendComponent({ addChecklist(event) { event.preventDefault(); const textarea = this.find('textarea.js-add-checklist-item'); const title = textarea.value.trim(); const cardId = this.currentData().cardId; const card = Cards.findOne(cardId); if (title) { Checklists.insert({ cardId, title, sort: card.checklists().count(), }); setTimeout(() => { this.$('.add-checklist-item').last().click(); }, 100); } textarea.value = ''; textarea.focus(); }, addChecklistItem(event) { event.preventDefault(); const textarea = this.find('textarea.js-add-checklist-item'); const title = textarea.value.trim(); const checklist = this.currentData().checklist; if (title) { checklist.addItem(title); } // We keep the form opened, empty it. textarea.value = ''; textarea.focus(); }, canModifyCard() { return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); }, deleteChecklist() { const checklist = this.currentData().checklist; if (checklist && checklist._id) { Checklists.remove(checklist._id); this.toggleDeleteDialog.set(false); } }, deleteItem() { const checklist = this.currentData().checklist; const item = this.currentData().item; if (checklist && item && item._id) { checklist.removeItem(item._id); } }, editChecklist(event) { event.preventDefault(); const textarea = this.find('textarea.js-edit-checklist-item'); const title = textarea.value.trim(); const checklist = this.currentData().checklist; checklist.setTitle(title); }, editChecklistItem(event) { event.preventDefault(); const textarea = this.find('textarea.js-edit-checklist-item'); const title = textarea.value.trim(); const itemId = this.currentData().item._id; const checklist = this.currentData().checklist; checklist.editItem(itemId, title); }, onCreated() { this.toggleDeleteDialog = new ReactiveVar(false); this.checklistToDelete = null; //Store data context to pass to checklistDeleteDialog template }, pressKey(event) { //If user press enter key inside a form, submit it //Unless the user is also holding down the 'shift' key if (event.keyCode === 13 && !event.shiftKey) { event.preventDefault(); const $form = $(event.currentTarget).closest('form'); $form.find('button[type=submit]').click(); } }, events() { const events = { 'click .toggle-delete-checklist-dialog'(event) { if($(event.target).hasClass('js-delete-checklist')){ this.checklistToDelete = this.currentData().checklist; //Store data context } this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get()); }, }; return [{ ...events, 'submit .js-add-checklist': this.addChecklist, 'submit .js-edit-checklist-title': this.editChecklist, 'submit .js-add-checklist-item': this.addChecklistItem, 'submit .js-edit-checklist-item': this.editChecklistItem, 'click .js-delete-checklist-item': this.deleteItem, 'click .confirm-checklist-delete': this.deleteChecklist, keydown: this.pressKey, }]; }, }).register('checklists'); Template.checklistDeleteDialog.onCreated(() => { const $cardDetails = this.$('.card-details'); this.scrollState = { position: $cardDetails.scrollTop(), //save current scroll position top: false, //required for smooth scroll animation }; //Callback's purpose is to only prevent scrolling after animation is complete $cardDetails.animate({ scrollTop: 0 }, 500, () => { this.scrollState.top = true; }); //Prevent scrolling while dialog is open $cardDetails.on('scroll', () => { if(this.scrollState.top) { //If it's already in position, keep it there. Otherwise let animation scroll $cardDetails.scrollTop(0); } }); }); Template.checklistDeleteDialog.onDestroyed(() => { const $cardDetails = this.$('.card-details'); $cardDetails.off('scroll'); //Reactivate scrolling $cardDetails.animate( { scrollTop: this.scrollState.position }); }); Template.itemDetail.helpers({ canModifyCard() { return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); }, }); BlazeComponent.extendComponent({ toggleItem() { const checklist = this.currentData().checklist; const item = this.currentData().item; if (checklist && item && item._id) { checklist.toggleItem(item._id); } }, events() { return [{ 'click .item .check-box': this.toggleItem, }]; }, }).register('itemDetail');