PDA

View Full Version : TreePicker + IconCombo



expensior
11 Oct 2012, 5:49 AM
Hello,

I am having an issue with the Picker. I extended it to show a treepanel instead of a bound list. On other pages I am using an IconCombo extension that shows a flat hierarchy of the tree with the node icons leading the text.

I would like the input field to look identical but I can't find how to render the value of the picker to include the iconCls of the currently selected node.

This is what I am currently using. I have 3 different types of nodes - system - locations and divisions and they each have a unique iconCls that I would like to see displayed in the input text field before the text.

Modifying fieldSubTpl has gotten me close to what I wanted to accomplish but not quite. What I need would be a renderer on the value to render a div inside or something like that.

Another issue that I fixed only with a dirty workaround yet is that when I set the value to the selected node itself the input field obviously displays "[Object object]". Not sure why the picker does not support displayField like a combobox or the documented Ext.ux.TreePicker in 4.1.X.. That is why I always set the value to node.text and put a reference to the actually node object in "selectedNode" before using setValue for the displayed text.



Ext.define("Ext.ux.TreeCombo", {
extend : "Ext.form.field.Picker",
alias: 'widget.treecombo',
initComponent : function() {
var self = this;
Ext.apply(self, {
fieldLabel : self.fieldLabel,
labelWidth : self.labelWidth
// pickerAlign : "tl"
});
self.addEvents('groupSelected');
self.callParent();
},
editable:false,
selectedNode: {},
createPicker : function() {
var self = this;
self.picker = new Ext.tree.Panel({
maxHeight : 300,
autoScroll : true,
floating : true,
resizable: false,
focusOnToFront : false,
shadow : true,
ownerCt : this.ownerCt,
useArrows : true,
store : self.store,
root: self.root,
rootVisible : true,
animCollapse : false,
animate : false,
listeners:{
scope:this,
select:this.valueSelected
}
});
return self.picker;
},
alignPicker : function() {
// override the original method because otherwise the height of the treepanel would be always 0
var me = this, picker, isAbove, aboveSfx = '-above';
if (this.isExpanded) {
picker = me.getPicker();
if (me.matchFieldWidth) {
// Auto the height (it will be constrained by min and max width) unless there are no records to display.
if (me.bodyEl.getWidth() > this.treeWidth){
picker.setWidth(me.bodyEl.getWidth());
} else picker.setWidth(this.treeWidth);
}
if (picker.isFloating()) {
picker.alignTo(me.inputEl, "", me.pickerOffset);// ""->tl
// add the {openCls}-above class if the picker was aligned above the field due to hitting the bottom of the viewport
isAbove = picker.el.getY() < me.inputEl.getY();
me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls
+ aboveSfx);
picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls
+ aboveSfx);
}
}
},
valueSelected: function(picker,value,options) {
this.selectedNode=value.data;
this.setValue(value.data.text);
this.fireEvent('valueSelected',this,value.data.id);
Ext.Function.defer(function(){
this.collapse();
}, 0, this);
}
});

var treeStore = Ext.create('Ext.data.TreeStore', {
proxy : {
type : 'ajax',
// url: '/ems/service/loadHierarchy.json',
url : context.contextPath + '/service/LoadHierarchy'
},
folderSort : true,
root: {
expanded: true,
iconCls:'showAllIcon',
text: messages.get('ems.common.treecombo.all'),
id:'ems_hierarchy_root'
},
sorters : [ {
property : 'text',
direction : 'ASC'
} ]
});

treeCombo = Ext.create('Ext.ux.TreeCombo',{
width: 260,
treeWidth: 260,
store : treeStore,
listeners: {
change: {
fn: function(field,newVal,oldVal,eOpts){
var id=field.selectedNode.id;
if(id==='ems_hierarchy_root'){
getConfItemBlocks();
}else{
getConfItemBlocksById(field.selectedNode.id);
}
}
}
}
});

treeStore.load( { callback: function(){
treeCombo.suspendEvents();
treeCombo.setValue(treeStore.getRootNode().text);
treeCombo.selectedNode=treeStore.getRootNode();
treeCombo.resumeEvents();
}});

expensior
18 Oct 2012, 6:42 AM
I accomplished this by modifying the TreePicker extension a little. I only have a minor issue with long node texts. For some reason they seem to get cut off by the icon and ignore the padding that short texts display correctly. However when I target the parent div in firebug it renders correctly, seems like magic to me.


I made it so that the textField displays the iconCls of the selected node left of the text value and I modified the picker alignment so that the TreePicker would be aligned to the start of the label and span the width of the label+field.


