Code:
/**
* Ext.sm.Form.ComboGrid - ExtJS Library for sYs-mini SDK
*
* For any licensing informations ask licensing@extjs.com or visit
* http://extjs.com/license
*
* @author Csaba Dobai (prometheus) <fejlesztes@php-sparcle.hu>
* @copyright All rights reserved by author, based on ExtJS 3.0 licensing!
*/
/**
* @namespace Ext.sm.Form
*/
Ext.namespace('Ext.sm.Form');
/**
* @component
* @class Ext.sm.Form.ComboGrid
* @extends Ext.form.ComboBox
* <p>This is a specialized combobox which uses grid component to dropdown
* instead of DataView. This one is to power-up the combobox usability for example
* with EditorGridPanel or multiple columns instead of template hacking, or with
* enabling column headers to use ordering, etc.</p>
* <code><pre>
var win = new Ext.Window({
title : 'Test',
items : [{
xtype : 'form',
autoHeight : true,
ctCls: 'testForm',
items : [{
xtype: 'combogrid',
store : [['1', 'One'], ['2', 'Two'], ['3', 'Three'], ['4', 'Four']],
columns: [
{id: 'value', hidden: true, sortable: false},
{
id: 'text',
dataIndex: 'text',
sortable: true,
tpl: '<tpl for="."><div class="x-combo-list-item">{text}</div></tpl>',
editor: new Ext.form.TextField({
allowBlank: false
})
}
],
name: 'cgtest',
hiddenName: 'cgtest',
fieldLabel: 'ComboGrid',
typeAhead : true,
forceSelection : true,
triggerAction : "all",
selectOnFocus :true,
mode: 'local',
selectEvent: 'dblclick',
pageSize: 4,
view: {
xtype: 'editorgrid',
clicksToEdit: 1
}
}],
buttons: [{
text: 'Save',
handler: function()
{
var fp = this.findParentByType('form');
if(fp.getForm().isValid()){
Ext.Msg.alert('Submitted Values', 'The following values could get by the server: <br />'+
fp.getForm().getValues(true));
}
}
}]
}],
listeners: {
afterrender: function(self)
{
self.find('ctCls', 'testForm')[0].load({
url: '/cstest.php'
});
}
}
});
win.show();
</pre></code>
*/
Ext.sm.Form.ComboGrid = Ext.extend(Ext.form.ComboBox, {
/**
* @cfg {Array} columns <p>An array of {@link Ext.grid.Column columns} to auto
* create a {@link Ext.grid.ColumnModel}. The ColumnModel may be explicitly
* created via the <tt>{@link Ext.grid.GridPanel#colModel}</tt> configuration
* property in {@link #view}.</p>
* <b>Note:</b> A default column specification generated by ComboGrid. you
* must be careful if you override this setting. In the view config, you`re
* able to override the view`s xtype - for example to 'editorgrid' - therefore
* you can specify editor controls in here for columns.
*/
/**
* @cfg {String} selectEvent <p>This specifies that what kind of event will
* cause item selection. Default is 'click'.</p>
* <b>Tipp:</b> If you override the {@link #view} (grid) xtype to 'editorgrid'
* change this setting to 'dblclick' then specify {@link Ext.grid.EditorGridPanel#clicksToEdit clicksToEdit}
* for view to 1. This is a workaround for usability conflicts.
*/
selectEvent: 'click',
/**
* @cfg {String/Ext.XTemplate} tpl
* <b>Note:</b> This setting mapped to the view by default to its
* {@link Ext.grid.TemplateColumn TemplateColumn} - so if you override the
* grid`s column settings, this option will unhandled!
* <p>The template string, or {@link Ext.XTemplate} instance to use to
* display each item in the dropdown list. The dropdown list is displayed in
* a DataView. See {@link #view}.</p>
* <p>The default template string is:</p><pre><code>
'<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>'
* </code></pre>
* <p>Override the default value to create custom UI layouts for items in
* the list. For example:</p><pre><code>
'<tpl for="."><div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}</div></tpl>'
* </code></pre>
* <p>The template <b>must</b> contain one or more substitution parameters
* using field names from the Combo's</b> {@link #store Store}. In the
* example above an <pre>ext:qtip</pre> attribute is added to display other
* fields from the Store.</p>
* <p>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>
* <p>Also see {@link #itemSelector} for additional details.</p>
*/
/**
* @cfg {String} valueTitle This option specifies the display title of
* grid`s column header. Header being hidden if this setting is empty. You
* can fully override this options working in the {@link #view} option.
* Default is empty.
*/
valueTitle: '',
/**
* @cfg {Component/Config} view This redefined config option now specifies
* a {@link Ext.grid.GridPanel Grid} component (or any GridPanel descendant)
* based on xtype.
*/
view: null,
/**
* @cfg {Function} defaultTpl
* Now you can specify a callback function to generate the {@link #tpl}
* option`s default value. No parameters passed but scope is 'this'. You must
* return a valid XTemplate string or an XTemplate definitoin string.
*/
defaultTpl: function()
{
return '<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>';
},
// private - overrided
initComponent: function()
{
this.lazyInit = false
this.inEditMode = false;
Ext.sm.Form.ComboGrid.superclass.initComponent.call(this);
},
// private - this will generate default config for view
defaultView: function()
{
var cols = [
{id: this.valueField, dataIndex: this.valueField, hidden: true, sortable: false},
{id: this.displayField, dataIndex: this.displayField, sortable: true, header: this.valueTitle, tpl: this.tpl}
];
return {
xtype: 'grid',
applyTo: this.innerList,
store: this.store,
cm: new Ext.grid.ColumnModel({
columns: (Ext.isEmpty(this.columns)? cols : this.columns),
defaults: {
hideable: false,
menuDisabled: true
},
xtype: 'templatecolumn'
}),
viewConfig: {
forceFit: true,
headersDisabled: Ext.isEmpty(this.valueTitle),
emptyText: this.listEmptyText,
scrollOffset: 0
},
sm: new Ext.grid.RowSelectionModel({moveEditorOnEnter: false}),
frame: false,
height: (this.minHeight + 5),
autoHeight: true
};
},
// private - overrided
bindStore : function(store, initial)
{
if (this.store && !initial)
{
this.store.un('beforeload', this.onBeforeLoad, this);
this.store.un('load', this.onLoad, this);
this.store.un('loadexception', this.collapse, this);
if (this.store !== store && this.store.autoDestroy)
{
this.store.destroy();
}
if (!store)
{
this.store = null;
}
}
if (store)
{
if (!initial)
{
this.lastQuery = null;
if (this.pageTb)
{
this.pageTb.bindStore(store);
}
}
this.store = Ext.StoreMgr.lookup(store);
this.store.on('beforeload', this.onBeforeLoad, this);
this.store.on('load', this.onLoad, this);
this.store.on('loadexception', this.collapse, this);
if (this.view)
{
this.view.view.refresh();
}
}
},
// private - overrided to create a grid instead of a DataView
initList: function()
{
var cls = 'x-combo-list';
if (!this.list)
{
// Begin of copy-paste
this.list = new Ext.Layer({
parentEl: this.getListParent(),
shadow: this.shadow,
cls: [cls, this.listClass].join(' '),
constrain:false
});
var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
this.list.setSize(lw, 0);
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.mon(this.innerList, 'mouseover', this.onViewOver, this);
this.mon(this.innerList, 'mouseout', this.onViewOut, this); // this one is mine :)
this.mon(this.innerList, '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();
}
// End of copy-paste
if (Ext.isEmpty(this.tpl)) this.tpl = this.defaultTpl.call(this);
if (Ext.isEmpty(this.view)) this.view = {};
if (!(this.view instanceof Ext.Component))
{
this.view = Ext.applyIf(this.view, this.defaultView());
this.view = Ext.ComponentMgr.create(this.view);
}
if (this.view instanceof Ext.grid.EditorGridPanel)
{
this.view.on('beforeedit', this.onViewBeforeEdit, this);
}
// Begin of copy-paste
this.mon(this.view, this.selectEvent, this.onViewClick, this);
this.view.on('keydown', this.onViewKeyDown, this); // this one is mine :)
this.bindStore(this.store, true);
if(this.resizable){
this.resizer = new Ext.Resizable(this.list, {
pinned:true, handles:'se'
});
this.mon(this.resizer, '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');
}
// End of copy-paste
}
},
// private - overrided
onLoad : function(){
if (this.view && this.view.view)
{
this.innerList.update('');
this.view.view.refresh();
this.view.body.appendTo(this.innerList);
}
Ext.sm.Form.ComboGrid.superclass.onLoad.call(this);
},
// private - overrided, updated
onViewOver : function(e, t)
{
if(this.inKeyMode || this.inEditMode) // prevent key nav and mouse over conflicts
{
return;
}
var v = this.view.getView();
var index = v.findRowIndex(t);
var row = v.findRow(t);
var r, rs;
var cls = 'x-grid3-cell-selected';
if (index !== false)
{
rs = this.view.getEl().query('.'+cls);
Ext.each(rs, function(i){Ext.get(i).removeClass(cls);});
r = Ext.get(row);
if (!r.hasClass(cls)) {
r.addClass(cls);
this.selectedIndex = index;
}
}
},
// private - new handler
onViewOut: function()
{
if (!this.inEditMode)
{
var v = this.view.getView();
var cls = 'x-grid3-cell-selected';
var rs;
rs = this.view.getEl().query('.'+cls);
Ext.each(rs, function(i){Ext.get(i).removeClass(cls);});
this.select(this.selectedIndex, false, this.hasFocus);
}
},
// private
onViewClick : function(doFocus)
{
var r = this.store.getAt(this.selectedIndex);
if (r)
{
this.onSelect(r, this.selectedIndex);
}
if (doFocus !== false)
{
this.el.focus();
}
},
// private - if view got focus, emulates original navigation.
onViewKeyDown: function(e)
{
switch (e.getKey())
{
case e.ENTER:
{
this.keyNav['enter'].call(this, e);
break;
}
case e.ESC:
{
this.keyNav['esc'].call(this, e);
break;
}
case e.TAB:
{
this.keyNav['tab'].call(this, e);
break;
}
}
e.stopEvent();
return false;
},
/**
* 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)
* @param {Boolean} focus Optional. If true, focus sets on textfield after
* select.
*/
select : function(index, scrollIntoView, focus)
{
var sm = this.view.getSelectionModel();
this.selectedIndex = index;
if (sm instanceof Ext.grid.RowSelectionModel)
{
sm.selectRow(Ext.isArray(index)? index[0] : index);
}
else if (sm instanceof Ext.grid.CellSelectionModel)
{
sm.select(
Ext.isArray(index)? index[0] : index
,
Ext.isArray(index)? index[1] : 1
);
}
if (scrollIntoView !== false)
{
var el = this.view.getView().getRow(index);
if (el)
{
this.innerList.scrollChildIntoView(el, false);
}
}
if (focus !== undefined && focus === true) this.focus();
},
onViewBeforeEdit: function(o)
{
var ce = o.grid.getColumnModel().getCellEditor(o.column, o.row);
ce.un('hide', this.onViewAfterEdit, this);
ce.on('hide', this.onViewAfterEdit, this);
this.inEditMode = true;
},
onViewAfterEdit: function()
{
this.inEditMode = false;
}
});
Ext.reg('combogrid', Ext.sm.Form.ComboGrid);