PHP Code:
/**
* Ext.sm.Form.CheckboxSelect - 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!
*/
Ext.namespace('Ext.sm.Form');
/**
* @class Ext.sm.Form.CheckboxSelect
* @extends Ext.Panel
* <p><b>Represent a composite control to realize special multiselection.</b></p>
* <p>This control displays an Ext.form.ComboBox, and a panel below the combobox.
* If you select an item from the combobox, a checked checkbox appears within the
* panel. User can uncheck a checkbox, then checkbox disappears (going to remove).</p>
* <p>You can configure fully the ComboBox as usual within the {@link #comboConfig}.
* Checkboxes are displayed in columns, next to each other. On default the control
* has three columns for that, but you can override this by set the {@link #checkboxColumns}
* configuration setting. Checkbox values parsed as array of values. For example you
* have a ComboBox with items ['first'='One', 'second'='Two'] (value=text) and {@link name}
* has the value 'numbers' - now if you select all items, you have two checkboxes on
* the form, the parsed result is similar: <i>...numbersItem[]=first&numbersItem[]=second</i>.</p>
* <p>The code below illustrates how the component works:</p><pre><code>
var win = new Ext.Window({
title : 'Test',
items : [{
xtype : 'form',
autoHeight : true,
ctCls: 'testForm',
items : [{
xtype : 'checkboxselect',
name : 'numbers',
hiddenName: 'numbersRaw',
fieldLabel : 'Test',
comboConfig : {
store : [['1', 'One'], ['2', 'Two'], ['3', 'Three'], ['4', 'Four']]
}
}],
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();
</code></pre></p>
*/
Ext.sm.Form.CheckboxSelect = Ext.extend(Ext.Panel, {
/**
* @cfg {String} cls A custom CSS class to apply to the field's underlying
* element (defaults to ""). Mapped to ComboBox.
*/
/**
* @cfg {String} fieldClass The default CSS class for the field (defaults
* to "x-form-field"). Mapped to ComboBox.
*/
/**
* @cfg {String} focusClass The CSS class to use when the field receives
* focus (defaults to "x-form-focus"). Mapped to ComboBox.
*/
/**
* @cfg {String} invalidClass The CSS class to use when marking a field
* invalid (defaults to "x-form-invalid").
*/
invalidClass: 'x-form-invalid',
/**
* @cfg {String} invalidText The error text to use when marking a field
* invalid and no message is provided (defaults to "The value in this field
* is invalid"). Mapped to ComboBox.
*/
/**
* @cfg {String} msgFx <b>Experimental</b> The effect used when displaying a
* validation message under the field (defaults to 'normal').
*/
msgFx: 'normal',
/**
* @cfg {String} msgTarget The location where error text should display.
* Should be one of the following values (defaults to 'qtip'):
* <pre>
Value Description
----------- ----------------------------------------------------------------------
qtip Display a quick tip when the user hovers over the field
title Display a default browser title attribute popup
under Add a block div beneath the field containing the error text
side Add an error icon to the right of the field with a popup on hover
[element id] Add the error text directly to the innerHTML of the specified element
</pre>
*/
msgTarget : 'qtip',
/**
* @cfg {String} name Name of Ext.form.Checkbox components. A
* default 'unnamed' set, but you must change this if you want a normal result
* on the server-side, or use this field by form load.
*/
name: 'unnamed',
/**
* @cfg {Boolean} readOnly True to mark the field as readOnly in HTML
* (defaults to false) -- Note: this only sets the element's readOnly DOM
* attribute. <b>This is mapped to all subfields!</b>
*/
readOnly : false,
/**
* @cfg {Number} tabIndex The tabIndex for this field. Note this only
* applies to fields that are rendered, not those which are built via
* applyTo (defaults to undefined). Mapped to ComboBox.
*/
/**
* @cfg {Boolean} validateOnBlur Whether the field should validate when
* it loses focus (defaults to true). Mapped to ComboBox.
*/
/**
* @cfg {String/Boolean} validationEvent The event that should initiate
* field validation. Set to false to disable automatic validation (defaults
* to "keyup"). Mapped to ComboBox.
*/
/**
* @cfg {Mixed} value A value to initialize this field with (defaults to
* undefined). Mapped to ComboBox.
*/
// private - indicates that this is a form field for BasicForm.
isFormField: true,
// private
originalValue : null,
// private
comboConfigDefault : {
xtype: 'combo',
typeAhead : true,
forceSelection : true,
triggerAction : "all",
selectOnFocus :true,
mode: 'local'
},
// private
cbpanelConfigDefault : {
xtype : "panel",
border : false,
layout : 'column'
},
// private
cbpanelColConfig : {
defaultType : 'checkbox',
layout : 'form',
border : false,
defaults : {
hideLabel : true,
anchor : '100%'
}
},
// private
combo : null,
// private
checkboxPanel : null,
// private
privateValueField : null,
/**
* @cfg {String} hiddenName
* This is the name of used hidden field for storing raw value for correctly
* submitting this field. If this has an empty (null, undefined, etc) value,
* hidden field will not created. Default is undefined.
*/
/**
* @cfg {Object} comboConfig
* This is the config of component`s ComboBox, here you can set the fieldLabel and
* store etc on it. Default settings are: typeAhead=true, forceSelection=true.
*/
comboConfig : {},
/**
* @cfg {Integer} checkboxColumns
* Number of columns in checkbox display panel. Default is 3.
*/
checkboxColumns : 3,
/**
* @cfg {String/Object} layout
* Layout is fixed to 'form', because this panel is going to a FormPanel
* component now.
*/
// private
lastColIdx : 0,
// private
initComponent : function()
{
var comboConfig = (this.comboConfig == undefined ? {} : this.comboConfig);
var colCfg;
var columns = [];
Ext.sm.Form.CheckboxSelect.superclass.initComponent.call(this);
this.border = false;
// combo
this.mapToCombo();
comboConfig = Ext.applyIf(comboConfig, this.comboConfigDefault);
this.combo = new Ext.ComponentMgr.create(comboConfig);
// checkbox panel
for (var i=0; i<this.checkboxColumns; i++)
{
colCfg = Ext.apply({}, this.cbpanelColConfig);
columns.push(colCfg);
}
Ext.apply(this.cbpanelConfigDefault, {
layoutConfig : {columns : this.checkboxColumns},
items : columns
});
this.checkboxPanel = new Ext.Panel(this.cbpanelConfigDefault);
// listener
this.combo.on('select', function(self, record, index){this.onComboSelect(self, record, index)}.createDelegate(this));
// add to me
this.add(this.combo);
this.add(this.checkboxPanel);
},
// private
onComboSelect : function(self, record, index)
{
var vf = this.combo.valueField;
var df = this.combo.displayField;
this.addCheckbox(record.data[df], record.data[vf]);
return true;
},
// private
addCheckbox : function(text, value)
{
var cb;
var p;
if (this.checkboxPanel.find('value', value).length == 0)
{
p = this.getColumnNext();
cb = new Ext.form.Checkbox({
boxLabel : text,
name : this.name + '[]',
value: value,
inputValue : value,
checked : true
});
cb.on('check', function(self, checked){this.onCheckboxCheck(self, checked)}.createDelegate(this));
p.add(cb);
p.bubble(function(){this.render(); return true;});
}
},
// private
onCheckboxCheck : function(self, checked)
{
if (!checked)
{
this.removeCheckbox(self);
}
},
/**
* Removes the passed checkbox from the panel and reorders the other.
* @param {Component} cb The checkbox to remove.
*/
removeCheckbox : function(cb)
{
var cbColList = [];
var cbConfigList = [];
var p;
var i, j = 0;
var maxLength = 0;
var cbi;
// Clone all checkbox components in order, except the removed one.
for (var i=0; i<this.checkboxColumns; i++)
{
cbColList.push(this.checkboxPanel.get(i).findByType('checkbox'));
}
for (var i=0; i<cbColList.length; i++)
{
if (maxLength < cbColList[i].length) maxLength = cbColList[i].length;
}
for (var i=0; i<maxLength; i++)
{
for (var j=0; j<this.checkboxColumns; j++)
{
cbi = this.checkboxPanel.get(j).get(i);
if (cbi !== undefined && cbi.getId() != cb.getId())
{
cbConfigList.push(cbi.cloneConfig());
}
}
}
// Remove checkboxes from all columns.
this.removeAllCheckboxes();
// recreate all checkboxes
this.lastColIdx = 0;
for (var i=0; i<cbConfigList.length; i++)
{
p = this.getColumnNext();
cbConfigList[i].on('check', function(self, checked){this.onCheckboxCheck(self, checked)}.createDelegate(this));
p.add(cbConfigList[i]);
p.bubble(function(){this.render(); return true;});
}
},
// private
removeAllCheckboxes: function()
{
for (var i=0; i<this.checkboxColumns; i++)
{
this.checkboxPanel.get(i).removeAll();
}
},
// private
getColumnNext : function()
{
var key = this.lastColIdx;
this.lastColIdx++;
if (this.lastColIdx >= this.checkboxColumns) this.lastColIdx = 0;
return this.checkboxPanel.get(key);
},
/**
* Returns selected items` values in array.
* @return {Array}
*/
getSelectedValues : function()
{
var result = [];
var chs;
chs = this.checkboxPanel.findByType('checkbox');
for (var i=0; i<chs.length; i++)
{
result.push(chs[i].inputValue);
}
return result;
},
//***
// Now implementing necessary Ext.form.Field methods.
//
/**
* Clear any invalid styles/messages for this field. Mapped to ComboBox.
*/
clearInvalid : function()
{
this.combo.clearInvalid();
},
/**
* Returns the name attribute of the field if available.
* @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
*/
getName : function()
{
return (Ext.isEmpty(this.name)? null : this.name);
},
/**
* Returns the raw data value which may or may not be a valid, defined value.
* To return a normalized value see {@link #getValue}. This value on this field
* is a dynamic set of values like 'value[]=1&value[]=2&value[]=3'.
* @return {String} value The field value
*/
getRawValue : function()
{
var v = this.getSelectedValues();
var ra = [];
var result;
if (this.rendered)
{
for (var i=0; i<v.length; i++)
{
ra.push(this.name + '[]=' + v[i]);
}
}
result = (ra.length == 0? '' : ra.join('&'));
return result;
},
/**
* Returns the normalized data value (undefined or emptyText will be returned
* as ''). To return the raw value see {@link #getRawValue}.
* @return {String} value The field value
*/
getValue : function()
{
return this.getRawValue();
},
/**
* <p>Returns true if the value of this Field has been changed from its original value,
* and is not disabled.</p>
* <p>Note that if the owning {@link Ext.form.BasicForm form} was configured with
* {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
* then the <i>original value</i> is updated when the values are loaded by
* {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#setValues setValues}.</p>
* @return {Boolean} True if this field has been changed from its original value (and
* is not disabled), false otherwise.
*/
isDirty : function()
{
if (this.disabled)
{
return false;
}
return String(this.getValue()) !== String(this.originalValue);
},
/**
* Returns whether or not the field value is currently valid. Mapped to ComboBox.
* @param {Boolean} preventMark True to disable marking the field invalid
* @return {Boolean} True if the value is valid, else false
*/
isValid : function(preventMark)
{
return this.combo.isValid(preventMark);
},
/**
* Mark this field as invalid, using {@link #msgTarget} to determine how to
* display the error and applying {@link #invalidClass} to the field's element.
* @param {String} msg (optional) The validation message (defaults to {@link #invalidText})
*/
markInvalid : function(msg)
{
var mt, t;
if (!this.rendered || this.preventMark) // not rendered
{
return;
}
msg = msg || this.invalidText;
mt = this.getMessageHandler();
if (mt)
{
mt.mark(this, msg);
}
else if (this.msgTarget)
{
this.el.addClass(this.invalidClass);
t = Ext.getDom(this.msgTarget);
if (t)
{
t.innerHTML = msg;
t.style.display = this.msgDisplay;
}
}
// TODO: events implementation.
//this.fireEvent('invalid', this, msg);
},
/**
* Resets the current field value to the originally loaded value and clears
* any validation messages.
* See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
*/
reset : function()
{
this.clearInvalid();
this.combo.reset();
this.removeAllCheckboxes();
this.setValue(this.originalValue);
},
/**
* Sets a data value into the field and validates it. To set the value directly
* without validation see {@link #setRawValue}. Applied values are type of
* String and Array.
* @param {String/Array} value The value to set
* @return {Ext.form.Field} this
*/
setValue : function(value)
{
this.value = '';
if (!Ext.isEmpty(value))
{
this.value = this.valueIfArray(value);
}
if (this.rendered)
{
this.setRawValue(this.value);
this.validate();
}
return this;
},
/**
* Sets the values by creating all checkboxes that are existed by combo`s
* store, bypassing validation. To set the value with validation see {@link #setValue}.
* @param {String/Array} value The value to set
* @return {String} value The field value that is set
*/
setRawValue : function(value)
{
var va = [];
var vi, v, dr;
var vf = this.combo.valueField;
var df = this.combo.displayField;
var result;
if (!Ext.isEmpty(value))
{
va = this.valueIfArray(value).split('&');
}
if (this.combo.store && this.combo.store.getCount() == 0) this.combo.store.load();
for (var i=0; i<va.length; i++)
{
vi = va[i].split('=');
v = (vi.length == 1? '' : vi[1]); // if has no value for this item.
dr = this.combo.findRecord(vf, v);
this.addCheckbox(dr.data[df], v);
}
result = this.getValue();
if (!Ext.isEmpty(this.hiddenName))
{
if (Ext.isEmpty(this.privateValueField))
{
this.privateValueField = new Ext.form.Hidden({
name: this.hiddenName,
value: result,
inputValue: result
});
this.add(this.privateValueField);
}
else
{
this.privateValueField.setValue(result);
}
}
return result;
},
/**
* Validates the field value. Mapped to ComboBox.
* @return {Boolean} True if the value is valid, else false
*/
validate : function()
{
return this.combo.validate();
},
// private
afterRender : function()
{
Ext.sm.Form.CheckboxSelect.superclass.afterRender.call(this);
// TODO: This is the place for initEvents() if needed.
//this.initEvents();
this.initValue();
},
// private
initValue : function()
{
if (this.value !== undefined)
{
this.setValue(this.value);
}
else
{
this.setValue(this.getValue());
}
// reference to original value for reset
this.originalValue = this.getValue();
},
// private
getMessageHandler : function()
{
return Ext.form.MessageTargets[this.msgTarget];
},
// private
valueIfArray : function(value)
{
var result = value;
var ri;
var ra = [];
if (Ext.isArray(value) && !Ext.isEmpty(value[0]))
{
for (var i=0; i<value.length; i++)
{
ri = this.name + '[]=' + value[i];
ra.push(ri);
}
result = ra.join('&');
}
return result;
},
// private
mapToCombo : function()
{
var toMap = [
'cls',
'fieldClass',
'focusClass',
'invalidText',
'tabIndex',
'validateOnBlur',
'validationEvent',
];
for (var i=0; i<toMap.length; i++)
{
if (this[toMap[i]] !== undefined) this.comboConfigDefault[toMap[i]] = this[toMap[i]];
}
}
});
Ext.reg('checkboxselect', Ext.sm.Form.CheckboxSelect);
Example that demonstrated the magic of using: