the_sortable_tree 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +67 -0
- data/app/assets/images/iconza/blue/add.png +0 -0
- data/app/assets/images/iconza/blue/delete.png +0 -0
- data/app/assets/images/iconza/blue/down.png +0 -0
- data/app/assets/images/iconza/blue/downloads_folder.png +0 -0
- data/app/assets/images/iconza/blue/edit.png +0 -0
- data/app/assets/images/iconza/blue/move.png +0 -0
- data/app/assets/images/iconza/blue/up.png +0 -0
- data/app/assets/images/iconza/gray/add.png +0 -0
- data/app/assets/images/iconza/gray/delete.png +0 -0
- data/app/assets/images/iconza/gray/down.png +0 -0
- data/app/assets/images/iconza/gray/edit.png +0 -0
- data/app/assets/images/iconza/gray/lock.png +0 -0
- data/app/assets/images/iconza/gray/mail.png +0 -0
- data/app/assets/images/iconza/gray/push_pin.png +0 -0
- data/app/assets/images/iconza/gray/up.png +0 -0
- data/app/assets/images/iconza/red/add.png +0 -0
- data/app/assets/images/iconza/red/delete.png +0 -0
- data/app/assets/images/iconza/red/down.png +0 -0
- data/app/assets/images/iconza/red/edit.png +0 -0
- data/app/assets/images/iconza/red/newspaper.png +0 -0
- data/app/assets/images/iconza/red/trash.png +0 -0
- data/app/assets/images/iconza/red/up.png +0 -0
- data/app/assets/images/iconza/red/zoom.png +0 -0
- data/app/assets/javascripts/jquery.ui.nestedSortable.js +356 -0
- data/app/assets/stylesheets/the_sortable_tree.css +114 -0
- data/app/controllers/the_sortable_tree_controller.rb +67 -0
- data/app/helpers/the_sortable_tree_helper.rb +107 -0
- data/app/views/the_sortable_tree/_controls.html.haml +16 -0
- data/app/views/the_sortable_tree/_js_init_sortable_tree.html.haml +17 -0
- data/app/views/the_sortable_tree/_js_on_update_tree.html.haml +12 -0
- data/app/views/the_sortable_tree/_js_rebuild_ajax.html.haml +21 -0
- data/app/views/the_sortable_tree/_link.html.haml +10 -0
- data/app/views/the_sortable_tree/_nested_set.html.haml +1 -0
- data/app/views/the_sortable_tree/_nested_set_item.html.haml +3 -0
- data/app/views/the_sortable_tree/_new.html.haml +3 -0
- data/app/views/the_sortable_tree/_tree.html.haml +6 -0
- data/lib/generators/the_sortable_tree/views_generator.rb +25 -0
- data/lib/the_sortable_tree/engine.rb +10 -0
- data/lib/the_sortable_tree/version.rb +1 -1
- data/the_sortable_tree.gemspec +2 -2
- metadata +45 -6
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            ### TheSortableTree
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            GUI for nested_set gem. Rails 3+
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            **Engine Based**
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ### Install
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                gem 'the_sortable_tree'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            bundle
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ### Require
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            1. gem 'nested_set'
         | 
| 16 | 
            +
            2. gem 'haml'
         | 
| 17 | 
            +
            3. JQuery UI
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ### Extend you Model
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ``` ruby
         | 
| 22 | 
            +
            # SCOPES FOR SORTABLE NESTED SET
         | 
| 23 | 
            +
            scope :nested_set,          order('lft ASC')
         | 
| 24 | 
            +
            scope :reversed_nested_set, order('lft DESC')
         | 
| 25 | 
            +
            ```
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ### Extend you Controller
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            ``` ruby
         | 
| 30 | 
            +
            include TheSortableTreeController::Rebuild
         | 
| 31 | 
            +
            ```
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ### Extend you Routes
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ``` ruby
         | 
| 36 | 
            +
            resources :elements do
         | 
| 37 | 
            +
              collection do
         | 
| 38 | 
            +
                get :manage
         | 
| 39 | 
            +
                post :rebuild
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| 42 | 
            +
            ```
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ### Find you tree
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            ``` ruby
         | 
| 47 | 
            +
            def manage
         | 
| 48 | 
            +
              @elements = Element.all.nested_set
         | 
| 49 | 
            +
            end
         | 
| 50 | 
            +
            ```
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ### Render you tree with TheSortableTree
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            ``` ruby
         | 
| 55 | 
            +
            - content_for :css do
         | 
| 56 | 
            +
              = stylesheet_link_tag 'the_sortable_tree', :media => :screen
         | 
| 57 | 
            +
            - content_for :js do
         | 
| 58 | 
            +
              = javascript_include_tag 'jquery.ui.nestedSortable'
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            = sortable_tree @elements, :klass => :element, :rebuild_url => rebuild_elements_path
         | 
| 61 | 
            +
            ```
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ### Customize
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            ``` ruby
         | 
| 66 | 
            +
            rails g the_sortable_tree:views elements
         | 
| 67 | 
            +
            ```
         | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| @@ -0,0 +1,356 @@ | |
