From fad4cba5e20b3273c2adc80b7a7c5c9fa57ed720 Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Wed, 17 Jun 2015 12:51:03 +0200 Subject: Improve card and list sortable drag Use a custom build of jquery-ui with only the plugins we need (instead of including everything). Fix a tricky bug of conflict between Blaze reactive updates and jquery-ui (which caused cards to sometimes disappear). --- .meteor/packages | 2 +- .meteor/versions | 2 +- client/components/boards/boardBody.js | 2 +- client/components/boards/boardBody.styl | 1 - client/components/cards/minicard.styl | 1 + client/components/lists/body.jade | 2 +- client/components/lists/main.js | 34 ++++++++++++++++++++------------- 7 files changed, 26 insertions(+), 18 deletions(-) diff --git a/.meteor/packages b/.meteor/packages index 87d21d6c..dd4f409e 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -47,9 +47,9 @@ underscore # UI components fortawesome:fontawesome -linto:jquery-ui mousetrap:mousetrap mquandalle:jquery-textcomplete +mquandalle:jquery-ui-drag-drop-sort peerlibrary:blaze-components perak:markdown reactive-var diff --git a/.meteor/versions b/.meteor/versions index 5fe0a841..58e12ef6 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -56,7 +56,6 @@ json@1.0.3 kenton:accounts-sandstorm@0.1.3 launch-screen@1.0.2 less@1.0.14 -linto:jquery-ui@1.11.2 livedata@1.0.13 localstorage@1.0.3 logging@1.0.7 @@ -76,6 +75,7 @@ mquandalle:bower@1.4.1 mquandalle:jade@0.4.3 mquandalle:jade-compiler@0.4.3 mquandalle:jquery-textcomplete@0.3.9_1 +mquandalle:jquery-ui-drag-drop-sort@0.1.0 mquandalle:moment@1.0.0 mquandalle:stylus@1.1.1 npm-bcrypt@0.7.8_2 diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 1fd64cbc..876f2cd0 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -72,10 +72,10 @@ BlazeComponent.extendComponent({ self.$(lists).sortable({ tolerance: 'pointer', - appendTo: '.js-lists', helper: 'clone', items: '.js-list:not(.js-list-composer)', placeholder: 'list placeholder', + distance: 7, start: function(evt, ui) { ui.placeholder.height(ui.helper.height()); Popup.close(); diff --git a/client/components/boards/boardBody.styl b/client/components/boards/boardBody.styl index 8ce478c3..b0649f45 100644 --- a/client/components/boards/boardBody.styl +++ b/client/components/boards/boardBody.styl @@ -31,7 +31,6 @@ position() &.is-dragging-active - .list-composer, .open-minicard-composer, .minicard-wrapper.is-checked display: none diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl index 4864a1bb..1cf87beb 100644 --- a/client/components/cards/minicard.styl +++ b/client/components/cards/minicard.styl @@ -14,6 +14,7 @@ border-radius: 2px &.ui-sortable-helper + cursor: grabbing transform: rotate(4deg) display: block !important diff --git a/client/components/lists/body.jade b/client/components/lists/body.jade index 60e7a57e..b0a374ea 100644 --- a/client/components/lists/body.jade +++ b/client/components/lists/body.jade @@ -16,7 +16,7 @@ template(name="listBody") +inlinedForm(autoclose=false position="bottom") +addCardForm(listId=_id position="bottom") else - a.open-minicard-composer.js-open-inlined-form + a.open-minicard-composer.js-card-composer.js-open-inlined-form i.fa.fa-plus | {{_ 'add-card'}} diff --git a/client/components/lists/main.js b/client/components/lists/main.js index c7f3f5e8..c4e38c46 100644 --- a/client/components/lists/main.js +++ b/client/components/lists/main.js @@ -12,25 +12,25 @@ BlazeComponent.extendComponent({ this.newCardFormIsVisible = new ReactiveVar(true); }, - // XXX The jQuery UI sortable plugin is far from ideal here. First we include - // all jQuery components but only use one. Second, it modifies the DOM itself, - // resulting in Blaze abandoning reactive update of the nodes that have been - // moved which result in bugs if multiple users use the board in real time. I - // tried sortable:sortable but that was not better. And dragula is not - // powerful enough for our use casesShould we “simply” write the drag&drop - // code ourselves? + // The jquery UI sortable library is the best solution I've found so far. I + // tried sortable and dragula but they were not powerful enough four our use + // case. I also considered writing/forking a drag-and-drop + sortable library + // but it's probably too much work. + // By calling asking the sortable library to cancel its move on the `stop` + // callback, we basically solve all issues related to reactive updates. A + // comment below provides further details. onRendered: function() { var self = this; if (! Meteor.userId() || ! Meteor.user().isBoardMember()) return; var boardComponent = self.componentParent(); - var itemsSelector = '.js-minicard:not(.placeholder, .js-composer)'; + var itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)'; var $cards = self.$('.js-minicards'); $cards.sortable({ connectWith: '.js-minicards', tolerance: 'pointer', - appendTo: '.js-lists', + appendTo: '#surface', helper: function(evt, item) { var helper = item.clone(); if (MultiSelection.isActive()) { @@ -46,7 +46,9 @@ BlazeComponent.extendComponent({ } return helper; }, + distance: 7, items: itemsSelector, + scroll: false, placeholder: 'minicard-wrapper placeholder', start: function(evt, ui) { ui.placeholder.height(ui.helper.height()); @@ -54,7 +56,7 @@ BlazeComponent.extendComponent({ boardComponent.setIsDragging(true); }, stop: function(evt, ui) { - // To attribute the new index number, we need to get the dom element + // To attribute the new index number, we need to get the DOM element // of the previous and the following card -- if any. var prevCardDom = ui.item.prev('.js-minicard').get(0); var nextCardDom = ui.item.next('.js-minicard').get(0); @@ -62,6 +64,15 @@ BlazeComponent.extendComponent({ var sortIndex = Utils.calculateIndex(prevCardDom, nextCardDom, nCards); var listId = Blaze.getData(ui.item.parents('.list').get(0))._id; + // Normally the jquery-ui sortable library moves the dragged DOM element + // to its new position, which disrupts Blaze reactive updates mechanism + // (especially when we move the last card of a list, or when multiple + // users move some cards at the same time). To prevent these UX glitches + // we ask sortable to gracefully cancel the move, and to put back the + // DOM in its initial state. The card move is then handled reactively by + // Blaze with the below query. + $cards.sortable('cancel'); + if (MultiSelection.isActive()) { Cards.find(MultiSelection.getMongoSelector()).forEach(function(c, i) { Cards.update(c._id, { @@ -77,9 +88,6 @@ BlazeComponent.extendComponent({ Cards.update(cardId, { $set: { listId: listId, - // XXX Using the same sort index for multiple cards is - // unacceptable. Keep that only until we figure out if we want to - // refactor the whole sorting mecanism or do something more basic. sort: sortIndex.base } }); -- cgit v1.2.3-1-g7c22