// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
 * @class   Ext.ux.tree.CheckTreePanel
 * @extends Ext.tree.TreePanel
 *
 * <p>
 * I've taken Condor's work from 
 * <a href="http://extjs.com/forum/showthread.php?t=44369">http://extjs.com/forum/showthread.php?t=44369</a>,
 * removed tri-state functionality and put all files into one extension.
 * </p>
 * <p>
 * CheckTreePanel has been designed to fully load nodes in one shot using
 * nested "children" array. 
 * </p>
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 * @revision $Id: Ext.ux.tree.CheckTreePanel.js 593 2009-02-24 10:17:11Z jozo $
 *
 * @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum    56875
 * @demo     http://checktree.extjs.eu
 * @download 
 * <ul>
 * <li><a href="http://checktree.extjs.eu/checktree.tar.bz2">checktree.tar.bz2</a></li>
 * <li><a href="http://checktree.extjs.eu/checktree.tar.gz">checktree.tar.gz</a></li>
 * <li><a href="http://checktree.extjs.eu/checktree.zip">checktree.zip</a></li>
 * </ul>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.tree');

// {{{
/**
 * Creates new CheckTreePanel
 * @constructor
 * @param {Object} config The configuration object
 */
Ext.ux.tree.CheckTreePanel = Ext.extend(Ext.tree.TreePanel, {

	// {{{
	// default config
	/**
	 * @cfg {String} bubbleCheck 
	 * Check/uncheck also parent nodes. Valid values: none, checked, unchecked, all. Defaults to 'checked'.
	 */
	 bubbleCheck:'checked' 

	/**
	 * @cfg {String} cascadeCheck 
	 * Check/uncheck also child nodes. Valid values: none, checked, unchecked, all. Defaults to 'unchecked'.
	 */
	,cascadeCheck:'unchecked'

	/**
	 * @cfg {Boolean} deepestOnly 
	 * Set this to true for getValue to return only deepest checked node ids. Defaults to false.
	 */
	,deepestOnly:false

	/**
	 * @cfg {Boolean} expandOnCheck 
	 * Expand the node on check if true. Defaults to true;
	 */
	,expandOnCheck:true

	/**
	 * @cfg {Boolean/Object} filter
	 * Set it to false to not create tree filter or set a custom filter. Defaults to true.
	 */
	,filter:true

	/**
	 * @cfg {String} separator
	 * Separator for convertValue function. Defaults to ',' (comma).
	 */
	,separator:','

	/**
	 * @cfg {String} cls
	 * Class to add to CheckTreePanel. A suitable css file must be included. Defaults to 'ux-checktree'.
	 */
	,cls:'ux-checktree'

	/**
	 * @cfg {Object} baseAttrs
	 * baseAttrs for loader. Defaults to {} (empty object).
	 */
	,baseAttrs:{}
	// }}}
	// {{{
	,initComponent:function() {

		// use our event model
		this.eventModel = new Ext.ux.tree.CheckTreeEventModel(this);

		// call parent initComponent
		Ext.ux.tree.CheckTreePanel.superclass.initComponent.apply(this, arguments);
    
		// pass this.baseAttrs and uiProvider down the line
		var baseAttrs = Ext.apply({uiProvider:Ext.ux.tree.CheckTreeNodeUI}, this.baseAttrs);
		Ext.applyIf(this.loader, {baseAttrs:baseAttrs, preloadChildren:true});

		// make sure that nodes are deeply preloaded
		if(true === this.loader.preloadChildren) {
			this.loader.on('load', function(loader, node) {
				node.cascade(function(n) {
					loader.doPreload(n);
					n.loaded = true;
				});
			});
		}

		// create tree filter
		if(true === this.filter) {
			var Filter = Ext.ux.tree.TreeFilterX ? Ext.ux.tree.TreeFilterX : Ext.tree.TreeFilter;
			this.filter = new Filter(this, {autoClear:true});
		}

	} // eo function initComponent
	// }}}
	// {{{
	/*
	 * get "value" (array of checked nodes ids)
	 * @return {Array} Array of chcecked nodes ids
	 */
	,getValue:function() {
		var a = [];
		this.root.cascade(function(n) {
			if(true === n.attributes.checked) {
				if(false === this.deepestOnly || !this.isChildChecked(n)) {
					a.push(n.id);
				}
			}
		}, this);
		return a;
	} // eo function getValue
	// {{{
	/**
	 * Helper function for getValue
	 * @param {Ext.tree.TreeNode} node
	 * @return {Boolean} true if node has a child checked, false otherwise
	 * @private
	 */
	,isChildChecked:function(node) {
		var checked = false;
		Ext.each(node.childNodes, function(child) {
			if(child.attributes.checked) {
				checked = true;
			}
		});
		return checked;
	} // eo function isChildChecked
	// }}}
	// }}}
	// {{{
	/*
	 * clears "value", unchecks all nodes
	 * @return {Ext.ux.tree.CheckTreePanel} this
	 */
	,clearValue:function() {
		this.root.cascade(function(n) {
			var ui = n.getUI();
			if(ui && ui.setChecked) {
				ui.setChecked(false);
			}
		});
		this.value = [];
		return this;
	} // eo function clearValue
	// }}}
	// {{{
	/*
	 * converts passed value to array
	 * @param {Mixed} val variable number of arguments, e.g. convertValue('1,8,7', 3, [9,4])
	 * @return {Array} converted value
	 */
	,convertValue:function(val) {
		// init return array
		var a = [];

		// calls itself recursively if necessary
		if(1 < arguments.length) {
			for(var i = 0; i < arguments.length; i++) {
				a.push(this.convertValue(arguments[i]));
			}
		}

		// nothing to do for arrays
		else if(Ext.isArray(val)) {
			a = val;
		}

		// just push numbers
		else if('number' === typeof val) {
			a.push(val);
		}

		// split strings
		else if('string' === typeof val) {
			a = val.split(this.separator);
		}

		return a;
	} // eo function convertValue
	// }}}
	// {{{
	/*
	 * Set nodes checked/unchecked
	 * @param {Mixed} val variable number of arguments, e.g. setValue('1,8,7', 3, [9,4])
	 * @return {Array} value. Array of checked nodes
	 */
	,setValue:function(val) {

		// uncheck all first
		this.clearValue();

		// process arguments
		this.value = this.convertValue.apply(this, arguments);

		// check nodes
		Ext.each(this.value, function(id) {
			var n = this.getNodeById(id);
			if(n) {
				var ui = n.getUI();
				if(ui && ui.setChecked) {
					ui.setChecked(true);

					// expand checked nodes
					if(true === this.expandOnCheck) {
						n.bubbleExpand();
					}
				}
			}
		}, this);

		return this.value;
	} // eo function setValue
	// }}}
	// {{{
	/*
	 * arbitrary attribute of checked nodes (text by default) is joined and separated with this.separator
	 * @param {String} attr Attribute to serialize
	 * @return {String} serialized attr of checked nodes
	 */
	,serialize:function(attr) {
		attr = attr || 'text';
		var a = [];
		this.root.cascade(function(n) {
			if(true === n.attributes.checked) {
				if(false === this.deepestOnly || !this.isChildChecked(n)) {
					a.push(n[attr]);
				}
			}
		}, this);
		return a.join(this.separator + ' ');
	} //  function serialize
	// }}}
	// {{{
	/*
	 * alias for serialize function
	 * @param {String} attr Attribute to serialize
	 * @return {String} serialized attr of checked nodes
	 */
	,getText:function(attr) {
		return this.serialize(attr);
	} // eo function getText
	// }}}
	// {{{
	/**
	 * Creates hidden field if we're running in form for BasicForm::getValues to work
	 * @private
	 */
	,onRender:function() {
		Ext.ux.tree.CheckTreePanel.superclass.onRender.apply(this, arguments);
		if(true === this.isFormField) {
			this.hiddenField = this.body.createChild({
				tag:'input', type:'hidden', name:this.name || this.id
			}, undefined, true);
		}
	} // eo function onRender
	// }}}
	// {{{
	/**
	 * Updates hidden field if one exists on checkchange
	 * @private
	 */
	,updateHidden:function() {
		if(this.hiddenField) {
			this.hiddenField.value = this.getValue().join(this.separator);
		}
	} // eo function updateHidden
	// }}}

	// form field compatibility methods
	// todo: They could be made much more clever
	,clearInvalid:Ext.emptyFn
	,markInvalid:Ext.emptyFn
	,validate:function() {
		return true;
	}
	,isValid:function() {
		return true;
	}
	,getName:function() {
		return this.name || this.id || '';
	}

}) // eo extend