| 1 | 
            +
            /*
         | 
| 2 | 
            +
             * jQuery UI Nested Sortable
         | 
| 3 | 
            +
             * v 1.3.4 / 28 apr 2011
         | 
| 4 | 
            +
             * http://mjsarfatti.com/sandbox/nestedSortable
         | 
| 5 | 
            +
             *
         | 
| 6 | 
            +
             * Depends:
         | 
| 7 | 
            +
             *	 jquery.ui.sortable.js 1.8+
         | 
| 8 | 
            +
             *
         | 
| 9 | 
            +
             * License CC BY-SA 3.0
         | 
| 10 | 
            +
             * Copyright 2010-2011, Manuele J Sarfatti
         | 
| 11 | 
            +
             */
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            (function($) {
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            	$.widget("ui.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            		options: {
         | 
| 18 | 
            +
            			tabSize: 20,
         | 
| 19 | 
            +
            			disableNesting: 'ui-nestedSortable-no-nesting',
         | 
| 20 | 
            +
            			errorClass: 'ui-nestedSortable-error',
         | 
| 21 | 
            +
            			listType: 'ol',
         | 
| 22 | 
            +
            			maxLevels: 0,
         | 
| 23 | 
            +
            			noJumpFix: 0
         | 
| 24 | 
            +
            		},
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            		_create: function(){
         | 
| 27 | 
            +
            			if (this.noJumpFix == false)
         | 
| 28 | 
            +
            				this.element.height(this.element.height());
         | 
| 29 | 
            +
            			this.element.data('sortable', this.element.data('nestedSortable'));
         | 
| 30 | 
            +
            			return $.ui.sortable.prototype._create.apply(this, arguments);
         | 
| 31 | 
            +
            		},
         | 
| 32 | 
            +
             | 
| 33 | 
            +
             | 
| 34 | 
            +
             | 
| 35 | 
            +
            		_mouseDrag: function(event) {
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            			//Compute the helpers position
         | 
| 38 | 
            +
            			this.position = this._generatePosition(event);
         | 
| 39 | 
            +
            			this.positionAbs = this._convertPositionTo("absolute");
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            			if (!this.lastPositionAbs) {
         | 
| 42 | 
            +
            				this.lastPositionAbs = this.positionAbs;
         | 
| 43 | 
            +
            			}
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            			//Do scrolling
         | 
| 46 | 
            +
            			if(this.options.scroll) {
         | 
| 47 | 
            +
            				var o = this.options, scrolled = false;
         | 
| 48 | 
            +
            				if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            					if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
         | 
| 51 | 
            +
            						this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
         | 
| 52 | 
            +
            					else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
         | 
| 53 | 
            +
            						this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            					if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
         | 
| 56 | 
            +
            						this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
         | 
| 57 | 
            +
            					else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
         | 
| 58 | 
            +
            						this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            				} else {
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            					if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
         | 
| 63 | 
            +
            						scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
         | 
| 64 | 
            +
            					else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
         | 
| 65 | 
            +
            						scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            					if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
         | 
| 68 | 
            +
            						scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
         | 
| 69 | 
            +
            					else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
         | 
| 70 | 
            +
            						scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            				}
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            				if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
         | 
| 75 | 
            +
            					$.ui.ddmanager.prepareOffsets(this, event);
         | 
| 76 | 
            +
            			}
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            			//Regenerate the absolute position used for position checks
         | 
| 79 | 
            +
            			this.positionAbs = this._convertPositionTo("absolute");
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            			//Set the helper position
         | 
| 82 | 
            +
            			if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
         | 
| 83 | 
            +
            			if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            			//Rearrange
         | 
| 86 | 
            +
            			for (var i = this.items.length - 1; i >= 0; i--) {
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            				//Cache variables and intersection, continue if no intersection
         | 
| 89 | 
            +
            				var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
         | 
| 90 | 
            +
            				if (!intersection) continue;
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            				if(itemElement != this.currentItem[0] //cannot intersect with itself
         | 
| 93 | 
            +
            					&&	this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
         | 
| 94 | 
            +
            					&&	!$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
         | 
| 95 | 
            +
            					&& (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
         | 
| 96 | 
            +
            					//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
         | 
| 97 | 
            +
            				) {
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            					this.direction = intersection == 1 ? "down" : "up";
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            					if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
         | 
| 102 | 
            +
            						this._rearrange(event, item);
         | 
| 103 | 
            +
            					} else {
         | 
| 104 | 
            +
            						break;
         | 
| 105 | 
            +
            					}
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            					// Clear emtpy ul's/ol's
         | 
| 108 | 
            +
            					this._clearEmpty(itemElement);
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            					this._trigger("change", event, this._uiHash());
         | 
| 111 | 
            +
            					break;
         | 
| 112 | 
            +
            				}
         | 
| 113 | 
            +
            			}
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            			var parentItem = (this.placeholder[0].parentNode.parentNode && $(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length) ? $(this.placeholder[0].parentNode.parentNode) : null;
         | 
| 116 | 
            +
            			var level = this._getLevel(this.placeholder);
         | 
| 117 | 
            +
            			var childLevels = this._getChildLevels(this.helper);
         | 
| 118 | 
            +
            			var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
         | 
| 119 | 
            +
            			if (previousItem != null) {
         | 
| 120 | 
            +
            				while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0]) {
         | 
| 121 | 
            +
            					if (previousItem[0].previousSibling) {
         | 
| 122 | 
            +
            						previousItem = $(previousItem[0].previousSibling);
         | 
| 123 | 
            +
            					} else {
         | 
| 124 | 
            +
            						previousItem = null;
         | 
| 125 | 
            +
            						break;
         | 
| 126 | 
            +
            					}
         | 
| 127 | 
            +
            				}
         | 
| 128 | 
            +
            			}
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            			newList = document.createElement(o.listType);
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            			this.beyondMaxLevels = 0;
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            			// If the item is moved to the left, send it to its parent level
         | 
| 135 | 
            +
            			if (parentItem != null && this.positionAbs.left < parentItem.offset().left) {
         | 
| 136 | 
            +
            				parentItem.after(this.placeholder[0]);
         | 
| 137 | 
            +
            				this._clearEmpty(parentItem[0]);
         | 
| 138 | 
            +
            				this._trigger("change", event, this._uiHash());
         | 
| 139 | 
            +
            			}
         | 
| 140 | 
            +
            			// If the item is below another one and is moved to the right, make it a children of it
         | 
| 141 | 
            +
            			else if (previousItem != null && this.positionAbs.left > previousItem.offset().left + o.tabSize) {
         | 
| 142 | 
            +
            				this._isAllowed(previousItem, level+childLevels+1);
         | 
| 143 | 
            +
            				if (!previousItem.children(o.listType).length) {
         | 
| 144 | 
            +
            					previousItem[0].appendChild(newList);
         | 
| 145 | 
            +
            				}
         | 
| 146 | 
            +
            				previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
         | 
| 147 | 
            +
            				this._trigger("change", event, this._uiHash());
         | 
| 148 | 
            +
            			}
         | 
| 149 | 
            +
            			else {
         | 
| 150 | 
            +
            				this._isAllowed(parentItem, level+childLevels);
         | 
| 151 | 
            +
            			}
         | 
| 152 | 
            +
             | 
| 153 | 
            +
            			//Post events to containers
         | 
| 154 | 
            +
            			this._contactContainers(event);
         | 
| 155 | 
            +
             | 
| 156 | 
            +
            			//Interconnect with droppables
         | 
| 157 | 
            +
            			if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
         | 
| 158 | 
            +
             | 
| 159 | 
            +
            			//Call callbacks
         | 
| 160 | 
            +
            			this._trigger('sort', event, this._uiHash());
         | 
| 161 | 
            +
             | 
| 162 | 
            +
            			this.lastPositionAbs = this.positionAbs;
         | 
| 163 | 
            +
            			return false;
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            		},
         | 
| 166 | 
            +
             | 
| 167 | 
            +
            		_mouseStop: function(event, noPropagation) {
         | 
| 168 | 
            +
             | 
| 169 | 
            +
            			// If the item is in a position not allowed, send it back
         | 
| 170 | 
            +
            			if (this.beyondMaxLevels) {
         | 
| 171 | 
            +
            				var parent = this.placeholder.parent().closest(this.options.items);
         | 
| 172 | 
            +
            				
         | 
| 173 | 
            +
            				for (var i = this.beyondMaxLevels - 1; i > 0; i--) {
         | 
| 174 | 
            +
            					parent = parent.parent().closest(this.options.items);
         | 
| 175 | 
            +
            				}
         | 
| 176 | 
            +
             | 
| 177 | 
            +
            				this.placeholder.removeClass(this.options.errorClass);
         | 
| 178 | 
            +
            				parent.after(this.placeholder);
         | 
| 179 | 
            +
            				this._trigger("change", event, this._uiHash());
         | 
| 180 | 
            +
            			}
         | 
| 181 | 
            +
             | 
| 182 | 
            +
            			$.ui.sortable.prototype._mouseStop.apply(this, arguments);
         | 
| 183 | 
            +
             | 
| 184 | 
            +
            		},
         | 
