/*
This file is part of Ext JS 3.4

Copyright (c) 2011-2013 Sencha Inc

Contact:  http://www.sencha.com/contact

GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as
published by the Free Software Foundation and appearing in the file LICENSE included in the
packaging of this file.

Please review the following information to ensure the GNU General Public License version 3.0
requirements will be met: http://www.gnu.org/copyleft/gpl.html.

If you are unsure which license is appropriate for your use, please contact the sales department
at http://www.sencha.com/contact.

Build date: 2013-04-03 15:07:25
*/
Ext.ns('Ext.ux');

/**
 * Ext.ux.MultiCombo
 */
Ext.ux.MultiCombo = Ext.extend(Ext.form.ComboBox, {

	/**
	 * @cfg {String} overClass [x-grid3-row-over]
	 */
	overClass : 'x-grid3-row-over',
	/**
	 * @cfg {Boolean} enableKeyEvents for typeAhead
	 */
	enableKeyEvents: true,
	/**
	 * @cfg {String} selectedClass [x-grid3-row-selected]
	 */
	selectedClass: 'x-grid3-row-selected',
	/**
	 * @cfg {String} highlightClass The css class applied to rows which are hovered with mouse
	 * selected via key-nav, or highlighted when a text-query matches a single item.
	 */
	highlightClass: 'x-grid3-row-over',
	/**
	 * @cfg {Number} autoSelectKey [44] COMMA Sets the key used to auto-select an auto-suggest
	 * highlighted query.  When pressed, the highlighted text-item will be selected as if the user
	 * selected the row with a mouse click.
	 */
	autoSelectKey : 44,
	/**
	 * @cfg {String} allSelectedText Text to display when all items are selected
	 */
	allSelectedText : 'All selected',
	/**
	 * @cfg {Number} maxDisplayRows The maximum number of rows to show before applying vscroll
	 */
	maxDisplayRows: null,

	mode: 'local',
	triggerAction: 'all',
	typeAhead: true,

	// private
	highlightIndex : null,
	highlightIndexPrev : null,

	query : null,


	/**
	 * @cfg {Array} value CheckboxCombo expresses its value as an array.
	 */
	value: [],

	/**
	 * @cfg {Integer} minChars [0]
	 */
	minChars: 0,

	initComponent : function() {
		var cls = 'x-combo-list';

		// when blurring out of field, ensure that rawValue contains ONLY items contained in Store.
		this.on('blur', this.validateSelections.createDelegate(this));

		// create an auto-select key handler, like *nix-based console [tab] key behaviour
		this.on('keypress', function(field, ev) {
			if (ev.getKey() == this.autoSelectKey) {	// COMMA
				this.onAutoSelect();
			}
		},this);

		this.addEvents(
			/**
			 * @event initview Fires when Combo#initView is called.
			 * gives plugins a chance to interact with DataView
			 * @author Chris Scott
			 * @param {Combo} this
			 * @param {DataView} dv
			 */
			'initview',
            'clearall'
		);

		// when list expands, constrain the height with @cfg maxDisplayRows
		if (this.maxDisplayRows) {
			this.on('expand', function(){
				var cnt = this.store.getCount();
				if (cnt > this.maxDisplayRows) {
					var children = this.view.getNodes();
					var h = 0;
					for (var n = 0; n < this.maxDisplayRows; n++) {
						h += Ext.fly(children[n]).getHeight();
					}
					this.maxHeight = h;
				}
			}, this, {
				single: true
			});
		}

		this.on('beforequery', this.onQuery, this);

		// Enforce that plugins is an Array.
		if (typeof(this.plugins) == 'undefined'){
			this.plugins = [];
		}
		else if (!Ext.isArray(this.plugins)) {
			this.plugins = [this.plugins];
		}

		var tmp = this.value;	// for case where transform is set.
		Ext.ux.MultiCombo.superclass.initComponent.call(this);
		if (this.transform) {
			if (typeof(tmp) == 'undefined') {
				tmp = [];
			}
			this.setValue(tmp);
		}
	},

	// private
    onViewClick : function(dv, index, node, ev){
		var rec = this.store.getAt(index);
		this.onSelect(rec, index);
		this.el.focus();
		/*
        if(doFocus !== false){
            this.el.focus();
        }
        */
    },

	// onTriggerClick, overrides Ext.form.ComboBox#onTriggerClick
	onTriggerClick: function() {
		if (this.highlightIndex != -1) {
			this.clearHighlight();
		}
		this.highlightIndex = -1;

		if(this.disabled){
            return;
        }
		if(this.isExpanded()){
            this.collapse();
            this.el.focus();
        }else {
            this.onFocus({});
			if(this.triggerAction == 'all') {
				this.doQuery(this.getRawValue(), true);
				var vlen = this.getValue().length, slen = this.view.getSelectedRecords().length;
				if (vlen != slen || vlen == 0) {
					this.selectByValue(this.value, true);
				}
            } else {
                this.expand();
				this.doQuery(this.getRawValue());
            }

			this.highlightIndex = -1
			this.highlightIndexPrev = null;
			this.selectNext();
			this.scrollIntoView();
            this.el.focus();
        }
	},

	// onQuery, beforequery listener, @return false
	onQuery : function(qe) {
		q = qe.query;
        forceAll = qe.forceAll;
        if(forceAll === true || (q.length >= this.minChars)){
            if(this.lastQuery !== q){
				if (typeof(this.lastQuery) != 'undefined') {
					if (q.match(new RegExp('^'+this.allSelectedText))) {
						this.query = this.store.data;
					}
					else if (this.lastQuery.length > q.length) {
						var items = q.replace(/\s+/g, '').split(',');
						if (items[items.length-1].length == 0) {
							items.pop();
						}
						this.query = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp('^'+items.join('$|^')+'$', "i"), false, false));
					}
					else {
						this.query = null;
					}
				}
                this.lastQuery = q;
                if(this.mode == 'local'){
					var raw = this.getRawValue();
					if (raw == this.allSelectedText) {

					}
					var items = raw.replace(/\s+/g, '').split(',');
					var last = items.pop();
					this.matches = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp('^'+last, "i"), false, false)).filterBy(this.createTypeAheadFilterFn(items));
					if (this.matches.getCount() == 0) {
						this.clearHighlight();
					}
					if (q.length == 0) {
						this.view.clearSelections();
						this.updateValue([]);
					}

                    this.onLoad();
                } else {
                    this.store.baseParams[this.queryParam] = q;
                    this.store.load({
                        params: this.getParams(q)
                    });
                    this.expand();
                }
            }else{
                this.selectedIndex = -1;
                this.onLoad();
            }
        }

		return false;
	},

	// onLoad, overrides Ext.form.ComboBox#onLoad
	onLoad : function(){

        if(!this.hasFocus){
            return;
        }
        if(this.store.getCount() > 0){
            if (!this.isExpanded()) {
				this.expand();
				this.restrictHeight();
			}
            if(this.lastQuery == this.allQuery){
                if(this.editable){
                    this.el.dom.select();
                }
            }else{
				if (this.query != null) {
					var values = [], indexes = [];
					this.query.each(function(r){
						values.push(r.data[this.valueField]);
						indexes.push(this.store.indexOf(r));
					}, this);
					this.view.clearSelections();
					this.updateValue(values, this.getRawValue());
					this.view.select(indexes);
				}
				if (this.matches != null) {
					if (this.matches.getCount() == 1) {
						this.highlight(this.store.indexOf(this.matches.first()));
						this.scrollIntoView();
					}
				}
				else {
					// @HACK: If store was configured with a proxy, set its mode to local now that its populated with data.
					// Re-execute the query now.
					this.mode = 'local';
					this.lastQuery = undefined;
					this.doQuery(this.getRawValue(), true);
				}
                if(this.typeAhead && this.lastKey != Ext.EventObject.DOWN && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
					this.taTask.delay(this.typeAheadDelay);
                }
            }
        }else{
            this.onEmptyResults();
        }
    },

	onSelect : function(record, index) {
		if (index == -1) {
			throw new Error('MultiCombo#onSelect did not receive a valid index');
		}

		// select only when user clicks [apply] button
		if (this.selectOnApply == true) {
			return;
		}

		if (this.fireEvent('beforeselect', this, record, index) !== false) {
			var text = [];
			var value = [];
			var rs = this.view.getSelectedRecords();
			for (var n = 0, len = rs.length; n < len; n++) {
				text.push(rs[n].data[this.displayField]);
				value.push(rs[n].data[this.valueField]);
			}
			this.updateValue(value, (value.length != this.store.getCount()) ? text.join(', ') : this.allSelectedText);
			var node = this.view.getNode(index);
			this.innerList.scrollChildIntoView(node, false);
			this.fireEvent('select', this, record, index);
		}
	},

	// private
    onViewOver : function(ev, node){
		var t = ev.getTarget(this.view.itemSelector);
		if (t == null) {
			return;
		}
		this.highlightIndex = this.store.indexOf(this.view.getRecord(t));
		this.clearHighlight();
		this.highlight(this.highlightIndex);
        if(this.inKeyMode){ // prevent key nav and mouse over conflicts
            return;null
        }
        return;
    },

	// private
    onTypeAhead : function(){
		if(this.store.getCount() > 0){
			this.inKeyMode = false;
            var raw = this.getRawValue();
			var pos = this.getCaretPosition(raw);
			var items = [];
			var query = '';
			if (pos !== false && pos < raw.length) {
				items = raw.substr(0, pos).replace(/\s+/g, '').split(',');
				query = items.pop();
			} else {
				items = raw.replace(/\s+/g, '').split(',');
				query = items.pop();
			}
			var rs = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp(query, "i"), false, false)).filterBy(this.createTypeAheadFilterFn(items));

			if (rs.getCount() == 1) {
				var r = rs.first();
				var rindex = this.store.indexOf(r)
				if (!this.view.isSelected(rindex)) {
		            this.typeAheadSelected = true;
					var selStart = raw.length;
					var len = items.join(',').length;
					var selEnd = null;
					var newValue = r.data[this.displayField];
					if (pos !== false && pos < raw.length) {
						var insertIdx = items.length;
						var selStart = pos;
						items = raw.replace(/\s+/g, '').split(',');
						items.splice(insertIdx, 1, newValue);
						selEnd = items.slice(0, insertIdx+1).join(', ').length;
						this.highlight(rindex);
						this.scrollIntoView();

					}
					else {
						items.push(newValue);
					}
					var len = items.join(',').length;
		            if(selStart != len){
						var lastWord = raw.split(',').pop();
						if (items.length >1 && lastWord.match(/^\s+/) == null) {
							selStart++;
						}
						this.setRawValue(items.join(', '));
		                this.selectText(selStart, (selEnd!=null) ? selEnd : this.getRawValue().length);
		            }
				}
			}
        }
    },

	apply : function() {
		var selected = 	this.view.getSelectedRecords();
		var value = [];
		for (var n=0,len=selected.length;n<len;n++) {
			value.push(selected[n].data[this.valueField]);
		}
		this.setValue(value);
	},

	getCaretPosition : function(raw) {
		raw = raw || this.getRawValue();
		if(document.selection) {	// <-- IE, ugh:  http://parentnode.org/javascript/working-with-the-cursor-position/
        	var range = document.selection.createRange();
			//Save the current value. We will need this value later to find out, where the text has been changed
			var orig = obj.value.replace(/rn/g, "n");
			// replace the text
			range.text = text;
			// Now get the new content and save it into a temporary variable
			var actual = tmp = obj.value.replace(/rn/g, "n");
			/* Find the first occurance, where the original differs
			   from the actual content. This could be the startposition
			   of our text selection, but it has not to be. Think of the
			   selection "ab" and replacing it with "ac". The first
			   difference would be the "c", while the start position
			   is the "a"
			*/
			for(var diff = 0; diff < orig.length; diff++) {
			    if(orig.charAt(diff) != actual.charAt(diff)) break;
			}

			/* To get the real start position, we iterate through
			   the string searching for the whole replacement
			   text - "abc", as long as the first difference is not
			   reached. If you do not understand that logic - no
			   blame to you, just copy & paste it ;)
			*/
			for(var index = 0, start = 0; tmp.match(text) && (tmp = tmp.replace(text, "")) && index <= diff; index = start + text.length) {
			    start = actual.indexOf(text, index);
			}
	    } else if(this.el.dom.selectionStart) {	// <-- Go the Gecko way
			return this.el.dom.selectionStart;
	    } else {
	        // Fallback for any other browser
			return false;
	    }
	},

	onAutoSelect : function() {
		if (!this.isExpanded()) {
			var vlen = this.getValue().length, slen = this.view.getSelectedRecords().length;
			if (vlen != slen || vlen == 0) {
				this.selectByValue(this.value, true);
			}
		}
		var raw = this.getRawValue();
		this.selectText(raw.length, raw.length);

		var pos = this.getCaretPosition(raw);
		var word = '';
		if (pos !== false && pos < raw.length) {
			word = Ext.util.Format.trim(raw.substr(0, pos).split(',').pop());
		} else {
			word = Ext.util.Format.trim(raw.split(',').pop());
		}
		var idx = this.store.find(this.displayField, word);
		if (idx > -1 && !this.view.isSelected(idx)) {
			var rec = this.store.getAt(idx);
			this.select(idx);
		}
	},
	// filters-out already-selected items from type-ahead queries.
	// e.g.: if store contains: "betty, barney, bart" and betty is already selected,
	// when user types "b", only "bart" and "barney" should be returned as possible matches,
	// since betty is *already* selected
	createTypeAheadFilterFn : function(items) {
		var key = this.displayField;
		return function(rec) {
			var re = new RegExp(rec.data[key], "i");
			var add = true;
			for (var n=0,len=items.length;n<len;n++) {
				if (re.test(items[n])) {
					add = false;
					break;
				}
			}
			return add;
		}
	},

	updateValue : function(value, text) {
		this.value = value;
		if(this.hiddenField){
			this.hiddenField.value = value.join(',');
		}
		if (typeof(text) == 'string') {
			this.setRawValue(text);
		}

	},

	/**
	 * setValue
	 * Accepts a comma-separated list of ids or an array.  if given a string, will conver to Array.
	 * @param {Array, String} v
	 */
	setValue : function(v) {
		var text = [];
		var value = [];

		if (typeof(v) == 'string') {	// <-- "1,2,3"
			value = v.match(/\d+/g); // <-- strip multiple spaces and split on ","
            if(value){
			    for (var n=0,len=value.length;n<len;n++) {
				    value[n] = parseInt(value[n]);
			    }
            }
		}
		else if (Ext.isArray(v)) {			// <-- [1,2,3]
			value = v;
		}
		if (value && value.length) {
			if (this.mode == 'local') {
				this.updateValue(value);
				this.setRawValue(this.getTextValue());
			}
			else {
				this.updateValue(value);
				this.store.load({
					callback: function() {
						this.setRawValue(this.getTextValue());
					},
					scope: this
				});
				this.mode = 'local';
			}
		}
	},

	getTextValue : function() {
		if (this.value.length == this.store.getCount()) {
			return this.allSelectedText;
		}
		else {
			var text = [];
			this.store.data.filterBy(this.store.createFilterFn(this.valueField, new RegExp(this.value.join('|'), "i"), false, false)).each(function(r){
				text.push(r.data[this.displayField]);
			}, this);
			return text.join(', ');
		}
	},

	/**
     * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
     * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
     * @param {Number} index The zero-based index of the list item to select
     * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
     * selected item if it is not currently in view (defaults to true)
     */
    select : function(index, scrollIntoView){
		if (!typeof(index) == 'number') {
			throw new Error('MultiCombo#select expected @param {Number} index but got: ' + typeof(index));
		}
        this.view.isSelected(index) ? this.view.deselect(index, true) : this.view.select(index, true);
		this.onSelect(this.store.getAt(index), index);

		this.matches = null;
        if(scrollIntoView !== false){
            var el = this.view.getNode(index);
            if(el){
                this.innerList.scrollChildIntoView(el, false);
            }
        }

    },

	getLastValue : function() {
		return Ext.util.Format.trim(this.getRawValue().split(',').pop());
	},

	/**
     * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
     * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
     * @param {String} value The data value of the item to select
     * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
     * selected item if it is not currently in view (defaults to true)
     * @return {Boolean} True if the value matched an item in the list, else false
     */
    selectByValue : function(v, scrollIntoView){
		if (v.length) {
			var indexes = [];
			var rs = this.store.data.filterBy(this.store.createFilterFn(this.valueField, new RegExp(v.join('|'), "i"))).each(function(r){
				indexes.push(this.store.indexOf(r));
			}, this);
			if (indexes.length) {
				this.view.select(indexes);
				return true;
			}
		}
		else {
			this.view.clearSelections();
			this.setRawValue('');
			return false;
		}
    },

	// private
    initEvents : function(){
        Ext.form.ComboBox.superclass.initEvents.call(this);
        this.keyNav = new Ext.KeyNav(this.el, {
            "up" : function(e){
				this.lastKey = Ext.EventObject.UP;
                this.inKeyMode = true;
                this.selectPrev();
				this.scrollIntoView();
            },

            "down" : function(e){
                this.inKeyMode = true;
				if(!this.isExpanded()){
					this.lastKey = Ext.EventObject.DOWN;
                    this.onTriggerClick();
                }else{
                    this.selectNext();
					this.scrollIntoView();
                }

            },

            "enter" : function(e){
				var idx = this.highlightIndex;
				if (this.inKeyMode === true) {
					if (this.plugins.length && (idx <= -1)) {
						if (this.plugins[idx + 1]) {
							this.plugins[idx + 1].onEnter(this);
						}
					}
					else
						if (this.plugins.length && this.highlightIndex == 0 && this.highlightIndexPrev == -1) {
							if (this.plugins[idx]) {
								this.plugins[idx].onEnter(this);
							}
						}
						else {
							var idx = this.getHighlightedIndex() || 0;
							if (this.highlightIndex != null && idx != null) {
								this.select(idx, true);
								//this.delayedCheck = true;
								//this.unsetDelayCheck.defer(10, this);

							}
						}
				}
				else {
					var v = this.getLastValue();
					var raw = this.getRawValue();

					/** this block should be moved to method getCurrentWord
					 *
					 */
					var pos = this.getCaretPosition(raw);
					var word = '';
					if (pos !== false && pos < raw.length) {
						word = Ext.util.Format.trim(raw.substr(0, pos).split(',').pop());
					} else {
						word = Ext.util.Format.trim(raw.split(',').pop());
					}
					/*******************************************************/

					var idx = this.store.find(this.displayField, word);
					if (idx != -1) {
						var rec = this.store.getAt(idx);
						this.select(idx, true);
					}
					raw = this.getRawValue();
					this.selectText(raw.length, raw.length);
					this.collapse();
				}
            },

            "esc" : function(e){
                this.collapse();
            },

            "tab" : function(e){
				if (this.matches != null && this.matches.getCount() == 1) {
					var idx = this.store.indexOf(this.matches.first());
					if (!this.view.isSelected(idx)) {
						this.select(this.store.indexOf(this.matches.first()), true);
					}
				}
				else if (this.value.length == 0 && this.getRawValue().length > 0) {
					this.setRawValue('');
				}
				this.collapse();
                return true;
            },

            scope : this,

            doRelay : function(foo, bar, hname){
                if(hname == 'down' || this.scope.isExpanded()){
                   return Ext.KeyNav.prototype.doRelay.apply(this, arguments);
                }
                return true;
            },

            forceKeyDown : true
        });
        this.queryDelay = Math.max(this.queryDelay || 10,
                this.mode == 'local' ? 10 : 250);
        this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
        if(this.typeAhead){
            this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
        }
        if(this.editable !== false){
            this.el.on("keyup", this.onKeyUp, this);
        }
        if(this.forceSelection){
            this.on('blur', this.doForce, this);
        }
    },

	// private, blur-handler to ensure that rawValue contains only values from selections, in the same order as selected
	validateSelections : function(field) {
		var v = this.getValue();
		var text = [];
		for (var i=0,len=v.length;i<len;i++) {
			var idx = this.store.find(this.valueField, v[i]);
			if (idx >=0) {
				text.push(this.store.getAt(idx).data[this.displayField]);
			}
		}
		this.setRawValue(text.join(', '));
	},

	scrollIntoView : function() {
		var el = this.getHighlightedNode();
		if (el) {
			this.innerList.scrollChildIntoView(el);
		}
	},

	// private
    selectNext : function(){
		this.clearHighlight();
		if (this.highlightIndex == null) {
			this.highlightIndex = -1;
		}
		if (this.highlightIndex <= -1 && this.highlightIndexPrev != -1) {
			if (this.plugins.length > 0) {
				var idx = Math.abs(this.highlightIndex)-1;
				if (this.plugins.length >= Math.abs(this.highlightIndex)) {
					this.plugins[idx].selectNext(this);
					this.highlightIndexPrev = this.highlightIndex;
					this.highlightIndex++;
					return false;
				}
			}
		}
		if (this.highlightIndexPrev == -1 && this.highlightIndex == 0) {
			this.highlightIndex = -1;
		}
		var ct = this.store.getCount();
		if(ct > 0){
            if (this.highlightIndex == -1 || this.highlightIndex+1 < ct) {
				if (this.highlightIndex == -1) {
					this.highlightIndexPrev = 0;
				}
				else {
					this.highlightIndexPrev = this.highlightIndex -1;
				}
				this.highlight(++this.highlightIndex);

			}
			else {
				this.highlight(ct-1);
			}
        }
    },

    // private
    selectPrev : function(){
		this.clearHighlight();
		if (this.highlightIndex <= 0) {
			var idx = Math.abs(this.highlightIndex);
			if (this.plugins.length >= idx+1 && this.highlightIndexPrev >= 0) {
				this.clearHighlight();
				this.plugins[idx].selectPrev(this);
				this.highlightIndexPrev = this.highlightIndex;
				this.highlightIndex--;
				if (this.highlightIndex == -1) {
					this.highlightIndexPrev = -1;
				}
				return false;
			}
			else {
				this.highlightIndex = -1;
				this.highlightIndexPrev = -1;
				this.collapse();
				return;
			}
		}

		this.highlightIndexPrev = this.highlightIndex;
        var ct = this.store.getCount();
        if(ct > 0){
			if (this.highlighIndex == -1) {
				this.highlightIndex = 0;
			}
			else if (this.highlightIndex != 0) {
				this.highlightIndex--;
			}
			else if (this.highlightIndex == 0) {
				this.collapse();
			}
			this.highlight(this.highlightIndex);
        }
    },

	collapse : function() {
		if (this.isExpanded()) {
			this.highlightIndex = null;
			this.highlightIndexPrev = null;
		}
		Ext.ux.MultiCombo.superclass.collapse.call(this);
	},

	highlight : function(index) {
		this.view.el.select('.'+this.highlightClass).removeClass(this.highlightClass);
		var node = Ext.fly(this.view.getNode(index));
		if (node) {
			node.addClass(this.highlightClass);
		}
	},

	getHighlightedIndex : function() {
		var node = this.view.el.child('.' + this.highlightClass, true);
		return (node) ? this.store.indexOf(this.view.getRecord(node)) : this.highlightIndex;
	},
	getHighlightedNode : function() {
		return this.view.el.child('.'+this.highlightClass, true);
	},

	clearHighlight : function() {
		if (typeof(this.view) != 'object') { return false; }
		var el = this.view.el.select('.'+this.highlightClass);
		if (el) {
			el.removeClass(this.highlightClass);
		}
	},

    // private
    initList : function(){
        if(!this.list){
            var cls = 'x-combo-list';

            this.list = new Ext.Layer({
                shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
            });

            var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
            this.list.setWidth(lw);
            this.list.swallowEvent('mousewheel');
            this.assetHeight = 0;
            if(this.syncFont !== false){
                this.list.setStyle('font-size', this.el.getStyle('font-size'));
            }
            if(this.title){
                this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
                this.assetHeight += this.header.getHeight();
            }

            this.innerList = this.list.createChild({cls:cls+'-inner'});
            this.innerList.on('mouseover', this.onViewOver, this);
            this.innerList.on('mousemove', this.onViewMove, this);
            this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));

            if(this.pageSize){
                this.footer = this.list.createChild({cls:cls+'-ft'});
                this.pageTb = new Ext.PagingToolbar({
                    store:this.store,
                    pageSize: this.pageSize,
                    renderTo:this.footer
                });
                this.assetHeight += this.footer.getHeight();
            }

            if(!this.tpl){
                /**
                * @cfg {String/Ext.XTemplate} tpl The template string, or {@link Ext.XTemplate}
                * instance to use to display each item in the dropdown list. Use
                * this to create custom UI layouts for items in the list.
                * <p>
                * If you wish to preserve the default visual look of list items, add the CSS
                * class name <pre>x-combo-list-item</pre> to the template's container element.
                * <p>
                * <b>The template must contain one or more substitution parameters using field
                * names from the Combo's</b> {@link #store Store}. An example of a custom template
                * would be adding an <pre>ext:qtip</pre> attribute which might display other fields
                * from the Store.
                * <p>
                * The dropdown list is displayed in a DataView. See {@link Ext.DataView} for details.
                */
                this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
                /**
                 * @cfg {String} itemSelector
                 * <b>This setting is required if a custom XTemplate has been specified in {@link #tpl}
                 * which assigns a class other than <pre>'x-combo-list-item'</pre> to dropdown list items</b>.
                 * A simple CSS selector (e.g. div.some-class or span:first-child) that will be
                 * used to determine what nodes the DataView which handles the dropdown display will
                 * be working with.
                 */
            }

            /**
            * The {@link Ext.DataView DataView} used to display the ComboBox's options.
            * @type Ext.DataView
            */
            this.view = new Ext.DataView({
                applyTo: this.innerList,
                tpl: this.tpl,
				simpleSelect: true,
                multiSelect: true,
				overClass: this.overClass,
                selectedClass: this.selectedClass,
                itemSelector: this.itemSelector || '.' + cls + '-item'
            });
            this.view.on('click', this.onViewClick, this);
			this.fireEvent('initview', this, this.view);
            this.bindStore(this.store, true);

            if(this.resizable){
                this.resizer = new Ext.Resizable(this.list,  {
                   pinned:true, handles:'se'
                });
                this.resizer.on('resize', function(r, w, h){
                    this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
                    this.listWidth = w;
                    this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
                    this.restrictHeight();
                }, this);
                this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
            }
        }
    }
});


Ext.reg('multicombo', Ext.ux.MultiCombo);