/**
* CheckBoxTreeView widget. Extrends TreeView widget to support relevant events and methods od checkbox tree.
* This widget represents the root cotainer for CheckBoxTreeNode objects that build the actual tree structure.
* Therefore this widget will not usually have any visual representation. Its also responsible for handling node events.
* @class CheckBoxTreeView
* @constructor
* @extends TreeView
* @param {Object} config User configuration object.
*/
Y.CheckBoxTreeView = Y.Base.create(CHECKBOXTREEVIEW, Y.TreeView, [], {
initializer : function(config) {
/**
* Fires when node checkbox state is changed
* @event check
* @param {TreeNode} treenode tree node that is checked
*/
this.publish("check", {
defaultFn: this._checkDefaultFn
});
},
/**
* Default event handler for "check" event
* @method _nodeClickDefaultFn
* @protected
*/
_checkDefaultFn: function(e) {
e.treenode.toggleCheckedState();
},
bindUI: function() {
var boundingBox = this.get(BOUNDING_BOX);
Y.CheckBoxTreeView.superclass.bindUI.apply(this, arguments);
boundingBox.on("click", function(e) {
var twidget = Y.Widget.getByNode(e.target),
check = false;
if (twidget instanceof Y.CheckBoxTreeNode) {
Y.Array.each(e.target.get("className").split(" "), function (className) {
switch (className) {
case classNames.checkbox:
check = true;
break;
case classNames.labelContent:
if (this.get("checkOnLabelClick")) {
check = true;
}
break;
}
}, this);
if (check) {
this.fire("check", {treenode: twidget});
}
}
}, this);
boundingBox.on("keypress", function(e) {
var target = e.target,
twidget = Y.Widget.getByNode(target),
keycode = e.keyCode;
if (!twidget instanceof Y.CheckBoxTreeNode) {
return;
}
if (keycode == 32) {
this.fire("check", {treenode: twidget});
e.preventDefault();
}
}, this);
},
/**
* Returns the list of nodes that are roots of checked subtrees
* @method getChecked
* @return {Array} array of tree nodes
*/
getChecked : function() {
var checkedChildren = Array(),
halfcheckedChildren = Array(),
child,
analyzeChild;
this.each(function (child) {
if (child.get("checked") == checkStates.checked) {
checkedChildren.push(child);
} else if (child.get("checked") == checkStates.halfchecked) {
halfcheckedChildren.push(child);
}
});
analyzeChild = function (child) {
if (child.get("checked") == checkStates.checked) {
checkedChildren.push(child);
} else if (child.get("checked") == checkStates.halfchecked) {
halfcheckedChildren.push(child);
}
};
while (halfcheckedChildren.length > 0) {
child = halfcheckedChildren.pop();
child.each(analyzeChild);
}
return checkedChildren;
},
/**
* Returns list of pathes (breadcrumbs) of nodes that are roots of checked subtrees
* @method getCheckedPaths
* @param cfg {Object} An object literal with the following properties:
* <dl>
* <dt><code>labelAttr</code></dt>
* <dd>Attribute name to use for node representation. Can be any attribute of TreeNode</dd>
* <dt><code>reverse</code></dt>
* <dd>Return breadcrumbs from the node to root instead of root to the node</dd>
* </dl>
* @return {Array} array of node label arrays
*/
getCheckedPaths : function(cfg) {
var nodes = this.getChecked(),
nodeArray = Array();
if (!cfg) {
cfg = {};
}
if (!cfg.labelAttr) {
cfg.labelAttr = "label";
}
Y.Array.each(nodes, function(node) {
nodeArray.push(node.path(cfg));
});
return nodeArray;
}
}, {
NAME : CHECKBOXTREEVIEW,
ATTRS : {
/**
* @attribute defaultChildType
* @type String
* @readOnly
* @description default child type definition
*/
defaultChildType : {
value: "CheckBoxTreeNode",
readOnly: true
},
/**
* @attribute checkOnLabelClick
* @type Boolean
* @description Whether to change node checked state on label clicks with addition to checkbox control clicks
*/
checkOnLabelClick : {
value: true,
validator: Y.Lang.isBoolean
}
}
});
/**
* CheckBoxTreeNode widget. Provides a tree style node widget with checkbox
* It extends Y.TreeNode, please refer to it's documentation for more info.
* @class CheckBoxTreeNode
* @constructor
* @extends TreeNode
* @param {Object} config User configuration object.
*/
Y.CheckBoxTreeNode = Y.Base.create(CHECKBOXTREENODE, Y.TreeNode, [], {
initializer : function() {
this.publish("childCheckedSateChange", {
defaultFn: this._childCheckedSateChangeDefaultFn,
bubbles: false
});
},
/**
* Default handler for childCheckedSateChange. Updates this parent state
* to match current children states.
* @method _childCheckedSateChangeDefaultFn
* @protected
*/
_childCheckedSateChangeDefaultFn : function(e) {
var checkedChildren = 0,
halfCheckedChildren = 0,
cstate;
this.each(function(child) {
cstate = child.get("checked");
if (cstate == checkStates.checked) {
checkedChildren++;
}
if (cstate == checkStates.halfchecked) {
halfCheckedChildren++;
}
});
if (checkedChildren == this.size()) {
this.set("checked", checkStates.checked);
} else if (checkedChildren > 0 || halfCheckedChildren > 0) {
this.set("checked", checkStates.halfchecked);
} else {
this.set("checked", checkStates.unchecked);
}
if (!this.isRoot()) {
this.get("parent").fire("childCheckedSateChange");
}
},
bindUI : function() {
Y.CheckBoxTreeNode.superclass.bindUI.apply(this, arguments);
this.on("checkedChange", this._onCheckedChange);
},
/**
* Event handler that updates UI according to checked attribute change
* @method _onCheckedChange
* @protected
*/
_onCheckedChange: function(e) {
e.stopPropagation();
this._updateCheckedStateUI(e.prevVal, e.newVal);
},
/**
* Synchronize CSS classes to conform to checked state
* @method _updateCheckedStateUI
* @protected
*/
_updateCheckedStateUI : function(oldState, newState) {
var checkBox = this._getCheckBoxNode();
checkBox.removeClass(checkStatesClasses[oldState]);
checkBox.addClass(checkStatesClasses[newState]);
},
/**
* Returns checkbox node
* @method _getCheckBoxNode
* @protected
*/
_getCheckBoxNode : function() {
return this.get(BOUNDING_BOX).one("." + classNames.checkbox);
},
CHECKBOX_TEMPLATE : "<span class={checkboxClassName}></span>",
renderUI : function() {
var parentNode,
labelContentNode,
checkboxNode;
Y.CheckBoxTreeNode.superclass.renderUI.apply(this, arguments);
checkboxNode = Y.Node.create(Y.substitute(this.CHECKBOX_TEMPLATE, {checkboxClassName: classNames.checkbox}));
labelContentNode = this._getLabelContentNode();
parentNode = labelContentNode.get("parentNode");
labelContentNode.remove();
checkboxNode.append(labelContentNode);
parentNode.append(checkboxNode);
// update state
this._getCheckBoxNode().addClass(checkStatesClasses[this.get("checked")]);
// reuse CSS
this.get(CONTENT_BOX).addClass(classNames.content);
},
syncUI : function() {
Y.CheckBoxTreeNode.superclass.syncUI.apply(this, arguments);
this._syncChildren();
},
/**
* Toggles checked / unchecked state of the node
* @method toggleCheckedState
*/
toggleCheckedState : function() {
if (this.get("checked") == checkStates.checked) {
this._uncheck();
} else {
this._check();
}
this.get("parent").fire("childCheckedSateChange");
},
/**
* Sets this node as checked and propagates to children
* @method _check
* @protected
*/
_check : function() {
this.set("checked", checkStates.checked);
this.each(function(child) {
child._check();
});
},
/**
* Set this node as unchecked and propagates to children
* @method _uncheck
* @protected
*/
_uncheck : function() {
this.set("checked", checkStates.unchecked);
this.each(function(child) {
child._uncheck();
});
},
/**
* Synchronizes children states to match the state of the current node
* @method _uncheck
* @protected
*/
_syncChildren : function() {
if (this.get("checked") == checkStates.unchecked) {
this._uncheck();
} else if (this.get("checked") == checkStates.checked) {
this._check();
} else {
this.each(function (child) {
child._syncChildren();
});
}
}
}, {
NAME : CHECKBOXTREENODE,
ATTRS : {
/**
* @attribute defaultChildType
* @type String
* @readOnly
* @description default child type definition
*/
defaultChildType : {
value: "CheckBoxTreeNode",
readOnly: true
},
/**
* @attribute checked
* @type {String|Number}
* @description Signifies current "checked" state. Accepts either <code>unchecked</code>, <code>halfchecked</code>, <code>checked</code>.
* or correspondingly 10, 20, 30. Getter returns only numeric value.
*/
checked : {
value : 10,
setter : function(val) {
var returnVal = Y.Attribute.INVALID_VALUE;
if (checkStates[val] !== null) {
returnVal = checkStates[val];
} else if ([10, 20, 30].indexOf(val) >= 0) {
returnVal = val;
}
return returnVal;
}
}
}
});