| 185 | 
            +
             | 
| 186 | 
            +
            		serialize: function(o) {
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            			var items = this._getItemsAsjQuery(o && o.connected);
         | 
| 189 | 
            +
            			var str = []; o = o || {};
         | 
| 190 | 
            +
             | 
| 191 | 
            +
            			$(items).each(function() {
         | 
| 192 | 
            +
            				var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
         | 
| 193 | 
            +
            				var pid = ($(o.item || this).parent(o.listType).parent('li').attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
         | 
| 194 | 
            +
            				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'));
         | 
| 195 | 
            +
            			});
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            			if(!str.length && o.key) {
         | 
| 198 | 
            +
            				str.push(o.key + '=');
         | 
| 199 | 
            +
            			}
         | 
| 200 | 
            +
             | 
| 201 | 
            +
            			return str.join('&');
         | 
| 202 | 
            +
             | 
| 203 | 
            +
            		},
         | 
| 204 | 
            +
             | 
| 205 | 
            +
            		toHierarchy: function(o) {
         | 
| 206 | 
            +
             | 
| 207 | 
            +
            			o = o || {};
         | 
| 208 | 
            +
            			var sDepth = o.startDepthCount || 0;
         | 
| 209 | 
            +
            			var ret = [];
         | 
| 210 | 
            +
             | 
| 211 | 
            +
            			$(this.element).children('li').each(function() {
         | 
| 212 | 
            +
            				var level = _recursiveItems($(this));
         | 
| 213 | 
            +
            				ret.push(level);
         | 
| 214 | 
            +
            			});
         | 
| 215 | 
            +
             | 
| 216 | 
            +
            			return ret;
         | 
| 217 | 
            +
             | 
| 218 | 
            +
            			function _recursiveItems(li) {
         | 
| 219 | 
            +
            				var id = ($(li).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
         | 
| 220 | 
            +
            				if (id != null) {
         | 
| 221 | 
            +
            					var item = {"id" : id[2]};
         | 
| 222 | 
            +
            					if ($(li).children(o.listType).children('li').length > 0) {
         | 
| 223 | 
            +
            						item.children = [];
         | 
| 224 | 
            +
            						$(li).children(o.listType).children('li').each(function () {
         | 
| 225 | 
            +
            							var level = _recursiveItems($(this));
         | 
| 226 | 
            +
            							item.children.push(level);
         | 
| 227 | 
            +
            						});
         | 
| 228 | 
            +
            					}
         | 
| 229 | 
            +
            					return item;
         | 
| 230 | 
            +
            				}
         | 
| 231 | 
            +
            			}
         | 
| 232 | 
            +
                    },
         | 
| 233 | 
            +
             | 
| 234 | 
            +
            		toArray: function(o) {
         | 
| 235 | 
            +
             | 
| 236 | 
            +
            			o = o || {};
         | 
| 237 | 
            +
            			var sDepth = o.startDepthCount || 0;
         | 
| 238 | 
            +
            			var ret = [];
         | 
| 239 | 
            +
            			var left = 2;
         | 
| 240 | 
            +
             | 
| 241 | 
            +
            			ret.push({"item_id": 'root', "parent_id": 'none', "depth": sDepth, "left": '1', "right": ($('li', this.element).length + 1) * 2});
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            			$(this.element).children('li').each(function () {
         | 
| 244 | 
            +
            				left = _recursiveArray(this, sDepth + 1, left);
         | 
| 245 | 
            +
            			});
         | 
| 246 | 
            +
             | 
| 247 | 
            +
            			function _sortByLeft(a,b) {
         | 
| 248 | 
            +
            				return a['left'] - b['left'];
         | 
| 249 | 
            +
            			}
         | 
| 250 | 
            +
            			ret = ret.sort(_sortByLeft);
         | 
| 251 | 
            +
             | 
| 252 | 
            +
            			return ret;
         | 
| 253 | 
            +
             | 
| 254 | 
            +
            			function _recursiveArray(item, depth, left) {
         | 
| 255 | 
            +
             | 
| 256 | 
            +
            				right = left + 1;
         | 
| 257 | 
            +
             | 
| 258 | 
            +
            				if ($(item).children(o.listType).children('li').length > 0) {
         | 
| 259 | 
            +
            					depth ++;
         | 
| 260 | 
            +
            					$(item).children(o.listType).children('li').each(function () {
         | 
| 261 | 
            +
            						right = _recursiveArray($(this), depth, right);
         | 
| 262 | 
            +
            					});
         | 
| 263 | 
            +
            					depth --;
         | 
| 264 | 
            +
            				}
         | 
| 265 | 
            +
             | 
| 266 | 
            +
            				id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
         | 
| 267 | 
            +
             | 
| 268 | 
            +
            				if (depth === sDepth + 1) pid = 'root';
         | 
| 269 | 
            +
            				else {
         | 
| 270 | 
            +
            					parentItem = ($(item).parent(o.listType).parent('li').attr('id')).match(o.expression || (/(.+)[-=_](.+)/));
         | 
| 271 | 
            +
            					pid = parentItem[2];
         | 
| 272 | 
            +
            				}
         | 
| 273 | 
            +
             | 
| 274 | 
            +
            				if (id != null) {
         | 
| 275 | 
            +
            						ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
         | 
| 276 | 
            +
            				}
         | 
| 277 | 
            +
             | 
| 278 | 
            +
            				return left = right + 1;
         | 
| 279 | 
            +
            			}
         | 
| 280 | 
            +
             | 
| 281 | 
            +
            		},
         | 
| 282 | 
            +
             | 
| 283 | 
            +
            		_clear: function(event, noPropagation) {
         | 
| 284 | 
            +
             | 
| 285 | 
            +
            			$.ui.sortable.prototype._clear.apply(this, arguments);
         | 
| 286 | 
            +
             | 
| 287 | 
            +
            			// Clean last empty ul/ol
         | 
| 288 | 
            +
            			for (var i = this.items.length - 1; i >= 0; i--) {
         | 
| 289 | 
            +
            				var item = this.items[i].item[0];
         | 
| 290 | 
            +
            				this._clearEmpty(item);
         | 
| 291 | 
            +
            			}
         | 
| 292 | 
            +
            			return true;
         | 
| 293 | 
            +
             | 
| 294 | 
            +
            		},
         | 
| 295 | 
            +
             | 
| 296 | 
            +
            		_clearEmpty: function(item) {
         | 
| 297 | 
            +
             | 
| 298 | 
            +
            			if (item.children[1] && item.children[1].children.length == 0) {
         | 
| 299 | 
            +
            				item.removeChild(item.children[1]);
         | 
| 300 | 
            +
            			}
         | 
| 301 | 
            +
             | 
| 302 | 
            +
            		},
         | 
| 303 | 
            +
             | 
| 304 | 
            +
            		_getLevel: function(item) {
         | 
| 305 | 
            +
             | 
| 306 | 
            +
            			var level = 1;
         | 
| 307 | 
            +
             | 
| 308 | 
            +
            			if (this.options.listType) {
         | 
| 309 | 
            +
            					var list = item.closest(this.options.listType);
         | 
| 310 | 
            +
            					while (!list.is('.ui-sortable')/* && level < this.options.maxLevels*/) {
         | 
| 311 | 
            +
            							level++;
         | 
| 312 | 
            +
            							list = list.parent().closest(this.options.listType);
         | 
| 313 | 
            +
            					}
         | 
| 314 | 
            +
            			}
         | 
| 315 | 
            +
             | 
| 316 | 
            +
            			return level;
         | 
| 317 | 
            +
            		},
         | 
| 318 | 
            +
             | 
| 319 | 
            +
            		_getChildLevels: function(parent, depth) {
         | 
| 320 | 
            +
            			var self = this,
         | 
| 321 | 
            +
            			    o = this.options,
         | 
| 322 | 
            +
            			    result = 0;
         | 
| 323 | 
            +
            			depth = depth || 0;
         | 
| 324 | 
            +
             | 
| 325 | 
            +
            			$(parent).children(o.listType).children(o.items).each(function (index, child) {
         | 
| 326 | 
            +
            					result = Math.max(self._getChildLevels(child, depth + 1), result);
         | 
| 327 | 
            +
            			});
         | 
| 328 | 
            +
             | 
| 329 | 
            +
            			return depth ? result + 1 : result;
         | 
| 330 | 
            +
            		},
         | 
| 331 | 
            +
             | 
| 332 | 
            +
            		_isAllowed: function(parentItem, levels) {
         | 
| 333 | 
            +
            			var o = this.options
         | 
| 334 | 
            +
            			// Are we trying to nest under a no-nest or are we nesting too deep?
         | 
| 335 | 
            +
            			if (parentItem == null || !(parentItem.hasClass(o.disableNesting))) {
         | 
| 336 | 
            +
            				if (o.maxLevels < levels && o.maxLevels != 0) {
         | 
| 337 | 
            +
            					this.placeholder.addClass(o.errorClass);
         | 
| 338 | 
            +
            					this.beyondMaxLevels = levels - o.maxLevels;
         | 
| 339 | 
            +
            				} else {
         | 
| 340 | 
            +
            					this.placeholder.removeClass(o.errorClass);
         | 
| 341 | 
            +
            					this.beyondMaxLevels = 0;
         | 
| 342 | 
            +
            				}
         | 
| 343 | 
            +
            			} else {
         | 
| 344 | 
            +
            				this.placeholder.addClass(o.errorClass);
         | 
| 345 | 
            +
            				if (o.maxLevels < levels && o.maxLevels != 0) {
         | 
| 346 | 
            +
            					this.beyondMaxLevels = levels - o.maxLevels;
         | 
| 347 | 
            +
            				} else {
         | 
| 348 | 
            +
            					this.beyondMaxLevels = 1;
         | 
| 349 | 
            +
            				}
         | 
| 350 | 
            +
            			}
         | 
| 351 | 
            +
            		}
         | 
| 352 | 
            +
             | 
| 353 | 
            +
            	}));
         | 
