X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..c8256059947f3aa8f5b0a9a2acf55e2142bb4742:/pkgs/pkg-forms-debug.js?ds=sidebyside diff --git a/pkgs/pkg-forms-debug.js b/pkgs/pkg-forms-debug.js index e3776075..12a4f5a7 100644 --- a/pkgs/pkg-forms-debug.js +++ b/pkgs/pkg-forms-debug.js @@ -1,6 +1,6 @@ /*! - * Ext JS Library 3.0.0 - * Copyright(c) 2006-2009 Ext JS, LLC + * Ext JS Library 3.2.1 + * Copyright(c) 2006-2010 Ext JS, Inc. * licensing@extjs.com * http://www.extjs.com/license */ @@ -14,9 +14,15 @@ * @xtype field */ Ext.form.Field = Ext.extend(Ext.BoxComponent, { + /** + *
The label Element associated with this Field. Only available after this Field has been rendered by a + * {@link form Ext.layout.FormLayout} layout manager.
+ * @type Ext.Element + * @property label + */ /** * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password, file (defaults - * to "text"). The types "file" and "password" must be used to render those field types currently -- there are + * to 'text'). The types 'file' and 'password' must be used to render those field types currently -- there are * no separate Ext components for those. Note that if you use inputType:'file', {@link #emptyText} * is not supported and should be avoided. */ @@ -28,32 +34,37 @@ Ext.form.Field = Ext.extend(Ext.BoxComponent, { * @cfg {Mixed} value A value to initialize this field with (defaults to undefined). */ /** - * @cfg {String} name The field's HTML name attribute (defaults to ""). + * @cfg {String} name The field's HTML name attribute (defaults to ''). * Note: this property must be set if this field is to be automatically included with * {@link Ext.form.BasicForm#submit form submit()}. */ /** - * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to ""). + * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to ''). */ /** - * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid") + * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to 'x-form-invalid') */ - invalidClass : "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") + * (defaults to 'The value in this field is invalid') + */ + invalidText : 'The value in this field is invalid', + /** + * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to 'x-form-focus') */ - invalidText : "The value in this field is invalid", + focusClass : 'x-form-focus', /** - * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus") + * @cfg {Boolean} preventMark + * true to disable {@link #markInvalid marking the field invalid}. + * Defaults to false. */ - focusClass : "x-form-focus", /** * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable - automatic validation (defaults to "keyup"). + automatic validation (defaults to 'keyup'). */ - validationEvent : "keyup", + validationEvent : 'keyup', /** * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true). */ @@ -67,25 +78,24 @@ Ext.form.Field = Ext.extend(Ext.BoxComponent, { * @cfg {String/Object} autoCreateA {@link Ext.DomHelper DomHelper} element spec, or true for a default * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:
- *{tag: "input", type: "text", size: "20", autocomplete: "off"}
+ * {tag: 'input', type: 'text', size: '20', autocomplete: 'off'}
*/
- defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "off"},
+ defaultAutoCreate : {tag: 'input', type: 'text', size: '20', autocomplete: 'off'},
/**
- * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field")
+ * @cfg {String} fieldClass The default CSS class for the field (defaults to 'x-form-field')
*/
- fieldClass : "x-form-field",
+ fieldClass : 'x-form-field',
/**
- * @cfg {String} msgTarget The location where error text should display. Should be one of the following values
- * (defaults to 'qtip'):
- *-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 -+ * @cfg {String} msgTarget
The location where the message text set through {@link #markInvalid} should display. + * Must be one of the following values:
+ *qtip
Display a quick tip containing the message when the user hovers over the field. This is the default.
+ * title
Display the message in a default browser title attribute popup.under
Add a block div beneath the field containing the error message.side
Add an error icon to the right of the field, displaying the message in a popup on hover.[element id]
Add the error message directly to the innerHTML of the specified element.The value that the Field had at the time it was last focused. This is the value that is passed + * to the {@link #change} event which is fired if the value has been changed when the Field is blurred.
+ *This will be undefined until the Field has been visited. Compare {@link #originalValue}.
+ * @type mixed + * @property startValue + */ this.startValue = this.getValue(); - this.fireEvent("focus", this); + this.fireEvent('focus', this); } }, @@ -325,18 +367,24 @@ var form = new Ext.form.FormPanel({ this.el.removeClass(this.focusClass); } this.hasFocus = false; - if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){ + if(this.validationEvent !== false && (this.validateOnBlur || this.validationEvent == 'blur')){ this.validate(); } var v = this.getValue(); if(String(v) !== String(this.startValue)){ this.fireEvent('change', this, v, this.startValue); } - this.fireEvent("blur", this); + this.fireEvent('blur', this); + this.postBlur(); }, + // private + postBlur : Ext.emptyFn, + /** - * Returns whether or not the field value is currently valid + * Returns whether or not the field value is currently valid by + * {@link #validateValue validating} the {@link #processValue processed value} + * of the field. Note: {@link #disabled} fields are ignored. * @param {Boolean} preventMark True to disable marking the field invalid * @return {Boolean} True if the value is valid, else false */ @@ -363,62 +411,128 @@ var form = new Ext.form.FormPanel({ return false; }, - // protected - should be overridden by subclasses if necessary to prepare raw values for validation + /** + * This method should only be overridden if necessary to prepare raw values + * for validation (see {@link #validate} and {@link #isValid}). This method + * is expected to return the processed value for the field which will + * be used for validation (see validateValue method). + * @param {Mixed} value + */ processValue : function(value){ return value; }, - // private - // Subclasses should provide the validation implementation by overriding this - validateValue : function(value){ - return true; + /** + * Uses getErrors to build an array of validation errors. If any errors are found, markInvalid is called + * with the first and false is returned, otherwise true is returned. Previously, subclasses were invited + * to provide an implementation of this to process validations - from 3.2 onwards getErrors should be + * overridden instead. + * @param {Mixed} The current value of the field + * @return {Boolean} True if all validations passed, false if one or more failed + */ + validateValue : function(value) { + //currently, we only show 1 error at a time for a field, so just use the first one + var error = this.getErrors(value)[0]; + + if (error == undefined) { + return true; + } else { + this.markInvalid(error); + return false; + } + }, + + /** + * Runs this field's validators and returns an array of error messages for any validation failures. + * This is called internally during validation and would not usually need to be used manually. + * Each subclass should override or augment the return value to provide their own errors + * @return {Array} All error messages for this field + */ + getErrors: function() { + return []; }, /** - * Mark this field as invalid, using {@link #msgTarget} to determine how to display the error and - * applying {@link #invalidClass} to the field's element. + * Gets the active error message for this field. + * @return {String} Returns the active error message on the field, if there is no error, an empty string is returned. + */ + getActiveError : function(){ + return this.activeError || ''; + }, + + /** + *Display an error message associated with this field, using {@link #msgTarget} to determine how to + * display the message and applying {@link #invalidClass} to the field's UI element.
+ *Note: this method does not cause the Field's {@link #validate} method to return false
+ * if the value does pass validation. So simply marking a Field as invalid will not prevent
+ * submission of forms submitted with the {@link Ext.form.Action.Submit#clientValidation} option set.
Validation
- *Field validation is processed in a particular order. If validation fails at any particular - * step the validation routine halts.
+ *The validation procedure is described in the documentation for {@link #validateValue}.
+ *Alter Validation Behavior
+ *Validation behavior for each field can be configured:
*If a field is configured with a {@link Ext.form.TextField#validator validator}
function,
- * it will be passed the current field value. The {@link Ext.form.TextField#validator validator}
- * function is expected to return boolean true if the value is valid or return a string to
- * represent the invalid message if invalid.
Basic validation is affected with the following configuration properties:
- *- * Validation Invalid Message - *- *{@link Ext.form.TextField#allowBlank allowBlank} {@link Ext.form.TextField#emptyText emptyText}
- *{@link Ext.form.TextField#minLength minLength} {@link Ext.form.TextField#minLengthText minLengthText}
- *{@link Ext.form.TextField#maxLength maxLength} {@link Ext.form.TextField#maxLengthText maxLengthText}
- *
Using VTypes offers a convenient way to reuse validation. If a field is configured with a
- * {@link Ext.form.TextField#vtype vtype}
, the corresponding {@link Ext.form.VTypes VTypes}
- * validation function will be used for validation. If invalid, either the field's
- * {@link Ext.form.TextField#vtypeText vtypeText}
or the VTypes vtype Text property will be
- * used for the invalid message. Keystrokes on the field will be filtered according to the VTypes
- * vtype Mask property.
Each field may also specify a {@link Ext.form.TextField#regex regex}
test.
- * The invalid message for this test is configured with
- * {@link Ext.form.TextField#regexText regexText}
.
Validation behavior for each field can be configured:
{@link Ext.form.TextField#invalidText invalidText}
: the default validation message to
* show if any validation step above does not provide a message when invalid{@link Ext.form.TextField#maskRe maskRe}
: filter out keystrokes before any validation occurs{@link Ext.form.Field#validateOnBlur validateOnBlur}
,
* {@link Ext.form.Field#validationDelay validationDelay}
, and
* {@link Ext.form.Field#validationEvent validationEvent}
: modify how/when validation is triggeredA custom validation function to be called during field validation ({@link #validateValue}) * (defaults to null). If specified, this function will be called first, allowing the - * developer to override the default validation process. This function will be passed the current - * field value and expected to return boolean true if the value is valid or a string - * error message if invalid. + * developer to override the default validation process.
+ *This function will be passed the following Parameters:
+ *value
: Mixed
+ * This function is to Return:
+ *true
: Boolean
+ * true
if the value is validmsg
: String
+ * Validates a value according to the field's validation rules and returns an array of errors + * for any failing validations. Validation rules are processed in the following order:
+ *A validator offers a way to customize and reuse a validation specification.
+ * If a field is configured with a {@link #validator}
+ * function, it will be passed the current field value. The {@link #validator}
+ * function is expected to return either:
+ *
If the {@link #validator}
has not halted validation,
+ * basic validation proceeds as follows:
{@link #allowBlank}
: (Invalid message =
+ * {@link #emptyText}
){@link #allowBlank}
, a
+ * blank field will cause validation to halt at this step and return
+ * Boolean true or false accordingly.
+ * {@link #minLength}
: (Invalid message =
+ * {@link #minLengthText}
){@link #minLength}
+ * specified, validation halts.
+ * {@link #maxLength}
: (Invalid message =
+ * {@link #maxLengthText}
){@link #maxLength}
+ * specified, validation halts.
+ * If none of the prior validation steps halts validation, a field
+ * configured with a {@link #vtype}
will utilize the
+ * corresponding {@link Ext.form.VTypes VTypes} validation function.
+ * If invalid, either the field's {@link #vtypeText}
or
+ * the VTypes vtype Text property will be used for the invalid message.
+ * Keystrokes on the field will be filtered according to the VTypes
+ * vtype Mask property.
If none of the prior validation steps halts validation, a field's
+ * configured {@link #regex}
test will be processed.
+ * The invalid message for this test is configured with
+ * {@link #regexText}
.
See also {@link #mode}.
*/ @@ -2281,8 +2583,9 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ /** * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this - * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'text' if - * {@link #transform transforming a select} a select). + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field1' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). *See also {@link #valueField}.
*Note: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not @@ -2290,8 +2593,9 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ /** * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this - * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'value' if - * {@link #transform transforming a select}). + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field2' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). *
Note: use of a valueField requires the user to make a selection in order for a value to be * mapped. See also {@link #hiddenName}, {@link #hiddenValue}, and {@link #displayField}.
*/ @@ -2342,8 +2646,10 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { */ shadow : 'sides', /** - * @cfg {String} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details - * on supported anchor positions (defaults to 'tl-bl?') + * @cfg {String/Array} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details + * on supported anchor positions and offsets. To specify x/y offsets as well, this value + * may be specified as an Array of {@link Ext.Element#alignTo} method arguments. + *[ 'tl-bl?', [6,0] ]
(defaults to 'tl-bl?')
*/
listAlign : 'tl-bl?',
/**
@@ -2374,6 +2680,12 @@ Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, {
* {@link Ext.form.TriggerField#editable editable} = false).
*/
minChars : 4,
+ /**
+ * @cfg {Boolean} autoSelect true to select the first result gathered by the data store (defaults
+ * to true). A false value would require a manual selection from the dropdown list to set the components value
+ * unless the value of ({@link #typeAheadDelay}) were true.
+ */
+ autoSelect : true,
/**
* @cfg {Boolean} typeAhead true to populate and autoselect the remainder of the text being
* typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
@@ -2482,6 +2794,19 @@ var combo = new Ext.form.ComboBox({
*/
lazyInit : true,
+ /**
+ * @cfg {Boolean} clearFilterOnReset true to clear any filters on the store (when in local mode) when reset is called
+ * (defaults to true)
+ */
+ clearFilterOnReset : true,
+
+ /**
+ * @cfg {Boolean} submitValue False to clear the name attribute on the field so that it is not submitted during a form post.
+ * If a hiddenName is specified, setting this to true will cause both the hidden field and the element to be submitted.
+ * Defaults to undefined.
+ */
+ submitValue: undefined,
+
/**
* The value of the match string used to filter the store. Delete this property to force a requery.
* Example use:
@@ -2529,6 +2854,7 @@ var combo = new Ext.form.ComboBox({
* @param {Ext.form.ComboBox} combo This combo box
*/
'collapse',
+
/**
* @event beforeselect
* Fires before a list item is selected. Return false to cancel the selection.
@@ -2588,10 +2914,8 @@ var combo = new Ext.form.ComboBox({
this.target = true;
this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate);
this.render(this.el.parentNode, s);
- Ext.removeNode(s); // remove it
- }else{
- Ext.removeNode(s); // remove it
}
+ Ext.removeNode(s);
}
//auto-configure store from local array data
else if(this.store){
@@ -2618,13 +2942,14 @@ var combo = new Ext.form.ComboBox({
// private
onRender : function(ct, position){
+ if(this.hiddenName && !Ext.isDefined(this.submitValue)){
+ this.submitValue = false;
+ }
Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
if(this.hiddenName){
this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
id: (this.hiddenId||this.hiddenName)}, 'before', true);
- // prevent input submission
- this.el.dom.removeAttribute('name');
}
if(Ext.isGecko){
this.el.dom.setAttribute('autocomplete', 'off');
@@ -2642,21 +2967,38 @@ var combo = new Ext.form.ComboBox({
Ext.form.ComboBox.superclass.initValue.call(this);
if(this.hiddenField){
this.hiddenField.value =
- Ext.isDefined(this.hiddenValue) ? this.hiddenValue :
- Ext.isDefined(this.value) ? this.value : '';
+ Ext.value(Ext.isDefined(this.hiddenValue) ? this.hiddenValue : this.value, '');
+ }
+ },
+
+ getParentZIndex : function(){
+ var zindex;
+ if (this.ownerCt){
+ this.findParentBy(function(ct){
+ zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
+ return !!zindex;
+ });
}
+ return zindex;
},
// private
initList : function(){
if(!this.list){
- var cls = 'x-combo-list';
+ var cls = 'x-combo-list',
+ listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
+ zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10);
+
+ if (!zindex) {
+ zindex = this.getParentZIndex();
+ }
this.list = new Ext.Layer({
- parentEl: this.getListParent(),
+ parentEl: listParent,
shadow: this.shadow,
cls: [cls, this.listClass].join(' '),
- constrain:false
+ constrain:false,
+ zindex: (zindex || 12000) + 5
});
var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
@@ -2727,10 +3069,15 @@ var combo = new Ext.form.ComboBox({
singleSelect: true,
selectedClass: this.selectedClass,
itemSelector: this.itemSelector || '.' + cls + '-item',
- emptyText: this.listEmptyText
+ emptyText: this.listEmptyText,
+ deferEmptyText: false
});
- this.mon(this.view, 'click', this.onViewClick, this);
+ this.mon(this.view, {
+ containerclick : this.onViewClick,
+ click : this.onViewClick,
+ scope :this
+ });
this.bindStore(this.store, true);
@@ -2803,17 +3150,21 @@ var menu = new Ext.menu.Menu({
// private
bindStore : function(store, initial){
if(this.store && !initial){
- this.store.un('beforeload', this.onBeforeLoad, this);
- this.store.un('load', this.onLoad, this);
- this.store.un('exception', this.collapse, this);
if(this.store !== store && this.store.autoDestroy){
this.store.destroy();
+ }else{
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('exception', this.collapse, this);
}
if(!store){
this.store = null;
if(this.view){
this.view.bindStore(null);
}
+ if(this.pageTb){
+ this.pageTb.bindStore(null);
+ }
}
}
if(store){
@@ -2838,10 +3189,34 @@ var menu = new Ext.menu.Menu({
}
},
+ reset : function(){
+ Ext.form.ComboBox.superclass.reset.call(this);
+ if(this.clearFilterOnReset && this.mode == 'local'){
+ this.store.clearFilter();
+ }
+ },
+
// private
initEvents : function(){
Ext.form.ComboBox.superclass.initEvents.call(this);
+ /**
+ * @property keyNav
+ * @type Ext.KeyNav
+ * A {@link Ext.KeyNav KeyNav} object which handles navigation keys for this ComboBox. This performs actions + * based on keystrokes typed when the input field is focused.
+ *After the ComboBox has been rendered, you may override existing navigation key functionality, + * or add your own based upon key names as specified in the {@link Ext.KeyNav KeyNav} class.
+ *The function is executed in the scope (this
reference of the ComboBox. Example:
+myCombo.keyNav.esc = function(e) { // Override ESC handling function
+ this.collapse(); // Standard behaviour of Ext's ComboBox.
+ this.setValue(this.startValue); // We reset to starting value on ESC
+};
+myCombo.keyNav.tab = function() { // Override TAB handling function
+ this.onViewClick(false); // Select the currently highlighted row
+};
+
+ */
this.keyNav = new Ext.KeyNav(this.el, {
"up" : function(e){
this.inKeyMode = true;
@@ -2859,8 +3234,6 @@ var menu = new Ext.menu.Menu({
"enter" : function(e){
this.onViewClick();
- this.delayedCheck = true;
- this.unsetDelayCheck.defer(10, this);
},
"esc" : function(e){
@@ -2868,20 +3241,31 @@ var menu = new Ext.menu.Menu({
},
"tab" : function(e){
- this.onViewClick(false);
+ if (this.forceSelection === true) {
+ this.collapse();
+ } else {
+ this.onViewClick(false);
+ }
return true;
},
scope : this,
- doRelay : function(foo, bar, hname){
+ doRelay : function(e, h, hname){
if(hname == 'down' || this.scope.isExpanded()){
- return Ext.KeyNav.prototype.doRelay.apply(this, arguments);
+ // this MUST be called before ComboBox#fireKey()
+ var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments);
+ if(!Ext.isIE && Ext.EventManager.useKeydown){
+ // call Combo#fireKey() for browsers which use keydown event (except IE)
+ this.scope.fireKey(e);
+ }
+ return relay;
}
return true;
},
- forceKeyDown : true
+ forceKeyDown : true,
+ defaultEventAction: 'stopEvent'
});
this.queryDelay = Math.max(this.queryDelay || 10,
this.mode == 'local' ? 10 : 250);
@@ -2889,11 +3273,12 @@ var menu = new Ext.menu.Menu({
if(this.typeAhead){
this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
}
- if(this.editable !== false && !this.enableKeyEvents){
+ if(!this.enableKeyEvents){
this.mon(this.el, 'keyup', this.onKeyUp, this);
}
},
+
// private
onDestroy : function(){
if (this.dqTask){
@@ -2907,34 +3292,29 @@ var menu = new Ext.menu.Menu({
this.pageTb,
this.list
);
+ Ext.destroyMembers(this, 'hiddenField');
Ext.form.ComboBox.superclass.onDestroy.call(this);
},
- // private
- unsetDelayCheck : function(){
- delete this.delayedCheck;
- },
-
// private
fireKey : function(e){
- var fn = function(ev){
- if (ev.isNavKeyPress() && !this.isExpanded() && !this.delayedCheck) {
- this.fireEvent("specialkey", this, ev);
- }
- };
- //For some reason I can't track down, the events fire in a different order in webkit.
- //Need a slight delay here
- if(this.inEditor && Ext.isWebKit && e.getKey() == e.TAB){
- fn.defer(10, this, [new Ext.EventObjectImpl(e)]);
- }else{
- fn.call(this, e);
+ if (!this.isExpanded()) {
+ Ext.form.ComboBox.superclass.fireKey.call(this, e);
}
},
// private
onResize : function(w, h){
Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
- if(this.list && !Ext.isDefined(this.listWidth)){
+ if(!isNaN(w) && this.isVisible() && this.list){
+ this.doResize(w);
+ }else{
+ this.bufferSize = w;
+ }
+ },
+
+ doResize: function(w){
+ if(!Ext.isDefined(this.listWidth)){
var lw = Math.max(w, this.minListWidth);
this.list.setWidth(lw);
this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
@@ -2973,26 +3353,29 @@ var menu = new Ext.menu.Menu({
if(!this.hasFocus){
return;
}
- if(this.store.getCount() > 0){
+ if(this.store.getCount() > 0 || this.listEmptyText){
this.expand();
this.restrictHeight();
if(this.lastQuery == this.allQuery){
if(this.editable){
this.el.dom.select();
}
- if(!this.selectByValue(this.value, true)){
+
+ if(this.autoSelect !== false && !this.selectByValue(this.value, true)){
this.select(0, true);
}
}else{
- this.selectNext();
+ if(this.autoSelect !== false){
+ this.selectNext();
+ }
if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
this.taTask.delay(this.typeAheadDelay);
}
}
}else{
- this.onEmptyResults();
+ this.collapse();
}
- //this.el.focus();
+
},
// private
@@ -3009,6 +3392,32 @@ var menu = new Ext.menu.Menu({
}
},
+ // private
+ assertValue : function(){
+ var val = this.getRawValue(),
+ rec = this.findRecord(this.displayField, val);
+
+ if(!rec && this.forceSelection){
+ if(val.length > 0 && val != this.emptyText){
+ this.el.dom.value = Ext.value(this.lastSelectionText, '');
+ this.applyEmptyText();
+ }else{
+ this.clearValue();
+ }
+ }else{
+ if(rec){
+ // onSelect may have already set the value and by doing so
+ // set the display field properly. Let's not wipe out the
+ // valueField here by just sending the displayField.
+ if (val == rec.get(this.displayField) && this.value == rec.get(this.valueField)){
+ return;
+ }
+ val = rec.get(this.valueField || this.displayField);
+ }
+ this.setValue(val);
+ }
+ },
+
// private
onSelect : function(record, index){
if(this.fireEvent('beforeselect', this, record, index) !== false){
@@ -3069,7 +3478,7 @@ var menu = new Ext.menu.Menu({
}
this.lastSelectionText = text;
if(this.hiddenField){
- this.hiddenField.value = v;
+ this.hiddenField.value = Ext.value(v, '');
}
Ext.form.ComboBox.superclass.setValue.call(this, text);
this.value = v;
@@ -3109,39 +3518,39 @@ var menu = new Ext.menu.Menu({
// private
onViewClick : function(doFocus){
- var index = this.view.getSelectedIndexes()[0];
- var r = this.store.getAt(index);
+ var index = this.view.getSelectedIndexes()[0],
+ s = this.store,
+ r = s.getAt(index);
if(r){
this.onSelect(r, index);
+ }else {
+ this.collapse();
}
if(doFocus !== false){
this.el.focus();
}
},
+
// private
restrictHeight : function(){
this.innerList.dom.style.height = '';
- var inner = this.innerList.dom;
- var pad = this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight;
- var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
- var ha = this.getPosition()[1]-Ext.getBody().getScroll().top;
- var hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height;
- var space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5;
+ var inner = this.innerList.dom,
+ pad = this.list.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight,
+ h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
+ ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
+ hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
+ space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5;
+
h = Math.min(h, space, this.maxHeight);
this.innerList.setHeight(h);
this.list.beginUpdate();
this.list.setHeight(h+pad);
- this.list.alignTo(this.wrap, this.listAlign);
+ this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
this.list.endUpdate();
},
- // private
- onEmptyResults : function(){
- this.collapse();
- },
-
/**
* Returns true if the dropdown list is expanded, else false.
*/
@@ -3184,6 +3593,7 @@ var menu = new Ext.menu.Menu({
this.innerList.scrollChildIntoView(el, false);
}
}
+
},
// private
@@ -3213,7 +3623,8 @@ var menu = new Ext.menu.Menu({
// private
onKeyUp : function(e){
var k = e.getKey();
- if(this.editable !== false && (k == e.BACKSPACE || !e.isSpecialKey())){
+ if(this.editable !== false && this.readOnly !== true && (k == e.BACKSPACE || !e.isSpecialKey())){
+
this.lastKey = k;
this.dqTask.delay(this.queryDelay);
}
@@ -3232,21 +3643,14 @@ var menu = new Ext.menu.Menu({
// private
beforeBlur : function(){
- var val = this.getRawValue();
- if(this.forceSelection){
- if(val.length > 0 && val != this.emptyText){
- this.el.dom.value = Ext.isDefined(this.lastSelectionText) ? this.lastSelectionText : '';
- this.applyEmptyText();
- }else{
- this.clearValue();
- }
- }else{
- var rec = this.findRecord(this.displayField, val);
- if(rec){
- val = rec.get(this.valueField || this.displayField);
- }
- this.setValue(val);
- }
+ this.assertValue();
+ },
+
+ // private
+ postBlur : function(){
+ Ext.form.ComboBox.superclass.postBlur.call(this);
+ this.collapse();
+ this.inKeyMode = false;
},
/**
@@ -3321,7 +3725,7 @@ var menu = new Ext.menu.Menu({
// private
collapseIf : function(e){
- if(!e.within(this.wrap) && !e.within(this.list)){
+ if(!this.isDestroyed && !e.within(this.wrap) && !e.within(this.list)){
this.collapse();
}
},
@@ -3333,12 +3737,37 @@ var menu = new Ext.menu.Menu({
if(this.isExpanded() || !this.hasFocus){
return;
}
- this.list.alignTo(this.wrap, this.listAlign);
+
+ if(this.title || this.pageSize){
+ this.assetHeight = 0;
+ if(this.title){
+ this.assetHeight += this.header.getHeight();
+ }
+ if(this.pageSize){
+ this.assetHeight += this.footer.getHeight();
+ }
+ }
+
+ if(this.bufferSize){
+ this.doResize(this.bufferSize);
+ delete this.bufferSize;
+ }
+ this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
+
+ // zindex can change, re-check it and set it if necessary
+ var listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
+ zindex = parseInt(Ext.fly(listParent).getStyle('z-index') ,10);
+ if (!zindex){
+ zindex = this.getParentZIndex();
+ }
+ if (zindex) {
+ this.list.setZIndex(zindex + 5);
+ }
this.list.show();
if(Ext.isGecko2){
this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
}
- Ext.getDoc().on({
+ this.mon(Ext.getDoc(), {
scope: this,
mousewheel: this.collapseIf,
mousedown: this.collapseIf
@@ -3353,7 +3782,7 @@ var menu = new Ext.menu.Menu({
// private
// Implements the default empty TriggerField.onTriggerClick function
onTriggerClick : function(){
- if(this.disabled){
+ if(this.readOnly || this.disabled){
return;
}
if(this.isExpanded()){
@@ -3385,7 +3814,8 @@ var menu = new Ext.menu.Menu({
*/
});
-Ext.reg('combo', Ext.form.ComboBox);/**
+Ext.reg('combo', Ext.form.ComboBox);
+/**
* @class Ext.form.Checkbox
* @extends Ext.form.Field
* Single checkbox field. Can be used as a direct replacement for traditional checkbox fields.
@@ -3407,6 +3837,10 @@ Ext.form.Checkbox = Ext.extend(Ext.form.Field, {
* @cfg {Boolean} checked true if the checkbox should render initially checked (defaults to false)
*/
checked : false,
+ /**
+ * @cfg {String} boxLabel The text that appears beside the checkbox
+ */
+ boxLabel: ' ',
/**
* @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
* {tag: 'input', type: 'checkbox', autocomplete: 'off'})
@@ -3419,7 +3853,7 @@ Ext.form.Checkbox = Ext.extend(Ext.form.Field, {
* @cfg {String} inputValue The value that should go into the generated input element's value attribute
*/
/**
- * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
+ * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
* handling the check event). The handler is passed the following parameters:
* The controls will be rendered one per column on one row and the width * of each column will be evenly distributed based on the width of the overall field container. This is the default.
If you specific a number (e.g., 3) that number of columns will be + *
If you specific a number (e.g., 3) that number of columns will be * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.
You can also specify an array of column widths, mixing integer
* (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will
@@ -3601,7 +4033,7 @@ Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
*/
columns : 'auto',
/**
- * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
+ * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
* top to bottom before starting on the next column. The number of controls in each column will be automatically
* calculated to keep columns as even as possible. The default value is false, so that controls will be added
* to columns one at a time, completely filling each row left to right before starting on the next row.
@@ -3613,17 +4045,17 @@ Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
*/
allowBlank : true,
/**
- * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
+ * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
* select at least one item in this group")
*/
blankText : "You must select at least one item in this group",
-
+
// private
defaultType : 'checkbox',
-
+
// private
groupCls : 'x-form-check-group',
-
+
// private
initComponent: function(){
this.addEvents(
@@ -3634,33 +4066,37 @@ Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
* @param {Array} checked An array containing the checked boxes.
*/
'change'
- );
+ );
+ this.on('change', this.validate, this);
Ext.form.CheckboxGroup.superclass.initComponent.call(this);
},
-
+
// private
onRender : function(ct, position){
if(!this.el){
var panelCfg = {
+ autoEl: {
+ id: this.id
+ },
cls: this.groupCls,
layout: 'column',
- border: false,
- renderTo: ct
+ renderTo: ct,
+ bufferResize: false // Default this to false, since it doesn't really have a proper ownerCt.
};
var colCfg = {
+ xtype: 'container',
defaultType: this.defaultType,
layout: 'form',
- border: false,
defaults: {
hideLabel: true,
anchor: '100%'
}
};
-
+
if(this.items[0].items){
-
+
// The container has standard ColumnLayout configs, so pass them in directly
-
+
Ext.apply(panelCfg, {
layoutConfig: {columns: this.items.length},
defaults: this.defaults,
@@ -3669,14 +4105,14 @@ Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
for(var i=0, len=this.items.length; i Encapsulates the DOM <form> element at the heart of the {@link Ext.form.FormPanel FormPanel} class, and provides
- * input field management, validation, submission, and form loading services. By default, Ext Forms are submitted through Ajax, using an instance of {@link Ext.form.Action.Submit}.
- * To enable normal browser submission of an Ext Form, use the {@link #standardSubmit} config option. File Uploads {@link #fileUpload File uploads} are not performed using Ajax submission, that
- * is they are not performed using XMLHttpRequests. Instead the form is submitted in the standard
- * manner with the DOM <form> element temporarily modified to have its
+/**
+ * @class Ext.form.Hidden
+ * @extends Ext.form.Field
+ * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
+ * @constructor
+ * Create a new Hidden field.
+ * @param {Object} config Configuration options
+ * @xtype hidden
+ */
+Ext.form.Hidden = Ext.extend(Ext.form.Field, {
+ // private
+ inputType : 'hidden',
+
+ // private
+ onRender : function(){
+ Ext.form.Hidden.superclass.onRender.apply(this, arguments);
+ },
+
+ // private
+ initEvents : function(){
+ this.originalValue = this.getValue();
+ },
+
+ // These are all private overrides
+ setSize : Ext.emptyFn,
+ setWidth : Ext.emptyFn,
+ setHeight : Ext.emptyFn,
+ setPosition : Ext.emptyFn,
+ setPagePosition : Ext.emptyFn,
+ markInvalid : Ext.emptyFn,
+ clearInvalid : Ext.emptyFn
+});
+Ext.reg('hidden', Ext.form.Hidden);/**
+ * @class Ext.form.BasicForm
+ * @extends Ext.util.Observable
+ * Encapsulates the DOM <form> element at the heart of the {@link Ext.form.FormPanel FormPanel} class, and provides
+ * input field management, validation, submission, and form loading services. By default, Ext Forms are submitted through Ajax, using an instance of {@link Ext.form.Action.Submit}.
+ * To enable normal browser submission of an Ext Form, use the {@link #standardSubmit} config option. File Uploads {@link #fileUpload File uploads} are not performed using Ajax submission, that
+ * is they are not performed using XMLHttpRequests. Instead the form is submitted in the standard
+ * manner with the DOM <form> element temporarily modified to have its
* target set to refer
* to a dynamically generated, hidden <iframe> which is inserted into the document
* but removed after the return data has been gathered. Note: When using standardSubmit, the options to {@link #submit} are ignored because Ext's
- * Ajax infrastracture is bypassed. To pass extra parameters (baseParams and params), you will need to
- * create hidden fields within the form. The url config option is also bypassed, so set the action as well: If set to true, standard HTML form submits are used instead
+ * of XHR (Ajax) style form submissions. Defaults to false. Note: When using Standard form container. Layout By default, FormPanel is configured with layout:'form' to use an {@link Ext.layout.FormLayout}
* layout manager, which styles and renders fields and labels correctly. When nesting additional Containers
* within a FormPanel, you should ensure that any descendant Containers which host input Fields use the
* {@link Ext.layout.FormLayout} layout manager. BasicForm Although not listed as configuration options of FormPanel, the FormPanel class accepts all
* of the config options required to configure its internal {@link Ext.form.BasicForm} for:
@@ -4932,11 +5921,11 @@ Ext.BasicForm = Ext.form.BasicForm;/**
*
// call with name and value
myCheckboxGroup.setValue('cb-col-1', true);
-// call with an array of boolean values
+// call with an array of boolean values
myCheckboxGroup.setValue([true, false, false]);
// call with an object literal specifying item:value pairs
myCheckboxGroup.setValue({
@@ -3857,47 +4343,71 @@ myCheckboxGroup.setValue('cb-col-1,cb-col-3');
* @param {Boolean} value (optional) The value to set the item.
* @return {Ext.form.CheckboxGroup} this
*/
- setValue : function(id, value){
+ setValue: function(){
if(this.rendered){
- if(arguments.length == 1){
- if(Ext.isArray(id)){
- //an array of boolean values
- Ext.each(id, function(val, idx){
+ this.onSetValue.apply(this, arguments);
+ }else{
+ this.buffered = true;
+ this.value = arguments;
+ }
+ return this;
+ },
+
+ /**
+ * @private
+ * Sets the values of one or more of the items within the CheckboxGroup
+ * @param {String|Array|Object} id Can take multiple forms. Can be optionally:
+ *
+ *
+ * @param {String} value The value to set the field to if the first argument was a string
+ */
+ onSetValue: function(id, value){
+ if(arguments.length == 1){
+ if(Ext.isArray(id)){
+ Ext.each(id, function(val, idx){
+ if (Ext.isObject(val) && val.setValue){ // array of checkbox components to be checked
+ val.setValue(true);
+ if (this.resetOriginal === true) {
+ val.originalValue = val.getValue();
+ }
+ } else { // an array of boolean values
var item = this.items.itemAt(idx);
if(item){
item.setValue(val);
}
- }, this);
- }else if(Ext.isObject(id)){
- //set of name/value pairs
- for(var i in id){
- var f = this.getBox(i);
- if(f){
- f.setValue(id[i]);
- }
}
- }else{
- this.setValueForItem(id);
+ }, this);
+ }else if(Ext.isObject(id)){
+ // set of name/value pairs
+ for(var i in id){
+ var f = this.getBox(i);
+ if(f){
+ f.setValue(id[i]);
+ }
}
}else{
- var f = this.getBox(id);
- if(f){
- f.setValue(value);
- }
+ this.setValueForItem(id);
}
}else{
- this.values = arguments;
+ var f = this.getBox(id);
+ if(f){
+ f.setValue(value);
+ }
}
- return this;
},
-
+
// private
- onDestroy: function(){
+ beforeDestroy: function(){
Ext.destroy(this.panel);
- Ext.form.CheckboxGroup.superclass.onDestroy.call(this);
+ Ext.form.CheckboxGroup.superclass.beforeDestroy.call(this);
},
-
+
setValueForItem : function(val){
val = String(val).split(',');
this.eachItem(function(item){
@@ -3906,7 +4416,7 @@ myCheckboxGroup.setValue('cb-col-1,cb-col-3');
}
});
},
-
+
// private
getBox : function(id){
var box = null;
@@ -3918,7 +4428,7 @@ myCheckboxGroup.setValue('cb-col-1,cb-col-3');
});
return box;
},
-
+
/**
* Gets an array of the selected {@link Ext.form.Checkbox} in the group.
* @return {Array} An array of the selected checkboxes.
@@ -3932,195 +4442,611 @@ myCheckboxGroup.setValue('cb-col-1,cb-col-3');
});
return out;
},
-
- // private
- eachItem: function(fn){
+
+ /**
+ * @private
+ * Convenience function which passes the given function to every item in the composite
+ * @param {Function} fn The function to call
+ * @param {Object} scope Optional scope object
+ */
+ eachItem: function(fn, scope) {
if(this.items && this.items.each){
- this.items.each(fn, this);
+ this.items.each(fn, scope || this);
}
},
-
+
/**
* @cfg {String} name
* @hide
*/
- /**
- * @method initValue
- * @hide
- */
- initValue : Ext.emptyFn,
- /**
- * @method getValue
- * @hide
- */
- getValue : Ext.emptyFn,
+
/**
* @method getRawValue
* @hide
*/
getRawValue : Ext.emptyFn,
-
+
/**
* @method setRawValue
* @hide
*/
setRawValue : Ext.emptyFn
-
+
});
Ext.reg('checkboxgroup', Ext.form.CheckboxGroup);
/**
- * @class Ext.form.Radio
- * @extends Ext.form.Checkbox
- * Single radio field. Same as Checkbox, but provided as a convenience for automatically setting the input type.
- * Radio grouping is handled automatically by the browser if you give each radio in a group the same name.
- * @constructor
- * Creates a new Radio
- * @param {Object} config Configuration options
- * @xtype radio
+ * @class Ext.form.CompositeField
+ * @extends Ext.form.Field
+ * Composite field allowing a number of form Fields to be rendered on the same row. The fields are rendered
+ * using an hbox layout internally, so all of the normal HBox layout config items are available. Example usage:
+ *
+{
+ xtype: 'compositefield',
+ labelWidth: 120
+ items: [
+ {
+ xtype : 'textfield',
+ fieldLabel: 'Title',
+ width : 20
+ },
+ {
+ xtype : 'textfield',
+ fieldLabel: 'First',
+ flex : 1
+ },
+ {
+ xtype : 'textfield',
+ fieldLabel: 'Last',
+ flex : 1
+ }
+ ]
+}
+ *
+ * In the example above the composite's fieldLabel will be set to 'Title, First, Last' as it groups the fieldLabels
+ * of each of its children. This can be overridden by setting a fieldLabel on the compositefield itself:
+ *
+{
+ xtype: 'compositefield',
+ fieldLabel: 'Custom label',
+ items: [...]
+}
+ *
+ * Any Ext.form.* component can be placed inside a composite field.
*/
-Ext.form.Radio = Ext.extend(Ext.form.Checkbox, {
- inputType: 'radio',
+Ext.form.CompositeField = Ext.extend(Ext.form.Field, {
/**
- * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
- * @method
+ * @property defaultMargins
+ * @type String
+ * The margins to apply by default to each field in the composite
*/
- markInvalid : Ext.emptyFn,
+ defaultMargins: '0 5 0 0',
+
/**
- * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
- * @method
+ * @property skipLastItemMargin
+ * @type Boolean
+ * If true, the defaultMargins are not applied to the last item in the composite field set (defaults to true)
*/
- clearInvalid : Ext.emptyFn,
+ skipLastItemMargin: true,
/**
- * If this radio is part of a group, it will return the selected value
- * @return {String}
+ * @property isComposite
+ * @type Boolean
+ * Signifies that this is a Composite field
*/
- getGroupValue : function(){
- var p = this.el.up('form') || Ext.getBody();
- var c = p.child('input[name='+this.el.dom.name+']:checked', true);
- return c ? c.value : null;
- },
-
- // private
- onClick : function(){
- if(this.el.dom.checked != this.checked){
- var els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
- els.each(function(el){
- if(el.dom.id == this.id){
- this.setValue(true);
- }else{
- Ext.getCmp(el.dom.id).setValue(false);
- }
- }, this);
- }
- },
+ isComposite: true,
/**
- * Sets either the checked/unchecked status of this Radio, or, if a string value
- * is passed, checks a sibling Radio of the same name whose value is the value specified.
- * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
- * @return {Ext.form.Field} this
+ * @property combineErrors
+ * @type Boolean
+ * True to combine errors from the individual fields into a single error message at the CompositeField level (defaults to true)
*/
- setValue : function(v){
- if (typeof v == 'boolean') {
- Ext.form.Radio.superclass.setValue.call(this, v);
- } else {
- var r = this.getCheckEl().child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
- if(r){
- Ext.getCmp(r.id).setValue(true);
+ combineErrors: true,
+
+ //inherit docs
+ //Builds the composite field label
+ initComponent: function() {
+ var labels = [],
+ items = this.items,
+ item;
+
+ for (var i=0, j = items.length; i < j; i++) {
+ item = items[i];
+
+ labels.push(item.fieldLabel);
+
+ //apply any defaults
+ Ext.apply(item, this.defaults);
+
+ //apply default margins to each item except the last
+ if (!(i == j - 1 && this.skipLastItemMargin)) {
+ Ext.applyIf(item, {margins: this.defaultMargins});
}
}
- return this;
+
+ this.fieldLabel = this.fieldLabel || this.buildLabel(labels);
+
+ /**
+ * @property fieldErrors
+ * @type Ext.util.MixedCollection
+ * MixedCollection of current errors on the Composite's subfields. This is used internally to track when
+ * to show and hide error messages at the Composite level. Listeners are attached to the MixedCollection's
+ * add, remove and replace events to update the error icon in the UI as errors are added or removed.
+ */
+ this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
+ return item.field;
+ });
+
+ this.fieldErrors.on({
+ scope : this,
+ add : this.updateInvalidMark,
+ remove : this.updateInvalidMark,
+ replace: this.updateInvalidMark
+ });
+
+ Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
},
-
- // private
- getCheckEl: function(){
- if(this.inGroup){
- return this.el.up('.x-form-radio-group')
- }
- return this.el.up('form') || Ext.getBody();
- }
-});
-Ext.reg('radio', Ext.form.Radio);
-/**
- * @class Ext.form.RadioGroup
- * @extends Ext.form.CheckboxGroup
- * A grouping container for {@link Ext.form.Radio} controls.
- * @constructor
- * Creates a new RadioGroup
- * @param {Object} config Configuration options
- * @xtype radiogroup
- */
-Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
+
/**
- * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
- * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
- * be used as the error text.
+ * @private
+ * Creates an internal container using hbox and renders the fields to it
*/
- allowBlank : true,
+ onRender: function(ct, position) {
+ if (!this.el) {
+ /**
+ * @property innerCt
+ * @type Ext.Container
+ * A container configured with hbox layout which is responsible for laying out the subfields
+ */
+ var innerCt = this.innerCt = new Ext.Container({
+ layout : 'hbox',
+ renderTo: ct,
+ items : this.items,
+ cls : 'x-form-composite',
+ defaultMargins: '0 3 0 0'
+ });
+
+ this.el = innerCt.getEl();
+
+ var fields = innerCt.findBy(function(c) {
+ return c.isFormField;
+ }, this);
+
+ /**
+ * @property items
+ * @type Ext.util.MixedCollection
+ * Internal collection of all of the subfields in this Composite
+ */
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+
+ //if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
+ //methods of each subfield and show them at the Composite level instead
+ if (this.combineErrors) {
+ this.eachItem(function(field) {
+ Ext.apply(field, {
+ markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
+ clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
+ });
+ });
+ }
+
+ //set the label 'for' to the first item
+ var l = this.el.parent().parent().child('label', true);
+ if (l) {
+ l.setAttribute('for', this.items.items[0].id);
+ }
+ }
+
+ Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
+ },
+
/**
- * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
- * (defaults to 'You must select one item in this group')
+ * Called if combineErrors is true and a subfield's markInvalid method is called.
+ * By default this just adds the subfield's error to the internal fieldErrors MixedCollection
+ * @param {Ext.form.Field} field The field that was marked invalid
+ * @param {String} message The error message
*/
- blankText : 'You must select one item in this group',
-
- // private
- defaultType : 'radio',
-
- // private
- groupCls : 'x-form-radio-group',
-
+ onFieldMarkInvalid: function(field, message) {
+ var name = field.getName(),
+ error = {field: name, error: message};
+
+ this.fieldErrors.replace(name, error);
+
+ field.el.addClass(field.invalidClass);
+ },
+
/**
- * @event change
- * Fires when the state of a child radio changes.
- * @param {Ext.form.RadioGroup} this
- * @param {Ext.form.Radio} checked The checked radio
+ * Called if combineErrors is true and a subfield's clearInvalid method is called.
+ * By default this just updates the internal fieldErrors MixedCollection.
+ * @param {Ext.form.Field} field The field that was marked invalid
*/
-
+ onFieldClearInvalid: function(field) {
+ this.fieldErrors.removeKey(field.getName());
+
+ field.el.removeClass(field.invalidClass);
+ },
+
/**
- * Gets the selected {@link Ext.form.Radio} in the group, if it exists.
- * @return {Ext.form.Radio} The selected radio.
+ * @private
+ * Called after a subfield is marked valid or invalid, this checks to see if any of the subfields are
+ * currently invalid. If any subfields are invalid it builds a combined error message marks the composite
+ * invalid, otherwise clearInvalid is called
*/
- getValue : function(){
- var out = null;
- this.eachItem(function(item){
- if(item.checked){
- out = item;
- return false;
+ updateInvalidMark: function() {
+ var ieStrict = Ext.isIE6 && Ext.isStrict;
+
+ if (this.fieldErrors.length == 0) {
+ this.clearInvalid();
+
+ //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
+ if (ieStrict) {
+ this.clearInvalid.defer(50, this);
}
- });
- return out;
+ } else {
+ var message = this.buildCombinedErrorMessage(this.fieldErrors.items);
+
+ this.sortErrors();
+ this.markInvalid(message);
+
+ //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
+ if (ieStrict) {
+ this.markInvalid(message);
+ }
+ }
},
-
+
+ /**
+ * Performs validation checks on each subfield and returns false if any of them fail validation.
+ * @return {Boolean} False if any subfield failed validation
+ */
+ validateValue: function() {
+ var valid = true;
+
+ this.eachItem(function(field) {
+ if (!field.isValid()) valid = false;
+ });
+
+ return valid;
+ },
+
+ /**
+ * Takes an object containing error messages for contained fields, returning a combined error
+ * string (defaults to just placing each item on a new line). This can be overridden to provide
+ * custom combined error message handling.
+ * @param {Array} errors Array of errors in format: [{field: 'title', error: 'some error'}]
+ * @return {String} The combined error message
+ */
+ buildCombinedErrorMessage: function(errors) {
+ var combined = [],
+ error;
+
+ for (var i = 0, j = errors.length; i < j; i++) {
+ error = errors[i];
+
+ combined.push(String.format("{0}: {1}", error.field, error.error));
+ }
+
+ return combined.join("
");
+ },
+
+ /**
+ * Sorts the internal fieldErrors MixedCollection by the order in which the fields are defined.
+ * This is called before displaying errors to ensure that the errors are presented in the expected order.
+ * This function can be overridden to provide a custom sorting order if needed.
+ */
+ sortErrors: function() {
+ var fields = this.items;
+
+ this.fieldErrors.sort("ASC", function(a, b) {
+ var findByName = function(key) {
+ return function(field) {
+ return field.getName() == key;
+ };
+ };
+
+ var aIndex = fields.findIndexBy(findByName(a.field)),
+ bIndex = fields.findIndexBy(findByName(b.field));
+
+ return aIndex < bIndex ? -1 : 1;
+ });
+ },
+
+ /**
+ * Resets each field in the composite to their previous value
+ */
+ reset: function() {
+ this.eachItem(function(item) {
+ item.reset();
+ });
+
+ // Defer the clearInvalid so if BaseForm's collection is being iterated it will be called AFTER it is complete.
+ // Important because reset is being called on both the group and the individual items.
+ (function() {
+ this.clearInvalid();
+ }).defer(50, this);
+ },
+
+ /**
+ * Calls clearInvalid on all child fields. This is a convenience function and should not often need to be called
+ * as fields usually take care of clearing themselves
+ */
+ clearInvalidChildren: function() {
+ this.eachItem(function(item) {
+ item.clearInvalid();
+ });
+ },
+
+ /**
+ * Builds a label string from an array of subfield labels.
+ * By default this just joins the labels together with a comma
+ * @param {Array} segments Array of each of the labels in the composite field's subfields
+ * @return {String} The built label
+ */
+ buildLabel: function(segments) {
+ return segments.join(", ");
+ },
+
+ /**
+ * Checks each field in the composite and returns true if any is dirty
+ * @return {Boolean} True if any field is dirty
+ */
+ isDirty: function(){
+ //override the behaviour to check sub items.
+ if (this.disabled || !this.rendered) {
+ return false;
+ }
+
+ var dirty = false;
+ this.eachItem(function(item){
+ if(item.isDirty()){
+ dirty = true;
+ return false;
+ }
+ });
+ return dirty;
+ },
+
+ /**
+ * @private
+ * Convenience function which passes the given function to every item in the composite
+ * @param {Function} fn The function to call
+ * @param {Object} scope Optional scope object
+ */
+ eachItem: function(fn, scope) {
+ if(this.items && this.items.each){
+ this.items.each(fn, scope || this);
+ }
+ },
+
+ /**
+ * @private
+ * Passes the resize call through to the inner panel
+ */
+ onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
+ var innerCt = this.innerCt;
+
+ if (this.rendered && innerCt.rendered) {
+ innerCt.setSize(adjWidth, adjHeight);
+ }
+
+ Ext.form.CompositeField.superclass.onResize.apply(this, arguments);
+ },
+
+ /**
+ * @private
+ * Forces the internal container to be laid out again
+ */
+ doLayout: function(shallow, force) {
+ if (this.rendered) {
+ var innerCt = this.innerCt;
+
+ innerCt.forceLayout = this.ownerCt.forceLayout;
+ innerCt.doLayout(shallow, force);
+ }
+ },
+
+ /**
+ * @private
+ */
+ beforeDestroy: function(){
+ Ext.destroy(this.innerCt);
+
+ Ext.form.CompositeField.superclass.beforeDestroy.call(this);
+ },
+
+ //override the behaviour to check sub items.
+ setReadOnly : function(readOnly) {
+ readOnly = readOnly || true;
+
+ if(this.rendered){
+ this.eachItem(function(item){
+ item.setReadOnly(readOnly);
+ });
+ }
+ this.readOnly = readOnly;
+ },
+
+ onShow : function() {
+ Ext.form.CompositeField.superclass.onShow.call(this);
+ this.doLayout();
+ },
+
+ //override the behaviour to check sub items.
+ onDisable : function(){
+ this.eachItem(function(item){
+ item.disable();
+ });
+ },
+
+ //override the behaviour to check sub items.
+ onEnable : function(){
+ this.eachItem(function(item){
+ item.enable();
+ });
+ }
+});
+
+Ext.reg('compositefield', Ext.form.CompositeField);
+/**
+ * @class Ext.form.Radio
+ * @extends Ext.form.Checkbox
+ * Single radio field. Same as Checkbox, but provided as a convenience for automatically setting the input type.
+ * Radio grouping is handled automatically by the browser if you give each radio in a group the same name.
+ * @constructor
+ * Creates a new Radio
+ * @param {Object} config Configuration options
+ * @xtype radio
+ */
+Ext.form.Radio = Ext.extend(Ext.form.Checkbox, {
+ inputType: 'radio',
+
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ markInvalid : Ext.emptyFn,
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ clearInvalid : Ext.emptyFn,
+
+ /**
+ * If this radio is part of a group, it will return the selected value
+ * @return {String}
+ */
+ getGroupValue : function(){
+ var p = this.el.up('form') || Ext.getBody();
+ var c = p.child('input[name='+this.el.dom.name+']:checked', true);
+ return c ? c.value : null;
+ },
+
+ // private
+ onClick : function(){
+ if(this.el.dom.checked != this.checked){
+ var els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
+ els.each(function(el){
+ if(el.dom.id == this.id){
+ this.setValue(true);
+ }else{
+ Ext.getCmp(el.dom.id).setValue(false);
+ }
+ }, this);
+ }
+ },
+
+ /**
+ * Sets either the checked/unchecked status of this Radio, or, if a string value
+ * is passed, checks a sibling Radio of the same name whose value is the value specified.
+ * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
+ * @return {Ext.form.Field} this
+ */
+ setValue : function(v){
+ if (typeof v == 'boolean') {
+ Ext.form.Radio.superclass.setValue.call(this, v);
+ } else if (this.rendered) {
+ var r = this.getCheckEl().child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
+ if(r){
+ Ext.getCmp(r.id).setValue(true);
+ }
+ }
+ return this;
+ },
+
+ // private
+ getCheckEl: function(){
+ if(this.inGroup){
+ return this.el.up('.x-form-radio-group')
+ }
+ return this.el.up('form') || Ext.getBody();
+ }
+});
+Ext.reg('radio', Ext.form.Radio);
+/**
+ * @class Ext.form.RadioGroup
+ * @extends Ext.form.CheckboxGroup
+ * A grouping container for {@link Ext.form.Radio} controls.
+ * @constructor
+ * Creates a new RadioGroup
+ * @param {Object} config Configuration options
+ * @xtype radiogroup
+ */
+Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
+ /**
+ * @cfg {Array} items An Array of {@link Ext.form.Radio Radio}s or Radio config objects
+ * to arrange in the group.
+ */
+ /**
+ * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
+ * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
+ * be used as the error text.
+ */
+ allowBlank : true,
+ /**
+ * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
+ * (defaults to 'You must select one item in this group')
+ */
+ blankText : 'You must select one item in this group',
+
+ // private
+ defaultType : 'radio',
+
+ // private
+ groupCls : 'x-form-radio-group',
+
+ /**
+ * @event change
+ * Fires when the state of a child radio changes.
+ * @param {Ext.form.RadioGroup} this
+ * @param {Ext.form.Radio} checked The checked radio
+ */
+
+ /**
+ * Gets the selected {@link Ext.form.Radio} in the group, if it exists.
+ * @return {Ext.form.Radio} The selected radio.
+ */
+ getValue : function(){
+ var out = null;
+ this.eachItem(function(item){
+ if(item.checked){
+ out = item;
+ return false;
+ }
+ });
+ return out;
+ },
+
/**
* Sets the checked radio in the group.
* @param {String/Ext.form.Radio} id The radio to check.
* @param {Boolean} value The value to set the radio.
* @return {Ext.form.RadioGroup} this
*/
- setValue : function(id, value){
- if(this.rendered){
- if(arguments.length > 1){
- var f = this.getBox(id);
- if(f){
- f.setValue(value);
- if(f.checked){
- this.eachItem(function(item){
- if (item !== f){
- item.setValue(false);
- }
- });
- }
+ onSetValue : function(id, value){
+ if(arguments.length > 1){
+ var f = this.getBox(id);
+ if(f){
+ f.setValue(value);
+ if(f.checked){
+ this.eachItem(function(item){
+ if (item !== f){
+ item.setValue(false);
+ }
+ });
}
- }else{
- this.setValueForItem(id);
}
}else{
- this.values = arguments;
+ this.setValueForItem(id);
}
- return this;
+ },
+
+ setValueForItem : function(val){
+ val = String(val).split(',')[0];
+ this.eachItem(function(item){
+ item.setValue(val == item.inputValue);
+ });
},
// private
@@ -4154,49 +5080,49 @@ Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
});
Ext.reg('radiogroup', Ext.form.RadioGroup);
-/**
- * @class Ext.form.Hidden
- * @extends Ext.form.Field
- * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
- * @constructor
- * Create a new Hidden field.
- * @param {Object} config Configuration options
- * @xtype hidden
- */
-Ext.form.Hidden = Ext.extend(Ext.form.Field, {
- // private
- inputType : 'hidden',
-
- // private
- onRender : function(){
- Ext.form.Hidden.superclass.onRender.apply(this, arguments);
- },
-
- // private
- initEvents : function(){
- this.originalValue = this.getValue();
- },
-
- // These are all private overrides
- setSize : Ext.emptyFn,
- setWidth : Ext.emptyFn,
- setHeight : Ext.emptyFn,
- setPosition : Ext.emptyFn,
- setPagePosition : Ext.emptyFn,
- markInvalid : Ext.emptyFn,
- clearInvalid : Ext.emptyFn
-});
-Ext.reg('hidden', Ext.form.Hidden);/**
- * @class Ext.form.BasicForm
- * @extends Ext.util.Observable
- *
- *
- * An example encapsulating the above:
+ * @cfg {Boolean} standardSubmit
+ *
-PANEL.getForm().getEl().dom.action = 'URL'
- *
standardSubmit
, the
+ * options
to {@link #submit}
are ignored because
+ * Ext's Ajax infrastracture is bypassed. To pass extra parameters (e.g.
+ * baseParams
and params
), utilize hidden fields
+ * to submit extra data, for example:
new Ext.FormPanel({
standardSubmit: true,
baseParams: {
foo: 'bar'
},
- url: 'myProcess.php',
+ {@link url}: 'myProcess.php',
items: [{
xtype: 'textfield',
name: 'userName'
@@ -4401,21 +5331,25 @@ new Ext.FormPanel({
buttons: [{
text: 'Save',
handler: function(){
- var O = this.ownerCt;
- if (O.getForm().isValid()) {
- if (O.url)
- O.getForm().getEl().dom.action = O.url;
- if (O.baseParams) {
- for (i in O.baseParams) {
- O.add({
+ var fp = this.ownerCt.ownerCt,
+ form = fp.getForm();
+ if (form.isValid()) {
+ // check if there are baseParams and if
+ // hiddent items have been added already
+ if (fp.baseParams && !fp.paramsAdded) {
+ // add hidden items for all baseParams
+ for (i in fp.baseParams) {
+ fp.add({
xtype: 'hidden',
name: i,
- value: O.baseParams[i]
- })
+ value: fp.baseParams[i]
+ });
}
- O.doLayout();
+ fp.doLayout();
+ // set a custom flag to prevent re-adding
+ fp.paramsAdded = true;
}
- O.getForm().submit();
+ form.{@link #submit}();
}
}
}]
@@ -4452,15 +5386,20 @@ new Ext.FormPanel({
e.stopEvent();
},
- // private
- destroy: function() {
- this.items.each(function(f){
- Ext.destroy(f);
- });
- if(this.el){
- this.el.removeAllListeners();
- this.el.remove();
+ /**
+ * Destroys this object.
+ * @private
+ * @param {Boolean} bound true if the object is bound to a form panel. If this is the case
+ * the FormPanel will take care of destroying certain things, so we're just doubling up.
+ */
+ destroy: function(bound){
+ if(bound !== true){
+ this.items.each(function(f){
+ Ext.destroy(f);
+ });
+ Ext.destroy(this.el);
}
+ this.items.clear();
this.purgeListeners();
},
@@ -4609,10 +5548,15 @@ myFormPanel.getForm().submit({
* @return {BasicForm} this
*/
submit : function(options){
+ options = options || {};
if(this.standardSubmit){
- var v = this.isValid();
+ var v = options.clientValidation === false || this.isValid();
if(v){
- this.el.dom.submit();
+ var el = this.el.dom;
+ if(this.url && Ext.isEmpty(el.action)){
+ el.action = this.url;
+ }
+ el.submit();
}
return v;
}
@@ -4664,6 +5608,12 @@ myFormPanel.getForm().submit({
// private
beforeAction : function(action){
+ // Call HtmlEditor's syncValue before actions
+ this.items.each(function(f){
+ if(f.isFormField && f.syncValue){
+ f.syncValue();
+ }
+ });
var o = action.options;
if(o.waitMsg){
if(this.waitMsgTarget === true){
@@ -4672,7 +5622,7 @@ myFormPanel.getForm().submit({
this.waitMsgTarget = Ext.get(this.waitMsgTarget);
this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
}else{
- Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle || 'Please Wait...');
+ Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle);
}
}
},
@@ -4709,15 +5659,23 @@ myFormPanel.getForm().submit({
* {@link Ext.grid.Column#dataIndex dataIndex}, {@link Ext.form.Field#getName name or hiddenName}).
* @return Field
*/
- findField : function(id){
+ findField : function(id) {
var field = this.items.get(id);
- if(!Ext.isObject(field)){
- this.items.each(function(f){
- if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
- field = f;
- return false;
+
+ if (!Ext.isObject(field)) {
+ //searches for the field corresponding to the given id. Used recursively for composite fields
+ var findMatchingField = function(f) {
+ if (f.isFormField) {
+ if (f.dataIndex == id || f.id == id || f.getName() == id) {
+ field = f;
+ return false;
+ } else if (f.isComposite && f.rendered) {
+ return f.items.each(findMatchingField);
+ }
}
- });
+ };
+
+ this.items.each(findMatchingField);
}
return field || null;
},
@@ -4729,7 +5687,7 @@ myFormPanel.getForm().submit({
* @return {BasicForm} this
*/
markInvalid : function(errors){
- if(Ext.isArray(errors)){
+ if (Ext.isArray(errors)) {
for(var i = 0, len = errors.length; i < len; i++){
var fieldError = errors[i];
var f = this.findField(fieldError.id);
@@ -4737,7 +5695,7 @@ myFormPanel.getForm().submit({
f.markInvalid(fieldError.msg);
}
}
- }else{
+ } else {
var field, id;
for(id in errors){
if(!Ext.isFunction(errors[id]) && (field = this.findField(id))){
@@ -4745,6 +5703,7 @@ myFormPanel.getForm().submit({
}
}
}
+
return this;
},
@@ -4805,10 +5764,33 @@ myFormPanel.getForm().submit({
return Ext.urlDecode(fs);
},
- getFieldValues : function(){
- var o = {};
- this.items.each(function(f){
- o[f.getName()] = f.getValue();
+ /**
+ * Retrieves the fields in the form as a set of key/value pairs, using the {@link Ext.form.Field#getValue getValue()} method.
+ * If multiple fields exist with the same name they are returned as an array.
+ * @param {Boolean} dirtyOnly (optional) True to return only fields that are dirty.
+ * @return {Object} The values in the form
+ */
+ getFieldValues : function(dirtyOnly){
+ var o = {},
+ n,
+ key,
+ val;
+ this.items.each(function(f) {
+ if (dirtyOnly !== true || f.isDirty()) {
+ n = f.getName();
+ key = o[n];
+ val = f.getValue();
+
+ if(Ext.isDefined(key)){
+ if(Ext.isArray(key)){
+ o[n].push(val);
+ }else{
+ o[n] = [key, val];
+ }
+ }else{
+ o[n] = val;
+ }
+ }
});
return o;
},
@@ -4853,7 +5835,6 @@ myFormPanel.getForm().submit({
return this;
},
-
/**
* Removes a field from the items collection (does NOT remove its markup).
* @param {Field} field
@@ -4864,6 +5845,13 @@ myFormPanel.getForm().submit({
return this;
},
+ /**
+ * Removes all fields from the collection that have been destroyed.
+ */
+ cleanDestroyed : function() {
+ this.items.filterBy(function(o) { return !!o.isDestroyed; }).each(this.remove, this);
+ },
+
/**
* Iterates through the {@link Ext.form.Field Field}s which have been {@link #add add}ed to this BasicForm,
* checks them for an id attribute, and calls {@link Ext.form.Field#applyToMarkup} on the existing dom element with that id.
@@ -4914,17 +5902,18 @@ myFormPanel.getForm().submit({
});
// back compat
-Ext.BasicForm = Ext.form.BasicForm;/**
+Ext.BasicForm = Ext.form.BasicForm;
+/**
* @class Ext.form.FormPanel
* @extends Ext.Panel
*
Note: If subclassing FormPanel, any configuration options for the BasicForm must be applied to * the initialConfig property of the FormPanel. Applying {@link Ext.form.BasicForm BasicForm} * configuration settings to this will not affect the BasicForm's configuration.
- * + * *Form Validation
*For information on form validation see the following:
*Form Submission
*By default, Ext Forms are submitted through Ajax, using {@link Ext.form.Action}. To enable normal browser * submission of the {@link Ext.form.BasicForm BasicForm} contained in this FormPanel, see the * {@link Ext.form.BasicForm#standardSubmit standardSubmit} option.
- * + * * @constructor * @param {Object} config Configuration options * @xtype form */ Ext.FormPanel = Ext.extend(Ext.Panel, { - /** - * @cfg {String} formId (optional) The id of the FORM tag (defaults to an auto-generated id). - */ + /** + * @cfg {String} formId (optional) The id of the FORM tag (defaults to an auto-generated id). + */ /** * @cfg {Boolean} hideLabels *true to hide field labels by default (sets display:none). Defaults to @@ -5020,7 +6009,7 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { monitorPoll : 200, /** - * @cfg {String} layout Defaults to 'form'. Normally this configuration property should not be altered. + * @cfg {String} layout Defaults to 'form'. Normally this configuration property should not be altered. * For additional details see {@link Ext.layout.FormLayout} and {@link Ext.Container#layout Ext.Container.layout}. */ layout : 'form', @@ -5040,7 +6029,7 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { this.bodyCfg.enctype = 'multipart/form-data'; } this.initItems(); - + this.addEvents( /** * @event clientvalidation @@ -5067,19 +6056,8 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { var fn = function(c){ if(formPanel.isField(c)){ f.add(c); - }if(c.isFieldWrap){ - Ext.applyIf(c, { - labelAlign: c.ownerCt.labelAlign, - labelWidth: c.ownerCt.labelWidth, - itemCls: c.ownerCt.itemCls - }); - f.add(c.field); - }else if(c.doLayout && c != formPanel){ - Ext.applyIf(c, { - labelAlign: c.ownerCt.labelAlign, - labelWidth: c.ownerCt.labelWidth, - itemCls: c.ownerCt.itemCls - }); + }else if(c.findBy && c != formPanel){ + formPanel.applySettings(c); //each check required for check/radio groups. if(c.items && c.items.each){ c.items.each(fn, this); @@ -5089,6 +6067,16 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { this.items.each(fn, this); }, + // private + applySettings: function(c){ + var ct = c.ownerCt; + Ext.applyIf(c, { + labelAlign: ct.labelAlign, + labelWidth: ct.labelWidth, + itemCls: ct.itemCls + }); + }, + // private getLayoutTarget : function(){ return this.form.el; @@ -5108,21 +6096,15 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { Ext.FormPanel.superclass.onRender.call(this, ct, position); this.form.initEl(this.body); }, - + // private beforeDestroy : function(){ this.stopMonitoring(); + this.form.destroy(true); Ext.FormPanel.superclass.beforeDestroy.call(this); - /* - * Clear the items here to prevent them being destroyed again. - * Don't move this behaviour to BasicForm because it can be used - * on it's own. - */ - this.form.items.clear(); - Ext.destroy(this.form); }, - // Determine if a Component is usable as a form Field. + // Determine if a Component is usable as a form Field. isField : function(c) { return !!c.setValue && !!c.getValue && !!c.markInvalid && !!c.clearInvalid; }, @@ -5130,1730 +6112,2055 @@ Ext.FormPanel = Ext.extend(Ext.Panel, { // private initEvents : function(){ Ext.FormPanel.superclass.initEvents.call(this); - this.on('remove', this.onRemove, this); - this.on('add', this.onAdd, this); + // Listeners are required here to catch bubbling events from children. + this.on({ + scope: this, + add: this.onAddEvent, + remove: this.onRemoveEvent + }); if(this.monitorValid){ // initialize after render this.startMonitoring(); } }, - + + // private + onAdd: function(c){ + Ext.FormPanel.superclass.onAdd.call(this, c); + this.processAdd(c); + }, + + // private + onAddEvent: function(ct, c){ + if(ct !== this){ + this.processAdd(c); + } + }, + // private - onAdd : function(ct, c) { - // If a single form Field, add it - if (this.isField(c)) { + processAdd : function(c){ + // If a single form Field, add it + if(this.isField(c)){ this.form.add(c); - // If a Container, add any Fields it might contain - } else if (c.findBy) { - Ext.applyIf(c, { - labelAlign: c.ownerCt.labelAlign, - labelWidth: c.ownerCt.labelWidth, - itemCls: c.ownerCt.itemCls - }); + // If a Container, add any Fields it might contain + }else if(c.findBy){ + this.applySettings(c); this.form.add.apply(this.form, c.findBy(this.isField)); } }, - + + // private + onRemove: function(c){ + Ext.FormPanel.superclass.onRemove.call(this, c); + this.processRemove(c); + }, + + onRemoveEvent: function(ct, c){ + if(ct !== this){ + this.processRemove(c); + } + }, + + // private + processRemove: function(c){ + if(!this.destroying){ + // If a single form Field, remove it + if(this.isField(c)){ + this.form.remove(c); + // If a Container, its already destroyed by the time it gets here. Remove any references to destroyed fields. + }else if (c.findBy){ + Ext.each(c.findBy(this.isField), this.form.remove, this.form); + if (c.isDestroyed) { + this.form.cleanDestroyed(); + } + } + } + }, + + /** + * Starts monitoring of the valid state of this form. Usually this is done by passing the config + * option "monitorValid" + */ + startMonitoring : function(){ + if(!this.validTask){ + this.validTask = new Ext.util.TaskRunner(); + this.validTask.start({ + run : this.bindHandler, + interval : this.monitorPoll || 200, + scope: this + }); + } + }, + + /** + * Stops monitoring of the valid state of this form + */ + stopMonitoring : function(){ + if(this.validTask){ + this.validTask.stopAll(); + this.validTask = null; + } + }, + + /** + * This is a proxy for the underlying BasicForm's {@link Ext.form.BasicForm#load} call. + * @param {Object} options The options to pass to the action (see {@link Ext.form.BasicForm#doAction} for details) + */ + load : function(){ + this.form.load.apply(this.form, arguments); + }, + + // private + onDisable : function(){ + Ext.FormPanel.superclass.onDisable.call(this); + if(this.form){ + this.form.items.each(function(){ + this.disable(); + }); + } + }, + + // private + onEnable : function(){ + Ext.FormPanel.superclass.onEnable.call(this); + if(this.form){ + this.form.items.each(function(){ + this.enable(); + }); + } + }, + + // private + bindHandler : function(){ + var valid = true; + this.form.items.each(function(f){ + if(!f.isValid(true)){ + valid = false; + return false; + } + }); + if(this.fbar){ + var fitems = this.fbar.items.items; + for(var i = 0, len = fitems.length; i < len; i++){ + var btn = fitems[i]; + if(btn.formBind === true && btn.disabled === valid){ + btn.setDisabled(!valid); + } + } + } + this.fireEvent('clientvalidation', this, valid); + } +}); +Ext.reg('form', Ext.FormPanel); + +Ext.form.FormPanel = Ext.FormPanel; +/** + * @class Ext.form.FieldSet + * @extends Ext.Panel + * Standard container used for grouping items within a {@link Ext.form.FormPanel form}. + *
+var form = new Ext.FormPanel({
+ title: 'Simple Form with FieldSets',
+ labelWidth: 75, // label settings here cascade unless overridden
+ url: 'save-form.php',
+ frame:true,
+ bodyStyle:'padding:5px 5px 0',
+ width: 700,
+ renderTo: document.body,
+ layout:'column', // arrange items in columns
+ defaults: { // defaults applied to items
+ layout: 'form',
+ border: false,
+ bodyStyle: 'padding:4px'
+ },
+ items: [{
+ // Fieldset in Column 1
+ xtype:'fieldset',
+ columnWidth: 0.5,
+ title: 'Fieldset 1',
+ collapsible: true,
+ autoHeight:true,
+ defaults: {
+ anchor: '-20' // leave room for error icon
+ },
+ defaultType: 'textfield',
+ items :[{
+ fieldLabel: 'Field 1'
+ }, {
+ fieldLabel: 'Field 2'
+ }, {
+ fieldLabel: 'Field 3'
+ }
+ ]
+ },{
+ // Fieldset in Column 2 - Panel inside
+ xtype:'fieldset',
+ title: 'Show Panel', // title, header, or checkboxToggle creates fieldset header
+ autoHeight:true,
+ columnWidth: 0.5,
+ checkboxToggle: true,
+ collapsed: true, // fieldset initially collapsed
+ layout:'anchor',
+ items :[{
+ xtype: 'panel',
+ anchor: '100%',
+ title: 'Panel inside a fieldset',
+ frame: true,
+ height: 100
+ }]
+ }]
+});
+ *
+ * @constructor
+ * @param {Object} config Configuration options
+ * @xtype fieldset
+ */
+Ext.form.FieldSet = Ext.extend(Ext.Panel, {
+ /**
+ * @cfg {Mixed} checkboxToggle true to render a checkbox into the fieldset frame just
+ * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
+ * to false).
+ * A {@link Ext.DomHelper DomHelper} element spec may also be specified to create the checkbox. + * If true is specified, the default DomHelper config object used to create the element + * is:
+ * {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}
+ *
+ */
+ /**
+ * @cfg {String} checkboxName The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
+ * (defaults to '[checkbox id]-checkbox').
+ */
+ /**
+ * @cfg {Boolean} collapsible
+ * true to make the fieldset collapsible and have the expand/collapse toggle button automatically
+ * rendered into the legend element, false to keep the fieldset statically sized with no collapse
+ * button (defaults to false). Another option is to configure {@link #checkboxToggle}.
+ */
+ /**
+ * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
+ */
+ /**
+ * @cfg {String} itemCls A css class to apply to the x-form-item of fields (see
+ * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} for details).
+ * This property cascades to child containers.
+ */
+ /**
+ * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to 'x-fieldset').
+ */
+ baseCls : 'x-fieldset',
+ /**
+ * @cfg {String} layout The {@link Ext.Container#layout} to use inside the fieldset (defaults to 'form').
+ */
+ layout : 'form',
+ /**
+ * @cfg {Boolean} animCollapse
+ * true to animate the transition when the panel is collapsed, false to skip the
+ * animation (defaults to false).
+ */
+ animCollapse : false,
+
+ // private
+ onRender : function(ct, position){
+ if(!this.el){
+ this.el = document.createElement('fieldset');
+ this.el.id = this.id;
+ if (this.title || this.header || this.checkboxToggle) {
+ this.el.appendChild(document.createElement('legend')).className = this.baseCls + '-header';
+ }
+ }
+
+ Ext.form.FieldSet.superclass.onRender.call(this, ct, position);
+
+ if(this.checkboxToggle){
+ var o = typeof this.checkboxToggle == 'object' ?
+ this.checkboxToggle :
+ {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'};
+ this.checkbox = this.header.insertFirst(o);
+ this.checkbox.dom.checked = !this.collapsed;
+ this.mon(this.checkbox, 'click', this.onCheckClick, this);
+ }
+ },
+
+ // private
+ onCollapse : function(doAnim, animArg){
+ if(this.checkbox){
+ this.checkbox.dom.checked = false;
+ }
+ Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg);
+
+ },
+
+ // private
+ onExpand : function(doAnim, animArg){
+ if(this.checkbox){
+ this.checkbox.dom.checked = true;
+ }
+ Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg);
+ },
+
+ /**
+ * This function is called by the fieldset's checkbox when it is toggled (only applies when
+ * checkboxToggle = true). This method should never be called externally, but can be
+ * overridden to provide custom behavior when the checkbox is toggled if needed.
+ */
+ onCheckClick : function(){
+ this[this.checkbox.dom.checked ? 'expand' : 'collapse']();
+ }
+
+ /**
+ * @cfg {String/Number} activeItem
+ * @hide
+ */
+ /**
+ * @cfg {Mixed} applyTo
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} bodyBorder
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} border
+ * @hide
+ */
+ /**
+ * @cfg {Boolean/Number} bufferResize
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} collapseFirst
+ * @hide
+ */
+ /**
+ * @cfg {String} defaultType
+ * @hide
+ */
+ /**
+ * @cfg {String} disabledClass
+ * @hide
+ */
+ /**
+ * @cfg {String} elements
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} floating
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} footer
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} frame
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} header
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} headerAsText
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} hideCollapseTool
+ * @hide
+ */
+ /**
+ * @cfg {String} iconCls
+ * @hide
+ */
+ /**
+ * @cfg {Boolean/String} shadow
+ * @hide
+ */
+ /**
+ * @cfg {Number} shadowOffset
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} shim
+ * @hide
+ */
+ /**
+ * @cfg {Object/Array} tbar
+ * @hide
+ */
+ /**
+ * @cfg {Array} tools
+ * @hide
+ */
+ /**
+ * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
+ * @hide
+ */
+ /**
+ * @cfg {String} xtype
+ * @hide
+ */
+ /**
+ * @property header
+ * @hide
+ */
+ /**
+ * @property footer
+ * @hide
+ */
+ /**
+ * @method focus
+ * @hide
+ */
+ /**
+ * @method getBottomToolbar
+ * @hide
+ */
+ /**
+ * @method getTopToolbar
+ * @hide
+ */
+ /**
+ * @method setIconClass
+ * @hide
+ */
+ /**
+ * @event activate
+ * @hide
+ */
+ /**
+ * @event beforeclose
+ * @hide
+ */
+ /**
+ * @event bodyresize
+ * @hide
+ */
+ /**
+ * @event close
+ * @hide
+ */
+ /**
+ * @event deactivate
+ * @hide
+ */
+});
+Ext.reg('fieldset', Ext.form.FieldSet);/**
+ * @class Ext.form.HtmlEditor
+ * @extends Ext.form.Field
+ * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
+ * automatically hidden when needed. These are noted in the config options where appropriate.
+ *
+// Simple example rendered with default options:
+Ext.QuickTips.init(); // enable tooltips
+new Ext.form.HtmlEditor({
+ renderTo: Ext.getBody(),
+ width: 800,
+ height: 300
+});
+
+// Passed via xtype into a container and with custom options:
+Ext.QuickTips.init(); // enable tooltips
+new Ext.Panel({
+ title: 'HTML Editor',
+ renderTo: Ext.getBody(),
+ width: 600,
+ height: 300,
+ frame: true,
+ layout: 'fit',
+ items: {
+ xtype: 'htmleditor',
+ enableColors: false,
+ enableAlignments: false
+ }
+});
+
+ * @constructor
+ * Create a new HtmlEditor
+ * @param {Object} config
+ * @xtype htmleditor
+ */
+
+Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
+ /**
+ * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
+ */
+ enableFormat : true,
+ /**
+ * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
+ */
+ enableFontSize : true,
+ /**
+ * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
+ */
+ enableColors : true,
+ /**
+ * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
+ */
+ enableAlignments : true,
+ /**
+ * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
+ */
+ enableLists : true,
+ /**
+ * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
+ */
+ enableSourceEdit : true,
+ /**
+ * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
+ */
+ enableLinks : true,
+ /**
+ * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
+ */
+ enableFont : true,
+ /**
+ * @cfg {String} createLinkText The default text for the create link prompt
+ */
+ createLinkText : 'Please enter the URL for the link:',
+ /**
+ * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
+ */
+ defaultLinkValue : 'http:/'+'/',
+ /**
+ * @cfg {Array} fontFamilies An array of available font families
+ */
+ fontFamilies : [
+ 'Arial',
+ 'Courier New',
+ 'Tahoma',
+ 'Times New Roman',
+ 'Verdana'
+ ],
+ defaultFont: 'tahoma',
+ /**
+ * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to (Non-breaking space) in Opera and IE6, (Zero-width space) in all other browsers).
+ */
+ defaultValue: (Ext.isOpera || Ext.isIE6) ? ' ' : '',
+
+ // private properties
+ actionMode: 'wrap',
+ validationEvent : false,
+ deferHeight: true,
+ initialized : false,
+ activated : false,
+ sourceEditMode : false,
+ onFocus : Ext.emptyFn,
+ iframePad:3,
+ hideMode:'offsets',
+ defaultAutoCreate : {
+ tag: "textarea",
+ style:"width:500px;height:300px;",
+ autocomplete: "off"
+ },
+
+ // private
+ initComponent : function(){
+ this.addEvents(
+ /**
+ * @event initialize
+ * Fires when the editor is fully initialized (including the iframe)
+ * @param {HtmlEditor} this
+ */
+ 'initialize',
+ /**
+ * @event activate
+ * Fires when the editor is first receives the focus. Any insertion must wait
+ * until after this event.
+ * @param {HtmlEditor} this
+ */
+ 'activate',
+ /**
+ * @event beforesync
+ * Fires before the textarea is updated with content from the editor iframe. Return false
+ * to cancel the sync.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'beforesync',
+ /**
+ * @event beforepush
+ * Fires before the iframe editor is updated with content from the textarea. Return false
+ * to cancel the push.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'beforepush',
+ /**
+ * @event sync
+ * Fires when the textarea is updated with content from the editor iframe.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'sync',
+ /**
+ * @event push
+ * Fires when the iframe editor is updated with content from the textarea.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'push',
+ /**
+ * @event editmodechange
+ * Fires when the editor switches edit modes
+ * @param {HtmlEditor} this
+ * @param {Boolean} sourceEdit True if source edit, false if standard editing.
+ */
+ 'editmodechange'
+ );
+ },
+
+ // private
+ createFontOptions : function(){
+ var buf = [], fs = this.fontFamilies, ff, lc;
+ for(var i = 0, len = fs.length; i< len; i++){
+ ff = fs[i];
+ lc = ff.toLowerCase();
+ buf.push(
+ ''
+ );
+ }
+ return buf.join('');
+ },
+
+ /*
+ * Protected method that will not generally be called directly. It
+ * is called when the editor creates its toolbar. Override this method if you need to
+ * add custom toolbar buttons.
+ * @param {HtmlEditor} editor
+ */
+ createToolbar : function(editor){
+ var items = [];
+ var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
+
+
+ function btn(id, toggle, handler){
+ return {
+ itemId : id,
+ cls : 'x-btn-icon',
+ iconCls: 'x-edit-'+id,
+ enableToggle:toggle !== false,
+ scope: editor,
+ handler:handler||editor.relayBtnCmd,
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
+ overflowText: editor.buttonTips[id].title || undefined,
+ tabIndex:-1
+ };
+ }
+
+
+ if(this.enableFont && !Ext.isSafari2){
+ var fontSelectItem = new Ext.Toolbar.Item({
+ autoEl: {
+ tag:'select',
+ cls:'x-font-select',
+ html: this.createFontOptions()
+ }
+ });
+
+ items.push(
+ fontSelectItem,
+ '-'
+ );
+ }
+
+ if(this.enableFormat){
+ items.push(
+ btn('bold'),
+ btn('italic'),
+ btn('underline')
+ );
+ }
+
+ if(this.enableFontSize){
+ items.push(
+ '-',
+ btn('increasefontsize', false, this.adjustFont),
+ btn('decreasefontsize', false, this.adjustFont)
+ );
+ }
+
+ if(this.enableColors){
+ items.push(
+ '-', {
+ itemId:'forecolor',
+ cls:'x-btn-icon',
+ iconCls: 'x-edit-forecolor',
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
+ tabIndex:-1,
+ menu : new Ext.menu.ColorMenu({
+ allowReselect: true,
+ focus: Ext.emptyFn,
+ value:'000000',
+ plain:true,
+ listeners: {
+ scope: this,
+ select: function(cp, color){
+ this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
+ this.deferFocus();
+ }
+ },
+ clickEvent:'mousedown'
+ })
+ }, {
+ itemId:'backcolor',
+ cls:'x-btn-icon',
+ iconCls: 'x-edit-backcolor',
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
+ tabIndex:-1,
+ menu : new Ext.menu.ColorMenu({
+ focus: Ext.emptyFn,
+ value:'FFFFFF',
+ plain:true,
+ allowReselect: true,
+ listeners: {
+ scope: this,
+ select: function(cp, color){
+ if(Ext.isGecko){
+ this.execCmd('useCSS', false);
+ this.execCmd('hilitecolor', color);
+ this.execCmd('useCSS', true);
+ this.deferFocus();
+ }else{
+ this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
+ this.deferFocus();
+ }
+ }
+ },
+ clickEvent:'mousedown'
+ })
+ }
+ );
+ }
+
+ if(this.enableAlignments){
+ items.push(
+ '-',
+ btn('justifyleft'),
+ btn('justifycenter'),
+ btn('justifyright')
+ );
+ }
+
+ if(!Ext.isSafari2){
+ if(this.enableLinks){
+ items.push(
+ '-',
+ btn('createlink', false, this.createLink)
+ );
+ }
+
+ if(this.enableLists){
+ items.push(
+ '-',
+ btn('insertorderedlist'),
+ btn('insertunorderedlist')
+ );
+ }
+ if(this.enableSourceEdit){
+ items.push(
+ '-',
+ btn('sourceedit', true, function(btn){
+ this.toggleSourceEdit(!this.sourceEditMode);
+ })
+ );
+ }
+ }
+
+ // build the toolbar
+ var tb = new Ext.Toolbar({
+ renderTo: this.wrap.dom.firstChild,
+ items: items
+ });
+
+ if (fontSelectItem) {
+ this.fontSelect = fontSelectItem.el;
+
+ this.mon(this.fontSelect, 'change', function(){
+ var font = this.fontSelect.dom.value;
+ this.relayCmd('fontname', font);
+ this.deferFocus();
+ }, this);
+ }
+
+ // stop form submits
+ this.mon(tb.el, 'click', function(e){
+ e.preventDefault();
+ });
+
+ this.tb = tb;
+ this.tb.doLayout();
+ },
+
+ onDisable: function(){
+ this.wrap.mask();
+ Ext.form.HtmlEditor.superclass.onDisable.call(this);
+ },
+
+ onEnable: function(){
+ this.wrap.unmask();
+ Ext.form.HtmlEditor.superclass.onEnable.call(this);
+ },
+
+ setReadOnly: function(readOnly){
+
+ Ext.form.HtmlEditor.superclass.setReadOnly.call(this, readOnly);
+ if(this.initialized){
+ if(Ext.isIE){
+ this.getEditorBody().contentEditable = !readOnly;
+ }else{
+ this.setDesignMode(!readOnly);
+ }
+ var bd = this.getEditorBody();
+ if(bd){
+ bd.style.cursor = this.readOnly ? 'default' : 'text';
+ }
+ this.disableItems(readOnly);
+ }
+ },
+
+ /**
+ * Protected method that will not generally be called directly. It
+ * is called when the editor initializes the iframe with HTML contents. Override this method if you
+ * want to change the initialization markup of the iframe (e.g. to add stylesheets).
+ *
+ * Note: IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility
+ */
+ getDocMarkup : function(){
+ var h = Ext.fly(this.iframe).getHeight() - this.iframePad * 2;
+ return String.format('', this.iframePad, h);
+ },
+
+ // private
+ getEditorBody : function(){
+ var doc = this.getDoc();
+ return doc.body || doc.documentElement;
+ },
+
+ // private
+ getDoc : function(){
+ return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
+ },
+
+ // private
+ getWin : function(){
+ return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
+ },
+
+ // private
+ onRender : function(ct, position){
+ Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
+ this.el.dom.style.border = '0 none';
+ this.el.dom.setAttribute('tabIndex', -1);
+ this.el.addClass('x-hidden');
+ if(Ext.isIE){ // fix IE 1px bogus margin
+ this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;');
+ }
+ this.wrap = this.el.wrap({
+ cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
+ });
+
+ this.createToolbar(this);
+
+ this.disableItems(true);
+
+ this.tb.doLayout();
+
+ this.createIFrame();
+
+ if(!this.width){
+ var sz = this.el.getSize();
+ this.setSize(sz.width, this.height || sz.height);
+ }
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+
+ createIFrame: function(){
+ var iframe = document.createElement('iframe');
+ iframe.name = Ext.id();
+ iframe.frameBorder = '0';
+ iframe.style.overflow = 'auto';
+
+ this.wrap.dom.appendChild(iframe);
+ this.iframe = iframe;
+
+ this.monitorTask = Ext.TaskMgr.start({
+ run: this.checkDesignMode,
+ scope: this,
+ interval:100
+ });
+ },
+
+ initFrame : function(){
+ Ext.TaskMgr.stop(this.monitorTask);
+ var doc = this.getDoc();
+ this.win = this.getWin();
+
+ doc.open();
+ doc.write(this.getDocMarkup());
+ doc.close();
+
+ var task = { // must defer to wait for browser to be ready
+ run : function(){
+ var doc = this.getDoc();
+ if(doc.body || doc.readyState == 'complete'){
+ Ext.TaskMgr.stop(task);
+ this.setDesignMode(true);
+ this.initEditor.defer(10, this);
+ }
+ },
+ interval : 10,
+ duration:10000,
+ scope: this
+ };
+ Ext.TaskMgr.start(task);
+ },
+
+
+ checkDesignMode : function(){
+ if(this.wrap && this.wrap.dom.offsetWidth){
+ var doc = this.getDoc();
+ if(!doc){
+ return;
+ }
+ if(!doc.editorInitialized || this.getDesignMode() != 'on'){
+ this.initFrame();
+ }
+ }
+ },
+
+ /* private
+ * set current design mode. To enable, mode can be true or 'on', off otherwise
+ */
+ setDesignMode : function(mode){
+ var doc ;
+ if(doc = this.getDoc()){
+ if(this.readOnly){
+ mode = false;
+ }
+ doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
+ }
+
+ },
+
+ // private
+ getDesignMode : function(){
+ var doc = this.getDoc();
+ if(!doc){ return ''; }
+ return String(doc.designMode).toLowerCase();
+
+ },
+
+ disableItems: function(disabled){
+ if(this.fontSelect){
+ this.fontSelect.dom.disabled = disabled;
+ }
+ this.tb.items.each(function(item){
+ if(item.getItemId() != 'sourceedit'){
+ item.setDisabled(disabled);
+ }
+ });
+ },
+
+ // private
+ onResize : function(w, h){
+ Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
+ if(this.el && this.iframe){
+ if(Ext.isNumber(w)){
+ var aw = w - this.wrap.getFrameWidth('lr');
+ this.el.setWidth(aw);
+ this.tb.setWidth(aw);
+ this.iframe.style.width = Math.max(aw, 0) + 'px';
+ }
+ if(Ext.isNumber(h)){
+ var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
+ this.el.setHeight(ah);
+ this.iframe.style.height = Math.max(ah, 0) + 'px';
+ var bd = this.getEditorBody();
+ if(bd){
+ bd.style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
+ }
+ }
+ }
+ },
+
+ /**
+ * Toggles the editor between standard and source edit mode.
+ * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
+ */
+ toggleSourceEdit : function(sourceEditMode){
+ var iframeHeight,
+ elHeight,
+ ls;
+
+ if (sourceEditMode === undefined) {
+ sourceEditMode = !this.sourceEditMode;
+ }
+ this.sourceEditMode = sourceEditMode === true;
+ var btn = this.tb.getComponent('sourceedit');
+
+ if (btn.pressed !== this.sourceEditMode) {
+ btn.toggle(this.sourceEditMode);
+ if (!btn.xtbHidden) {
+ return;
+ }
+ }
+ if (this.sourceEditMode) {
+ // grab the height of the containing panel before we hide the iframe
+ ls = this.getSize();
+
+ iframeHeight = Ext.get(this.iframe).getHeight();
+
+ this.disableItems(true);
+ this.syncValue();
+ this.iframe.className = 'x-hidden';
+ this.el.removeClass('x-hidden');
+ this.el.dom.removeAttribute('tabIndex');
+ this.el.focus();
+ this.el.dom.style.height = iframeHeight + 'px';
+ }
+ else {
+ elHeight = parseInt(this.el.dom.style.height, 10);
+ if (this.initialized) {
+ this.disableItems(this.readOnly);
+ }
+ this.pushValue();
+ this.iframe.className = '';
+ this.el.addClass('x-hidden');
+ this.el.dom.setAttribute('tabIndex', -1);
+ this.deferFocus();
+
+ this.setSize(ls);
+ this.iframe.style.height = elHeight + 'px';
+ }
+ this.fireEvent('editmodechange', this, this.sourceEditMode);
+ },
+
+ // private used internally
+ createLink : function() {
+ var url = prompt(this.createLinkText, this.defaultLinkValue);
+ if(url && url != 'http:/'+'/'){
+ this.relayCmd('createlink', url);
+ }
+ },
+
+ // private
+ initEvents : function(){
+ this.originalValue = this.getValue();
+ },
+
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ markInvalid : Ext.emptyFn,
+
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ clearInvalid : Ext.emptyFn,
+
+ // docs inherit from Field
+ setValue : function(v){
+ Ext.form.HtmlEditor.superclass.setValue.call(this, v);
+ this.pushValue();
+ return this;
+ },
+
+ /**
+ * Protected method that will not generally be called directly. If you need/want
+ * custom HTML cleanup, this is the method you should override.
+ * @param {String} html The HTML to be cleaned
+ * @return {String} The cleaned HTML
+ */
+ cleanHtml: function(html) {
+ html = String(html);
+ if(Ext.isWebKit){ // strip safari nonsense
+ html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
+ }
+
+ /*
+ * Neat little hack. Strips out all the non-digit characters from the default
+ * value and compares it to the character code of the first character in the string
+ * because it can cause encoding issues when posted to the server.
+ */
+ if(html.charCodeAt(0) == this.defaultValue.replace(/\D/g, '')){
+ html = html.substring(1);
+ }
+ return html;
+ },
+
+ /**
+ * Protected method that will not generally be called directly. Syncs the contents
+ * of the editor iframe with the textarea.
+ */
+ syncValue : function(){
+ if(this.initialized){
+ var bd = this.getEditorBody();
+ var html = bd.innerHTML;
+ if(Ext.isWebKit){
+ var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
+ var m = bs.match(/text-align:(.*?);/i);
+ if(m && m[1]){
+ html = '
+{
+ bold : {
+ title: 'Bold (Ctrl+B)',
+ text: 'Make the selected text bold.',
+ cls: 'x-html-editor-tip'
+ },
+ italic : {
+ title: 'Italic (Ctrl+I)',
+ text: 'Make the selected text italic.',
+ cls: 'x-html-editor-tip'
+ },
+ ...
+
+ * @type Object
+ */
+ buttonTips : {
+ bold : {
+ title: 'Bold (Ctrl+B)',
+ text: 'Make the selected text bold.',
+ cls: 'x-html-editor-tip'
+ },
+ italic : {
+ title: 'Italic (Ctrl+I)',
+ text: 'Make the selected text italic.',
+ cls: 'x-html-editor-tip'
+ },
+ underline : {
+ title: 'Underline (Ctrl+U)',
+ text: 'Underline the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ increasefontsize : {
+ title: 'Grow Text',
+ text: 'Increase the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ decreasefontsize : {
+ title: 'Shrink Text',
+ text: 'Decrease the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ backcolor : {
+ title: 'Text Highlight Color',
+ text: 'Change the background color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ forecolor : {
+ title: 'Font Color',
+ text: 'Change the color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyleft : {
+ title: 'Align Text Left',
+ text: 'Align text to the left.',
+ cls: 'x-html-editor-tip'
+ },
+ justifycenter : {
+ title: 'Center Text',
+ text: 'Center text in the editor.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyright : {
+ title: 'Align Text Right',
+ text: 'Align text to the right.',
+ cls: 'x-html-editor-tip'
+ },
+ insertunorderedlist : {
+ title: 'Bullet List',
+ text: 'Start a bulleted list.',
+ cls: 'x-html-editor-tip'
+ },
+ insertorderedlist : {
+ title: 'Numbered List',
+ text: 'Start a numbered list.',
+ cls: 'x-html-editor-tip'
+ },
+ createlink : {
+ title: 'Hyperlink',
+ text: 'Make the selected text a hyperlink.',
+ cls: 'x-html-editor-tip'
+ },
+ sourceedit : {
+ title: 'Source Edit',
+ text: 'Switch to source editing mode.',
+ cls: 'x-html-editor-tip'
+ }
+ }
+
+ // hide stuff that is not compatible
+ /**
+ * @event blur
+ * @hide
+ */
+ /**
+ * @event change
+ * @hide
+ */
+ /**
+ * @event focus
+ * @hide
+ */
+ /**
+ * @event specialkey
+ * @hide
+ */
+ /**
+ * @cfg {String} fieldClass @hide
+ */
+ /**
+ * @cfg {String} focusClass @hide
+ */
+ /**
+ * @cfg {String} autoCreate @hide
+ */
+ /**
+ * @cfg {String} inputType @hide
+ */
+ /**
+ * @cfg {String} invalidClass @hide
+ */
+ /**
+ * @cfg {String} invalidText @hide
+ */
+ /**
+ * @cfg {String} msgFx @hide
+ */
+ /**
+ * @cfg {String} validateOnBlur @hide
+ */
+ /**
+ * @cfg {Boolean} allowDomMove @hide
+ */
+ /**
+ * @cfg {String} applyTo @hide
+ */
+ /**
+ * @cfg {String} autoHeight @hide
+ */
+ /**
+ * @cfg {String} autoWidth @hide
+ */
+ /**
+ * @cfg {String} cls @hide
+ */
+ /**
+ * @cfg {String} disabled @hide
+ */
+ /**
+ * @cfg {String} disabledClass @hide
+ */
+ /**
+ * @cfg {String} msgTarget @hide
+ */
+ /**
+ * @cfg {String} readOnly @hide
+ */
+ /**
+ * @cfg {String} style @hide
+ */
+ /**
+ * @cfg {String} validationDelay @hide
+ */
+ /**
+ * @cfg {String} validationEvent @hide
+ */
+ /**
+ * @cfg {String} tabIndex @hide
+ */
+ /**
+ * @property disabled
+ * @hide
+ */
+ /**
+ * @method applyToMarkup
+ * @hide
+ */
+ /**
+ * @method disable
+ * @hide
+ */
+ /**
+ * @method enable
+ * @hide
+ */
+ /**
+ * @method validate
+ * @hide
+ */
+ /**
+ * @event valid
+ * @hide
+ */
+ /**
+ * @method setDisabled
+ * @hide
+ */
+ /**
+ * @cfg keys
+ * @hide
+ */
+});
+Ext.reg('htmleditor', Ext.form.HtmlEditor);
+/**
+ * @class Ext.form.TimeField
+ * @extends Ext.form.ComboBox
+ * Provides a time input field with a time dropdown and automatic time validation. Example usage:
+ *
+new Ext.form.TimeField({
+ minValue: '9:00 AM',
+ maxValue: '6:00 PM',
+ increment: 30
+});
+
+ * @constructor
+ * Create a new TimeField
+ * @param {Object} config
+ * @xtype timefield
+ */
+Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, {
+ /**
+ * @cfg {Date/String} minValue
+ * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string
+ * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
+ */
+ minValue : undefined,
+ /**
+ * @cfg {Date/String} maxValue
+ * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string
+ * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
+ */
+ maxValue : undefined,
+ /**
+ * @cfg {String} minText
+ * The error text to display when the date in the cell is before minValue (defaults to
+ * 'The time in this field must be equal to or after {0}').
+ */
+ minText : "The time in this field must be equal to or after {0}",
+ /**
+ * @cfg {String} maxText
+ * The error text to display when the time is after maxValue (defaults to
+ * 'The time in this field must be equal to or before {0}').
+ */
+ maxText : "The time in this field must be equal to or before {0}",
+ /**
+ * @cfg {String} invalidText
+ * The error text to display when the time in the field is invalid (defaults to
+ * '{value} is not a valid time').
+ */
+ invalidText : "{0} is not a valid time",
+ /**
+ * @cfg {String} format
+ * The default time format string which can be overriden for localization support. The format must be
+ * valid according to {@link Date#parseDate} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time
+ * format try 'H:i' instead.
+ */
+ format : "g:i A",
+ /**
+ * @cfg {String} altFormats
+ * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
+ * format (defaults to 'g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A').
+ */
+ altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A",
+ /**
+ * @cfg {Number} increment
+ * The number of minutes between each time value in the list (defaults to 15).
+ */
+ increment: 15,
+
+ // private override
+ mode: 'local',
+ // private override
+ triggerAction: 'all',
+ // private override
+ typeAhead: false,
+
+ // private - This is the date to use when generating time values in the absence of either minValue
+ // or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an
+ // arbitrary "safe" date that can be any date aside from DST boundary dates.
+ initDate: '1/1/2008',
+
+ initDateFormat: 'j/n/Y',
+
// private
- onRemove : function(ct, c) {
- // If a single form Field, remove it
- if (this.isField(c)) {
- Ext.destroy(c.container.up('.x-form-item'));
- this.form.remove(c);
- // If a Container, remove any Fields it might contain
- } else if (c.findByType) {
- Ext.each(c.findBy(this.isField), this.form.remove, this.form);
+ initComponent : function(){
+ if(Ext.isDefined(this.minValue)){
+ this.setMinValue(this.minValue, true);
}
- },
-
- /**
- * Starts monitoring of the valid state of this form. Usually this is done by passing the config
- * option "monitorValid"
- */
- startMonitoring : function(){
- if(!this.validTask){
- this.validTask = new Ext.util.TaskRunner();
- this.validTask.start({
- run : this.bindHandler,
- interval : this.monitorPoll || 200,
- scope: this
- });
+ if(Ext.isDefined(this.maxValue)){
+ this.setMaxValue(this.maxValue, true);
+ }
+ if(!this.store){
+ this.generateStore(true);
}
+ Ext.form.TimeField.superclass.initComponent.call(this);
},
/**
- * Stops monitoring of the valid state of this form
+ * Replaces any existing {@link #minValue} with the new time and refreshes the store.
+ * @param {Date/String} value The minimum time that can be selected
*/
- stopMonitoring : function(){
- if(this.validTask){
- this.validTask.stopAll();
- this.validTask = null;
- }
+ setMinValue: function(value, /* private */ initial){
+ this.setLimit(value, true, initial);
+ return this;
},
/**
- * This is a proxy for the underlying BasicForm's {@link Ext.form.BasicForm#load} call.
- * @param {Object} options The options to pass to the action (see {@link Ext.form.BasicForm#doAction} for details)
+ * Replaces any existing {@link #maxValue} with the new time and refreshes the store.
+ * @param {Date/String} value The maximum time that can be selected
*/
- load : function(){
- this.form.load.apply(this.form, arguments);
+ setMaxValue: function(value, /* private */ initial){
+ this.setLimit(value, false, initial);
+ return this;
},
// private
- onDisable : function(){
- Ext.FormPanel.superclass.onDisable.call(this);
- if(this.form){
- this.form.items.each(function(){
- this.disable();
- });
+ generateStore: function(initial){
+ var min = this.minValue || new Date(this.initDate).clearTime(),
+ max = this.maxValue || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1),
+ times = [];
+
+ while(min <= max){
+ times.push(min.dateFormat(this.format));
+ min = min.add('mi', this.increment);
}
+ this.bindStore(times, initial);
},
// private
- onEnable : function(){
- Ext.FormPanel.superclass.onEnable.call(this);
- if(this.form){
- this.form.items.each(function(){
- this.enable();
- });
+ setLimit: function(value, isMin, initial){
+ var d;
+ if(Ext.isString(value)){
+ d = this.parseDate(value);
+ }else if(Ext.isDate(value)){
+ d = value;
+ }
+ if(d){
+ var val = new Date(this.initDate).clearTime();
+ val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
+ this[isMin ? 'minValue' : 'maxValue'] = val;
+ if(!initial){
+ this.generateStore();
+ }
}
},
- // private
- bindHandler : function(){
- var valid = true;
- this.form.items.each(function(f){
- if(!f.isValid(true)){
- valid = false;
- return false;
+ // inherited docs
+ getValue : function(){
+ var v = Ext.form.TimeField.superclass.getValue.call(this);
+ return this.formatDate(this.parseDate(v)) || '';
+ },
+
+ // inherited docs
+ setValue : function(value){
+ return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value)));
+ },
+
+ // private overrides
+ validateValue : Ext.form.DateField.prototype.validateValue,
+
+ formatDate : Ext.form.DateField.prototype.formatDate,
+
+ parseDate: function(value) {
+ if (!value || Ext.isDate(value)) {
+ return value;
+ }
+
+ var id = this.initDate + ' ',
+ idf = this.initDateFormat + ' ',
+ v = Date.parseDate(id + value, idf + this.format), // *** handle DST. note: this.format is a TIME-only format
+ af = this.altFormats;
+
+ if (!v && af) {
+ if (!this.altFormatsArray) {
+ this.altFormatsArray = af.split("|");
}
- });
- if(this.fbar){
- var fitems = this.fbar.items.items;
- for(var i = 0, len = fitems.length; i < len; i++){
- var btn = fitems[i];
- if(btn.formBind === true && btn.disabled === valid){
- btn.setDisabled(!valid);
- }
+ for (var i = 0, afa = this.altFormatsArray, len = afa.length; i < len && !v; i++) {
+ v = Date.parseDate(id + value, idf + afa[i]);
}
}
- this.fireEvent('clientvalidation', this, valid);
+
+ return v;
}
});
-Ext.reg('form', Ext.FormPanel);
-
-Ext.form.FormPanel = Ext.FormPanel;
-
-/**
- * @class Ext.form.FieldSet
- * @extends Ext.Panel
- * Standard container used for grouping items within a {@link Ext.form.FormPanel form}.
- *
-var form = new Ext.FormPanel({
- title: 'Simple Form with FieldSets',
- labelWidth: 75, // label settings here cascade unless overridden
- url: 'save-form.php',
- frame:true,
- bodyStyle:'padding:5px 5px 0',
- width: 700,
- renderTo: document.body,
- layout:'column', // arrange items in columns
- defaults: { // defaults applied to items
- layout: 'form',
- border: false,
- bodyStyle: 'padding:4px'
- },
- items: [{
- // Fieldset in Column 1
- xtype:'fieldset',
- columnWidth: 0.5,
- title: 'Fieldset 1',
- collapsible: true,
- autoHeight:true,
- defaults: {
- anchor: '-20' // leave room for error icon
- },
- defaultType: 'textfield',
- items :[{
- fieldLabel: 'Field 1'
- }, {
- fieldLabel: 'Field 2'
- }, {
- fieldLabel: 'Field 3'
- }
- ]
- },{
- // Fieldset in Column 2 - Panel inside
- xtype:'fieldset',
- title: 'Show Panel', // title, header, or checkboxToggle creates fieldset header
- autoHeight:true,
- columnWidth: 0.5,
- checkboxToggle: true,
- collapsed: true, // fieldset initially collapsed
- layout:'anchor',
- items :[{
- xtype: 'panel',
- anchor: '100%',
- title: 'Panel inside a fieldset',
- frame: true,
- height: 100
- }]
- }]
-});
- *
- * @constructor
- * @param {Object} config Configuration options
- * @xtype fieldset
- */
-Ext.form.FieldSet = Ext.extend(Ext.Panel, {
- /**
- * @cfg {Mixed} checkboxToggle true to render a checkbox into the fieldset frame just
- * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
- * to false).
- * A {@link Ext.DomHelper DomHelper} element spec may also be specified to create the checkbox. - * If true is specified, the default DomHelper config object used to create the element - * is:
- * {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}
- *
- */
- /**
- * @cfg {String} checkboxName The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
- * (defaults to '[checkbox id]-checkbox').
- */
- /**
- * @cfg {Boolean} collapsible
- * true to make the fieldset collapsible and have the expand/collapse toggle button automatically
- * rendered into the legend element, false to keep the fieldset statically sized with no collapse
- * button (defaults to false). Another option is to configure {@link #checkboxToggle}.
- */
- /**
- * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
- */
- /**
- * @cfg {String} itemCls A css class to apply to the x-form-item of fields (see
- * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} for details).
- * This property cascades to child containers.
- */
- /**
- * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to 'x-fieldset').
- */
- baseCls : 'x-fieldset',
- /**
- * @cfg {String} layout The {@link Ext.Container#layout} to use inside the fieldset (defaults to 'form').
- */
- layout : 'form',
- /**
- * @cfg {Boolean} animCollapse
- * true to animate the transition when the panel is collapsed, false to skip the
- * animation (defaults to false).
- */
- animCollapse : false,
-
- // private
- onRender : function(ct, position){
- if(!this.el){
- this.el = document.createElement('fieldset');
- this.el.id = this.id;
- if (this.title || this.header || this.checkboxToggle) {
- this.el.appendChild(document.createElement('legend')).className = 'x-fieldset-header';
- }
- }
-
- Ext.form.FieldSet.superclass.onRender.call(this, ct, position);
-
- if(this.checkboxToggle){
- var o = typeof this.checkboxToggle == 'object' ?
- this.checkboxToggle :
- {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'};
- this.checkbox = this.header.insertFirst(o);
- this.checkbox.dom.checked = !this.collapsed;
- this.mon(this.checkbox, 'click', this.onCheckClick, this);
- }
- },
-
- // private
- onCollapse : function(doAnim, animArg){
- if(this.checkbox){
- this.checkbox.dom.checked = false;
- }
- Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg);
-
- },
-
- // private
- onExpand : function(doAnim, animArg){
- if(this.checkbox){
- this.checkbox.dom.checked = true;
- }
- Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg);
- },
-
- /**
- * This function is called by the fieldset's checkbox when it is toggled (only applies when
- * checkboxToggle = true). This method should never be called externally, but can be
- * overridden to provide custom behavior when the checkbox is toggled if needed.
- */
- onCheckClick : function(){
- this[this.checkbox.dom.checked ? 'expand' : 'collapse']();
- }
-
- /**
- * @cfg {String/Number} activeItem
- * @hide
- */
- /**
- * @cfg {Mixed} applyTo
- * @hide
- */
- /**
- * @cfg {Boolean} bodyBorder
- * @hide
- */
- /**
- * @cfg {Boolean} border
- * @hide
- */
- /**
- * @cfg {Boolean/Number} bufferResize
- * @hide
- */
- /**
- * @cfg {Boolean} collapseFirst
- * @hide
- */
- /**
- * @cfg {String} defaultType
- * @hide
- */
- /**
- * @cfg {String} disabledClass
- * @hide
- */
- /**
- * @cfg {String} elements
- * @hide
- */
- /**
- * @cfg {Boolean} floating
- * @hide
- */
- /**
- * @cfg {Boolean} footer
- * @hide
- */
- /**
- * @cfg {Boolean} frame
- * @hide
- */
- /**
- * @cfg {Boolean} header
- * @hide
- */
- /**
- * @cfg {Boolean} headerAsText
- * @hide
- */
- /**
- * @cfg {Boolean} hideCollapseTool
- * @hide
- */
- /**
- * @cfg {String} iconCls
- * @hide
- */
- /**
- * @cfg {Boolean/String} shadow
- * @hide
- */
- /**
- * @cfg {Number} shadowOffset
- * @hide
- */
- /**
- * @cfg {Boolean} shim
- * @hide
- */
- /**
- * @cfg {Object/Array} tbar
- * @hide
- */
- /**
- * @cfg {String} tabTip
- * @hide
- */
- /**
- * @cfg {Boolean} titleCollapse
- * @hide
- */
- /**
- * @cfg {Array} tools
- * @hide
- */
- /**
- * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
- * @hide
- */
- /**
- * @cfg {String} xtype
- * @hide
- */
- /**
- * @property header
- * @hide
- */
- /**
- * @property footer
- * @hide
- */
- /**
- * @method focus
- * @hide
- */
- /**
- * @method getBottomToolbar
- * @hide
- */
- /**
- * @method getTopToolbar
- * @hide
- */
- /**
- * @method setIconClass
- * @hide
- */
- /**
- * @event activate
- * @hide
- */
- /**
- * @event beforeclose
- * @hide
- */
- /**
- * @event bodyresize
- * @hide
- */
- /**
- * @event close
- * @hide
- */
- /**
- * @event deactivate
- * @hide
- */
-});
-Ext.reg('fieldset', Ext.form.FieldSet);
-/**
- * @class Ext.form.HtmlEditor
- * @extends Ext.form.Field
- * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
- * automatically hidden when needed. These are noted in the config options where appropriate.
- *
-// Simple example rendered with default options:
-Ext.QuickTips.init(); // enable tooltips
-new Ext.form.HtmlEditor({
- renderTo: Ext.getBody(),
- width: 800,
- height: 300
-});
-
-// Passed via xtype into a container and with custom options:
-Ext.QuickTips.init(); // enable tooltips
-new Ext.Panel({
- title: 'HTML Editor',
- renderTo: Ext.getBody(),
- width: 600,
- height: 300,
- frame: true,
- layout: 'fit',
- items: {
- xtype: 'htmleditor',
- enableColors: false,
- enableAlignments: false
- }
-});
-
- * @constructor
- * Create a new HtmlEditor
- * @param {Object} config
- * @xtype htmleditor
- */
-
-Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
- /**
- * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
- */
- enableFormat : true,
- /**
- * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
- */
- enableFontSize : true,
- /**
- * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
- */
- enableColors : true,
- /**
- * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
- */
- enableAlignments : true,
- /**
- * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
- */
- enableLists : true,
- /**
- * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
- */
- enableSourceEdit : true,
- /**
- * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
- */
- enableLinks : true,
- /**
- * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
- */
- enableFont : true,
- /**
- * @cfg {String} createLinkText The default text for the create link prompt
- */
- createLinkText : 'Please enter the URL for the link:',
- /**
- * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
- */
- defaultLinkValue : 'http:/'+'/',
- /**
- * @cfg {Array} fontFamilies An array of available font families
- */
- fontFamilies : [
- 'Arial',
- 'Courier New',
- 'Tahoma',
- 'Times New Roman',
- 'Verdana'
- ],
- defaultFont: 'tahoma',
- /**
- * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to (Zero-width space), (Non-breaking space) in Opera and IE6).
- */
- defaultValue: (Ext.isOpera || Ext.isIE6) ? ' ' : '',
-
- // private properties
- actionMode: 'wrap',
- validationEvent : false,
- deferHeight: true,
- initialized : false,
- activated : false,
- sourceEditMode : false,
- onFocus : Ext.emptyFn,
- iframePad:3,
- hideMode:'offsets',
- defaultAutoCreate : {
- tag: "textarea",
- style:"width:500px;height:300px;",
- autocomplete: "off"
- },
-
- // private
- initComponent : function(){
- this.addEvents(
- /**
- * @event initialize
- * Fires when the editor is fully initialized (including the iframe)
- * @param {HtmlEditor} this
- */
- 'initialize',
- /**
- * @event activate
- * Fires when the editor is first receives the focus. Any insertion must wait
- * until after this event.
- * @param {HtmlEditor} this
- */
- 'activate',
- /**
- * @event beforesync
- * Fires before the textarea is updated with content from the editor iframe. Return false
- * to cancel the sync.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- 'beforesync',
- /**
- * @event beforepush
- * Fires before the iframe editor is updated with content from the textarea. Return false
- * to cancel the push.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- 'beforepush',
- /**
- * @event sync
- * Fires when the textarea is updated with content from the editor iframe.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- 'sync',
- /**
- * @event push
- * Fires when the iframe editor is updated with content from the textarea.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- 'push',
- /**
- * @event editmodechange
- * Fires when the editor switches edit modes
- * @param {HtmlEditor} this
- * @param {Boolean} sourceEdit True if source edit, false if standard editing.
- */
- 'editmodechange'
- )
- },
-
- // private
- createFontOptions : function(){
- var buf = [], fs = this.fontFamilies, ff, lc;
- for(var i = 0, len = fs.length; i< len; i++){
- ff = fs[i];
- lc = ff.toLowerCase();
- buf.push(
- ''
- );
- }
- return buf.join('');
- },
-
- /*
- * Protected method that will not generally be called directly. It
- * is called when the editor creates its toolbar. Override this method if you need to
- * add custom toolbar buttons.
- * @param {HtmlEditor} editor
- */
- createToolbar : function(editor){
-
- var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
-
- function btn(id, toggle, handler){
- return {
- itemId : id,
- cls : 'x-btn-icon',
- iconCls: 'x-edit-'+id,
- enableToggle:toggle !== false,
- scope: editor,
- handler:handler||editor.relayBtnCmd,
- clickEvent:'mousedown',
- tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
- overflowText: editor.buttonTips[id].title || undefined,
- tabIndex:-1
- };
- }
-
- // build the toolbar
- var tb = new Ext.Toolbar({
- renderTo:this.wrap.dom.firstChild
- });
-
- // stop form submits
- this.mon(tb.el, 'click', function(e){
- e.preventDefault();
- });
-
- if(this.enableFont && !Ext.isSafari2){
- this.fontSelect = tb.el.createChild({
- tag:'select',
- cls:'x-font-select',
- html: this.createFontOptions()
- });
- this.mon(this.fontSelect, 'change', function(){
- var font = this.fontSelect.dom.value;
- this.relayCmd('fontname', font);
- this.deferFocus();
- }, this);
-
- tb.add(
- this.fontSelect.dom,
- '-'
- );
- }
-
- if(this.enableFormat){
- tb.add(
- btn('bold'),
- btn('italic'),
- btn('underline')
- );
- }
-
- if(this.enableFontSize){
- tb.add(
- '-',
- btn('increasefontsize', false, this.adjustFont),
- btn('decreasefontsize', false, this.adjustFont)
- );
- }
-
- if(this.enableColors){
- tb.add(
- '-', {
- itemId:'forecolor',
- cls:'x-btn-icon',
- iconCls: 'x-edit-forecolor',
- clickEvent:'mousedown',
- tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
- tabIndex:-1,
- menu : new Ext.menu.ColorMenu({
- allowReselect: true,
- focus: Ext.emptyFn,
- value:'000000',
- plain:true,
- listeners: {
- scope: this,
- select: function(cp, color){
- this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
- this.deferFocus();
- }
- },
- clickEvent:'mousedown'
- })
- }, {
- itemId:'backcolor',
- cls:'x-btn-icon',
- iconCls: 'x-edit-backcolor',
- clickEvent:'mousedown',
- tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
- tabIndex:-1,
- menu : new Ext.menu.ColorMenu({
- focus: Ext.emptyFn,
- value:'FFFFFF',
- plain:true,
- allowReselect: true,
- listeners: {
- scope: this,
- select: function(cp, color){
- if(Ext.isGecko){
- this.execCmd('useCSS', false);
- this.execCmd('hilitecolor', color);
- this.execCmd('useCSS', true);
- this.deferFocus();
- }else{
- this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
- this.deferFocus();
- }
- }
- },
- clickEvent:'mousedown'
- })
- }
- );
- }
-
- if(this.enableAlignments){
- tb.add(
- '-',
- btn('justifyleft'),
- btn('justifycenter'),
- btn('justifyright')
- );
- }
-
- if(!Ext.isSafari2){
- if(this.enableLinks){
- tb.add(
- '-',
- btn('createlink', false, this.createLink)
- );
- }
-
- if(this.enableLists){
- tb.add(
- '-',
- btn('insertorderedlist'),
- btn('insertunorderedlist')
- );
- }
- if(this.enableSourceEdit){
- tb.add(
- '-',
- btn('sourceedit', true, function(btn){
- this.toggleSourceEdit(!this.sourceEditMode);
- })
- );
- }
- }
-
- this.tb = tb;
- },
-
- /**
- * Protected method that will not generally be called directly. It
- * is called when the editor initializes the iframe with HTML contents. Override this method if you
- * want to change the initialization markup of the iframe (e.g. to add stylesheets).
- */
- getDocMarkup : function(){
- return '';
- },
-
- // private
- getEditorBody : function(){
- return this.doc.body || this.doc.documentElement;
- },
-
- // private
- getDoc : function(){
- return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
- },
-
- // private
- getWin : function(){
- return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
- },
-
- // private
- onRender : function(ct, position){
- Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
- this.el.dom.style.border = '0 none';
- this.el.dom.setAttribute('tabIndex', -1);
- this.el.addClass('x-hidden');
- if(Ext.isIE){ // fix IE 1px bogus margin
- this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
- }
- this.wrap = this.el.wrap({
- cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
- });
-
- this.createToolbar(this);
-
- this.disableItems(true);
- // is this needed?
- // this.tb.doLayout();
-
- this.createIFrame();
-
- if(!this.width){
- var sz = this.el.getSize();
- this.setSize(sz.width, this.height || sz.height);
- }
- },
-
- createIFrame: function(){
- var iframe = document.createElement('iframe');
- iframe.name = Ext.id();
- iframe.frameBorder = '0';
- iframe.src = Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;";
- this.wrap.dom.appendChild(iframe);
-
- this.iframe = iframe;
-
- this.monitorTask = Ext.TaskMgr.start({
- run: this.checkDesignMode,
- scope: this,
- interval:100
- });
- },
-
- initFrame : function(){
- Ext.TaskMgr.stop(this.monitorTask);
- this.doc = this.getDoc();
- this.win = this.getWin();
-
- this.doc.open();
- this.doc.write(this.getDocMarkup());
- this.doc.close();
-
- var task = { // must defer to wait for browser to be ready
- run : function(){
- if(this.doc.body || this.doc.readyState == 'complete'){
- Ext.TaskMgr.stop(task);
- this.doc.designMode="on";
- this.initEditor.defer(10, this);
- }
- },
- interval : 10,
- duration:10000,
- scope: this
- };
- Ext.TaskMgr.start(task);
- },
-
-
- checkDesignMode : function(){
- if(this.wrap && this.wrap.dom.offsetWidth){
- var doc = this.getDoc();
- if(!doc){
- return;
- }
- if(!doc.editorInitialized || String(doc.designMode).toLowerCase() != 'on'){
- this.initFrame();
- }
- }
- },
-
- disableItems: function(disabled){
- if(this.fontSelect){
- this.fontSelect.dom.disabled = disabled;
- }
- this.tb.items.each(function(item){
- if(item.itemId != 'sourceedit'){
- item.setDisabled(disabled);
- }
- });
- },
-
- // private
- onResize : function(w, h){
- Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
- if(this.el && this.iframe){
- if(typeof w == 'number'){
- var aw = w - this.wrap.getFrameWidth('lr');
- this.el.setWidth(this.adjustWidth('textarea', aw));
- this.tb.setWidth(aw);
- this.iframe.style.width = Math.max(aw, 0) + 'px';
- }
- if(typeof h == 'number'){
- var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
- this.el.setHeight(this.adjustWidth('textarea', ah));
- this.iframe.style.height = Math.max(ah, 0) + 'px';
- if(this.doc){
- this.getEditorBody().style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
- }
- }
- }
- },
-
- /**
- * Toggles the editor between standard and source edit mode.
- * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
- */
- toggleSourceEdit : function(sourceEditMode){
- if(sourceEditMode === undefined){
- sourceEditMode = !this.sourceEditMode;
- }
- this.sourceEditMode = sourceEditMode === true;
- var btn = this.tb.items.get('sourceedit');
- if(btn.pressed !== this.sourceEditMode){
- btn.toggle(this.sourceEditMode);
- if(!btn.xtbHidden){
- return;
- }
- }
- if(this.sourceEditMode){
- this.disableItems(true);
- this.syncValue();
- this.iframe.className = 'x-hidden';
- this.el.removeClass('x-hidden');
- this.el.dom.removeAttribute('tabIndex');
- this.el.focus();
- }else{
- if(this.initialized){
- this.disableItems(false);
- }
- this.pushValue();
- this.iframe.className = '';
- this.el.addClass('x-hidden');
- this.el.dom.setAttribute('tabIndex', -1);
- this.deferFocus();
- }
- var lastSize = this.lastSize;
- if(lastSize){
- delete this.lastSize;
- this.setSize(lastSize);
- }
- this.fireEvent('editmodechange', this, this.sourceEditMode);
- },
-
- // private used internally
- createLink : function(){
- var url = prompt(this.createLinkText, this.defaultLinkValue);
- if(url && url != 'http:/'+'/'){
- this.relayCmd('createlink', url);
- }
- },
-
- // private (for BoxComponent)
- adjustSize : Ext.BoxComponent.prototype.adjustSize,
-
- // private (for BoxComponent)
- getResizeEl : function(){
- return this.wrap;
- },
-
- // private (for BoxComponent)
- getPositionEl : function(){
- return this.wrap;
- },
-
- // private
- initEvents : function(){
- this.originalValue = this.getValue();
- },
-
- /**
- * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
- * @method
- */
- markInvalid : Ext.emptyFn,
-
- /**
- * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
- * @method
- */
- clearInvalid : Ext.emptyFn,
-
- // docs inherit from Field
- setValue : function(v){
- Ext.form.HtmlEditor.superclass.setValue.call(this, v);
- this.pushValue();
- return this;
- },
-
- /**
- * Protected method that will not generally be called directly. If you need/want
- * custom HTML cleanup, this is the method you should override.
- * @param {String} html The HTML to be cleaned
- * @return {String} The cleaned HTML
- */
- cleanHtml : function(html){
- html = String(html);
- if(html.length > 5){
- if(Ext.isWebKit){ // strip safari nonsense
- html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
- }
- }
- if(html == this.defaultValue){
- html = '';
- }
- return html;
- },
-
- /**
- * Protected method that will not generally be called directly. Syncs the contents
- * of the editor iframe with the textarea.
- */
- syncValue : function(){
- if(this.initialized){
- var bd = this.getEditorBody();
- var html = bd.innerHTML;
- if(Ext.isWebKit){
- var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
- var m = bs.match(/text-align:(.*?);/i);
- if(m && m[1]){
- html = '
-{
- bold : {
- title: 'Bold (Ctrl+B)',
- text: 'Make the selected text bold.',
- cls: 'x-html-editor-tip'
- },
- italic : {
- title: 'Italic (Ctrl+I)',
- text: 'Make the selected text italic.',
- cls: 'x-html-editor-tip'
- },
- ...
-
- * @type Object
- */
- buttonTips : {
- bold : {
- title: 'Bold (Ctrl+B)',
- text: 'Make the selected text bold.',
- cls: 'x-html-editor-tip'
- },
- italic : {
- title: 'Italic (Ctrl+I)',
- text: 'Make the selected text italic.',
- cls: 'x-html-editor-tip'
- },
- underline : {
- title: 'Underline (Ctrl+U)',
- text: 'Underline the selected text.',
- cls: 'x-html-editor-tip'
- },
- increasefontsize : {
- title: 'Grow Text',
- text: 'Increase the font size.',
- cls: 'x-html-editor-tip'
- },
- decreasefontsize : {
- title: 'Shrink Text',
- text: 'Decrease the font size.',
- cls: 'x-html-editor-tip'
- },
- backcolor : {
- title: 'Text Highlight Color',
- text: 'Change the background color of the selected text.',
- cls: 'x-html-editor-tip'
- },
- forecolor : {
- title: 'Font Color',
- text: 'Change the color of the selected text.',
- cls: 'x-html-editor-tip'
- },
- justifyleft : {
- title: 'Align Text Left',
- text: 'Align text to the left.',
- cls: 'x-html-editor-tip'
- },
- justifycenter : {
- title: 'Center Text',
- text: 'Center text in the editor.',
- cls: 'x-html-editor-tip'
- },
- justifyright : {
- title: 'Align Text Right',
- text: 'Align text to the right.',
- cls: 'x-html-editor-tip'
- },
- insertunorderedlist : {
- title: 'Bullet List',
- text: 'Start a bulleted list.',
- cls: 'x-html-editor-tip'
- },
- insertorderedlist : {
- title: 'Numbered List',
- text: 'Start a numbered list.',
- cls: 'x-html-editor-tip'
- },
- createlink : {
- title: 'Hyperlink',
- text: 'Make the selected text a hyperlink.',
- cls: 'x-html-editor-tip'
- },
- sourceedit : {
- title: 'Source Edit',
- text: 'Switch to source editing mode.',
- cls: 'x-html-editor-tip'
- }
- }
-
- // hide stuff that is not compatible
- /**
- * @event blur
- * @hide
- */
- /**
- * @event change
- * @hide
- */
- /**
- * @event focus
- * @hide
- */
- /**
- * @event specialkey
- * @hide
- */
- /**
- * @cfg {String} fieldClass @hide
- */
- /**
- * @cfg {String} focusClass @hide
- */
- /**
- * @cfg {String} autoCreate @hide
- */
- /**
- * @cfg {String} inputType @hide
- */
- /**
- * @cfg {String} invalidClass @hide
- */
- /**
- * @cfg {String} invalidText @hide
- */
- /**
- * @cfg {String} msgFx @hide
- */
- /**
- * @cfg {String} validateOnBlur @hide
- */
- /**
- * @cfg {Boolean} allowDomMove @hide
- */
- /**
- * @cfg {String} applyTo @hide
- */
- /**
- * @cfg {String} autoHeight @hide
- */
- /**
- * @cfg {String} autoWidth @hide
- */
- /**
- * @cfg {String} cls @hide
- */
- /**
- * @cfg {String} disabled @hide
- */
- /**
- * @cfg {String} disabledClass @hide
- */
- /**
- * @cfg {String} msgTarget @hide
- */
- /**
- * @cfg {String} readOnly @hide
- */
- /**
- * @cfg {String} style @hide
- */
- /**
- * @cfg {String} validationDelay @hide
- */
- /**
- * @cfg {String} validationEvent @hide
- */
- /**
- * @cfg {String} tabIndex @hide
- */
- /**
- * @property disabled
- * @hide
- */
- /**
- * @method applyToMarkup
- * @hide
- */
- /**
- * @method disable
- * @hide
- */
- /**
- * @method enable
- * @hide
- */
- /**
- * @method validate
- * @hide
- */
- /**
- * @event valid
- * @hide
- */
- /**
- * @method setDisabled
- * @hide
- */
- /**
- * @cfg keys
- * @hide
- */
-});
-Ext.reg('htmleditor', Ext.form.HtmlEditor);/**
- * @class Ext.form.TimeField
- * @extends Ext.form.ComboBox
- * Provides a time input field with a time dropdown and automatic time validation. Example usage:
- *
-new Ext.form.TimeField({
- minValue: '9:00 AM',
- maxValue: '6:00 PM',
- increment: 30
-});
-
- * @constructor
- * Create a new TimeField
- * @param {Object} config
- * @xtype timefield
- */
-Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, {
- /**
- * @cfg {Date/String} minValue
- * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string
- * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null).
- */
- minValue : null,
- /**
- * @cfg {Date/String} maxValue
- * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string
- * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null).
- */
- maxValue : null,
- /**
- * @cfg {String} minText
- * The error text to display when the date in the cell is before minValue (defaults to
- * 'The time in this field must be equal to or after {0}').
- */
- minText : "The time in this field must be equal to or after {0}",
- /**
- * @cfg {String} maxText
- * The error text to display when the time is after maxValue (defaults to
- * 'The time in this field must be equal to or before {0}').
- */
- maxText : "The time in this field must be equal to or before {0}",
- /**
- * @cfg {String} invalidText
- * The error text to display when the time in the field is invalid (defaults to
- * '{value} is not a valid time').
- */
- invalidText : "{0} is not a valid time",
- /**
- * @cfg {String} format
- * The default time format string which can be overriden for localization support. The format must be
- * valid according to {@link Date#parseDate} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time
- * format try 'H:i' instead.
- */
- format : "g:i A",
- /**
- * @cfg {String} altFormats
- * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
- * format (defaults to 'g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H').
- */
- altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H",
- /**
- * @cfg {Number} increment
- * The number of minutes between each time value in the list (defaults to 15).
- */
- increment: 15,
-
- // private override
- mode: 'local',
- // private override
- triggerAction: 'all',
- // private override
- typeAhead: false,
-
- // private - This is the date to use when generating time values in the absence of either minValue
- // or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an
- // arbitrary "safe" date that can be any date aside from DST boundary dates.
- initDate: '1/1/2008',
-
- // private
- initComponent : function(){
- if(typeof this.minValue == "string"){
- this.minValue = this.parseDate(this.minValue);
- }
- if(typeof this.maxValue == "string"){
- this.maxValue = this.parseDate(this.maxValue);
- }
-
- if(!this.store){
- var min = this.parseDate(this.minValue) || new Date(this.initDate).clearTime();
- var max = this.parseDate(this.maxValue) || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1);
- var times = [];
- while(min <= max){
- times.push(min.dateFormat(this.format));
- min = min.add('mi', this.increment);
- }
- this.store = times;
- }
- Ext.form.TimeField.superclass.initComponent.call(this);
- },
-
- // inherited docs
- getValue : function(){
- var v = Ext.form.TimeField.superclass.getValue.call(this);
- return this.formatDate(this.parseDate(v)) || '';
- },
-
- // inherited docs
- setValue : function(value){
- return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value)));
- },
-
- // private overrides
- validateValue : Ext.form.DateField.prototype.validateValue,
- parseDate : Ext.form.DateField.prototype.parseDate,
- formatDate : Ext.form.DateField.prototype.formatDate,
-
- // private
- beforeBlur : function(){
- var v = this.parseDate(this.getRawValue());
- if(v){
- this.setValue(v.dateFormat(this.format));
- }
- Ext.form.TimeField.superclass.beforeBlur.call(this);
- }
-
- /**
- * @cfg {Boolean} grow @hide
- */
- /**
- * @cfg {Number} growMin @hide
- */
- /**
- * @cfg {Number} growMax @hide
- */
- /**
- * @hide
- * @method autoSize
- */
-});
Ext.reg('timefield', Ext.form.TimeField);/**
+ * @class Ext.form.SliderField
+ * @extends Ext.form.Field
+ * Wraps a {@link Ext.Slider Slider} so it can be used as a form field.
+ * @constructor
+ * Creates a new SliderField
+ * @param {Object} config Configuration options. Note that you can pass in any slider configuration options, as well as
+ * as any field configuration options.
+ * @xtype sliderfield
+ */
+Ext.form.SliderField = Ext.extend(Ext.form.Field, {
+
+ /**
+ * @cfg {Boolean} useTips
+ * True to use an Ext.slider.Tip to display tips for the value. Defaults to true.
+ */
+ useTips : true,
+
+ /**
+ * @cfg {Function} tipText
+ * A function used to display custom text for the slider tip. Defaults to null, which will
+ * use the default on the plugin.
+ */
+ tipText : null,
+
+ // private override
+ actionMode: 'wrap',
+
+ /**
+ * Initialize the component.
+ * @private
+ */
+ initComponent : function() {
+ var cfg = Ext.copyTo({
+ id: this.id + '-slider'
+ }, this.initialConfig, ['vertical', 'minValue', 'maxValue', 'decimalPrecision', 'keyIncrement', 'increment', 'clickToChange', 'animate']);
+
+ // only can use it if it exists.
+ if (this.useTips) {
+ var plug = this.tipText ? {getText: this.tipText} : {};
+ cfg.plugins = [new Ext.slider.Tip(plug)];
+ }
+ this.slider = new Ext.Slider(cfg);
+ Ext.form.SliderField.superclass.initComponent.call(this);
+ },
+
+ /**
+ * Set up the hidden field
+ * @param {Object} ct The container to render to.
+ * @param {Object} position The position in the container to render to.
+ * @private
+ */
+ onRender : function(ct, position){
+ this.autoCreate = {
+ id: this.id,
+ name: this.name,
+ type: 'hidden',
+ tag: 'input'
+ };
+ Ext.form.SliderField.superclass.onRender.call(this, ct, position);
+ this.wrap = this.el.wrap({cls: 'x-form-field-wrap'});
+ this.resizeEl = this.positionEl = this.wrap;
+ this.slider.render(this.wrap);
+ },
+
+ /**
+ * Ensure that the slider size is set automatically when the field resizes.
+ * @param {Object} w The width
+ * @param {Object} h The height
+ * @param {Object} aw The adjusted width
+ * @param {Object} ah The adjusted height
+ * @private
+ */
+ onResize : function(w, h, aw, ah){
+ Ext.form.SliderField.superclass.onResize.call(this, w, h, aw, ah);
+ this.slider.setSize(w, h);
+ },
+
+ /**
+ * Initialize any events for this class.
+ * @private
+ */
+ initEvents : function(){
+ Ext.form.SliderField.superclass.initEvents.call(this);
+ this.slider.on('change', this.onChange, this);
+ },
+
+ /**
+ * Utility method to set the value of the field when the slider changes.
+ * @param {Object} slider The slider object.
+ * @param {Object} v The new value.
+ * @private
+ */
+ onChange : function(slider, v){
+ this.setValue(v, undefined, true);
+ },
+
+ /**
+ * Enable the slider when the field is enabled.
+ * @private
+ */
+ onEnable : function(){
+ Ext.form.SliderField.superclass.onEnable.call(this);
+ this.slider.enable();
+ },
+
+ /**
+ * Disable the slider when the field is disabled.
+ * @private
+ */
+ onDisable : function(){
+ Ext.form.SliderField.superclass.onDisable.call(this);
+ this.slider.disable();
+ },
+
+ /**
+ * Ensure the slider is destroyed when the field is destroyed.
+ * @private
+ */
+ beforeDestroy : function(){
+ Ext.destroy(this.slider);
+ Ext.form.SliderField.superclass.beforeDestroy.call(this);
+ },
+
+ /**
+ * If a side icon is shown, do alignment to the slider
+ * @private
+ */
+ alignErrorIcon : function(){
+ this.errorIcon.alignTo(this.slider.el, 'tl-tr', [2, 0]);
+ },
+
+ /**
+ * Sets the minimum field value.
+ * @param {Number} v The new minimum value.
+ * @return {Ext.form.SliderField} this
+ */
+ setMinValue : function(v){
+ this.slider.setMinValue(v);
+ return this;
+ },
+
+ /**
+ * Sets the maximum field value.
+ * @param {Number} v The new maximum value.
+ * @return {Ext.form.SliderField} this
+ */
+ setMaxValue : function(v){
+ this.slider.setMaxValue(v);
+ return this;
+ },
+
+ /**
+ * Sets the value for this field.
+ * @param {Number} v The new value.
+ * @param {Boolean} animate (optional) Whether to animate the transition. If not specified, it will default to the animate config.
+ * @return {Ext.form.SliderField} this
+ */
+ setValue : function(v, animate, /* private */ silent){
+ // silent is used if the setValue method is invoked by the slider
+ // which means we don't need to set the value on the slider.
+ if(!silent){
+ this.slider.setValue(v, animate);
+ }
+ return Ext.form.SliderField.superclass.setValue.call(this, this.slider.getValue());
+ },
+
+ /**
+ * Gets the current value for this field.
+ * @return {Number} The current value.
+ */
+ getValue : function(){
+ return this.slider.getValue();
+ }
+});
+
+Ext.reg('sliderfield', Ext.form.SliderField);/**
* @class Ext.form.Label
* @extends Ext.BoxComponent
* Basic Label field.
@@ -7019,6 +8326,11 @@ Ext.form.Action.prototype = {
* during the time the action is being processed.
*/
+/**
+ * @cfg {Boolean} submitEmptyText If set to true, the emptyText value will be sent with the form
+ * when it is submitted. Defaults to true.
+ */
+
/**
* The type of action this Action instance performs.
* Currently only "submit" and "load" are supported.
@@ -7237,10 +8549,20 @@ Ext.extend(Ext.form.Action.Submit, Ext.form.Action, {
// private
run : function(){
- var o = this.options;
- var method = this.getMethod();
- var isGet = method == 'GET';
+ var o = this.options,
+ method = this.getMethod(),
+ isGet = method == 'GET';
if(o.clientValidation === false || this.form.isValid()){
+ if (o.submitEmptyText === false) {
+ var fields = this.form.items,
+ emptyFields = [];
+ fields.each(function(f) {
+ if (f.el.getValue() == f.emptyText) {
+ emptyFields.push(f);
+ f.el.dom.value = "";
+ }
+ });
+ }
Ext.Ajax.request(Ext.apply(this.createCallback(o), {
form:this.form.el.dom,
url:this.getUrl(isGet),
@@ -7249,6 +8571,13 @@ Ext.extend(Ext.form.Action.Submit, Ext.form.Action, {
params:!isGet ? this.getParams() : null,
isUpload: this.form.fileUpload
}));
+ if (o.submitEmptyText === false) {
+ Ext.each(emptyFields, function(f) {
+ if (f.applyEmptyText) {
+ f.applyEmptyText();
+ }
+ });
+ }
}else if (o.clientValidation !== false){ // client validation failed
this.failureType = Ext.form.Action.CLIENT_INVALID;
this.form.afterAction(this, false);
@@ -7264,8 +8593,8 @@ Ext.extend(Ext.form.Action.Submit, Ext.form.Action, {
}
if(result.errors){
this.form.markInvalid(result.errors);
- this.failureType = Ext.form.Action.SERVER_INVALID;
}
+ this.failureType = Ext.form.Action.SERVER_INVALID;
this.form.afterAction(this, false);
},
@@ -7328,7 +8657,7 @@ myFormPanel.{@link Ext.form.FormPanel#getForm getForm}().{@link Ext.form.BasicFo
params: {
consignmentRef: myConsignmentRef
},
- failure: function(form, action() {
+ failure: function(form, action) {
Ext.Msg.alert("Load failed", action.result.errorMessage);
}
});
@@ -7403,62 +8732,90 @@ Ext.extend(Ext.form.Action.Load, Ext.form.Action, {
/**
* @class Ext.form.Action.DirectLoad
* @extends Ext.form.Action.Load
- * Provides Ext.direct support for loading form data. This example illustrates usage
- * of Ext.Direct to load a submit a form through Ext.Direct.
+ * Provides Ext.direct support for loading form data.
+ *This example illustrates usage of Ext.Direct to load a form through Ext.Direct.
*
var myFormPanel = new Ext.form.FormPanel({
// configs for FormPanel
title: 'Basic Information',
- border: false,
+ renderTo: document.body,
+ width: 300, height: 160,
padding: 10,
- buttons:[{
- text: 'Submit',
- handler: function(){
- basicInfo.getForm().submit({
- params: {
- uid: 5
- }
- });
- }
- }],
-
+
// configs apply to child items
defaults: {anchor: '100%'},
defaultType: 'textfield',
- items: [
- // form fields go here
- ],
-
+ items: [{
+ fieldLabel: 'Name',
+ name: 'name'
+ },{
+ fieldLabel: 'Email',
+ name: 'email'
+ },{
+ fieldLabel: 'Company',
+ name: 'company'
+ }],
+
// configs for BasicForm
api: {
+ // The server-side method to call for load() requests
load: Profile.getBasicInfo,
// The server-side must mark the submit handler as a 'formHandler'
submit: Profile.updateBasicInfo
- },
- paramOrder: ['uid']
+ },
+ // specify the order for the passed params
+ paramOrder: ['uid', 'foo']
});
// load the form
myFormPanel.getForm().load({
+ // pass 2 arguments to server side getBasicInfo method (len=2)
params: {
- uid: 5
+ foo: 'bar',
+ uid: 34
}
});
+ *
+ * The data packet sent to the server will resemble something like:
+ *
+[
+ {
+ "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
+ "data":[34,"bar"] // note the order of the params
+ }
+]
+ *
+ * The form will process a data packet returned by the server that is similar
+ * to the following format:
+ *
+[
+ {
+ "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
+ "result":{
+ "success":true,
+ "data":{
+ "name":"Fred Flintstone",
+ "company":"Slate Rock and Gravel",
+ "email":"fred.flintstone@slaterg.com"
+ }
+ }
+ }
+]
*
*/
Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, {
- constructor: function(form, opts) {
+ constructor: function(form, opts) {
Ext.form.Action.DirectLoad.superclass.constructor.call(this, form, opts);
},
- type: 'directload',
-
+ type : 'directload',
+
run : function(){
var args = this.getParams();
- args.push(this.success, this);
+ args.push(this.success, this);
this.form.api.load.apply(window, args);
},
-
- getParams: function() {
+
+ getParams : function() {
var buf = [], o = {};
var bp = this.form.baseParams;
var p = this.options.params;
@@ -7476,23 +8833,114 @@ Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, {
// Direct actions have already been processed and therefore
// we can directly set the result; Direct Actions do not have
// a this.response property.
- processResponse: function(result) {
+ processResponse : function(result) {
this.result = result;
- return result;
+ return result;
+ },
+
+ success : function(response, trans){
+ if(trans.type == Ext.Direct.exceptions.SERVER){
+ response = {};
+ }
+ Ext.form.Action.DirectLoad.superclass.success.call(this, response);
}
});
/**
* @class Ext.form.Action.DirectSubmit
* @extends Ext.form.Action.Submit
- * Provides Ext.direct support for submitting form data.
- * See {@link Ext.form.Action.DirectLoad}.
+ * Provides Ext.direct support for submitting form data.
+ *This example illustrates usage of Ext.Direct to submit a form through Ext.Direct.
+ *
+var myFormPanel = new Ext.form.FormPanel({
+ // configs for FormPanel
+ title: 'Basic Information',
+ renderTo: document.body,
+ width: 300, height: 160,
+ padding: 10,
+ buttons:[{
+ text: 'Submit',
+ handler: function(){
+ myFormPanel.getForm().submit({
+ params: {
+ foo: 'bar',
+ uid: 34
+ }
+ });
+ }
+ }],
+
+ // configs apply to child items
+ defaults: {anchor: '100%'},
+ defaultType: 'textfield',
+ items: [{
+ fieldLabel: 'Name',
+ name: 'name'
+ },{
+ fieldLabel: 'Email',
+ name: 'email'
+ },{
+ fieldLabel: 'Company',
+ name: 'company'
+ }],
+
+ // configs for BasicForm
+ api: {
+ // The server-side method to call for load() requests
+ load: Profile.getBasicInfo,
+ // The server-side must mark the submit handler as a 'formHandler'
+ submit: Profile.updateBasicInfo
+ },
+ // specify the order for the passed params
+ paramOrder: ['uid', 'foo']
+});
+ *
+ * The data packet sent to the server will resemble something like:
+ *
+{
+ "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
+ "result":{
+ "success":true,
+ "id":{
+ "extAction":"Profile","extMethod":"updateBasicInfo",
+ "extType":"rpc","extTID":"6","extUpload":"false",
+ "name":"Aaron Conran","email":"aaron@extjs.com","company":"Ext JS, LLC"
+ }
+ }
+}
+ *
+ * The form will process a data packet returned by the server that is similar
+ * to the following:
+ *
+// sample success packet (batched requests)
+[
+ {
+ "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
+ "result":{
+ "success":true
+ }
+ }
+]
+
+// sample failure packet (one request)
+{
+ "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
+ "result":{
+ "errors":{
+ "email":"already taken"
+ },
+ "success":false,
+ "foo":"bar"
+ }
+}
+ *
+ * Also see the discussion in {@link Ext.form.Action.DirectLoad}.
*/
Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, {
- constructor: function(form, opts) {
+ constructor : function(form, opts) {
Ext.form.Action.DirectSubmit.superclass.constructor.call(this, form, opts);
},
- type: 'directsubmit',
+ type : 'directsubmit',
// override of Submit
run : function(){
var o = this.options;
@@ -7506,29 +8954,35 @@ Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, {
this.form.afterAction(this, false);
}
},
-
- getParams: function() {
+
+ getParams : function() {
var o = {};
var bp = this.form.baseParams;
var p = this.options.params;
Ext.apply(o, p, bp);
return o;
- },
+ },
// Direct actions have already been processed and therefore
// we can directly set the result; Direct Actions do not have
// a this.response property.
- processResponse: function(result) {
+ processResponse : function(result) {
this.result = result;
- return result;
+ return result;
+ },
+
+ success : function(response, trans){
+ if(trans.type == Ext.Direct.exceptions.SERVER){
+ response = {};
+ }
+ Ext.form.Action.DirectSubmit.superclass.success.call(this, response);
}
});
-
Ext.form.Action.ACTION_TYPES = {
'load' : Ext.form.Action.Load,
'submit' : Ext.form.Action.Submit,
- 'directload': Ext.form.Action.DirectLoad,
- 'directsubmit': Ext.form.Action.DirectSubmit
+ 'directload' : Ext.form.Action.DirectLoad,
+ 'directsubmit' : Ext.form.Action.DirectSubmit
};
/**
* @class Ext.form.VTypes
@@ -7551,7 +9005,7 @@ Ext.apply(Ext.form.VTypes, {
timeMask: /[\d\s:amp]/i
});
*
- * Another example:
+ * Another example:
*
// custom Vtype for vtype:'IPAddress'
Ext.apply(Ext.form.VTypes, {
@@ -7566,10 +9020,10 @@ Ext.apply(Ext.form.VTypes, {
*/
Ext.form.VTypes = function(){
// closure these in so they are only created once.
- var alpha = /^[a-zA-Z_]+$/;
- var alphanum = /^[a-zA-Z0-9_]+$/;
- var email = /^(\w+)([-+.][\w]+)*@(\w[-\w]*\.){1,5}([A-Za-z]){2,4}$/;
- var url = /(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@`~=%!]*)(\.\w{2,})?)*\/?)/i;
+ var alpha = /^[a-zA-Z_]+$/,
+ alphanum = /^[a-zA-Z0-9_]+$/,
+ email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
+ url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@`~=%!]*)(\.\w{2,})?)*\/?)/i;
// All these messages and functions are configurable
return {
@@ -7577,7 +9031,7 @@ Ext.form.VTypes = function(){
* The function used to validate email addresses. Note that this is a very basic validation -- complete
* validation per the email RFC specifications is very complex and beyond the scope of this class, although
* this function can be overridden if a more comprehensive validation scheme is desired. See the validation
- * section of the Wikipedia article on email addresses
+ * section of the Wikipedia article on email addresses
* for additional information. This implementation is intended to validate the following emails:
* 'barney@example.de', 'barney.rubble@example.com', 'barney-rubble@example.coop', 'barney+rubble@example.com'
* .
@@ -7594,12 +9048,12 @@ Ext.form.VTypes = function(){
*/
'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
/**
- * The keystroke filter mask to be applied on email input. See the {@link #email} method for
+ * The keystroke filter mask to be applied on email input. See the {@link #email} method for
* information about more complex email validation. Defaults to:
* /[a-z0-9_\.\-@]/i
* @type RegExp
*/
- 'emailMask' : /[a-z0-9_\.\-@]/i,
+ 'emailMask' : /[a-z0-9_\.\-@\+]/i,
/**
* The function used to validate URLs
@@ -7615,7 +9069,7 @@ Ext.form.VTypes = function(){
* @type String
*/
'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
-
+
/**
* The function used to validate alpha values
* @param {String} value The value
@@ -7658,4 +9112,4 @@ Ext.form.VTypes = function(){
*/
'alphanumMask' : /[a-z0-9_]/i
};
-}();
\ No newline at end of file
+}();