/** * A Picker field that contains a tree panel on its popup, enabling selection of tree nodes. */ Ext.define('Ips.view.strategy.TermTreePicker', { extend: 'Ext.form.field.Picker', xtype: 'multitreepicker', requiers:['Ext.data.TreeStore'], uses: [ 'Ext.tree.Panel' ], triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger', records:new Array(), values:new Array(), config: { rootVisible:false, /** * @cfg {Ext.data.TreeStore} store * A tree store that the tree picker will be bound to */ store: null, /** * @cfg {String} displayField * The field inside the model that will be used as the node's text. * Defaults to the default value of {@link Ext.tree.Panel}'s `displayField` configuration. */ displayField: 'text', /** * @cfg {Array} columns * An optional array of columns for multi-column trees */ columns: null, /** * @cfg {Boolean} selectOnTab * Whether the Tab key should select the currently highlighted item. Defaults to `true`. */ selectOnTab: true, /** * @cfg {Number} maxPickerHeight * The maximum height of the tree dropdown. Defaults to 300. */ maxPickerHeight: 300, /** * @cfg {Number} minPickerHeight * The minimum height of the tree dropdown. Defaults to 100. */ minPickerHeight: 100, }, editable: false, initComponent: function() { var me = this; me.callParent(arguments); me.addEvents( /** * @event select * Fires when a tree node is selected * @param {Ext.ux.TreePicker} picker This tree picker * @param {Ext.data.Model} record The selected record */ 'select' ); me.mon(me.store, { scope: me, load: me.onLoad, update: me.onUpdate }); }, /** * Creates and returns the tree panel to be used as this field's picker. */ createPicker: function() { var me = this, picker = new Ext.tree.Panel({ rootVisible: me.rootVisible, useArrows:true, shrinkWrapDock: 2, store: me.store, floating: true, displayField: me.displayField, columns: me.columns, minHeight: me.minPickerHeight, maxHeight: me.maxPickerHeight, manageHeight: false, shadow: false, listeners: { scope: me, itemclick: me.onItemClick, //checkchange: me.onCheckChange, //itemexpand: me.onitemExpand, afteritemexpand: me.AfterItemExpand }, viewConfig: { listeners: { scope: me, render: me.onViewRender }, } }), view = picker.getView(); //console.log(me.store); if (Ext.isIE9 && Ext.isStrict) { // In IE9 strict mode, the tree view grows by the height of the horizontal scroll bar when the items are highlighted or unhighlighted. // Also when items are collapsed or expanded the height of the view is off. Forcing a repaint fixes the problem. view.on({ scope: me, highlightitem: me.repaintPickerView, unhighlightitem: me.repaintPickerView, afteritemexpand: me.repaintPickerView, afteritemcollapse: me.repaintPickerView }); } return picker; }, onViewRender: function(view){ view.getEl().on('keypress', this.onPickerKeypress, this); }, /** * repaints the tree view */ repaintPickerView: function() { var style = this.picker.getView().getEl().dom.style; // can't use Element.repaint because it contains a setTimeout, which results in a flicker effect style.display = style.display; }, /** * Aligns the picker to the input element */ alignPicker: function() { var me = this, picker; if (me.isExpanded) { picker = me.getPicker(); if (me.matchFieldWidth) { // Auto the height (it will be constrained by max height) picker.setWidth(me.bodyEl.getWidth()); } if (picker.isFloating()) { me.doAlign(); } } }, /** * Handles a click even on a tree node * @private * @param {Ext.tree.View} view * @param {Ext.data.Model} record * @param {HTMLElement} node * @param {Number} rowIndex * @param {Ext.EventObject} e */ onItemClick: function(view, record, node, rowIndex, e) { var checked = record.data.checked? false: true; this.onCheckChange(record, checked); //this.selectItem(record); }, /** * Handles a checkchange even on a tree node * @private * @param {Ext.data.NodeInterface} node * @param {Boolean} checked * have a question: why NodeInterface looks just like the Model? */ onCheckChange: function(node, checked){//only one level's checked is changed var me = this; node.set('checked', checked); if(checked){//change from unchecked to checked node.cascadeBy(function(cNode){/*ok~*/ me.removeChildItems(cNode); if(cNode.hasChildNodes()){ cNode.eachChild(function(childNode){ childNode.set('checked',true); }); } }); me.selectItem(node);//this function can refresh the value shown in textfield// node.bubble(function(pNode){//pNode begins from the current Node/*ok~*/ /*so i directly find it's parent and check if the parent's children *is all checked(true), if it is then set the parentNode's checked *'true*/ if(pNode.parentNode != null){ var checkParent = true; pNode.parentNode.eachChild(function (subling){ if(!subling.data.checked){checkParent = false;} }); if(checkParent){ pNode.parentNode.set('checked',true); me.removeChildItems(pNode.parentNode); me.selectItem(pNode.parentNode); } } }); }else{//change from checked to unchecked node.bubble(function(pNode){ if(pNode.parentNode != null){ me.addChildItems(pNode.parentNode);//the current node is also added } me.removeItem(pNode);//the current node is also removed pNode.set('checked',false); }); node.cascadeBy(function(cNode){ cNode.set('checked',false); }); } }, onitemExpand: function(node, opts){ var me = this, picker = me.picker; //picker.expandPath(node.getPath()); }, AfterItemExpand: function(node, index, item, opts){ //alert('after expand this node'); }, /** * Handles a keypress event on the picker element * @private * @param {Ext.EventObject} e * @param {HTMLElement} el */ onPickerKeypress: function(e, el) { var key = e.getKey(); if(key === e.ENTER || (key === e.TAB && this.selectOnTab)) { this.selectItem(this.picker.getSelectionModel().getSelection()[0]); } }, /** * Changes the selection to a given record and closes the picker * @private * @param {Ext.data.Model} record */ selectItem: function(record) { var me = this; //records.push(record.raw.text); //Ext.Array.each(records, function(rec){ // names.push(rec.raw.text); //}); me.setValue(record.getId()); //me.picker.hide(); me.inputEl.focus(); me.fireEvent('select', me, record) }, addChildItems: function(pNode){ var me = this; if(pNode.data.checked && pNode.hasChildNodes()){//declude the selected Node pNode.eachChild(function (node){ if(node.data.checked){ me.selectItem(node); } }); } }, removeItem: function(node){ var me = this; //if(node != null){/*the case that node is null is not exist in current situation*/ var tmpRecord = (node? node.get(me.displayField) : ''); var tmpIndex = me.records.indexOf(tmpRecord); if( tmpRecord != '' && tmpIndex != -1){ me.records.splice(tmpIndex,1); } var tmpValue = node?node.get('id'):'';//this.value.toString(); var tmpValueIndex = me.values.indexOf(tmpValue); if(tmpValue != '' && tmpValueIndex != -1){ me.values.splice(tmpValueIndex,1); } //} me.setRawValue(me.records ? me.records.toString():''); }, removeChildItems: function(record){ var me = this; if(record.hasChildNodes()){ record.eachChild(function (node){ if(node.data.checked){ me.removeItem(node); } }); } }, /** * Runs when the picker is expanded. Selects the appropriate tree node based on the value of the input element, * and focuses the picker so that keyboard navigation will work. * @private */ onExpand: function() { console.log('expand the picker'); var me = this, picker = me.picker, store = picker.store, value = me.value, node; if (value) { node = store.getNodeById(value); } if (!node) { node = store.getRootNode(); } picker.selectPath(node.getPath()); Ext.defer(function() { picker.getView().focus(); }, 1); }, /** * Sets the specified value into the field * @param {Mixed} value * @return {Ext.ux.TreePicker} this */ setValue: function(value) { var me = this, record; me.value = value; if (me.store.loading) {//Ensure it is processed by the onLoad method. return me; } // try to find a record in the store that matches the value record = value ? me.store.getNodeById(value) : me.store.getRootNode(); if (value === undefined) { record = me.store.getRootNode(); me.value = record.getId(); } else { record = me.store.getNodeById(value); // set the raw value to the record's display field if a record was found var tmpRecord = (record? record.get(me.displayField) : ''); var tmpIndex = me.records.indexOf(tmpRecord); if( tmpRecord != '' && tmpIndex == -1){ me.records.push(tmpRecord); } var tmpValue = record?record.get('id'):'';//this.value.toString(); var tmpValueIndex = me.values.indexOf(tmpValue); if(tmpValue!= '' && tmpValueIndex == -1){ me.values.push(tmpValue); } //console.log('length:' + me.records.length); me.setRawValue(me.records ? me.records.toString():''); } return me; }, getSubmitValue: function(){ return this.value; }, /** * Returns the current data value of the field (the idProperty of the record) * @return {Number} */ getValue: function() { return (this.values ? this.values.toString():''); }, /** * Handles the store's load event. * @private */ onLoad: function() { var value = this.value; if (value) { this.setValue(value); } }, onUpdate: function(store, rec, type, modifiedFieldNames){ var display = this.displayField; if (type === 'edit' && modifiedFieldNames && Ext.Array.contains(modifiedFieldNames, display) && this.value === rec.getId()) { this.setRawValue(rec.get(display)); } } });