| 354 | 
            +
             | 
| 355 | 
            +
            	$.ui.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.ui.nestedSortable.prototype.options);
         | 
| 356 | 
            +
            })(jQuery);
         | 
| @@ -0,0 +1,114 @@ | |
| 1 | 
            +
            .nested_set{
         | 
| 2 | 
            +
              padding: 0;
         | 
| 3 | 
            +
            }
         | 
| 4 | 
            +
            .nested_set ol{
         | 
| 5 | 
            +
              margin:0 0 0 25px;
         | 
| 6 | 
            +
              border-left:1px dashed gray;
         | 
| 7 | 
            +
            }
         | 
| 8 | 
            +
            .nested_set li{
         | 
| 9 | 
            +
              margin-bottom:5px;
         | 
| 10 | 
            +
            }
         | 
| 11 | 
            +
            .nested_set a{
         | 
| 12 | 
            +
              color:#000;
         | 
| 13 | 
            +
              font-size:10pt;
         | 
| 14 | 
            +
              font-weight:normal;
         | 
| 15 | 
            +
              text-decoration:none;
         | 
| 16 | 
            +
              line-height: 150%;
         | 
| 17 | 
            +
              margin-left:30px;
         | 
| 18 | 
            +
              margin-right:120px;
         | 
| 19 | 
            +
              display:block;
         | 
| 20 | 
            +
            }
         | 
| 21 | 
            +
            .nested_set a img{float:left;}
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            .nested_set .root a{
         | 
| 24 | 
            +
              font-weight:bold;
         | 
| 25 | 
            +
            }
         | 