Ext.reg('checktreepanel', Ext.ux.tree.CheckTreePanel);
// }}}
// {{{
/**
 * @private
 * @ignore
 */
Ext.override(Ext.tree.TreeNode, {
	/**
	 * Expands all parent nodes of this node
	 * @private
	 */
	bubbleExpand:function() {
		var root = this.getOwnerTree().root;
		var branch = [];
		var p = this;
		while(p !== root) {
			p = p.parentNode;
			branch.push(p);
		}
		branch.reverse();
		Ext.each(branch, function(n) {
			n.expand(false, false);
		});
	}
});
// }}}
// {{{
/**
 * @class Ext.ux.tree.CheckTreeNodeUI
 * @extends Ext.tree.TreeNodeUI
 *
 * Adds checkbox to the tree node UI. This class is not intended
 * to be instantiated explicitly; it is used internally in CheckTreePanel.
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 *
 * @license Ext.ux.tree.CheckTreeNodeUI is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */
/**
 * @constructor
 * @Creates new CheckTreeNodeUI
 * @param {Ext.tree.TreeNode} node Node to create the UI for
 */
Ext.ux.tree.CheckTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {

	// {{{
	/**
	 * This is slightly adjusted original renderElements method
	 * @private
	 */
	renderElements:function(n, a, targetNode, bulkRender){
		
		this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() :'';
		var checked = n.attributes.checked;
		var href = a.href ? a.href : Ext.isGecko ? "" :"#";
        var buf = [
			 '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">'
			,'<span class="x-tree-node-indent">',this.indentMarkup,"</span>"
			,'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />'
			,'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" :""),(a.iconCls ? " "+a.iconCls :""),'" unselectable="on" />'
			,'<img src="'+this.emptyIcon+'" class="x-tree-checkbox'+(true === checked ? ' x-tree-node-checked' :'')+'" />'
			,'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" '
			,a.hrefTarget ? ' target="'+a.hrefTarget+'"' :"", '><span unselectable="on">',n.text,"</span></a></div>"
			,'<ul class="x-tree-node-ct" style="display:none;"></ul>'
			,"</li>"
		].join('');
		var nel;
		if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
			this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
		}else{
			this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
		}
		this.elNode = this.wrap.childNodes[0];
		this.ctNode = this.wrap.childNodes[1];
		var cs = this.elNode.childNodes;
		this.indentNode = cs[0];
		this.ecNode = cs[1];
		this.iconNode = cs[2];
		this.checkbox = cs[3];
		this.cbEl = Ext.get(this.checkbox);
		this.anchor = cs[4];
		this.textNode = cs[4].firstChild;
	} // eo function renderElements
	// }}}
	// {{{
	/**
	 * Sets iconCls
	 * @param {String} iconCls
	 * @private
	 */
	,setIconCls:function(iconCls) {
		Ext.fly(this.iconNode).set({cls:'x-tree-node-icon ' + iconCls});
	} // eo function setIconCls
	// }}}
	// {{{
	/**
	 * @return {Boolean} true if the node is checked, false otherwise
	 * @private
	 */
	,isChecked:function() {
		return this.node.attributes.checked === true;
	} // eo function isChecked
	// }}}
	// {{{
	/**
	 * Called when check changes
	 * @private
	 */
	,onCheckChange:function() {
		var checked = this.isChecked();
		var tree = this.node.getOwnerTree();
		var bubble = tree.bubbleCheck;
		var cascade = tree.cascadeCheck;

		if('all' === bubble || (checked && 'checked' === bubble) || (!checked && 'unchecked' === bubble)) {
			this.updateParent(checked);
		}
		if('all' === cascade || (checked && 'checked' === cascade) || (!checked && 'unchecked' === cascade)) {
			this.updateChildren(checked);
		}

		tree.updateHidden();
		this.fireEvent('checkchange', this.node, checked);
	} // eo function onCheckChange
	// }}}
	// {{{
	/**
	 * Sets node UI checked/unchecked
	 * @param {Boolean} checked true to set node checked, false to uncheck
	 * @return {Boolean} checked
	 */
	,setChecked:function(checked) {
		checked = true === checked ? checked : false;
		var cb = this.cbEl || false;
		if(cb) {
			true === checked ? cb.addClass('x-tree-node-checked') : cb.removeClass('x-tree-node-checked');
		}
		this.node.attributes.checked = checked;
		this.onCheckChange();
		return checked;
	} // eo function setChecked
	// }}}
	// {{{
	/**
	 * Toggles check
	 * @return {Boolean} value after toggle
	 */
	,toggleCheck:function() {
		var checked = !this.isChecked();
		this.setChecked(checked);
		return checked;
	} // eo function toggleCheck
	// }}}
	// {{{
	/**
	 * Sets parents checked/unchecked. Used if bubbleCheck is not 'none'
	 * @param {Boolean} checked
	 * @private
	 */
	,updateParent:function(checked) {
		var p = this.node.parentNode;
		var ui = p ? p.getUI() : false;
		
		if(ui && ui.setChecked) {
			ui.setChecked(checked);
		}
	} // eo function updateParent
	// }}}
	// {{{
	/**
	 * Sets children checked/unchecked. Used if cascadeCheck is not 'none'
	 * @param {Boolean} checked
	 * @private
	 */
	,updateChildren:function(checked) {
		this.node.eachChild(function(n) {
			var ui = n.getUI();
			if(ui && ui.setChecked) {
				ui.setChecked(checked);
			}
		});
	} // eo function updateChildren
	// }}}
	// {{{
	/**
	 * Checkbox click event handler
	 * @private
	 */
	,onCheckboxClick:function() {
		if(!this.disabled) {
			this.toggleCheck();
		}
	} // eo function onCheckboxClick
	// }}}
	// {{{
	/**
	 * Checkbox over event handler
	 * @private
	 */
	,onCheckboxOver:function() {
		this.addClass('x-tree-checkbox-over');
	} // eo function onCheckboxOver
	// }}}
	// {{{
	/**
	 * Checkbox out event handler
	 * @private
	 */
	,onCheckboxOut:function() {
		this.removeClass('x-tree-checkbox-over');
	} // eo function onCheckboxOut
	// }}}
	// {{{
	/**
	 * Mouse down on checkbox event handler
	 * @private
	 */
	,onCheckboxDown:function() {
		this.addClass('x-tree-checkbox-down');
	} // eo function onCheckboxDown
	// }}}
	// {{{
	/**
	 * Mouse up on checkbox event handler
	 * @private
	 */
	,onCheckboxUp:function() {
		this.removeClass('x-tree-checkbox-down');
	} // eo function onCheckboxUp
	// }}}
 
}); // eo extend
// }}}
// {{{
/**
 * @class   Ext.ux.tree.CheckTreeEventModel
 * @extends Ext.tree.TreeEventModel
 *
 * Tree event model suitable for use with CheckTreePanel.
 * This class is not intended to be instantiated explicitly by a user
 * but it is used internally by CheckTreePanel.
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 *
 * @license Ext.ux.tree.CheckTreeEventModel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" 
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */
/**
 * Create new CheckTreeEventModel
 * @constructor
 * @param {Ext.ux.tree.CheckTreePanel} tree The tree to apply this event model to
 */
