From 3d2cea8b72a60f9dd70516311a939b15782bd7d6 Mon Sep 17 00:00:00 2001 From: Manuele Sarfatti Date: Thu, 21 Jun 2012 17:59:43 +0200 Subject: [PATCH 01/13] Update README.md --- README.md | 42 +++- jquery.mjs.nestedSortable.js | 426 +++++++++++++++++++++++++++++++++++ 2 files changed, 456 insertions(+), 12 deletions(-) create mode 100644 jquery.mjs.nestedSortable.js diff --git a/README.md b/README.md index 30fd192..48ef9d6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -### NOTE: Please use the develop branch - -Until further notice, the develop branch is to be considered the most stable and active one. I will only accept pull requests made on the develp branch. - # nestedSortable jQuery plugin *nestedSortable* is a jQuery plugin that extends jQuery Sortable UI functionalities to nested lists. @@ -12,8 +8,8 @@ Until further notice, the develop branch is to be considered the most stable and - Items can be sorted in their own list, moved across the tree, or nested under other items. - Sublists are created and deleted on the fly - All jQuery Sortable options, events and methods are available -- It is possible to define elements that will not accept a new nested item/list -- It is possible to define a maximum depth for nested items +- It is possible to define elements that will not accept a new nested item/list and a maximum depth for nested items +- The root level can be protected ## Usage @@ -31,21 +27,43 @@ Until further notice, the develop branch is to be considered the most stable and ``` +``` + $(document).ready(function(){ + + $('.sortable').nestedSortable({ + handle: 'div', + items: 'li', + toleranceElement: '> div' + }); + + }); +``` + Please note: every `
  • ` must have either one or two direct children, the first one being a container element (such as `
    ` in the above example), and the (optional) second one being the nested list. The container element has to be set as the 'toleranceElement' in the options, and this, or one of its children, as the 'handle'. +Also, the default list type is `
      `. + ## Custom Options
      tabSize
      How far right or left (in pixels) the item has to travel in order to be nested or to be sent outside its current list. Default: 20
      -
      disableNesting
      +
      disableNesting
      The class name of the items that will not accept nested lists. Default: ui-nestedSortable-no-nesting
      -
      errorClass
      +
      errorClass
      The class given to the placeholder in case of error. Default: ui-nestedSortable-error
      -
      listType
      +
      listType
      The list type used (ordered or unordered). Default: ol
      -
      maxLevels
      +
      maxLevels
      The maximum depth of nested items the list can accept. If set to '0' the levels are unlimited. Default: 0
      +
      protectRoot
      +
      Wether to protect the root level (i.e. root items can be sorted but not nested, sub-items cannot become root items). Default: false
      +
      rootID
      +
      The id given to the root element (set this to whatever suits your data structure). Default: null
      +
      rtl
      +
      Set this to true if you have a right-to-left page. Default: false
      +
      isAllowed (function)
      +
      You can specify a custom function to verify if a drop location is allowed. Default: function(parent, item) { return true; }
      ## Custom Methods @@ -98,7 +116,7 @@ Tested with: IE 6/7/8, Firefox 3.6/4, Chrome, Safari 3 ## License -This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License. +This work is licensed under the MIT License. -This work is *pizzaware*. If it saved your life, or you just feel good at heart, please consider offering me a pizza. This can be done in two ways: (1) use the Paypal button at the bottom of the project's [home page](http://mjsarfatti.com/sandbox/nestedSortable); (2) send me cash via traditional mail to my home address in Italy. Is the second method legal? It is in Italy if you use Posta assicurata. You should check with your local laws if you live elsewhere. +This work is *pizzaware*. If it saved your life, or you just feel good at heart, please consider offering me a pizza. This can be done in two ways: (1) follow [this link](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=RSJEW3N9PRMYY&lc=IT&item_name=Manuele%20Sarfatti¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) to donate through paypal; (2) send me cash via traditional mail to my home address in Italy. Is the second method legal? It is in Italy if you use Posta assicurata. You should check with your local laws if you live elsewhere. \ No newline at end of file diff --git a/jquery.mjs.nestedSortable.js b/jquery.mjs.nestedSortable.js new file mode 100644 index 0000000..019aa09 --- /dev/null +++ b/jquery.mjs.nestedSortable.js @@ -0,0 +1,426 @@ +/* + * jQuery UI Nested Sortable + * v 1.3.5 / 21 jun 2012 + * http://mjsarfatti.com/code/nestedSortable + * + * Depends on: + * jquery.ui.sortable.js 1.8+ + * + * Copyright (c) 2010-2012 Manuele J Sarfatti + * Licensed under the MIT License + * http://www.opensource.org/licenses/mit-license.php + */ + +(function($) { + + $.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, { + + options: { + tabSize: 20, + disableNesting: 'mjs-nestedSortable-no-nesting', + errorClass: 'mjs-nestedSortable-error', + listType: 'ol', + maxLevels: 0, + protectRoot: false, + rootID: null, + rtl: false, + isAllowed: function(item, parent) { return true; } + }, + + _create: function() { + this.element.data('sortable', this.element.data('nestedSortable')); + + if (!this.element.is(this.options.listType)) + throw new Error('nestedSortable: Please check the listType option is set to your actual list type'); + + return $.ui.sortable.prototype._create.apply(this, arguments); + }, + + destroy: function() { + this.element + .removeData("nestedSortable") + .unbind(".nestedSortable"); + return $.ui.sortable.prototype.destroy.apply(this, arguments); + }, + + _mouseDrag: function(event) { + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + if (!this.lastPositionAbs) { + this.lastPositionAbs = this.positionAbs; + } + + //Do scrolling + if(this.options.scroll) { + var o = this.options, scrolled = false; + if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') { + + if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; + else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; + + if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; + else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; + + } else { + + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) + $.ui.ddmanager.prepareOffsets(this, event); + } + + //Regenerate the absolute position used for position checks + this.positionAbs = this._convertPositionTo("absolute"); + + // Find the top offset before rearrangement, + var previousTopOffset = this.placeholder.offset().top; + + //Set the helper position + if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; + if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; + + //Rearrange + for (var i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); + if (!intersection) continue; + + if(itemElement != this.currentItem[0] //cannot intersect with itself + && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before + && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked + && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true) + //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container + ) { + + $(itemElement).mouseenter(); + + this.direction = intersection == 1 ? "down" : "up"; + + if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) { + $(itemElement).mouseleave(); + this._rearrange(event, item); + } else { + break; + } + + // Clear emtpy ul's/ol's + this._clearEmpty(itemElement); + + this._trigger("change", event, this._uiHash()); + break; + } + } + + var parentItem = (this.placeholder[0].parentNode.parentNode && + $(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length) + ? $(this.placeholder[0].parentNode.parentNode) + : null, + level = this._getLevel(this.placeholder), + childLevels = this._getChildLevels(this.helper); + + // To find the previous sibling in the list, keep backtracking until we hit a valid list item. + var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null; + if (previousItem != null) { + while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) { + if (previousItem[0].previousSibling) { + previousItem = $(previousItem[0].previousSibling); + } else { + previousItem = null; + break; + } + } + } + + // To find the next sibling in the list, keep stepping forward until we hit a valid list item. + var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null; + if (nextItem != null) { + while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) { + if (nextItem[0].nextSibling) { + nextItem = $(nextItem[0].nextSibling); + } else { + nextItem = null; + break; + } + } + } + + var newList = document.createElement(o.listType); + + this.beyondMaxLevels = 0; + + // If the item is moved to the left, send it to its parent's level unless there are siblings below it. + if (parentItem != null && nextItem == null && + (o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth()) || + !o.rtl && (this.positionAbs.left < parentItem.offset().left))) { + parentItem.after(this.placeholder[0]); + this._clearEmpty(parentItem[0]); + this._trigger("change", event, this._uiHash()); + } + // If the item is below a sibling and is moved to the right, make it a child of that sibling. + else if (previousItem != null && + (o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize) || + !o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))) { + this._isAllowed(previousItem, level, level+childLevels+1); + if (!previousItem.children(o.listType).length) { + previousItem[0].appendChild(newList); + } + // If this item is being moved from the top, add it to the top of the list. + if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) { + previousItem.children(o.listType).prepend(this.placeholder); + } + // Otherwise, add it to the bottom of the list. + else { + previousItem.children(o.listType)[0].appendChild(this.placeholder[0]); + } + this._trigger("change", event, this._uiHash()); + } + else { + this._isAllowed(parentItem, level, level+childLevels); + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); + + //Call callbacks + this._trigger('sort', event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function(event, noPropagation) { + + // If the item is in a position not allowed, send it back + if (this.beyondMaxLevels) { + + this.placeholder.removeClass(this.options.errorClass); + + if (this.domPosition.prev) { + $(this.domPosition.prev).after(this.placeholder); + } else { + $(this.domPosition.parent).prepend(this.placeholder); + } + + this._trigger("revert", event, this._uiHash()); + + } + + // Clean last empty ul/ol + for (var i = this.items.length - 1; i >= 0; i--) { + var item = this.items[i].item[0]; + this._clearEmpty(item); + } + + $.ui.sortable.prototype._mouseStop.apply(this, arguments); + + }, + + serialize: function(options) { + + var o = $.extend({}, this.options, options), + items = this._getItemsAsjQuery(o && o.connected), + str = []; + + $(items).each(function() { + var res = ($(o.item || this).attr(o.attribute || 'id') || '') + .match(o.expression || (/(.+)[-=_](.+)/)), + pid = ($(o.item || this).parent(o.listType) + .parent(o.items) + .attr(o.attribute || 'id') || '') + .match(o.expression || (/(.+)[-=_](.+)/)); + + if (res) { + str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']') + + '=' + + (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID)); + } + }); + + if(!str.length && o.key) { + str.push(o.key + '='); + } + + return str.join('&'); + + }, + + toHierarchy: function(options) { + + var o = $.extend({}, this.options, options), + sDepth = o.startDepthCount || 0, + ret = []; + + $(this.element).children(o.items).each(function () { + var level = _recursiveItems(this); + ret.push(level); + }); + + return ret; + + function _recursiveItems(item) { + var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/)); + if (id) { + var currentItem = {"id" : id[2]}; + if ($(item).children(o.listType).children(o.items).length > 0) { + currentItem.children = []; + $(item).children(o.listType).children(o.items).each(function() { + var level = _recursiveItems(this); + currentItem.children.push(level); + }); + } + return currentItem; + } + } + }, + + toArray: function(options) { + + var o = $.extend({}, this.options, options), + sDepth = o.startDepthCount || 0, + ret = [], + left = 2; + + ret.push({ + "item_id": o.rootID, + "parent_id": 'none', + "depth": sDepth, + "left": '1', + "right": ($(o.items, this.element).length + 1) * 2 + }); + + $(this.element).children(o.items).each(function () { + left = _recursiveArray(this, sDepth + 1, left); + }); + + ret = ret.sort(function(a,b){ return (a.left - b.left); }); + + return ret; + + function _recursiveArray(item, depth, left) { + + var right = left + 1, + id, + pid; + + if ($(item).children(o.listType).children(o.items).length > 0) { + depth ++; + $(item).children(o.listType).children(o.items).each(function () { + right = _recursiveArray($(this), depth, right); + }); + depth --; + } + + id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/)); + + if (depth === sDepth + 1) { + pid = o.rootID; + } else { + var parentItem = ($(item).parent(o.listType) + .parent(o.items) + .attr(o.attribute || 'id')) + .match(o.expression || (/(.+)[-=_](.+)/)); + pid = parentItem[2]; + } + + if (id) { + ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right}); + } + + left = right + 1; + return left; + } + + }, + + _clearEmpty: function(item) { + + var emptyList = $(item).children(this.options.listType); + if (emptyList.length && !emptyList.children().length) { + emptyList.remove(); + } + + }, + + _getLevel: function(item) { + + var level = 1; + + if (this.options.listType) { + var list = item.closest(this.options.listType); + while (list && list.length > 0 && + !list.is('.ui-sortable')) { + level++; + list = list.parent().closest(this.options.listType); + } + } + + return level; + }, + + _getChildLevels: function(parent, depth) { + var self = this, + o = this.options, + result = 0; + depth = depth || 0; + + $(parent).children(o.listType).children(o.items).each(function (index, child) { + result = Math.max(self._getChildLevels(child, depth + 1), result); + }); + + return depth ? result + 1 : result; + }, + + _isAllowed: function(parentItem, level, levels) { + var o = this.options, + isRoot = $(this.domPosition.parent).hasClass('ui-sortable') ? true : false, + maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list + + // Is the root protected? + // Are we trying to nest under a no-nest? + // Are we nesting too deep? + if (!o.isAllowed(parentItem, this.placeholder) || + parentItem && parentItem.hasClass(o.disableNesting) || + o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) { + this.placeholder.addClass(o.errorClass); + if (maxLevels < levels && maxLevels != 0) { + this.beyondMaxLevels = levels - maxLevels; + } else { + this.beyondMaxLevels = 1; + } + } else { + if (maxLevels < levels && maxLevels != 0) { + this.placeholder.addClass(o.errorClass); + this.beyondMaxLevels = levels - maxLevels; + } else { + this.placeholder.removeClass(o.errorClass); + this.beyondMaxLevels = 0; + } + } + } + + })); + + $.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options); +})(jQuery); \ No newline at end of file From 17c8d428841678b7d284d06a994697aea527c78e Mon Sep 17 00:00:00 2001 From: Manuele Sarfatti Date: Thu, 21 Jun 2012 18:07:25 +0200 Subject: [PATCH 02/13] Deleted old plugin file --- jquery.ui.nestedSortable.js | 391 ------------------------------------ 1 file changed, 391 deletions(-) delete mode 100644 jquery.ui.nestedSortable.js diff --git a/jquery.ui.nestedSortable.js b/jquery.ui.nestedSortable.js deleted file mode 100644 index aad83af..0000000 --- a/jquery.ui.nestedSortable.js +++ /dev/null @@ -1,391 +0,0 @@ -/* - * jQuery UI Nested Sortable - * v 1.3.4 / 28 apr 2011 - * http://mjsarfatti.com/sandbox/nestedSortable - * - * Depends: - * jquery.ui.sortable.js 1.8+ - * - * License CC BY-SA 3.0 - * Copyright 2010-2011, Manuele J Sarfatti - */ - -(function($) { - - $.widget("ui.nestedSortable", $.extend({}, $.ui.sortable.prototype, { - - options: { - tabSize: 20, - disableNesting: 'ui-nestedSortable-no-nesting', - errorClass: 'ui-nestedSortable-error', - listType: 'ol', - maxLevels: 0, - revertOnError: 1 - }, - - _create: function() { - this.element.data('sortable', this.element.data('nestedSortable')); - return $.ui.sortable.prototype._create.apply(this, arguments); - }, - - destroy: function() { - this.element - .removeData("nestedSortable") - .unbind(".nestedSortable"); - return $.ui.sortable.prototype.destroy.apply(this, arguments); - }, - - _mouseDrag: function(event) { - - //Compute the helpers position - this.position = this._generatePosition(event); - this.positionAbs = this._convertPositionTo("absolute"); - - if (!this.lastPositionAbs) { - this.lastPositionAbs = this.positionAbs; - } - - //Do scrolling - if(this.options.scroll) { - var o = this.options, scrolled = false; - if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') { - - if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; - else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; - - if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; - else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; - - } else { - - if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) - scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); - else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) - scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); - - if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) - scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); - else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) - scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); - - } - - if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) - $.ui.ddmanager.prepareOffsets(this, event); - } - - //Regenerate the absolute position used for position checks - this.positionAbs = this._convertPositionTo("absolute"); - - //Set the helper position - if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; - if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; - - //Rearrange - for (var i = this.items.length - 1; i >= 0; i--) { - - //Cache variables and intersection, continue if no intersection - var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); - if (!intersection) continue; - - if(itemElement != this.currentItem[0] //cannot intersect with itself - && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before - && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked - && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true) - //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container - ) { - - $(itemElement).mouseenter(); - - this.direction = intersection == 1 ? "down" : "up"; - - if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) { - $(itemElement).mouseleave(); - this._rearrange(event, item); - } else { - break; - } - - // Clear emtpy ul's/ol's - this._clearEmpty(itemElement); - - this._trigger("change", event, this._uiHash()); - break; - } - } - - var parentItem = (this.placeholder[0].parentNode.parentNode - && $(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length) - ? $(this.placeholder[0].parentNode.parentNode) - : null, - level = this._getLevel(this.placeholder), - childLevels = this._getChildLevels(this.helper), - previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null; - - if (previousItem != null) { - while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0]) { - if (previousItem[0].previousSibling) { - previousItem = $(previousItem[0].previousSibling); - } else { - previousItem = null; - break; - } - } - } - - newList = document.createElement(o.listType); - - this.beyondMaxLevels = 0; - - // If the item is moved to the left, send it to its parent level - if (parentItem != null && this.positionAbs.left < parentItem.offset().left) { - parentItem.after(this.placeholder[0]); - this._clearEmpty(parentItem[0]); - this._trigger("change", event, this._uiHash()); - } - // If the item is below another one and is moved to the right, make it a children of it - else if (previousItem != null && this.positionAbs.left > previousItem.offset().left + o.tabSize) { - this._isAllowed(previousItem, level+childLevels+1); - if (!previousItem.children(o.listType).length) { - previousItem[0].appendChild(newList); - } - previousItem.children(o.listType)[0].appendChild(this.placeholder[0]); - this._trigger("change", event, this._uiHash()); - } - else { - this._isAllowed(parentItem, level+childLevels); - } - - //Post events to containers - this._contactContainers(event); - - //Interconnect with droppables - if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); - - //Call callbacks - this._trigger('sort', event, this._uiHash()); - - this.lastPositionAbs = this.positionAbs; - return false; - - }, - - _mouseStop: function(event, noPropagation) { - - // If the item is in a position not allowed, send it back - if (this.beyondMaxLevels) { - - this.placeholder.removeClass(this.options.errorClass); - - if (this.options.revertOnError) { - if (this.domPosition.prev) { - $(this.domPosition.prev).after(this.placeholder); - } else { - $(this.domPosition.parent).prepend(this.placeholder); - } - this._trigger("revert", event, this._uiHash()); - } else { - var parent = this.placeholder.parent().closest(this.options.items); - - for (var i = this.beyondMaxLevels - 1; i > 0; i--) { - parent = parent.parent().closest(this.options.items); - } - - parent.after(this.placeholder); - this._trigger("change", event, this._uiHash()); - } - - } - - // Clean last empty ul/ol - for (var i = this.items.length - 1; i >= 0; i--) { - var item = this.items[i].item[0]; - this._clearEmpty(item); - } - - $.ui.sortable.prototype._mouseStop.apply(this, arguments); - - }, - - serialize: function(o) { - - var items = this._getItemsAsjQuery(o && o.connected), - str = []; o = o || {}; - - $(items).each(function() { - var res = ($(o.item || this).attr(o.attribute || 'id') || '') - .match(o.expression || (/(.+)[-=_](.+)/)), - pid = ($(o.item || this).parent(o.listType) - .parent('li') - .attr(o.attribute || 'id') || '') - .match(o.expression || (/(.+)[-=_](.+)/)); - - if (res) { - str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']') - + '=' - + (pid ? (o.key && o.expression ? pid[1] : pid[2]) : 'root')); - } - }); - - if(!str.length && o.key) { - str.push(o.key + '='); - } - - return str.join('&'); - - }, - - toHierarchy: function(o) { - - o = o || {}; - var sDepth = o.startDepthCount || 0, - ret = []; - - $(this.element).children('li').each(function () { - var level = _recursiveItems($(this)); - ret.push(level); - }); - - return ret; - - function _recursiveItems(li) { - var id = ($(li).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/)); - if (id) { - var item = {"id" : id[2]}; - if ($(li).children(o.listType).children('li').length > 0) { - item.children = []; - $(li).children(o.listType).children('li').each(function() { - var level = _recursiveItems($(this)); - item.children.push(level); - }); - } - return item; - } - } - }, - - toArray: function(o) { - - o = o || {}; - var sDepth = o.startDepthCount || 0, - ret = [], - left = 2; - - ret.push({ - "item_id": 'root', - "parent_id": 'none', - "depth": sDepth, - "left": '1', - "right": ($('li', this.element).length + 1) * 2 - }); - - $(this.element).children('li').each(function () { - left = _recursiveArray(this, sDepth + 1, left); - }); - - ret = ret.sort(function(a,b){ return (a.left - b.left); }); - - return ret; - - function _recursiveArray(item, depth, left) { - - var right = left + 1, - id, - pid; - - if ($(item).children(o.listType).children('li').length > 0) { - depth ++; - $(item).children(o.listType).children('li').each(function () { - right = _recursiveArray($(this), depth, right); - }); - depth --; - } - - id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/)); - - if (depth === sDepth + 1) { - pid = 'root'; - } else { - var parentItem = ($(item).parent(o.listType) - .parent('li') - .attr(o.attribute || 'id')) - .match(o.expression || (/(.+)[-=_](.+)/)); - pid = parentItem[2]; - } - - if (id) { - ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right}); - } - - left = right + 1; - return left; - } - - }, - - _clearEmpty: function(item) { - - var emptyList = $(item).children(this.options.listType); - if (emptyList.length && !emptyList.children().length) { - emptyList.remove(); - } - - }, - - _getLevel: function(item) { - - var level = 1; - - if (this.options.listType) { - var list = item.closest(this.options.listType); - while (!list.is('.ui-sortable')) { - level++; - list = list.parent().closest(this.options.listType); - } - } - - return level; - }, - - _getChildLevels: function(parent, depth) { - var self = this, - o = this.options, - result = 0; - depth = depth || 0; - - $(parent).children(o.listType).children(o.items).each(function (index, child) { - result = Math.max(self._getChildLevels(child, depth + 1), result); - }); - - return depth ? result + 1 : result; - }, - - _isAllowed: function(parentItem, levels) { - var o = this.options; - // Are we trying to nest under a no-nest or are we nesting too deep? - if (parentItem == null || !(parentItem.hasClass(o.disableNesting))) { - if (o.maxLevels < levels && o.maxLevels != 0) { - this.placeholder.addClass(o.errorClass); - this.beyondMaxLevels = levels - o.maxLevels; - } else { - this.placeholder.removeClass(o.errorClass); - this.beyondMaxLevels = 0; - } - } else { - this.placeholder.addClass(o.errorClass); - if (o.maxLevels < levels && o.maxLevels != 0) { - this.beyondMaxLevels = levels - o.maxLevels; - } else { - this.beyondMaxLevels = 1; - } - } - } - - })); - - $.ui.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.ui.nestedSortable.prototype.options); -})(jQuery); \ No newline at end of file From d3512032aa5f26e8fc22a10ddf1f9744dbf073ae Mon Sep 17 00:00:00 2001 From: Royce Feng Date: Thu, 5 Jul 2012 13:40:30 -0400 Subject: [PATCH 03/13] Allow a doNotClear field for lists that should not be cleared when empty --- jquery.mjs.nestedSortable.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jquery.mjs.nestedSortable.js b/jquery.mjs.nestedSortable.js index 019aa09..f347418 100644 --- a/jquery.mjs.nestedSortable.js +++ b/jquery.mjs.nestedSortable.js @@ -19,6 +19,7 @@ tabSize: 20, disableNesting: 'mjs-nestedSortable-no-nesting', errorClass: 'mjs-nestedSortable-error', + doNotClear: 'mjs-doNotClear', listType: 'ol', maxLevels: 0, protectRoot: false, @@ -357,7 +358,7 @@ _clearEmpty: function(item) { var emptyList = $(item).children(this.options.listType); - if (emptyList.length && !emptyList.children().length) { + if (emptyList.length && !emptyList.children().length && !emptyList.hasClass(this.options.doNotClear)) { emptyList.remove(); } @@ -423,4 +424,4 @@ })); $.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options); -})(jQuery); \ No newline at end of file +})(jQuery); From 317ddd62d9a8a230e3978783866a2700a1a7d1c7 Mon Sep 17 00:00:00 2001 From: Royce Feng Date: Fri, 13 Jul 2012 09:26:53 -0400 Subject: [PATCH 04/13] Change doNotClear to a flag instead of class --- jquery.mjs.nestedSortable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery.mjs.nestedSortable.js b/jquery.mjs.nestedSortable.js index f347418..1eaf979 100644 --- a/jquery.mjs.nestedSortable.js +++ b/jquery.mjs.nestedSortable.js @@ -19,7 +19,7 @@ tabSize: 20, disableNesting: 'mjs-nestedSortable-no-nesting', errorClass: 'mjs-nestedSortable-error', - doNotClear: 'mjs-doNotClear', + doNotClear: false, listType: 'ol', maxLevels: 0, protectRoot: false, @@ -358,7 +358,7 @@ _clearEmpty: function(item) { var emptyList = $(item).children(this.options.listType); - if (emptyList.length && !emptyList.children().length && !emptyList.hasClass(this.options.doNotClear)) { + if (emptyList.length && !emptyList.children().length && !this.options.doNotClear) { emptyList.remove(); } From ff99d424b530bb3c868ecab7af9ddb85841c8675 Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 8 Aug 2012 10:21:07 +0200 Subject: [PATCH 05/13] Corrected argument order for isAllowed function --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48ef9d6..6913825 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Also, the default list type is `
        `.
        rtl
        Set this to true if you have a right-to-left page. Default: false
        isAllowed (function)
        -
        You can specify a custom function to verify if a drop location is allowed. Default: function(parent, item) { return true; }
        +
        You can specify a custom function to verify if a drop location is allowed. Default: function(item, parent) { return true; }
        ## Custom Methods From c1d6a863a66636c5b27c9abe25178dca62443ba7 Mon Sep 17 00:00:00 2001 From: Manuele J Sarfatti Date: Thu, 16 Aug 2012 17:51:18 +0300 Subject: [PATCH 06/13] fix isAllowed custom function and readme --- jquery.mjs.nestedSortable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.mjs.nestedSortable.js b/jquery.mjs.nestedSortable.js index 1eaf979..33dcf01 100644 --- a/jquery.mjs.nestedSortable.js +++ b/jquery.mjs.nestedSortable.js @@ -401,7 +401,7 @@ // Is the root protected? // Are we trying to nest under a no-nest? // Are we nesting too deep? - if (!o.isAllowed(parentItem, this.placeholder) || + if (!o.isAllowed(this.placeholder, parentItem) || parentItem && parentItem.hasClass(o.disableNesting) || o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) { this.placeholder.addClass(o.errorClass); From 552453c2a78aba614ac8cb47002fc7bd30236600 Mon Sep 17 00:00:00 2001 From: Manuele J Sarfatti Date: Fri, 7 Sep 2012 12:46:21 +0300 Subject: [PATCH 07/13] Fix Issue #51 --- jquery.mjs.nestedSortable.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jquery.mjs.nestedSortable.js b/jquery.mjs.nestedSortable.js index 33dcf01..8eaf402 100644 --- a/jquery.mjs.nestedSortable.js +++ b/jquery.mjs.nestedSortable.js @@ -54,9 +54,11 @@ this.lastPositionAbs = this.positionAbs; } + var o = this.options; + //Do scrolling if(this.options.scroll) { - var o = this.options, scrolled = false; + var scrolled = false; if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') { if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) From 28fc2a03db30f1c3ff5aabf92099cb5417c9c2c7 Mon Sep 17 00:00:00 2001 From: vcarel Date: Tue, 11 Sep 2012 13:25:06 +0300 Subject: [PATCH 08/13] Fix call to isAllowed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Referring to the discussion about commit c1d6a86, isAllowed should be called with the currentItem instead of the item's placeholder, which is pretty useless. --- jquery.mjs.nestedSortable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.mjs.nestedSortable.js b/jquery.mjs.nestedSortable.js index 8eaf402..e6c0d62 100644 --- a/jquery.mjs.nestedSortable.js +++ b/jquery.mjs.nestedSortable.js @@ -403,7 +403,7 @@ // Is the root protected? // Are we trying to nest under a no-nest? // Are we nesting too deep? - if (!o.isAllowed(this.placeholder, parentItem) || + if (!o.isAllowed(this.currentItem, parentItem) || parentItem && parentItem.hasClass(o.disableNesting) || o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) { this.placeholder.addClass(o.errorClass); From c2067c4281268b608478296d4d4fc323a1c607aa Mon Sep 17 00:00:00 2001 From: Manuele J Sarfatti Date: Mon, 29 Oct 2012 19:09:21 +0100 Subject: [PATCH 09/13] Update README.md to announce version 2.0 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6913825..4c6782a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # nestedSortable jQuery plugin -*nestedSortable* is a jQuery plugin that extends jQuery Sortable UI functionalities to nested lists. +*nestedSortable* is a jQuery plugin that extends jQuery Sortable UI functionalities to nested lists. +*Note: **Version 2.0** is published in branch '2.0alpha' and is ready for testing! At the moment it has only been tested in Firefox and Chrome, if you work with IE feel free to give it a shot and let me know if something goes wrong.* ## Features From 5e74da18f298c6f45681f4d134b9613137a3900d Mon Sep 17 00:00:00 2001 From: Manuele J Sarfatti Date: Mon, 29 Oct 2012 19:09:47 +0100 Subject: [PATCH 10/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c6782a..a0a8a9d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # nestedSortable jQuery plugin *nestedSortable* is a jQuery plugin that extends jQuery Sortable UI functionalities to nested lists. -*Note: **Version 2.0** is published in branch '2.0alpha' and is ready for testing! At the moment it has only been tested in Firefox and Chrome, if you work with IE feel free to give it a shot and let me know if something goes wrong.* +*Note:* **Version 2.0** *is published in branch '2.0alpha' and is ready for testing! At the moment it has only been tested in Firefox and Chrome, if you work with IE feel free to give it a shot and let me know if something goes wrong.* ## Features From ad442245af7e48527bddc36b56f62b4f04de3021 Mon Sep 17 00:00:00 2001 From: Manuele J Sarfatti Date: Mon, 10 Jun 2013 12:02:09 +0300 Subject: [PATCH 11/13] Add "developer needed" announcement --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a0a8a9d..042f91c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +**DEVELOPER NEEDED** +**I'm sorry to say that I am not able to keep the pace developing this project anymore. I know how much nestedSortable is important for web applications, and I still can't understand why it's not part of jQuery-UI. I also think the base of the plugin is very strong, and deserves much more attention and involvement. If anybody is willing to take this project, please say so [here](https://github.com/mjsarfatti/nestedSortable/issues/95).** +**Thank you.** + # nestedSortable jQuery plugin *nestedSortable* is a jQuery plugin that extends jQuery Sortable UI functionalities to nested lists. @@ -120,4 +124,4 @@ Tested with: IE 6/7/8, Firefox 3.6/4, Chrome, Safari 3 This work is licensed under the MIT License. This work is *pizzaware*. If it saved your life, or you just feel good at heart, please consider offering me a pizza. This can be done in two ways: (1) follow [this link](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=RSJEW3N9PRMYY&lc=IT&item_name=Manuele%20Sarfatti¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) to donate through paypal; (2) send me cash via traditional mail to my home address in Italy. Is the second method legal? It is in Italy if you use Posta assicurata. You should check with your local laws if you live elsewhere. - \ No newline at end of file + From cf72a2526e2d0263fde7d5df063d0747a201545f Mon Sep 17 00:00:00 2001 From: Manuele J Sarfatti Date: Mon, 10 Jun 2013 12:02:47 +0300 Subject: [PATCH 12/13] Fix "developer needed" announcement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 042f91c..5138dde 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -**DEVELOPER NEEDED** +**DEVELOPER NEEDED** **I'm sorry to say that I am not able to keep the pace developing this project anymore. I know how much nestedSortable is important for web applications, and I still can't understand why it's not part of jQuery-UI. I also think the base of the plugin is very strong, and deserves much more attention and involvement. If anybody is willing to take this project, please say so [here](https://github.com/mjsarfatti/nestedSortable/issues/95).** **Thank you.** From dfd95dda908ce48f96341fcfa7f2fa117812e7d0 Mon Sep 17 00:00:00 2001 From: Manuele J Sarfatti Date: Tue, 29 Jul 2014 11:21:31 +0200 Subject: [PATCH 13/13] new maintainer --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5138dde..7429153 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ -**DEVELOPER NEEDED** -**I'm sorry to say that I am not able to keep the pace developing this project anymore. I know how much nestedSortable is important for web applications, and I still can't understand why it's not part of jQuery-UI. I also think the base of the plugin is very strong, and deserves much more attention and involvement. If anybody is willing to take this project, please say so [here](https://github.com/mjsarfatti/nestedSortable/issues/95).** -**Thank you.** +# 29 July 2014 +**nestedSortable is currently maintained at [ilikenwf/nestedSortable](https://github.com/ilikenwf/nestedSortable). Please [go there](https://github.com/ilikenwf/nestedSortable).** + +**~~DEVELOPER NEEDED~~** +**~~I'm sorry to say that I am not able to keep the pace developing this project anymore. I know how much nestedSortable is important for web applications, and I still can't understand why it's not part of jQuery-UI. I also think the base of the plugin is very strong, and deserves much more attention and involvement. If anybody is willing to take this project, please say so [here](https://github.com/mjsarfatti/nestedSortable/issues/95).~~** +**~~Thank you.~~** # nestedSortable jQuery plugin