| 26 | 
            +
            .nested_set a:hover{
         | 
| 27 | 
            +
              color:#2476FF;
         | 
| 28 | 
            +
              text-decoration:none;
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
            .nested_set .handle{
         | 
| 31 | 
            +
              cursor: move;
         | 
| 32 | 
            +
            }
         | 
| 33 | 
            +
            .nested_set .link{
         | 
| 34 | 
            +
              position: relative;
         | 
| 35 | 
            +
              overflow: hidden;
         | 
| 36 | 
            +
              zoom:1;
         | 
| 37 | 
            +
              padding:5px;
         | 
| 38 | 
            +
              margin:0 0 5px 5px;
         | 
| 39 | 
            +
              background:#EFEFEF;
         | 
| 40 | 
            +
              -moz-border-radius:5px;
         | 
| 41 | 
            +
            }
         | 
| 42 | 
            +
            .nested_set .link:hover{
         | 
| 43 | 
            +
              background:#FEE;
         | 
| 44 | 
            +
            }
         | 
| 45 | 
            +
            .nested_set .controls{
         | 
| 46 | 
            +
              position: absolute;
         | 
| 47 | 
            +
              top: 5px; right: 10px;
         | 
| 48 | 
            +
            }
         | 
| 49 | 
            +
            .nested_set .button{
         | 
| 50 | 
            +
              width:20px;
         | 
| 51 | 
            +
              height:20px;
         | 
| 52 | 
            +
              display:block;
         | 
| 53 | 
            +
              float:left;
         | 
| 54 | 
            +
              margin:0 0 0 3px;
         | 
| 55 | 
            +
              cursor:pointer;
         | 
| 56 | 
            +
            }
         | 
| 57 | 
            +
            .root_create_image{
         | 
| 58 | 
            +
              vertical-align:middle;
         | 
| 59 | 
            +
              border:none;
         | 
| 60 | 
            +
            }
         | 
| 61 | 
            +
            .root_create_link{
         | 
| 62 | 
            +
              margin:25px 0;
         | 
| 63 | 
            +
            }
         | 
| 64 | 
            +
            .root_create_link a{
         | 
| 65 | 
            +
              font-size:14pt;
         | 
| 66 | 
            +
            }
         | 
| 67 | 
            +
            .nested_set .handle{
         | 
| 68 | 
            +
              width:16px;
         | 
| 69 | 
            +
              height:16px;
         | 
| 70 | 
            +
              float:left;
         | 
| 71 | 
            +
              background:transparent url(/assets/iconza/blue/move.png) no-repeat scroll center center;
         | 
| 72 | 
            +
            }
         | 
| 73 | 
            +
            .nested_set .placeholder {
         | 
| 74 | 
            +
              background-color:#EEF;
         | 
| 75 | 
            +
              border:1px dashed blue;
         | 
| 76 | 
            +
            }
         | 
| 77 | 
            +
            .nested_set .ui-nestedSortable-error {
         | 
| 78 | 
            +
              background:#FAA;
         | 
| 79 | 
            +
              color:#8a1f11;
         | 
| 80 | 
            +
            }
         | 
| 81 | 
            +
            .nested_set .button.new{ background:transparent url(/assets/iconza/blue/add.png) no-repeat scroll center center; }
         | 
| 82 | 
            +
            .nested_set .button.new:hover{ background:transparent url(/assets/iconza/red/add.png) no-repeat scroll center center;}
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            .nested_set .button.edit{ background:transparent url(/assets/iconza/blue/edit.png) no-repeat scroll center center; }
         | 
| 85 | 
            +
            .nested_set .button.edit:hover{ background:transparent url(/assets/iconza/red/edit.png) no-repeat scroll center center;}
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            .nested_set .button.up{ background:transparent url(/assets/iconza/blue/up.png) no-repeat scroll center center; }
         | 
| 88 | 
            +
            .nested_set .button.up:hover{ background:transparent url(/assets/iconza/red/up.png) no-repeat scroll center center;}
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            .nested_set .button.down{ background:transparent url(/assets/iconza/blue/down.png) no-repeat scroll center center; }
         | 
| 91 | 
            +
            .nested_set .button.down:hover{ background:transparent url(/assets/iconza/red/down.png) no-repeat scroll center center;}
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            .nested_set .button.delete{ background:transparent url(/assets/iconza/blue/delete.png) no-repeat scroll center center; }
         | 
| 94 | 
            +
            .nested_set .button.delete:hover{ background:transparent url(/assets/iconza/red/delete.png) no-repeat scroll center center;}
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            .nested_set .button.hard_delete{ background:transparent url(/assets/iconza/gray/delete.png) no-repeat scroll center center; }
         | 
| 97 | 
            +
            .nested_set .button.hard_delete:hover{ background:transparent url(/assets/iconza/red/delete.png) no-repeat scroll center center;}
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            .nested_set .undeleted{ background:transparent url(/assets/iconza/gray/delete.png) no-repeat scroll center center; }
         | 
| 100 | 
            +
            .nested_set .cantup{ background:transparent url(/assets/iconza/gray/up.png) no-repeat scroll center center; }
         | 
| 101 | 
            +
            .nested_set .cantdown{ background:transparent url(/assets/iconza/gray/down.png) no-repeat scroll center center; }
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            /*STATES*/
         | 
| 104 | 
            +
            .nested_set .unsafe{ background:transparent url(/assets/modern/mini-icons/unsafe.png) no-repeat scroll center center; cursor: help; }
         | 
| 105 | 
            +
            .nested_set .draft{ background:transparent url(/assets/modern/mini-icons/draft.png) no-repeat scroll center center; cursor: help;}
         | 
| 106 | 
            +
            .nested_set .published{ background:transparent url(/assets/modern/mini-icons/publiched.png) no-repeat scroll center center; cursor: help;}
         | 
| 107 | 
            +
            .nested_set .restricted{ background:transparent url(/assets/modern/mini-icons/restricted.png) no-repeat scroll center center; cursor: help;}
         | 
| 108 | 
            +
            .nested_set .archived{ background:transparent url(/assets/modern/mini-icons/archived.png) no-repeat scroll center center; cursor: help;}
         | 
| 109 | 
            +
            .nested_set .deleted{ background:transparent url(/assets/modern/mini-icons/bin.png) no-repeat scroll center center; cursor: help;}
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            /* Moderation states*/
         | 
| 112 | 
            +
            .nested_set .moderation_unsafe{ background:transparent url(/assets/modern/mini-icons/moderation_unsafe.png) no-repeat scroll center center; cursor: help;}
         | 
| 113 | 
            +
            .nested_set .moderation_safe{ background:transparent url(/assets/modern/mini-icons/shield.png) no-repeat scroll center center; cursor: help;}
         | 
| 114 | 
            +
            .nested_set .moderation_blocked{ background:transparent url(/assets/modern/mini-icons/moderation_blocked.png) no-repeat scroll center center; cursor: help;}
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            module TheSortableTreeController
         | 
| 2 | 
            +
              # include TheSortableTreeController::ReversedRebuild
         | 
| 3 | 
            +
              # include TheSortableTreeController::Rebuild
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              module DefineVariablesMethod
         | 
| 6 | 
            +
                public
         | 
| 7 | 
            +
                def the_define_common_variables
         | 
| 8 | 
            +
                  collection =  self.class.to_s.underscore.split('_').first # recipes
         | 
| 9 | 
            +
                  variable =    collection.singularize                      # recipe
         | 
| 10 | 
            +
                  klass =       variable.classify.constantize               # Recipe
         | 
| 11 | 
            +
                  ["@#{variable}", collection, klass]
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end#DefineVariablesMethod
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              module Rebuild
         | 
| 16 | 
            +
                include DefineVariablesMethod
         | 
| 17 | 
            +
                public
         | 
| 18 | 
            +
                def rebuild
         | 
| 19 | 
            +
                  id        = params[:id].to_i
         | 
| 20 | 
            +
                  parent_id = params[:parent_id].to_i
         | 
| 21 | 
            +
                  prev_id   = params[:prev_id].to_i
         | 
| 22 | 
            +
                  next_id   = params[:next_id].to_i
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  render :text => "Do nothing" and return if parent_id.zero? && prev_id.zero? && next_id.zero?
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  variable, collection, klass = self.the_define_common_variables
         | 
| 27 | 
            +
                  variable = self.instance_variable_set(variable, klass.find(id))
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  if prev_id.zero? && next_id.zero?
         | 
| 30 | 
            +
                    variable.move_to_child_of klass.find(parent_id)
         | 
| 31 | 
            +
                  elsif !prev_id.zero?
         | 
| 32 | 
            +
                    variable.move_to_right_of klass.find(prev_id)
         | 
| 33 | 
            +
                  elsif !next_id.zero?
         | 
| 34 | 
            +
                    variable.move_to_left_of klass.find(next_id)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  render(:nothing => true)
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end#Rebuild
         | 
| 40 | 
            +
              
         | 
| 41 | 
            +
              module ReversedRebuild
         | 
| 42 | 
            +
                include DefineVariablesMethod
         | 
| 43 | 
            +
                public
         | 
| 44 | 
            +
                def rebuild
         | 
| 45 | 
            +
                  id        = params[:id].to_i
         | 
| 46 | 
            +
                  parent_id = params[:parent_id].to_i
         | 
| 47 | 
            +
                  prev_id   = params[:prev_id].to_i
         | 
| 48 | 
            +
                  next_id   = params[:next_id].to_i
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  render :text => "Do nothing" and return if parent_id.zero? && prev_id.zero? && next_id.zero?
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  variable, collection, klass = self.the_define_common_variables
         | 
| 53 | 
            +
                  variable = self.instance_variable_set(variable, klass.find(id))
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  if prev_id.zero? && next_id.zero?
         | 
| 56 | 
            +
                    variable.move_to_child_of klass.find(parent_id)
         | 
| 57 | 
            +
                  elsif !prev_id.zero?
         | 
| 58 | 
            +
                    variable.move_to_left_of klass.find(prev_id)
         | 
| 59 | 
            +
                  elsif !next_id.zero?
         | 
| 60 | 
            +
                    variable.move_to_right_of klass.find(next_id)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  render(:nothing => true)
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end#ReversedRebuild
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            end
         | 
| @@ -0,0 +1,107 @@ | |
| 1 | 
            +
            module TheSortableTreeHelper
         | 
| 2 | 
            +
              # Publicated by MIT
         | 
| 3 | 
            +
              # Nested Set View Helper
         | 
| 4 | 
            +
              # Ilya Zykin, zykin-ilya@ya.ru, Russia, Ivanovo 2009-2011
         | 
| 5 | 
            +
              # github.com/the-teacher
         | 
| 6 | 
            +
              #-------------------------------------------------------------------------------------------------------
         | 
| 7 | 
            +
              
         | 
| 8 | 
            +
              # = sortable_tree(@pages)
         | 
| 9 | 
            +
              # = sortable_tree @products, :klass => :product, :path => 'products/the_sortable_tree', :rebuild_url => rebuild_products_path
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def create_root_element_link(options= {})
         | 
| 12 | 
            +
                opts= {:top_root => false}.merge!(options)
         | 
| 13 | 
            +
                render :partial  =>  "#{opts[:path]}/new", :locals  =>  { :opts  =>  opts }
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def ans_controls(node, options= {})
         | 
| 17 | 
            +
                opts= {
         | 
| 18 | 
            +
                  :id_field => 'id',    # id field name, id by default
         | 
| 19 | 
            +
                  :first => false,      # first element flag
         | 
| 20 | 
            +
                  :last => false,       # last element flag
         | 
| 21 | 
            +
                  :has_childs => false  # has childs?
         | 
| 22 | 
            +
                }.merge!(options)
         | 
| 23 | 
            +
                render :partial  =>  "#{opts[:path]}/controls", :locals  =>  { :node => node, :opts  =>  opts }
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def sortable_tree(tree, options= {})
         | 
| 27 | 
            +
                path = 'the_sortable_tree'
         | 
| 28 | 
            +
                path = options[:path] if options[:path] && File.directory?([Rails.root, :app, :views, options[:path]].join '/')
         | 
| 29 | 
            +
                klass = options[:klass].to_s
         | 
| 30 | 
            +
                tree = sortable_tree_bilder(tree, options.merge!({:path => path, :klass => klass}))
         | 
| 31 | 
            +
                render :partial => "#{path}/tree", :locals => { :tree => tree, :opts => options }
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def sortable_tree_bilder(tree, options= {})
         | 
| 35 | 
            +
                result= ''
         | 
| 36 | 
            +
                opts= {
         | 
| 37 | 
            +
                  :node => nil,         # node
         | 
| 38 | 
            +
                  :admin => true,       # render admin tree?
         | 
| 39 | 
            +
                  :root => false,       # is it root node? 
         | 
| 40 | 
            +
                  :id_field => 'id',    # id field name, id by default
         | 
| 41 | 
            +
                  :first => false,      # first element flag
         | 
| 42 | 
            +
                  :last => false,       # last element flag
         | 
| 43 | 
            +
                  :level =>  0,         # recursion level
         | 
| 44 | 
            +
                  :clean => true        # delete element from tree after rendering. ~25% time economy when rendering tree once on a page
         | 
| 45 | 
            +
                }.merge!(options)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                node = opts[:node]
         | 
| 48 | 
            +
                root = opts[:root]
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                # must be string
         | 
| 51 | 
            +
                opts[:id_field] = opts[:id_field].to_s
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                unless node
         | 
| 54 | 
            +
                  roots= tree.select{|elem| elem.parent_id.nil?}
         | 
| 55 | 
            +
                  # find ids of first and last root node
         | 
| 56 | 
            +
                  roots_first_id= roots.empty? ? nil : roots.first.id
         | 
| 57 | 
            +
                  roots_last_id=  roots.empty? ? nil : roots.last.id
         | 
| 58 | 
            +
                  
         | 
| 59 | 
            +
                  # render roots
         | 
| 60 | 
            +
                  roots.each do |root|
         | 
| 61 | 
            +
                    is_first= (root.id==roots_first_id)
         | 
| 62 | 
            +
                    is_last= (root.id==roots_last_id)
         | 
| 63 | 
            +
                    _opts = opts.merge({:node => root, :root => true, :level => opts[:level].next, :first => is_first, :last => is_last})
         | 
| 64 | 
            +
                    result<< sortable_tree_bilder(tree, _opts)
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                else
         | 
| 67 | 
            +
                  res= ''
         | 
| 68 | 
            +
                  controls= ''
         | 
| 69 | 
            +
                  childs_res= ''
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  # select childs
         | 
| 72 | 
            +
                  childs= tree.select{|elem| elem.parent_id == node.id}
         | 
| 73 | 
            +
                  opts.merge!({:has_childs => childs.blank?})
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  # admin controls
         | 
| 76 | 
            +
                  if opts[:admin]
         | 
| 77 | 
            +
                    c = ans_controls(node, opts)
         | 
| 78 | 
            +
                    controls= content_tag(:span, c, :class => :controls)
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  # find id of first and last node
         | 
| 82 | 
            +
                  childs_first_id= childs.empty? ? nil : childs.first.id
         | 
| 83 | 
            +
                  childs_last_id=  childs.empty? ? nil : childs.last.id
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  # render childs
         | 
| 86 | 
            +
                  childs.each do |elem|
         | 
| 87 | 
            +
                    is_first= (elem.id==childs_first_id)
         | 
| 88 | 
            +
                    is_last= (elem.id==childs_last_id)
         | 
| 89 | 
            +
                    _opts = opts.merge({:node => elem, :root => false, :level => opts[:level].next, :first => is_first, :last => is_last})
         | 
| 90 | 
            +
                    childs_res << sortable_tree_bilder(tree, _opts)
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  # build views
         | 
| 94 | 
            +
                  childs_res= childs_res.blank? ? '' : render(:partial => "#{opts[:path]}/nested_set", :locals => {:opts => opts, :parent => node, :childs => childs_res})
         | 
| 95 | 
            +
                  link= render(:partial => "#{opts[:path]}/link", :locals => {:opts => opts, :node => node, :root => root, :controls => controls})
         | 
| 96 | 
            +
                  res= render(:partial => "#{opts[:path]}/nested_set_item",  :locals => {:opts => opts, :node => node, :link => link, :childs => childs_res})
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  # delete current node from tree if you want
         | 
| 99 | 
            +
                  # recursively moving by tree is 25%+ faster on 500 elems
         | 
| 100 | 
            +
                  # remove from Array, not from DB! ;) ?! Array#pull?!
         | 
| 101 | 
            +
                  # tree.delete(node) if opts[:clean]
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  result << res
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
                raw result
         | 
| 106 | 
            +
              end#sortable_tree_bilder
         | 
| 107 | 
            +
            end# module
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            -# EDIT
         | 
| 2 | 
            +
            - edit= link_to "", polymorphic_url(node, :action => :edit), :title => t("#{opts[:klass].pluralize}.edit_this"), :class => "button edit"
         | 
| 3 | 
            +
            -# DELETE
         | 
| 4 | 
            +
            - if opts[:has_childs]
         | 
| 5 | 
            +
              - delete= link_to("", url_for(node),
         | 
| 6 | 
            +
                :title    =>  t("#{opts[:klass].pluralize}.delete"),
         | 
| 7 | 
            +
                :method   =>  :delete,
         | 
| 8 | 
            +
                :confirm  =>  t("#{opts[:klass].pluralize}.delete_confirm"),
         | 
| 9 | 
            +
                :class    =>  "button delete")
         | 
| 10 | 
            +
            - else
         | 
| 11 | 
            +
              - delete=  link_to("", "#",
         | 
| 12 | 
            +
                :title    =>  t("#{opts[:klass].pluralize}.cant_be_deleted"),
         | 
| 13 | 
            +
                :onclick  =>  t("#{opts[:klass].pluralize}.delete_nested_elements"),
         | 
| 14 | 
            +
                :class    =>  "button undeleted")
         | 
| 15 | 
            +
            -# OUTPUT
         | 
| 16 | 
            +
            = edit + delete
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            :javascript
         | 
| 2 | 
            +
              $(document).ready(function(){
         | 
| 3 | 
            +
                $('ol.sortable').nestedSortable({
         | 
| 4 | 
            +
                  disableNesting: 'no-nest',
         | 
| 5 | 
            +
                  forcePlaceholderSize: true,
         | 
| 6 | 
            +
                  handle: 'div.handle',
         | 
| 7 | 
            +
                  helper: 'clone',
         | 
| 8 | 
            +
                  items: 'li',
         | 
| 9 | 
            +
                  maxLevels: 3,
         | 
| 10 | 
            +
                  opacity: .6,
         | 
| 11 | 
            +
                  placeholder: 'placeholder',
         | 
| 12 | 
            +
                  revert: 250,
         | 
| 13 | 
            +
                  tabSize: 25,
         | 
| 14 | 
            +
                  tolerance: 'pointer',
         | 
| 15 | 
            +
                  toleranceElement: '> div'
         | 
| 16 | 
            +
                })
         | 
| 17 | 
            +
              });
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            :javascript
         | 
| 2 | 
            +
              $(document).ready(function(){
         | 
| 3 | 
            +
                $('ol.sortable').sortable({
         | 
| 4 | 
            +
                  update: function(event, ui){
         | 
| 5 | 
            +
                    parent_id = ui.item.parent().parent().attr('id');
         | 
| 6 | 
            +
                    item_id = ui.item.attr('id');
         | 
| 7 | 
            +
                    prev_id = ui.item.prev().attr('id');
         | 
| 8 | 
            +
                    next_id = ui.item.next().attr('id');
         | 
| 9 | 
            +
                    sortable_tree(item_id, parent_id, prev_id, next_id);
         | 
| 10 | 
            +
                  }
         | 
| 11 | 
            +
                });
         | 
| 12 | 
            +
              });
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            :javascript
         | 
| 2 | 
            +
              function sortable_tree(item_id, parent_id, prev_id, next_id){
         | 
| 3 | 
            +
                jQuery.ajax({
         | 
| 4 | 
            +
                  type: 'POST',
         | 
| 5 | 
            +
                  url: '#{opts[:rebuild_url]}',
         | 
| 6 | 
            +
                  data: { id: item_id,
         | 
| 7 | 
            +
                          parent_id: parent_id,
         | 
| 8 | 
            +
                          prev_id: prev_id,
         | 
| 9 | 
            +
                          next_id: next_id },
         | 
| 10 | 
            +
                  dataType: 'script',
         | 
| 11 | 
            +
                  beforeSend: function(xhr){
         | 
| 12 | 
            +
                    //$('##{opts[:klass]}_nested_set .handle').hide();
         | 
| 13 | 
            +
                  },
         | 
| 14 | 
            +
                  success: function(data, status, xhr){
         | 
| 15 | 
            +
                    //$('##{opts[:klass]}_nested_set .handle').show();
         | 
| 16 | 
            +
                  },
         | 
| 17 | 
            +
                  error: function(xhr, status, error){
         | 
| 18 | 
            +
                    alert(error);
         | 
| 19 | 
            +
                  }
         | 
| 20 | 
            +
                });
         | 
| 21 | 
            +
              }
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            - if opts[:admin]
         | 
| 2 | 
            +
              - handle=   content_tag :div, raw(" "), :class => :handle
         | 
| 3 | 
            +
              - link=     link_to(node.title, url_for(node), :title => node.title)
         | 
| 4 | 
            +
              - content=  raw(handle.to_s) + controls + link
         | 
| 5 | 
            +
              %div{ :class => "link#{' root' if root}" }
         | 
| 6 | 
            +
                = content
         | 
| 7 | 
            +
            - else
         | 
| 8 | 
            +
              %div{:class => "link #{"root" if root}"}
         | 
| 9 | 
            +
                = link_to(node.title, polymorphic_path(node), :title => node.title)
         | 
| 10 | 
            +
             | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            = content_tag(:ol, raw(childs), :id=>"#{parent.id}_page_childs")
         | 
| @@ -0,0 +1,6 @@ | |
| 1 | 
            +
            = render :partial => "#{opts[:path]}/js_init_sortable_tree"
         | 
| 2 | 
            +
            = render :partial => "#{opts[:path]}/js_on_update_tree"
         | 
| 3 | 
            +
            = render :partial => "#{opts[:path]}/js_rebuild_ajax", :locals => { :opts => opts }
         | 
| 4 | 
            +
            %ol.sortable.ui-sortable.nested_set{ :id => "#{opts[:klass]}_nested_set" }
         | 
| 5 | 
            +
              = raw tree
         | 
| 6 | 
            +
            = render :partial => "#{opts[:path]}/new", :locals => { :opts => opts }
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            module TheSortableTree
         | 
| 2 | 
            +
              module Generators
         | 
| 3 | 
            +
                class ViewsGenerator < Rails::Generators::NamedBase
         | 
| 4 | 
            +
                  source_root File.expand_path('../../../../app/views', __FILE__)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def self.banner
         | 
| 7 | 
            +
                    <<-BANNER.chomp
         | 
| 8 | 
            +
            rails g the_sortable_tree:views MODEL
         | 
| 9 | 
            +
              Copies files for rendering sortable nested set
         | 
| 10 | 
            +
                    BANNER
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def copy_sortable_tree_files
         | 
| 14 | 
            +
                    directory "the_sortable_tree", "app/views/#{folder}/the_sortable_tree"
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                    
         | 
| 17 | 
            +
                  private
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def folder
         | 
| 20 | 
            +
                    name.pluralize.downcase
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                end#ViewsGenerator
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
    
        data/the_sortable_tree.gemspec
    CHANGED
    
    | @@ -8,8 +8,8 @@ Gem::Specification.new do |s| | |
| 8 8 | 
             
              s.authors     = ["Ilya N. Zykin"]
         | 
| 9 9 | 
             
              s.email       = ["zykin-ilya@ya.ru"]
         | 
| 10 10 | 
             
              s.homepage    = "https://github.com/the-teacher/the_sortable_tree"
         | 
| 11 | 
            -
              s.summary     = %q{ | 
| 12 | 
            -
              s.description = %q{ | 
| 11 | 
            +
              s.summary     = %q{Drug&Drop GUI for nested_set gem. Sortable tree view helper}
         | 
| 12 | 
            +
              s.description = %q{Drug&Drop GUI for nested_set gem. Sortable tree view helper}
         | 
| 13 13 |  | 
| 14 14 | 
             
              s.rubyforge_project = "the_sortable_tree"
         | 
| 15 15 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: the_sortable_tree
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 5 | 
             
              prerelease: 
         | 
| 6 6 | 
             
            platform: ruby
         | 
| 7 7 | 
             
            authors:
         | 
| @@ -9,11 +9,11 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2011- | 
| 12 | 
            +
            date: 2011-12-02 00:00:00.000000000Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: haml
         | 
| 16 | 
            -
              requirement: & | 
| 16 | 
            +
              requirement: &84037710 !ruby/object:Gem::Requirement
         | 
| 17 17 | 
             
                none: false
         | 
| 18 18 | 
             
                requirements:
         | 
| 19 19 | 
             
                - - ~>
         | 
| @@ -21,8 +21,8 @@ dependencies: | |
| 21 21 | 
             
                    version: '3.0'
         | 
| 22 22 | 
             
              type: :runtime
         | 
| 23 23 | 
             
              prerelease: false
         | 
| 24 | 
            -
              version_requirements: * | 
| 25 | 
            -
            description:  | 
| 24 | 
            +
              version_requirements: *84037710
         | 
| 25 | 
            +
            description: Drug&Drop GUI for nested_set gem. Sortable tree view helper
         | 
| 26 26 | 
             
            email:
         | 
| 27 27 | 
             
            - zykin-ilya@ya.ru
         | 
| 28 28 | 
             
            executables: []
         | 
| @@ -31,8 +31,47 @@ extra_rdoc_files: [] | |
| 31 31 | 
             
            files:
         | 
| 32 32 | 
             
            - .gitignore
         | 
| 33 33 | 
             
            - Gemfile
         | 
| 34 | 
            +
            - README.md
         | 
| 34 35 | 
             
            - Rakefile
         | 
| 36 | 
            +
            - app/assets/images/iconza/blue/add.png
         | 
| 37 | 
            +
            - app/assets/images/iconza/blue/delete.png
         | 
| 38 | 
            +
            - app/assets/images/iconza/blue/down.png
         | 
| 39 | 
            +
            - app/assets/images/iconza/blue/downloads_folder.png
         | 
| 40 | 
            +
            - app/assets/images/iconza/blue/edit.png
         | 
| 41 | 
            +
            - app/assets/images/iconza/blue/move.png
         | 
| 42 | 
            +
            - app/assets/images/iconza/blue/up.png
         | 
| 43 | 
            +
            - app/assets/images/iconza/gray/add.png
         | 
| 44 | 
            +
            - app/assets/images/iconza/gray/delete.png
         | 
| 45 | 
            +
            - app/assets/images/iconza/gray/down.png
         | 
| 46 | 
            +
            - app/assets/images/iconza/gray/edit.png
         | 
| 47 | 
            +
            - app/assets/images/iconza/gray/lock.png
         | 
| 48 | 
            +
            - app/assets/images/iconza/gray/mail.png
         | 
| 49 | 
            +
            - app/assets/images/iconza/gray/push_pin.png
         | 
| 50 | 
            +
            - app/assets/images/iconza/gray/up.png
         | 
| 51 | 
            +
            - app/assets/images/iconza/red/add.png
         | 
| 52 | 
            +
            - app/assets/images/iconza/red/delete.png
         | 
| 53 | 
            +
            - app/assets/images/iconza/red/down.png
         | 
| 54 | 
            +
            - app/assets/images/iconza/red/edit.png
         | 
| 55 | 
            +
            - app/assets/images/iconza/red/newspaper.png
         | 
| 56 | 
            +
            - app/assets/images/iconza/red/trash.png
         | 
| 57 | 
            +
            - app/assets/images/iconza/red/up.png
         | 
| 58 | 
            +
            - app/assets/images/iconza/red/zoom.png
         | 
| 59 | 
            +
            - app/assets/javascripts/jquery.ui.nestedSortable.js
         | 
| 60 | 
            +
            - app/assets/stylesheets/the_sortable_tree.css
         | 
| 61 | 
            +
            - app/controllers/the_sortable_tree_controller.rb
         | 
| 62 | 
            +
            - app/helpers/the_sortable_tree_helper.rb
         | 
| 63 | 
            +
            - app/views/the_sortable_tree/_controls.html.haml
         | 
| 64 | 
            +
            - app/views/the_sortable_tree/_js_init_sortable_tree.html.haml
         | 
| 65 | 
            +
            - app/views/the_sortable_tree/_js_on_update_tree.html.haml
         | 
| 66 | 
            +
            - app/views/the_sortable_tree/_js_rebuild_ajax.html.haml
         | 
| 67 | 
            +
            - app/views/the_sortable_tree/_link.html.haml
         | 
| 68 | 
            +
            - app/views/the_sortable_tree/_nested_set.html.haml
         | 
| 69 | 
            +
            - app/views/the_sortable_tree/_nested_set_item.html.haml
         | 
| 70 | 
            +
            - app/views/the_sortable_tree/_new.html.haml
         | 
| 71 | 
            +
            - app/views/the_sortable_tree/_tree.html.haml
         | 
| 72 | 
            +
            - lib/generators/the_sortable_tree/views_generator.rb
         | 
| 35 73 | 
             
            - lib/the_sortable_tree.rb
         | 
| 74 | 
            +
            - lib/the_sortable_tree/engine.rb
         | 
| 36 75 | 
             
            - lib/the_sortable_tree/version.rb
         | 
| 37 76 | 
             
            - the_sortable_tree.gemspec
         | 
| 38 77 | 
             
            homepage: https://github.com/the-teacher/the_sortable_tree
         | 
| @@ -58,5 +97,5 @@ rubyforge_project: the_sortable_tree | |
| 58 97 | 
             
            rubygems_version: 1.8.10
         | 
| 59 98 | 
             
            signing_key: 
         | 
| 60 99 | 
             
            specification_version: 3
         | 
| 61 | 
            -
            summary:  | 
| 100 | 
            +
            summary: Drug&Drop GUI for nested_set gem. Sortable tree view helper
         | 
| 62 101 | 
             
            test_files: []
         |