feat: Add comprehensive documentation suite and reorganize project structure
- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
BIN
f_modules/m_backend/m_tools/m_treeview/images/ajax-loader.gif
Normal file
|
After Width: | Height: | Size: 649 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/file.gif
Normal file
|
After Width: | Height: | Size: 109 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/folder-closed.gif
Normal file
|
After Width: | Height: | Size: 102 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/folder.gif
Normal file
|
After Width: | Height: | Size: 106 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/minus.gif
Normal file
|
After Width: | Height: | Size: 58 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/plus.gif
Normal file
|
After Width: | Height: | Size: 61 B |
|
After Width: | Height: | Size: 1010 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/treeview-black.gif
Normal file
|
After Width: | Height: | Size: 381 B |
|
After Width: | Height: | Size: 848 B |
|
After Width: | Height: | Size: 387 B |
|
After Width: | Height: | Size: 37 B |
|
After Width: | Height: | Size: 545 B |
|
After Width: | Height: | Size: 1010 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/treeview-gray.gif
Normal file
|
After Width: | Height: | Size: 394 B |
|
After Width: | Height: | Size: 1010 B |
BIN
f_modules/m_backend/m_tools/m_treeview/images/treeview-red.gif
Normal file
|
After Width: | Height: | Size: 394 B |
113
f_modules/m_backend/m_tools/m_treeview/jquery.treeview.async.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Async Treeview 0.1 - Lazy-loading extension for Treeview
|
||||
*
|
||||
* http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
|
||||
*
|
||||
* Copyright (c) 2007 Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id$
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
function load(settings, root, child, container) {
|
||||
function createNode(parent) {
|
||||
var current = $("<li/>").attr("id", this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
|
||||
if (this.classes) {
|
||||
current.children("span").addClass(this.classes);
|
||||
}
|
||||
if (this.expanded) {
|
||||
current.addClass("open");
|
||||
}
|
||||
if (this.hasChildren || this.children && this.children.length) {
|
||||
var branch = $("<ul/>").appendTo(current);
|
||||
if (this.hasChildren) {
|
||||
current.addClass("hasChildren");
|
||||
createNode.call({
|
||||
classes: "placeholder",
|
||||
text: " ",
|
||||
children:[]
|
||||
}, branch);
|
||||
}
|
||||
if (this.children && this.children.length) {
|
||||
$.each(this.children, createNode, [branch])
|
||||
}
|
||||
}
|
||||
}
|
||||
$.ajax($.extend(true, {
|
||||
url: settings.url,
|
||||
dataType: "json",
|
||||
data: {
|
||||
root: root
|
||||
},
|
||||
success: function(response) {
|
||||
child.empty();
|
||||
$.each(response, createNode, [child]);
|
||||
$(container).treeview({add: child});
|
||||
}
|
||||
}, settings.ajax));
|
||||
/*
|
||||
$.getJSON(settings.url, {root: root}, function(response) {
|
||||
function createNode(parent) {
|
||||
var current = $("<li/>").attr("id", this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
|
||||
if (this.classes) {
|
||||
current.children("span").addClass(this.classes);
|
||||
}
|
||||
if (this.expanded) {
|
||||
current.addClass("open");
|
||||
}
|
||||
if (this.hasChildren || this.children && this.children.length) {
|
||||
var branch = $("<ul/>").appendTo(current);
|
||||
if (this.hasChildren) {
|
||||
current.addClass("hasChildren");
|
||||
createNode.call({
|
||||
classes: "placeholder",
|
||||
text: " ",
|
||||
children:[]
|
||||
}, branch);
|
||||
}
|
||||
if (this.children && this.children.length) {
|
||||
$.each(this.children, createNode, [branch])
|
||||
}
|
||||
}
|
||||
}
|
||||
child.empty();
|
||||
$.each(response, createNode, [child]);
|
||||
$(container).treeview({add: child});
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
var proxied = $.fn.treeview;
|
||||
$.fn.treeview = function(settings) {
|
||||
if (!settings.url) {
|
||||
return proxied.apply(this, arguments);
|
||||
}
|
||||
if (!settings.root) {
|
||||
settings.root = "source";
|
||||
}
|
||||
var container = this;
|
||||
if (!container.children().size())
|
||||
load(settings, settings.root, this, container);
|
||||
var userToggle = settings.toggle;
|
||||
return proxied.call(this, $.extend({}, settings, {
|
||||
collapsed: true,
|
||||
toggle: function() {
|
||||
var $this = $(this);
|
||||
if ($this.hasClass("hasChildren")) {
|
||||
var childList = $this.removeClass("hasChildren").find("ul");
|
||||
load(settings, this.id, childList, container);
|
||||
}
|
||||
if (userToggle) {
|
||||
userToggle.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
75
f_modules/m_backend/m_tools/m_treeview/jquery.treeview.css
Normal file
@@ -0,0 +1,75 @@
|
||||
.treeview, .treeview ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.treeview ul {
|
||||
background-color: white;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.treeview .hitarea {
|
||||
background: url(images/treeview-default.gif) -64px -25px no-repeat;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-left: -16px;
|
||||
float: left;
|
||||
/* cursor: pointer;
|
||||
*/
|
||||
}
|
||||
/* fix for IE6 */
|
||||
* html .hitarea {
|
||||
display: inline;
|
||||
float:none;
|
||||
}
|
||||
|
||||
.treeview li {
|
||||
margin: 0;
|
||||
padding: 3px 0pt 3px 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.treeview a.selected {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
#treecontrol { margin: 1em 0; display: none; }
|
||||
|
||||
.treeview .hover { color: red; cursor: pointer; }
|
||||
|
||||
.treeview li { background: url(images/treeview-default-line.gif) 0 0 no-repeat; }
|
||||
.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; }
|
||||
|
||||
.treeview .expandable-hitarea { background-position: -80px -3px; }
|
||||
|
||||
.treeview li.last { background-position: 0 -1766px }
|
||||
.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(images/treeview-default.gif); }
|
||||
.treeview li.lastCollapsable { background-position: 0 -111px }
|
||||
.treeview li.lastExpandable { background-position: -32px -67px }
|
||||
|
||||
.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; }
|
||||
|
||||
.treeview-red li { background-image: url(images/treeview-red-line.gif); }
|
||||
.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(images/treeview-red.gif); }
|
||||
|
||||
.treeview-black li { background-image: url(images/treeview-black-line.gif); }
|
||||
.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(images/treeview-black.gif); }
|
||||
|
||||
.treeview-gray li { background-image: url(images/treeview-gray-line.gif); }
|
||||
.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(images/treeview-gray.gif); }
|
||||
|
||||
.treeview-famfamfam li { background-image: url(images/treeview-famfamfam-line.gif); }
|
||||
.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(images/treeview-famfamfam.gif); }
|
||||
|
||||
.treeview .placeholder {
|
||||
background: url(images/ajax-loader.gif) 0 0 no-repeat;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.filetree li { padding: 3px 0 2px 16px; }
|
||||
.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; }
|
||||
.filetree span.folder { background: url(images/folder.gif) 0 0 no-repeat; }
|
||||
.filetree li.expandable span.folder { background: url(images/folder-closed.gif) 0 0 no-repeat; }
|
||||
@@ -0,0 +1,37 @@
|
||||
(function($) {
|
||||
var CLASSES = $.treeview.classes;
|
||||
var proxied = $.fn.treeview;
|
||||
$.fn.treeview = function(settings) {
|
||||
settings = $.extend({}, settings);
|
||||
if (settings.add) {
|
||||
return this.trigger("add", [settings.add]);
|
||||
}
|
||||
if (settings.remove) {
|
||||
return this.trigger("remove", [settings.remove]);
|
||||
}
|
||||
return proxied.apply(this, arguments).bind("add", function(event, branches) {
|
||||
$(branches).prev()
|
||||
.removeClass(CLASSES.last)
|
||||
.removeClass(CLASSES.lastCollapsable)
|
||||
.removeClass(CLASSES.lastExpandable)
|
||||
.find(">.hitarea")
|
||||
.removeClass(CLASSES.lastCollapsableHitarea)
|
||||
.removeClass(CLASSES.lastExpandableHitarea);
|
||||
$(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, $(this).data("toggler"));
|
||||
}).bind("remove", function(event, branches) {
|
||||
var prev = $(branches).prev();
|
||||
var parent = $(branches).parent();
|
||||
$(branches).remove();
|
||||
prev.filter(":last-child").addClass(CLASSES.last)
|
||||
.filter("." + CLASSES.expandable).replaceClass(CLASSES.last, CLASSES.lastExpandable).end()
|
||||
.find(">.hitarea").replaceClass(CLASSES.expandableHitarea, CLASSES.lastExpandableHitarea).end()
|
||||
.filter("." + CLASSES.collapsable).replaceClass(CLASSES.last, CLASSES.lastCollapsable).end()
|
||||
.find(">.hitarea").replaceClass(CLASSES.collapsableHitarea, CLASSES.lastCollapsableHitarea);
|
||||
if (parent.is(":not(:has(>))") && parent[0] != this) {
|
||||
parent.parent().removeClass(CLASSES.collapsable).removeClass(CLASSES.expandable)
|
||||
parent.siblings(".hitarea").andSelf().remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
256
f_modules/m_backend/m_tools/m_treeview/jquery.treeview.js
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Treeview 1.5pre - jQuery plugin to hide and show branches of a tree
|
||||
*
|
||||
* http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
|
||||
* http://docs.jquery.com/Plugins/Treeview
|
||||
*
|
||||
* Copyright (c) 2007 Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id: jquery.treeview.js 5759 2008-07-01 07:50:28Z joern.zaefferer $
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
// TODO rewrite as a widget, removing all the extra plugins
|
||||
$.extend($.fn, {
|
||||
swapClass: function(c1, c2) {
|
||||
var c1Elements = this.filter('.' + c1);
|
||||
this.filter('.' + c2).removeClass(c2).addClass(c1);
|
||||
c1Elements.removeClass(c1).addClass(c2);
|
||||
return this;
|
||||
},
|
||||
replaceClass: function(c1, c2) {
|
||||
return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
|
||||
},
|
||||
hoverClass: function(className) {
|
||||
className = className || "hover";
|
||||
return this.hover(function() {
|
||||
$(this).addClass(className);
|
||||
}, function() {
|
||||
$(this).removeClass(className);
|
||||
});
|
||||
},
|
||||
heightToggle: function(animated, callback) {
|
||||
animated ?
|
||||
this.animate({ height: "toggle" }, animated, callback) :
|
||||
this.each(function(){
|
||||
jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
|
||||
if(callback)
|
||||
callback.apply(this, arguments);
|
||||
});
|
||||
},
|
||||
heightHide: function(animated, callback) {
|
||||
if (animated) {
|
||||
this.animate({ height: "hide" }, animated, callback);
|
||||
} else {
|
||||
this.hide();
|
||||
if (callback)
|
||||
this.each(callback);
|
||||
}
|
||||
},
|
||||
prepareBranches: function(settings) {
|
||||
if (!settings.prerendered) {
|
||||
// mark last tree items
|
||||
this.filter(":last-child:not(ul)").addClass(CLASSES.last);
|
||||
// collapse whole tree, or only those marked as closed, anyway except those marked as open
|
||||
this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
|
||||
}
|
||||
// return all items with sublists
|
||||
return this.filter(":has(>ul)");
|
||||
},
|
||||
applyClasses: function(settings, toggler) {
|
||||
// TODO use event delegation
|
||||
this.filter(":has(>ul):not(:has(>a))").find(">span").unbind("click.treeview").bind("click.treeview", function(event) {
|
||||
// don't handle click events on children, eg. checkboxes
|
||||
if ( this == event.target )
|
||||
toggler.apply($(this).next());
|
||||
}).add( $("a", this) ).hoverClass();
|
||||
|
||||
if (!settings.prerendered) {
|
||||
// handle closed ones first
|
||||
this.filter(":has(>ul:hidden)")
|
||||
.addClass(CLASSES.expandable)
|
||||
.replaceClass(CLASSES.last, CLASSES.lastExpandable);
|
||||
|
||||
// handle open ones
|
||||
this.not(":has(>ul:hidden)")
|
||||
.addClass(CLASSES.collapsable)
|
||||
.replaceClass(CLASSES.last, CLASSES.lastCollapsable);
|
||||
|
||||
// create hitarea if not present
|
||||
var hitarea = this.find("div." + CLASSES.hitarea);
|
||||
if (!hitarea.length)
|
||||
hitarea = this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea);
|
||||
hitarea.removeClass().addClass(CLASSES.hitarea).each(function() {
|
||||
var classes = "";
|
||||
$.each($(this).parent().attr("class").split(" "), function() {
|
||||
classes += this + "-hitarea ";
|
||||
});
|
||||
$(this).addClass( classes );
|
||||
})
|
||||
}
|
||||
|
||||
// apply event to hitarea
|
||||
this.find("div." + CLASSES.hitarea).click( toggler );
|
||||
},
|
||||
treeview: function(settings) {
|
||||
|
||||
settings = $.extend({
|
||||
cookieId: "treeview"
|
||||
}, settings);
|
||||
|
||||
if ( settings.toggle ) {
|
||||
var callback = settings.toggle;
|
||||
settings.toggle = function() {
|
||||
return callback.apply($(this).parent()[0], arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// factory for treecontroller
|
||||
function treeController(tree, control) {
|
||||
// factory for click handlers
|
||||
function handler(filter) {
|
||||
return function() {
|
||||
// reuse toggle event handler, applying the elements to toggle
|
||||
// start searching for all hitareas
|
||||
toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
|
||||
// for plain toggle, no filter is provided, otherwise we need to check the parent element
|
||||
return filter ? $(this).parent("." + filter).length : true;
|
||||
}) );
|
||||
return false;
|
||||
};
|
||||
}
|
||||
// click on first element to collapse tree
|
||||
$("a:eq(0)", control).click( handler(CLASSES.collapsable) );
|
||||
// click on second to expand tree
|
||||
$("a:eq(1)", control).click( handler(CLASSES.expandable) );
|
||||
// click on third to toggle tree
|
||||
$("a:eq(2)", control).click( handler() );
|
||||
}
|
||||
|
||||
// handle toggle event
|
||||
function toggler() {
|
||||
$(this)
|
||||
.parent()
|
||||
// swap classes for hitarea
|
||||
.find(">.hitarea")
|
||||
.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
|
||||
.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
|
||||
.end()
|
||||
// swap classes for parent li
|
||||
.swapClass( CLASSES.collapsable, CLASSES.expandable )
|
||||
.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
|
||||
// find child lists
|
||||
.find( ">ul" )
|
||||
// toggle them
|
||||
.heightToggle( settings.animated, settings.toggle );
|
||||
if ( settings.unique ) {
|
||||
$(this).parent()
|
||||
.siblings()
|
||||
// swap classes for hitarea
|
||||
.find(">.hitarea")
|
||||
.replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
|
||||
.replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
|
||||
.end()
|
||||
.replaceClass( CLASSES.collapsable, CLASSES.expandable )
|
||||
.replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
|
||||
.find( ">ul" )
|
||||
.heightHide( settings.animated, settings.toggle );
|
||||
}
|
||||
}
|
||||
this.data("toggler", toggler);
|
||||
|
||||
function serialize() {
|
||||
function binary(arg) {
|
||||
return arg ? 1 : 0;
|
||||
}
|
||||
var data = [];
|
||||
branches.each(function(i, e) {
|
||||
data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
|
||||
});
|
||||
$.cookie(settings.cookieId, data.join(""), settings.cookieOptions );
|
||||
}
|
||||
|
||||
function deserialize() {
|
||||
var stored = $.cookie(settings.cookieId);
|
||||
if ( stored ) {
|
||||
var data = stored.split("");
|
||||
branches.each(function(i, e) {
|
||||
$(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// add treeview class to activate styles
|
||||
this.addClass("treeview");
|
||||
|
||||
// prepare branches and find all tree items with child lists
|
||||
var branches = this.find("li").prepareBranches(settings);
|
||||
|
||||
switch(settings.persist) {
|
||||
case "cookie":
|
||||
var toggleCallback = settings.toggle;
|
||||
settings.toggle = function() {
|
||||
serialize();
|
||||
if (toggleCallback) {
|
||||
toggleCallback.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
deserialize();
|
||||
break;
|
||||
case "location":
|
||||
var current = this.find("a").filter(function() {
|
||||
return this.href.toLowerCase() == location.href.toLowerCase();
|
||||
});
|
||||
if ( current.length ) {
|
||||
// TODO update the open/closed classes
|
||||
var items = current.addClass("selected").parents("ul, li").add( current.next() ).show();
|
||||
if (settings.prerendered) {
|
||||
// if prerendered is on, replicate the basic class swapping
|
||||
items.filter("li")
|
||||
.swapClass( CLASSES.collapsable, CLASSES.expandable )
|
||||
.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
|
||||
.find(">.hitarea")
|
||||
.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
|
||||
.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
branches.applyClasses(settings, toggler);
|
||||
|
||||
// if control option is set, create the treecontroller and show it
|
||||
if ( settings.control ) {
|
||||
treeController(this, settings.control);
|
||||
$(settings.control).show();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
// classes used by the plugin
|
||||
// need to be styled via external stylesheet, see first example
|
||||
$.treeview = {};
|
||||
var CLASSES = ($.treeview.classes = {
|
||||
open: "open",
|
||||
closed: "closed",
|
||||
expandable: "expandable",
|
||||
expandableHitarea: "expandable-hitarea",
|
||||
lastExpandableHitarea: "lastExpandable-hitarea",
|
||||
collapsable: "collapsable",
|
||||
collapsableHitarea: "collapsable-hitarea",
|
||||
lastCollapsableHitarea: "lastCollapsable-hitarea",
|
||||
lastCollapsable: "lastCollapsable",
|
||||
lastExpandable: "lastExpandable",
|
||||
last: "last",
|
||||
hitarea: "hitarea"
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* jQuery UI Sortable
|
||||
*
|
||||
* Copyright (c) 2008 Paul Bakaus
|
||||
* Dual licensed under the MIT (MIT-LICENSE.txt)
|
||||
* and GPL (GPL-LICENSE.txt) licenses.
|
||||
*
|
||||
* http://docs.jquery.com/UI/Sortables
|
||||
*
|
||||
* Depends:
|
||||
* ui.base.js
|
||||
*
|
||||
* Revision: $Id: ui.sortable.js 5262 2008-04-17 13:13:51Z paul.bakaus $
|
||||
*/
|
||||
;(function($) {
|
||||
|
||||
if (window.Node && Node.prototype && !Node.prototype.contains) {
|
||||
Node.prototype.contains = function (arg) {
|
||||
return !!(this.compareDocumentPosition(arg) & 16);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
$.widget("ui.sortableTree", $.extend($.ui.mouse, {
|
||||
init: function() {
|
||||
|
||||
//Initialize needed constants
|
||||
var self = this, o = this.options;
|
||||
this.containerCache = {};
|
||||
this.element.addClass("ui-sortableTree");
|
||||
|
||||
//Get the items
|
||||
this.refresh();
|
||||
|
||||
//Let's determine the parent's offset
|
||||
if(!(/(relative|absolute|fixed)/).test(this.element.css('position'))) this.element.css('position', 'relative');
|
||||
this.offset = this.element.offset();
|
||||
|
||||
//Initialize mouse events for interaction
|
||||
this.mouseInit();
|
||||
|
||||
//Prepare cursorAt
|
||||
if(o.cursorAt && o.cursorAt.constructor == Array)
|
||||
o.cursorAt = { left: o.cursorAt[0], top: o.cursorAt[1] };
|
||||
|
||||
},
|
||||
plugins: {},
|
||||
ui: function(inst) {
|
||||
return {
|
||||
helper: (inst || this)["helper"],
|
||||
position: (inst || this)["position"].current,
|
||||
absolutePosition: (inst || this)["position"].absolute,
|
||||
instance: this,
|
||||
options: this.options,
|
||||
element: this.element,
|
||||
item: (inst || this)["currentItem"],
|
||||
sender: inst ? inst.element : null
|
||||
};
|
||||
},
|
||||
propagate: function(n,e,inst) {
|
||||
$.ui.plugin.call(this, n, [e, this.ui(inst)]);
|
||||
this.element.triggerHandler(n == "sort" ? n : "sort"+n, [e, this.ui(inst)], this.options[n]);
|
||||
},
|
||||
serialize: function(o) {
|
||||
|
||||
var items = $(this.options.items, this.element).not('.ui-sortableTree-helper'); //Only the items of the sortable itself
|
||||
var str = []; o = o || {};
|
||||
|
||||
items.each(function() {
|
||||
var res = ($(this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
|
||||
if(res) str.push((o.key || res[1])+'[]='+(o.key ? res[1] : res[2]));
|
||||
});
|
||||
|
||||
return str.join('&');
|
||||
|
||||
},
|
||||
toArray: function(attr) {
|
||||
var items = $(this.options.items, this.element).not('.ui-sortableTree-helper'); //Only the items of the sortable itself
|
||||
var ret = [];
|
||||
|
||||
items.each(function() { ret.push($(this).attr(attr || 'id')); });
|
||||
return ret;
|
||||
},
|
||||
enable: function() {
|
||||
this.element.removeClass("ui-sortableTree-disabled");
|
||||
this.options.disabled = false;
|
||||
},
|
||||
disable: function() {
|
||||
this.element.addClass("ui-sortableTree-disabled");
|
||||
this.options.disabled = true;
|
||||
},
|
||||
/* Be careful with the following core functions */
|
||||
intersectsWith: function(item) {
|
||||
|
||||
var x1 = this.position.absolute.left - 10, x2 = x1 + 10,
|
||||
y1 = this.position.absolute.top - 10, y2 = y1 + 10;
|
||||
var l = item.left, r = l + item.width,
|
||||
t = item.top, b = t + item.height;
|
||||
|
||||
return ( l < x1 + (this.helperProportions.width / 2) // Right Half
|
||||
&& x2 - (this.helperProportions.width / 2) < r // Left Half
|
||||
&& t < y1 + (this.helperProportions.height / 2) // Bottom Half
|
||||
&& y2 - (this.helperProportions.height / 2) < b ); // Top Half
|
||||
|
||||
},
|
||||
intersectsWithEdge: function(item) {
|
||||
var y1 = this.position.absolute.top - 10, y2 = y1 + 10;
|
||||
var t = item.top, b = t + item.height;
|
||||
|
||||
if(!this.intersectsWith(item.item.parents(".ui-sortableTree").data("sortableTree").containerCache)) return false;
|
||||
|
||||
if (!( t < y1 + (this.helperProportions.height / 2) // Bottom Half
|
||||
&& y2 - (this.helperProportions.height / 2) < b )) return false; // Top Half
|
||||
|
||||
if(y2 > t && y1 < t) return 1; //Crosses top edge
|
||||
if(y1 < b && y2 > b) return 2; //Crosses bottom edge
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
refresh: function() {
|
||||
this.refreshItems();
|
||||
this.refreshPositions();
|
||||
},
|
||||
refreshItems: function() {
|
||||
|
||||
this.items = [];
|
||||
this.containers = [this];
|
||||
var items = this.items;
|
||||
var queries = [$(this.options.items, this.element)];
|
||||
|
||||
if(this.options.connectWith) {
|
||||
for (var i = this.options.connectWith.length - 1; i >= 0; i--){
|
||||
var cur = $(this.options.connectWith[i]);
|
||||
for (var j = cur.length - 1; j >= 0; j--){
|
||||
var inst = $.data(cur[j], 'sortableTree');
|
||||
if(inst && !inst.options.disabled) {
|
||||
queries.push($(inst.options.items, inst.element));
|
||||
this.containers.push(inst);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = queries.length - 1; i >= 0; i--){
|
||||
queries[i].each(function() {
|
||||
$.data(this, 'sortableTree-item', true); // Data for target checking (mouse manager)
|
||||
items.push({
|
||||
item: $(this),
|
||||
width: 0, height: 0,
|
||||
left: 0, top: 0
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
},
|
||||
refreshPositions: function(fast) {
|
||||
for (var i = this.items.length - 1; i >= 0; i--){
|
||||
if(!fast) this.items[i].height = this.items[i].item.outerHeight();
|
||||
this.items[i].top = this.items[i].item.offset().top;
|
||||
};
|
||||
for (var i = this.containers.length - 1; i >= 0; i--){
|
||||
var p =this.containers[i].element.offset();
|
||||
this.containers[i].containerCache.left = p.left;
|
||||
this.containers[i].containerCache.top = p.top;
|
||||
this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
|
||||
this.containers[i].containerCache.height= this.containers[i].element.outerHeight();
|
||||
};
|
||||
},
|
||||
destroy: function() {
|
||||
|
||||
this.element
|
||||
.removeClass("ui-sortableTree ui-sortableTree-disabled")
|
||||
.removeData("sortableTree")
|
||||
.unbind(".sortableTree");
|
||||
this.mouseDestroy();
|
||||
|
||||
for ( var i = this.items.length - 1; i >= 0; i-- )
|
||||
this.items[i].item.removeData("sortableTree-item");
|
||||
|
||||
},
|
||||
contactContainers: function(e) {
|
||||
for (var i = this.containers.length - 1; i >= 0; i--){
|
||||
|
||||
if(this.intersectsWith(this.containers[i].containerCache)) {
|
||||
if(!this.containers[i].containerCache.over) {
|
||||
|
||||
if(this.currentContainer != this.containers[i]) {
|
||||
|
||||
//When entering a new container, we will find the item with the least distance and append our item near it
|
||||
var dist = 10000; var itemWithLeastDistance = null; var base = this.position.absolute.top;
|
||||
for (var j = this.items.length - 1; j >= 0; j--) {
|
||||
if(!this.containers[i].element[0].contains(this.items[j].item[0])) continue;
|
||||
var cur = this.items[j].top;
|
||||
if(Math.abs(cur - base) < dist) {
|
||||
dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
|
||||
}
|
||||
}
|
||||
|
||||
itemWithLeastDistance ? this.rearrange(e, itemWithLeastDistance) : this.rearrange(e, null, this.containers[i].element);
|
||||
this.propagate("change", e); //Call plugins and callbacks
|
||||
this.containers[i].propagate("change", e, this); //Call plugins and callbacks
|
||||
this.currentContainer = this.containers[i];
|
||||
|
||||
}
|
||||
|
||||
this.containers[i].propagate("over", e, this);
|
||||
this.containers[i].containerCache.over = 1;
|
||||
}
|
||||
} else {
|
||||
if(this.containers[i].containerCache.over) {
|
||||
this.containers[i].propagate("out", e, this);
|
||||
this.containers[i].containerCache.over = 0;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
mouseStart: function(e,el) {
|
||||
|
||||
if(this.options.disabled || this.options.type == 'static') return false;
|
||||
|
||||
//Find out if the clicked node (or one of its parents) is a actual item in this.items
|
||||
var currentItem = null, nodes = $(e.target).parents().each(function() {
|
||||
if($.data(this, 'sortableTree-item')) {
|
||||
currentItem = $(this);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if($.data(e.target, 'sortableTree-item')) currentItem = $(e.target);
|
||||
|
||||
if(!currentItem) return false;
|
||||
if(this.options.handle) {
|
||||
var validHandle = false;
|
||||
$(this.options.handle, currentItem).each(function() { if(this == e.target) validHandle = true; });
|
||||
if(!validHandle) return false;
|
||||
}
|
||||
|
||||
this.currentItem = currentItem;
|
||||
|
||||
var o = this.options;
|
||||
this.currentContainer = this;
|
||||
this.refresh();
|
||||
|
||||
//Create and append the visible helper
|
||||
this.helper = typeof o.helper == 'function' ? $(o.helper.apply(this.element[0], [e, this.currentItem])) : this.currentItem.clone();
|
||||
if(!this.helper.parents('body').length) this.helper.appendTo("body"); //Add the helper to the DOM if that didn't happen already
|
||||
this.helper.css({ position: 'absolute', clear: 'both' }).addClass('ui-sortableTree-helper'); //Position it absolutely and add a helper class
|
||||
|
||||
//Prepare variables for position generation
|
||||
$.extend(this, {
|
||||
offsetParent: this.helper.offsetParent(),
|
||||
offsets: { absolute: this.currentItem.offset() }
|
||||
});
|
||||
|
||||
//Save the first time position
|
||||
$.extend(this, {
|
||||
position: {
|
||||
current: { left: e.pageX, top: e.pageY },
|
||||
absolute: { left: e.pageX, top: e.pageY },
|
||||
dom: this.currentItem.prev()[0]
|
||||
},
|
||||
clickOffset: { left: -5, top: -5 }
|
||||
});
|
||||
|
||||
this.propagate("start", e); //Call plugins and callbacks
|
||||
this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() }; //Save and store the helper proportions
|
||||
|
||||
for (var i = this.containers.length - 1; i >= 0; i--) {
|
||||
this.containers[i].propagate("activate", e, this);
|
||||
} //Post 'activate' events to possible containers
|
||||
|
||||
//Prepare possible droppables
|
||||
if($.ui.ddmanager) $.ui.ddmanager.current = this;
|
||||
if ($.ui.ddmanager && !o.dropBehaviour) $.ui.ddmanager.prepareOffsets(this, e);
|
||||
|
||||
this.dragging = true;
|
||||
return true;
|
||||
|
||||
},
|
||||
mouseStop: function(e) {
|
||||
|
||||
if(this.newPositionAt) this.options.sortIndication.remove.call(this.currentItem, this.newPositionAt); //remove sort indicator
|
||||
this.propagate("stop", e); //Call plugins and trigger callbacks
|
||||
|
||||
//If we are using droppables, inform the manager about the drop
|
||||
var dropped = ($.ui.ddmanager && !this.options.dropBehaviour) ? $.ui.ddmanager.drop(this, e) : false;
|
||||
if(!dropped && this.newPositionAt) this.newPositionAt[this.direction == 'down' ? 'before' : 'after'](this.currentItem); //Append to element to its new position
|
||||
|
||||
if(this.position.dom != this.currentItem.prev()[0]) this.propagate("update", e); //Trigger update callback if the DOM position has changed
|
||||
if(!this.element[0].contains(this.currentItem[0])) { //Node was moved out of the current element
|
||||
this.propagate("remove", e);
|
||||
for (var i = this.containers.length - 1; i >= 0; i--){
|
||||
if(this.containers[i].element[0].contains(this.currentItem[0])) {
|
||||
this.containers[i].propagate("update", e, this);
|
||||
this.containers[i].propagate("receive", e, this);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
//Post events to containers
|
||||
for (var i = this.containers.length - 1; i >= 0; i--){
|
||||
this.containers[i].propagate("deactivate", e, this);
|
||||
if(this.containers[i].containerCache.over) {
|
||||
this.containers[i].propagate("out", e, this);
|
||||
this.containers[i].containerCache.over = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.dragging = false;
|
||||
if(this.cancelHelperRemoval) return false;
|
||||
this.helper.remove();
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
mouseDrag: function(e) {
|
||||
|
||||
//Compute the helpers position
|
||||
this.position.current = { top: e.pageY + 5, left: e.pageX + 5 };
|
||||
this.position.absolute = { left: e.pageX + 5, top: e.pageY + 5 };
|
||||
|
||||
//Interconnect with droppables
|
||||
if($.ui.ddmanager) $.ui.ddmanager.drag(this, e);
|
||||
var intersectsWithDroppable = false;
|
||||
$.each($.ui.ddmanager.droppables, function() {
|
||||
if(this.isover) intersectsWithDroppable = true;
|
||||
});
|
||||
|
||||
//Rearrange
|
||||
if(intersectsWithDroppable) {
|
||||
if(this.newPositionAt) this.options.sortIndication.remove.call(this.currentItem, this.newPositionAt);
|
||||
} else {
|
||||
for (var i = this.items.length - 1; i >= 0; i--) {
|
||||
|
||||
if(this.currentItem[0].contains(this.items[i].item[0])) continue;
|
||||
|
||||
var intersection = this.intersectsWithEdge(this.items[i]);
|
||||
if(!intersection) continue;
|
||||
|
||||
this.direction = intersection == 1 ? "down" : "up";
|
||||
this.rearrange(e, this.items[i]);
|
||||
this.propagate("change", e); //Call plugins and callbacks
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Post events to containers
|
||||
this.contactContainers(e);
|
||||
|
||||
this.propagate("sort", e); //Call plugins and callbacks
|
||||
this.helper.css({ left: this.position.current.left+'px', top: this.position.current.top+'px' }); // Stick the helper to the cursor
|
||||
return false;
|
||||
|
||||
},
|
||||
rearrange: function(e, i, a) {
|
||||
if(i) {
|
||||
if(this.newPositionAt) this.options.sortIndication.remove.call(this.currentItem, this.newPositionAt);
|
||||
this.newPositionAt = i.item;
|
||||
this.options.sortIndication[this.direction].call(this.currentItem, this.newPositionAt);
|
||||
} else {
|
||||
//Append
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
$.extend($.ui.sortableTree, {
|
||||
defaults: {
|
||||
items: '> *',
|
||||
zIndex: 1000,
|
||||
distance: 1
|
||||
},
|
||||
getter: "serialize toArray"
|
||||
});
|
||||
|
||||
|
||||
|
||||
})(jQuery);
|
||||