diff --git a/Backbone.CollectionBinder.js b/Backbone.CollectionBinder.js index d53d5ef..395f25d 100644 --- a/Backbone.CollectionBinder.js +++ b/Backbone.CollectionBinder.js @@ -160,18 +160,94 @@ }, sortRootEls: function(){ - this._collection.each(function(model, modelIndex){ + // sorting an empty collection or a collection with a single element won't change anything + if (this._collection.length < 2) return; + + // Algorithm: one by one take elements and put to the correct position in DOM + // Computational complexity: O(n^2) + // DOM operations: + // - $.children() calls: 1 + // - $.before/after calls: n + + + // ----- + // prepare data for sorting + + var children = $(this._elManagerFactory._getParentEl()).children(); + + var order = 0; + + var getElData = function (model) { var modelElManager = this.getManagerForModel(model); - if(modelElManager){ - var modelEl = modelElManager.getEl(); - var currentRootEls = $(this._elManagerFactory._getParentEl()).children(); + if (!modelElManager) return; - if(currentRootEls[modelIndex] !== modelEl[0]){ - modelEl.detach(); - modelEl.insertBefore(currentRootEls[modelIndex]); - } + var modelEl = modelElManager.getEl(); + if (!modelEl) return; + + return { + el: modelEl, + elIndex: children.index(modelEl), + order: order++ } - }, this); + }; + + var elements = _.chain(this._collection.models) + .map(getElData, this) + .compact() + .value(); + + var minElIndex = _.min(elements, function (e) { return e.elIndex; }).elIndex; + var maxElIndex = _.max(elements, function (e) { return e.elIndex; }).elIndex; + + // ----- + // define some helpers + + var findElByOrder = function (order) { + return _.findWhere(elements, {order: order}); + }; + + var findElByElIndex = function (elIndex) { + return _.findWhere(elements, {elIndex: elIndex}); + }; + + var moveEl = function (el, oldIndex, newIndex) { + var moveForward = newIndex > oldIndex; + + if (newIndex < maxElIndex) { + var nextEl = findElByElIndex(newIndex + (moveForward ? 1 : 0)); + nextEl.el.before(el); + } else { + var lastEl = findElByElIndex(newIndex); + lastEl.el.after(el); + } + }; + + var updateIndexesAfterMove = function (oldIndex, newIndex) { + var moveForward = newIndex > oldIndex; + + // update indexes of all elements that were shifted after the current element movement + _.each(elements, function (e) { + var elIndex = e.elIndex; + if (moveForward && elIndex > oldIndex && elIndex <= newIndex) e.elIndex--; + if (!moveForward && elIndex >= newIndex && elIndex < oldIndex) e.elIndex++; + }); + }; + + // ----- + // Actual sorting happens here + _.times(elements.length, function (order) { + var elementData = findElByOrder(order); + var oldIndex = elementData.elIndex; + + var newIndex = order + minElIndex; + + if (oldIndex === newIndex) return; + + moveEl(elementData.el, oldIndex, newIndex); + updateIndexesAfterMove(oldIndex, newIndex); + + elementData.elIndex = newIndex; + }); } }); diff --git a/spec/javascripts/CollectionBinder/sorting.spec.js b/spec/javascripts/CollectionBinder/sorting.spec.js index 9ae6ee2..de75f5d 100644 --- a/spec/javascripts/CollectionBinder/sorting.spec.js +++ b/spec/javascripts/CollectionBinder/sorting.spec.js @@ -50,6 +50,18 @@ describe("CollectionBinder: sorting", function(){ } }); + var ParentViewWithStaticContent = ParentViewAutoSort.extend({ + render: function () { + this.$el.append('