mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
(lazy loading) using the require.js. This allows us to load the javascript required for any node, only when it was loaded in the browser tree. Also, introduced the mechanism to show/edit/create of any node in a tab panel (wcDocker.Panel).
6986 lines
287 KiB
JavaScript
6986 lines
287 KiB
JavaScript
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* The aciTree low-level DOM functions.
|
|
*
|
|
* A collection of functions optimised for aciTree DOM structure.
|
|
*
|
|
* Need to be included before the aciTree core and after aciPlugin.
|
|
*/
|
|
|
|
aciPluginClass.plugins.aciTree_dom = {
|
|
// get the UL container from a LI
|
|
// `node` must be valid LI DOM node
|
|
// can return NULL
|
|
container: function(node) {
|
|
var container = node.lastChild;
|
|
if (container && (container.nodeName == 'UL')) {
|
|
return container;
|
|
}
|
|
return null;
|
|
},
|
|
// get the first children from a LI (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node
|
|
// can return NULL
|
|
firstChild: function(node, callback) {
|
|
var container = this.container(node);
|
|
if (container) {
|
|
var firstChild = container.firstChild;
|
|
if (callback) {
|
|
while (firstChild && !callback.call(this, firstChild)) {
|
|
firstChild = firstChild.nextSibling;
|
|
}
|
|
}
|
|
return firstChild;
|
|
}
|
|
return null;
|
|
},
|
|
// get the last children from a LI (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node
|
|
// can return NULL
|
|
lastChild: function(node, callback) {
|
|
var container = this.container(node);
|
|
if (container) {
|
|
var lastChild = container.lastChild;
|
|
if (callback) {
|
|
while (lastChild && !callback.call(this, lastChild)) {
|
|
lastChild = lastChild.previousSibling;
|
|
}
|
|
}
|
|
return lastChild;
|
|
}
|
|
return null;
|
|
},
|
|
// get the previous LI sibling (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node
|
|
// can return NULL
|
|
prev: function(node, callback) {
|
|
var previous = node.previousSibling;
|
|
if (callback) {
|
|
while (previous && !callback.call(this, previous)) {
|
|
previous = previous.previousSibling;
|
|
}
|
|
}
|
|
return previous;
|
|
},
|
|
// get the next LI sibling (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node
|
|
// can return NULL
|
|
next: function(node, callback) {
|
|
var next = node.nextSibling;
|
|
if (callback) {
|
|
while (next && !callback.call(this, next)) {
|
|
next = next.nextSibling;
|
|
}
|
|
}
|
|
return next;
|
|
},
|
|
// get the previous LI in tree order (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
|
|
// can return NULL
|
|
prevAll: function(node, callback) {
|
|
var previous, lastChild, drillDown, match, prev, parent;
|
|
while (true) {
|
|
previous = this.prev(node);
|
|
if (previous) {
|
|
if (callback) {
|
|
match = callback.call(this, previous);
|
|
if (match === null) {
|
|
node = previous;
|
|
continue;
|
|
}
|
|
}
|
|
lastChild = this.lastChild(previous);
|
|
if (lastChild) {
|
|
if (callback && (callback.call(this, lastChild) === null)) {
|
|
node = lastChild;
|
|
continue;
|
|
}
|
|
prev = false;
|
|
while (drillDown = this.lastChild(lastChild)) {
|
|
lastChild = drillDown;
|
|
if (callback) {
|
|
match = callback.call(this, lastChild);
|
|
if (match === null) {
|
|
node = lastChild;
|
|
prev = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (prev) {
|
|
continue;
|
|
}
|
|
if (callback) {
|
|
match = callback.call(this, lastChild);
|
|
if (match) {
|
|
return lastChild;
|
|
} else if (match !== null) {
|
|
node = lastChild;
|
|
continue;
|
|
}
|
|
} else {
|
|
return lastChild;
|
|
}
|
|
} else {
|
|
if (!callback || match) {
|
|
return previous;
|
|
} else {
|
|
node = previous;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
parent = this.parent(node);
|
|
if (parent) {
|
|
if (callback) {
|
|
match = callback.call(this, parent);
|
|
if (match) {
|
|
return parent;
|
|
} else {
|
|
node = parent;
|
|
}
|
|
} else {
|
|
return parent;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
// get the next LI in tree order (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
|
|
// can return NULL
|
|
nextAll: function(node, callback) {
|
|
var firstChild, match, next, parent, child;
|
|
while (true) {
|
|
firstChild = this.firstChild(node);
|
|
if (firstChild) {
|
|
if (callback) {
|
|
match = callback.call(this, firstChild);
|
|
if (match) {
|
|
return firstChild;
|
|
} else {
|
|
node = firstChild;
|
|
if (match !== null) {
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
return firstChild;
|
|
}
|
|
}
|
|
while (true) {
|
|
next = this.next(node);
|
|
if (next) {
|
|
if (callback) {
|
|
match = callback.call(this, next);
|
|
if (match) {
|
|
return next;
|
|
} else {
|
|
node = next;
|
|
if (match !== null) {
|
|
break;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
return next;
|
|
}
|
|
} else {
|
|
parent = node;
|
|
child = null;
|
|
while (parent = this.parent(parent)) {
|
|
next = this.next(parent);
|
|
if (next) {
|
|
if (callback) {
|
|
match = callback.call(this, next);
|
|
if (match) {
|
|
return next;
|
|
} else {
|
|
node = next;
|
|
if (match !== null) {
|
|
child = true;
|
|
} else {
|
|
child = false;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
return next;
|
|
}
|
|
}
|
|
}
|
|
if (child !== null) {
|
|
if (child) {
|
|
break;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
// get the first LI in tree order (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
|
|
// can return NULL
|
|
first: function(node, callback) {
|
|
var container = this.container(node);
|
|
if (container) {
|
|
var firstChild = container.firstChild;
|
|
if (firstChild) {
|
|
if (callback && !callback.call(this, firstChild)) {
|
|
return this.nextAll(firstChild, callback);
|
|
}
|
|
return firstChild;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
// get the last LI in tree order (with filtering)
|
|
// `node` must be valid LI DOM node
|
|
// `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
|
|
// can return NULL
|
|
last: function(node, callback) {
|
|
var container = this.container(node);
|
|
if (container) {
|
|
var lastChild = container.lastChild;
|
|
if (lastChild) {
|
|
if (callback && (callback.call(this, lastChild) === null)) {
|
|
return this.prevAll(lastChild, callback);
|
|
} else {
|
|
var drillDown;
|
|
while (drillDown = this.lastChild(lastChild)) {
|
|
lastChild = drillDown;
|
|
}
|
|
if (callback && !callback.call(this, lastChild)) {
|
|
return this.prevAll(lastChild, callback);
|
|
}
|
|
return lastChild;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
// get the children LI from the node
|
|
// `node` must be valid LI DOM node
|
|
// `drillDown` if TRUE all children are returned
|
|
// `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
|
|
children: function(node, drillDown, callback) {
|
|
var children = [], levels = [], match, next, skip;
|
|
var firstChild = this.firstChild(node);
|
|
if (firstChild) {
|
|
while (true) {
|
|
skip = false;
|
|
do {
|
|
if (callback) {
|
|
match = callback.call(this, firstChild);
|
|
if (match) {
|
|
children.push(firstChild);
|
|
}
|
|
if (drillDown && (match !== null)) {
|
|
next = this.firstChild(firstChild);
|
|
if (next) {
|
|
levels.push(firstChild);
|
|
firstChild = next;
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
children.push(firstChild);
|
|
if (drillDown) {
|
|
next = this.firstChild(firstChild);
|
|
if (next) {
|
|
levels.push(firstChild);
|
|
firstChild = next;
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} while (firstChild = firstChild.nextSibling);
|
|
if (!skip) {
|
|
while (firstChild = levels.pop()) {
|
|
firstChild = firstChild.nextSibling;
|
|
if (firstChild) {
|
|
break;
|
|
}
|
|
}
|
|
if (!firstChild) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return children;
|
|
},
|
|
// get a children from the node
|
|
// `node` must be valid DOM node
|
|
// `callback` can return FALSE to skip a node or NULL to stop the search
|
|
// can return NULL
|
|
childrenTill: function(node, callback) {
|
|
var levels = [], match, next, skip;
|
|
var firstChild = node.firstChild;
|
|
if (firstChild) {
|
|
while (true) {
|
|
skip = false;
|
|
do {
|
|
match = callback.call(this, firstChild);
|
|
if (match) {
|
|
return firstChild;
|
|
} else if (match === null) {
|
|
return null;
|
|
}
|
|
next = firstChild.firstChild;
|
|
if (next) {
|
|
levels.push(firstChild);
|
|
firstChild = next;
|
|
skip = true;
|
|
break;
|
|
}
|
|
} while (firstChild = firstChild.nextSibling);
|
|
if (!skip) {
|
|
while (firstChild = levels.pop()) {
|
|
firstChild = firstChild.nextSibling;
|
|
if (firstChild) {
|
|
break;
|
|
}
|
|
}
|
|
if (!firstChild) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
// get a children from the node having a class
|
|
// `node` must be valid DOM node
|
|
// `className` String or Array to check for
|
|
// can return NULL
|
|
childrenByClass: function(node, className) {
|
|
if (node.getElementsByClassName) {
|
|
var list = node.getElementsByClassName(className instanceof Array ? className.join(' ') : className);
|
|
return list ? list[0] : null;
|
|
} else {
|
|
return this.childrenTill(node, function(node) {
|
|
return this.hasClass(node, className);
|
|
});
|
|
}
|
|
},
|
|
// get the parent LI from the children LI
|
|
// `node` must be valid LI DOM node
|
|
// can return NULL
|
|
parent: function(node) {
|
|
var parent = node.parentNode.parentNode;
|
|
if (parent && (parent.nodeName == 'LI')) {
|
|
return parent;
|
|
}
|
|
return null;
|
|
},
|
|
// get the parent LI from any children
|
|
// `node` must be valid children of a LI DOM node
|
|
// can return NULL
|
|
parentFrom: function(node) {
|
|
while (node.nodeName != 'LI') {
|
|
node = node.parentNode;
|
|
if (!node) {
|
|
return null;
|
|
}
|
|
}
|
|
return node;
|
|
},
|
|
// get a parent from the node
|
|
// `node` must be valid DOM node
|
|
// `callback` can return FALSE to skip a node or NULL to stop the search
|
|
// can return NULL
|
|
parentTill: function(node, callback) {
|
|
var match;
|
|
while (node = node.parentNode) {
|
|
match = callback.call(this, node);
|
|
if (match) {
|
|
return node;
|
|
} else if (match === null) {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
// get a parent from the node having a class
|
|
// `node` must be valid DOM node
|
|
// `className` String or Array to check for
|
|
// can return NULL
|
|
parentByClass: function(node, className) {
|
|
return this.parentTill(node, function(node) {
|
|
return this.hasClass(node, className);
|
|
});
|
|
},
|
|
// test if node has class(es)
|
|
// `className` String or Array to check for
|
|
// `withOut` String or Array to exclude with
|
|
hasClass: function(node, className, withOut) {
|
|
var oldClass = ' ' + node.className + ' ';
|
|
if (withOut instanceof Array) {
|
|
for (var i = 0; i < withOut.length; i++) {
|
|
if (oldClass.indexOf(' ' + withOut[i] + ' ') != -1) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (withOut && oldClass.indexOf(' ' + withOut + ' ') != -1) {
|
|
return false;
|
|
}
|
|
}
|
|
if (className instanceof Array) {
|
|
for (var i = 0; i < className.length; i++) {
|
|
if (oldClass.indexOf(' ' + className[i] + ' ') == -1) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (className && oldClass.indexOf(' ' + className + ' ') == -1) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
// filter nodes with class(es)
|
|
// `nodes` Array of DOM nodes
|
|
// @see `hasClass`
|
|
withClass: function(nodes, className, withOut) {
|
|
var filter = [];
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (this.hasClass(nodes[i], className, withOut)) {
|
|
filter.push(nodes[i]);
|
|
}
|
|
}
|
|
return filter;
|
|
},
|
|
// test if node has any class(es)
|
|
// `className` String or Array to check for (any class)
|
|
// `withOut` String or Array to exclude with
|
|
hasAnyClass: function(node, className, withOut) {
|
|
var oldClass = ' ' + node.className + ' ';
|
|
if (withOut instanceof Array) {
|
|
for (var i = 0; i < withOut.length; i++) {
|
|
if (oldClass.indexOf(' ' + withOut[i] + ' ') != -1) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (withOut && oldClass.indexOf(' ' + withOut + ' ') != -1) {
|
|
return false;
|
|
}
|
|
}
|
|
if (className instanceof Array) {
|
|
for (var i = 0; i < className.length; i++) {
|
|
if (oldClass.indexOf(' ' + className[i] + ' ') != -1) {
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
if (className && oldClass.indexOf(' ' + className + ' ') != -1) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
// filter nodes with any class(es)
|
|
// `nodes` Array of DOM nodes
|
|
// @see `hasAnyClass`
|
|
withAnyClass: function(nodes, className, withOut) {
|
|
var filter = [];
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (this.hasAnyClass(nodes[i], className, withOut)) {
|
|
filter.push(nodes[i]);
|
|
}
|
|
}
|
|
return filter;
|
|
},
|
|
// add class(es) to node
|
|
// `node` must be valid DOM node
|
|
// `className` String or Array to add
|
|
// return TRUE if className changed
|
|
addClass: function(node, className) {
|
|
var oldClass = ' ' + node.className + ' ', append = '';
|
|
if (className instanceof Array) {
|
|
for (var i = 0; i < className.length; i++) {
|
|
if (oldClass.indexOf(' ' + className[i] + ' ') == -1) {
|
|
append += ' ' + className[i];
|
|
}
|
|
}
|
|
} else {
|
|
if (oldClass.indexOf(' ' + className + ' ') == -1) {
|
|
append += ' ' + className;
|
|
}
|
|
}
|
|
if (append) {
|
|
node.className = node.className + append;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
// add class(es) to nodes
|
|
// `nodes` Array of DOM nodes
|
|
// @see `addClass`
|
|
addListClass: function(nodes, className, callback) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
this.addClass(nodes[i], className);
|
|
if (callback) {
|
|
callback.call(this, nodes[i]);
|
|
}
|
|
}
|
|
},
|
|
// remove class(es) from node
|
|
// `node` must be valid DOM node
|
|
// `className` String or Array to remove
|
|
// return TRUE if className changed
|
|
removeClass: function(node, className) {
|
|
var oldClass = ' ' + node.className + ' ';
|
|
if (className instanceof Array) {
|
|
for (var i = 0; i < className.length; i++) {
|
|
oldClass = oldClass.replace(' ' + className[i] + ' ', ' ');
|
|
}
|
|
} else {
|
|
oldClass = oldClass.replace(' ' + className + ' ', ' ');
|
|
}
|
|
oldClass = oldClass.substr(1, oldClass.length - 2);
|
|
if (node.className != oldClass) {
|
|
node.className = oldClass;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
// remove class(es) from nodes
|
|
// `nodes` Array of DOM nodes
|
|
// @see `removeClass`
|
|
removeListClass: function(nodes, className, callback) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
this.removeClass(nodes[i], className);
|
|
if (callback) {
|
|
callback.call(this, nodes[i]);
|
|
}
|
|
}
|
|
},
|
|
// toggle node class(es)
|
|
// `node` must be valid DOM node
|
|
// `className` String or Array to toggle
|
|
// `add` TRUE to add them
|
|
// return TRUE if className changed
|
|
toggleClass: function(node, className, add) {
|
|
if (add) {
|
|
return this.addClass(node, className);
|
|
} else {
|
|
return this.removeClass(node, className);
|
|
}
|
|
},
|
|
// toggle nodes class(es)
|
|
// `nodes` Array of DOM nodes
|
|
// @see `toggleClass`
|
|
toggleListClass: function(nodes, className, add, callback) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
this.toggleClass(nodes[i], className, add);
|
|
if (callback) {
|
|
callback.call(this, nodes[i]);
|
|
}
|
|
}
|
|
},
|
|
// add/remove and keep old class(es)
|
|
// `node` must be valid DOM node
|
|
// `addClass` String or Array to add
|
|
// `removeClass` String or Array to remove
|
|
// return TRUE if className changed
|
|
addRemoveClass: function(node, addClass, removeClass) {
|
|
var oldClass = ' ' + node.className + ' ';
|
|
if (removeClass) {
|
|
if (removeClass instanceof Array) {
|
|
for (var i = 0; i < removeClass.length; i++) {
|
|
oldClass = oldClass.replace(' ' + removeClass[i] + ' ', ' ');
|
|
}
|
|
} else {
|
|
oldClass = oldClass.replace(' ' + removeClass + ' ', ' ');
|
|
}
|
|
}
|
|
if (addClass) {
|
|
var append = '';
|
|
if (addClass instanceof Array) {
|
|
for (var i = 0; i < addClass.length; i++) {
|
|
if (oldClass.indexOf(' ' + addClass[i] + ' ') == -1) {
|
|
append += addClass[i] + ' ';
|
|
}
|
|
}
|
|
} else {
|
|
if (oldClass.indexOf(' ' + addClass + ' ') == -1) {
|
|
append += addClass + ' ';
|
|
}
|
|
}
|
|
oldClass += append;
|
|
}
|
|
oldClass = oldClass.substr(1, oldClass.length - 2);
|
|
if (node.className != oldClass) {
|
|
node.className = oldClass;
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
// add/remove and keep old class(es)
|
|
// `nodes` Array of DOM nodes
|
|
// @see `addRemoveClass`
|
|
addRemoveListClass: function(nodes, addClass, removeClass, callback) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
this.addRemoveClass(nodes[i], addClass, removeClass);
|
|
if (callback) {
|
|
callback.call(this, nodes[i]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* The aciTree core.
|
|
*
|
|
* A few words about how item data looks like:
|
|
*
|
|
* for a leaf node (a node that does not have any children):
|
|
*
|
|
* {
|
|
* id: 'some_file_ID', // should be unique item ID
|
|
* label: 'This is a File Item', // the item label (text value)
|
|
* inode: false, // FALSE means is a leaf node (can be omitted)
|
|
* icon: 'fileIcon', // CSS class name for the icon (if any), can also be an Array ['CSS class name', background-position-x, background-position-y]
|
|
* disabled: false, // TRUE means the item is disabled (can be omitted)
|
|
* random_prop: 'random 1' // sample user defined property (you can have any number defined)
|
|
* }
|
|
*
|
|
* for a inner node (a node that have at least a children under it):
|
|
*
|
|
* {
|
|
* id: 'some_folder_ID', // should be unique item ID
|
|
* label: 'This is a Folder Item', // the item label (text value)
|
|
* inode: true, // can also be NULL to find at runtime if its an inode (on load will be transformed in a leaf node if there aren't any children)
|
|
* open: false, // if TRUE then the node will be opened when the tree is loaded (can be omitted)
|
|
* icon: 'folderIcon', // CSS class name for the icon (if any), can also be an Array ['CSS class name', background-position-x, background-position-y]
|
|
* disabled: false, // TRUE means the item is disabled (can be omitted)
|
|
* source: 'myDataSource', // the data source name (if any) to read the children from, by default `aciTree.options.ajax` is used
|
|
* branch: [ // a list of children
|
|
* { ... item data ... },
|
|
* { ... item data ... },
|
|
* ...
|
|
* ],
|
|
* random_prop: 'random 2' // sample user defined property (you can have any number defined)
|
|
* }
|
|
*
|
|
* The `branch` array can be empty, in this case the children will be loaded when the node will be opened for the first time.
|
|
*
|
|
* Please note that the item data should be valid (in the expected format). No checking is done and errors can appear on invalid data.
|
|
*
|
|
* One note about a item: a item is always the LI element with the class 'aciTreeLi'.
|
|
* The children of a node are all added under a UL element with the class 'aciTreeUl'.
|
|
*
|
|
* Almost all API functions expect only one item. If you need to process more at once then you'll need to loop between all of them yourself.
|
|
*
|
|
* The `options` parameter for all API methods (when there is one) is a object with the properties (not all are required or used):
|
|
*
|
|
* {
|
|
* uid: string -> operation UID (defaults to `ui`)
|
|
* success: function (item, options) -> callback to be called on success (you can access plugin API with `this` keyword inside the callback)
|
|
* fail: function (item, options) -> callback to be called on fail (you can access plugin API with `this` keyword inside the callback)
|
|
* notify: function (item, options) -> notify callback (internal use for when already in the requested state, will call `success` by default)
|
|
* expand: true/false -> propagate on open/toggle
|
|
* collapse: true/false -> propagate on close/toggle
|
|
* unique: true/false -> should other branches be closed (on open/toggle) ?
|
|
* unanimated: true/false -> if it's TRUE then no animations are to be run (used on open/close/toggle)
|
|
* itemData: object[item data]/array[item data] -> used when adding/updating items
|
|
* }
|
|
*
|
|
* Note: when using the API methods that support the `options` parameter, you will need to use the success/fail callbacks if you need to do
|
|
* any processing after the API call. This because there can be async operations that will complete at a later time and the API methods will
|
|
* exit before the job is actually completed. This will happen when items are loaded with AJAX, on animations and other delayed operations (see _queue).
|
|
*
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// default options
|
|
|
|
var options = {
|
|
// the AJAX options (see jQuery.ajax) where the `success` and `error` are overridden by aciTree
|
|
ajax: {
|
|
url: null, // URL from where to take the data, something like `path/script?nodeId=` (the node ID value will be added for each request)
|
|
dataType: 'json'
|
|
},
|
|
dataSource: null, // a list of data sources to be used (each entry in `aciTree.options.ajax` format)
|
|
rootData: null, // initial ROOT data for the Tree (if NULL then one initial AJAX request is made on init)
|
|
queue: {
|
|
async: 4, // the number of simultaneous async (AJAX) tasks
|
|
interval: 50, // interval [ms] after which to insert a `delay`
|
|
delay: 20 // how many [ms] delay between tasks (after `interval` expiration)
|
|
},
|
|
loaderDelay: 500, // how many msec to wait before showing the main loader ? (on lengthy operations)
|
|
expand: false, // if TRUE then all children of a node are expanded when the node is opened
|
|
collapse: false, // if TRUE then all children of a node are collapsed when the node is closed
|
|
unique: false, // if TRUE then a single tree branch will stay open, the oters are closed when a node is opened
|
|
empty: false, // if TRUE then all children of a node are removed when the node is closed
|
|
show: {// show node/ROOT animation (default is slideDown)
|
|
props: {
|
|
'height': 'show'
|
|
},
|
|
duration: 'medium',
|
|
easing: 'linear'
|
|
},
|
|
animateRoot: true, // if the ROOT should be animated on init
|
|
hide: {// hide node animation (default is slideUp)
|
|
props: {
|
|
'height': 'hide'
|
|
},
|
|
duration: 'medium',
|
|
easing: 'linear'
|
|
},
|
|
view: {// scroll item into view animation
|
|
duration: 'medium',
|
|
easing: 'linear'
|
|
},
|
|
// called for each AJAX request when a node needs to be loaded
|
|
// `item` is the item who will be loaded
|
|
// `settings` is the `aciTree.options.ajax` object or an entry from `aciTree.options.dataSource`
|
|
ajaxHook: function(item, settings) {
|
|
// the default implementation changes the URL by adding the item ID at the end
|
|
settings.url += (item ? this.getId(item) : '');
|
|
},
|
|
// called after each item is created but before is inserted into the DOM
|
|
// `parent` is the parent item (can be empty)
|
|
// `item` is the new created item
|
|
// `itemData` is the object used to create the item
|
|
// `level` is the #0 based item level
|
|
itemHook: function(parent, item, itemData, level) {
|
|
// there is no default implementation
|
|
},
|
|
// called for each item to serialize its value
|
|
// `item` is the tree item to be serialized
|
|
// `what` is the option telling what is being serialized
|
|
// `value` is the current serialized value (from the `item`, value type depending of `what`)
|
|
serialize: function(item, what, value) {
|
|
if (typeof what == 'object') {
|
|
return value;
|
|
} else {
|
|
// the default implementation uses a `|` (pipe) character to separate values
|
|
return '|' + value;
|
|
}
|
|
}
|
|
};
|
|
|
|
// aciTree plugin core
|
|
|
|
var aciTree_core = {
|
|
// add extra data
|
|
__extend: function() {
|
|
$.extend(this._instance, {
|
|
queue: new this._queue(this, this._instance.options.queue) // the global tree queue
|
|
});
|
|
$.extend(this._private, {
|
|
locked: false, // to tell the tree state
|
|
itemClone: {// keep a clone of the LI for faster tree item creation
|
|
},
|
|
// timeouts for the loader
|
|
loaderHide: null,
|
|
loaderInterval: null,
|
|
// busy delay counter
|
|
delayBusy: 0
|
|
});
|
|
},
|
|
// init the treeview
|
|
init: function(options) {
|
|
options = this._options(options);
|
|
// check if was init already
|
|
if (this.wasInit()) {
|
|
this._trigger(null, 'wasinit', options);
|
|
this._fail(null, options);
|
|
return;
|
|
}
|
|
// check if is locked
|
|
if (this.isLocked()) {
|
|
this._trigger(null, 'locked', options);
|
|
this._fail(null, options);
|
|
return;
|
|
}
|
|
// a way to cancel the operation
|
|
if (!this._trigger(null, 'beforeinit', options)) {
|
|
this._trigger(null, 'initfail', options);
|
|
this._fail(null, options);
|
|
return;
|
|
}
|
|
this._private.locked = true;
|
|
this._instance.jQuery.addClass('aciTree' + this._instance.index).attr('role', 'tree').on('click' + this._instance.nameSpace, '.aciTreeButton', this.proxy(function(e) {
|
|
// process click on button
|
|
var item = this.itemFrom(e.target);
|
|
// skip when busy
|
|
if (!this.isBusy(item)) {
|
|
// tree button pressed
|
|
this.toggle(item, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: this._instance.options.expand,
|
|
unique: this._instance.options.unique
|
|
});
|
|
}
|
|
})).on('mouseenter' + this._instance.nameSpace + ' mouseleave' + this._instance.nameSpace, '.aciTreePush', function(e) {
|
|
// handle the aciTreeHover class
|
|
var element = e.target;
|
|
if (!domApi.hasClass(element, 'aciTreePush')) {
|
|
element = domApi.parentByClass(element, 'aciTreePush');
|
|
}
|
|
domApi.toggleClass(element, 'aciTreeHover', e.type == 'mouseenter');
|
|
}).on('mouseenter' + this._instance.nameSpace + ' mouseleave' + this._instance.nameSpace, '.aciTreeLine', function(e) {
|
|
// handle the aciTreeHover class
|
|
var element = e.target;
|
|
if (!domApi.hasClass(element, 'aciTreeLine')) {
|
|
element = domApi.parentByClass(element, 'aciTreeLine');
|
|
}
|
|
domApi.toggleClass(element, 'aciTreeHover', e.type == 'mouseenter');
|
|
});
|
|
this._initHook();
|
|
// call on success
|
|
var success = this.proxy(function() {
|
|
// call the parent
|
|
this._super();
|
|
this._private.locked = false;
|
|
this._trigger(null, 'init', options);
|
|
this._success(null, options);
|
|
});
|
|
// call on fail
|
|
var fail = this.proxy(function() {
|
|
// call the parent
|
|
this._super();
|
|
this._private.locked = false;
|
|
this._trigger(null, 'initfail', options);
|
|
this._fail(null, options);
|
|
});
|
|
if (this._instance.options.rootData) {
|
|
// the rootData was set, use it to init the tree
|
|
this.loadFrom(null, this._inner(options, {
|
|
success: success,
|
|
fail: fail,
|
|
itemData: this._instance.options.rootData
|
|
}));
|
|
} else if (this._instance.options.ajax.url) {
|
|
// the AJAX url was set, init with AJAX
|
|
this.ajaxLoad(null, this._inner(options, {
|
|
success: success,
|
|
fail: fail
|
|
}));
|
|
} else {
|
|
success.apply(this);
|
|
}
|
|
},
|
|
_initHook: function() {
|
|
// override this to do extra init
|
|
},
|
|
// check locked state
|
|
isLocked: function() {
|
|
return this._private.locked;
|
|
},
|
|
// get a formatted message
|
|
// `raw` is the raw message text (can contain %NUMBER sequences, replaced with values from `params`)
|
|
// `params` is a list of values to be replaced into the message (by #0 based index)
|
|
_format: function(raw, params) {
|
|
if (!(params instanceof Array)) {
|
|
return raw;
|
|
}
|
|
var parts = raw.split(/(%[0-9]+)/gm);
|
|
var compile = '', part, index, last = false, len;
|
|
var test = new window.RegExp('^%[0-9]+$');
|
|
for (var i = 0; i < parts.length; i++) {
|
|
part = parts[i];
|
|
len = part.length;
|
|
if (len) {
|
|
if (!last && test.test(part)) {
|
|
index = window.parseInt(part.substr(1)) - 1;
|
|
if ((index >= 0) && (index < params.length)) {
|
|
compile += params[index];
|
|
continue;
|
|
}
|
|
} else {
|
|
last = false;
|
|
if (part.substr(len - 1) == '%') {
|
|
if (part.substr(len - 2) != '%%') {
|
|
last = true;
|
|
}
|
|
part = part.substr(0, len - 1);
|
|
}
|
|
}
|
|
compile += part;
|
|
}
|
|
}
|
|
return compile;
|
|
},
|
|
// low level DOM functions
|
|
_coreDOM: {
|
|
// set as leaf node
|
|
leaf: function(items) {
|
|
domApi.addRemoveListClass(items.toArray(), 'aciTreeLeaf', ['aciTreeInode', 'aciTreeInodeMaybe', 'aciTreeOpen'], function(node) {
|
|
node.firstChild.removeAttribute('aria-expanded');
|
|
});
|
|
},
|
|
// set as inner node
|
|
inode: function(items, branch) {
|
|
domApi.addRemoveListClass(items.toArray(), branch ? 'aciTreeInode' : 'aciTreeInodeMaybe', 'aciTreeLeaf', function(node) {
|
|
node.firstChild.setAttribute('aria-expanded', false);
|
|
});
|
|
},
|
|
// set as open/closed
|
|
toggle: function(items, state) {
|
|
domApi.toggleListClass(items.toArray(), 'aciTreeOpen', state, function(node) {
|
|
node.firstChild.setAttribute('aria-expanded', state);
|
|
});
|
|
},
|
|
// set odd/even classes
|
|
oddEven: function(items, odd) {
|
|
var list = items.toArray();
|
|
for (var i = 0; i < list.length; i++) {
|
|
domApi.addRemoveClass(list[i], odd ? 'aciTreeOdd' : 'aciTreeEven', odd ? 'aciTreeEven' : 'aciTreeOdd');
|
|
odd = !odd;
|
|
}
|
|
}
|
|
},
|
|
// a small queue implementation
|
|
// `context` the context to be used with `callback.call`
|
|
// `options` are the queue options
|
|
_queue: function(context, options) {
|
|
var locked = false;
|
|
var fifo = [], fifoAsync = [];
|
|
var load = 0, loadAsync = 0, schedule = 0, stack = 0;
|
|
// run the queue
|
|
var run = function() {
|
|
if (locked) {
|
|
stack--;
|
|
return;
|
|
}
|
|
var now = new window.Date().getTime();
|
|
if (schedule > now) {
|
|
stack--;
|
|
return;
|
|
}
|
|
var callback, async = false;
|
|
if (load < options.async * 2) {
|
|
// get the next synchronous callback
|
|
callback = fifo.shift();
|
|
}
|
|
if (!callback && (loadAsync < options.async)) {
|
|
// get the next async callback
|
|
callback = fifoAsync.shift();
|
|
async = true;
|
|
}
|
|
if (callback) {
|
|
// run the callback
|
|
if (async) {
|
|
loadAsync++;
|
|
callback.call(context, function() {
|
|
loadAsync--;
|
|
});
|
|
if (stack < 40) {
|
|
stack++;
|
|
run();
|
|
}
|
|
} else {
|
|
load++;
|
|
callback.call(context, function() {
|
|
if (now - schedule > options.interval) {
|
|
schedule = now + options.delay;
|
|
}
|
|
load--;
|
|
if (stack < 40) {
|
|
stack++;
|
|
run();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
stack--;
|
|
};
|
|
var interval = [];
|
|
// start the queue
|
|
var start = function() {
|
|
for (var i = 0; i < 4; i++) {
|
|
interval[i] = window.setInterval(function() {
|
|
if (stack < 20) {
|
|
stack++;
|
|
run();
|
|
}
|
|
}, 10);
|
|
}
|
|
};
|
|
// stop the queue
|
|
var stop = function() {
|
|
for (var i = 0; i < interval.length; i++) {
|
|
window.clearInterval(interval[i]);
|
|
}
|
|
};
|
|
start();
|
|
// init the queue
|
|
this.init = function() {
|
|
this.destroy();
|
|
start();
|
|
return this;
|
|
};
|
|
// push `callback` function (complete) for later call
|
|
// `async` tells if is async callback
|
|
this.push = function(callback, async) {
|
|
if (!locked) {
|
|
if (async) {
|
|
fifoAsync.push(callback);
|
|
} else {
|
|
fifo.push(callback);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
// test if busy
|
|
this.busy = function() {
|
|
return (load != 0) || (loadAsync != 0) || (fifo.length != 0) || (fifoAsync.length != 0);
|
|
};
|
|
// destroy queue
|
|
this.destroy = function() {
|
|
locked = true;
|
|
stop();
|
|
fifo = [];
|
|
fifoAsync = [];
|
|
load = 0;
|
|
loadAsync = 0;
|
|
schedule = 0;
|
|
stack = 0;
|
|
locked = false;
|
|
return this;
|
|
};
|
|
},
|
|
// used with a `queue` to execute something at the end
|
|
// `endCallback` function (complete) is the callback called at the end
|
|
_task: function(queue, endCallback) {
|
|
var counter = 0, finish = false;
|
|
// push a `callback` function (complete) for later call
|
|
this.push = function(callback, async) {
|
|
counter++;
|
|
queue.push(function(complete) {
|
|
var context = this;
|
|
callback.call(this, function() {
|
|
counter--;
|
|
if ((counter < 1) && !finish) {
|
|
finish = true;
|
|
endCallback.call(context, complete);
|
|
} else {
|
|
complete();
|
|
}
|
|
});
|
|
}, async);
|
|
};
|
|
},
|
|
// helper function to extend the `options` object
|
|
// `object` the initial options object
|
|
// _success, _fail, _notify are callbacks or string (the event name to be triggered)
|
|
// `item` is the item to trigger events for
|
|
_options: function(object, _success, _fail, _notify, item) {
|
|
// options object (need to be in this form for all API functions
|
|
// that have the `options` parameter, not all properties are required)
|
|
var options = $.extend({
|
|
uid: 'ui',
|
|
success: null, // success callback
|
|
fail: null, // fail callback
|
|
notify: null, // notify callback (internal use for when already in the requested state)
|
|
expand: this._instance.options.expand, // propagate (on open)
|
|
collapse: this._instance.options.collapse, // propagate (on close)
|
|
unique: this._instance.options.unique, // keep a single branch open (on open)
|
|
unanimated: false, // unanimated (open/close/toggle)
|
|
itemData: {
|
|
} // items data (object) or a list (array) of them (used when creating branches)
|
|
},
|
|
object);
|
|
var success = _success ? ((typeof _success == 'string') ? function() {
|
|
this._trigger(item, _success, options);
|
|
} : _success) : null;
|
|
var fail = _fail ? ((typeof _fail == 'string') ? function() {
|
|
this._trigger(item, _fail, options);
|
|
} : _fail) : null;
|
|
var notify = _notify ? ((typeof _notify == 'string') ? function() {
|
|
this._trigger(item, _notify, options);
|
|
} : _notify) : null;
|
|
if (success) {
|
|
// success callback
|
|
if (object && object.success) {
|
|
options.success = function() {
|
|
success.apply(this, arguments);
|
|
object.success.apply(this, arguments);
|
|
};
|
|
} else {
|
|
options.success = success;
|
|
}
|
|
}
|
|
if (fail) {
|
|
// fail callback
|
|
if (object && object.fail) {
|
|
options.fail = function() {
|
|
fail.apply(this, arguments);
|
|
object.fail.apply(this, arguments);
|
|
};
|
|
} else {
|
|
options.fail = fail;
|
|
}
|
|
}
|
|
if (notify) {
|
|
// notify callback
|
|
if (object && object.notify) {
|
|
options.notify = function() {
|
|
notify.apply(this, arguments);
|
|
object.notify.apply(this, arguments);
|
|
};
|
|
} else if (!options.notify && object && object.success) {
|
|
options.notify = function() {
|
|
notify.apply(this, arguments);
|
|
object.success.apply(this, arguments);
|
|
};
|
|
} else {
|
|
options.notify = notify;
|
|
}
|
|
} else if (!options.notify && object && object.success) {
|
|
// by default, run success callback
|
|
options.notify = object.success;
|
|
}
|
|
return options;
|
|
},
|
|
// helper for passing `options` object to inner methods
|
|
// the callbacks are removed and `override` can be used to update properties
|
|
_inner: function(options, override) {
|
|
// removing success/fail/notify from options
|
|
return $.extend({
|
|
}, options, {
|
|
success: null,
|
|
fail: null,
|
|
notify: null
|
|
},
|
|
override);
|
|
},
|
|
// trigger the aciTree events on the tree container
|
|
_trigger: function(item, eventName, options) {
|
|
var event = $.Event('acitree');
|
|
if (!options) {
|
|
options = this._options();
|
|
}
|
|
this._instance.jQuery.trigger(event, [this, item, eventName, options]);
|
|
return !event.isDefaultPrevented();
|
|
},
|
|
// call on success
|
|
_success: function(item, options) {
|
|
if (options && options.success) {
|
|
options.success.call(this, item, options);
|
|
}
|
|
},
|
|
// call on fail
|
|
_fail: function(item, options) {
|
|
if (options && options.fail) {
|
|
options.fail.call(this, item, options);
|
|
}
|
|
},
|
|
// call on notify (should be same as `success` but called when already in the requested state)
|
|
_notify: function(item, options) {
|
|
if (options && options.notify) {
|
|
options.notify.call(this, item, options);
|
|
}
|
|
},
|
|
// delay callback on busy item
|
|
_delayBusy: function(item, callback) {
|
|
if ((this._private.delayBusy < 10) && this.isBusy(item)) {
|
|
this._private.delayBusy++;
|
|
window.setTimeout(this.proxy(function() {
|
|
this._delayBusy.call(this, item, callback);
|
|
this._private.delayBusy--;
|
|
}), 10);
|
|
return;
|
|
}
|
|
callback.apply(this);
|
|
},
|
|
// return the data source for item
|
|
// defaults to `aciTree.options.ajax` if not set on the item/his parents
|
|
_dataSource: function(item) {
|
|
var dataSource = this._instance.options.dataSource;
|
|
if (dataSource) {
|
|
var data = this.itemData(item);
|
|
if (data && data.source && dataSource[data.source]) {
|
|
return dataSource[data.source];
|
|
}
|
|
var parent;
|
|
do {
|
|
parent = this.parent(item);
|
|
data = this.itemData(parent);
|
|
if (data && data.source && dataSource[data.source]) {
|
|
return dataSource[data.source];
|
|
}
|
|
} while (parent.length);
|
|
}
|
|
return this._instance.options.ajax;
|
|
},
|
|
// process item loading with AJAX
|
|
// `item` can be NULL to load the ROOT
|
|
// loaded data need to be array of item objects
|
|
// each item can have children (defined as `itemData.branch` - array of item data objects)
|
|
ajaxLoad: function(item, options) {
|
|
if (item && this.isBusy(item)) {
|
|
// delay the load if busy
|
|
this._delayBusy(item, function() {
|
|
this.ajaxLoad(item, options);
|
|
});
|
|
return;
|
|
}
|
|
options = this._options(options, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'loaded', options);
|
|
}, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'loadfail', options);
|
|
}, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'wasloaded', options);
|
|
});
|
|
if (!item || this.isInode(item)) {
|
|
// add the task to the queue
|
|
this._instance.queue.push(function(complete) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeload', options)) {
|
|
this._fail(item, options);
|
|
complete();
|
|
return;
|
|
}
|
|
this._loading(item, true);
|
|
if (this.wasLoad(item)) {
|
|
// was load already
|
|
this._notify(item, options);
|
|
complete();
|
|
return;
|
|
}
|
|
// ensure we work on a copy of the dataSource object
|
|
var settings = $.extend({
|
|
}, this._dataSource(item));
|
|
// call the `aciTree.options.ajaxHook`
|
|
this._instance.options.ajaxHook.call(this, item, settings);
|
|
// loaded data need to be array of item objects
|
|
settings.success = this.proxy(function(itemList) {
|
|
if (itemList && (itemList instanceof Array) && itemList.length) {
|
|
// the AJAX returned some items
|
|
var process = function() {
|
|
if (this.wasLoad(item)) {
|
|
this._notify(item, options);
|
|
complete();
|
|
} else {
|
|
// create a branch from `itemList`
|
|
this._createBranch(item, this._inner(options, {
|
|
success: function() {
|
|
this._success(item, options);
|
|
complete();
|
|
},
|
|
fail: function() {
|
|
this._fail(item, options);
|
|
complete();
|
|
},
|
|
itemData: itemList
|
|
}));
|
|
}
|
|
};
|
|
if (!item || this.isInode(item)) {
|
|
process.apply(this);
|
|
} else {
|
|
// change the item to inode, then load
|
|
this.setInode(item, this._inner(options, {
|
|
success: process,
|
|
fail: options.fail
|
|
}));
|
|
}
|
|
} else {
|
|
// the AJAX response was not just right (or not a inode)
|
|
var process = function() {
|
|
this._fail(item, options);
|
|
complete();
|
|
};
|
|
if (!item || this.isLeaf(item)) {
|
|
process.apply(this);
|
|
} else {
|
|
// change the item to leaf
|
|
this.setLeaf(item, this._inner(options, {
|
|
success: process,
|
|
fail: process
|
|
}));
|
|
}
|
|
}
|
|
});
|
|
settings.error = this.proxy(function() {
|
|
// AJAX failed
|
|
this._fail(item, options);
|
|
complete();
|
|
});
|
|
$.ajax(settings);
|
|
}, true);
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// process item loading
|
|
// `item` can be NULL to load the ROOT
|
|
// `options.itemData` need to be array of item objects
|
|
// each item can have children (defined as `itemData.branch` - array of item data objects)
|
|
loadFrom: function(item, options) {
|
|
if (item && this.isBusy(item)) {
|
|
// delay the load if busy
|
|
this._delayBusy(item, function() {
|
|
this.loadFrom(item, options);
|
|
});
|
|
return;
|
|
}
|
|
options = this._options(options, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'loaded', options);
|
|
}, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'loadfail', options);
|
|
}, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'wasloaded', options);
|
|
});
|
|
if (!item || this.isInode(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeload', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
this._loading(item, true);
|
|
if (this.wasLoad(item)) {
|
|
// was load already
|
|
this._notify(item, options);
|
|
return;
|
|
}
|
|
// data need to be array of item objects
|
|
if (options.itemData && (options.itemData instanceof Array) && options.itemData.length) {
|
|
// create the branch from `options.itemData`
|
|
var process = function() {
|
|
if (this.wasLoad(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
this._createBranch(item, options);
|
|
}
|
|
};
|
|
if (!item || this.isInode(item)) {
|
|
process.apply(this);
|
|
} else {
|
|
// change the item to inode, then create children
|
|
this.setInode(item, this._inner(options, {
|
|
success: process,
|
|
fail: options.fail
|
|
}));
|
|
}
|
|
} else {
|
|
// this is not a inode
|
|
if (!item || this.isLeaf(item)) {
|
|
this._fail(item, options);
|
|
} else {
|
|
// change the item to leaf
|
|
this.setLeaf(item, this._inner(options, {
|
|
success: options.fail,
|
|
fail: options.fail
|
|
}));
|
|
}
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// unload item
|
|
// `item` can be NULL to unload the entire tree
|
|
unload: function(item, options) {
|
|
options = this._options(options, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'unloaded', options);
|
|
}, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'unloadfail', options);
|
|
}, function() {
|
|
this._loading(item);
|
|
this._trigger(item, 'notloaded', options);
|
|
});
|
|
if (!item || this.isInode(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeunload', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
this._loading(item, true);
|
|
if (!this.wasLoad(item)) {
|
|
// if was not loaded
|
|
this._notify(item, options);
|
|
return;
|
|
}
|
|
// first check each children
|
|
var cancel = false;
|
|
var children = this.children(item, true, true);
|
|
children.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (this.isInode(item)) {
|
|
if (this.isOpen(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeclose', options)) {
|
|
cancel = true;
|
|
return false;
|
|
}
|
|
}
|
|
if (this.wasLoad(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeunload', options)) {
|
|
cancel = true;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeremove', options)) {
|
|
cancel = true;
|
|
return false;
|
|
}
|
|
}, true));
|
|
if (cancel) {
|
|
// it was canceled
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var process = function() {
|
|
children.each(this.proxy(function(element) {
|
|
// trigger the events before DOM changes
|
|
var item = $(element);
|
|
if (this.isInode(item)) {
|
|
if (this.isOpen(item)) {
|
|
this._trigger(item, 'closed', options);
|
|
}
|
|
if (this.wasLoad(item)) {
|
|
this._trigger(item, 'unloaded', options);
|
|
}
|
|
}
|
|
this._trigger(item, 'removed', options);
|
|
}, true));
|
|
};
|
|
// process the child remove
|
|
if (item) {
|
|
if (this.isOpen(item)) {
|
|
// first close the item, then remove children
|
|
this.close(item, this._inner(options, {
|
|
success: function() {
|
|
process.call(this);
|
|
this._removeContainer(item);
|
|
this._success(item, options);
|
|
},
|
|
fail: options.fail
|
|
}));
|
|
} else {
|
|
process.call(this);
|
|
this._removeContainer(item);
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
// unload the ROOT
|
|
this._animate(item, false, !this._instance.options.animateRoot || options.unanimated, function() {
|
|
process.call(this);
|
|
this._removeContainer();
|
|
this._success(item, options);
|
|
});
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// remove item
|
|
remove: function(item, options) {
|
|
if (this.isItem(item)) {
|
|
if (this.hasSiblings(item, true)) {
|
|
options = this._options(options, function() {
|
|
if (this.isOpenPath(item)) {
|
|
// if the parents are opened (visible) update the item states
|
|
domApi.removeClass(item[0], 'aciTreeVisible');
|
|
this._setOddEven(item);
|
|
}
|
|
this._trigger(item, 'removed', options);
|
|
}, 'removefail', null, item);
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeremove', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.wasLoad(item)) {
|
|
// unload the inode then remove
|
|
this.unload(item, this._inner(options, {
|
|
success: function() {
|
|
this._success(item, options);
|
|
this._removeItem(item);
|
|
},
|
|
fail: options.fail
|
|
}));
|
|
} else {
|
|
// just remove the item
|
|
this._success(item, options);
|
|
this._removeItem(item);
|
|
}
|
|
} else {
|
|
var parent = this.parent(item);
|
|
if (parent.length) {
|
|
this.setLeaf(parent, options);
|
|
} else {
|
|
this.unload(null, options);
|
|
}
|
|
}
|
|
} else {
|
|
this._trigger(item, 'removefail', options)
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// open item children
|
|
_openChildren: function(item, options) {
|
|
if (options.expand) {
|
|
var queue = this._instance.queue;
|
|
// process the children inodes
|
|
this.inodes(this.children(item)).each(function() {
|
|
var item = $(this);
|
|
// queue node opening
|
|
queue.push(function(complete) {
|
|
this.open(item, this._inner(options));
|
|
complete();
|
|
});
|
|
});
|
|
queue.push(function(complete) {
|
|
this._success(item, options);
|
|
complete();
|
|
});
|
|
} else {
|
|
this._success(item, options);
|
|
}
|
|
},
|
|
// process item open
|
|
_openItem: function(item, options) {
|
|
if (!options.unanimated && !this.isVisible(item)) {
|
|
options.unanimated = true;
|
|
}
|
|
if (options.unique) {
|
|
// close other opened nodes
|
|
this.closeOthers(item);
|
|
options.unique = false;
|
|
}
|
|
// open the node
|
|
this._coreDOM.toggle(item, true);
|
|
// (temporarily) update children states
|
|
this._setOddEvenChildren(item);
|
|
this._animate(item, true, options.unanimated, function() {
|
|
this._openChildren(item, options);
|
|
});
|
|
},
|
|
// open item and his children if requested
|
|
open: function(item, options) {
|
|
options = this._options(options, function() {
|
|
if (this.isOpenPath(item)) {
|
|
// if all parents are open, update the items after
|
|
this._updateVisible(item);
|
|
this._setOddEven(item);
|
|
}
|
|
this._trigger(item, 'opened', options);
|
|
}, 'openfail', 'wasopened', item);
|
|
if (this.isInode(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeopen', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isOpen(item)) {
|
|
options.success = options.notify;
|
|
// propagate/open children (if required)
|
|
this._openChildren(item, options);
|
|
} else {
|
|
if (this.wasLoad(item)) {
|
|
this._openItem(item, options);
|
|
} else {
|
|
// try to load the node, then open
|
|
this.ajaxLoad(item, this._inner(options, {
|
|
success: function() {
|
|
this._openItem(item, options);
|
|
},
|
|
fail: options.fail
|
|
}));
|
|
}
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// close item children
|
|
_closeChildren: function(item, options) {
|
|
if (this._instance.options.empty) {
|
|
// unload on close
|
|
options.unanimated = true;
|
|
this.unload(item, options);
|
|
} else if (options.collapse) {
|
|
var queue = this._instance.queue;
|
|
// process the children inodes
|
|
this.inodes(this.children(item)).each(function() {
|
|
var item = $(this);
|
|
// queue node close
|
|
queue.push(function(complete) {
|
|
this.close(item, this._inner(options, {
|
|
unanimated: true
|
|
}));
|
|
complete();
|
|
});
|
|
});
|
|
queue.push(function(complete) {
|
|
this._success(item, options);
|
|
complete();
|
|
});
|
|
} else {
|
|
this._success(item, options);
|
|
}
|
|
},
|
|
// process item close
|
|
_closeItem: function(item, options) {
|
|
if (!options.unanimated && !this.isVisible(item)) {
|
|
options.unanimated = true;
|
|
}
|
|
// close the item
|
|
this._coreDOM.toggle(item, false);
|
|
this._animate(item, false, options.unanimated, function() {
|
|
this._closeChildren(item, options);
|
|
});
|
|
},
|
|
// close item and his children if requested
|
|
close: function(item, options) {
|
|
options = this._options(options, function() {
|
|
if (this.isOpenPath(item)) {
|
|
// if all parents are open, update the items after
|
|
this._updateVisible(item);
|
|
this._setOddEven(item);
|
|
}
|
|
this._trigger(item, 'closed', options);
|
|
}, 'closefail', 'wasclosed', item);
|
|
if (this.isInode(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeclose', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isOpen(item)) {
|
|
this._closeItem(item, options);
|
|
} else if (this.wasLoad(item)) {
|
|
options.success = options.notify;
|
|
// propagate/close/empty children (if required)
|
|
this._closeChildren(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// update visible state
|
|
_updateVisible: function(item) {
|
|
if (this.isOpenPath(item)) {
|
|
if (!this.isHidden(item)) {
|
|
// if open parents and not hidden
|
|
domApi.addClass(item[0], 'aciTreeVisible');
|
|
if (this.isOpen(item)) {
|
|
// process children
|
|
domApi.children(item[0], false, this.proxy(function(node) {
|
|
if (!domApi.hasClass(node, 'aciTreeVisible')) {
|
|
this._updateVisible($(node));
|
|
}
|
|
}));
|
|
} else {
|
|
// children are not visible
|
|
domApi.children(item[0], true, function(node) {
|
|
return domApi.removeClass(node, 'aciTreeVisible') ? true : null;
|
|
});
|
|
}
|
|
}
|
|
} else if (domApi.removeClass(item[0], 'aciTreeVisible')) {
|
|
domApi.children(item[0], true, function(node) {
|
|
return domApi.removeClass(node, 'aciTreeVisible') ? true : null;
|
|
});
|
|
}
|
|
},
|
|
// keep just one branch open
|
|
closeOthers: function(item, options) {
|
|
options = this._options(options);
|
|
if (this.isItem(item)) {
|
|
var queue = this._instance.queue;
|
|
// exclude the item and his parents
|
|
var exclude = item.add(this.path(item)).add(this.children(item, true));
|
|
// close all other open nodes
|
|
this.inodes(this.children(null, true, true), true).not(exclude).each(function() {
|
|
var item = $(this);
|
|
// add node to close queue
|
|
queue.push(function(complete) {
|
|
this.close(item, this._inner(options));
|
|
complete();
|
|
});
|
|
});
|
|
queue.push(function(complete) {
|
|
this._success(item, options);
|
|
complete();
|
|
});
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// toggle item
|
|
toggle: function(item, options) {
|
|
options = this._options(options, 'toggled', 'togglefail', null, item);
|
|
if (this.isInode(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforetoggle', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isOpen(item)) {
|
|
this.close(item, options);
|
|
} else {
|
|
this.open(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// get item path starting from the top parent (ROOT)
|
|
// when `reverse` is TRUE returns the path in reverse order
|
|
path: function(item, reverse) {
|
|
if (item) {
|
|
var parent = item[0], list = [];
|
|
while (parent = domApi.parent(parent)) {
|
|
list.push(parent);
|
|
}
|
|
return reverse ? $(list) : $(list.reverse());
|
|
}
|
|
return $([]);
|
|
},
|
|
// test if item is in view
|
|
// when `center` is TRUE will test if is centered in view
|
|
isVisible: function(item, center) {
|
|
if (item && domApi.hasClass(item[0], 'aciTreeVisible')) {
|
|
// the item path need to be open
|
|
var rect = this._instance.jQuery[0].getBoundingClientRect();
|
|
var size = item[0].firstChild;
|
|
var test = size.getBoundingClientRect();
|
|
var height = size.offsetHeight;
|
|
var offset = center ? (rect.bottom - rect.top) / 2 : 0;
|
|
if ((test.bottom - height < rect.top + offset) || (test.top + height > rect.bottom - offset)) {
|
|
// is out of view
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
// open path to item
|
|
openPath: function(item, options) {
|
|
options = this._options(options);
|
|
if (this.isItem(item)) {
|
|
var queue = this._instance.queue;
|
|
// process closed inodes
|
|
this.inodes(this.path(item), false).each(function() {
|
|
var item = $(this);
|
|
// add node to open queue
|
|
queue.push(function(complete) {
|
|
this.open(item, this._inner(options));
|
|
complete();
|
|
});
|
|
});
|
|
queue.push(function(complete) {
|
|
this._success(item, options);
|
|
complete();
|
|
});
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if path to item is open
|
|
isOpenPath: function(item) {
|
|
var parent = this.parent(item);
|
|
return parent[0] ? this.isOpen(parent) && domApi.hasClass(parent[0], 'aciTreeVisible') : true;
|
|
},
|
|
// get animation speed vs. offset size
|
|
// `speed` is the raw speed
|
|
// `totalSize` is the available size
|
|
// `required` is the offset used for calculations
|
|
_speedFraction: function(speed, totalSize, required) {
|
|
if ((required < totalSize) && totalSize) {
|
|
var numeric = parseInt(speed);
|
|
if (isNaN(numeric)) {
|
|
// predefined string values
|
|
switch (speed) {
|
|
case 'slow':
|
|
numeric = 600;
|
|
break;
|
|
case 'medium':
|
|
numeric = 400;
|
|
break;
|
|
case 'fast':
|
|
numeric = 200;
|
|
break;
|
|
default:
|
|
return speed;
|
|
}
|
|
}
|
|
return numeric * required / totalSize;
|
|
}
|
|
return speed;
|
|
},
|
|
// bring item in view
|
|
// `options.center` says if should be centered in view
|
|
setVisible: function(item, options) {
|
|
options = this._options(options, 'visible', 'visiblefail', 'wasvisible', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforevisible', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isVisible(item)) {
|
|
// is visible already
|
|
this._notify(item, options);
|
|
return;
|
|
}
|
|
var process = function() {
|
|
// compute position with getBoundingClientRect
|
|
var rect = this._instance.jQuery[0].getBoundingClientRect();
|
|
var size = item[0].firstChild;
|
|
var test = size.getBoundingClientRect();
|
|
var height = size.offsetHeight;
|
|
var offset = options.center ? (rect.bottom - rect.top) / 2 : 0;
|
|
if (test.bottom - height < rect.top + offset) {
|
|
// item somewhere before the first visible
|
|
var diff = rect.top + offset - test.bottom + height;
|
|
if (!options.unanimated && this._instance.options.view) {
|
|
this._instance.jQuery.stop(true).animate({
|
|
scrollTop: this._instance.jQuery.scrollTop() - diff
|
|
},
|
|
{
|
|
duration: this._speedFraction(this._instance.options.view.duration, rect.bottom - rect.top, diff),
|
|
easing: this._instance.options.view.easing,
|
|
complete: this.proxy(function() {
|
|
this._success(item, options);
|
|
})
|
|
});
|
|
} else {
|
|
this._instance.jQuery.stop(true)[0].scrollTop = this._instance.jQuery.scrollTop() - diff;
|
|
this._success(item, options);
|
|
}
|
|
} else if (test.top + height > rect.bottom - offset) {
|
|
// item somewhere after the last visible
|
|
var diff = test.top - rect.bottom + offset + height;
|
|
if (!options.unanimated && this._instance.options.view) {
|
|
this._instance.jQuery.stop(true).animate({
|
|
scrollTop: this._instance.jQuery.scrollTop() + diff
|
|
},
|
|
{
|
|
duration: this._speedFraction(this._instance.options.view.duration, rect.bottom - rect.top, diff),
|
|
easing: this._instance.options.view.easing,
|
|
complete: this.proxy(function() {
|
|
this._success(item, options);
|
|
})
|
|
});
|
|
} else {
|
|
this._instance.jQuery.stop(true)[0].scrollTop = this._instance.jQuery.scrollTop() + diff;
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._success(item, options);
|
|
}
|
|
};
|
|
if (this.hasParent(item)) {
|
|
// first we need to open the path to item
|
|
this.openPath(item, this._inner(options, {
|
|
success: process,
|
|
fail: options.fail
|
|
}));
|
|
} else {
|
|
process.apply(this);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if item has parent
|
|
hasParent: function(item) {
|
|
return this.parent(item).length > 0;
|
|
},
|
|
// get item parent
|
|
parent: function(item) {
|
|
return item ? $(domApi.parent(item[0])) : $([]);
|
|
},
|
|
// get item top (ROOT) parent
|
|
topParent: function(item) {
|
|
return this.path(item).eq(0);
|
|
},
|
|
// create tree branch
|
|
// `options.itemData` need to be in the same format as for .append
|
|
_createBranch: function(item, options) {
|
|
var total = 0;
|
|
var count = function(itemList) {
|
|
var itemData;
|
|
for (var i = 0; i < itemList.length; i++) {
|
|
itemData = itemList[i];
|
|
if (itemData.branch && (itemData.branch instanceof Array) && itemData.branch.length) {
|
|
count(itemData.branch);
|
|
}
|
|
}
|
|
total++;
|
|
};
|
|
count(options.itemData);
|
|
var index = 0;
|
|
var complete = this.proxy(function() {
|
|
index++;
|
|
if (index >= total) {
|
|
this._success(item, options);
|
|
}
|
|
});
|
|
var process = this.proxy(function(node, itemList) {
|
|
if (node) {
|
|
// set it as a inode
|
|
domApi.addRemoveClass(node[0], 'aciTreeInode', 'aciTreeInodeMaybe');
|
|
}
|
|
// use .append to add new items
|
|
this.append(node, this._inner(options, {
|
|
success: function(item, options) {
|
|
var itemData;
|
|
for (var i = 0; i < options.itemData.length; i++) {
|
|
itemData = options.itemData[i];
|
|
// children need to be array of item objects
|
|
if (itemData.branch && (itemData.branch instanceof Array) && itemData.branch.length) {
|
|
process(options.items.eq(i), itemData.branch);
|
|
}
|
|
if (itemData.open) {
|
|
// open the item is requuested
|
|
this.open(options.items.eq(i), this._inner(options, {
|
|
itemData: null,
|
|
items: null
|
|
}));
|
|
}
|
|
}
|
|
complete();
|
|
},
|
|
fail: options.fail,
|
|
itemData: itemList
|
|
}));
|
|
});
|
|
process(item, options.itemData);
|
|
},
|
|
// get first/last items
|
|
_getFirstLast: function(parent) {
|
|
if (!parent) {
|
|
parent = this._instance.jQuery;
|
|
}
|
|
return $(domApi.withAnyClass(domApi.children(parent[0]), ['aciTreeFirst', 'aciTreeLast']));
|
|
},
|
|
// update first/last items
|
|
_setFirstLast: function(parent, clear) {
|
|
if (clear) {
|
|
domApi.removeListClass(clear.toArray(), ['aciTreeFirst', 'aciTreeLast']);
|
|
}
|
|
var first = this.first(parent);
|
|
if (first[0]) {
|
|
domApi.addClass(first[0], 'aciTreeFirst');
|
|
domApi.addClass(this.last(parent)[0], 'aciTreeLast');
|
|
}
|
|
},
|
|
// update odd/even state
|
|
_setOddEven: function(items) {
|
|
// consider only visible items
|
|
var visible;
|
|
if (this._instance.jQuery[0].getElementsByClassName) {
|
|
visible = this._instance.jQuery[0].getElementsByClassName('aciTreeVisible');
|
|
visible = visible ? window.Array.prototype.slice.call(visible) : [];
|
|
} else {
|
|
visible = $(domApi.children(this._instance.jQuery[0], true, function(node) {
|
|
return this.hasClass(node, 'aciTreeVisible') ? true : null;
|
|
}));
|
|
}
|
|
var odd = true;
|
|
if (visible.length) {
|
|
var index = 0;
|
|
if (items) {
|
|
// search the item to start with (by index)
|
|
items.each(function() {
|
|
if (visible.indexOf) {
|
|
var found = visible.indexOf(this);
|
|
if (found != -1) {
|
|
index = window.Math.min(found, index);
|
|
}
|
|
} else {
|
|
for (var i = 0; i < visible.length; i++) {
|
|
if (visible[i] === this) {
|
|
index = window.Math.min(i, index);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
index = window.Math.max(index - 1, 0);
|
|
}
|
|
if (index > 0) {
|
|
// determine with what to start with (odd/even)
|
|
var first = visible[index];
|
|
if (domApi.hasClass(first, 'aciTreOdd')) {
|
|
odd = false;
|
|
}
|
|
// process only after index
|
|
visible = visible.slice(index + 1);
|
|
}
|
|
}
|
|
this._coreDOM.oddEven($(visible), odd);
|
|
},
|
|
// update odd/even state for direct children
|
|
_setOddEvenChildren: function(item) {
|
|
var odd = domApi.hasClass(item[0], 'aciTreeOdd');
|
|
var children = this.children(item);
|
|
this._coreDOM.oddEven(children, !odd);
|
|
},
|
|
// process item before inserting into the DOM
|
|
_itemHook: function(parent, item, itemData, level) {
|
|
if (this._instance.options.itemHook) {
|
|
this._instance.options.itemHook.apply(this, arguments);
|
|
}
|
|
},
|
|
// create item by `itemData`
|
|
// `level` is the #0 based item level
|
|
_createItem: function(itemData, level) {
|
|
if (this._private.itemClone[level]) {
|
|
var li = this._private.itemClone[level].cloneNode(true);
|
|
var line = li.firstChild;
|
|
var icon = line;
|
|
for (var i = 0; i < level; i++) {
|
|
icon = icon.firstChild;
|
|
}
|
|
icon = icon.firstChild.lastChild.firstChild;
|
|
var text = icon.nextSibling;
|
|
} else {
|
|
var li = window.document.createElement('LI');
|
|
li.setAttribute('role', 'presentation');
|
|
var line = window.document.createElement('DIV');
|
|
li.appendChild(line);
|
|
line.setAttribute('tabindex', -1);
|
|
line.setAttribute('role', 'treeitem');
|
|
line.setAttribute('aria-selected', false);
|
|
line.className = 'aciTreeLine';
|
|
var last = line, branch;
|
|
for (var i = 0; i < level; i++) {
|
|
branch = window.document.createElement('DIV');
|
|
last.appendChild(branch);
|
|
branch.className = 'aciTreeBranch aciTreeLevel' + i;
|
|
last = branch;
|
|
}
|
|
var entry = window.document.createElement('DIV');
|
|
last.appendChild(entry);
|
|
entry.className = 'aciTreeEntry';
|
|
var button = window.document.createElement('SPAN');
|
|
entry.appendChild(button);
|
|
button.className = 'aciTreeButton';
|
|
var push = window.document.createElement('SPAN');
|
|
button.appendChild(push);
|
|
push.className = 'aciTreePush';
|
|
push.appendChild(window.document.createElement('SPAN'));
|
|
var item = window.document.createElement('SPAN');
|
|
entry.appendChild(item);
|
|
item.className = 'aciTreeItem';
|
|
var icon = window.document.createElement('SPAN');
|
|
item.appendChild(icon);
|
|
var text = window.document.createElement('SPAN');
|
|
item.appendChild(text);
|
|
text.className = 'aciTreeText';
|
|
this._private.itemClone[level] = li.cloneNode(true);
|
|
}
|
|
li.className = 'aciTreeLi' + (itemData.inode || (itemData.inode === null) ? (itemData.inode || (itemData.branch && itemData.branch.length) ? ' aciTreeInode' : ' aciTreeInodeMaybe') : ' aciTreeLeaf') + ' aciTreeLevel' + level + (itemData.disabled ? ' aciTreeDisabled' : '');
|
|
line.setAttribute('aria-level', level + 1);
|
|
if (itemData.inode || (itemData.inode === null)) {
|
|
line.setAttribute('aria-expanded', false);
|
|
}
|
|
if (itemData.icon) {
|
|
if (itemData.icon instanceof Array) {
|
|
icon.className = 'aciTreeIcon ' + itemData.icon[0];
|
|
icon.style.backgroundPosition = itemData.icon[1] + 'px ' + itemData.icon[2] + 'px';
|
|
} else {
|
|
icon.className = 'aciTreeIcon ' + itemData.icon;
|
|
}
|
|
} else {
|
|
icon.parentNode.removeChild(icon);
|
|
}
|
|
text.innerHTML = itemData.label;
|
|
var $li = $(li);
|
|
$li.data('itemData' + this._instance.nameSpace, $.extend({
|
|
}, itemData, {
|
|
branch: itemData.branch && itemData.branch.length
|
|
}));
|
|
return $li;
|
|
},
|
|
// remove item
|
|
_removeItem: function(item) {
|
|
var parent = this.parent(item);
|
|
item.remove();
|
|
// update sibling state
|
|
this._setFirstLast(parent.length ? parent : null);
|
|
},
|
|
// create & add one or more items
|
|
// `ul`, `before` and `after` are set depending on the caller
|
|
// `itemData` need to be array of objects or just an object (one item)
|
|
// `level` is the #0 based level
|
|
// `callback` function (items) is called at the end of the operation
|
|
_createItems: function(ul, before, after, itemData, level, callback) {
|
|
var items = [], fragment = window.document.createDocumentFragment();
|
|
var task = new this._task(this._instance.queue, function(complete) {
|
|
items = $(items);
|
|
if (items.length) {
|
|
// add the new items
|
|
if (ul) {
|
|
ul[0].appendChild(fragment);
|
|
} else if (before) {
|
|
before[0].parentNode.insertBefore(fragment, before[0]);
|
|
} else if (after) {
|
|
after[0].parentNode.insertBefore(fragment, after[0].nextSibling);
|
|
}
|
|
}
|
|
callback.call(this, items);
|
|
complete();
|
|
});
|
|
if (itemData) {
|
|
this._loader(true);
|
|
var parent;
|
|
if (ul) {
|
|
parent = this.itemFrom(ul);
|
|
} else if (before) {
|
|
parent = this.parent(before);
|
|
} else if (after) {
|
|
parent = this.parent(after);
|
|
}
|
|
if (itemData instanceof Array) {
|
|
// this is a list of items
|
|
for (var i = 0; i < itemData.length; i++) {
|
|
(function(itemData) {
|
|
task.push(function(complete) {
|
|
var item = this._createItem(itemData, level);
|
|
this._itemHook(parent, item, itemData, level);
|
|
fragment.appendChild(item[0]);
|
|
items.push(item[0]);
|
|
complete();
|
|
});
|
|
})(itemData[i]);
|
|
}
|
|
} else {
|
|
task.push(function(complete) {
|
|
// only one item
|
|
var item = this._createItem(itemData, level);
|
|
this._itemHook(parent, item, itemData, level);
|
|
fragment.appendChild(item[0]);
|
|
items.push(item[0]);
|
|
complete();
|
|
});
|
|
}
|
|
}
|
|
// run at least once
|
|
task.push(function(complete) {
|
|
complete();
|
|
});
|
|
},
|
|
// create children container
|
|
_createContainer: function(item) {
|
|
if (!item) {
|
|
item = this._instance.jQuery;
|
|
}
|
|
// ensure we have a UL in place
|
|
var ul = domApi.container(item[0]);
|
|
if (!ul) {
|
|
var ul = window.document.createElement('UL');
|
|
ul.setAttribute('role', 'group');
|
|
ul.className = 'aciTreeUl';
|
|
ul.style.display = 'none';
|
|
item[0].appendChild(ul);
|
|
}
|
|
return $(ul);
|
|
},
|
|
// remove children container
|
|
_removeContainer: function(item) {
|
|
if (!item) {
|
|
item = this._instance.jQuery;
|
|
}
|
|
var ul = domApi.container(item[0]);
|
|
ul.parentNode.removeChild(ul);
|
|
},
|
|
// append one or more items to item
|
|
// `options.itemData` can be a item object or array of item objects
|
|
// `options.items` will keep a list of added items
|
|
append: function(item, options) {
|
|
options = this._options(options, 'appended', 'appendfail', null, item);
|
|
if (item) {
|
|
if (this.isInode(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeappend', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var container = this._createContainer(item);
|
|
var last = this.last(item);
|
|
this._createItems(container, null, null, options.itemData, this.level(item) + 1, function(list) {
|
|
if (list.length) {
|
|
// some items created, update states
|
|
domApi.addRemoveClass(item[0], 'aciTreeInode', 'aciTreeInodeMaybe');
|
|
this._setFirstLast(item, last);
|
|
if (this.isHidden(item)) {
|
|
domApi.addListClass(list.toArray(), 'aciTreeHidden');
|
|
} else if (this.isOpenPath(item) && this.isOpen(item)) {
|
|
domApi.addListClass(list.toArray(), 'aciTreeVisible');
|
|
this._setOddEven(list.first());
|
|
}
|
|
// trigger `added` for each item
|
|
list.each(this.proxy(function(element) {
|
|
this._trigger($(element), 'added', options);
|
|
}, true));
|
|
} else if (!this.hasChildren(item, true)) {
|
|
container.remove();
|
|
}
|
|
options.items = list;
|
|
this._success(item, options);
|
|
});
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
} else {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeappend', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var container = this._createContainer();
|
|
var last = this.last();
|
|
this._createItems(container, null, null, options.itemData, 0, function(list) {
|
|
if (list.length) {
|
|
// some items created, update states
|
|
this._setFirstLast(null, last);
|
|
domApi.addListClass(list.toArray(), 'aciTreeVisible');
|
|
this._setOddEven();
|
|
// trigger `added` for each item
|
|
list.each(this.proxy(function(element) {
|
|
this._trigger($(element), 'added', options);
|
|
}, true));
|
|
this._animate(null, true, !this._instance.options.animateRoot || options.unanimated);
|
|
} else if (!this.hasChildren(null, true)) {
|
|
// remove the children container
|
|
container.remove();
|
|
}
|
|
options.items = list;
|
|
this._success(item, options);
|
|
});
|
|
}
|
|
},
|
|
// insert one or more items before item
|
|
// `options.itemData` can be a item object or array of item objects
|
|
// `options.items` will keep a list of added items
|
|
before: function(item, options) {
|
|
options = this._options(options, 'before', 'beforefail', null, item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforebefore', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var prev = this.prev(item);
|
|
this._createItems(null, item, null, options.itemData, this.level(item), function(list) {
|
|
if (list.length) {
|
|
// some items created, update states
|
|
if (!prev.length) {
|
|
domApi.removeClass(item[0], 'aciTreeFirst');
|
|
domApi.addClass(list.first()[0], 'aciTreeFirst');
|
|
}
|
|
var parent = this.parent(item);
|
|
if (parent.length && this.isHidden(parent)) {
|
|
domApi.addListClass(list.toArray(), 'aciTreeHidden');
|
|
} else if (this.isOpenPath(item)) {
|
|
domApi.addListClass(list.toArray(), 'aciTreeVisible');
|
|
this._setOddEven(list.first());
|
|
}
|
|
// trigger `added` for each item
|
|
list.each(this.proxy(function(element) {
|
|
this._trigger($(element), 'added', options);
|
|
}, true));
|
|
}
|
|
options.items = list;
|
|
this._success(item, options);
|
|
});
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// insert one or more items after item
|
|
// `options.itemData` can be a item object or array of item objects
|
|
// `options.items` will keep a list of added items
|
|
after: function(item, options) {
|
|
options = this._options(options, 'after', 'afterfail', null, item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeafter', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var next = this.next(item);
|
|
this._createItems(null, null, item, options.itemData, this.level(item), function(list) {
|
|
if (list.length) {
|
|
// some items created, update states
|
|
if (!next.length) {
|
|
domApi.removeClass(item[0], 'aciTreeLast');
|
|
domApi.addClass(list.last()[0], 'aciTreeLast');
|
|
}
|
|
var parent = this.parent(item);
|
|
if (parent.length && this.isHidden(parent)) {
|
|
domApi.addListClass(list.toArray(), 'aciTreeHidden');
|
|
} else if (this.isOpenPath(item)) {
|
|
domApi.addListClass(list.toArray(), 'aciTreeVisible');
|
|
this._setOddEven(list.first());
|
|
}
|
|
// trigger `added` for each item
|
|
list.each(this.proxy(function(element) {
|
|
this._trigger($(element), 'added', options);
|
|
}, true));
|
|
}
|
|
options.items = list;
|
|
this._success(item, options);
|
|
});
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// get item having the element
|
|
itemFrom: function(element) {
|
|
if (element) {
|
|
var item = $(element);
|
|
if (item[0] === this._instance.jQuery[0]) {
|
|
return $([]);
|
|
} else {
|
|
return $(domApi.parentFrom(item[0]));
|
|
}
|
|
}
|
|
return $([]);
|
|
},
|
|
// get item children
|
|
// if `branch` is TRUE then all children are returned
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
children: function(item, branch, hidden) {
|
|
return $(domApi.children(item && item[0] ? item[0] : this._instance.jQuery[0], branch, hidden ? null : function(node) {
|
|
return this.hasClass(node, 'aciTreeHidden') ? null : true;
|
|
}));
|
|
},
|
|
// filter only the visible items (items with all parents opened)
|
|
// if `view` is TRUE then only the items in view are returned
|
|
visible: function(items, view) {
|
|
var list = domApi.withClass(items.toArray(), 'aciTreeVisible');
|
|
if (view) {
|
|
var filter = [];
|
|
for (var i = 0; i < list.length; i++) {
|
|
if (this.isVisible($(list[i]))) {
|
|
filter.push(list[i]);
|
|
}
|
|
}
|
|
return $(filter);
|
|
}
|
|
return $(list);
|
|
},
|
|
// filter only inner nodes from items
|
|
// if `state` is set then filter only open/closed ones
|
|
inodes: function(items, state) {
|
|
if (state !== undefined) {
|
|
if (state) {
|
|
return $(domApi.withClass(items.toArray(), 'aciTreeOpen'));
|
|
} else {
|
|
return $(domApi.withAnyClass(items.toArray(), ['aciTreeInode', 'aciTreeInodeMaybe'], 'aciTreeOpen'));
|
|
}
|
|
}
|
|
return $(domApi.withAnyClass(items.toArray(), ['aciTreeInode', 'aciTreeInodeMaybe']));
|
|
},
|
|
// filter only leaf nodes from items
|
|
leaves: function(items) {
|
|
return $(domApi.withClass(items.toArray(), 'aciTreeLeaf'));
|
|
},
|
|
// test if is a inner node
|
|
isInode: function(item) {
|
|
return item && domApi.hasAnyClass(item[0], ['aciTreeInode', 'aciTreeInodeMaybe']);
|
|
},
|
|
// test if is a leaf node
|
|
isLeaf: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeLeaf');
|
|
},
|
|
// test if item was loaded
|
|
wasLoad: function(item) {
|
|
if (item) {
|
|
return domApi.container(item[0]) !== null;
|
|
}
|
|
return domApi.container(this._instance.jQuery[0]) !== null;
|
|
},
|
|
// set item as inner node
|
|
setInode: function(item, options) {
|
|
options = this._options(options, 'inodeset', 'inodefail', 'wasinode', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeinode', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isLeaf(item)) {
|
|
this._coreDOM.inode(item, true);
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// set item as leaf node
|
|
setLeaf: function(item, options) {
|
|
options = this._options(options, 'leafset', 'leaffail', 'wasleaf', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeleaf', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isInode(item)) {
|
|
var process = function() {
|
|
this._coreDOM.leaf(item);
|
|
this._success(item, options);
|
|
};
|
|
if (this.wasLoad(item)) {
|
|
// first unload the node
|
|
this.unload(item, this._inner(options, {
|
|
success: process,
|
|
fail: options.fail
|
|
}));
|
|
} else {
|
|
process.apply(this);
|
|
}
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// add/update item icon
|
|
// `options.icon` can be the CSS class name or array['CSS class name', background-position-x, background-position-y]
|
|
// `options.oldIcon` will keep the old icon
|
|
addIcon: function(item, options) {
|
|
options = this._options(options, 'iconadded', 'addiconfail', 'wasicon', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeaddicon', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var data = this.itemData(item);
|
|
// keep the old one
|
|
options.oldIcon = data.icon;
|
|
var parent = domApi.childrenByClass(item[0].firstChild, 'aciTreeItem');
|
|
var found = domApi.childrenByClass(parent, 'aciTreeIcon');
|
|
if (found && data.icon && (options.icon.toString() == data.icon.toString())) {
|
|
this._notify(item, options);
|
|
} else {
|
|
if (!found) {
|
|
found = window.document.createElement('DIV');
|
|
parent.insertBefore(found, parent.firstChild);
|
|
}
|
|
if (options.icon instanceof Array) {
|
|
// icon with background-position
|
|
found.className = 'aciTreeIcon ' + options.icon[0];
|
|
found.style.backgroundPosition = options.icon[1] + 'px ' + options.icon[2] + 'px';
|
|
} else {
|
|
// only the CSS class name
|
|
found.className = 'aciTreeIcon ' + options.icon;
|
|
}
|
|
// remember this one
|
|
data.icon = options.icon;
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// remove item icon
|
|
// options.oldIcon will keep the old icon
|
|
removeIcon: function(item, options) {
|
|
options = this._options(options, 'iconremoved', 'removeiconfail', 'noticon', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeremoveicon', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var data = this.itemData(item);
|
|
// keep the old one
|
|
options.oldIcon = data.icon;
|
|
var parent = domApi.childrenByClass(item[0].firstChild, 'aciTreeItem');
|
|
var found = domApi.childrenByClass(parent, 'aciTreeIcon');
|
|
if (found) {
|
|
parent.removeChild(found);
|
|
// remember was removed
|
|
data.icon = null;
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if item has icon
|
|
hasIcon: function(item) {
|
|
return !!this.getIcon(item);
|
|
},
|
|
// get item icon
|
|
getIcon: function(item) {
|
|
var data = this.itemData(item);
|
|
return data ? data.icon : null;
|
|
},
|
|
// set item label
|
|
// `options.label` is the new label
|
|
// `options.oldLabel` will keep the old label
|
|
setLabel: function(item, options) {
|
|
options = this._options(options, 'labelset', 'labelfail', 'waslabel', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforelabel', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var data = this.itemData(item);
|
|
// keep the old one
|
|
options.oldLabel = data.label;
|
|
if (options.label == options.oldLabel) {
|
|
this._notify(item, options);
|
|
} else {
|
|
// set the label
|
|
domApi.childrenByClass(item[0].firstChild, 'aciTreeText').innerHTML = options.label;
|
|
// remember this one
|
|
data.label = options.label;
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// disable item
|
|
disable: function(item, options) {
|
|
options = this._options(options, 'disabled', 'disablefail', 'wasdisabled', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforedisable', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isDisabled(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
domApi.addClass(item[0], 'aciTreeDisabled');
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if item is disabled
|
|
isDisabled: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeDisabled');
|
|
},
|
|
// test if any of parents are disabled
|
|
isDisabledPath: function(item) {
|
|
return domApi.withClass(this.path(item).toArray(), 'aciTreeDisabled').length > 0;
|
|
},
|
|
// filter only the disabled items
|
|
disabled: function(items) {
|
|
return $(domApi.withClass(items.toArray(), 'aciTreeDisabled'));
|
|
},
|
|
// enable item
|
|
enable: function(item, options) {
|
|
options = this._options(options, 'enabled', 'enablefail', 'wasenabled', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeenable', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isDisabled(item)) {
|
|
domApi.removeClass(item[0], 'aciTreeDisabled');
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if item is enabled
|
|
isEnabled: function(item) {
|
|
return item && !domApi.hasClass(item[0], 'aciTreeDisabled');
|
|
},
|
|
// test if all parents are enabled
|
|
isEnabledPath: function(item) {
|
|
return domApi.withClass(this.path(item).toArray(), 'aciTreeDisabled').length == 0;
|
|
},
|
|
// filter only the enabled items
|
|
enabled: function(items) {
|
|
return $(domApi.withClass(items.toArray(), null, 'aciTreeDisabled'));
|
|
},
|
|
// set item as hidden
|
|
hide: function(item, options) {
|
|
options = this._options(options, 'hidden', 'hidefail', 'washidden', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforehide', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isHidden(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
domApi.addRemoveClass(item[0], 'aciTreeHidden', 'aciTreeVisible');
|
|
// process children
|
|
domApi.addRemoveClass(this.children(item, true).toArray(), 'aciTreeHidden', 'aciTreeVisible');
|
|
// update item states
|
|
var parent = this.parent(item);
|
|
this._setFirstLast(parent.length ? parent : null, item);
|
|
this._setOddEven(item);
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if item is hidden
|
|
isHidden: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeHidden');
|
|
},
|
|
// test if any of parents are hidden
|
|
isHiddenPath: function(item) {
|
|
var parent = this.parent(item);
|
|
return parent[0] && domApi.hasClass(parent[0], 'aciTreeHidden');
|
|
},
|
|
// update hidden state
|
|
_updateHidden: function(item) {
|
|
if (this.isHiddenPath(item)) {
|
|
if (!this.isHidden(item)) {
|
|
domApi.addClass(item[0], 'aciTreeHidden');
|
|
this._updateVisible(item);
|
|
}
|
|
} else {
|
|
this._updateVisible(item);
|
|
}
|
|
},
|
|
// filter only the hidden items
|
|
hidden: function(items) {
|
|
return $(domApi.withClass(items.toArray(), 'aciTreeHidden'));
|
|
},
|
|
// show hidden item
|
|
_showHidden: function(item) {
|
|
var parent = null;
|
|
this.path(item).add(item).each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (this.isHidden(item)) {
|
|
domApi.removeClass(item[0], 'aciTreeHidden');
|
|
if (this.isOpenPath(item) && (!parent || this.isOpen(parent))) {
|
|
domApi.addClass(item[0], 'aciTreeVisible');
|
|
}
|
|
// update item states
|
|
this._setFirstLast(parent, this._getFirstLast(parent));
|
|
}
|
|
parent = item;
|
|
}, true));
|
|
},
|
|
// show hidden item
|
|
show: function(item, options) {
|
|
options = this._options(options, 'shown', 'showfail', 'wasshown', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeshow', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isHidden(item)) {
|
|
this._showHidden(item);
|
|
var parent = this.topParent(item);
|
|
// update item states
|
|
this._setOddEven(parent.length ? parent : item);
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if item is open
|
|
isOpen: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeOpen');
|
|
},
|
|
// test if item is closed
|
|
isClosed: function(item) {
|
|
return item && !domApi.hasClass(item[0], 'aciTreeOpen');
|
|
},
|
|
// test if item has children
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
hasChildren: function(item, hidden) {
|
|
return this.children(item, false, hidden).length > 0;
|
|
},
|
|
// test if item has siblings
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
hasSiblings: function(item, hidden) {
|
|
return this.siblings(item, hidden).length > 0;
|
|
},
|
|
// test if item has another before
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
hasPrev: function(item, hidden) {
|
|
return this.prev(item, hidden).length > 0;
|
|
},
|
|
// test if item has another after
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
hasNext: function(item, hidden) {
|
|
return this.next(item, hidden).length > 0;
|
|
},
|
|
// get item siblings
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
siblings: function(item, hidden) {
|
|
return item ? $(domApi.children(item[0].parentNode.parentNode, false, function(node) {
|
|
return (node != item[0]) && (hidden || !this.hasClass(node, 'aciTreeHidden'));
|
|
})) : $([]);
|
|
},
|
|
// get previous item
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
prev: function(item, hidden) {
|
|
return item ? $(domApi.prev(item[0], hidden ? null : function(node) {
|
|
return !this.hasClass(node, 'aciTreeHidden');
|
|
})) : $([]);
|
|
},
|
|
// get next item
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
next: function(item, hidden) {
|
|
return item ? $(domApi.next(item[0], hidden ? null : function(node) {
|
|
return !this.hasClass(node, 'aciTreeHidden');
|
|
})) : $([]);
|
|
},
|
|
// get item level - starting from 0
|
|
// return -1 for invalid items
|
|
level: function(item) {
|
|
var level = -1;
|
|
if (item) {
|
|
var node = item[0];
|
|
while (domApi.hasClass(node, 'aciTreeLi')) {
|
|
node = node.parentNode.parentNode;
|
|
level++;
|
|
}
|
|
}
|
|
return level;
|
|
},
|
|
// get item ID
|
|
getId: function(item) {
|
|
var data = this.itemData(item);
|
|
return data ? data.id : null;
|
|
},
|
|
// get item data
|
|
itemData: function(item) {
|
|
return item ? item.data('itemData' + this._instance.nameSpace) : null;
|
|
},
|
|
// set item ID
|
|
// `options.id` is the new item ID
|
|
// `options.oldId` will keep the old ID
|
|
setId: function(item, options) {
|
|
options = this._options(options, 'idset', 'idfail', 'wasid', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeid', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var data = this.itemData(item);
|
|
// keep the old one
|
|
options.oldId = data.id;
|
|
if (options.id == options.oldId) {
|
|
this._notify(item, options);
|
|
} else {
|
|
// remember this one
|
|
data.id = options.id;
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// get item index - starting from #0
|
|
getIndex: function(item) {
|
|
if (item && item[0]) {
|
|
if (window.Array.prototype.indexOf) {
|
|
return window.Array.prototype.indexOf.call(item[0].parentNode.childNodes, item[0]);
|
|
} else {
|
|
var children = item[0].parentNode.childNodes;
|
|
for (var i = 0; i < children.length; i++) {
|
|
if (children[i] == item[0]) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
// set item index - #0 based
|
|
// `options.index` is the new index
|
|
// `options.oldIndex` will keep the old index
|
|
setIndex: function(item, options) {
|
|
options = this._options(options, 'indexset', 'indexfail', 'wasindex', item);
|
|
if (this.isItem(item)) {
|
|
var oldIndex = this.getIndex(item);
|
|
var siblings = this.siblings(item);
|
|
if ((options.index != oldIndex) && !siblings.length) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeindex', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
// keep the old one
|
|
options.oldIndex = oldIndex;
|
|
if (options.index == oldIndex) {
|
|
this._notify(item, options);
|
|
} else {
|
|
// set the new index
|
|
if (options.index < 1) {
|
|
siblings.first().before(item);
|
|
} else if (options.index >= siblings.length) {
|
|
siblings.last().after(item);
|
|
} else {
|
|
siblings.eq(options.index).before(item);
|
|
}
|
|
var parent = this.parent(item);
|
|
// update item states
|
|
this._setFirstLast(parent.length ? parent : null, item.add([siblings[0], siblings.get(-1)]));
|
|
this._setOddEven(parent);
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// get item label
|
|
getLabel: function(item) {
|
|
var data = this.itemData(item);
|
|
return data ? data.label : null;
|
|
},
|
|
// test if is valid item
|
|
isItem: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeLi');
|
|
},
|
|
// item animation
|
|
// `state` if TRUE then show, FALSE then hide
|
|
// `unanimated` if TRUE then don't use animations
|
|
// `callback` function () to call at the end
|
|
_animate: function(item, state, unanimated, callback) {
|
|
if (!item) {
|
|
item = this._instance.jQuery;
|
|
}
|
|
if (!unanimated) {
|
|
// use the defined animation props
|
|
var setting = state ? this._instance.options.show : this._instance.options.hide;
|
|
if (setting) {
|
|
var ul = domApi.container(item[0]);
|
|
if (ul) {
|
|
// animate children container
|
|
$(ul).stop(true, true).animate(setting.props, {
|
|
duration: setting.duration,
|
|
easing: setting.easing,
|
|
complete: callback ? this.proxy(callback) : null
|
|
});
|
|
} else if (callback) {
|
|
callback.apply(this);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// use no animation
|
|
$(domApi.container(item[0])).stop(true, true).toggle(state);
|
|
if (callback) {
|
|
callback.apply(this);
|
|
}
|
|
},
|
|
// get first children of item
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
first: function(item, hidden) {
|
|
if (!item) {
|
|
item = this._instance.jQuery;
|
|
}
|
|
return $(domApi.firstChild(item[0], hidden ? null : function(node) {
|
|
return !this.hasClass(node, 'aciTreeHidden');
|
|
}));
|
|
},
|
|
// test if item is the first one for his parent
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
isFirst: function(item, hidden) {
|
|
if (item) {
|
|
var parent = domApi.parent(item[0]);
|
|
return this.first(parent ? $(parent) : null, hidden)[0] == item[0];
|
|
}
|
|
return false;
|
|
},
|
|
// get last children of item
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
last: function(item, hidden) {
|
|
if (!item) {
|
|
item = this._instance.jQuery;
|
|
}
|
|
return $(domApi.lastChild(item[0], hidden ? null : function(node) {
|
|
return !this.hasClass(node, 'aciTreeHidden');
|
|
}));
|
|
},
|
|
// test if item is the last one for his parent
|
|
// if `hidden` is TRUE then the hidden items will be considered too
|
|
isLast: function(item, hidden) {
|
|
if (item) {
|
|
var parent = domApi.parent(item[0]);
|
|
return this.last(parent ? $(parent) : null, hidden)[0] == item[0];
|
|
}
|
|
return false;
|
|
},
|
|
// test if item is busy/loading
|
|
isBusy: function(item) {
|
|
if (item) {
|
|
return domApi.hasClass(item[0], 'aciTreeLoad');
|
|
} else {
|
|
return this._instance.queue.busy();
|
|
}
|
|
},
|
|
// set loading state
|
|
_loading: function(item, state) {
|
|
if (item) {
|
|
domApi.toggleClass(item[0], 'aciTreeLoad', state);
|
|
if (state) {
|
|
item[0].firstChild.setAttribute('aria-busy', true);
|
|
} else {
|
|
item[0].firstChild.removeAttribute('aria-busy');
|
|
}
|
|
} else if (state) {
|
|
this._loader(state);
|
|
}
|
|
},
|
|
// show loader image
|
|
_loader: function(show) {
|
|
if (show || this.isBusy()) {
|
|
if (!this._private.loaderInterval) {
|
|
this._private.loaderInterval = window.setInterval(this.proxy(function() {
|
|
this._loader();
|
|
}), this._instance.options.loaderDelay);
|
|
}
|
|
domApi.addClass(this._instance.jQuery[0], 'aciTreeLoad');
|
|
window.clearTimeout(this._private.loaderHide);
|
|
this._private.loaderHide = window.setTimeout(this.proxy(function() {
|
|
domApi.removeClass(this._instance.jQuery[0], 'aciTreeLoad');
|
|
}), this._instance.options.loaderDelay * 2);
|
|
}
|
|
},
|
|
// test if parent has children
|
|
isChildren: function(parent, children) {
|
|
if (!parent) {
|
|
parent = this._instance.jQuery;
|
|
}
|
|
return children && (parent.has(children).length > 0);
|
|
},
|
|
// test if parent has immediate children
|
|
isImmediateChildren: function(parent, children) {
|
|
if (!parent) {
|
|
parent = this._instance.jQuery;
|
|
}
|
|
return children && parent.children('.aciTreeUl').children('.aciTreeLi').is(children);
|
|
},
|
|
// test if items share the same parent
|
|
sameParent: function(item1, item2) {
|
|
if (item1 && item2) {
|
|
var parent1 = this.parent(item1);
|
|
var parent2 = this.parent(item2);
|
|
return (!parent1.length && !parent2.length) || (parent1[0] == parent2[0]);
|
|
}
|
|
return false;
|
|
},
|
|
// test if items share the same top parent
|
|
sameTopParent: function(item1, item2) {
|
|
if (item1 && item2) {
|
|
var parent1 = this.topParent(item1);
|
|
var parent2 = this.topParent(item2);
|
|
return (!parent1.length && !parent2.length) || (parent1[0] == parent2[0]);
|
|
}
|
|
return false;
|
|
},
|
|
// return the updated item data
|
|
// `callback` function (item) called for each item
|
|
_serialize: function(item, callback) {
|
|
var data = this.itemData(item);
|
|
if (this.isInode(item)) {
|
|
data.inode = true;
|
|
if (this.wasLoad(item)) {
|
|
if (data.hasOwnProperty('open')) {
|
|
data.open = this.isOpen(item);
|
|
} else if (this.isOpen(item)) {
|
|
data.open = true;
|
|
}
|
|
data.branch = [];
|
|
this.children(item, false, true).each(this.proxy(function(element) {
|
|
var entry = this._serialize($(element), callback);
|
|
if (callback) {
|
|
entry = callback.call(this, $(element), {
|
|
}, entry);
|
|
} else {
|
|
entry = this._instance.options.serialize.call(this, $(element), {
|
|
}, entry);
|
|
}
|
|
if (entry) {
|
|
data.branch.push(entry);
|
|
}
|
|
}, true));
|
|
if (!data.branch.length) {
|
|
data.branch = null;
|
|
}
|
|
} else {
|
|
if (data.hasOwnProperty('open')) {
|
|
data.open = false;
|
|
}
|
|
if (data.hasOwnProperty('branch')) {
|
|
data.branch = null;
|
|
}
|
|
}
|
|
} else {
|
|
if (data.hasOwnProperty('inode')) {
|
|
data.inode = false;
|
|
}
|
|
if (data.hasOwnProperty('open')) {
|
|
data.open = null;
|
|
}
|
|
if (data.hasOwnProperty('branch')) {
|
|
data.branch = null;
|
|
}
|
|
}
|
|
if (data.hasOwnProperty('disabled')) {
|
|
data.disabled = this.isDisabled(item);
|
|
} else if (this.isDisabled(item)) {
|
|
data.disabled = true;
|
|
}
|
|
return data;
|
|
},
|
|
// return serialized data
|
|
// `callback` function (item, what, value) - see `aciTree.options.serialize`
|
|
serialize: function(item, what, callback) {
|
|
// override this to provide serialized data
|
|
if (typeof what == 'object') {
|
|
if (item) {
|
|
var data = this._serialize(item, callback);
|
|
if (callback) {
|
|
data = callback.call(this, item, {
|
|
}, data);
|
|
} else {
|
|
data = this._instance.options.serialize.call(this, item, {
|
|
}, data);
|
|
}
|
|
return data;
|
|
} else {
|
|
var list = [];
|
|
this.children(null, false, true).each(this.proxy(function(element) {
|
|
var data = this._serialize($(element), callback);
|
|
if (callback) {
|
|
data = callback.call(this, $(element), {
|
|
}, data);
|
|
} else {
|
|
data = this._instance.options.serialize.call(this, $(element), {
|
|
}, data);
|
|
}
|
|
if (data) {
|
|
list.push(data);
|
|
}
|
|
}, true));
|
|
return list;
|
|
}
|
|
}
|
|
return '';
|
|
},
|
|
// destroy the control
|
|
destroy: function(options) {
|
|
options = this._options(options);
|
|
// check if was init
|
|
if (!this.wasInit()) {
|
|
this._trigger(null, 'notinit', options);
|
|
this._fail(null, options);
|
|
return;
|
|
}
|
|
// check if is locked
|
|
if (this.isLocked()) {
|
|
this._trigger(null, 'locked', options);
|
|
this._fail(null, options);
|
|
return;
|
|
}
|
|
// a way to cancel the operation
|
|
if (!this._trigger(null, 'beforedestroy', options)) {
|
|
this._trigger(null, 'destroyfail', options);
|
|
this._fail(null, options);
|
|
return;
|
|
}
|
|
this._private.locked = true;
|
|
this._instance.jQuery.addClass('aciTreeLoad').attr('aria-busy', true);
|
|
this._instance.queue.destroy();
|
|
this._destroyHook(false);
|
|
// unload the entire treeview
|
|
this.unload(null, this._inner(options, {
|
|
success: this.proxy(function() {
|
|
window.clearTimeout(this._private.loaderHide);
|
|
window.clearInterval(this._private.loaderInterval);
|
|
this._private.itemClone = {
|
|
};
|
|
this._destroyHook(true);
|
|
this._instance.jQuery.unbind(this._instance.nameSpace).off(this._instance.nameSpace, '.aciTreeButton').off(this._instance.nameSpace, '.aciTreeLine');
|
|
this._instance.jQuery.removeClass('aciTree' + this._instance.index + ' aciTreeLoad').removeAttr('role aria-busy');
|
|
this._private.locked = false;
|
|
// call the parent
|
|
this._super();
|
|
this._trigger(null, 'destroyed', options);
|
|
this._success(null, options);
|
|
}),
|
|
fail: function() {
|
|
this._instance.jQuery.removeClass('aciTreeLoad');
|
|
this._private.locked = false;
|
|
this._trigger(null, 'destroyfail', options);
|
|
this._fail(null, options);
|
|
}
|
|
}));
|
|
},
|
|
_destroyHook: function(unloaded) {
|
|
// override this to do extra destroy before/after unload
|
|
}
|
|
|
|
};
|
|
|
|
// extend the base aciPluginUi class and store into aciPluginClass.plugins
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.aciPluginUi.extend(aciTree_core, 'aciTreeCore');
|
|
|
|
// publish the plugin & the default options
|
|
aciPluginClass.publish('aciTree', options);
|
|
|
|
// for internal access
|
|
var domApi = aciPluginClass.plugins.aciTree_dom;
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* A few utility functions for aciTree.
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
// called when items need to be filtered, for each tree item
|
|
// return TRUE/FALSE to include/exclude items on filtering
|
|
filterHook: function(item, search, regexp) {
|
|
return search.length ? regexp.test(window.String(this.getLabel(item))) : true;
|
|
}
|
|
};
|
|
|
|
// aciTree utils extension
|
|
// adds item update option, branch processing, moving items & item swapping, item search by ID
|
|
|
|
var aciTree_utils = {
|
|
__extend: function() {
|
|
// add extra data
|
|
$.extend(this._instance, {
|
|
filter: new this._queue(this, this._instance.options.queue)
|
|
});
|
|
// stop queue until needed
|
|
this._instance.filter.destroy();
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// call the `callback` function (item) for each children of item
|
|
// when `load` is TRUE will also try to load nodes
|
|
branch: function(item, callback, load) {
|
|
var queue = this._instance.queue;
|
|
var process = this.proxy(function(item, callback, next) {
|
|
var child = next ? this.next(item) : this.first(item);
|
|
if (child.length) {
|
|
if (this.isInode(child)) {
|
|
if (this.wasLoad(child)) {
|
|
queue.push(function(complete) {
|
|
callback.call(this, child);
|
|
process(child, callback);
|
|
process(child, callback, true);
|
|
complete();
|
|
});
|
|
} else if (load) {
|
|
// load the item first
|
|
this.ajaxLoad(child, {
|
|
success: function() {
|
|
callback.call(this, child);
|
|
process(child, callback);
|
|
process(child, callback, true);
|
|
},
|
|
fail: function() {
|
|
process(child, callback, true);
|
|
}
|
|
});
|
|
} else {
|
|
queue.push(function(complete) {
|
|
callback.call(this, child);
|
|
process(child, callback, true);
|
|
complete();
|
|
});
|
|
}
|
|
} else {
|
|
queue.push(function(complete) {
|
|
callback.call(this, child);
|
|
process(child, callback, true);
|
|
complete();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
process(item, callback);
|
|
},
|
|
// swap two items (they can't be parent & children)
|
|
// `options.item1` & `options.item2` are the swapped items
|
|
swap: function(options) {
|
|
options = this._options(options, null, 'swapfail', null, null);
|
|
var item1 = options.item1;
|
|
var item2 = options.item2;
|
|
if (this.isItem(item1) && this.isItem(item2) && !this.isChildren(item1, item2) && !this.isChildren(item2, item1) && (item1[0] != item2[0])) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(null, 'beforeswap', options)) {
|
|
this._fail(null, options);
|
|
return;
|
|
}
|
|
var prev = this.prev(item1);
|
|
if (prev.length) {
|
|
if (item2[0] == prev[0]) {
|
|
item2.before(item1);
|
|
} else {
|
|
item1.insertAfter(item2);
|
|
item2.insertAfter(prev);
|
|
}
|
|
} else {
|
|
var next = this.next(item1);
|
|
if (next.length) {
|
|
if (item2[0] == next[0]) {
|
|
item2.after(item1);
|
|
} else {
|
|
item1.insertAfter(item2);
|
|
item2.insertBefore(next);
|
|
}
|
|
} else {
|
|
var parent = item1.parent();
|
|
item1.insertAfter(item2);
|
|
parent.append(item2);
|
|
}
|
|
}
|
|
// update item states
|
|
this._updateLevel(item1);
|
|
var parent = this.parent(item1);
|
|
this._setFirstLast(parent.length ? parent : null, item1);
|
|
this._updateHidden(item1);
|
|
this._updateLevel(item2);
|
|
parent = this.parent(item2);
|
|
this._setFirstLast(parent.length ? parent : null, item2);
|
|
this._updateHidden(item2);
|
|
this._setOddEven(item1.add(item2));
|
|
this._trigger(null, 'swapped', options);
|
|
this._success(null, options);
|
|
} else {
|
|
this._fail(null, options);
|
|
}
|
|
},
|
|
// update item level
|
|
_updateItemLevel: function(item, fromLevel, toLevel) {
|
|
domApi.addRemoveClass(item[0], 'aciTreeLevel' + toLevel, 'aciTreeLevel' + fromLevel);
|
|
var line = item[0].firstChild;
|
|
line.setAttribute('aria-level', toLevel + 1);
|
|
var entry = domApi.childrenByClass(line, 'aciTreeEntry');
|
|
if (fromLevel < toLevel) {
|
|
line = entry.parentNode;
|
|
var branch;
|
|
for (var i = fromLevel; i < toLevel; i++) {
|
|
branch = window.document.createElement('DIV');
|
|
line.appendChild(branch);
|
|
branch.className = 'aciTreeBranch aciTreeLevel' + i;
|
|
line = branch;
|
|
}
|
|
line.appendChild(entry);
|
|
} else {
|
|
var branch = entry;
|
|
for (var i = toLevel; i <= fromLevel; i++) {
|
|
branch = branch.parentNode;
|
|
}
|
|
branch.removeChild(branch.firstChild);
|
|
branch.appendChild(entry);
|
|
}
|
|
},
|
|
// update child level
|
|
_updateChildLevel: function(item, fromLevel, toLevel) {
|
|
this.children(item, false, true).each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
this._updateItemLevel(item, fromLevel, toLevel);
|
|
if (this.isInode(item)) {
|
|
this._updateChildLevel(item, fromLevel + 1, toLevel + 1);
|
|
}
|
|
}, true));
|
|
},
|
|
// update item level
|
|
_updateLevel: function(item) {
|
|
var level = this.level(item);
|
|
var found = window.parseInt(item.attr('class').match(/aciTreeLevel[0-9]+/)[0].match(/[0-9]+/));
|
|
if (level != found) {
|
|
this._updateItemLevel(item, found, level);
|
|
this._updateChildLevel(item, found + 1, level + 1);
|
|
}
|
|
},
|
|
// move item up
|
|
moveUp: function(item, options) {
|
|
options = this._options(options);
|
|
options.index = window.Math.max(this.getIndex(item) - 1, 0);
|
|
this.setIndex(item, options);
|
|
},
|
|
// move item down
|
|
moveDown: function(item, options) {
|
|
options = this._options(options);
|
|
options.index = window.Math.min(this.getIndex(item) + 1, this.siblings(item).length);
|
|
this.setIndex(item, options);
|
|
},
|
|
// move item in first position
|
|
moveFirst: function(item, options) {
|
|
options = this._options(options);
|
|
options.index = 0;
|
|
this.setIndex(item, options);
|
|
},
|
|
// move item in last position
|
|
moveLast: function(item, options) {
|
|
options = this._options(options);
|
|
options.index = this.siblings(item).length;
|
|
this.setIndex(item, options);
|
|
},
|
|
// move item before another (they can't be parent & children)
|
|
// `options.before` is the element before which the item will be moved
|
|
moveBefore: function(item, options) {
|
|
options = this._options(options, null, 'movefail', 'wasbefore', item);
|
|
var before = options.before;
|
|
if (this.isItem(item) && this.isItem(before) && !this.isChildren(item, before) && (item[0] != before[0])) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforemove', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.prev(before, true)[0] == item[0]) {
|
|
this._notify(item, options);
|
|
} else {
|
|
var parent = this.parent(item);
|
|
var prev = this.prev(item, true);
|
|
if (!prev.length) {
|
|
prev = parent.length ? parent : this.first();
|
|
}
|
|
item.insertBefore(before);
|
|
if (parent.length && !this.hasChildren(parent, true)) {
|
|
this.setLeaf(parent);
|
|
}
|
|
this._updateLevel(item);
|
|
// update item states
|
|
this._setFirstLast(parent.length ? parent : null);
|
|
parent = this.parent(item);
|
|
this._setFirstLast(parent.length ? parent : null, item.add(before));
|
|
this._updateHidden(item);
|
|
this._setOddEven(item.add(before).add(prev));
|
|
this._trigger(item, 'moved', options);
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// move item after another (they can't be parent & children)
|
|
// `options.after` is the element after which the item will be moved
|
|
moveAfter: function(item, options) {
|
|
options = this._options(options, null, 'movefail', 'wasafter', item);
|
|
var after = options.after;
|
|
if (this.isItem(item) && this.isItem(after) && !this.isChildren(item, after) && (item[0] != after[0])) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforemove', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.next(after, true)[0] == item[0]) {
|
|
this._notify(item, options);
|
|
} else {
|
|
var parent = this.parent(item);
|
|
var prev = this.prev(item, true);
|
|
if (!prev.length) {
|
|
prev = parent.length ? parent : this.first();
|
|
}
|
|
item.insertAfter(after);
|
|
if (parent.length && !this.hasChildren(parent, true)) {
|
|
this.setLeaf(parent);
|
|
}
|
|
this._updateLevel(item);
|
|
this._setFirstLast(parent.length ? parent : null);
|
|
parent = this.parent(item);
|
|
this._setFirstLast(parent.length ? parent : null, item.add(after));
|
|
this._updateHidden(item);
|
|
this._setOddEven(item.add(after).add(prev));
|
|
this._trigger(item, 'moved', options);
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// move item to be a child of another (they can't be parent & children and the targeted parent item must be empty)
|
|
// `options.parent` is the parent element on which the item will be added
|
|
asChild: function(item, options) {
|
|
options = this._options(options, null, 'childfail', null, item);
|
|
var parent = options.parent;
|
|
if (this.isItem(item) && this.isItem(parent) && !this.isChildren(item, parent) && !this.hasChildren(parent, true) && (item[0] != parent[0])) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforechild', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var process = function() {
|
|
var oldParent = this.parent(item);
|
|
var prev = this.prev(item);
|
|
if (!prev.length) {
|
|
prev = oldParent.length ? oldParent : this.first();
|
|
}
|
|
var container = this._createContainer(parent);
|
|
container.append(item);
|
|
if (oldParent.length && !this.hasChildren(oldParent, true)) {
|
|
// no more children
|
|
this.setLeaf(oldParent);
|
|
}
|
|
// update item states
|
|
this._updateLevel(item);
|
|
this._setFirstLast(oldParent.length ? oldParent : null);
|
|
this._setFirstLast(parent.length ? parent : null, item);
|
|
this._updateHidden(item);
|
|
this._setOddEven(item.add(prev));
|
|
this._trigger(item, 'childset', options);
|
|
this._success(item, options);
|
|
};
|
|
if (this.isInode(parent)) {
|
|
process.apply(this);
|
|
} else {
|
|
// set as inode first
|
|
this.setInode(parent, this._inner(options, {
|
|
success: process,
|
|
fail: options.fail
|
|
}));
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// search a `path` ID from a parent
|
|
_search: function(parent, pathId) {
|
|
var items = this.children(parent);
|
|
var item, id, length, found, exact = false;
|
|
for (var i = 0, size = items.length; i < size; i++) {
|
|
item = items.eq(i);
|
|
id = window.String(this.getId(item));
|
|
length = id.length;
|
|
if (length) {
|
|
if (id == pathId.substr(0, length)) {
|
|
found = item;
|
|
exact = pathId.length == length;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
if (!exact) {
|
|
// try to search children
|
|
var child = this._search(found, pathId);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
}
|
|
return {
|
|
item: found,
|
|
exact: exact
|
|
};
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
// search items by ID
|
|
// `options.id` is the ID to search for
|
|
// if `path` is TRUE then the search will be more optimized
|
|
// and reduced to the first branch that matches the ID
|
|
// but the ID must be set like a path otherwise will not work
|
|
// if `load` is TRUE will also try to load nodes (works only when `path` is TRUE)
|
|
searchId: function(path, load, options) {
|
|
options = this._options(options);
|
|
var id = options.id;
|
|
if (path) {
|
|
if (load) {
|
|
var process = this.proxy(function(item) {
|
|
var found = this._search(item, id);
|
|
if (found) {
|
|
if (found.exact) {
|
|
this._success(found.item, options);
|
|
} else {
|
|
if (this.wasLoad(found.item)) {
|
|
this._fail(item, options);
|
|
} else {
|
|
// load the item
|
|
this.ajaxLoad(found.item, this._inner(options, {
|
|
success: function() {
|
|
process(found.item);
|
|
},
|
|
fail: options.fail
|
|
}));
|
|
}
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
});
|
|
process();
|
|
} else {
|
|
var found = this._search(null, id);
|
|
if (found && found.exact) {
|
|
this._success(found.item, options);
|
|
} else {
|
|
this._fail(null, options);
|
|
}
|
|
}
|
|
} else {
|
|
var found = $();
|
|
this._instance.jQuery.find('.aciTreeLi').each(this.proxy(function(element) {
|
|
if (id == this.getId($(element))) {
|
|
found = $(element);
|
|
return false;
|
|
}
|
|
}, true));
|
|
if (found.length) {
|
|
this._success(found, options);
|
|
} else {
|
|
this._fail(null, options);
|
|
}
|
|
}
|
|
},
|
|
// search nodes by ID or custom property starting from item
|
|
// `options.search` is the value to be searched
|
|
// `options.load` if TRUE will try to load nodes
|
|
// `options.callback` function (item, search) return TRUE for the custom match
|
|
// `options.results` will keep the search results
|
|
search: function(item, options) {
|
|
var results = [];
|
|
options = this._options(options);
|
|
var task = new this._task(new this._queue(this, this._instance.options.queue), function(complete) {
|
|
// run this at the end
|
|
if (results.length) {
|
|
options.results = $(results);
|
|
this._success($(results[0]), options);
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
complete();
|
|
});
|
|
var children = this.proxy(function(item) {
|
|
this.children(item, false, true).each(this.proxy(function(element) {
|
|
if (options.callback) {
|
|
// custom search
|
|
var match = options.callback.call(this, $(element), options.search);
|
|
if (match) {
|
|
results.push(element);
|
|
} else if (match === null) {
|
|
// skip childrens
|
|
return;
|
|
}
|
|
} else if (this.getId($(element)) == options.search) {
|
|
// default ID match
|
|
results.push(element);
|
|
}
|
|
if (this.isInode($(element))) {
|
|
// process children
|
|
task.push(function(complete) {
|
|
search($(element));
|
|
complete();
|
|
});
|
|
}
|
|
}, true));
|
|
});
|
|
var search = this.proxy(function(item) {
|
|
if (this.wasLoad(item)) {
|
|
// process children
|
|
task.push(function(complete) {
|
|
children(item);
|
|
complete();
|
|
});
|
|
} else if (options.load) {
|
|
task.push(function(complete) {
|
|
// load the item first
|
|
this.ajaxLoad(item, {
|
|
success: function() {
|
|
children(item);
|
|
complete();
|
|
},
|
|
fail: complete
|
|
});
|
|
});
|
|
}
|
|
});
|
|
// run the search
|
|
task.push(function(complete) {
|
|
search(item);
|
|
complete();
|
|
});
|
|
},
|
|
// search node by a list of IDs starting from item
|
|
// `options.path` is a list of IDs to be searched - the path to the node
|
|
// `options.load` if TRUE will try to load nodes
|
|
searchPath: function(item, options) {
|
|
options = this._options(options);
|
|
var path = options.path;
|
|
var search = this.proxy(function(item, id) {
|
|
this.search(item, {
|
|
success: function(item) {
|
|
if (path.length) {
|
|
search(item, path.shift());
|
|
} else {
|
|
this._success(item, options);
|
|
}
|
|
},
|
|
fail: function() {
|
|
this._fail(item, options);
|
|
},
|
|
search: id,
|
|
load: options.load,
|
|
callback: function(item, search) {
|
|
// prevent drill-down
|
|
return (this.getId(item) == search) ? true : null;
|
|
}
|
|
});
|
|
});
|
|
search(item, path.shift());
|
|
},
|
|
// get item path IDs starting from the top parent (ROOT)
|
|
// when `reverse` is TRUE returns the IDs in reverse order
|
|
pathId: function(item, reverse) {
|
|
var path = this.path(item, reverse), id = [];
|
|
path.each(this.proxy(function(element) {
|
|
id.push(this.getId($(element)));
|
|
}, true));
|
|
return id;
|
|
},
|
|
// escape string and return RegExp
|
|
_regexp: function(search) {
|
|
return new window.RegExp(window.String(search).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').replace(/\x08/g, '\\x08'), 'i');
|
|
},
|
|
// filter the tree items based on search criteria
|
|
// `options.search` is the keyword
|
|
// `options.first` will be the first matched item (if any)
|
|
filter: function(item, options) {
|
|
options = this._options(options, null, 'filterfail', null, item);
|
|
if (!item || this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforefilter', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var search = window.String(options.search);
|
|
var regexp = this._regexp(search);
|
|
var first = null;
|
|
this._instance.filter.init();
|
|
var task = new this._task(this._instance.filter, function(complete) {
|
|
// run this at the end
|
|
this._instance.filter.destroy();
|
|
options.first = first;
|
|
this._setOddEven();
|
|
this._trigger(item, 'filtered', options);
|
|
this._success(item, options);
|
|
complete();
|
|
});
|
|
// process children
|
|
var process = this.proxy(function(parent) {
|
|
var children = this.children(parent, false, true);
|
|
var found = false;
|
|
children.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (this._instance.options.filterHook.call(this, item, search, regexp)) {
|
|
if (!first) {
|
|
first = item;
|
|
}
|
|
found = true;
|
|
domApi.removeClass(item[0], 'aciTreeHidden');
|
|
} else {
|
|
domApi.addRemoveClass(item[0], 'aciTreeHidden', 'aciTreeVisible');
|
|
}
|
|
if (this.isInode(item)) {
|
|
// continue with the children
|
|
task.push(function(complete) {
|
|
process(item);
|
|
complete();
|
|
});
|
|
}
|
|
}, true));
|
|
if (found) {
|
|
// update item states
|
|
if (parent && this.isHidden(parent)) {
|
|
this._showHidden(parent);
|
|
}
|
|
if (!parent || (this.isOpenPath(parent) && this.isOpen(parent))) {
|
|
children.not('.aciTreeHidden').addClass('aciTreeVisible');
|
|
}
|
|
this._setFirstLast(parent, this._getFirstLast(parent));
|
|
}
|
|
});
|
|
task.push(function(complete) {
|
|
process(item);
|
|
complete();
|
|
});
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// call the `callback` function (item) for the first item
|
|
_firstAll: function(callback) {
|
|
callback.call(this, this.first());
|
|
},
|
|
// call the `callback` function (item) for the last item
|
|
// when `load` is TRUE will also try to load nodes
|
|
_lastAll: function(item, callback, load) {
|
|
if (item) {
|
|
if (this.isInode(item)) {
|
|
if (this.wasLoad(item)) {
|
|
this._lastAll(this.last(item), callback, load);
|
|
return;
|
|
} else if (load) {
|
|
this.ajaxLoad(item, {
|
|
success: function() {
|
|
this._lastAll(this.last(item), callback, load);
|
|
},
|
|
fail: function() {
|
|
callback.call(this, item);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
callback.call(this, item);
|
|
} else {
|
|
callback.call(this, this.last());
|
|
}
|
|
},
|
|
// call the `callback` function (item) for the next item from tree
|
|
// when `load` is TRUE will also try to load nodes
|
|
_nextAll: function(item, callback, load) {
|
|
if (item) {
|
|
if (this.isInode(item)) {
|
|
if (this.wasLoad(item)) {
|
|
callback.call(this, this.first(item));
|
|
return;
|
|
} else if (load) {
|
|
this.ajaxLoad(item, {
|
|
success: function() {
|
|
callback.call(this, this.first(item));
|
|
},
|
|
fail: function() {
|
|
this._nextAll(item, callback, load);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
var next = this.next(item);
|
|
if (next.length) {
|
|
callback.call(this, next);
|
|
} else {
|
|
// search next by parents
|
|
var search = this.proxy(function(item) {
|
|
var parent = this.parent(item);
|
|
if (parent.length) {
|
|
var next = this.next(parent);
|
|
if (next.length) {
|
|
return next;
|
|
} else {
|
|
return search(parent);
|
|
}
|
|
}
|
|
return null;
|
|
});
|
|
callback.call(this, search(item));
|
|
}
|
|
} else {
|
|
callback.call(this, this.first());
|
|
}
|
|
},
|
|
// call the `callback` function (item) for the previous item from tree
|
|
// when `load` is TRUE will also try to load nodes
|
|
_prevAll: function(item, callback, load) {
|
|
if (item) {
|
|
var prev = this.prev(item);
|
|
if (prev.length) {
|
|
if (this.isInode(prev)) {
|
|
this._lastAll(prev, callback, load);
|
|
} else {
|
|
callback.call(this, prev);
|
|
}
|
|
} else {
|
|
var parent = this.parent(item);
|
|
callback.call(this, parent.length ? parent : null);
|
|
}
|
|
} else {
|
|
callback.call(this, this.last());
|
|
}
|
|
},
|
|
// call the `callback` function (item) with the previous found item based on search criteria
|
|
// `search` is the keyword
|
|
prevMatch: function(item, search, callback) {
|
|
var regexp = this._regexp(search);
|
|
this._instance.filter.init();
|
|
var task = new this._task(this._instance.filter, function(complete) {
|
|
this._instance.filter.destroy();
|
|
complete();
|
|
});
|
|
var process = function(item) {
|
|
task.push(function(complete) {
|
|
this._prevAll(item, function(item) {
|
|
if (item) {
|
|
if (this._instance.options.filterHook.call(this, item, search, regexp)) {
|
|
callback.call(this, item);
|
|
} else {
|
|
process(item);
|
|
}
|
|
} else {
|
|
callback.call(this, null);
|
|
}
|
|
complete();
|
|
});
|
|
});
|
|
};
|
|
process(this.isItem(item) ? item : null);
|
|
},
|
|
// call the `callback` function (item) with the next found item based on search criteria
|
|
// `search` is the keyword
|
|
nextMatch: function(item, search, callback) {
|
|
var regexp = this._regexp(search);
|
|
this._instance.filter.init();
|
|
var task = new this._task(this._instance.filter, function(complete) {
|
|
this._instance.filter.destroy();
|
|
complete();
|
|
});
|
|
var process = function(item) {
|
|
task.push(function(complete) {
|
|
this._nextAll(item, function(item) {
|
|
if (item) {
|
|
if (this._instance.options.filterHook.call(this, item, search, regexp)) {
|
|
callback.call(this, item);
|
|
} else {
|
|
process(item);
|
|
}
|
|
} else {
|
|
callback.call(this, null);
|
|
}
|
|
complete();
|
|
});
|
|
});
|
|
};
|
|
process(this.isItem(item) ? item : null);
|
|
}
|
|
|
|
};
|
|
|
|
// extend the base aciTree class and add the utils stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_utils, 'aciTreeUtils');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
// for internal access
|
|
var domApi = aciPluginClass.plugins.aciTree_dom;
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds item selection/keyboard navigation to aciTree and need to
|
|
* be always included if you care about accessibility.
|
|
*
|
|
* There is an extra property for the item data:
|
|
*
|
|
* {
|
|
* ...
|
|
* selected: false, // TRUE means the item will be selected
|
|
* ...
|
|
* }
|
|
*
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
selectable: true, // if TRUE then one item can be selected (and the tree navigation with the keyboard will be enabled)
|
|
multiSelectable: false, // if TRUE then multiple items can be selected at a time
|
|
// the 'tabIndex' attribute need to be >= 0 set on the tree container (by default will be set to 0)
|
|
fullRow: false, // if TRUE then the selection will be made on the entire row (the CSS should reflect this)
|
|
textSelection: false // if FALSE then the item text can't be selected
|
|
};
|
|
|
|
// aciTree selectable extension
|
|
// adds item selection & keyboard navigation (left/right, up/down, pageup/pagedown, home/end, space, enter, escape)
|
|
// dblclick also toggles the item
|
|
|
|
var aciTree_selectable = {
|
|
__extend: function() {
|
|
// add extra data
|
|
$.extend(this._instance, {
|
|
focus: false
|
|
});
|
|
$.extend(this._private, {
|
|
blurTimeout: null,
|
|
spinPoint: null // the selected item to operate against when using the shift key with selection
|
|
});
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// test if has focus
|
|
hasFocus: function() {
|
|
return this._instance.focus;
|
|
},
|
|
// init selectable
|
|
_selectableInit: function() {
|
|
if (this._instance.jQuery.attr('tabindex') === undefined) {
|
|
// ensure the tree can get focus
|
|
this._instance.jQuery.attr('tabindex', 0);
|
|
}
|
|
if (!this._instance.options.textSelection) {
|
|
// disable text selection
|
|
this._selectable(false);
|
|
}
|
|
this._instance.jQuery.bind('acitree' + this._private.nameSpace, function(event, api, item, eventName, options) {
|
|
switch (eventName) {
|
|
case 'closed':
|
|
var focused = api.focused();
|
|
if (api.isChildren(item, focused)) {
|
|
// move focus to parent on close
|
|
api._focusOne(item);
|
|
}
|
|
// deselect children on parent close
|
|
api.children(item, true).each(api.proxy(function(element) {
|
|
var item = $(element);
|
|
if (this.isSelected(item)) {
|
|
this.deselect(item);
|
|
}
|
|
}, true));
|
|
break;
|
|
}
|
|
}).bind('focusin' + this._private.nameSpace, this.proxy(function() {
|
|
// handle tree focus
|
|
window.clearTimeout(this._private.blurTimeout);
|
|
if (!this.hasFocus()) {
|
|
this._instance.focus = true;
|
|
domApi.addClass(this._instance.jQuery[0], 'aciTreeFocus');
|
|
this._trigger(null, 'focused');
|
|
}
|
|
})).bind('focusout' + this._private.nameSpace, this.proxy(function() {
|
|
// handle tree focus
|
|
window.clearTimeout(this._private.blurTimeout);
|
|
this._private.blurTimeout = window.setTimeout(this.proxy(function() {
|
|
if (this.hasFocus()) {
|
|
this._instance.focus = false;
|
|
domApi.removeClass(this._instance.jQuery[0], 'aciTreeFocus');
|
|
this._trigger(null, 'blurred');
|
|
}
|
|
}), 10);
|
|
})).bind('keydown' + this._private.nameSpace, this.proxy(function(e) {
|
|
if (!this.hasFocus()) {
|
|
// do not handle if we do not have focus
|
|
return;
|
|
}
|
|
var focused = this.focused();
|
|
if (focused.length && this.isBusy(focused)) {
|
|
// skip when busy
|
|
return false;
|
|
}
|
|
var item = $([]);
|
|
switch (e.which) {
|
|
case 65: // aA
|
|
if (this._instance.options.multiSelectable && e.ctrlKey) {
|
|
// select all visible items
|
|
var select = this.visible(this.enabled(this.children(null, true))).not(this.selected());
|
|
select.each(this.proxy(function(element) {
|
|
this.select($(element), {
|
|
focus: false
|
|
});
|
|
}, true));
|
|
if (!this.focused().length) {
|
|
// ensure one item has focus
|
|
this._focusOne(this.visible(select, true).first());
|
|
}
|
|
// prevent default action
|
|
e.preventDefault();
|
|
}
|
|
break;
|
|
case 38: // up
|
|
item = focused.length ? this._prev(focused) : this.first();
|
|
break;
|
|
case 40: // down
|
|
item = focused.length ? this._next(focused) : this.first();
|
|
break;
|
|
case 37: // left
|
|
if (focused.length) {
|
|
if (this.isOpen(focused)) {
|
|
item = focused;
|
|
// close the item
|
|
this.close(focused, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: this._instance.options.expand,
|
|
unique: this._instance.options.unique
|
|
});
|
|
} else {
|
|
item = this.parent(focused);
|
|
}
|
|
} else {
|
|
item = this._first();
|
|
}
|
|
break;
|
|
case 39: // right
|
|
if (focused.length) {
|
|
if (this.isInode(focused) && this.isClosed(focused)) {
|
|
item = focused;
|
|
// open the item
|
|
this.open(focused, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: this._instance.options.expand,
|
|
unique: this._instance.options.unique
|
|
});
|
|
} else {
|
|
item = this.first(focused);
|
|
}
|
|
} else {
|
|
item = this._first();
|
|
}
|
|
break;
|
|
case 33: // pgup
|
|
item = focused.length ? this._prevPage(focused) : this._first();
|
|
break;
|
|
case 34: // pgdown
|
|
item = focused.length ? this._nextPage(focused) : this._first();
|
|
break;
|
|
case 36: // home
|
|
item = this._first();
|
|
break;
|
|
case 35: // end
|
|
item = this._last();
|
|
break;
|
|
case 13: // enter
|
|
case 107: // numpad [+]
|
|
item = focused;
|
|
if (this.isInode(focused) && this.isClosed(focused)) {
|
|
// open the item
|
|
this.open(focused, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: this._instance.options.expand,
|
|
unique: this._instance.options.unique
|
|
});
|
|
}
|
|
break;
|
|
case 27: // escape
|
|
case 109: // numpad [-]
|
|
item = focused;
|
|
if (this.isOpen(focused)) {
|
|
// close the item
|
|
this.close(focused, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: this._instance.options.expand,
|
|
unique: this._instance.options.unique
|
|
});
|
|
}
|
|
if (e.which == 27) {
|
|
// prevent default action on ESC
|
|
e.preventDefault();
|
|
}
|
|
break;
|
|
case 32: // space
|
|
item = focused;
|
|
if (this.isInode(focused) && !e.ctrlKey) {
|
|
// toggle the item
|
|
this.toggle(focused, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: this._instance.options.expand,
|
|
unique: this._instance.options.unique
|
|
});
|
|
}
|
|
// prevent page scroll
|
|
e.preventDefault();
|
|
break;
|
|
case 106: // numpad [*]
|
|
item = focused;
|
|
if (this.isInode(focused)) {
|
|
// open all children
|
|
this.open(focused, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: true,
|
|
unique: this._instance.options.unique
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
if (item.length) {
|
|
if (this._instance.options.multiSelectable && !e.ctrlKey && !e.shiftKey) {
|
|
// unselect others
|
|
this._unselect(this.selected().not(item));
|
|
}
|
|
if (!this.isVisible(item)) {
|
|
// bring it into view
|
|
this.setVisible(item);
|
|
}
|
|
if (e.ctrlKey) {
|
|
if ((e.which == 32) && this.isEnabled(item)) { // space
|
|
if (this.isSelected(item)) {
|
|
this.deselect(item);
|
|
} else {
|
|
this.select(item);
|
|
}
|
|
// remember for later
|
|
this._private.spinPoint = item;
|
|
} else {
|
|
this._focusOne(item);
|
|
}
|
|
} else if (e.shiftKey) {
|
|
this._shiftSelect(item);
|
|
} else {
|
|
if (!this.isSelected(item) && this.isEnabled(item)) {
|
|
this.select(item);
|
|
} else {
|
|
this._focusOne(item);
|
|
}
|
|
// remember for later
|
|
this._private.spinPoint = item;
|
|
}
|
|
return false;
|
|
}
|
|
}));
|
|
this._fullRow(this._instance.options.fullRow);
|
|
this._multiSelectable(this._instance.options.multiSelectable);
|
|
},
|
|
// change full row mode
|
|
_fullRow: function(state) {
|
|
this._instance.jQuery.off(this._private.nameSpace, '.aciTreeLine,.aciTreeItem').off(this._private.nameSpace, '.aciTreeItem');
|
|
this._instance.jQuery.on('mousedown' + this._private.nameSpace + ' click' + this._private.nameSpace, state ? '.aciTreeLine,.aciTreeItem' : '.aciTreeItem', this.proxy(function(e) {
|
|
var item = this.itemFrom(e.target);
|
|
if (!this.isVisible(item)) {
|
|
this.setVisible(item);
|
|
}
|
|
if (e.ctrlKey) {
|
|
if (e.type == 'click') {
|
|
if (this.isEnabled(item)) {
|
|
// (de)select item
|
|
if (this.isSelected(item)) {
|
|
this.deselect(item);
|
|
this._focusOne(item);
|
|
} else {
|
|
this.select(item);
|
|
}
|
|
} else {
|
|
this._focusOne(item);
|
|
}
|
|
}
|
|
} else if (this._instance.options.multiSelectable && e.shiftKey) {
|
|
this._shiftSelect(item);
|
|
} else {
|
|
if (this._instance.options.multiSelectable && (!this.isSelected(item) || (e.type == 'click'))) {
|
|
// deselect all other (keep the old focus)
|
|
this._unselect(this.selected().not(item));
|
|
}
|
|
this._selectOne(item);
|
|
}
|
|
if (!e.shiftKey) {
|
|
this._private.spinPoint = item;
|
|
}
|
|
})).on('dblclick' + this._private.nameSpace, state ? '.aciTreeLine,.aciTreeItem' : '.aciTreeItem', this.proxy(function(e) {
|
|
var item = this.itemFrom(e.target);
|
|
if (this.isInode(item)) {
|
|
// toggle the item
|
|
this.toggle(item, {
|
|
collapse: this._instance.options.collapse,
|
|
expand: this._instance.options.expand,
|
|
unique: this._instance.options.unique
|
|
});
|
|
return false;
|
|
}
|
|
}));
|
|
if (state) {
|
|
domApi.addClass(this._instance.jQuery[0], 'aciTreeFullRow');
|
|
} else {
|
|
domApi.removeClass(this._instance.jQuery[0], 'aciTreeFullRow');
|
|
}
|
|
},
|
|
// change selection mode
|
|
_multiSelectable: function(state) {
|
|
if (state) {
|
|
this._instance.jQuery.attr('aria-multiselectable', true);
|
|
} else {
|
|
var focused = this.focused();
|
|
this._unselect(this.selected().not(focused));
|
|
this._instance.jQuery.removeAttr('aria-multiselectable');
|
|
}
|
|
},
|
|
// process `shift` key selection
|
|
_shiftSelect: function(item) {
|
|
var spinPoint = this._private.spinPoint;
|
|
if (!spinPoint || !$.contains(this._instance.jQuery[0], spinPoint[0]) || !this.isOpenPath(spinPoint)) {
|
|
spinPoint = this.focused();
|
|
}
|
|
if (spinPoint.length) {
|
|
// select a range of items
|
|
var select = [item[0]], start = spinPoint[0], found = false, stop = item[0];
|
|
var visible = this.visible(this.children(null, true));
|
|
visible.each(this.proxy(function(element) {
|
|
// find what items to select
|
|
if (found) {
|
|
if (this.isEnabled($(element))) {
|
|
select.push(element);
|
|
}
|
|
if ((element == start) || (element == stop)) {
|
|
return false;
|
|
}
|
|
} else if ((element == start) || (element == stop)) {
|
|
if (this.isEnabled($(element))) {
|
|
select.push(element);
|
|
}
|
|
if ((element == start) && (element == stop)) {
|
|
return false;
|
|
}
|
|
found = true;
|
|
}
|
|
}, true));
|
|
this._unselect(this.selected().not(select));
|
|
// select the items
|
|
$(select).not(item).each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (!this.isSelected(item)) {
|
|
// select item (keep the old focus)
|
|
this.select(item, {
|
|
focus: false
|
|
});
|
|
}
|
|
}, true));
|
|
}
|
|
this._selectOne(item);
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this.extSelectable()) {
|
|
this._selectableInit();
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// override `_itemHook`
|
|
_itemHook: function(parent, item, itemData, level) {
|
|
if (this.extSelectable() && itemData.selected) {
|
|
this._selectableDOM.select(item, true);
|
|
}
|
|
// call the parent
|
|
this._super(parent, item, itemData, level);
|
|
},
|
|
// low level DOM functions
|
|
_selectableDOM: {
|
|
// (de)select one or more items
|
|
select: function(items, state) {
|
|
if (state) {
|
|
domApi.addListClass(items.toArray(), 'aciTreeSelected', function(node) {
|
|
node.firstChild.setAttribute('aria-selected', true);
|
|
});
|
|
} else {
|
|
domApi.removeListClass(items.toArray(), 'aciTreeSelected', function(node) {
|
|
node.firstChild.setAttribute('aria-selected', false);
|
|
});
|
|
}
|
|
},
|
|
// focus one item, unfocus one or more items
|
|
focus: function(items, state) {
|
|
if (state) {
|
|
domApi.addClass(items[0], 'aciTreeFocus');
|
|
items[0].firstChild.focus();
|
|
} else {
|
|
domApi.removeListClass(items.toArray(), 'aciTreeFocus');
|
|
}
|
|
}
|
|
},
|
|
// make element (un)selectable
|
|
_selectable: function(state) {
|
|
if (state) {
|
|
this._instance.jQuery.css({
|
|
'-webkit-user-select': 'text',
|
|
'-moz-user-select': 'text',
|
|
'-ms-user-select': 'text',
|
|
'-o-user-select': 'text',
|
|
'user-select': 'text'
|
|
}).attr({
|
|
'unselectable': null,
|
|
'onselectstart': null
|
|
}).unbind('selectstart' + this._private.nameSpace);
|
|
} else {
|
|
this._instance.jQuery.css({
|
|
'-webkit-user-select': 'none',
|
|
'-moz-user-select': '-moz-none',
|
|
'-ms-user-select': 'none',
|
|
'-o-user-select': 'none',
|
|
'user-select': 'none'
|
|
}).attr({
|
|
'unselectable': 'on',
|
|
'onselectstart': 'return false'
|
|
}).bind('selectstart' + this._private.nameSpace, function(e) {
|
|
if (!$(e.target).is('input,textarea')) {
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
// get first visible item
|
|
_first: function() {
|
|
return $(domApi.first(this._instance.jQuery[0], function(node) {
|
|
return this.hasClass(node, 'aciTreeVisible') ? true : null;
|
|
}));
|
|
},
|
|
// get last visible item
|
|
_last: function() {
|
|
return $(domApi.last(this._instance.jQuery[0], function(node) {
|
|
return this.hasClass(node, 'aciTreeVisible') ? true : null;
|
|
}));
|
|
},
|
|
// get previous visible starting with item
|
|
_prev: function(item) {
|
|
return $(domApi.prevAll(item[0], function(node) {
|
|
return this.hasClass(node, 'aciTreeVisible') ? true : null;
|
|
}));
|
|
},
|
|
// get next visible starting with item
|
|
_next: function(item) {
|
|
return $(domApi.nextAll(item[0], function(node) {
|
|
return this.hasClass(node, 'aciTreeVisible') ? true : null;
|
|
}));
|
|
},
|
|
// get previous page starting with item
|
|
_prevPage: function(item) {
|
|
var space = this._instance.jQuery.height();
|
|
var now = item[0].firstChild.offsetHeight;
|
|
var prev = item, last = $();
|
|
while (now < space) {
|
|
prev = this._prev(prev);
|
|
if (prev[0]) {
|
|
now += prev[0].firstChild.offsetHeight;
|
|
last = prev;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return last;
|
|
},
|
|
// get next page starting with item
|
|
_nextPage: function(item) {
|
|
var space = this._instance.jQuery.height();
|
|
var now = item[0].firstChild.offsetHeight;
|
|
var next = item, last = $();
|
|
while (now < space) {
|
|
next = this._next(next);
|
|
if (next[0]) {
|
|
now += next[0].firstChild.offsetHeight;
|
|
last = next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return last;
|
|
},
|
|
// select one item
|
|
_selectOne: function(item) {
|
|
if (this.isSelected(item)) {
|
|
this._focusOne(item);
|
|
} else {
|
|
if (this.isEnabled(item)) {
|
|
// select the item
|
|
this.select(item);
|
|
} else {
|
|
this._focusOne(item);
|
|
}
|
|
}
|
|
},
|
|
// unselect the items
|
|
_unselect: function(items) {
|
|
items.each(this.proxy(function(element) {
|
|
this.deselect($(element));
|
|
}, true));
|
|
},
|
|
// focus one item
|
|
_focusOne: function(item) {
|
|
if (!this._instance.options.multiSelectable) {
|
|
this._unselect(this.selected().not(item));
|
|
}
|
|
if (!this.isFocused(item)) {
|
|
this.focus(item);
|
|
}
|
|
},
|
|
// select item
|
|
// `options.focus` when set to FALSE will not set the focus
|
|
// `options.oldSelected` will keep the old selected items
|
|
select: function(item, options) {
|
|
options = this._options(options, 'selected', 'selectfail', 'wasselected', item);
|
|
if (this.extSelectable() && this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeselect', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
// keep the old ones
|
|
options.oldSelected = this.selected();
|
|
if (!this._instance.options.multiSelectable) {
|
|
// deselect all other
|
|
var unselect = options.oldSelected.not(item);
|
|
this._selectableDOM.select(unselect, false);
|
|
unselect.each(this.proxy(function(element) {
|
|
this._trigger($(element), 'deselected', options);
|
|
}, true));
|
|
}
|
|
if (this.isSelected(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
this._selectableDOM.select(item, true);
|
|
this._success(item, options);
|
|
}
|
|
// process focus
|
|
if ((options.focus === undefined) || options.focus) {
|
|
if (!this.isFocused(item) || options.focus) {
|
|
this.focus(item, this._inner(options));
|
|
}
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// deselect item
|
|
deselect: function(item, options) {
|
|
options = this._options(options, 'deselected', 'deselectfail', 'notselected', item);
|
|
if (this.extSelectable() && this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforedeselect', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isSelected(item)) {
|
|
this._selectableDOM.select(item, false);
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// set `virtual` focus
|
|
// `options.oldFocused` will keep the old focused item
|
|
focus: function(item, options) {
|
|
options = this._options(options, 'focus', 'focusfail', 'wasfocused', item);
|
|
if (this.extSelectable() && this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforefocus', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
// keep the old ones
|
|
options.oldFocused = this.focused();
|
|
// blur all other
|
|
var unfocus = options.oldFocused.not(item);
|
|
this._selectableDOM.focus(unfocus, false);
|
|
// unfocus all others
|
|
unfocus.each(this.proxy(function(element) {
|
|
this._trigger($(element), 'blur', options);
|
|
}, true));
|
|
if (this.isFocused(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
this._selectableDOM.focus(item, true);
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// remove `virtual` focus
|
|
blur: function(item, options) {
|
|
options = this._options(options, 'blur', 'blurfail', 'notfocused', item);
|
|
if (this.extSelectable() && this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeblur', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isFocused(item)) {
|
|
this._selectableDOM.focus(item, false);
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// get selected items
|
|
selected: function() {
|
|
return this._instance.jQuery.find('.aciTreeSelected');
|
|
},
|
|
// override `_serialize`
|
|
_serialize: function(item, callback) {
|
|
// call the parent
|
|
var data = this._super(item, callback);
|
|
if (data && this.extSelectable()) {
|
|
if (data.hasOwnProperty('selected')) {
|
|
data.selected = this.isSelected(item);
|
|
} else if (this.isSelected(item)) {
|
|
data.selected = true;
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
// test if item is selected
|
|
isSelected: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeSelected');
|
|
},
|
|
// return the focused item
|
|
focused: function() {
|
|
return this._instance.jQuery.find('.aciTreeFocus');
|
|
},
|
|
// test if item is focused
|
|
isFocused: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeFocus');
|
|
},
|
|
// test if selectable is enabled
|
|
extSelectable: function() {
|
|
return this._instance.options.selectable;
|
|
},
|
|
// override set `option`
|
|
option: function(option, value) {
|
|
if (this.wasInit() && !this.isLocked()) {
|
|
if ((option == 'selectable') && (value != this.extSelectable())) {
|
|
if (value) {
|
|
this._selectableInit();
|
|
} else {
|
|
this._selectableDone();
|
|
}
|
|
}
|
|
if ((option == 'multiSelectable') && (value != this._instance.options.multiSelectable)) {
|
|
this._multiSelectable(value);
|
|
}
|
|
if ((option == 'fullRow') && (value != this._instance.options.fullRow)) {
|
|
this._fullRow(value);
|
|
}
|
|
if ((option == 'textSelection') && (value != this._instance.options.textSelection)) {
|
|
this._selectable(value);
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(option, value);
|
|
},
|
|
// done selectable
|
|
_selectableDone: function(destroy) {
|
|
if (this._instance.jQuery.attr('tabindex') == 0) {
|
|
this._instance.jQuery.removeAttr('tabindex');
|
|
}
|
|
if (!this._instance.options.textSelection) {
|
|
this._selectable(true);
|
|
}
|
|
this._instance.jQuery.unbind(this._private.nameSpace);
|
|
this._instance.jQuery.off(this._private.nameSpace, '.aciTreeLine,.aciTreeItem').off(this._private.nameSpace, '.aciTreeItem');
|
|
domApi.removeClass(this._instance.jQuery[0], ['aciTreeFocus', 'aciTreeFullRow']);
|
|
this._instance.jQuery.removeAttr('aria-multiselectable');
|
|
this._instance.focus = false;
|
|
this._private.spinPoint = null;
|
|
if (!destroy) {
|
|
// remove selection
|
|
this._unselect(this.selected());
|
|
var focused = this.focused();
|
|
if (focused.length) {
|
|
this.blur(focused);
|
|
}
|
|
}
|
|
},
|
|
// override `_destroyHook`
|
|
_destroyHook: function(unloaded) {
|
|
if (unloaded) {
|
|
this._selectableDone(true);
|
|
}
|
|
// call the parent
|
|
this._super(unloaded);
|
|
}
|
|
|
|
};
|
|
|
|
// extend the base aciTree class and add the selectable stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_selectable, 'aciTreeSelectable');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
// for internal access
|
|
var domApi = aciPluginClass.plugins.aciTree_dom;
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds checkbox support to aciTree,
|
|
* should be used with the selectable extension.
|
|
*
|
|
* The are a few extra properties for the item data:
|
|
*
|
|
* {
|
|
* ...
|
|
* checkbox: true, // TRUE (default) means the item will have a checkbox (can be omitted if the `radio` extension is not used)
|
|
* checked: false, // if should be checked or not
|
|
* ...
|
|
* }
|
|
*
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
checkbox: false, // if TRUE then each item will have a checkbox
|
|
checkboxChain: true,
|
|
// if TRUE the selection will propagate to the parents/children
|
|
// if -1 the selection will propagate only to parents
|
|
// if +1 the selection will propagate only to children
|
|
// if FALSE the selection will not propagate in any way
|
|
checkboxBreak: true, // if TRUE then a missing checkbox will break the chaining
|
|
checkboxClick: false // if TRUE then a click will trigger a state change only when made over the checkbox itself
|
|
};
|
|
|
|
// aciTree checkbox extension
|
|
|
|
var aciTree_checkbox = {
|
|
// init checkbox
|
|
_checkboxInit: function() {
|
|
this._instance.jQuery.bind('acitree' + this._private.nameSpace, function(event, api, item, eventName, options) {
|
|
switch (eventName) {
|
|
case 'loaded':
|
|
// check/update on item load
|
|
api._checkboxLoad(item);
|
|
break;
|
|
}
|
|
}).bind('keydown' + this._private.nameSpace, this.proxy(function(e) {
|
|
switch (e.which) {
|
|
case 32: // space
|
|
// support `selectable` extension
|
|
if (this.extSelectable && this.extSelectable() && !e.ctrlKey) {
|
|
var item = this.focused();
|
|
if (this.hasCheckbox(item) && this.isEnabled(item)) {
|
|
if (this.isChecked(item)) {
|
|
this.uncheck(item);
|
|
} else {
|
|
this.check(item);
|
|
}
|
|
e.stopImmediatePropagation();
|
|
// prevent page scroll
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
})).on('click' + this._private.nameSpace, '.aciTreeItem', this.proxy(function(e) {
|
|
if (!this._instance.options.checkboxClick || $(e.target).is('.aciTreeCheck')) {
|
|
var item = this.itemFrom(e.target);
|
|
if (this.hasCheckbox(item) && this.isEnabled(item) && (!this.extSelectable || !this.extSelectable() || (!e.ctrlKey && !e.shiftKey))) {
|
|
// change state on click
|
|
if (this.isChecked(item)) {
|
|
this.uncheck(item);
|
|
} else {
|
|
this.check(item);
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
}));
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this.extCheckbox()) {
|
|
this._checkboxInit();
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// override `_itemHook`
|
|
_itemHook: function(parent, item, itemData, level) {
|
|
if (this.extCheckbox()) {
|
|
// support `radio` extension
|
|
var radio = this.extRadio && this.hasRadio(item);
|
|
if (!radio && (itemData.checkbox || ((itemData.checkbox === undefined) && (!this.extRadio || !this.extRadio())))) {
|
|
this._checkboxDOM.add(item, itemData);
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(parent, item, itemData, level);
|
|
},
|
|
// low level DOM functions
|
|
_checkboxDOM: {
|
|
// add item checkbox
|
|
add: function(item, itemData) {
|
|
domApi.addClass(item[0], itemData.checked ? ['aciTreeCheckbox', 'aciTreeChecked'] : 'aciTreeCheckbox');
|
|
var text = domApi.childrenByClass(item[0].firstChild, 'aciTreeText');
|
|
var parent = text.parentNode;
|
|
var label = window.document.createElement('LABEL');
|
|
var check = window.document.createElement('SPAN');
|
|
check.className = 'aciTreeCheck';
|
|
label.appendChild(check);
|
|
label.appendChild(text);
|
|
parent.appendChild(label);
|
|
item[0].firstChild.setAttribute('aria-checked', !!itemData.checked);
|
|
},
|
|
// remove item checkbox
|
|
remove: function(item) {
|
|
domApi.removeClass(item[0], ['aciTreeCheckbox', 'aciTreeChecked', 'aciTreeTristate']);
|
|
var text = domApi.childrenByClass(item[0].firstChild, 'aciTreeText');
|
|
var label = text.parentNode;
|
|
var parent = label.parentNode;
|
|
parent.replaceChild(text, label)
|
|
item[0].firstChild.removeAttribute('aria-checked');
|
|
},
|
|
// (un)check items
|
|
check: function(items, state) {
|
|
domApi.toggleListClass(items.toArray(), 'aciTreeChecked', state, function(node) {
|
|
node.firstChild.setAttribute('aria-checked', state);
|
|
});
|
|
},
|
|
// (un)set tristate items
|
|
tristate: function(items, state) {
|
|
domApi.toggleListClass(items.toArray(), 'aciTreeTristate', state);
|
|
}
|
|
},
|
|
// update items on load, starting from the loaded node
|
|
_checkboxLoad: function(item) {
|
|
if (this._instance.options.checkboxChain === false) {
|
|
// do not update on load
|
|
return;
|
|
}
|
|
var state = undefined;
|
|
if (this.hasCheckbox(item)) {
|
|
if (this.isChecked(item)) {
|
|
if (!this.checkboxes(this.children(item, false, true), true).length) {
|
|
// the item is checked but no children are, check them all
|
|
state = true;
|
|
}
|
|
} else {
|
|
// the item is not checked, uncheck all children
|
|
state = false;
|
|
}
|
|
}
|
|
this._checkboxUpdate(item, state);
|
|
},
|
|
// get children list
|
|
_checkboxChildren: function(item) {
|
|
if (this._instance.options.checkboxBreak) {
|
|
var list = [];
|
|
var process = this.proxy(function(item) {
|
|
var children = this.children(item, false, true);
|
|
children.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
// break on missing checkbox
|
|
if (this.hasCheckbox(item)) {
|
|
list.push(element);
|
|
process(item);
|
|
}
|
|
}, true));
|
|
});
|
|
process(item);
|
|
return $(list);
|
|
} else {
|
|
var children = this.children(item, true, true);
|
|
return this.checkboxes(children);
|
|
}
|
|
},
|
|
// update checkbox state
|
|
_checkboxUpdate: function(item, state) {
|
|
// update children
|
|
var checkDown = this.proxy(function(item, count, state) {
|
|
var children = this.children(item, false, true);
|
|
var total = 0;
|
|
var checked = 0;
|
|
children.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
var subCount = {
|
|
total: 0,
|
|
checked: 0
|
|
};
|
|
if (this.hasCheckbox(item)) {
|
|
if ((state !== undefined) && (this._instance.options.checkboxChain !== -1)) {
|
|
this._checkboxDOM.check(item, state);
|
|
}
|
|
total++;
|
|
if (this.isChecked(item)) {
|
|
checked++;
|
|
}
|
|
checkDown(item, subCount, state);
|
|
} else {
|
|
if (this._instance.options.checkboxBreak) {
|
|
var reCount = {
|
|
total: 0,
|
|
checked: 0
|
|
};
|
|
checkDown(item, reCount);
|
|
} else {
|
|
checkDown(item, subCount, state);
|
|
}
|
|
}
|
|
total += subCount.total;
|
|
checked += subCount.checked;
|
|
}, true));
|
|
if (item) {
|
|
this._checkboxDOM.tristate(item, (checked > 0) && (checked != total));
|
|
count.total += total;
|
|
count.checked += checked;
|
|
}
|
|
});
|
|
var count = {
|
|
total: 0,
|
|
checked: 0
|
|
};
|
|
checkDown(item, count, state);
|
|
// update parents
|
|
var checkUp = this.proxy(function(item, tristate, state) {
|
|
var parent = this.parent(item);
|
|
if (parent.length) {
|
|
if (!tristate) {
|
|
var children = this._checkboxChildren(parent);
|
|
var checked = this.checkboxes(children, true).length;
|
|
var tristate = (checked > 0) && (checked != children.length);
|
|
}
|
|
if (this.hasCheckbox(parent)) {
|
|
if ((state !== undefined) && (this._instance.options.checkboxChain !== 1)) {
|
|
this._checkboxDOM.check(parent, tristate ? true : state);
|
|
}
|
|
this._checkboxDOM.tristate(parent, tristate);
|
|
checkUp(parent, tristate, state);
|
|
} else {
|
|
if (this._instance.options.checkboxBreak) {
|
|
checkUp(parent);
|
|
} else {
|
|
checkUp(parent, tristate, state);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
checkUp(item, undefined, state);
|
|
},
|
|
// test if item have a checkbox
|
|
hasCheckbox: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeCheckbox');
|
|
},
|
|
// add checkbox
|
|
addCheckbox: function(item, options) {
|
|
options = this._options(options, 'checkboxadded', 'addcheckboxfail', 'wascheckbox', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeaddcheckbox', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.hasCheckbox(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
var process = function() {
|
|
this._checkboxDOM.add(item, {
|
|
});
|
|
this._success(item, options);
|
|
};
|
|
// support `radio` extension
|
|
if (this.extRadio && this.hasRadio(item)) {
|
|
// remove radio first
|
|
this.removeRadio(item, this._inner(options, {
|
|
success: process,
|
|
fail: options.fail
|
|
}));
|
|
} else {
|
|
process.apply(this);
|
|
}
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// remove checkbox
|
|
removeCheckbox: function(item, options) {
|
|
options = this._options(options, 'checkboxremoved', 'removecheckboxfail', 'notcheckbox', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeremovecheckbox', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.hasCheckbox(item)) {
|
|
this._checkboxDOM.remove(item);
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if it's checked
|
|
isChecked: function(item) {
|
|
if (this.hasCheckbox(item)) {
|
|
return domApi.hasClass(item[0], 'aciTreeChecked');
|
|
}
|
|
// support `radio` extension
|
|
if (this._super) {
|
|
// call the parent
|
|
return this._super(item);
|
|
}
|
|
return false;
|
|
},
|
|
// check checkbox
|
|
check: function(item, options) {
|
|
if (this.extCheckbox && this.hasCheckbox(item)) {
|
|
options = this._options(options, 'checked', 'checkfail', 'waschecked', item);
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforecheck', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isChecked(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
this._checkboxDOM.check(item, true);
|
|
if (this._instance.options.checkboxChain !== false) {
|
|
// chain them
|
|
this._checkboxUpdate(item, true);
|
|
}
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
// support `radio` extension
|
|
if (this._super) {
|
|
// call the parent
|
|
this._super(item, options);
|
|
} else {
|
|
this._trigger(item, 'checkfail', options);
|
|
this._fail(item, options);
|
|
}
|
|
}
|
|
},
|
|
// uncheck checkbox
|
|
uncheck: function(item, options) {
|
|
if (this.extCheckbox && this.hasCheckbox(item)) {
|
|
options = this._options(options, 'unchecked', 'uncheckfail', 'notchecked', item);
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeuncheck', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isChecked(item)) {
|
|
this._checkboxDOM.check(item, false);
|
|
if (this._instance.options.checkboxChain !== false) {
|
|
// chain them
|
|
this._checkboxUpdate(item, false);
|
|
}
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
// support `radio` extension
|
|
if (this._super) {
|
|
// call the parent
|
|
this._super(item, options);
|
|
} else {
|
|
this._trigger(item, 'uncheckfail', options);
|
|
this._fail(item, options);
|
|
}
|
|
}
|
|
},
|
|
// filter items with checkbox by state (if set)
|
|
checkboxes: function(items, state) {
|
|
if (state !== undefined) {
|
|
return $(domApi.withClass(items.toArray(), state ? ['aciTreeCheckbox', 'aciTreeChecked'] : 'aciTreeCheckbox', state ? null : 'aciTreeChecked'));
|
|
}
|
|
return $(domApi.withClass(items.toArray(), 'aciTreeCheckbox'));
|
|
},
|
|
// override `_serialize`
|
|
_serialize: function(item, callback) {
|
|
var data = this._super(item, callback);
|
|
if (data && this.extCheckbox()) {
|
|
if (data.hasOwnProperty('checkbox')) {
|
|
data.checkbox = this.hasCheckbox(item);
|
|
data.checked = this.isChecked(item);
|
|
} else if (this.hasCheckbox(item)) {
|
|
if (this.extRadio && this.extRadio()) {
|
|
data.checkbox = true;
|
|
}
|
|
data.checked = this.isChecked(item);
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
// override `serialize`
|
|
serialize: function(item, what, callback) {
|
|
if (what == 'checkbox') {
|
|
var serialized = '';
|
|
var children = this.children(item, true, true);
|
|
this.checkboxes(children, true).each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (callback) {
|
|
serialized += callback.call(this, item, what, this.getId(item));
|
|
} else {
|
|
serialized += this._instance.options.serialize.call(this, item, what, this.getId(item));
|
|
}
|
|
}, true));
|
|
return serialized;
|
|
}
|
|
return this._super(item, what, callback);
|
|
},
|
|
// test if item is in tristate
|
|
isTristate: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeTristate');
|
|
},
|
|
// filter tristate items
|
|
tristate: function(items) {
|
|
return $(domApi.withClass(items.toArray(), 'aciTreeTristate'));
|
|
},
|
|
// test if checkbox is enabled
|
|
extCheckbox: function() {
|
|
return this._instance.options.checkbox;
|
|
},
|
|
// override set `option`
|
|
option: function(option, value) {
|
|
if (this.wasInit() && !this.isLocked()) {
|
|
if ((option == 'checkbox') && (value != this.extCheckbox())) {
|
|
if (value) {
|
|
this._checkboxInit();
|
|
} else {
|
|
this._checkboxDone();
|
|
}
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(option, value);
|
|
},
|
|
// done checkbox
|
|
_checkboxDone: function(destroy) {
|
|
this._instance.jQuery.unbind(this._private.nameSpace);
|
|
this._instance.jQuery.off(this._private.nameSpace, '.aciTreeItem');
|
|
if (!destroy) {
|
|
// remove checkboxes
|
|
this.checkboxes(this.children(null, true, true)).each(this.proxy(function(element) {
|
|
this.removeCheckbox($(element));
|
|
}, true));
|
|
}
|
|
},
|
|
// override `_destroyHook`
|
|
_destroyHook: function(unloaded) {
|
|
if (unloaded) {
|
|
this._checkboxDone(true);
|
|
}
|
|
// call the parent
|
|
this._super(unloaded);
|
|
}
|
|
|
|
};
|
|
|
|
// extend the base aciTree class and add the checkbox stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_checkbox, 'aciTreeCheckbox');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
// for internal access
|
|
var domApi = aciPluginClass.plugins.aciTree_dom;
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds radio-button support to aciTree,
|
|
* should be used with the selectable extension.
|
|
*
|
|
* The are a few extra properties for the item data:
|
|
*
|
|
* {
|
|
* ...
|
|
* radio: true, // TRUE (default) means the item will have a radio button (can be omitted if the `checkbox` extension is not used)
|
|
* checked: false, // if should be checked or not
|
|
* ...
|
|
* }
|
|
*
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
radio: false, // if TRUE then each item will have a radio button
|
|
radioChain: true, // if TRUE the selection will propagate to the parents/children
|
|
radioBreak: true, // if TRUE then a missing radio button will break the chaining
|
|
radioClick: false // if TRUE then a click will trigger a state change only when made over the radio-button itself
|
|
};
|
|
|
|
// aciTree radio extension
|
|
|
|
var aciTree_radio = {
|
|
// init radio
|
|
_radioInit: function() {
|
|
this._instance.jQuery.bind('acitree' + this._private.nameSpace, function(event, api, item, eventName, options) {
|
|
switch (eventName) {
|
|
case 'loaded':
|
|
if (item) {
|
|
// check/update on item load
|
|
api._radioLoad(item);
|
|
}
|
|
break;
|
|
}
|
|
}).bind('keydown' + this._private.nameSpace, this.proxy(function(e) {
|
|
switch (e.which) {
|
|
case 32: // space
|
|
// support `selectable` extension
|
|
if (this.extSelectable && this.extSelectable() && !e.ctrlKey) {
|
|
var item = this.focused();
|
|
if (this.hasRadio(item) && this.isEnabled(item)) {
|
|
if (!this.isChecked(item)) {
|
|
this.check(item);
|
|
}
|
|
e.stopImmediatePropagation();
|
|
// prevent page scroll
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
})).on('click' + this._private.nameSpace, '.aciTreeItem', this.proxy(function(e) {
|
|
if (!this._instance.options.radioClick || $(e.target).is('.aciTreeCheck')) {
|
|
var item = this.itemFrom(e.target);
|
|
if (this.hasRadio(item) && this.isEnabled(item) && (!this.extSelectable || !this.extSelectable() || (!e.ctrlKey && !e.shiftKey))) {
|
|
// change state on click
|
|
if (!this.isChecked(item)) {
|
|
this.check(item);
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
}));
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this.extRadio()) {
|
|
this._radioInit();
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// override `_itemHook`
|
|
_itemHook: function(parent, item, itemData, level) {
|
|
if (this.extRadio()) {
|
|
// support `checkbox` extension
|
|
var checkbox = this.extCheckbox && this.hasCheckbox(item);
|
|
if (!checkbox && (itemData.radio || ((itemData.radio === undefined) && (!this.extCheckbox || !this.extCheckbox())))) {
|
|
this._radioDOM.add(item, itemData);
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(parent, item, itemData, level);
|
|
},
|
|
// low level DOM functions
|
|
_radioDOM: {
|
|
// add item radio
|
|
add: function(item, itemData) {
|
|
domApi.addClass(item[0], itemData.checked ? ['aciTreeRadio', 'aciTreeChecked'] : 'aciTreeRadio');
|
|
var text = domApi.childrenByClass(item[0].firstChild, 'aciTreeText');
|
|
var parent = text.parentNode;
|
|
var label = window.document.createElement('LABEL');
|
|
var check = window.document.createElement('SPAN');
|
|
check.className = 'aciTreeCheck';
|
|
label.appendChild(check);
|
|
label.appendChild(text);
|
|
parent.appendChild(label);
|
|
item[0].firstChild.setAttribute('aria-checked', !!itemData.checked);
|
|
},
|
|
// remove item radio
|
|
remove: function(item) {
|
|
domApi.removeClass(item[0], ['aciTreeRadio', 'aciTreeChecked']);
|
|
var text = domApi.childrenByClass(item[0].firstChild, 'aciTreeText');
|
|
var label = text.parentNode;
|
|
var parent = label.parentNode;
|
|
parent.replaceChild(text, label)
|
|
item[0].firstChild.removeAttribute('aria-checked');
|
|
},
|
|
// (un)check items
|
|
check: function(items, state) {
|
|
domApi.toggleListClass(items.toArray(), 'aciTreeChecked', state, function(node) {
|
|
node.firstChild.setAttribute('aria-checked', state);
|
|
});
|
|
}
|
|
},
|
|
// update item on load
|
|
_radioLoad: function(item) {
|
|
if (!this._instance.options.radioChain) {
|
|
// do not update on load
|
|
return;
|
|
}
|
|
if (this.hasRadio(item)) {
|
|
if (this.isChecked(item)) {
|
|
if (!this.radios(this.children(item, false, true), true).length) {
|
|
// the item is checked but no children are, check the children
|
|
this._radioUpdate(item, true);
|
|
}
|
|
} else {
|
|
// the item is not checked, uncheck children
|
|
this._radioUpdate(item);
|
|
}
|
|
}
|
|
},
|
|
// get children list
|
|
_radioChildren: function(item) {
|
|
if (this._instance.options.radioBreak) {
|
|
var list = [];
|
|
var process = this.proxy(function(item) {
|
|
var children = this.children(item, false, true);
|
|
children.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
// break on missing radio
|
|
if (this.hasRadio(item)) {
|
|
list.push(element);
|
|
process(item);
|
|
}
|
|
}, true));
|
|
});
|
|
process(item);
|
|
return $(list);
|
|
} else {
|
|
var children = this.children(item, true, true);
|
|
return this.radios(children);
|
|
}
|
|
},
|
|
// get children across items
|
|
_radioLevel: function(items) {
|
|
var list = [];
|
|
items.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
var children = this.children(item, false, true);
|
|
children.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (!this._instance.options.radioBreak || this.hasRadio(item)) {
|
|
list.push(element);
|
|
}
|
|
}, true));
|
|
}, true));
|
|
return $(list);
|
|
},
|
|
// update radio state
|
|
_radioUpdate: function(item, state) {
|
|
// update siblings
|
|
var siblings = this.proxy(function(item) {
|
|
var siblings = this.siblings(item, true);
|
|
this._radioDOM.check(this.radios(siblings), false);
|
|
siblings.each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (!this._instance.options.radioBreak || this.hasRadio(item)) {
|
|
this._radioDOM.check(this._radioChildren(item), false);
|
|
}
|
|
}, true));
|
|
});
|
|
if (state) {
|
|
siblings(item);
|
|
}
|
|
// update children
|
|
var checkDown = this.proxy(function(item) {
|
|
var children = this._radioLevel(item);
|
|
var radios = this.radios(children);
|
|
if (radios.length) {
|
|
var checked = this.radios(children, true);
|
|
if (checked.length) {
|
|
checked = checked.first();
|
|
this._radioDOM.check(checked, true);
|
|
siblings(checked);
|
|
checkDown(checked);
|
|
} else {
|
|
checked = radios.first();
|
|
this._radioDOM.check(checked, true);
|
|
siblings(checked);
|
|
checkDown(checked);
|
|
}
|
|
} else if (children.length) {
|
|
checkDown(children);
|
|
}
|
|
});
|
|
if (state) {
|
|
checkDown(item);
|
|
} else {
|
|
this._radioDOM.check(this._radioChildren(item), false);
|
|
}
|
|
// update parents
|
|
var checkUp = this.proxy(function(item) {
|
|
var parent = this.parent(item);
|
|
if (parent.length) {
|
|
if (this.hasRadio(parent)) {
|
|
if (state) {
|
|
siblings(parent);
|
|
}
|
|
this._radioDOM.check(parent, state);
|
|
checkUp(parent);
|
|
} else {
|
|
if (!this._instance.options.radioBreak) {
|
|
if (state) {
|
|
siblings(parent);
|
|
}
|
|
checkUp(parent);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (state !== undefined) {
|
|
checkUp(item);
|
|
}
|
|
},
|
|
// test if item have a radio
|
|
hasRadio: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeRadio');
|
|
},
|
|
// add radio button
|
|
addRadio: function(item, options) {
|
|
options = this._options(options, 'radioadded', 'addradiofail', 'wasradio', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeaddradio', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.hasRadio(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
var process = function() {
|
|
this._radioDOM.add(item, {
|
|
});
|
|
this._success(item, options);
|
|
};
|
|
// support `checkbox` extension
|
|
if (this.extCheckbox && this.hasCheckbox(item)) {
|
|
// remove checkbox first
|
|
this.removeCheckbox(item, this._inner(options, {
|
|
success: process,
|
|
fail: options.fail
|
|
}));
|
|
} else {
|
|
process.apply(this);
|
|
}
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// remove radio button
|
|
removeRadio: function(item, options) {
|
|
options = this._options(options, 'radioremoved', 'removeradiofail', 'notradio', item);
|
|
if (this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeremoveradio', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.hasRadio(item)) {
|
|
this._radioDOM.remove(item);
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if it's checked
|
|
isChecked: function(item) {
|
|
if (this.hasRadio(item)) {
|
|
return domApi.hasClass(item[0], 'aciTreeChecked');
|
|
}
|
|
// support `checkbox` extension
|
|
if (this._super) {
|
|
// call the parent
|
|
return this._super(item);
|
|
}
|
|
return false;
|
|
},
|
|
// check radio button
|
|
check: function(item, options) {
|
|
if (this.extRadio && this.hasRadio(item)) {
|
|
options = this._options(options, 'checked', 'checkfail', 'waschecked', item);
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforecheck', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isChecked(item)) {
|
|
this._notify(item, options);
|
|
} else {
|
|
this._radioDOM.check(item, true);
|
|
if (this._instance.options.radioChain) {
|
|
// chain them
|
|
this._radioUpdate(item, true);
|
|
}
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
// support `checkbox` extension
|
|
if (this._super) {
|
|
// call the parent
|
|
this._super(item, options);
|
|
} else {
|
|
this._trigger(item, 'checkfail', options);
|
|
this._fail(item, options);
|
|
}
|
|
}
|
|
},
|
|
// uncheck radio button
|
|
uncheck: function(item, options) {
|
|
if (this.extRadio && this.hasRadio(item)) {
|
|
options = this._options(options, 'unchecked', 'uncheckfail', 'notchecked', item);
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeuncheck', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
if (this.isChecked(item)) {
|
|
this._radioDOM.check(item, false);
|
|
if (this._instance.options.radioChain) {
|
|
// chain them
|
|
this._radioUpdate(item, false);
|
|
}
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
// support `checkbox` extension
|
|
if (this._super) {
|
|
// call the parent
|
|
this._super(item, options);
|
|
} else {
|
|
this._trigger(item, 'uncheckfail', options);
|
|
this._fail(item, options);
|
|
}
|
|
}
|
|
},
|
|
// filter items with radio by state (if set)
|
|
radios: function(items, state) {
|
|
if (state !== undefined) {
|
|
return $(domApi.withClass(items.toArray(), state ? ['aciTreeRadio', 'aciTreeChecked'] : 'aciTreeRadio', state ? null : 'aciTreeChecked'));
|
|
}
|
|
return $(domApi.withClass(items.toArray(), 'aciTreeRadio'));
|
|
},
|
|
// override `_serialize`
|
|
_serialize: function(item, callback) {
|
|
var data = this._super(item, callback);
|
|
if (data && this.extRadio()) {
|
|
if (data.hasOwnProperty('radio')) {
|
|
data.radio = this.hasRadio(item);
|
|
data.checked = this.isChecked(item);
|
|
} else if (this.hasRadio(item)) {
|
|
if (this.extCheckbox && this.extCheckbox()) {
|
|
data.radio = true;
|
|
}
|
|
data.checked = this.isChecked(item);
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
// override `serialize`
|
|
serialize: function(item, what, callback) {
|
|
if (what == 'radio') {
|
|
var serialized = '';
|
|
var children = this.children(item, true, true);
|
|
this.radios(children, true).each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (callback) {
|
|
serialized += callback.call(this, item, what, this.getId(item));
|
|
} else {
|
|
serialized += this._instance.options.serialize.call(this, item, what, this.getId(item));
|
|
}
|
|
}, true));
|
|
return serialized;
|
|
}
|
|
return this._super(item, what, callback);
|
|
},
|
|
// test if radio is enabled
|
|
extRadio: function() {
|
|
return this._instance.options.radio;
|
|
},
|
|
// override set `option`
|
|
option: function(option, value) {
|
|
if (this.wasInit() && !this.isLocked()) {
|
|
if ((option == 'radio') && (value != this.extRadio())) {
|
|
if (value) {
|
|
this._radioInit();
|
|
} else {
|
|
this._radioDone();
|
|
}
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(option, value);
|
|
},
|
|
// done radio
|
|
_radioDone: function(destroy) {
|
|
this._instance.jQuery.unbind(this._private.nameSpace);
|
|
this._instance.jQuery.off(this._private.nameSpace, '.aciTreeItem');
|
|
if (!destroy) {
|
|
// remove radios
|
|
this.radios(this.children(null, true, true)).each(this.proxy(function(element) {
|
|
this.removeRadio($(element));
|
|
}, true));
|
|
}
|
|
},
|
|
// override `_destroyHook`
|
|
_destroyHook: function(unloaded) {
|
|
if (unloaded) {
|
|
this._radioDone(true);
|
|
}
|
|
// call the parent
|
|
this._super(unloaded);
|
|
}
|
|
|
|
};
|
|
|
|
// extend the base aciTree class and add the radio stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_radio, 'aciTreeRadio');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
// for internal access
|
|
var domApi = aciPluginClass.plugins.aciTree_dom;
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds multiple column support to aciTree.
|
|
*
|
|
* The `columnData` option is used to tell what are the columns and show one or
|
|
* more values that will be read from the item data object.
|
|
*
|
|
* Column data is an array of column definitions, each column definition is
|
|
* an object:
|
|
*
|
|
* {
|
|
* width: 100,
|
|
* props: 'column_x',
|
|
* value: 'default'
|
|
* }
|
|
*
|
|
* where the `width` is the column width in [px], if undefined - then the value
|
|
* from the CSS will be used; the `props` is the property name that will be
|
|
* read from the item data, if undefined (or the `item-data[column.props]`
|
|
* is undefined) then a default value will be set for the column: the `value`.
|
|
*
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
columnData: [] // column definitions list
|
|
};
|
|
|
|
// aciTree columns extension
|
|
// adds item columns, set width with CSS or using the API
|
|
|
|
var aciTree_column = {
|
|
__extend: function() {
|
|
// add extra data
|
|
$.extend(this._private, {
|
|
propsIndex: { // column index cache
|
|
}
|
|
});
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this._instance.options.columnData.length) {
|
|
// check column width
|
|
var found = false, data;
|
|
for (var i in this._instance.options.columnData) {
|
|
data = this._instance.options.columnData[i];
|
|
if (data.width !== undefined) {
|
|
// update column width
|
|
this._updateCss('.aciTree.aciTree' + this._instance.index + ' .aciTreeColumn' + i, 'width:' + data.width + 'px;');
|
|
found = true;
|
|
}
|
|
this._private.propsIndex[data.props] = i;
|
|
}
|
|
if (found) {
|
|
// at least a column width set
|
|
this._updateWidth();
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// read property value from a CSS class name
|
|
_getCss: function(className, property, numeric) {
|
|
var id = '_getCss_' + window.String(className).replace(/[^a-z0-9_-]/ig, '_');
|
|
var test = $('body').find('#' + id);
|
|
if (!test.length) {
|
|
if (className instanceof Array) {
|
|
var style = '', end = '';
|
|
for (var i in className) {
|
|
style += '<div class="' + className[i] + '">';
|
|
end += '</div>';
|
|
}
|
|
style += end;
|
|
} else {
|
|
var style = '<div class="' + className + '"></div>';
|
|
}
|
|
$('body').append('<div id="' + id + '" style="position:relative;display:inline-block;width:0px;height:0px;line-height:0px;overflow:hidden">' + style + '</div>');
|
|
test = $('body').find('#' + id);
|
|
}
|
|
var value = test.find('*:last').css(property);
|
|
if (numeric) {
|
|
value = parseInt(value);
|
|
if (isNaN(value)) {
|
|
value = null;
|
|
}
|
|
}
|
|
return value;
|
|
},
|
|
// dynamically change a CSS class definition
|
|
_updateCss: function(className, definition) {
|
|
var id = '_updateCss_' + window.String(className).replace('>', '_gt_').replace(/[^a-z0-9_-]/ig, '_');
|
|
var style = '<style id="' + id + '" type="text/css">' + className + '{' + definition + '}</style>';
|
|
var test = $('body').find('#' + id);
|
|
if (test.length) {
|
|
test.replaceWith(style);
|
|
} else {
|
|
$('body').prepend(style);
|
|
}
|
|
},
|
|
// get column width
|
|
// `index` is the #0 based column index
|
|
getWidth: function(index) {
|
|
if ((index >= 0) && (index < this.columns())) {
|
|
return this._getCss(['aciTree aciTree' + this._instance.index, 'aciTreeColumn' + index], 'width', true);
|
|
}
|
|
return null;
|
|
},
|
|
// set column width
|
|
// `index` is the #0 based column index
|
|
setWidth: function(index, width) {
|
|
if ((index >= 0) && (index < this.columns())) {
|
|
this._updateCss('.aciTree.aciTree' + this._instance.index + ' .aciTreeColumn' + index, 'width:' + width + 'px;');
|
|
this._updateWidth();
|
|
}
|
|
},
|
|
// update item margins
|
|
_updateWidth: function() {
|
|
var width = 0;
|
|
for (var i in this._instance.options.columnData) {
|
|
if (this.isColumn(i)) {
|
|
width += this.getWidth(i);
|
|
}
|
|
}
|
|
var icon = this._getCss(['aciTree', 'aciTreeIcon'], 'width', true);
|
|
// add item padding
|
|
width += this._getCss(['aciTree', 'aciTreeItem'], 'padding-left', true) + this._getCss(['aciTree', 'aciTreeItem'], 'padding-right', true);
|
|
this._updateCss('.aciTree.aciTree' + this._instance.index + ' .aciTreeItem', 'margin-right:' + (icon + width) + 'px;');
|
|
this._updateCss('.aciTree[dir=rtl].aciTree' + this._instance.index + ' .aciTreeItem', 'margin-right:0;margin-left:' + (icon + width) + 'px;');
|
|
},
|
|
// test if column is visible
|
|
// `index` is the #0 based column index
|
|
isColumn: function(index) {
|
|
if ((index >= 0) && (index < this.columns())) {
|
|
return this._getCss(['aciTree aciTree' + this._instance.index, 'aciTreeColumn' + index], 'display') != 'none';
|
|
}
|
|
return false;
|
|
},
|
|
// get column index by `props`
|
|
// return -1 if the column does not exists
|
|
columnIndex: function(props) {
|
|
if (this._private.propsIndex[props] !== undefined) {
|
|
return this._private.propsIndex[props];
|
|
}
|
|
return -1;
|
|
},
|
|
// get the column count
|
|
columns: function() {
|
|
return this._instance.options.columnData.length;
|
|
},
|
|
// set column to be visible or hidden
|
|
// `index` is the #0 based column index
|
|
// if `show` is undefined then the column visibility will be toggled
|
|
toggleColumn: function(index, show) {
|
|
if ((index >= 0) && (index < this.columns())) {
|
|
if (show === undefined) {
|
|
var show = !this.isColumn(index);
|
|
}
|
|
this._updateCss('.aciTree.aciTree' + this._instance.index + ' .aciTreeColumn' + index, 'display:' + (show ? 'inherit' : 'none') + ';');
|
|
this._updateWidth();
|
|
}
|
|
},
|
|
// override `_itemHook`
|
|
_itemHook: function(parent, item, itemData, level) {
|
|
if (this.columns()) {
|
|
var position = domApi.childrenByClass(item[0].firstChild, 'aciTreeEntry'), data, column;
|
|
for (var i in this._instance.options.columnData) {
|
|
data = this._instance.options.columnData[i];
|
|
column = this._createColumn(itemData, data, i);
|
|
position.insertBefore(column, position.firstChild);
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(parent, item, itemData, level);
|
|
},
|
|
// create column markup
|
|
// `itemData` item data object
|
|
// `columnData` column data definition
|
|
// `index` is the #0 based column index
|
|
_createColumn: function(itemData, columnData, index) {
|
|
var value = columnData.props && (itemData[columnData.props] !== undefined) ? itemData[columnData.props] :
|
|
((columnData.value === undefined) ? '' : columnData.value);
|
|
var column = window.document.createElement('DIV');
|
|
column.className = 'aciTreeColumn aciTreeColumn' + index;
|
|
column.innerHTML = value.length ? value : ' ';
|
|
return column;
|
|
},
|
|
// set column content
|
|
// `options.index` the #0 based column index
|
|
// `options.value` is the new content
|
|
// `options.oldValue` will keep the old content
|
|
setColumn: function(item, options) {
|
|
options = this._options(options, 'columnset', 'columnfail', 'wascolumn', item);
|
|
if (this.isItem(item) && (options.index >= 0) && (options.index < this.columns())) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforecolumn', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var data = this.itemData(item);
|
|
// keep the old one
|
|
options.oldValue = data[this._instance.options.columnData[options.index].props];
|
|
if (options.value == options.oldValue) {
|
|
this._notify(item, options);
|
|
} else {
|
|
// set the column
|
|
item.children('.aciTreeLine').find('.aciTreeColumn' + options.index).html(options.value);
|
|
// remember this one
|
|
data[this._instance.options.columnData[options.index].props] = options.value;
|
|
this._success(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// get column content
|
|
getColumn: function(item, index) {
|
|
if ((index >= 0) && (index < this.columns())) {
|
|
var data = this.itemData(item);
|
|
return data ? data[this._instance.options.columnData[index].props] : null;
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// extend the base aciTree class and add the columns stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_column, 'aciTreeColumn');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
// for internal access
|
|
var domApi = aciPluginClass.plugins.aciTree_dom;
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds inplace edit support to aciTree,
|
|
* should be used with the selectable extension.
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
editable: false, // if TRUE then each item will be inplace editable
|
|
editDelay: 250 // how many [ms] to wait (with mouse down) before starting the edit (on mouse release)
|
|
};
|
|
|
|
// aciTree editable extension
|
|
// add inplace item editing by pressing F2 key or mouse click (to enter edit mode)
|
|
// press enter/escape to save/cancel the text edit
|
|
|
|
var aciTree_editable = {
|
|
__extend: function() {
|
|
// add extra data
|
|
$.extend(this._private, {
|
|
editTimestamp: null
|
|
});
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// init editable
|
|
_editableInit: function() {
|
|
this._instance.jQuery.bind('acitree' + this._private.nameSpace, function(event, api, item, eventName, options) {
|
|
switch (eventName) {
|
|
case 'blurred':
|
|
// support `selectable` extension
|
|
var item = api.edited();
|
|
if (item.length) {
|
|
// cancel edit/save the changes
|
|
api.endEdit();
|
|
}
|
|
break;
|
|
case 'deselected':
|
|
// support `selectable` extension
|
|
if (api.isEdited(item)) {
|
|
// cancel edit/save the changes
|
|
api.endEdit();
|
|
}
|
|
break;
|
|
}
|
|
}).bind('click' + this._private.nameSpace, this.proxy(function() {
|
|
// click on the tree
|
|
var item = this.edited();
|
|
if (item.length) {
|
|
// cancel edit/save the changes
|
|
this.endEdit();
|
|
}
|
|
})).bind('keydown' + this._private.nameSpace, this.proxy(function(e) {
|
|
switch (e.which) {
|
|
case 113: // F2
|
|
// support `selectable` extension
|
|
if (this.extSelectable && this.extSelectable()) {
|
|
var item = this.focused();
|
|
if (item.length && !this.isEdited(item) && this.isEnabled(item)) {
|
|
// enable edit on F2 key
|
|
this.edit(item);
|
|
// prevent default F2 key function
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
})).on('mousedown' + this._private.nameSpace, '.aciTreeItem', this.proxy(function(e) {
|
|
if ($(e.target).is('.aciTreeItem,.aciTreeText')) {
|
|
this._private.editTimestamp = $.now();
|
|
}
|
|
})).on('mouseup' + this._private.nameSpace, '.aciTreeItem', this.proxy(function(e) {
|
|
if ($(e.target).is('.aciTreeItem,.aciTreeText')) {
|
|
var passed = $.now() - this._private.editTimestamp;
|
|
// start edit only after N [ms] but before N * 4 [ms] have passed
|
|
if ((passed > this._instance.options.editDelay) && (passed < this._instance.options.editDelay * 4)) {
|
|
var item = this.itemFrom(e.target);
|
|
if ((!this.extSelectable || !this.extSelectable() || (this.isFocused(item) && (this.selected().length == 1))) && this.isEnabled(item)) {
|
|
// edit on mouseup
|
|
this.edit(item);
|
|
}
|
|
}
|
|
}
|
|
})).on('keydown' + this._private.nameSpace, 'input[type=text]', this.proxy(function(e) {
|
|
// key handling
|
|
switch (e.which) {
|
|
case 13: // enter
|
|
this.itemFrom(e.target).focus();
|
|
this.endEdit();
|
|
e.stopPropagation();
|
|
break;
|
|
case 27: // escape
|
|
this.itemFrom(e.target).focus();
|
|
this.endEdit({
|
|
save: false
|
|
});
|
|
e.stopPropagation();
|
|
// prevent default action on ESC
|
|
e.preventDefault();
|
|
break;
|
|
case 38: // up
|
|
case 40: // down
|
|
case 37: // left
|
|
case 39: // right
|
|
case 33: // pgup
|
|
case 34: // pgdown
|
|
case 36: // home
|
|
case 35: // end
|
|
case 32: // space
|
|
case 107: // numpad [+]
|
|
case 109: // numpad [-]
|
|
case 106: // numpad [*]
|
|
e.stopPropagation();
|
|
break;
|
|
}
|
|
})).on('blur' + this._private.nameSpace, 'input[type=text]', this.proxy(function() {
|
|
if (!this.extSelectable || !this.extSelectable()) {
|
|
// cancel edit/save the changes
|
|
this.endEdit();
|
|
}
|
|
})).on('click' + this._private.nameSpace + ' dblclick' + this._private.nameSpace, 'input[type=text]', function(e) {
|
|
e.stopPropagation();
|
|
});
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this.extEditable()) {
|
|
this._editableInit();
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// low level DOM functions
|
|
_editableDOM: {
|
|
// add edit field
|
|
add: function(item) {
|
|
var line = item.addClass('aciTreeEdited').children('.aciTreeLine');
|
|
line.find('.aciTreeText').html('<input id="aciTree-editable-tree-item" type="text" value="" style="-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;-o-user-select:text;user-select:text" />');
|
|
line.find('label').attr('for', 'aciTree-editable-tree-item');
|
|
this._editableDOM.get(item).val(this.getLabel(item));
|
|
},
|
|
// remove edit field
|
|
remove: function(item, label) {
|
|
var line = item.removeClass('aciTreeEdited').children('.aciTreeLine');
|
|
line.find('.aciTreeText').html(this.getLabel(item));
|
|
line.find('label').removeAttr('for');
|
|
},
|
|
// return edit field
|
|
get: function(item) {
|
|
return item ? item.children('.aciTreeLine').find('input[type=text]') : $([]);
|
|
}
|
|
},
|
|
// get edited item
|
|
edited: function() {
|
|
return this._instance.jQuery.find('.aciTreeEdited');
|
|
},
|
|
// test if item is edited
|
|
isEdited: function(item) {
|
|
return item && domApi.hasClass(item[0], 'aciTreeEdited');
|
|
},
|
|
// set focus to the input
|
|
_focusEdit: function(item) {
|
|
var field = this._editableDOM.get(item).focus().trigger('click')[0];
|
|
if (field) {
|
|
if (typeof field.selectionStart == 'number') {
|
|
field.selectionStart = field.selectionEnd = field.value.length;
|
|
} else if (field.createTextRange !== undefined) {
|
|
var range = field.createTextRange();
|
|
range.collapse(false);
|
|
range.select();
|
|
}
|
|
}
|
|
},
|
|
// override `setLabel`
|
|
setLabel: function(item, options) {
|
|
if (!this.extEditable() || !this.isEdited(item)) {
|
|
// call the parent
|
|
this._super(item, options);
|
|
}
|
|
},
|
|
// edit item inplace
|
|
edit: function(item, options) {
|
|
options = this._options(options, 'edit', 'editfail', 'wasedit', item);
|
|
if (this.extEditable() && this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeedit', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var edited = this.edited();
|
|
if (edited.length) {
|
|
if (edited[0] == item[0]) {
|
|
this._notify(item, options);
|
|
return;
|
|
} else {
|
|
this._editableDOM.remove.call(this, edited);
|
|
this._trigger(edited, 'endedit', options);
|
|
}
|
|
}
|
|
this._editableDOM.add.call(this, item);
|
|
this._focusEdit(item);
|
|
this._success(item, options);
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// end edit
|
|
// `options.save` when set to FALSE will not save the changes
|
|
endEdit: function(options) {
|
|
var item = this.edited();
|
|
options = this._options(options, 'edited', 'endeditfail', 'endedit', item);
|
|
if (this.extEditable() && this.isItem(item)) {
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforeendedit', options)) {
|
|
this._fail(item, options);
|
|
return;
|
|
}
|
|
var text = this._editableDOM.get(item).val();
|
|
this._editableDOM.remove.call(this, item);
|
|
if ((options.save === undefined) || options.save) {
|
|
this.setLabel(item, {
|
|
label: text
|
|
});
|
|
this._success(item, options);
|
|
} else {
|
|
this._notify(item, options);
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
},
|
|
// test if editable is enabled
|
|
extEditable: function() {
|
|
return this._instance.options.editable;
|
|
},
|
|
// override set `option`
|
|
option: function(option, value) {
|
|
if (this.wasInit() && !this.isLocked()) {
|
|
if ((option == 'editable') && (value != this.extEditable())) {
|
|
if (value) {
|
|
this._editableInit();
|
|
} else {
|
|
this._editableDone();
|
|
}
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(option, value);
|
|
},
|
|
// done editable
|
|
_editableDone: function() {
|
|
this._instance.jQuery.unbind(this._private.nameSpace);
|
|
this._instance.jQuery.off(this._private.nameSpace, '.aciTreeItem');
|
|
this._instance.jQuery.off(this._private.nameSpace, 'input[type=text]');
|
|
var edited = this.edited();
|
|
if (edited.length) {
|
|
this.endEdit();
|
|
}
|
|
},
|
|
// override `_destroyHook`
|
|
_destroyHook: function(unloaded) {
|
|
if (unloaded) {
|
|
this._editableDone();
|
|
}
|
|
// call the parent
|
|
this._super(unloaded);
|
|
}
|
|
|
|
};
|
|
|
|
// extend the base aciTree class and add the editable stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_editable, 'aciTreeEditable');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
// for internal access
|
|
var domApi = aciPluginClass.plugins.aciTree_dom;
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds save/restore support for item states (open/selected) using local storage.
|
|
* The states are saved on item select/open and restored on treeview init.
|
|
* Require jStorage https://github.com/andris9/jStorage and the utils extension for finding items by ID.
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
persist: null // the storage key name to keep the states (should be unique/treeview)
|
|
};
|
|
|
|
// aciTree persist extension
|
|
// save/restore item state in/from local storage
|
|
|
|
var aciTree_persist = {
|
|
__extend: function() {
|
|
$.extend(this._private, {
|
|
// timeouts for the save operation
|
|
selectTimeout: null,
|
|
focusTimeout: null,
|
|
openTimeout: null
|
|
});
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// init persist
|
|
_initPersist: function() {
|
|
this._instance.jQuery.bind('acitree' + this._private.nameSpace, function(event, api, item, eventName, options) {
|
|
if (options.uid == 'ui.persist') {
|
|
// skip processing itself
|
|
return;
|
|
}
|
|
switch (eventName) {
|
|
case 'init':
|
|
api._persistRestore();
|
|
break;
|
|
case 'selected':
|
|
case 'deselected':
|
|
// support `selectable` extension
|
|
api._persistLater('selected');
|
|
break;
|
|
case 'focus':
|
|
case 'blur':
|
|
// support `selectable` extension
|
|
api._persistLater('focused');
|
|
break;
|
|
case 'opened':
|
|
case 'closed':
|
|
api._persistLater('opened');
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this.extPersist()) {
|
|
this._initPersist();
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// persist states
|
|
_persistLater: function(type) {
|
|
switch (type) {
|
|
case 'selected':
|
|
window.clearTimeout(this._private.selectTimeout);
|
|
this._private.selectTimeout = window.setTimeout(this.proxy(function() {
|
|
this._persistSelected();
|
|
}), 250);
|
|
break;
|
|
case 'focused':
|
|
window.clearTimeout(this._private.focusTimeout);
|
|
this._private.focusTimeout = window.setTimeout(this.proxy(function() {
|
|
this._persistFocused();
|
|
}), 250);
|
|
break;
|
|
case 'opened':
|
|
window.clearTimeout(this._private.openTimeout);
|
|
this._private.openTimeout = window.setTimeout(this.proxy(function() {
|
|
this._persistOpened();
|
|
}), 250);
|
|
break;
|
|
}
|
|
},
|
|
// restore item states
|
|
_persistRestore: function() {
|
|
var queue = new this._queue(this, this._instance.options.queue);
|
|
var task = new this._task(queue, function(complete) {
|
|
// support `selectable` extension
|
|
if (this.extSelectable && this.extSelectable()) {
|
|
var selected = $.jStorage.get('aciTree_' + this._instance.options.persist + '_selected');
|
|
if (selected instanceof Array) {
|
|
// select all saved items
|
|
for (var i in selected) {
|
|
(function(path) {
|
|
queue.push(function(complete) {
|
|
this.searchPath(null, {
|
|
success: function(item) {
|
|
this.select(item, {
|
|
uid: 'ui.persist',
|
|
success: function() {
|
|
complete();
|
|
},
|
|
fail: complete,
|
|
focus: false
|
|
});
|
|
},
|
|
fail: complete,
|
|
path: path.split(';')
|
|
});
|
|
});
|
|
})(selected[i]);
|
|
if (!this._instance.options.multiSelectable) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
var focused = $.jStorage.get('aciTree_' + this._instance.options.persist + '_focused');
|
|
if (focused instanceof Array) {
|
|
// focus all saved items
|
|
for (var i in focused) {
|
|
(function(path) {
|
|
queue.push(function(complete) {
|
|
this.searchPath(null, {
|
|
success: function(item) {
|
|
this.focus(item, {
|
|
uid: 'ui.persist',
|
|
success: function(item) {
|
|
this.setVisible(item, {
|
|
center: true
|
|
});
|
|
complete();
|
|
},
|
|
fail: complete
|
|
});
|
|
},
|
|
fail: complete,
|
|
path: path.split(';')
|
|
});
|
|
});
|
|
})(focused[i]);
|
|
}
|
|
}
|
|
}
|
|
complete();
|
|
});
|
|
var opened = $.jStorage.get('aciTree_' + this._instance.options.persist + '_opened');
|
|
if (opened instanceof Array) {
|
|
// open all saved items
|
|
for (var i in opened) {
|
|
(function(path) {
|
|
// add item to queue
|
|
task.push(function(complete) {
|
|
this.searchPath(null, {
|
|
success: function(item) {
|
|
this.open(item, {
|
|
uid: 'ui.persist',
|
|
success: complete,
|
|
fail: complete
|
|
});
|
|
},
|
|
fail: complete,
|
|
path: path.split(';'),
|
|
load: true
|
|
});
|
|
});
|
|
})(opened[i]);
|
|
}
|
|
}
|
|
},
|
|
// persist selected items
|
|
_persistSelected: function() {
|
|
// support `selectable` extension
|
|
if (this.extSelectable && this.extSelectable()) {
|
|
var selected = [];
|
|
this.selected().each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
var path = this.pathId(item);
|
|
path.push(this.getId(item));
|
|
selected.push(path.join(';'));
|
|
}, true));
|
|
$.jStorage.set('aciTree_' + this._instance.options.persist + '_selected', selected);
|
|
}
|
|
},
|
|
// persist focused item
|
|
_persistFocused: function() {
|
|
// support `selectable` extension
|
|
if (this.extSelectable && this.extSelectable()) {
|
|
var focused = [];
|
|
this.focused().each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
var path = this.pathId(item);
|
|
path.push(this.getId(item));
|
|
focused.push(path.join(';'));
|
|
}, true));
|
|
$.jStorage.set('aciTree_' + this._instance.options.persist + '_focused', focused);
|
|
}
|
|
},
|
|
// persist opened items
|
|
_persistOpened: function() {
|
|
var opened = [];
|
|
this.inodes(this.children(null, true), true).each(this.proxy(function(element) {
|
|
var item = $(element);
|
|
if (this.isOpenPath(item)) {
|
|
var path = this.pathId(item);
|
|
path.push(this.getId(item));
|
|
opened.push(path.join(';'));
|
|
}
|
|
}, true));
|
|
$.jStorage.set('aciTree_' + this._instance.options.persist + '_opened', opened);
|
|
},
|
|
// test if there is any saved data
|
|
isPersist: function() {
|
|
if (this.extPersist()) {
|
|
var selected = $.jStorage.get('aciTree_' + this._instance.options.persist + '_selected');
|
|
if (selected instanceof Array) {
|
|
return true;
|
|
}
|
|
var focused = $.jStorage.get('aciTree_' + this._instance.options.persist + '_focused');
|
|
if (focused instanceof Array) {
|
|
return true;
|
|
}
|
|
var opened = $.jStorage.get('aciTree_' + this._instance.options.persist + '_opened');
|
|
if (opened instanceof Array) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
// remove any saved states
|
|
unpersist: function() {
|
|
if (this.extPersist()) {
|
|
$.jStorage.deleteKey('aciTree_' + this._instance.options.persist + '_selected');
|
|
$.jStorage.deleteKey('aciTree_' + this._instance.options.persist + '_focused');
|
|
$.jStorage.deleteKey('aciTree_' + this._instance.options.persist + '_opened');
|
|
}
|
|
},
|
|
// test if persist is enabled
|
|
extPersist: function() {
|
|
return this._instance.options.persist;
|
|
},
|
|
// override set `option`
|
|
option: function(option, value) {
|
|
var persist = this.extPersist();
|
|
// call the parent
|
|
this._super(option, value);
|
|
if (this.extPersist() != persist) {
|
|
if (persist) {
|
|
this._donePersist();
|
|
} else {
|
|
this._initPersist();
|
|
}
|
|
}
|
|
},
|
|
// done persist
|
|
_donePersist: function() {
|
|
this._instance.jQuery.unbind(this._private.nameSpace);
|
|
},
|
|
// override `_destroyHook`
|
|
_destroyHook: function(unloaded) {
|
|
if (unloaded) {
|
|
this._donePersist();
|
|
}
|
|
// call the parent
|
|
this._super(unloaded);
|
|
}
|
|
};
|
|
|
|
// extend the base aciTree class and add the persist stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_persist, 'aciTreePersist');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds hash/fragment support using aciFragment, it opens/select item(s) based on variables stored in the fragment part of the URL.
|
|
* The states are loaded from the URL fragment and set on treeview init. Multiple item IDs separated with ";" are supported for
|
|
* opening/selecting deep items (if loading nodes is required).
|
|
* Require aciFragment https://github.com/dragosu/jquery-aciFragment and the utils extension for finding items by ID.
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
selectHash: null, // hash key name to select a item (item path IDs as key value, multiple item IDs separated with a ";")
|
|
openHash: null // hash key name to open item(s) (item path IDs as key value, multiple item IDs separated with a ";")
|
|
};
|
|
|
|
// aciTree hash extension
|
|
// select/open items based on IDs stored in the fragment of the current URL
|
|
|
|
var aciTree_hash = {
|
|
__extend: function() {
|
|
$.extend(this._private, {
|
|
lastSelect: null,
|
|
lastOpen: null,
|
|
// store `aciFragment` api
|
|
hashApi: null
|
|
});
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// init hash
|
|
_hashInit: function() {
|
|
// init `aciFragment`
|
|
this._instance.jQuery.aciFragment();
|
|
this._private.hashApi = this._instance.jQuery.aciFragment('api');
|
|
this._instance.jQuery.bind('acitree' + this._private.nameSpace, function(event, api, item, eventName, options) {
|
|
switch (eventName) {
|
|
case 'init':
|
|
api._hashRestore();
|
|
break;
|
|
}
|
|
}).bind('acifragment' + this._private.nameSpace, this.proxy(function(event, api, anchorChanged) {
|
|
event.stopPropagation();
|
|
this._hashRestore();
|
|
}));
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this.extHast()) {
|
|
this._hashInit();
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// restore item states from hash
|
|
_hashRestore: function() {
|
|
var queue = this._instance.queue;
|
|
var process = function(opened) {
|
|
// open all hash items
|
|
for (var i in opened) {
|
|
(function(id) {
|
|
// add item to queue
|
|
queue.push(function(complete) {
|
|
this.search(null, {
|
|
success: function(item) {
|
|
this.open(item, {
|
|
uid: 'ui.hash',
|
|
success: complete,
|
|
fail: complete
|
|
});
|
|
},
|
|
fail: complete,
|
|
search: id
|
|
});
|
|
});
|
|
})(opened[i]);
|
|
}
|
|
};
|
|
if (this._instance.options.openHash) {
|
|
var hash = this._private.hashApi.get(this._instance.options.openHash, '');
|
|
if (hash.length && (hash != this._private.lastOpen)) {
|
|
this._private.lastOpen = hash;
|
|
var opened = hash.split(';');
|
|
process(opened);
|
|
}
|
|
}
|
|
// support `selectable` extension
|
|
if (this._instance.options.selectHash && this.extSelectable && this.extSelectable()) {
|
|
var hash = this._private.hashApi.get(this._instance.options.selectHash, '');
|
|
if (hash.length && (hash != this._private.lastSelect)) {
|
|
this._private.lastSelect = hash;
|
|
var opened = hash.split(';');
|
|
var selected = opened.pop();
|
|
process(opened);
|
|
if (selected) {
|
|
// select item
|
|
queue.push(function(complete) {
|
|
this.search(null, {
|
|
success: function(item) {
|
|
this.select(item, {
|
|
uid: 'ui.hash',
|
|
success: function(item) {
|
|
this.setVisible(item, {
|
|
center: true
|
|
});
|
|
complete();
|
|
},
|
|
fail: complete
|
|
});
|
|
},
|
|
fail: complete,
|
|
search: selected
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
// test if hash is enabled
|
|
extHast: function() {
|
|
return this._instance.options.selectHash || this._instance.options.openHash;
|
|
},
|
|
// override set option
|
|
option: function(option, value) {
|
|
var hash = this.extHast();
|
|
// call the parent
|
|
this._super(option, value);
|
|
if (this.extHast() != hash) {
|
|
if (hash) {
|
|
this._hashDone();
|
|
} else {
|
|
this._hashInit();
|
|
}
|
|
}
|
|
},
|
|
// done hash
|
|
_hashDone: function() {
|
|
this._instance.jQuery.unbind(this._private.nameSpace);
|
|
this._private.hashApi = null;
|
|
this._instance.jQuery.aciFragment('destroy');
|
|
},
|
|
// override `_destroyHook`
|
|
_destroyHook: function(unloaded) {
|
|
if (unloaded) {
|
|
this._hashDone();
|
|
}
|
|
// call the parent
|
|
this._super(unloaded);
|
|
}
|
|
};
|
|
|
|
// extend the base aciTree class and add the hash stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_hash, 'aciTreeHash');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
})(jQuery, this);
|
|
|
|
/*
|
|
* aciTree jQuery Plugin v4.5.0-rc.7
|
|
* http://acoderinsights.ro
|
|
*
|
|
* Copyright (c) 2014 Dragos Ursu
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
*
|
|
* Require jQuery Library >= v1.9.0 http://jquery.com
|
|
* + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
|
|
*/
|
|
|
|
/*
|
|
* This extension adds the possibility to sort the tree items.
|
|
* Require aciSortable https://github.com/dragosu/jquery-aciSortable and the utils extension for reordering items.
|
|
*/
|
|
|
|
(function($, window, undefined) {
|
|
|
|
// extra default options
|
|
|
|
var options = {
|
|
sortable: false, // if TRUE then the tree items can be sorted
|
|
sortDelay: 750, // how many [ms] before opening a inode on hovering when in drag
|
|
// called by the `aciSortable` inside the `drag` callback
|
|
sortDrag: function(item, placeholder, isValid, helper) {
|
|
if (!isValid) {
|
|
var move = this.getLabel(item);
|
|
if (this._private.dragDrop && (this._private.dragDrop.length > 1)) {
|
|
move += ' and #' + (this._private.dragDrop.length - 1) + ' more';
|
|
}
|
|
helper.html(move);
|
|
}
|
|
},
|
|
// called by the `aciSortable` inside the `valid` callback
|
|
sortValid: function(item, hover, before, isContainer, placeholder, helper) {
|
|
var move = this.getLabel(item);
|
|
if (this._private.dragDrop.length > 1) {
|
|
move += ' and #' + (this._private.dragDrop.length - 1) + ' more';
|
|
}
|
|
if (isContainer) {
|
|
helper.html('move ' + move + ' to ' + this.getLabel(this.itemFrom(hover)));
|
|
placeholder.removeClass('aciTreeAfter aciTreeBefore');
|
|
} else if (before !== null) {
|
|
if (before) {
|
|
helper.html('move ' + move + ' before ' + this.getLabel(hover));
|
|
placeholder.removeClass('aciTreeAfter').addClass('aciTreeBefore');
|
|
} else {
|
|
helper.html('move ' + move + ' after ' + this.getLabel(hover));
|
|
placeholder.removeClass('aciTreeBefore').addClass('aciTreeAfter');
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// aciTree sortable extension
|
|
|
|
var aciTree_sortable = {
|
|
__extend: function() {
|
|
// add extra data
|
|
$.extend(this._private, {
|
|
openTimeout: null,
|
|
dragDrop: null // the items used in drag & drop
|
|
});
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// init sortable
|
|
_sortableInit: function() {
|
|
this._instance.jQuery.aciSortable({
|
|
container: '.aciTreeUl',
|
|
item: '.aciTreeLi',
|
|
child: 50,
|
|
childHolder: '<ul class="aciTreeUl aciTreeChild"></ul>',
|
|
childHolderSelector: '.aciTreeChild',
|
|
placeholder: '<li class="aciTreeLi aciTreePlaceholder"><div></div></li>',
|
|
placeholderSelector: '.aciTreePlaceholder',
|
|
helper: '<div class="aciTreeHelper"></div>',
|
|
helperSelector: '.aciTreeHelper',
|
|
// just before drag start
|
|
before: this.proxy(function(item) {
|
|
// init before drag
|
|
if (!this._initDrag(item)) {
|
|
return false;
|
|
}
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'beforedrag')) {
|
|
this._trigger(item, 'dragfail');
|
|
return false;
|
|
}
|
|
return true;
|
|
}),
|
|
// just after drag start, before dragging
|
|
start: this.proxy(function(item, placeholder, helper) {
|
|
this._instance.jQuery.addClass('aciTreeDragDrop');
|
|
helper.stop(true).css('opacity', 1);
|
|
}),
|
|
// when in drag
|
|
drag: this.proxy(function(item, placeholder, isValid, helper) {
|
|
if (!isValid) {
|
|
window.clearTimeout(this._private.openTimeout);
|
|
}
|
|
if (this._instance.options.sortDrag) {
|
|
this._instance.options.sortDrag.apply(this, arguments);
|
|
}
|
|
}),
|
|
// to check the drop target (when the placeholder is repositioned)
|
|
valid: this.proxy(function(item, hover, before, isContainer, placeholder, helper) {
|
|
window.clearTimeout(this._private.openTimeout);
|
|
if (!this._checkDrop(item, hover, before, isContainer, placeholder, helper)) {
|
|
return false;
|
|
}
|
|
var options = this._options({
|
|
hover: hover,
|
|
before: before,
|
|
isContainer: isContainer,
|
|
placeholder: placeholder,
|
|
helper: helper
|
|
});
|
|
// a way to cancel the operation
|
|
if (!this._trigger(item, 'checkdrop', options)) {
|
|
return false;
|
|
}
|
|
if (this.isInode(hover) && !this.isOpen(hover)) {
|
|
this._private.openTimeout = window.setTimeout(this.proxy(function() {
|
|
this.open(hover);
|
|
}), this._instance.options.sortDelay);
|
|
}
|
|
if (this._instance.options.sortValid) {
|
|
this._instance.options.sortValid.apply(this, arguments);
|
|
}
|
|
return true;
|
|
}),
|
|
// when dragged as child
|
|
create: this.proxy(function(api, item, hover) {
|
|
if (this.isLeaf(hover)) {
|
|
hover.append(api._instance.options.childHolder);
|
|
return true;
|
|
}
|
|
return false;
|
|
}, true),
|
|
// on drag end
|
|
end: this.proxy(function(item, hover, placeholder, helper) {
|
|
window.clearTimeout(this._private.openTimeout);
|
|
var options = {
|
|
placeholder: placeholder,
|
|
helper: helper
|
|
};
|
|
options = this._options(options, 'sorted', 'dropfail', null, item);
|
|
if (placeholder.parent().length) {
|
|
var prev = this.prev(placeholder, true);
|
|
if (prev.length) {
|
|
// add after a item
|
|
placeholder.detach();
|
|
var items = $(this._private.dragDrop.get().reverse());
|
|
this._private.dragDrop = null;
|
|
items.each(this.proxy(function(element) {
|
|
this.moveAfter($(element), this._inner(options, {
|
|
success: options.success,
|
|
fail: options.fail,
|
|
after: prev
|
|
}));
|
|
}, true));
|
|
} else {
|
|
var next = this.next(placeholder, true);
|
|
if (next.length) {
|
|
// add before a item
|
|
placeholder.detach();
|
|
var items = $(this._private.dragDrop.get().reverse());
|
|
this._private.dragDrop = null;
|
|
items.each(this.proxy(function(element) {
|
|
this.moveBefore($(element), this._inner(options, {
|
|
success: options.success,
|
|
fail: options.fail,
|
|
before: next
|
|
}));
|
|
}, true));
|
|
} else {
|
|
// add as a child
|
|
var parent = this.parent(placeholder);
|
|
var container = placeholder.parent();
|
|
placeholder.detach();
|
|
container.remove();
|
|
if (this.isLeaf(parent)) {
|
|
// we can set asChild only for leaves
|
|
var items = this._private.dragDrop;
|
|
this.asChild(items.eq(0), this._inner(options, {
|
|
success: function() {
|
|
this._success(item, options);
|
|
this.open(parent);
|
|
items.filter(':gt(0)').each(this.proxy(function(element) {
|
|
this.moveAfter($(element), this._inner(options, {
|
|
success: options.success,
|
|
fail: options.fail,
|
|
after: this.last(parent)
|
|
}));
|
|
}, true));
|
|
},
|
|
fail: options.fail,
|
|
parent: parent
|
|
}));
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this._fail(item, options);
|
|
}
|
|
this._private.dragDrop = null;
|
|
if (helper.parent().length) {
|
|
// the helper is inserted in the DOM
|
|
var top = $(window).scrollTop();
|
|
var left = $(window).scrollLeft();
|
|
var rect = item[0].getBoundingClientRect();
|
|
// animate helper to item position
|
|
helper.animate({
|
|
top: rect.top + top,
|
|
left: rect.left + left,
|
|
opacity: 0
|
|
},
|
|
{
|
|
complete: function() {
|
|
// detach the helper when completed
|
|
helper.detach();
|
|
}
|
|
});
|
|
}
|
|
this._instance.jQuery.removeClass('aciTreeDragDrop');
|
|
})
|
|
});
|
|
},
|
|
// override `_initHook`
|
|
_initHook: function() {
|
|
if (this.extSortable()) {
|
|
this._sortableInit();
|
|
}
|
|
// call the parent
|
|
this._super();
|
|
},
|
|
// reduce items by removing the childrens
|
|
_parents: function(items) {
|
|
var len = items.length, a, b, remove = [];
|
|
for (var i = 0; i < len - 1; i++) {
|
|
a = items.eq(i);
|
|
for (var j = i + 1; j < len; j++) {
|
|
b = items.eq(j);
|
|
if (this.isChildren(a, b)) {
|
|
remove.push(items[j]);
|
|
} else if (this.isChildren(b, a)) {
|
|
remove.push(items[i]);
|
|
}
|
|
}
|
|
}
|
|
return items.not(remove);
|
|
},
|
|
// called before drag start
|
|
_initDrag: function(item) {
|
|
// support `selectable` extension
|
|
if (this.extSelectable && this.extSelectable()) {
|
|
if (!this.hasFocus()) {
|
|
this._instance.jQuery.focus();
|
|
}
|
|
if (!this.isEnabled(item)) {
|
|
return false;
|
|
}
|
|
var drag = this.selected();
|
|
if (drag.length) {
|
|
if (!this.isSelected(item)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
drag = item;
|
|
}
|
|
this._private.dragDrop = this._parents(drag);
|
|
} else {
|
|
this._instance.jQuery.focus();
|
|
this._private.dragDrop = item;
|
|
}
|
|
return true;
|
|
},
|
|
// check the drop target
|
|
_checkDrop: function(item, hover, before, isContainer, placeholder, helper) {
|
|
var items = this._private.dragDrop;
|
|
if (!items) {
|
|
return false;
|
|
}
|
|
var test = this.itemFrom(hover);
|
|
if (items.is(test) || items.has(test[0]).length) {
|
|
return false;
|
|
}
|
|
if (!isContainer) {
|
|
test = before ? this.prev(hover) : this.next(hover);
|
|
if (items.is(test)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
// test if sortable is enabled
|
|
extSortable: function() {
|
|
return this._instance.options.sortable;
|
|
},
|
|
// override set `option`
|
|
option: function(option, value) {
|
|
if (this.wasInit() && !this.isLocked()) {
|
|
if ((option == 'sortable') && (value != this.extSortable())) {
|
|
if (value) {
|
|
this._sortableInit();
|
|
} else {
|
|
this._sortableDone();
|
|
}
|
|
}
|
|
}
|
|
// call the parent
|
|
this._super(option, value);
|
|
},
|
|
// done sortable
|
|
_sortableDone: function() {
|
|
this._instance.jQuery.unbind(this._private.nameSpace);
|
|
this._instance.jQuery.aciSortable('destroy');
|
|
},
|
|
// override `_destroyHook`
|
|
_destroyHook: function(unloaded) {
|
|
if (unloaded) {
|
|
this._sortableDone();
|
|
}
|
|
// call the parent
|
|
this._super(unloaded);
|
|
}
|
|
};
|
|
|
|
// extend the base aciTree class and add the sortable stuff
|
|
aciPluginClass.plugins.aciTree = aciPluginClass.plugins.aciTree.extend(aciTree_sortable, 'aciTreeSortable');
|
|
|
|
// add extra default options
|
|
aciPluginClass.defaults('aciTree', options);
|
|
|
|
})(jQuery, this);
|