murrah
27 Dec 2008, 2:16 PM
Hi,
I had a need to make certain fields on a formPanel readonly depending upon the data on each specific record. I also wanted to change their style so that it was clear that the field could not be edited.
There are other solutions around that have slightly different behaviours
eg http://www.extjs.com/forum/showthread.php?t=40842 and http://extjs.com/learn/Extension:InlineFormFields
This is the solution I came up with. It is an override to Ext.form.Field that adds a new method I have called uxDisplayOnly(). This method is inherited by all descendants of Field so it means that existing code wont need to be refactored as it would if I had used Ext.extend (for example). It also works with Saki's DateTime (http://extjs.com/forum/showthread.php?t=22661) field.
The new method will remove trigger fields (eg the calendar button on a date field and the down arrow on a combo box). You also have the option of specifying a default style to use when the field is in displayOnly mode and you can also add another style on a field-by-field basis for more flexibility. You can also cause the method to use the same style as for editable fields if you wish - ie readOnly but looks like a "normal" field.
The field value attribute will contain the field data even when in displayOnly mode and will be returned to the server upon save. Click the Save button in the example to see what will be returned.
The override was tested and works in FF3, Air and Opera 9. It mostly works in IE7 but there is a problem with radio groups and checkbox groups (a single check box is ok). I have posted a forum help request here (http://extjs.com/forum/showthread.php?p=267031) and will update the code on this post when I have a solution.
To use the override, add the code after the ext script includes as shown in the example below. If you are using Ext.ux.form.DateTime, include that script before the override.
To test the example, unzip the attached file into your ext\examples\form folder and navigate to displayOnly.html.
Here is the js from the example:
/*
* Ext JS Library 2.2
* Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*
* Override and example by Murray Hopkins (www.murrah.com.au)
*
*/
Ext.onReady(function(){
Ext.QuickTips.init();
// turn on validation errors beside the field globally
Ext.form.Field.prototype.msgTarget = 'side';
// simple array store - used for combobox field below
var store = new Ext.data.SimpleStore({
fields: ['abbr', 'state', 'nick'],
data : Ext.exampledata.states // from states.js
});
//======================= Start of extention ============================
/* Use this override to add the uxDisplayOnly method to Ext.form.Field
* All descendants of Ext.form.Field will then inherit this method
* Version 1.0
* Known bug: Doesnt work with radiogroup and checkbox group in IE7 - the this.el.removeClass(cls)
* results in the radiogroup or checkbox group becoming invisible
*/
Ext.override(Ext.form.Field, {
uxDisplayOnly: function(displayOnly,cls){
// If no parameter, assume true
var displayOnly = (displayOnly===false) ? false : true;
// If a class name is passed in, use that, otherwise use the default one.
// The classes are defined in displayOnly.html for this example
var cls = (cls) ? cls : 'x-form-display-only-field';
// Add or remove the class
if (displayOnly) {
this.el.addClass(cls);
} else {
this.el.removeClass(cls);
}
// Set the underlying DOM element's readOnly attribute
this.el.dom.readOnly = displayOnly;
// Get this field's xtype (ie what kind of field is it?)
var xType = this.getXType();
if (xType=='checkbox'){
this.readOnly = displayOnly; // We need to also set the config readOnly attribute for checkboxes
}
// For radio groups and checkbox groups
// we need to set the config readOnly attribute for on each item in the group
if (xType == 'radiogroup' || xType == 'checkboxgroup') {
var items = this.items.items;
for (var i=0; i<items.length; i++) {
items[i].readOnly = displayOnly;
};
}
// For fields that have triggers (eg date,time,dateTime),
// show/hide the trigger
if (this.trigger) {
this.trigger.setDisplayed(!displayOnly);
}
}
});
// For Saki's DateTime extention we need to add a method which
// then calls the internal date and time fields
// If you arent using the DateTime component, remove/comment out this override
Ext.override(Ext.ux.form.DateTime, {
uxDisplayOnly: function(displayOnly,cls){
this.df.uxDisplayOnly(displayOnly);
this.tf.uxDisplayOnly(displayOnly);
}
});
//======================= End of extention ============================
// This is just a normal form panel. Apart from the first checkbox,
// there is nothing special here.
var myForm = new Ext.FormPanel({
labelWidth: 180,
url:'save-form.php',
frame:true,
title: 'My Form',
bodyStyle:'padding:5px 5px 0',
width: 470,
defaultType: 'textfield',
items: [
{
// This check box is here for the convenience of this example
// to illustrate how you might set the displayOnly functionality
// dynamically. In other situations you might
// call the uxDisplayOnly() method in an
// onRender or onLoad listener elsewhere in your code (for example)
xtype: 'checkbox',
fieldLabel: 'Show fields as display only - no editing allowed',
name: 'displayonly',
handler: function(cb,checked){
// To use the override above,
// call the field's uxDisplayOnly() method for each field you wish to make displayOnly.
// The checked parameter will be true or false depending on the checkbox state.
// If you pass no parameters to uxDisplayOnly() it defaults to true
// An example of adding your own special style to this one field (optional)
myForm.getForm().findField('textfield').uxDisplayOnly(checked,'x-form-display-only-field-special');
myForm.getForm().findField('textarea').uxDisplayOnly(checked);
myForm.getForm().findField('numberfield').uxDisplayOnly(checked);
// an example of how to keep the existing "editbox" style (ie add a space for the style)
myForm.getForm().findField('datefield').uxDisplayOnly(checked,' ');
myForm.getForm().findField('timefield').uxDisplayOnly(checked);
myForm.getForm().findField('dtf').uxDisplayOnly(checked);
myForm.getForm().findField('checkbox').uxDisplayOnly(checked);
myForm.getForm().findField('rgroup').uxDisplayOnly(checked);
myForm.getForm().findField('cbgroup').uxDisplayOnly(checked);
myForm.getForm().findField('combo').uxDisplayOnly(checked);
}
},
{
fieldLabel: 'Text field',
name: 'textfield',
value:'Acme Widgets Inc'
},
{
fieldLabel: 'Text area',
name: 'textarea',
height:50,
value:'Some long text here'
},
{
fieldLabel: 'Number field',
name: 'numberfield',
value:'23.00'
},
new Ext.form.DateField({
fieldLabel: 'Date field',
name: 'datefield',
value:'24/12/2008',
width:100
}),
new Ext.form.TimeField({
fieldLabel: 'Time field',
name: 'timefield',
value:'11:34am',
width:80,
minValue: '8:00am',
maxValue: '6:00pm'
}),
// This is an ext extention - DateTime combination field (Ext.ux.form.DateTime)
// See: http://extjs.com/forum/showthread.php?t=22661
{ xtype:'xdatetime'
,id:'dtf'
,fieldLabel:'Date & Time'
,anchor:'-18'
,timeFormat:'H:i'
,value:'19:15'
,timeConfig: {
altFormats:'H:i'
,allowBlank:true
}
,dateFormat:'d/m/Y'
,value:'10/12/2008'
,dateConfig: {
altFormats:'Y-m-d|Y-n-d'
,allowBlank:true
}
},
{
xtype: 'checkbox',
fieldLabel: 'Checkbox',
name: 'checkbox'
},
{
xtype: 'radiogroup',
fieldLabel: 'Radio group',
name:'rgroup',
id:'rgroup',
// For some reason I couldnt figure out, I needed to use the or
// for IE7 the box labels would wrap!!
// Condor said: The wrapping problem can be solved with this patch (it's number 9 on the checkbox buglist):
// http://www.extjs.com/forum/showthread.php?t=44603
// Murray said: This does work but I didnt apply it here in order NOT to complicate this example
items: [
{boxLabel: 'Item 1', name: 'rb-auto', inputValue: 1},
{boxLabel: 'Item 2', name: 'rb-auto', inputValue: 2, checked: true},
{boxLabel: 'Item 3', name: 'rb-auto', inputValue: 3}
]
},
{
xtype: 'checkboxgroup',
fieldLabel: 'Check box group',
id:'cbgroup',
items: [
{boxLabel: 'Item 1', name: 'cb-auto-1'},
{boxLabel: 'Item 2', name: 'cb-auto-2', checked: true},
{boxLabel: 'Item 3', name: 'cb-auto-3'}
]
},
new Ext.form.ComboBox({
id:'combo',
store: store,
displayField:'state',
fieldLabel: 'Combobox',
typeAhead: true,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
value:'Florida',
emptyText:'Select a state...',
selectOnFocus:true
})
],
buttons: [{
text: 'Save',
handler: function(){
if(myForm.getForm().isValid()){
Ext.Msg.alert('Submitted Values', 'The following will be sent to the server: <br />'+
myForm.getForm().getValues(true).replace(/&/g,', '));
}
}
},{
text: 'Cancel'
}]
});
myForm.render(document.body);
});
I had a need to make certain fields on a formPanel readonly depending upon the data on each specific record. I also wanted to change their style so that it was clear that the field could not be edited.
There are other solutions around that have slightly different behaviours
eg http://www.extjs.com/forum/showthread.php?t=40842 and http://extjs.com/learn/Extension:InlineFormFields
This is the solution I came up with. It is an override to Ext.form.Field that adds a new method I have called uxDisplayOnly(). This method is inherited by all descendants of Field so it means that existing code wont need to be refactored as it would if I had used Ext.extend (for example). It also works with Saki's DateTime (http://extjs.com/forum/showthread.php?t=22661) field.
The new method will remove trigger fields (eg the calendar button on a date field and the down arrow on a combo box). You also have the option of specifying a default style to use when the field is in displayOnly mode and you can also add another style on a field-by-field basis for more flexibility. You can also cause the method to use the same style as for editable fields if you wish - ie readOnly but looks like a "normal" field.
The field value attribute will contain the field data even when in displayOnly mode and will be returned to the server upon save. Click the Save button in the example to see what will be returned.
The override was tested and works in FF3, Air and Opera 9. It mostly works in IE7 but there is a problem with radio groups and checkbox groups (a single check box is ok). I have posted a forum help request here (http://extjs.com/forum/showthread.php?p=267031) and will update the code on this post when I have a solution.
To use the override, add the code after the ext script includes as shown in the example below. If you are using Ext.ux.form.DateTime, include that script before the override.
To test the example, unzip the attached file into your ext\examples\form folder and navigate to displayOnly.html.
Here is the js from the example:
/*
* Ext JS Library 2.2
* Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*
* Override and example by Murray Hopkins (www.murrah.com.au)
*
*/
Ext.onReady(function(){
Ext.QuickTips.init();
// turn on validation errors beside the field globally
Ext.form.Field.prototype.msgTarget = 'side';
// simple array store - used for combobox field below
var store = new Ext.data.SimpleStore({
fields: ['abbr', 'state', 'nick'],
data : Ext.exampledata.states // from states.js
});
//======================= Start of extention ============================
/* Use this override to add the uxDisplayOnly method to Ext.form.Field
* All descendants of Ext.form.Field will then inherit this method
* Version 1.0
* Known bug: Doesnt work with radiogroup and checkbox group in IE7 - the this.el.removeClass(cls)
* results in the radiogroup or checkbox group becoming invisible
*/
Ext.override(Ext.form.Field, {
uxDisplayOnly: function(displayOnly,cls){
// If no parameter, assume true
var displayOnly = (displayOnly===false) ? false : true;
// If a class name is passed in, use that, otherwise use the default one.
// The classes are defined in displayOnly.html for this example
var cls = (cls) ? cls : 'x-form-display-only-field';
// Add or remove the class
if (displayOnly) {
this.el.addClass(cls);
} else {
this.el.removeClass(cls);
}
// Set the underlying DOM element's readOnly attribute
this.el.dom.readOnly = displayOnly;
// Get this field's xtype (ie what kind of field is it?)
var xType = this.getXType();
if (xType=='checkbox'){
this.readOnly = displayOnly; // We need to also set the config readOnly attribute for checkboxes
}
// For radio groups and checkbox groups
// we need to set the config readOnly attribute for on each item in the group
if (xType == 'radiogroup' || xType == 'checkboxgroup') {
var items = this.items.items;
for (var i=0; i<items.length; i++) {
items[i].readOnly = displayOnly;
};
}
// For fields that have triggers (eg date,time,dateTime),
// show/hide the trigger
if (this.trigger) {
this.trigger.setDisplayed(!displayOnly);
}
}
});
// For Saki's DateTime extention we need to add a method which
// then calls the internal date and time fields
// If you arent using the DateTime component, remove/comment out this override
Ext.override(Ext.ux.form.DateTime, {
uxDisplayOnly: function(displayOnly,cls){
this.df.uxDisplayOnly(displayOnly);
this.tf.uxDisplayOnly(displayOnly);
}
});
//======================= End of extention ============================
// This is just a normal form panel. Apart from the first checkbox,
// there is nothing special here.
var myForm = new Ext.FormPanel({
labelWidth: 180,
url:'save-form.php',
frame:true,
title: 'My Form',
bodyStyle:'padding:5px 5px 0',
width: 470,
defaultType: 'textfield',
items: [
{
// This check box is here for the convenience of this example
// to illustrate how you might set the displayOnly functionality
// dynamically. In other situations you might
// call the uxDisplayOnly() method in an
// onRender or onLoad listener elsewhere in your code (for example)
xtype: 'checkbox',
fieldLabel: 'Show fields as display only - no editing allowed',
name: 'displayonly',
handler: function(cb,checked){
// To use the override above,
// call the field's uxDisplayOnly() method for each field you wish to make displayOnly.
// The checked parameter will be true or false depending on the checkbox state.
// If you pass no parameters to uxDisplayOnly() it defaults to true
// An example of adding your own special style to this one field (optional)
myForm.getForm().findField('textfield').uxDisplayOnly(checked,'x-form-display-only-field-special');
myForm.getForm().findField('textarea').uxDisplayOnly(checked);
myForm.getForm().findField('numberfield').uxDisplayOnly(checked);
// an example of how to keep the existing "editbox" style (ie add a space for the style)
myForm.getForm().findField('datefield').uxDisplayOnly(checked,' ');
myForm.getForm().findField('timefield').uxDisplayOnly(checked);
myForm.getForm().findField('dtf').uxDisplayOnly(checked);
myForm.getForm().findField('checkbox').uxDisplayOnly(checked);
myForm.getForm().findField('rgroup').uxDisplayOnly(checked);
myForm.getForm().findField('cbgroup').uxDisplayOnly(checked);
myForm.getForm().findField('combo').uxDisplayOnly(checked);
}
},
{
fieldLabel: 'Text field',
name: 'textfield',
value:'Acme Widgets Inc'
},
{
fieldLabel: 'Text area',
name: 'textarea',
height:50,
value:'Some long text here'
},
{
fieldLabel: 'Number field',
name: 'numberfield',
value:'23.00'
},
new Ext.form.DateField({
fieldLabel: 'Date field',
name: 'datefield',
value:'24/12/2008',
width:100
}),
new Ext.form.TimeField({
fieldLabel: 'Time field',
name: 'timefield',
value:'11:34am',
width:80,
minValue: '8:00am',
maxValue: '6:00pm'
}),
// This is an ext extention - DateTime combination field (Ext.ux.form.DateTime)
// See: http://extjs.com/forum/showthread.php?t=22661
{ xtype:'xdatetime'
,id:'dtf'
,fieldLabel:'Date & Time'
,anchor:'-18'
,timeFormat:'H:i'
,value:'19:15'
,timeConfig: {
altFormats:'H:i'
,allowBlank:true
}
,dateFormat:'d/m/Y'
,value:'10/12/2008'
,dateConfig: {
altFormats:'Y-m-d|Y-n-d'
,allowBlank:true
}
},
{
xtype: 'checkbox',
fieldLabel: 'Checkbox',
name: 'checkbox'
},
{
xtype: 'radiogroup',
fieldLabel: 'Radio group',
name:'rgroup',
id:'rgroup',
// For some reason I couldnt figure out, I needed to use the or
// for IE7 the box labels would wrap!!
// Condor said: The wrapping problem can be solved with this patch (it's number 9 on the checkbox buglist):
// http://www.extjs.com/forum/showthread.php?t=44603
// Murray said: This does work but I didnt apply it here in order NOT to complicate this example
items: [
{boxLabel: 'Item 1', name: 'rb-auto', inputValue: 1},
{boxLabel: 'Item 2', name: 'rb-auto', inputValue: 2, checked: true},
{boxLabel: 'Item 3', name: 'rb-auto', inputValue: 3}
]
},
{
xtype: 'checkboxgroup',
fieldLabel: 'Check box group',
id:'cbgroup',
items: [
{boxLabel: 'Item 1', name: 'cb-auto-1'},
{boxLabel: 'Item 2', name: 'cb-auto-2', checked: true},
{boxLabel: 'Item 3', name: 'cb-auto-3'}
]
},
new Ext.form.ComboBox({
id:'combo',
store: store,
displayField:'state',
fieldLabel: 'Combobox',
typeAhead: true,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
value:'Florida',
emptyText:'Select a state...',
selectOnFocus:true
})
],
buttons: [{
text: 'Save',
handler: function(){
if(myForm.getForm().isValid()){
Ext.Msg.alert('Submitted Values', 'The following will be sent to the server: <br />'+
myForm.getForm().getValues(true).replace(/&/g,', '));
}
}
},{
text: 'Cancel'
}]
});
myForm.render(document.body);
});