/**
* A {@link Ext.ux.statusbar.StatusBar} plugin that provides automatic error
* notification when the associated form contains validation errors.
*/
Ext.define('Ext.ux.statusbar.ValidationStatus', {
extend: 'Ext.Component',
alias: 'plugin.validationstatus',
requires: ['Ext.util.MixedCollection'],
/**
* @cfg {String} errorIconCls
* The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
* to the status message when there is a validation error.
*/
errorIconCls: 'x-status-error',
/**
* @cfg {String} errorListCls
* The css class to be used for the error list when there are validation errors.
*/
errorListCls: 'x-status-error-list',
/**
* @cfg {String} validIconCls
* The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
* to the status message when the form validates.
*/
validIconCls: 'x-status-valid',
/**
* @cfg {String} showText
* The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
* there is a form validation error.
*/
showText: 'The form has errors (click for details...)',
/**
* @cfg {String} hideText
* The {@link Ext.ux.statusbar.StatusBar#text text} value to display when
* the error list is displayed.
*/
hideText: 'Click again to hide the error list',
/**
* @cfg {String} submitText
* The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
* the form is being submitted.
*/
submitText: 'Saving...',
/**
* @private
*/
init: function(sb) {
var me = this;
me.statusBar = sb;
sb.on({
single: true,
scope: me,
render: me.onStatusbarRender
});
sb.on({
click: {
element: 'el',
fn: me.onStatusClick,
scope: me,
buffer: 200
}
});
},
onStatusbarRender: function(sb) {
var me = this,
startMonitor = function() {
me.monitor = true;
};
me.monitor = true;
me.errors = Ext.create('Ext.util.MixedCollection');
me.listAlign = (sb.statusAlign === 'right' ? 'br-tr?' : 'bl-tl?');
if (me.form) {
// Allow either an id, or a reference to be specified as the form name.
me.formPanel = Ext.getCmp(me.form) ||
me.statusBar.lookupController().lookupReference(me.form);
me.basicForm = me.formPanel.getForm();
me.startMonitoring();
me.basicForm.on({
beforeaction: function(f, action) {
if (action.type === 'submit') {
// Ignore monitoring while submitting otherwise the field validation
// events cause the status message to reset too early
me.monitor = false;
}
}
});
me.formPanel.on({
beforedestroy: me.destroy,
scope: me
});
me.basicForm.on('actioncomplete', startMonitor);
me.basicForm.on('actionfailed', startMonitor);
}
},
/**
* @private
*/
startMonitoring: function() {
this.basicForm.getFields().each(function(f) {
f.on('validitychange', this.onFieldValidation, this);
}, this);
},
/**
* @private
*/
stopMonitoring: function() {
var form = this.basicForm;
if (!form.destroyed) {
form.getFields().each(function(f) {
f.un('validitychange', this.onFieldValidation, this);
}, this);
}
},
doDestroy: function() {
Ext.destroy(this.msgEl);
this.stopMonitoring();
this.statusBar.statusEl.un('click', this.onStatusClick, this);
this.callParent();
},
/**
* @private
*/
onFieldValidation: function(f, isValid) {
var me = this,
msg;
if (!me.monitor) {
return false;
}
msg = f.getErrors()[0];
if (msg) {
me.errors.add(f.id, { field: f, msg: msg });
}
else {
me.errors.removeAtKey(f.id);
}
this.updateErrorList();
if (me.errors.getCount() > 0) {
if (me.statusBar.getText() !== me.showText) {
me.statusBar.setStatus({
text: me.showText,
iconCls: me.errorIconCls
});
}
}
else {
me.statusBar.clearStatus().setIcon(me.validIconCls);
}
},
/**
* @private
*/
updateErrorList: function() {
var me = this,
msg,
msgEl = me.getMsgEl();
if (me.errors.getCount() > 0) {
msg = ['<ul>'];
this.errors.each(function(err) {
msg.push('<li id="x-err-', err.field.id, '"><a href="#">', err.msg, '</a></li>');
});
msg.push('</ul>');
msgEl.update(msg.join(''));
}
else {
msgEl.update('');
}
// reset msgEl size
msgEl.setSize('auto', 'auto');
},
/**
* @private
*/
getMsgEl: function() {
var me = this,
msgEl = me.msgEl,
t;
if (!msgEl) {
msgEl = me.msgEl = Ext.DomHelper.append(Ext.getBody(), {
cls: me.errorListCls
}, true);
msgEl.hide();
msgEl.on('click', function(e) {
t = e.getTarget('li', 10, true);
if (t) {
Ext.getCmp(t.id.split('x-err-')[1]).focus();
me.hideErrors();
}
}, null, { stopEvent: true }); // prevent anchor click navigation
}
return msgEl;
},
/**
* @private
*/
showErrors: function() {
var me = this;
me.updateErrorList();
me.getMsgEl().alignTo(me.statusBar.getEl(), me.listAlign).slideIn(
'b', { duration: 300, easing: 'easeOut' }
);
me.statusBar.setText(me.hideText);
// hide if the user clicks directly into the form
me.formPanel.body.on('click', me.hideErrors, me, { single: true });
},
/**
* @private
*/
hideErrors: function() {
var el = this.getMsgEl();
if (el.isVisible()) {
el.slideOut('b', { duration: 300, easing: 'easeIn' });
this.statusBar.setText(this.showText);
}
this.formPanel.body.un('click', this.hideErrors, this);
},
/**
* @private
*/
onStatusClick: function() {
if (this.getMsgEl().isVisible()) {
this.hideErrors();
}
else if (this.errors.getCount() > 0) {
this.showErrors();
}
}
});