OpenXE/www/js/aciTree/js/jquery.aciTree.checkbox.js
2021-05-21 08:49:41 +02:00

484 lines
20 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
*/
/*
* 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);