PDA

View Full Version : Extension to dynamically change form fields to readonly ("displayOnly")



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 &nbsp; 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&nbsp;1', name: 'rb-auto', inputValue: 1},
{boxLabel: 'Item&nbsp;2', name: 'rb-auto', inputValue: 2, checked: true},
{boxLabel: 'Item&nbsp;3', name: 'rb-auto', inputValue: 3}
]
},
{
xtype: 'checkboxgroup',
fieldLabel: 'Check box group',
id:'cbgroup',
items: [
{boxLabel: 'Item&nbsp;1', name: 'cb-auto-1'},
{boxLabel: 'Item&nbsp;2', name: 'cb-auto-2', checked: true},
{boxLabel: 'Item&nbsp;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);

});

mjlecomte
27 Dec 2008, 6:55 PM
Hey, thanks for the contribution. One comment after only a brief cursory review of the code would be to inquire if you tested this with form validation, formBind, etc. The reason I say is that I noticed Ext3 has a displayField in svn. I think it serves a different purpose than what you have done here, but all the same, I think I noticed that the validation methods were reset to emptyFn presumably to not muck up validation of the form. Just a comment from the peanut gallery...

murrah
27 Dec 2008, 7:36 PM
Just a comment from the peanut gallery...

Your comments are very welcome. I am still getting my head around Ext and I may well not have understood something vital.

I had tested the date and time field validation and they both work before and after using the method. I just added an email field with vtype:'email' and that worked also.

Should I be testing something else?

Thanks again,
Murray

mjlecomte
28 Dec 2008, 4:24 AM
As pappy used to say, don't fix what isn't broken.

moegal
14 Jan 2009, 2:04 PM
How can I load them as disabled without the brief jump from normal to displayonly?
Thanks, Marty

murrah
16 Jan 2009, 11:55 AM
How can I load them as disabled without the brief jump from normal to displayonly?
Thanks, Marty

Can you be a bit more specific about what you are doing please? ie how are you implementing this override? Do you mean load them as "disabled" or load them as "readonly"?

moegal
16 Jan 2009, 7:48 PM
I added a listener of afterlayout on the formpanel and put in your code to change the field to display only. Looks pretty good in IE but it shows the original fields and then as display only in FF 2. I tried on render, show and others with little success.

Thanks, Marty



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',
listeners: {
'afterlayout': function() {
// 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(1,'x-form-display-only-field-special');

myForm.getForm().findField('textarea').uxDisplayOnly(1);
myForm.getForm().findField('numberfield').uxDisplayOnly(1);

// an example of how to keep the existing "editbox" style (ie add a space for the style)
myForm.getForm().findField('datefield').uxDisplayOnly(1,' ');

myForm.getForm().findField('timefield').uxDisplayOnly(1);
myForm.getForm().findField('dtf').uxDisplayOnly(1);
myForm.getForm().findField('checkbox').uxDisplayOnly(1);
myForm.getForm().findField('rgroup').uxDisplayOnly(1);
myForm.getForm().findField('cbgroup').uxDisplayOnly(1);
myForm.getForm().findField('combo').uxDisplayOnly(1,' ');
}...

murrah
17 Jan 2009, 2:57 PM
I added a listener of afterlayout on the formpanel and put in your code to change the field to display only. Looks pretty good in IE but it shows the original fields and then as display only in FF 2. I tried on render, show and others with little success.


My need was to have a form permanently displayed and then change some fields to display only or not depending on the data for the specific record being displayed. So, the change to/from display only was part of the process. It may be that one of the other solutions mentioned in my first post will be more applicable in your situation.

Cheers,
Murray

moegal
18 Jan 2009, 5:03 AM
I like your solution better, except to the flash at load. I may try masking or hiding the form until after it is loaded fully and then displaying it..
Thanks, Marty

PS. I will look at the other solutions as well. I currently have a view form and a modify form but it would be nice to combine the 2.

udotirol
8 Feb 2009, 5:41 AM
with ComboBoxes, you only disable the trigger icon, but one can still see the list options using keyboard navigation (eg. using cursor down) and subsequently change the value of the ComboBox

accilies
4 Mar 2009, 10:47 PM
with ComboBoxes, you only disable the trigger icon, but one can still see the list options using keyboard navigation (eg. using cursor down) and subsequently change the value of the ComboBox

Any solution to disable the key navigation in combo?

mystix
4 Mar 2009, 11:04 PM
Any solution to disable the key navigation in combo?

try


myCombo.keyNav.disable();


or the complete override:


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

if (this.isXType('combo') && this.keyNav) {
this.keyNav[displayOnly? 'disable' : 'enable']();
}
}
});

accilies
4 Mar 2009, 11:47 PM
Thanks for the quick reply mystix but both solutions did not work for me. I did try keyNav.disable() earlier too but when i press the down arrow key, the list is still seen.

mystix
5 Mar 2009, 12:22 AM
Thanks for the quick reply mystix but both solutions did not work for me. I did try keyNav.disable() earlier too but when i press the down arrow key, the list is still seen.

that's not possible.

try this on the online combo example (http://extjs.com/deploy/dev/examples/form/combos.html):


// get the first combo on the page (i.e. local states combo)
var combo = Ext.ComponentMgr.all.find(function(c) { return c.isXType && c.isXType('combo'); });

combo.keyNav.disable();

for me, the down arrow key doesn't bring up the list, as expected.

mabello
5 Mar 2009, 2:55 AM
Not sure it can help:
http://extjs.com/forum/showthread.php?t=40842

moegal
15 Apr 2009, 7:33 AM
Anyone looked at the new DisplayField in 3.0: A display-only text field which is not validated and not submitted.

I know it has been just realeased, but I am looking at the new stuff to see how I can use it.

Marty

devtig
2 Feb 2010, 4:08 AM
Mystix, your solution to not show the list with the arrow key,works nicely in 3.1. I used:
Ext.ComponentMgr.get('id_dealer1Combobox').keyNav.disable();

But, when multiple entries exist after typing some letter, then you may want to use the arrow keys to select an entry. So, we need a conditional keyNav possibility:
When no entries are shown then keyNav should be disabled
When entries are shown then keyNav should be enabled.