Ext.ux.tree.CheckTreeEventModel = Ext.extend(Ext.tree.TreeEventModel, {
     initEvents:function(){
        var el = this.tree.getTreeEl();
        el.on('click', this.delegateClick, this);
        if(this.tree.trackMouseOver !== false){
            el.on('mouseover', this.delegateOver, this);
            el.on('mouseout', this.delegateOut, this);
        }
        el.on('mousedown', this.delegateDown, this);
        el.on('mouseup', this.delegateUp, this);
        el.on('dblclick', this.delegateDblClick, this);
        el.on('contextmenu', this.delegateContextMenu, this);
    }
	,delegateOver:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(this.lastEcOver){
            this.onIconOut(e, this.lastEcOver);
            delete this.lastEcOver;
        }
        if(this.lastCbOver){
            this.onCheckboxOut(e, this.lastCbOver);
            delete this.lastCbOver;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            this.lastEcOver = this.getNode(e);
            this.onIconOver(e, this.lastEcOver);
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            this.lastCbOver = this.getNode(e);
            this.onCheckboxOver(e, this.lastCbOver);
        }
        if(t = this.getNodeTarget(e)){
            this.onNodeOver(e, this.getNode(e));
        }
    }
	,delegateOut:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            var n = this.getNode(e);
            this.onIconOut(e, n);
            if(n == this.lastEcOver){
                delete this.lastEcOver;
            }
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            var n = this.getNode(e);
            this.onCheckboxOut(e, n);
            if(n == this.lastCbOver){
                delete this.lastCbOver;
            }
        }
        if((t = this.getNodeTarget(e)) && !e.within(t, true)){
            this.onNodeOut(e, this.getNode(e));
        }
    }
	,delegateDown:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-checkbox', 1)){
            this.onCheckboxDown(e, this.getNode(e));
        }
    }
	,delegateUp:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-checkbox', 1)){
            this.onCheckboxUp(e, this.getNode(e));
        }
    }
	,delegateOut:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            var n = this.getNode(e);
            this.onIconOut(e, n);
            if(n == this.lastEcOver){
                delete this.lastEcOver;
            }
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            var n = this.getNode(e);
            this.onCheckboxOut(e, n);
            if(n == this.lastCbOver){
                delete this.lastCbOver;
            }
        }
        if((t = this.getNodeTarget(e)) && !e.within(t, true)){
            this.onNodeOut(e, this.getNode(e));
        }
    }
	,delegateClick:function(e, t){
		if(!this.beforeEvent(e)){
			return;
		}
		if(e.getTarget('.x-tree-checkbox', 1)){
			this.onCheckboxClick(e, this.getNode(e));
		}
		else if(e.getTarget('.x-tree-ec-icon', 1)){
			this.onIconClick(e, this.getNode(e));
		}
		else if(this.getNodeTarget(e)){
			this.onNodeClick(e, this.getNode(e));
		}
	}
	,onCheckboxClick:function(e, node){
		node.ui.onCheckboxClick();
	}
	,onCheckboxOver:function(e, node){
		node.ui.onCheckboxOver();
	}
	,onCheckboxOut:function(e, node){
		node.ui.onCheckboxOut();
	}
	,onCheckboxDown:function(e, node){
		node.ui.onCheckboxDown();
	}
	,onCheckboxUp:function(e, node){
		node.ui.onCheckboxUp();
	}
}); // eo extend
// }}}

// eof
