checklists.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. function initSorting(items) {
  2. items.sortable({
  3. tolerance: 'pointer',
  4. helper: 'clone',
  5. items: '.js-checklist-item:not(.placeholder)',
  6. axis: 'y',
  7. distance: 7,
  8. placeholder: 'placeholder',
  9. scroll: false,
  10. start(evt, ui) {
  11. ui.placeholder.height(ui.helper.height());
  12. EscapeActions.executeUpTo('popup-close');
  13. },
  14. stop(evt, ui) {
  15. const parent = ui.item.parents('.js-checklist-items');
  16. const orderedItems = [];
  17. parent.find('.js-checklist-item').each(function(i, item) {
  18. const checklistItem = Blaze.getData(item).item;
  19. orderedItems.push(checklistItem._id);
  20. });
  21. items.sortable('cancel');
  22. const formerParent = ui.item.parents('.js-checklist-items');
  23. const checklist = Blaze.getData(parent.get(0)).checklist;
  24. const oldChecklist = Blaze.getData(formerParent.get(0)).checklist;
  25. if (oldChecklist._id !== checklist._id) {
  26. const currentItem = Blaze.getData(ui.item.get(0)).item;
  27. for (let i = 0; i < orderedItems.length; i++) {
  28. const itemId = orderedItems[i];
  29. if (itemId !== currentItem._id) continue;
  30. const newItem = {
  31. _id: checklist.getNewItemId(),
  32. title: currentItem.title,
  33. sort: i,
  34. isFinished: currentItem.isFinished,
  35. };
  36. checklist.addFullItem(newItem);
  37. orderedItems[i] = currentItem._id;
  38. oldChecklist.removeItem(itemId);
  39. }
  40. } else {
  41. checklist.sortItems(orderedItems);
  42. }
  43. },
  44. });
  45. }
  46. Template.checklists.onRendered(function () {
  47. const self = BlazeComponent.getComponentForElement(this.firstNode);
  48. self.itemsDom = this.$('.card-checklist-items');
  49. initSorting(self.itemsDom);
  50. self.itemsDom.mousedown(function(evt) {
  51. evt.stopPropagation();
  52. });
  53. function userIsMember() {
  54. return Meteor.user() && Meteor.user().isBoardMember();
  55. }
  56. // Disable sorting if the current user is not a board member
  57. self.autorun(() => {
  58. const $itemsDom = $(self.itemsDom);
  59. if ($itemsDom.data('sortable')) {
  60. $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
  61. }
  62. });
  63. });
  64. BlazeComponent.extendComponent({
  65. addChecklist(event) {
  66. event.preventDefault();
  67. const textarea = this.find('textarea.js-add-checklist-item');
  68. const title = textarea.value.trim();
  69. const cardId = this.currentData().cardId;
  70. const card = Cards.findOne(cardId);
  71. if (title) {
  72. Checklists.insert({
  73. cardId,
  74. title,
  75. sort: card.checklists().count(),
  76. });
  77. setTimeout(() => {
  78. this.$('.add-checklist-item').last().click();
  79. }, 100);
  80. }
  81. textarea.value = '';
  82. textarea.focus();
  83. },
  84. addChecklistItem(event) {
  85. event.preventDefault();
  86. const textarea = this.find('textarea.js-add-checklist-item');
  87. const title = textarea.value.trim();
  88. const checklist = this.currentData().checklist;
  89. if (title) {
  90. checklist.addItem(title);
  91. }
  92. // We keep the form opened, empty it.
  93. textarea.value = '';
  94. textarea.focus();
  95. },
  96. canModifyCard() {
  97. return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  98. },
  99. deleteChecklist() {
  100. const checklist = this.currentData().checklist;
  101. if (checklist && checklist._id) {
  102. Checklists.remove(checklist._id);
  103. this.toggleDeleteDialog.set(false);
  104. }
  105. },
  106. deleteDialog() {
  107. return this.toggleDeleteDialog.get();
  108. },
  109. deleteItem() {
  110. const checklist = this.currentData().checklist;
  111. const item = this.currentData().item;
  112. if (checklist && item && item._id) {
  113. checklist.removeItem(item._id);
  114. }
  115. },
  116. editChecklist(event) {
  117. event.preventDefault();
  118. const textarea = this.find('textarea.js-edit-checklist-item');
  119. const title = textarea.value.trim();
  120. const checklist = this.currentData().checklist;
  121. checklist.setTitle(title);
  122. },
  123. editChecklistItem(event) {
  124. event.preventDefault();
  125. const textarea = this.find('textarea.js-edit-checklist-item');
  126. const title = textarea.value.trim();
  127. const itemId = this.currentData().item._id;
  128. const checklist = this.currentData().checklist;
  129. checklist.editItem(itemId, title);
  130. },
  131. onCreated() {
  132. this.toggleDeleteDialog = new ReactiveVar(false);
  133. this.checklistToDelete = null; //Store data context to pass to checklistDeleteDialog template
  134. },
  135. pressKey(event) {
  136. //If user press enter key inside a form, submit it, so user doesn't have to leave keyboard to submit a form.
  137. if (event.keyCode === 13) {
  138. event.preventDefault();
  139. const $form = $(event.currentTarget).closest('form');
  140. $form.find('button[type=submit]').click();
  141. }
  142. },
  143. events() {
  144. const events = {
  145. 'click .toggle-delete-checklist-dialog'(event) {
  146. if($(event.target).hasClass('js-delete-checklist')){
  147. this.checklistToDelete = this.currentData().checklist; //Store data context
  148. }
  149. this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
  150. },
  151. };
  152. return [{
  153. ...events,
  154. 'submit .js-add-checklist': this.addChecklist,
  155. 'submit .js-edit-checklist-title': this.editChecklist,
  156. 'submit .js-add-checklist-item': this.addChecklistItem,
  157. 'submit .js-edit-checklist-item': this.editChecklistItem,
  158. 'click .js-delete-checklist-item': this.deleteItem,
  159. 'click .confirm-checklist-delete': this.deleteChecklist,
  160. keydown: this.pressKey,
  161. }];
  162. },
  163. }).register('checklists');
  164. Template.checklistDeleteDialog.onCreated(() => {
  165. const $cardDetails = this.$('.card-details');
  166. this.scrollState = { position: $cardDetails.scrollTop(), //save current scroll position
  167. top: false //required for smooth scroll animation
  168. };
  169. //Callback's purpose is to only prevent scrolling after animation is complete
  170. $cardDetails.animate({ scrollTop: 0 }, 500, () => { this.scrollState.top = true; });
  171. //Prevent scrolling while dialog is open
  172. $cardDetails.on('scroll', () => {
  173. if(this.scrollState.top) { //If it's already in position, keep it there. Otherwise let animation scroll
  174. $cardDetails.scrollTop(0);
  175. }
  176. });
  177. });
  178. Template.checklistDeleteDialog.onDestroyed(() => {
  179. const $cardDetails = this.$('.card-details');
  180. $cardDetails.off('scroll'); //Reactivate scrolling
  181. $cardDetails.animate( { scrollTop: this.scrollState.position });
  182. });
  183. BlazeComponent.extendComponent({
  184. events() {
  185. const handlers = {
  186. 'click .confirm-checklist-delete'() {
  187. console.log(this.scrollState)
  188. },
  189. }
  190. return [ handlers ];
  191. }
  192. }).register('checklistDeleteDialog');
  193. Template.itemDetail.helpers({
  194. canModifyCard() {
  195. return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  196. },
  197. });
  198. BlazeComponent.extendComponent({
  199. toggleItem() {
  200. const checklist = this.currentData().checklist;
  201. const item = this.currentData().item;
  202. if (checklist && item && item._id) {
  203. checklist.toggleItem(item._id);
  204. }
  205. },
  206. events() {
  207. return [{
  208. 'click .item .check-box': this.toggleItem,
  209. }];
  210. },
  211. }).register('itemDetail');