/**
* A Picker field that contains a tree panel on its popup, enabling selection of tree nodes.
*/
Ext.define('Ext.ux.TreePicker', {
extend: 'Ext.form.field.Picker',
xtype: 'treepicker',

triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',

config: {
/**
* @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: null,

/**
* @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.store.on('load', me.onLoad, me);
},

/**
* Creates and returns the tree panel to be used as this field's picker.
*/
createPicker: function() {
var me = this,
picker = Ext.create('Ext.tree.Panel', {
store: me.store,
floating: true,
hidden: true,
displayField: me.displayField,
columns: me.columns,
maxHeight: me.maxTreeHeight,
shadow: false,
manageHeight: false,
rootVisible: false,
listeners: {
itemclick: Ext.bind(me.onItemClick, me)
},
viewConfig: {
listeners: {
render: function(view) {
view.getEl().on('keypress', me.onPickerKeypress, me);
}
}
}
}),
view = picker.getView();

view.on('render', me.setPickerViewStyles, me);

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;
},

/**
* Sets min/max height styles on the tree picker's view element after it is rendered.
* @param {Ext.tree.View} view
* @private
*/
setPickerViewStyles: function(view) {
view.getEl().setStyle({
'min-height': this.minPickerHeight + 'px',
'max-height': this.maxPickerHeight + 'px'
});
},

/**
* 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() {
// override the original method because otherwise the height of the treepanel would be always 0
var me = this, picker, isAbove, aboveSfx = '-above';
if (this.isExpanded) {
picker = me.getPicker();
if (me.matchFieldWidth) {

picker.setWidth(me.labelEl.getWidth()+me.bodyEl.getWidth());
}
if (picker.isFloating()) {
picker.alignTo(me.inputEl, "", [(me.labelEl.getWidth()*-1),0]);// ""->tl
// add the {openCls}-above class if the picker was aligned above the field due to hitting the bottom of the viewport
isAbove = picker.el.getY() < me.inputEl.getY();
me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls
+ aboveSfx);
picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls
+ aboveSfx);
}
}
},
fieldSubTpl : [ '<div class="{hiddenDataCls}" role="presentation"></div>',
'<div class="ux-icon-combo-wrap"><input id="{id}" type="{type}" {inputAttrTpl}', '<tpl if="value"> value="{value}"</tpl>',
'<tpl if="name"> name="{name}"</tpl>', '<tpl if="placeholder"> placeholder="{placeholder}"</tpl>',
'<tpl if="size"> size="{size}"</tpl>', '<tpl if="maxLength !== undefined"> maxlength="{maxLength}"</tpl>',
'<tpl if="readOnly"> readonly="readonly"</tpl>', '<tpl if="disabled"> disabled="disabled"</tpl>',
'<tpl if="tabIdx"> tabIndex="{tabIdx}"</tpl>', '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
'class="{fieldCls} {typeCls}" autocomplete="off" /></div>', {
compiled : true,
disableFormats : true
} ],
onRender : function(ct, position) {
// call parent onRender
this.callParent(arguments);

// adjust styles
this.el.down('div[class=ux-icon-combo-wrap]').applyStyles({
position : 'relative'
});
this.el.down('input').addCls('ux-icon-combo-input');

// add div for icon
this.icon = Ext.core.DomHelper.append(this.el.down('div[class=ux-icon-combo-wrap]'), {
tag : 'div',
style : 'position:absolute'
});
}, // end of function onRender

setIconCls : function(value) {
var rec;
if (this.rendered) {
if(value !== '' && value !== null && value !== undefined){
rec = this.store.getNodeById(value);
}
if (rec)
this.icon.className = 'ux-icon-combo-icon ' + rec.get('iconCls');
} else {
this.on('render', this.setIconCls, this, {
single : true
});
}
}, // end of function setIconCls


/**
* 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) {
this.selectItem(record);
},

/**
* 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;
me.setValue(record.getId());
me.picker.hide();
me.inputEl.focus();
me.fireEvent('select', me, record)

},

/**
* 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() {
var me = this,
picker = me.picker,
store = picker.store,
value = me.value;

if(value) {
picker.selectPath(store.getNodeById(value).getPath());
} else {
picker.getSelectionModel().select(store.getRootNode());
}

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.setIconCls(value);
me.value = value;

if (me.store.loading) {
// Called while the Store is 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
me.setRawValue(record ? record.get(me.displayField) : '');

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.value;
},

/**
* Handles the store's load event.
* @private
*/
onLoad: function() {
var value = this.value;

if (value) {
this.setValue(value);
}
}

});