Skip to content

Instantly share code, notes, and snippets.

@esatterwhite
Created July 26, 2011 13:37
Show Gist options
  • Save esatterwhite/1106777 to your computer and use it in GitHub Desktop.
Save esatterwhite/1106777 to your computer and use it in GitHub Desktop.
Class For rendering and validating forms in Sencha Touch
/**
* Module for General utility functions and classes
* @module utils
*
*/
Ext.ns('BlackjackM','BlackjackM.utils', 'BlackjackM.utils.forms');
/**
* A class which renders and validates a form based on a Ext.data.Model or Model instace
* @class ModelForm
* @namespace BlackjackM.utils.forms
*/
BlackjackM.utils.forms.ModelForm = Ext.extend(Ext.form.FormPanel, {
/**
* the name of the model to be used to render and validate the form
* This is required if an instance isn't given
* @config model
* @type String
* @default null
*/
model: null
/**
* <optional> a model instance to be used to rnder and validate the form against
* @config instance
* @type Ext.data.Model
* @default null
*/
,instance: null
/**
* A set of key value pairs used in conjunction with the model property to pre populate the form with
* @config initial
* @type Object
* @default {}
*/
,initial: {}
/**
* The css class to apply to invalid fields
* @config errorCls
* @type String
* @default x-field-error
*/
,errorCls: 'x-field-error'
/**
* If set to true each field will be rendered in it's own fieldset
* @config fieldsets
* @type boolean
* @default false
*/
,fieldsets: false
/**
* The name of a field or an a array of field names to exclude from form rendering
* @config excludes
* @type {String | Array}
* @default []
*/
,exclude:[]
/**
* names of fiels to be rendered as hidden input field
* @config hiddenFields
* @type {String | Array}
* @default []
*/
,hiddenFields:[]
,wrapForm: false
/**
* The Contstructor class
* @constructor
* @method initComponent
*/
,initComponent: function () {
this.fieldcache = [];
if ( !this.model && !this.instance) {
throw "ModelForm requires a model type or a Model instance";
}
this.addEvents({
formvalid: true
,forminvalid: true
,submitsuccess: true});
this.instance = this.instance || Ext.ModelMgr.create(this.initial || {}, this.model);
Ext.apply(this, {
items: this.fieldsets ? this.asFieldSets() : this.asFields()
});
this.on('afterlayout', function( c ){
Ext.each(this.query('field[rel=modelform-field]'), function (fld) {
fld.on('focus', function () {
fld.removeCls(this.errorCls);
}, this);
}, this);
})
BlackjackM.utils.forms.ModelForm.superclass.initComponent.call(this);
}
/**
* Sets up the focus event listener to remove error css class from fields
* @private
* @method afterRender
*/
,afterRender: function () {
this.loadRecord(this.instance);
// loadRecord saves the instance as `record`
delete this.instance;
BlackjackM.utils.forms.ModelForm.superclass.afterRender.call(this);
}
/**
* Validates the form againes it's internal model instance and returns an errors object and adds error class to invalid fields
* Fields that were excluded from the form do not got through validation
* @method validate
* @return Ext.data.Errors
*/
,validate: function () {
var excludes
,errors
,filterFn
,addErrors;
this.updateRecord(this.record);
excludes = Array.from( this.exclude );
errors = this.record.validate();
filterFn = function(obj, key){
return !excludes.contains( obj.field );
};
addErrors = function (err) {
var field = this.getFields(err.field);
field.addCls(this.errorCls);
};
// don't validate fields that were excluded from the form.
// they will always fail
errors = errors.filterBy( filterFn );
addErrors = Ext.createDelegate( addErrors, this );
errors.each(addErrors, this);
if ( errors.isValid() ) {
/**
* Fired when the form passes validation
* @event formvalid
* @param form {ModelForm} The current form instance
*/
this.fireEvent('formvalid', this);
} else {
/**
* Fired when the form fails validation
* @event forminvalid
* @param form {ModelForm} The current form instance
* @param err {Ext.data.Errors} The collection of errors returned from validation
*/
this.fireEvent('forminvalid', this, errors);
}
this.errors = errors;
return errors;
}
/**
* Returns true if the form passes validation
* @method isValid
* @return boolean
*/
,isValid: function () {
return !this.validate().length;
}
/**
* Clears error class from all form fields
* @method clearInvalid
* @return form {ModelForm} the form instance
*/
,clearInvalid: function (){
var clear_func = function( field ){
this.getFields( field.name ).removeCls( this.errorCls );
};
clear_func = Ext.createDelegate( clear_func, this );
this.record.fields.each( clear_func );
return this;
}
/**
* maks a field as being invalid
* @method markInvalid
* @param name {String} The name of the field to mark invalid
* @return form {ModelForm} the form instance
*/
,markInvalid: function ( name ) {
var fld = this.getFields( name );
if( fld ){
fld.addCls( this.errorCls );
}
return this;
}
/**
* returns an array of form field config objects
* @method asFields
* @return Array
* @private
*/
,asFields: function ( ) {
var excludes = Array.from( this.exclude )
,hiddens = Array.from( this.hiddenFields )
,that = this
,flds = []
,generator;
generator = function (field) {
var overrideCfg;
overrideCfg = that[ field.name + 'Cfg'] || null;
if( !excludes.contains( field.name ) ){
flds.push( Ext.apply({
name: field.name
,label:field.name.replace(/_/g, ' ')
,rel:'modelform-field'
,xtype:hiddens.contains(field.name) ?'hiddenfield' : field.fieldtype || 'textfield'
}, overrideCfg || field.fieldCfg || {})
);
}
};
this.instance.fields.each( generator, this );
return this.wrapForm ? new Ext.Container({
cls:'modelform-wrap'
,items:flds.clean()
}) : flds.clean();
}
/**
* Returns an array of form field config objects each wrapped in a fieldset config object
* @method asFieldSets
* @return Array
* @private
*/
,asFieldSets: function ( ) {
var excludes = Array.from( this.exclude )
,hiddens = Array.from( this.hiddenFields )
,that = this
,flds =[]
,generator;
generator = function (field) {
var should_hide
,itemcfg
,overrideCfg;
should_hide = hiddens.contains(field.name);
overrideCfg = that[ field.name + 'Cfg'] || null;
if( !excludes.contains( field.name ) ){
itemcfg = Ext.apply({
name: field.name
,rel:'modelform-field'
,xtype: should_hide?'hiddenfield' : field.fieldtype || 'textfield'
}, overrideCfg || field.fieldCfg || {});
flds.push(
should_hide ? itemcfg:{
xtype: 'fieldset'
,title: itemcfg.name.replace(/_/g, ' ')
,items: [itemcfg]
}
);
}
};
this.instance.fields.each( generator, this );
return this.wrapForm ? new Ext.Container({
cls:'modelform-wrap'
,items:flds.clean()
}) : flds.clean();
}
/**
* Returns the current values out of the associated model instance
* @method getModelValues
* @return values {Object} an object containing data from the model where keys are names of the fields
* @protected
*/
,getModelValues: function(){
var excludes = Array.from( this.exclude );
var ret_data = {};
var model_data = this.record[ this.record.persistanceProperty ];
for( var key in model_data ){
if( !excludes.contains( key ) ){
ret_data[key] = model_data[ key ];
}
}
return ret_data;
}
,getCleanValues: function(){
var data = this.getModelValues();
return this.cleanFormData( data );
}
/**
* A hook which allows developer to modify the data just before it is sent to the server
* By default the data just passes straight through
* @method cleanFormdata <override>
* @return form_data {object} the data object to send to the server
*/
,cleanFormData: function( data ){
return data;
}
/**
*
* @method submit
* @param config {Object} config object for the submit function
* @return form {ModelForm} the current form instance
*/
,submit: function( config ){
var errors
,url
,conn
,tmp_data
,model_data;
errors = this.validate();
if( !!errors.length){
return false;
}
config = config || {};
url = config.url || this.url;
if( !url ){ throw "You must specify a url propery :: ModelForm.js";}
model_data = this.getModelValues();
Object.merge( model_data, ( config.data || {}));
/**
* Fired just before the clean step takes place
* @event beforeclean
* @param data {Object} the data before it is cleaned
*/
this.fireEvent('beforeclean', model_data, this.record);
// the function can alter the internal data, but can not change the
// object it self. if the developer returns a value, we will use that
// otherwise we continue on.
tmp_data = this.cleanFormData( model_data );
if( tmp_data ){
model_data = tmp_data;
tmp_data = null;
}
/**
* Fired just after the form data has been cleaned
* @event afterclean
* @param data {Object} the data after it has been cleaned
*/
this.fireEvent('afterclean', model_data, this.record);
if( config.ajax && !this.fileUpload){
conn = new BlackjackM.Ajax({
url:url
,method: ( config.method || 'POST' )
,scope:this
,autoSend:true
,headers:{
'Accept':'application/json'
,'X-Requested-With':'XMLHttpRequest'
,'Content-Type':"application/json"
}
,jsonData: model_data
,success: function( response ){
if( config.success && typeof config.success == 'function'){
config.success.call( config.scope || this, response, this.record);
}
/**
* Fired when the form is successfully submitted
* @event submitsuccess
* @param form {ModelForm} the current form instance
* @param response {Object} the response object returned from the server
*/
this.fireEvent('submitsuccess', this, response, this.record);
}
});
}else{
BlackjackM.utils.forms.ModelForm.superclass.submit.call( this, config );
}
return this;
}
/**
* removes error classes and sets the fiels to their default values
* @method reset
* @return form {ModelForm} the current instance
*/
,reset: function(){
this.fields.each(function( field ){
field.removeCls(this.errorCls)
}, this)
BlackjackM.utils.forms.ModelForm.superclass.reset.call( this );
return this;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment