/**
* jQuery Tree Control
*
* @author Maxim Vasiliev
*/
(function($){
var CLASS_JQUERY_TREE = 'jquery-tree';
var CLASS_JQUERY_TREE_CONTROLS = 'jquery-tree-controls';
var CLASS_JQUERY_TREE_COLLAPSE_ALL = 'jquery-tree-collapseall';
var CLASS_JQUERY_TREE_EXPAND_ALL = 'jquery-tree-expandall';
var CLASS_JQUERY_TREE_COLLAPSED = 'jquery-tree-collapsed';
var CLASS_JQUERY_TREE_HANDLE = 'jquery-tree-handle';
var CLASS_JQUERY_TREE_TITLE = 'jquery-tree-title';
var CLASS_JQUERY_TREE_NODE = 'jquery-tree-node';
var CLASS_JQUERY_TREE_LEAF = 'jquery-tree-leaf';
var CLASS_JQUERY_TREE_CHECKED = 'jquery-tree-checked';
var CLASS_JQUERY_TREE_UNCHECKED = 'jquery-tree-unchecked';
var CLASS_JQUERY_TREE_CHECKED_PARTIAL = 'jquery-tree-checked-partial';
var COLLAPSE_ALL_CODE = 'Свернуть все';
var EXPAND_ALL_CODE = 'Развернуть все';
var TREE_CONTROLS_CODE = '
' +
COLLAPSE_ALL_CODE +
EXPAND_ALL_CODE +
'
';
var TREE_NODE_HANDLE_CODE = '+';
var TREE_NODE_HANDLE_COLLAPSED = "+";
var TREE_NODE_HANDLE_EXPANDED = "−";
$.fn.extend({
/**
* Делает дерево из структуры вида:
*
*/
Tree: function(){
// Добавим контролы для всего дерева (все свернуть, развернуть и т.д.), и добавим класс
$(this)
.addClass(CLASS_JQUERY_TREE)
.before(TREE_CONTROLS_CODE)
.prev('.' + CLASS_JQUERY_TREE_CONTROLS)
.find('.' + CLASS_JQUERY_TREE_COLLAPSE_ALL).click(function(){
$(this).parent().next('.' + CLASS_JQUERY_TREE)
.find('li:has(ul)')
.addClass(CLASS_JQUERY_TREE_COLLAPSED)
.find('.' + CLASS_JQUERY_TREE_HANDLE)
.html(TREE_NODE_HANDLE_COLLAPSED);
})
.parent('.' + CLASS_JQUERY_TREE_CONTROLS).find('.' + CLASS_JQUERY_TREE_EXPAND_ALL)
.click(function(){
$(this).parent().next('.' + CLASS_JQUERY_TREE)
.find('li:has(ul)')
.removeClass(CLASS_JQUERY_TREE_COLLAPSED)
.find('.' + CLASS_JQUERY_TREE_HANDLE)
.html(TREE_NODE_HANDLE_EXPANDED);
});
$('li', this).find(':first').addClass(CLASS_JQUERY_TREE_TITLE)
.closest('li').addClass(CLASS_JQUERY_TREE_LEAF);
// Для всех элементов, являющихся узлами (имеющих дочерние элементы)...
$('li:has(ul:has(li))', this).find(':first')
// ... добавим элемент, открывающий/закрывающий узел
.before(TREE_NODE_HANDLE_CODE)
// ... добавим к контейнеру класс "узел дерева" и "свернем".
.closest('li')
.addClass(CLASS_JQUERY_TREE_NODE)
.addClass(CLASS_JQUERY_TREE_COLLAPSED)
.removeClass(CLASS_JQUERY_TREE_LEAF);
// ... повесим обработчик клика
$('.' + CLASS_JQUERY_TREE_HANDLE, this).bind('click', function(){
var leafContainer = $(this).parent('li');
var leafHandle = leafContainer.find('>.' + CLASS_JQUERY_TREE_HANDLE);
leafContainer.toggleClass(CLASS_JQUERY_TREE_COLLAPSED);
if (leafContainer.hasClass(CLASS_JQUERY_TREE_COLLAPSED))
leafHandle.html(TREE_NODE_HANDLE_COLLAPSED);
else
leafHandle.html(TREE_NODE_HANDLE_EXPANDED);
});
// Добавляем обработку клика по чекбоксам
$('input:checkbox', this).click(function(){
setLabelClass(this);
checkCheckbox(this);
})
// Выставляем чекбоксам изначальные классы
.each(function(){
setLabelClass(this);
if (this.checked)
checkParentCheckboxes(this);
})
// Для IE вешаем обработчики на лейбл
.closest('label').click(function(){
labelClick(this);
checkCheckbox($('input:checkbox', this));
});
}
});
/**
* Рекурсивно проверяет, все ли чекбоксы в поддереве родительского узла выбраны.
* Если ни один чекбокс не выбран - снимает чек с родительского чекбокса
* Если хотя бы один, но не все - выставляет класс CLASS_JQUERY_TREE_CHECKED_PARTIAL родительскому чекбоксу
* Если все - ставит чек на родительский чекбокс
*
* @param {Object} checkboxElement текущий чекбокс
*/
function checkParentCheckboxes(checkboxElement){
if (typeof checkboxElement == 'undefined' || !checkboxElement)
return;
// проверим, все ли чекбоксы выделены/частично выделены на вышележащем уровне
var closestNode = $(checkboxElement).closest('ul');
var allCheckboxes = closestNode.find('input:checkbox');
var checkedCheckboxes = closestNode.find('input:checkbox:checked');
var allChecked = allCheckboxes.length == checkedCheckboxes.length;
var parentCheckbox = closestNode.closest('li').find('>.' + CLASS_JQUERY_TREE_TITLE + ' input:checkbox');
if (parentCheckbox.length > 0) {
parentCheckbox.get(0).checked = allChecked;
if (!allChecked && checkedCheckboxes.length > 0)
parentCheckbox.closest('label')
.addClass(CLASS_JQUERY_TREE_CHECKED_PARTIAL)
.removeClass(CLASS_JQUERY_TREE_CHECKED)
.removeClass(CLASS_JQUERY_TREE_UNCHECKED);
else
if (allChecked)
parentCheckbox.closest('label')
.removeClass(CLASS_JQUERY_TREE_CHECKED_PARTIAL)
.removeClass(CLASS_JQUERY_TREE_UNCHECKED)
.addClass(CLASS_JQUERY_TREE_CHECKED);
else
parentCheckbox.closest('label')
.removeClass(CLASS_JQUERY_TREE_CHECKED_PARTIAL)
.removeClass(CLASS_JQUERY_TREE_CHECKED)
.addClass(CLASS_JQUERY_TREE_UNCHECKED);
checkParentCheckboxes(parentCheckbox.get(0));
}
}
/**
* Если у текущего чекбокса есть дочерние узлы - меняет их состояние
* на состояние текущего чекбокса
*
* @param {Object} checkboxElement текущий чекбокс
*/
function checkCheckbox(checkboxElement){
// чекнем/анчекнем нижележащие чекбоксы
$(checkboxElement).closest('li').find('input:checkbox').each(function(){
this.checked = $(checkboxElement).attr('checked');
setLabelClass(this);
});
checkParentCheckboxes(checkboxElement);
};
/**
* Выставляет класс лейблу в зависимости от состояния чекбокса
*
* @param {Object} checkboxElement чекбокс
*/
function setLabelClass(checkboxElement){
isChecked = $(checkboxElement).attr('checked');
if (isChecked) {
$(checkboxElement).closest('label')
.addClass(CLASS_JQUERY_TREE_CHECKED)
.removeClass(CLASS_JQUERY_TREE_UNCHECKED)
.removeClass(CLASS_JQUERY_TREE_CHECKED_PARTIAL);
}
else {
$(checkboxElement).closest('label')
.addClass(CLASS_JQUERY_TREE_UNCHECKED)
.removeClass(CLASS_JQUERY_TREE_CHECKED)
.removeClass(CLASS_JQUERY_TREE_CHECKED_PARTIAL);
}
};
/**
* Обрабатывает клик по лейблу (для IE6)
*/
function labelClick(labelElement){
var checkbox = $('input:checkbox', labelElement);
var checked = checkbox.attr('checked');
checkbox.attr('checked', !checked);
setLabelClass(checkbox);
}
})(jQuery);