Forked mumble-django project from https://bitbucket.org/Svedrin/mumble-django
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9069 lines
309 KiB

  1. /*!
  2. * Ext JS Library 3.2.0
  3. * Copyright(c) 2006-2010 Ext JS, Inc.
  4. * licensing@extjs.com
  5. * http://www.extjs.com/license
  6. */
  7. /**
  8. * @class Ext.form.Field
  9. * @extends Ext.BoxComponent
  10. * Base class for form fields that provides default event handling, sizing, value handling and other functionality.
  11. * @constructor
  12. * Creates a new Field
  13. * @param {Object} config Configuration options
  14. * @xtype field
  15. */
  16. Ext.form.Field = Ext.extend(Ext.BoxComponent, {
  17. /**
  18. * <p>The label Element associated with this Field. <b>Only available after this Field has been rendered by a
  19. * {@link form Ext.layout.FormLayout} layout manager.</b></p>
  20. * @type Ext.Element
  21. * @property label
  22. */
  23. /**
  24. * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password, file (defaults
  25. * to 'text'). The types 'file' and 'password' must be used to render those field types currently -- there are
  26. * no separate Ext components for those. Note that if you use <tt>inputType:'file'</tt>, {@link #emptyText}
  27. * is not supported and should be avoided.
  28. */
  29. /**
  30. * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered,
  31. * not those which are built via applyTo (defaults to undefined).
  32. */
  33. /**
  34. * @cfg {Mixed} value A value to initialize this field with (defaults to undefined).
  35. */
  36. /**
  37. * @cfg {String} name The field's HTML name attribute (defaults to '').
  38. * <b>Note</b>: this property must be set if this field is to be automatically included with
  39. * {@link Ext.form.BasicForm#submit form submit()}.
  40. */
  41. /**
  42. * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to '').
  43. */
  44. /**
  45. * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to 'x-form-invalid')
  46. */
  47. invalidClass : 'x-form-invalid',
  48. /**
  49. * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided
  50. * (defaults to 'The value in this field is invalid')
  51. */
  52. invalidText : 'The value in this field is invalid',
  53. /**
  54. * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to 'x-form-focus')
  55. */
  56. focusClass : 'x-form-focus',
  57. /**
  58. * @cfg {Boolean} preventMark
  59. * <tt>true</tt> to disable {@link #markInvalid marking the field invalid}.
  60. * Defaults to <tt>false</tt>.
  61. */
  62. /**
  63. * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable
  64. automatic validation (defaults to 'keyup').
  65. */
  66. validationEvent : 'keyup',
  67. /**
  68. * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true).
  69. */
  70. validateOnBlur : true,
  71. /**
  72. * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation
  73. * is initiated (defaults to 250)
  74. */
  75. validationDelay : 250,
  76. /**
  77. * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
  78. * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
  79. * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
  80. * <pre><code>{tag: 'input', type: 'text', size: '20', autocomplete: 'off'}</code></pre>
  81. */
  82. defaultAutoCreate : {tag: 'input', type: 'text', size: '20', autocomplete: 'off'},
  83. /**
  84. * @cfg {String} fieldClass The default CSS class for the field (defaults to 'x-form-field')
  85. */
  86. fieldClass : 'x-form-field',
  87. /**
  88. * @cfg {String} msgTarget <p>The location where the message text set through {@link #markInvalid} should display.
  89. * Must be one of the following values:</p>
  90. * <div class="mdetail-params"><ul>
  91. * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
  92. * <div class="subdesc"><b>{@link Ext.QuickTips#init Ext.QuickTips.init} must have been called for this setting to work.</b></div</li>
  93. * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
  94. * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
  95. * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
  96. * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
  97. * </ul></div>
  98. */
  99. msgTarget : 'qtip',
  100. /**
  101. * @cfg {String} msgFx <b>Experimental</b> The effect used when displaying a validation message under the field
  102. * (defaults to 'normal').
  103. */
  104. msgFx : 'normal',
  105. /**
  106. * @cfg {Boolean} readOnly <tt>true</tt> to mark the field as readOnly in HTML
  107. * (defaults to <tt>false</tt>).
  108. * <br><p><b>Note</b>: this only sets the element's readOnly DOM attribute.
  109. * Setting <code>readOnly=true</code>, for example, will not disable triggering a
  110. * ComboBox or DateField; it gives you the option of forcing the user to choose
  111. * via the trigger without typing in the text box. To hide the trigger use
  112. * <code>{@link Ext.form.TriggerField#hideTrigger hideTrigger}</code>.</p>
  113. */
  114. readOnly : false,
  115. /**
  116. * @cfg {Boolean} disabled True to disable the field (defaults to false).
  117. * <p>Be aware that conformant with the <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.12.1">HTML specification</a>,
  118. * disabled Fields will not be {@link Ext.form.BasicForm#submit submitted}.</p>
  119. */
  120. disabled : false,
  121. /**
  122. * @cfg {Boolean} submitValue False to clear the name attribute on the field so that it is not submitted during a form post.
  123. * Defaults to <tt>true</tt>.
  124. */
  125. submitValue: true,
  126. // private
  127. isFormField : true,
  128. // private
  129. msgDisplay: '',
  130. // private
  131. hasFocus : false,
  132. // private
  133. initComponent : function(){
  134. Ext.form.Field.superclass.initComponent.call(this);
  135. this.addEvents(
  136. /**
  137. * @event focus
  138. * Fires when this field receives input focus.
  139. * @param {Ext.form.Field} this
  140. */
  141. 'focus',
  142. /**
  143. * @event blur
  144. * Fires when this field loses input focus.
  145. * @param {Ext.form.Field} this
  146. */
  147. 'blur',
  148. /**
  149. * @event specialkey
  150. * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.
  151. * To handle other keys see {@link Ext.Panel#keys} or {@link Ext.KeyMap}.
  152. * You can check {@link Ext.EventObject#getKey} to determine which key was pressed.
  153. * For example: <pre><code>
  154. var form = new Ext.form.FormPanel({
  155. ...
  156. items: [{
  157. fieldLabel: 'Field 1',
  158. name: 'field1',
  159. allowBlank: false
  160. },{
  161. fieldLabel: 'Field 2',
  162. name: 'field2',
  163. listeners: {
  164. specialkey: function(field, e){
  165. // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
  166. // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
  167. if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
  168. var form = field.ownerCt.getForm();
  169. form.submit();
  170. }
  171. }
  172. }
  173. }
  174. ],
  175. ...
  176. });
  177. * </code></pre>
  178. * @param {Ext.form.Field} this
  179. * @param {Ext.EventObject} e The event object
  180. */
  181. 'specialkey',
  182. /**
  183. * @event change
  184. * Fires just before the field blurs if the field value has changed.
  185. * @param {Ext.form.Field} this
  186. * @param {Mixed} newValue The new value
  187. * @param {Mixed} oldValue The original value
  188. */
  189. 'change',
  190. /**
  191. * @event invalid
  192. * Fires after the field has been marked as invalid.
  193. * @param {Ext.form.Field} this
  194. * @param {String} msg The validation message
  195. */
  196. 'invalid',
  197. /**
  198. * @event valid
  199. * Fires after the field has been validated with no errors.
  200. * @param {Ext.form.Field} this
  201. */
  202. 'valid'
  203. );
  204. },
  205. /**
  206. * Returns the {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
  207. * attribute of the field if available.
  208. * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
  209. */
  210. getName : function(){
  211. return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || '';
  212. },
  213. // private
  214. onRender : function(ct, position){
  215. if(!this.el){
  216. var cfg = this.getAutoCreate();
  217. if(!cfg.name){
  218. cfg.name = this.name || this.id;
  219. }
  220. if(this.inputType){
  221. cfg.type = this.inputType;
  222. }
  223. this.autoEl = cfg;
  224. }
  225. Ext.form.Field.superclass.onRender.call(this, ct, position);
  226. if(this.submitValue === false){
  227. this.el.dom.removeAttribute('name');
  228. }
  229. var type = this.el.dom.type;
  230. if(type){
  231. if(type == 'password'){
  232. type = 'text';
  233. }
  234. this.el.addClass('x-form-'+type);
  235. }
  236. if(this.readOnly){
  237. this.setReadOnly(true);
  238. }
  239. if(this.tabIndex !== undefined){
  240. this.el.dom.setAttribute('tabIndex', this.tabIndex);
  241. }
  242. this.el.addClass([this.fieldClass, this.cls]);
  243. },
  244. // private
  245. getItemCt : function(){
  246. return this.itemCt;
  247. },
  248. // private
  249. initValue : function(){
  250. if(this.value !== undefined){
  251. this.setValue(this.value);
  252. }else if(!Ext.isEmpty(this.el.dom.value) && this.el.dom.value != this.emptyText){
  253. this.setValue(this.el.dom.value);
  254. }
  255. /**
  256. * The original value of the field as configured in the {@link #value} configuration, or
  257. * as loaded by the last form load operation if the form's {@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
  258. * setting is <code>true</code>.
  259. * @type mixed
  260. * @property originalValue
  261. */
  262. this.originalValue = this.getValue();
  263. },
  264. /**
  265. * <p>Returns true if the value of this Field has been changed from its original value.
  266. * Will return false if the field is disabled or has not been rendered yet.</p>
  267. * <p>Note that if the owning {@link Ext.form.BasicForm form} was configured with
  268. * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
  269. * then the <i>original value</i> is updated when the values are loaded by
  270. * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#setValues setValues}.</p>
  271. * @return {Boolean} True if this field has been changed from its original value (and
  272. * is not disabled), false otherwise.
  273. */
  274. isDirty : function() {
  275. if(this.disabled || !this.rendered) {
  276. return false;
  277. }
  278. return String(this.getValue()) !== String(this.originalValue);
  279. },
  280. /**
  281. * Sets the read only state of this field.
  282. * @param {Boolean} readOnly Whether the field should be read only.
  283. */
  284. setReadOnly : function(readOnly){
  285. if(this.rendered){
  286. this.el.dom.readOnly = readOnly;
  287. }
  288. this.readOnly = readOnly;
  289. },
  290. // private
  291. afterRender : function(){
  292. Ext.form.Field.superclass.afterRender.call(this);
  293. this.initEvents();
  294. this.initValue();
  295. },
  296. // private
  297. fireKey : function(e){
  298. if(e.isSpecialKey()){
  299. this.fireEvent('specialkey', this, e);
  300. }
  301. },
  302. /**
  303. * Resets the current field value to the originally loaded value and clears any validation messages.
  304. * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
  305. */
  306. reset : function(){
  307. this.setValue(this.originalValue);
  308. this.clearInvalid();
  309. },
  310. // private
  311. initEvents : function(){
  312. this.mon(this.el, Ext.EventManager.useKeydown ? 'keydown' : 'keypress', this.fireKey, this);
  313. this.mon(this.el, 'focus', this.onFocus, this);
  314. // standardise buffer across all browsers + OS-es for consistent event order.
  315. // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
  316. this.mon(this.el, 'blur', this.onBlur, this, this.inEditor ? {buffer:10} : null);
  317. },
  318. // private
  319. preFocus: Ext.emptyFn,
  320. // private
  321. onFocus : function(){
  322. this.preFocus();
  323. if(this.focusClass){
  324. this.el.addClass(this.focusClass);
  325. }
  326. if(!this.hasFocus){
  327. this.hasFocus = true;
  328. /**
  329. * <p>The value that the Field had at the time it was last focused. This is the value that is passed
  330. * to the {@link #change} event which is fired if the value has been changed when the Field is blurred.</p>
  331. * <p><b>This will be undefined until the Field has been visited.</b> Compare {@link #originalValue}.</p>
  332. * @type mixed
  333. * @property startValue
  334. */
  335. this.startValue = this.getValue();
  336. this.fireEvent('focus', this);
  337. }
  338. },
  339. // private
  340. beforeBlur : Ext.emptyFn,
  341. // private
  342. onBlur : function(){
  343. this.beforeBlur();
  344. if(this.focusClass){
  345. this.el.removeClass(this.focusClass);
  346. }
  347. this.hasFocus = false;
  348. if(this.validationEvent !== false && (this.validateOnBlur || this.validationEvent == 'blur')){
  349. this.validate();
  350. }
  351. var v = this.getValue();
  352. if(String(v) !== String(this.startValue)){
  353. this.fireEvent('change', this, v, this.startValue);
  354. }
  355. this.fireEvent('blur', this);
  356. this.postBlur();
  357. },
  358. // private
  359. postBlur : Ext.emptyFn,
  360. /**
  361. * Returns whether or not the field value is currently valid by
  362. * {@link #validateValue validating} the {@link #processValue processed value}
  363. * of the field. <b>Note</b>: {@link #disabled} fields are ignored.
  364. * @param {Boolean} preventMark True to disable marking the field invalid
  365. * @return {Boolean} True if the value is valid, else false
  366. */
  367. isValid : function(preventMark){
  368. if(this.disabled){
  369. return true;
  370. }
  371. var restore = this.preventMark;
  372. this.preventMark = preventMark === true;
  373. var v = this.validateValue(this.processValue(this.getRawValue()));
  374. this.preventMark = restore;
  375. return v;
  376. },
  377. /**
  378. * Validates the field value
  379. * @return {Boolean} True if the value is valid, else false
  380. */
  381. validate : function(){
  382. if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
  383. this.clearInvalid();
  384. return true;
  385. }
  386. return false;
  387. },
  388. /**
  389. * This method should only be overridden if necessary to prepare raw values
  390. * for validation (see {@link #validate} and {@link #isValid}). This method
  391. * is expected to return the processed value for the field which will
  392. * be used for validation (see validateValue method).
  393. * @param {Mixed} value
  394. */
  395. processValue : function(value){
  396. return value;
  397. },
  398. /**
  399. * Uses getErrors to build an array of validation errors. If any errors are found, markInvalid is called
  400. * with the first and false is returned, otherwise true is returned. Previously, subclasses were invited
  401. * to provide an implementation of this to process validations - from 3.2 onwards getErrors should be
  402. * overridden instead.
  403. * @param {Mixed} The current value of the field
  404. * @return {Boolean} True if all validations passed, false if one or more failed
  405. */
  406. validateValue : function(value) {
  407. //currently, we only show 1 error at a time for a field, so just use the first one
  408. var error = this.getErrors(value)[0];
  409. if (error == undefined) {
  410. return true;
  411. } else {
  412. this.markInvalid(error);
  413. return false;
  414. }
  415. },
  416. /**
  417. * Runs this field's validators and returns an array of error messages for any validation failures.
  418. * This is called internally during validation and would not usually need to be used manually.
  419. * Each subclass should override or augment the return value to provide their own errors
  420. * @return {Array} All error messages for this field
  421. */
  422. getErrors: function() {
  423. return [];
  424. },
  425. /**
  426. * Gets the active error message for this field.
  427. * @return {String} Returns the active error message on the field, if there is no error, an empty string is returned.
  428. */
  429. getActiveError : function(){
  430. return this.activeError || '';
  431. },
  432. /**
  433. * <p>Display an error message associated with this field, using {@link #msgTarget} to determine how to
  434. * display the message and applying {@link #invalidClass} to the field's UI element.</p>
  435. * <p><b>Note</b>: this method does not cause the Field's {@link #validate} method to return <code>false</code>
  436. * if the value does <i>pass</i> validation. So simply marking a Field as invalid will not prevent
  437. * submission of forms submitted with the {@link Ext.form.Action.Submit#clientValidation} option set.</p>
  438. * {@link #isValid invalid}.
  439. * @param {String} msg (optional) The validation message (defaults to {@link #invalidText})
  440. */
  441. markInvalid : function(msg){
  442. //don't set the error icon if we're not rendered or marking is prevented
  443. if (this.rendered && !this.preventMark) {
  444. msg = msg || this.invalidText;
  445. var mt = this.getMessageHandler();
  446. if(mt){
  447. mt.mark(this, msg);
  448. }else if(this.msgTarget){
  449. this.el.addClass(this.invalidClass);
  450. var t = Ext.getDom(this.msgTarget);
  451. if(t){
  452. t.innerHTML = msg;
  453. t.style.display = this.msgDisplay;
  454. }
  455. }
  456. }
  457. this.setActiveError(msg);
  458. },
  459. /**
  460. * Clear any invalid styles/messages for this field
  461. */
  462. clearInvalid : function(){
  463. //don't remove the error icon if we're not rendered or marking is prevented
  464. if (this.rendered && !this.preventMark) {
  465. this.el.removeClass(this.invalidClass);
  466. var mt = this.getMessageHandler();
  467. if(mt){
  468. mt.clear(this);
  469. }else if(this.msgTarget){
  470. this.el.removeClass(this.invalidClass);
  471. var t = Ext.getDom(this.msgTarget);
  472. if(t){
  473. t.innerHTML = '';
  474. t.style.display = 'none';
  475. }
  476. }
  477. }
  478. this.unsetActiveError();
  479. },
  480. /**
  481. * Sets the current activeError to the given string. Fires the 'invalid' event.
  482. * This does not set up the error icon, only sets the message and fires the event. To show the error icon,
  483. * use markInvalid instead, which calls this method internally
  484. * @param {String} msg The error message
  485. * @param {Boolean} suppressEvent True to suppress the 'invalid' event from being fired
  486. */
  487. setActiveError: function(msg, suppressEvent) {
  488. this.activeError = msg;
  489. if (suppressEvent !== true) this.fireEvent('invalid', this, msg);
  490. },
  491. /**
  492. * Clears the activeError and fires the 'valid' event. This is called internally by clearInvalid and would not
  493. * usually need to be called manually
  494. * @param {Boolean} suppressEvent True to suppress the 'invalid' event from being fired
  495. */
  496. unsetActiveError: function(suppressEvent) {
  497. delete this.activeError;
  498. if (suppressEvent !== true) this.fireEvent('valid', this);
  499. },
  500. // private
  501. getMessageHandler : function(){
  502. return Ext.form.MessageTargets[this.msgTarget];
  503. },
  504. // private
  505. getErrorCt : function(){
  506. return this.el.findParent('.x-form-element', 5, true) || // use form element wrap if available
  507. this.el.findParent('.x-form-field-wrap', 5, true); // else direct field wrap
  508. },
  509. // Alignment for 'under' target
  510. alignErrorEl : function(){
  511. this.errorEl.setWidth(this.getErrorCt().getWidth(true) - 20);
  512. },
  513. // Alignment for 'side' target
  514. alignErrorIcon : function(){
  515. this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]);
  516. },
  517. /**
  518. * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}.
  519. * @return {Mixed} value The field value
  520. */
  521. getRawValue : function(){
  522. var v = this.rendered ? this.el.getValue() : Ext.value(this.value, '');
  523. if(v === this.emptyText){
  524. v = '';
  525. }
  526. return v;
  527. },
  528. /**
  529. * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}.
  530. * @return {Mixed} value The field value
  531. */
  532. getValue : function(){
  533. if(!this.rendered) {
  534. return this.value;
  535. }
  536. var v = this.el.getValue();
  537. if(v === this.emptyText || v === undefined){
  538. v = '';
  539. }
  540. return v;
  541. },
  542. /**
  543. * Sets the underlying DOM field's value directly, bypassing validation. To set the value with validation see {@link #setValue}.
  544. * @param {Mixed} value The value to set
  545. * @return {Mixed} value The field value that is set
  546. */
  547. setRawValue : function(v){
  548. return this.rendered ? (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)) : '';
  549. },
  550. /**
  551. * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}.
  552. * @param {Mixed} value The value to set
  553. * @return {Ext.form.Field} this
  554. */
  555. setValue : function(v){
  556. this.value = v;
  557. if(this.rendered){
  558. this.el.dom.value = (Ext.isEmpty(v) ? '' : v);
  559. this.validate();
  560. }
  561. return this;
  562. },
  563. // private, does not work for all fields
  564. append : function(v){
  565. this.setValue([this.getValue(), v].join(''));
  566. }
  567. /**
  568. * @cfg {Boolean} autoWidth @hide
  569. */
  570. /**
  571. * @cfg {Boolean} autoHeight @hide
  572. */
  573. /**
  574. * @cfg {String} autoEl @hide
  575. */
  576. });
  577. Ext.form.MessageTargets = {
  578. 'qtip' : {
  579. mark: function(field, msg){
  580. field.el.addClass(field.invalidClass);
  581. field.el.dom.qtip = msg;
  582. field.el.dom.qclass = 'x-form-invalid-tip';
  583. if(Ext.QuickTips){ // fix for floating editors interacting with DND
  584. Ext.QuickTips.enable();
  585. }
  586. },
  587. clear: function(field){
  588. field.el.removeClass(field.invalidClass);
  589. field.el.dom.qtip = '';
  590. }
  591. },
  592. 'title' : {
  593. mark: function(field, msg){
  594. field.el.addClass(field.invalidClass);
  595. field.el.dom.title = msg;
  596. },
  597. clear: function(field){
  598. field.el.dom.title = '';
  599. }
  600. },
  601. 'under' : {
  602. mark: function(field, msg){
  603. field.el.addClass(field.invalidClass);
  604. if(!field.errorEl){
  605. var elp = field.getErrorCt();
  606. if(!elp){ // field has no container el
  607. field.el.dom.title = msg;
  608. return;
  609. }
  610. field.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
  611. field.on('resize', field.alignErrorEl, field);
  612. field.on('destroy', function(){
  613. Ext.destroy(this.errorEl);
  614. }, field);
  615. }
  616. field.alignErrorEl();
  617. field.errorEl.update(msg);
  618. Ext.form.Field.msgFx[field.msgFx].show(field.errorEl, field);
  619. },
  620. clear: function(field){
  621. field.el.removeClass(field.invalidClass);
  622. if(field.errorEl){
  623. Ext.form.Field.msgFx[field.msgFx].hide(field.errorEl, field);
  624. }else{
  625. field.el.dom.title = '';
  626. }
  627. }
  628. },
  629. 'side' : {
  630. mark: function(field, msg){
  631. field.el.addClass(field.invalidClass);
  632. if(!field.errorIcon){
  633. var elp = field.getErrorCt();
  634. // field has no container el
  635. if(!elp){
  636. field.el.dom.title = msg;
  637. return;
  638. }
  639. field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
  640. if (field.ownerCt) {
  641. field.ownerCt.on('afterlayout', field.alignErrorIcon, field);
  642. field.ownerCt.on('expand', field.alignErrorIcon, field);
  643. }
  644. field.on('resize', field.alignErrorIcon, field);
  645. field.on('destroy', function(){
  646. Ext.destroy(this.errorIcon);
  647. }, field);
  648. }
  649. field.alignErrorIcon();
  650. field.errorIcon.dom.qtip = msg;
  651. field.errorIcon.dom.qclass = 'x-form-invalid-tip';
  652. field.errorIcon.show();
  653. },
  654. clear: function(field){
  655. field.el.removeClass(field.invalidClass);
  656. if(field.errorIcon){
  657. field.errorIcon.dom.qtip = '';
  658. field.errorIcon.hide();
  659. }else{
  660. field.el.dom.title = '';
  661. }
  662. }
  663. }
  664. };
  665. // anything other than normal should be considered experimental
  666. Ext.form.Field.msgFx = {
  667. normal : {
  668. show: function(msgEl, f){
  669. msgEl.setDisplayed('block');
  670. },
  671. hide : function(msgEl, f){
  672. msgEl.setDisplayed(false).update('');
  673. }
  674. },
  675. slide : {
  676. show: function(msgEl, f){
  677. msgEl.slideIn('t', {stopFx:true});
  678. },
  679. hide : function(msgEl, f){
  680. msgEl.slideOut('t', {stopFx:true,useDisplay:true});
  681. }
  682. },
  683. slideRight : {
  684. show: function(msgEl, f){
  685. msgEl.fixDisplay();
  686. msgEl.alignTo(f.el, 'tl-tr');
  687. msgEl.slideIn('l', {stopFx:true});
  688. },
  689. hide : function(msgEl, f){
  690. msgEl.slideOut('l', {stopFx:true,useDisplay:true});
  691. }
  692. }
  693. };
  694. Ext.reg('field', Ext.form.Field);
  695. /**
  696. * @class Ext.form.TextField
  697. * @extends Ext.form.Field
  698. * <p>Basic text field. Can be used as a direct replacement for traditional text inputs,
  699. * or as the base class for more sophisticated input controls (like {@link Ext.form.TextArea}
  700. * and {@link Ext.form.ComboBox}).</p>
  701. * <p><b><u>Validation</u></b></p>
  702. * <p>The validation procedure is described in the documentation for {@link #validateValue}.</p>
  703. * <p><b><u>Alter Validation Behavior</u></b></p>
  704. * <p>Validation behavior for each field can be configured:</p>
  705. * <div class="mdetail-params"><ul>
  706. * <li><code>{@link Ext.form.TextField#invalidText invalidText}</code> : the default validation message to
  707. * show if any validation step above does not provide a message when invalid</li>
  708. * <li><code>{@link Ext.form.TextField#maskRe maskRe}</code> : filter out keystrokes before any validation occurs</li>
  709. * <li><code>{@link Ext.form.TextField#stripCharsRe stripCharsRe}</code> : filter characters after being typed in,
  710. * but before being validated</li>
  711. * <li><code>{@link Ext.form.Field#invalidClass invalidClass}</code> : alternate style when invalid</li>
  712. * <li><code>{@link Ext.form.Field#validateOnBlur validateOnBlur}</code>,
  713. * <code>{@link Ext.form.Field#validationDelay validationDelay}</code>, and
  714. * <code>{@link Ext.form.Field#validationEvent validationEvent}</code> : modify how/when validation is triggered</li>
  715. * </ul></div>
  716. *
  717. * @constructor Creates a new TextField
  718. * @param {Object} config Configuration options
  719. *
  720. * @xtype textfield
  721. */
  722. Ext.form.TextField = Ext.extend(Ext.form.Field, {
  723. /**
  724. * @cfg {String} vtypeText A custom error message to display in place of the default message provided
  725. * for the <b><code>{@link #vtype}</code></b> currently set for this field (defaults to <tt>''</tt>). <b>Note</b>:
  726. * only applies if <b><code>{@link #vtype}</code></b> is set, else ignored.
  727. */
  728. /**
  729. * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value
  730. * before validation (defaults to <tt>null</tt>).
  731. */
  732. /**
  733. * @cfg {Boolean} grow <tt>true</tt> if this field should automatically grow and shrink to its content
  734. * (defaults to <tt>false</tt>)
  735. */
  736. grow : false,
  737. /**
  738. * @cfg {Number} growMin The minimum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
  739. * to <tt>30</tt>)
  740. */
  741. growMin : 30,
  742. /**
  743. * @cfg {Number} growMax The maximum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
  744. * to <tt>800</tt>)
  745. */
  746. growMax : 800,
  747. /**
  748. * @cfg {String} vtype A validation type name as defined in {@link Ext.form.VTypes} (defaults to <tt>null</tt>)
  749. */
  750. vtype : null,
  751. /**
  752. * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do
  753. * not match (defaults to <tt>null</tt>)
  754. */
  755. maskRe : null,
  756. /**
  757. * @cfg {Boolean} disableKeyFilter Specify <tt>true</tt> to disable input keystroke filtering (defaults
  758. * to <tt>false</tt>)
  759. */
  760. disableKeyFilter : false,
  761. /**
  762. * @cfg {Boolean} allowBlank Specify <tt>false</tt> to validate that the value's length is > 0 (defaults to
  763. * <tt>true</tt>)
  764. */
  765. allowBlank : true,
  766. /**
  767. * @cfg {Number} minLength Minimum input field length required (defaults to <tt>0</tt>)
  768. */
  769. minLength : 0,
  770. /**
  771. * @cfg {Number} maxLength Maximum input field length allowed by validation (defaults to Number.MAX_VALUE).
  772. * This behavior is intended to provide instant feedback to the user by improving usability to allow pasting
  773. * and editing or overtyping and back tracking. To restrict the maximum number of characters that can be
  774. * entered into the field use <tt><b>{@link Ext.form.Field#autoCreate autoCreate}</b></tt> to add
  775. * any attributes you want to a field, for example:<pre><code>
  776. var myField = new Ext.form.NumberField({
  777. id: 'mobile',
  778. anchor:'90%',
  779. fieldLabel: 'Mobile',
  780. maxLength: 16, // for validation
  781. autoCreate: {tag: 'input', type: 'text', size: '20', autocomplete: 'off', maxlength: '10'}
  782. });
  783. </code></pre>
  784. */
  785. maxLength : Number.MAX_VALUE,
  786. /**
  787. * @cfg {String} minLengthText Error text to display if the <b><tt>{@link #minLength minimum length}</tt></b>
  788. * validation fails (defaults to <tt>'The minimum length for this field is {minLength}'</tt>)
  789. */
  790. minLengthText : 'The minimum length for this field is {0}',
  791. /**
  792. * @cfg {String} maxLengthText Error text to display if the <b><tt>{@link #maxLength maximum length}</tt></b>
  793. * validation fails (defaults to <tt>'The maximum length for this field is {maxLength}'</tt>)
  794. */
  795. maxLengthText : 'The maximum length for this field is {0}',
  796. /**
  797. * @cfg {Boolean} selectOnFocus <tt>true</tt> to automatically select any existing field text when the field
  798. * receives input focus (defaults to <tt>false</tt>)
  799. */
  800. selectOnFocus : false,
  801. /**
  802. * @cfg {String} blankText The error text to display if the <b><tt>{@link #allowBlank}</tt></b> validation
  803. * fails (defaults to <tt>'This field is required'</tt>)
  804. */
  805. blankText : 'This field is required',
  806. /**
  807. * @cfg {Function} validator
  808. * <p>A custom validation function to be called during field validation ({@link #validateValue})
  809. * (defaults to <tt>null</tt>). If specified, this function will be called first, allowing the
  810. * developer to override the default validation process.</p>
  811. * <br><p>This function will be passed the following Parameters:</p>
  812. * <div class="mdetail-params"><ul>
  813. * <li><code>value</code>: <i>Mixed</i>
  814. * <div class="sub-desc">The current field value</div></li>
  815. * </ul></div>
  816. * <br><p>This function is to Return:</p>
  817. * <div class="mdetail-params"><ul>
  818. * <li><code>true</code>: <i>Boolean</i>
  819. * <div class="sub-desc"><code>true</code> if the value is valid</div></li>
  820. * <li><code>msg</code>: <i>String</i>
  821. * <div class="sub-desc">An error message if the value is invalid</div></li>
  822. * </ul></div>
  823. */
  824. validator : null,
  825. /**
  826. * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation
  827. * (defaults to <tt>null</tt>). If the test fails, the field will be marked invalid using
  828. * <b><tt>{@link #regexText}</tt></b>.
  829. */
  830. regex : null,
  831. /**
  832. * @cfg {String} regexText The error text to display if <b><tt>{@link #regex}</tt></b> is used and the
  833. * test fails during validation (defaults to <tt>''</tt>)
  834. */
  835. regexText : '',
  836. /**
  837. * @cfg {String} emptyText The default text to place into an empty field (defaults to <tt>null</tt>).
  838. * <b>Note</b>: that this value will be submitted to the server if this field is enabled and configured
  839. * with a {@link #name}.
  840. */
  841. emptyText : null,
  842. /**
  843. * @cfg {String} emptyClass The CSS class to apply to an empty field to style the <b><tt>{@link #emptyText}</tt></b>
  844. * (defaults to <tt>'x-form-empty-field'</tt>). This class is automatically added and removed as needed
  845. * depending on the current field value.
  846. */
  847. emptyClass : 'x-form-empty-field',
  848. /**
  849. * @cfg {Boolean} enableKeyEvents <tt>true</tt> to enable the proxying of key events for the HTML input
  850. * field (defaults to <tt>false</tt>)
  851. */
  852. initComponent : function(){
  853. Ext.form.TextField.superclass.initComponent.call(this);
  854. this.addEvents(
  855. /**
  856. * @event autosize
  857. * Fires when the <tt><b>{@link #autoSize}</b></tt> function is triggered. The field may or
  858. * may not have actually changed size according to the default logic, but this event provides
  859. * a hook for the developer to apply additional logic at runtime to resize the field if needed.
  860. * @param {Ext.form.Field} this This text field
  861. * @param {Number} width The new field width
  862. */
  863. 'autosize',
  864. /**
  865. * @event keydown
  866. * Keydown input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
  867. * is set to true.
  868. * @param {Ext.form.TextField} this This text field
  869. * @param {Ext.EventObject} e
  870. */
  871. 'keydown',
  872. /**
  873. * @event keyup
  874. * Keyup input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
  875. * is set to true.
  876. * @param {Ext.form.TextField} this This text field
  877. * @param {Ext.EventObject} e
  878. */
  879. 'keyup',
  880. /**
  881. * @event keypress
  882. * Keypress input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
  883. * is set to true.
  884. * @param {Ext.form.TextField} this This text field
  885. * @param {Ext.EventObject} e
  886. */
  887. 'keypress'
  888. );
  889. },
  890. // private
  891. initEvents : function(){
  892. Ext.form.TextField.superclass.initEvents.call(this);
  893. if(this.validationEvent == 'keyup'){
  894. this.validationTask = new Ext.util.DelayedTask(this.validate, this);
  895. this.mon(this.el, 'keyup', this.filterValidation, this);
  896. }
  897. else if(this.validationEvent !== false && this.validationEvent != 'blur'){
  898. this.mon(this.el, this.validationEvent, this.validate, this, {buffer: this.validationDelay});
  899. }
  900. if(this.selectOnFocus || this.emptyText){
  901. this.mon(this.el, 'mousedown', this.onMouseDown, this);
  902. if(this.emptyText){
  903. this.applyEmptyText();
  904. }
  905. }
  906. if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Ext.form.VTypes[this.vtype+'Mask']))){
  907. this.mon(this.el, 'keypress', this.filterKeys, this);
  908. }
  909. if(this.grow){
  910. this.mon(this.el, 'keyup', this.onKeyUpBuffered, this, {buffer: 50});
  911. this.mon(this.el, 'click', this.autoSize, this);
  912. }
  913. if(this.enableKeyEvents){
  914. this.mon(this.el, {
  915. scope: this,
  916. keyup: this.onKeyUp,
  917. keydown: this.onKeyDown,
  918. keypress: this.onKeyPress
  919. });
  920. }
  921. },
  922. onMouseDown: function(e){
  923. if(!this.hasFocus){
  924. this.mon(this.el, 'mouseup', Ext.emptyFn, this, { single: true, preventDefault: true });
  925. }
  926. },
  927. processValue : function(value){
  928. if(this.stripCharsRe){
  929. var newValue = value.replace(this.stripCharsRe, '');
  930. if(newValue !== value){
  931. this.setRawValue(newValue);
  932. return newValue;
  933. }
  934. }
  935. return value;
  936. },
  937. filterValidation : function(e){
  938. if(!e.isNavKeyPress()){
  939. this.validationTask.delay(this.validationDelay);
  940. }
  941. },
  942. //private
  943. onDisable: function(){
  944. Ext.form.TextField.superclass.onDisable.call(this);
  945. if(Ext.isIE){
  946. this.el.dom.unselectable = 'on';
  947. }
  948. },
  949. //private
  950. onEnable: function(){
  951. Ext.form.TextField.superclass.onEnable.call(this);
  952. if(Ext.isIE){
  953. this.el.dom.unselectable = '';
  954. }
  955. },
  956. // private
  957. onKeyUpBuffered : function(e){
  958. if(this.doAutoSize(e)){
  959. this.autoSize();
  960. }
  961. },
  962. // private
  963. doAutoSize : function(e){
  964. return !e.isNavKeyPress();
  965. },
  966. // private
  967. onKeyUp : function(e){
  968. this.fireEvent('keyup', this, e);
  969. },
  970. // private
  971. onKeyDown : function(e){
  972. this.fireEvent('keydown', this, e);
  973. },
  974. // private
  975. onKeyPress : function(e){
  976. this.fireEvent('keypress', this, e);
  977. },
  978. /**
  979. * Resets the current field value to the originally-loaded value and clears any validation messages.
  980. * Also adds <tt><b>{@link #emptyText}</b></tt> and <tt><b>{@link #emptyClass}</b></tt> if the
  981. * original value was blank.
  982. */
  983. reset : function(){
  984. Ext.form.TextField.superclass.reset.call(this);
  985. this.applyEmptyText();
  986. },
  987. applyEmptyText : function(){
  988. if(this.rendered && this.emptyText && this.getRawValue().length < 1 && !this.hasFocus){
  989. this.setRawValue(this.emptyText);
  990. this.el.addClass(this.emptyClass);
  991. }
  992. },
  993. // private
  994. preFocus : function(){
  995. var el = this.el;
  996. if(this.emptyText){
  997. if(el.dom.value == this.emptyText){
  998. this.setRawValue('');
  999. }
  1000. el.removeClass(this.emptyClass);
  1001. }
  1002. if(this.selectOnFocus){
  1003. el.dom.select();
  1004. }
  1005. },
  1006. // private
  1007. postBlur : function(){
  1008. this.applyEmptyText();
  1009. },
  1010. // private
  1011. filterKeys : function(e){
  1012. if(e.ctrlKey){
  1013. return;
  1014. }
  1015. var k = e.getKey();
  1016. if(Ext.isGecko && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){
  1017. return;
  1018. }
  1019. var cc = String.fromCharCode(e.getCharCode());
  1020. if(!Ext.isGecko && e.isSpecialKey() && !cc){
  1021. return;
  1022. }
  1023. if(!this.maskRe.test(cc)){
  1024. e.stopEvent();
  1025. }
  1026. },
  1027. setValue : function(v){
  1028. if(this.emptyText && this.el && !Ext.isEmpty(v)){
  1029. this.el.removeClass(this.emptyClass);
  1030. }
  1031. Ext.form.TextField.superclass.setValue.apply(this, arguments);
  1032. this.applyEmptyText();
  1033. this.autoSize();
  1034. return this;
  1035. },
  1036. /**
  1037. * <p>Validates a value according to the field's validation rules and returns an array of errors
  1038. * for any failing validations. Validation rules are processed in the following order:</p>
  1039. * <div class="mdetail-params"><ul>
  1040. *
  1041. * <li><b>1. Field specific validator</b>
  1042. * <div class="sub-desc">
  1043. * <p>A validator offers a way to customize and reuse a validation specification.
  1044. * If a field is configured with a <code>{@link #validator}</code>
  1045. * function, it will be passed the current field value. The <code>{@link #validator}</code>
  1046. * function is expected to return either:
  1047. * <div class="mdetail-params"><ul>
  1048. * <li>Boolean <tt>true</tt> if the value is valid (validation continues).</li>
  1049. * <li>a String to represent the invalid message if invalid (validation halts).</li>
  1050. * </ul></div>
  1051. * </div></li>
  1052. *
  1053. * <li><b>2. Basic Validation</b>
  1054. * <div class="sub-desc">
  1055. * <p>If the <code>{@link #validator}</code> has not halted validation,
  1056. * basic validation proceeds as follows:</p>
  1057. *
  1058. * <div class="mdetail-params"><ul>
  1059. *
  1060. * <li><code>{@link #allowBlank}</code> : (Invalid message =
  1061. * <code>{@link #emptyText}</code>)<div class="sub-desc">
  1062. * Depending on the configuration of <code>{@link #allowBlank}</code>, a
  1063. * blank field will cause validation to halt at this step and return
  1064. * Boolean true or false accordingly.
  1065. * </div></li>
  1066. *
  1067. * <li><code>{@link #minLength}</code> : (Invalid message =
  1068. * <code>{@link #minLengthText}</code>)<div class="sub-desc">
  1069. * If the passed value does not satisfy the <code>{@link #minLength}</code>
  1070. * specified, validation halts.
  1071. * </div></li>
  1072. *
  1073. * <li><code>{@link #maxLength}</code> : (Invalid message =
  1074. * <code>{@link #maxLengthText}</code>)<div class="sub-desc">
  1075. * If the passed value does not satisfy the <code>{@link #maxLength}</code>
  1076. * specified, validation halts.
  1077. * </div></li>
  1078. *
  1079. * </ul></div>
  1080. * </div></li>
  1081. *
  1082. * <li><b>3. Preconfigured Validation Types (VTypes)</b>
  1083. * <div class="sub-desc">
  1084. * <p>If none of the prior validation steps halts validation, a field
  1085. * configured with a <code>{@link #vtype}</code> will utilize the
  1086. * corresponding {@link Ext.form.VTypes VTypes} validation function.
  1087. * If invalid, either the field's <code>{@link #vtypeText}</code> or
  1088. * the VTypes vtype Text property will be used for the invalid message.
  1089. * Keystrokes on the field will be filtered according to the VTypes
  1090. * vtype Mask property.</p>
  1091. * </div></li>
  1092. *
  1093. * <li><b>4. Field specific regex test</b>
  1094. * <div class="sub-desc">
  1095. * <p>If none of the prior validation steps halts validation, a field's
  1096. * configured <code>{@link #regex}</code> test will be processed.
  1097. * The invalid message for this test is configured with
  1098. * <code>{@link #regexText}</code>.</p>
  1099. * </div></li>
  1100. *
  1101. * @param {Mixed} value The value to validate. The processed raw value will be used if nothing is passed
  1102. * @return {Array} Array of any validation errors
  1103. */
  1104. getErrors: function(value) {
  1105. var errors = Ext.form.TextField.superclass.getErrors.apply(this, arguments);
  1106. value = value || this.processValue(this.getRawValue());
  1107. if(Ext.isFunction(this.validator)){
  1108. var msg = this.validator(value);
  1109. if (msg !== true) {
  1110. errors.push(msg);
  1111. }
  1112. }
  1113. if (!this.allowBlank && (value.length < 1 || value === this.emptyText)) { // if it's blank
  1114. errors.push(this.blankText);
  1115. }
  1116. if (value.length < this.minLength) {
  1117. errors.push(String.format(this.minLengthText, this.minLength));
  1118. }
  1119. if (value.length > this.maxLength) {
  1120. errors.push(String.format(this.maxLengthText, this.maxLength));
  1121. }
  1122. if (this.vtype) {
  1123. var vt = Ext.form.VTypes;
  1124. if(!vt[this.vtype](value, this)){
  1125. errors.push(this.vtypeText || vt[this.vtype +'Text']);
  1126. }
  1127. }
  1128. if (this.regex && !this.regex.test(value)) {
  1129. errors.push(this.regexText);
  1130. }
  1131. return errors;
  1132. },
  1133. /**
  1134. * Selects text in this field
  1135. * @param {Number} start (optional) The index where the selection should start (defaults to 0)
  1136. * @param {Number} end (optional) The index where the selection should end (defaults to the text length)
  1137. */
  1138. selectText : function(start, end){
  1139. var v = this.getRawValue();
  1140. var doFocus = false;
  1141. if(v.length > 0){
  1142. start = start === undefined ? 0 : start;
  1143. end = end === undefined ? v.length : end;
  1144. var d = this.el.dom;
  1145. if(d.setSelectionRange){
  1146. d.setSelectionRange(start, end);
  1147. }else if(d.createTextRange){
  1148. var range = d.createTextRange();
  1149. range.moveStart('character', start);
  1150. range.moveEnd('character', end-v.length);
  1151. range.select();
  1152. }
  1153. doFocus = Ext.isGecko || Ext.isOpera;
  1154. }else{
  1155. doFocus = true;
  1156. }
  1157. if(doFocus){
  1158. this.focus();
  1159. }
  1160. },
  1161. /**
  1162. * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed.
  1163. * This only takes effect if <tt><b>{@link #grow}</b> = true</tt>, and fires the {@link #autosize} event.
  1164. */
  1165. autoSize : function(){
  1166. if(!this.grow || !this.rendered){
  1167. return;
  1168. }
  1169. if(!this.metrics){
  1170. this.metrics = Ext.util.TextMetrics.createInstance(this.el);
  1171. }
  1172. var el = this.el;
  1173. var v = el.dom.value;
  1174. var d = document.createElement('div');
  1175. d.appendChild(document.createTextNode(v));
  1176. v = d.innerHTML;
  1177. Ext.removeNode(d);
  1178. d = null;
  1179. v += '&#160;';
  1180. var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin));
  1181. this.el.setWidth(w);
  1182. this.fireEvent('autosize', this, w);
  1183. },
  1184. onDestroy: function(){
  1185. if(this.validationTask){
  1186. this.validationTask.cancel();
  1187. this.validationTask = null;
  1188. }
  1189. Ext.form.TextField.superclass.onDestroy.call(this);
  1190. }
  1191. });
  1192. Ext.reg('textfield', Ext.form.TextField);
  1193. /**
  1194. * @class Ext.form.TriggerField
  1195. * @extends Ext.form.TextField
  1196. * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
  1197. * The trigger has no default action, so you must assign a function to implement the trigger click handler by
  1198. * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox
  1199. * for which you can provide a custom implementation. For example:
  1200. * <pre><code>
  1201. var trigger = new Ext.form.TriggerField();
  1202. trigger.onTriggerClick = myTriggerFn;
  1203. trigger.applyToMarkup('my-field');
  1204. </code></pre>
  1205. *
  1206. * However, in general you will most likely want to use TriggerField as the base class for a reusable component.
  1207. * {@link Ext.form.DateField} and {@link Ext.form.ComboBox} are perfect examples of this.
  1208. *
  1209. * @constructor
  1210. * Create a new TriggerField.
  1211. * @param {Object} config Configuration options (valid {@Ext.form.TextField} config options will also be applied
  1212. * to the base TextField)
  1213. * @xtype trigger
  1214. */
  1215. Ext.form.TriggerField = Ext.extend(Ext.form.TextField, {
  1216. /**
  1217. * @cfg {String} triggerClass
  1218. * An additional CSS class used to style the trigger button. The trigger will always get the
  1219. * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
  1220. */
  1221. /**
  1222. * @cfg {Mixed} triggerConfig
  1223. * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the
  1224. * trigger element for this Field. (Optional).</p>
  1225. * <p>Specify this when you need a customized element to act as the trigger button for a TriggerField.</p>
  1226. * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing, positioning
  1227. * and appearance of the trigger. Defaults to:</p>
  1228. * <pre><code>{tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}</code></pre>
  1229. */
  1230. /**
  1231. * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
  1232. * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
  1233. * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
  1234. * <pre><code>{tag: "input", type: "text", size: "16", autocomplete: "off"}</code></pre>
  1235. */
  1236. defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"},
  1237. /**
  1238. * @cfg {Boolean} hideTrigger <tt>true</tt> to hide the trigger element and display only the base
  1239. * text field (defaults to <tt>false</tt>)
  1240. */
  1241. hideTrigger:false,
  1242. /**
  1243. * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field,
  1244. * the field will only respond to a click on the trigger to set the value. (defaults to <tt>true</tt>).
  1245. */
  1246. editable: true,
  1247. /**
  1248. * @cfg {Boolean} readOnly <tt>true</tt> to prevent the user from changing the field, and
  1249. * hides the trigger. Superceeds the editable and hideTrigger options if the value is true.
  1250. * (defaults to <tt>false</tt>)
  1251. */
  1252. readOnly: false,
  1253. /**
  1254. * @cfg {String} wrapFocusClass The class added to the to the wrap of the trigger element. Defaults to
  1255. * <tt>x-trigger-wrap-focus</tt>.
  1256. */
  1257. wrapFocusClass: 'x-trigger-wrap-focus',
  1258. /**
  1259. * @hide
  1260. * @method autoSize
  1261. */
  1262. autoSize: Ext.emptyFn,
  1263. // private
  1264. monitorTab : true,
  1265. // private
  1266. deferHeight : true,
  1267. // private
  1268. mimicing : false,
  1269. actionMode: 'wrap',
  1270. defaultTriggerWidth: 17,
  1271. // private
  1272. onResize : function(w, h){
  1273. Ext.form.TriggerField.superclass.onResize.call(this, w, h);
  1274. var tw = this.getTriggerWidth();
  1275. if(Ext.isNumber(w)){
  1276. this.el.setWidth(w - tw);
  1277. }
  1278. this.wrap.setWidth(this.el.getWidth() + tw);
  1279. },
  1280. getTriggerWidth: function(){
  1281. var tw = this.trigger.getWidth();
  1282. if(!this.hideTrigger && !this.readOnly && tw === 0){
  1283. tw = this.defaultTriggerWidth;
  1284. }
  1285. return tw;
  1286. },
  1287. // private
  1288. alignErrorIcon : function(){
  1289. if(this.wrap){
  1290. this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
  1291. }
  1292. },
  1293. // private
  1294. onRender : function(ct, position){
  1295. this.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc();
  1296. Ext.form.TriggerField.superclass.onRender.call(this, ct, position);
  1297. this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'});
  1298. this.trigger = this.wrap.createChild(this.triggerConfig ||
  1299. {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass});
  1300. this.initTrigger();
  1301. if(!this.width){
  1302. this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
  1303. }
  1304. this.resizeEl = this.positionEl = this.wrap;
  1305. },
  1306. getWidth: function() {
  1307. return(this.el.getWidth() + this.trigger.getWidth());
  1308. },
  1309. updateEditState: function(){
  1310. if(this.rendered){
  1311. if (this.readOnly) {
  1312. this.el.dom.readOnly = true;
  1313. this.el.addClass('x-trigger-noedit');
  1314. this.mun(this.el, 'click', this.onTriggerClick, this);
  1315. this.trigger.setDisplayed(false);
  1316. } else {
  1317. if (!this.editable) {
  1318. this.el.dom.readOnly = true;
  1319. this.el.addClass('x-trigger-noedit');
  1320. this.mon(this.el, 'click', this.onTriggerClick, this);
  1321. } else {
  1322. this.el.dom.readOnly = false;
  1323. this.el.removeClass('x-trigger-noedit');
  1324. this.mun(this.el, 'click', this.onTriggerClick, this);
  1325. }
  1326. this.trigger.setDisplayed(!this.hideTrigger);
  1327. }
  1328. this.onResize(this.width || this.wrap.getWidth());
  1329. }
  1330. },
  1331. setHideTrigger: function(hideTrigger){
  1332. if(hideTrigger != this.hideTrigger){
  1333. this.hideTrigger = hideTrigger;
  1334. this.updateEditState();
  1335. }
  1336. },
  1337. /**
  1338. * @param {Boolean} value True to allow the user to directly edit the field text
  1339. * Allow or prevent the user from directly editing the field text. If false is passed,
  1340. * the user will only be able to modify the field using the trigger. Will also add
  1341. * a click event to the text field which will call the trigger. This method
  1342. * is the runtime equivalent of setting the 'editable' config option at config time.
  1343. */
  1344. setEditable: function(editable){
  1345. if(editable != this.editable){
  1346. this.editable = editable;
  1347. this.updateEditState();
  1348. }
  1349. },
  1350. /**
  1351. * @param {Boolean} value True to prevent the user changing the field and explicitly
  1352. * hide the trigger.
  1353. * Setting this to true will superceed settings editable and hideTrigger.
  1354. * Setting this to false will defer back to editable and hideTrigger. This method
  1355. * is the runtime equivalent of setting the 'readOnly' config option at config time.
  1356. */
  1357. setReadOnly: function(readOnly){
  1358. if(readOnly != this.readOnly){
  1359. this.readOnly = readOnly;
  1360. this.updateEditState();
  1361. }
  1362. },
  1363. afterRender : function(){
  1364. Ext.form.TriggerField.superclass.afterRender.call(this);
  1365. this.updateEditState();
  1366. },
  1367. // private
  1368. initTrigger : function(){
  1369. this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true});
  1370. this.trigger.addClassOnOver('x-form-trigger-over');
  1371. this.trigger.addClassOnClick('x-form-trigger-click');
  1372. },
  1373. // private
  1374. onDestroy : function(){
  1375. Ext.destroy(this.trigger, this.wrap);
  1376. if (this.mimicing){
  1377. this.doc.un('mousedown', this.mimicBlur, this);
  1378. }
  1379. delete this.doc;
  1380. Ext.form.TriggerField.superclass.onDestroy.call(this);
  1381. },
  1382. // private
  1383. onFocus : function(){
  1384. Ext.form.TriggerField.superclass.onFocus.call(this);
  1385. if(!this.mimicing){
  1386. this.wrap.addClass(this.wrapFocusClass);
  1387. this.mimicing = true;
  1388. this.doc.on('mousedown', this.mimicBlur, this, {delay: 10});
  1389. if(this.monitorTab){
  1390. this.on('specialkey', this.checkTab, this);
  1391. }
  1392. }
  1393. },
  1394. // private
  1395. checkTab : function(me, e){
  1396. if(e.getKey() == e.TAB){
  1397. this.triggerBlur();
  1398. }
  1399. },
  1400. // private
  1401. onBlur : Ext.emptyFn,
  1402. // private
  1403. mimicBlur : function(e){
  1404. if(!this.isDestroyed && !this.wrap.contains(e.target) && this.validateBlur(e)){
  1405. this.triggerBlur();
  1406. }
  1407. },
  1408. // private
  1409. triggerBlur : function(){
  1410. this.mimicing = false;
  1411. this.doc.un('mousedown', this.mimicBlur, this);
  1412. if(this.monitorTab && this.el){
  1413. this.un('specialkey', this.checkTab, this);
  1414. }
  1415. Ext.form.TriggerField.superclass.onBlur.call(this);
  1416. if(this.wrap){
  1417. this.wrap.removeClass(this.wrapFocusClass);
  1418. }
  1419. },
  1420. beforeBlur : Ext.emptyFn,
  1421. // private
  1422. // This should be overriden by any subclass that needs to check whether or not the field can be blurred.
  1423. validateBlur : function(e){
  1424. return true;
  1425. },
  1426. /**
  1427. * The function that should handle the trigger's click event. This method does nothing by default
  1428. * until overridden by an implementing function. See Ext.form.ComboBox and Ext.form.DateField for
  1429. * sample implementations.
  1430. * @method
  1431. * @param {EventObject} e
  1432. */
  1433. onTriggerClick : Ext.emptyFn
  1434. /**
  1435. * @cfg {Boolean} grow @hide
  1436. */
  1437. /**
  1438. * @cfg {Number} growMin @hide
  1439. */
  1440. /**
  1441. * @cfg {Number} growMax @hide
  1442. */
  1443. });
  1444. /**
  1445. * @class Ext.form.TwinTriggerField
  1446. * @extends Ext.form.TriggerField
  1447. * TwinTriggerField is not a public class to be used directly. It is meant as an abstract base class
  1448. * to be extended by an implementing class. For an example of implementing this class, see the custom
  1449. * SearchField implementation here:
  1450. * <a href="http://extjs.com/deploy/ext/examples/form/custom.html">http://extjs.com/deploy/ext/examples/form/custom.html</a>
  1451. */
  1452. Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, {
  1453. /**
  1454. * @cfg {Mixed} triggerConfig
  1455. * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the trigger elements
  1456. * for this Field. (Optional).</p>
  1457. * <p>Specify this when you need a customized element to contain the two trigger elements for this Field.
  1458. * Each trigger element must be marked by the CSS class <tt>x-form-trigger</tt> (also see
  1459. * <tt>{@link #trigger1Class}</tt> and <tt>{@link #trigger2Class}</tt>).</p>
  1460. * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing,
  1461. * positioning and appearance of the triggers.</p>
  1462. */
  1463. /**
  1464. * @cfg {String} trigger1Class
  1465. * An additional CSS class used to style the trigger button. The trigger will always get the
  1466. * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
  1467. */
  1468. /**
  1469. * @cfg {String} trigger2Class
  1470. * An additional CSS class used to style the trigger button. The trigger will always get the
  1471. * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
  1472. */
  1473. initComponent : function(){
  1474. Ext.form.TwinTriggerField.superclass.initComponent.call(this);
  1475. this.triggerConfig = {
  1476. tag:'span', cls:'x-form-twin-triggers', cn:[
  1477. {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
  1478. {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class}
  1479. ]};
  1480. },
  1481. getTrigger : function(index){
  1482. return this.triggers[index];
  1483. },
  1484. initTrigger : function(){
  1485. var ts = this.trigger.select('.x-form-trigger', true);
  1486. var triggerField = this;
  1487. ts.each(function(t, all, index){
  1488. var triggerIndex = 'Trigger'+(index+1);
  1489. t.hide = function(){
  1490. var w = triggerField.wrap.getWidth();
  1491. this.dom.style.display = 'none';
  1492. triggerField.el.setWidth(w-triggerField.trigger.getWidth());
  1493. this['hidden' + triggerIndex] = true;
  1494. };
  1495. t.show = function(){
  1496. var w = triggerField.wrap.getWidth();
  1497. this.dom.style.display = '';
  1498. triggerField.el.setWidth(w-triggerField.trigger.getWidth());
  1499. this['hidden' + triggerIndex] = false;
  1500. };
  1501. if(this['hide'+triggerIndex]){
  1502. t.dom.style.display = 'none';
  1503. this['hidden' + triggerIndex] = true;
  1504. }
  1505. this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true});
  1506. t.addClassOnOver('x-form-trigger-over');
  1507. t.addClassOnClick('x-form-trigger-click');
  1508. }, this);
  1509. this.triggers = ts.elements;
  1510. },
  1511. getTriggerWidth: function(){
  1512. var tw = 0;
  1513. Ext.each(this.triggers, function(t, index){
  1514. var triggerIndex = 'Trigger' + (index + 1),
  1515. w = t.getWidth();
  1516. if(w === 0 && !this['hidden' + triggerIndex]){
  1517. tw += this.defaultTriggerWidth;
  1518. }else{
  1519. tw += w;
  1520. }
  1521. }, this);
  1522. return tw;
  1523. },
  1524. // private
  1525. onDestroy : function() {
  1526. Ext.destroy(this.triggers);
  1527. Ext.form.TwinTriggerField.superclass.onDestroy.call(this);
  1528. },
  1529. /**
  1530. * The function that should handle the trigger's click event. This method does nothing by default
  1531. * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
  1532. * for additional information.
  1533. * @method
  1534. * @param {EventObject} e
  1535. */
  1536. onTrigger1Click : Ext.emptyFn,
  1537. /**
  1538. * The function that should handle the trigger's click event. This method does nothing by default
  1539. * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
  1540. * for additional information.
  1541. * @method
  1542. * @param {EventObject} e
  1543. */
  1544. onTrigger2Click : Ext.emptyFn
  1545. });
  1546. Ext.reg('trigger', Ext.form.TriggerField);
  1547. /**
  1548. * @class Ext.form.TextArea
  1549. * @extends Ext.form.TextField
  1550. * Multiline text field. Can be used as a direct replacement for traditional textarea fields, plus adds
  1551. * support for auto-sizing.
  1552. * @constructor
  1553. * Creates a new TextArea
  1554. * @param {Object} config Configuration options
  1555. * @xtype textarea
  1556. */
  1557. Ext.form.TextArea = Ext.extend(Ext.form.TextField, {
  1558. /**
  1559. * @cfg {Number} growMin The minimum height to allow when <tt>{@link Ext.form.TextField#grow grow}=true</tt>
  1560. * (defaults to <tt>60</tt>)
  1561. */
  1562. growMin : 60,
  1563. /**
  1564. * @cfg {Number} growMax The maximum height to allow when <tt>{@link Ext.form.TextField#grow grow}=true</tt>
  1565. * (defaults to <tt>1000</tt>)
  1566. */
  1567. growMax: 1000,
  1568. growAppend : '&#160;\n&#160;',
  1569. enterIsSpecial : false,
  1570. /**
  1571. * @cfg {Boolean} preventScrollbars <tt>true</tt> to prevent scrollbars from appearing regardless of how much text is
  1572. * in the field. This option is only relevant when {@link #grow} is <tt>true</tt>. Equivalent to setting overflow: hidden, defaults to
  1573. * <tt>false</tt>.
  1574. */
  1575. preventScrollbars: false,
  1576. /**
  1577. * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
  1578. * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
  1579. * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
  1580. * <pre><code>{tag: "textarea", style: "width:100px;height:60px;", autocomplete: "off"}</code></pre>
  1581. */
  1582. // private
  1583. onRender : function(ct, position){
  1584. if(!this.el){
  1585. this.defaultAutoCreate = {
  1586. tag: "textarea",
  1587. style:"width:100px;height:60px;",
  1588. autocomplete: "off"
  1589. };
  1590. }
  1591. Ext.form.TextArea.superclass.onRender.call(this, ct, position);
  1592. if(this.grow){
  1593. this.textSizeEl = Ext.DomHelper.append(document.body, {
  1594. tag: "pre", cls: "x-form-grow-sizer"
  1595. });
  1596. if(this.preventScrollbars){
  1597. this.el.setStyle("overflow", "hidden");
  1598. }
  1599. this.el.setHeight(this.growMin);
  1600. }
  1601. },
  1602. onDestroy : function(){
  1603. Ext.removeNode(this.textSizeEl);
  1604. Ext.form.TextArea.superclass.onDestroy.call(this);
  1605. },
  1606. fireKey : function(e){
  1607. if(e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() != e.ENTER || e.hasModifier()))){
  1608. this.fireEvent("specialkey", this, e);
  1609. }
  1610. },
  1611. // private
  1612. doAutoSize : function(e){
  1613. return !e.isNavKeyPress() || e.getKey() == e.ENTER;
  1614. },
  1615. /**
  1616. * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed.
  1617. * This only takes effect if grow = true, and fires the {@link #autosize} event if the height changes.
  1618. */
  1619. autoSize: function(){
  1620. if(!this.grow || !this.textSizeEl){
  1621. return;
  1622. }
  1623. var el = this.el,
  1624. v = Ext.util.Format.htmlEncode(el.dom.value),
  1625. ts = this.textSizeEl,
  1626. h;
  1627. Ext.fly(ts).setWidth(this.el.getWidth());
  1628. if(v.length < 1){
  1629. v = "&#160;&#160;";
  1630. }else{
  1631. v += this.growAppend;
  1632. if(Ext.isIE){
  1633. v = v.replace(/\n/g, '&#160;<br />');
  1634. }
  1635. }
  1636. ts.innerHTML = v;
  1637. h = Math.min(this.growMax, Math.max(ts.offsetHeight, this.growMin));
  1638. if(h != this.lastHeight){
  1639. this.lastHeight = h;
  1640. this.el.setHeight(h);
  1641. this.fireEvent("autosize", this, h);
  1642. }
  1643. }
  1644. });
  1645. Ext.reg('textarea', Ext.form.TextArea);/**
  1646. * @class Ext.form.NumberField
  1647. * @extends Ext.form.TextField
  1648. * Numeric text field that provides automatic keystroke filtering and numeric validation.
  1649. * @constructor
  1650. * Creates a new NumberField
  1651. * @param {Object} config Configuration options
  1652. * @xtype numberfield
  1653. */
  1654. Ext.form.NumberField = Ext.extend(Ext.form.TextField, {
  1655. /**
  1656. * @cfg {RegExp} stripCharsRe @hide
  1657. */
  1658. /**
  1659. * @cfg {RegExp} maskRe @hide
  1660. */
  1661. /**
  1662. * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field x-form-num-field")
  1663. */
  1664. fieldClass: "x-form-field x-form-num-field",
  1665. /**
  1666. * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
  1667. */
  1668. allowDecimals : true,
  1669. /**
  1670. * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
  1671. */
  1672. decimalSeparator : ".",
  1673. /**
  1674. * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
  1675. */
  1676. decimalPrecision : 2,
  1677. /**
  1678. * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
  1679. */
  1680. allowNegative : true,
  1681. /**
  1682. * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
  1683. */
  1684. minValue : Number.NEGATIVE_INFINITY,
  1685. /**
  1686. * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
  1687. */
  1688. maxValue : Number.MAX_VALUE,
  1689. /**
  1690. * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
  1691. */
  1692. minText : "The minimum value for this field is {0}",
  1693. /**
  1694. * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
  1695. */
  1696. maxText : "The maximum value for this field is {0}",
  1697. /**
  1698. * @cfg {String} nanText Error text to display if the value is not a valid number. For example, this can happen
  1699. * if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
  1700. */
  1701. nanText : "{0} is not a valid number",
  1702. /**
  1703. * @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789').
  1704. */
  1705. baseChars : "0123456789",
  1706. // private
  1707. initEvents : function(){
  1708. var allowed = this.baseChars + '';
  1709. if (this.allowDecimals) {
  1710. allowed += this.decimalSeparator;
  1711. }
  1712. if (this.allowNegative) {
  1713. allowed += '-';
  1714. }
  1715. this.maskRe = new RegExp('[' + Ext.escapeRe(allowed) + ']');
  1716. Ext.form.NumberField.superclass.initEvents.call(this);
  1717. },
  1718. /**
  1719. * Runs all of NumberFields validations and returns an array of any errors. Note that this first
  1720. * runs TextField's validations, so the returned array is an amalgamation of all field errors.
  1721. * The additional validations run test that the value is a number, and that it is within the
  1722. * configured min and max values.
  1723. * @param {Mixed} value The value to get errors for (defaults to the current field value)
  1724. * @return {Array} All validation errors for this field
  1725. */
  1726. getErrors: function(value) {
  1727. var errors = Ext.form.NumberField.superclass.getErrors.apply(this, arguments);
  1728. value = value || this.processValue(this.getRawValue());
  1729. if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
  1730. return errors;
  1731. }
  1732. value = String(value).replace(this.decimalSeparator, ".");
  1733. if(isNaN(value)){
  1734. errors.push(String.format(this.nanText, value));
  1735. }
  1736. var num = this.parseValue(value);
  1737. if(num < this.minValue){
  1738. errors.push(String.format(this.minText, this.minValue));
  1739. }
  1740. if(num > this.maxValue){
  1741. errors.push(String.format(this.maxText, this.maxValue));
  1742. }
  1743. return errors;
  1744. },
  1745. getValue : function(){
  1746. return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this)));
  1747. },
  1748. setValue : function(v){
  1749. v = Ext.isNumber(v) ? v : parseFloat(String(v).replace(this.decimalSeparator, "."));
  1750. v = isNaN(v) ? '' : String(v).replace(".", this.decimalSeparator);
  1751. return Ext.form.NumberField.superclass.setValue.call(this, v);
  1752. },
  1753. /**
  1754. * Replaces any existing {@link #minValue} with the new value.
  1755. * @param {Number} value The minimum value
  1756. */
  1757. setMinValue : function(value){
  1758. this.minValue = Ext.num(value, Number.NEGATIVE_INFINITY);
  1759. },
  1760. /**
  1761. * Replaces any existing {@link #maxValue} with the new value.
  1762. * @param {Number} value The maximum value
  1763. */
  1764. setMaxValue : function(value){
  1765. this.maxValue = Ext.num(value, Number.MAX_VALUE);
  1766. },
  1767. // private
  1768. parseValue : function(value){
  1769. value = parseFloat(String(value).replace(this.decimalSeparator, "."));
  1770. return isNaN(value) ? '' : value;
  1771. },
  1772. // private
  1773. fixPrecision : function(value){
  1774. var nan = isNaN(value);
  1775. if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
  1776. return nan ? '' : value;
  1777. }
  1778. return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
  1779. },
  1780. beforeBlur : function(){
  1781. var v = this.parseValue(this.getRawValue());
  1782. if(!Ext.isEmpty(v)){
  1783. this.setValue(this.fixPrecision(v));
  1784. }
  1785. }
  1786. });
  1787. Ext.reg('numberfield', Ext.form.NumberField);/**
  1788. * @class Ext.form.DateField
  1789. * @extends Ext.form.TriggerField
  1790. * Provides a date input field with a {@link Ext.DatePicker} dropdown and automatic date validation.
  1791. * @constructor
  1792. * Create a new DateField
  1793. * @param {Object} config
  1794. * @xtype datefield
  1795. */
  1796. Ext.form.DateField = Ext.extend(Ext.form.TriggerField, {
  1797. /**
  1798. * @cfg {String} format
  1799. * The default date format string which can be overriden for localization support. The format must be
  1800. * valid according to {@link Date#parseDate} (defaults to <tt>'m/d/Y'</tt>).
  1801. */
  1802. format : "m/d/Y",
  1803. /**
  1804. * @cfg {String} altFormats
  1805. * Multiple date formats separated by "<tt>|</tt>" to try when parsing a user input value and it
  1806. * does not match the defined format (defaults to
  1807. * <tt>'m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d'</tt>).
  1808. */
  1809. altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d",
  1810. /**
  1811. * @cfg {String} disabledDaysText
  1812. * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
  1813. */
  1814. disabledDaysText : "Disabled",
  1815. /**
  1816. * @cfg {String} disabledDatesText
  1817. * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)
  1818. */
  1819. disabledDatesText : "Disabled",
  1820. /**
  1821. * @cfg {String} minText
  1822. * The error text to display when the date in the cell is before <tt>{@link #minValue}</tt> (defaults to
  1823. * <tt>'The date in this field must be after {minValue}'</tt>).
  1824. */
  1825. minText : "The date in this field must be equal to or after {0}",
  1826. /**
  1827. * @cfg {String} maxText
  1828. * The error text to display when the date in the cell is after <tt>{@link #maxValue}</tt> (defaults to
  1829. * <tt>'The date in this field must be before {maxValue}'</tt>).
  1830. */
  1831. maxText : "The date in this field must be equal to or before {0}",
  1832. /**
  1833. * @cfg {String} invalidText
  1834. * The error text to display when the date in the field is invalid (defaults to
  1835. * <tt>'{value} is not a valid date - it must be in the format {format}'</tt>).
  1836. */
  1837. invalidText : "{0} is not a valid date - it must be in the format {1}",
  1838. /**
  1839. * @cfg {String} triggerClass
  1840. * An additional CSS class used to style the trigger button. The trigger will always get the
  1841. * class <tt>'x-form-trigger'</tt> and <tt>triggerClass</tt> will be <b>appended</b> if specified
  1842. * (defaults to <tt>'x-form-date-trigger'</tt> which displays a calendar icon).
  1843. */
  1844. triggerClass : 'x-form-date-trigger',
  1845. /**
  1846. * @cfg {Boolean} showToday
  1847. * <tt>false</tt> to hide the footer area of the DatePicker containing the Today button and disable
  1848. * the keyboard handler for spacebar that selects the current date (defaults to <tt>true</tt>).
  1849. */
  1850. showToday : true,
  1851. /**
  1852. * @cfg {Date/String} minValue
  1853. * The minimum allowed date. Can be either a Javascript date object or a string date in a
  1854. * valid format (defaults to null).
  1855. */
  1856. /**
  1857. * @cfg {Date/String} maxValue
  1858. * The maximum allowed date. Can be either a Javascript date object or a string date in a
  1859. * valid format (defaults to null).
  1860. */
  1861. /**
  1862. * @cfg {Array} disabledDays
  1863. * An array of days to disable, 0 based (defaults to null). Some examples:<pre><code>
  1864. // disable Sunday and Saturday:
  1865. disabledDays: [0, 6]
  1866. // disable weekdays:
  1867. disabledDays: [1,2,3,4,5]
  1868. * </code></pre>
  1869. */
  1870. /**
  1871. * @cfg {Array} disabledDates
  1872. * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular
  1873. * expression so they are very powerful. Some examples:<pre><code>
  1874. // disable these exact dates:
  1875. disabledDates: ["03/08/2003", "09/16/2003"]
  1876. // disable these days for every year:
  1877. disabledDates: ["03/08", "09/16"]
  1878. // only match the beginning (useful if you are using short years):
  1879. disabledDates: ["^03/08"]
  1880. // disable every day in March 2006:
  1881. disabledDates: ["03/../2006"]
  1882. // disable every day in every March:
  1883. disabledDates: ["^03"]
  1884. * </code></pre>
  1885. * Note that the format of the dates included in the array should exactly match the {@link #format} config.
  1886. * In order to support regular expressions, if you are using a {@link #format date format} that has "." in
  1887. * it, you will have to escape the dot when restricting dates. For example: <tt>["03\\.08\\.03"]</tt>.
  1888. */
  1889. /**
  1890. * @cfg {String/Object} autoCreate
  1891. * A {@link Ext.DomHelper DomHelper element specification object}, or <tt>true</tt> for the default element
  1892. * specification object:<pre><code>
  1893. * autoCreate: {tag: "input", type: "text", size: "10", autocomplete: "off"}
  1894. * </code></pre>
  1895. */
  1896. // private
  1897. defaultAutoCreate : {tag: "input", type: "text", size: "10", autocomplete: "off"},
  1898. // in the absence of a time value, a default value of 12 noon will be used
  1899. // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
  1900. initTime: '12', // 24 hour format
  1901. initTimeFormat: 'H',
  1902. // PUBLIC -- to be documented
  1903. safeParse : function(value, format) {
  1904. if (/[gGhH]/.test(format.replace(/(\\.)/g, ''))) {
  1905. // if parse format contains hour information, no DST adjustment is necessary
  1906. return Date.parseDate(value, format);
  1907. } else {
  1908. // set time to 12 noon, then clear the time
  1909. var parsedDate = Date.parseDate(value + ' ' + this.initTime, format + ' ' + this.initTimeFormat);
  1910. if (parsedDate) return parsedDate.clearTime();
  1911. }
  1912. },
  1913. initComponent : function(){
  1914. Ext.form.DateField.superclass.initComponent.call(this);
  1915. this.addEvents(
  1916. /**
  1917. * @event select
  1918. * Fires when a date is selected via the date picker.
  1919. * @param {Ext.form.DateField} this
  1920. * @param {Date} date The date that was selected
  1921. */
  1922. 'select'
  1923. );
  1924. if(Ext.isString(this.minValue)){
  1925. this.minValue = this.parseDate(this.minValue);
  1926. }
  1927. if(Ext.isString(this.maxValue)){
  1928. this.maxValue = this.parseDate(this.maxValue);
  1929. }
  1930. this.disabledDatesRE = null;
  1931. this.initDisabledDays();
  1932. },
  1933. initEvents: function() {
  1934. Ext.form.DateField.superclass.initEvents.call(this);
  1935. this.keyNav = new Ext.KeyNav(this.el, {
  1936. "down": function(e) {
  1937. this.onTriggerClick();
  1938. },
  1939. scope: this,
  1940. forceKeyDown: true
  1941. });
  1942. },
  1943. // private
  1944. initDisabledDays : function(){
  1945. if(this.disabledDates){
  1946. var dd = this.disabledDates,
  1947. len = dd.length - 1,
  1948. re = "(?:";
  1949. Ext.each(dd, function(d, i){
  1950. re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
  1951. if(i != len){
  1952. re += '|';
  1953. }
  1954. }, this);
  1955. this.disabledDatesRE = new RegExp(re + ')');
  1956. }
  1957. },
  1958. /**
  1959. * Replaces any existing disabled dates with new values and refreshes the DatePicker.
  1960. * @param {Array} disabledDates An array of date strings (see the <tt>{@link #disabledDates}</tt> config
  1961. * for details on supported values) used to disable a pattern of dates.
  1962. */
  1963. setDisabledDates : function(dd){
  1964. this.disabledDates = dd;
  1965. this.initDisabledDays();
  1966. if(this.menu){
  1967. this.menu.picker.setDisabledDates(this.disabledDatesRE);
  1968. }
  1969. },
  1970. /**
  1971. * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
  1972. * @param {Array} disabledDays An array of disabled day indexes. See the <tt>{@link #disabledDays}</tt>
  1973. * config for details on supported values.
  1974. */
  1975. setDisabledDays : function(dd){
  1976. this.disabledDays = dd;
  1977. if(this.menu){
  1978. this.menu.picker.setDisabledDays(dd);
  1979. }
  1980. },
  1981. /**
  1982. * Replaces any existing <tt>{@link #minValue}</tt> with the new value and refreshes the DatePicker.
  1983. * @param {Date} value The minimum date that can be selected
  1984. */
  1985. setMinValue : function(dt){
  1986. this.minValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
  1987. if(this.menu){
  1988. this.menu.picker.setMinDate(this.minValue);
  1989. }
  1990. },
  1991. /**
  1992. * Replaces any existing <tt>{@link #maxValue}</tt> with the new value and refreshes the DatePicker.
  1993. * @param {Date} value The maximum date that can be selected
  1994. */
  1995. setMaxValue : function(dt){
  1996. this.maxValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
  1997. if(this.menu){
  1998. this.menu.picker.setMaxDate(this.maxValue);
  1999. }
  2000. },
  2001. /**
  2002. * Runs all of NumberFields validations and returns an array of any errors. Note that this first
  2003. * runs TextField's validations, so the returned array is an amalgamation of all field errors.
  2004. * The additional validation checks are testing that the date format is valid, that the chosen
  2005. * date is within the min and max date constraints set, that the date chosen is not in the disabledDates
  2006. * regex and that the day chosed is not one of the disabledDays.
  2007. * @param {Mixed} value The value to get errors for (defaults to the current field value)
  2008. * @return {Array} All validation errors for this field
  2009. */
  2010. getErrors: function(value) {
  2011. var errors = Ext.form.DateField.superclass.getErrors.apply(this, arguments);
  2012. value = this.formatDate(value || this.processValue(this.getRawValue()));
  2013. if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
  2014. return errors;
  2015. }
  2016. var svalue = value;
  2017. value = this.parseDate(value);
  2018. if (!value) {
  2019. errors.push(String.format(this.invalidText, svalue, this.format));
  2020. return errors;
  2021. }
  2022. var time = value.getTime();
  2023. if (this.minValue && time < this.minValue.getTime()) {
  2024. errors.push(String.format(this.minText, this.formatDate(this.minValue)));
  2025. }
  2026. if (this.maxValue && time > this.maxValue.getTime()) {
  2027. errors.push(String.format(this.maxText, this.formatDate(this.maxValue)));
  2028. }
  2029. if (this.disabledDays) {
  2030. var day = value.getDay();
  2031. for(var i = 0; i < this.disabledDays.length; i++) {
  2032. if (day === this.disabledDays[i]) {
  2033. errors.push(this.disabledDaysText);
  2034. break;
  2035. }
  2036. }
  2037. }
  2038. var fvalue = this.formatDate(value);
  2039. if (this.disabledDatesRE && this.disabledDatesRE.test(fvalue)) {
  2040. errors.push(String.format(this.disabledDatesText, fvalue));
  2041. }
  2042. return errors;
  2043. },
  2044. // private
  2045. // Provides logic to override the default TriggerField.validateBlur which just returns true
  2046. validateBlur : function(){
  2047. return !this.menu || !this.menu.isVisible();
  2048. },
  2049. /**
  2050. * Returns the current date value of the date field.
  2051. * @return {Date} The date value
  2052. */
  2053. getValue : function(){
  2054. return this.parseDate(Ext.form.DateField.superclass.getValue.call(this)) || "";
  2055. },
  2056. /**
  2057. * Sets the value of the date field. You can pass a date object or any string that can be
  2058. * parsed into a valid date, using <tt>{@link #format}</tt> as the date format, according
  2059. * to the same rules as {@link Date#parseDate} (the default format used is <tt>"m/d/Y"</tt>).
  2060. * <br />Usage:
  2061. * <pre><code>
  2062. //All of these calls set the same date value (May 4, 2006)
  2063. //Pass a date object:
  2064. var dt = new Date('5/4/2006');
  2065. dateField.setValue(dt);
  2066. //Pass a date string (default format):
  2067. dateField.setValue('05/04/2006');
  2068. //Pass a date string (custom format):
  2069. dateField.format = 'Y-m-d';
  2070. dateField.setValue('2006-05-04');
  2071. </code></pre>
  2072. * @param {String/Date} date The date or valid date string
  2073. * @return {Ext.form.Field} this
  2074. */
  2075. setValue : function(date){
  2076. return Ext.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));
  2077. },
  2078. // private
  2079. parseDate : function(value) {
  2080. if(!value || Ext.isDate(value)){
  2081. return value;
  2082. }
  2083. var v = this.safeParse(value, this.format),
  2084. af = this.altFormats,
  2085. afa = this.altFormatsArray;
  2086. if (!v && af) {
  2087. afa = afa || af.split("|");
  2088. for (var i = 0, len = afa.length; i < len && !v; i++) {
  2089. v = this.safeParse(value, afa[i]);
  2090. }
  2091. }
  2092. return v;
  2093. },
  2094. // private
  2095. onDestroy : function(){
  2096. Ext.destroy(this.menu, this.keyNav);
  2097. Ext.form.DateField.superclass.onDestroy.call(this);
  2098. },
  2099. // private
  2100. formatDate : function(date){
  2101. return Ext.isDate(date) ? date.dateFormat(this.format) : date;
  2102. },
  2103. /**
  2104. * @method onTriggerClick
  2105. * @hide
  2106. */
  2107. // private
  2108. // Implements the default empty TriggerField.onTriggerClick function to display the DatePicker
  2109. onTriggerClick : function(){
  2110. if(this.disabled){
  2111. return;
  2112. }
  2113. if(this.menu == null){
  2114. this.menu = new Ext.menu.DateMenu({
  2115. hideOnClick: false,
  2116. focusOnSelect: false
  2117. });
  2118. }
  2119. this.onFocus();
  2120. Ext.apply(this.menu.picker, {
  2121. minDate : this.minValue,
  2122. maxDate : this.maxValue,
  2123. disabledDatesRE : this.disabledDatesRE,
  2124. disabledDatesText : this.disabledDatesText,
  2125. disabledDays : this.disabledDays,
  2126. disabledDaysText : this.disabledDaysText,
  2127. format : this.format,
  2128. showToday : this.showToday,
  2129. minText : String.format(this.minText, this.formatDate(this.minValue)),
  2130. maxText : String.format(this.maxText, this.formatDate(this.maxValue))
  2131. });
  2132. this.menu.picker.setValue(this.getValue() || new Date());
  2133. this.menu.show(this.el, "tl-bl?");
  2134. this.menuEvents('on');
  2135. },
  2136. //private
  2137. menuEvents: function(method){
  2138. this.menu[method]('select', this.onSelect, this);
  2139. this.menu[method]('hide', this.onMenuHide, this);
  2140. this.menu[method]('show', this.onFocus, this);
  2141. },
  2142. onSelect: function(m, d){
  2143. this.setValue(d);
  2144. this.fireEvent('select', this, d);
  2145. this.menu.hide();
  2146. },
  2147. onMenuHide: function(){
  2148. this.focus(false, 60);
  2149. this.menuEvents('un');
  2150. },
  2151. // private
  2152. beforeBlur : function(){
  2153. var v = this.parseDate(this.getRawValue());
  2154. if(v){
  2155. this.setValue(v);
  2156. }
  2157. }
  2158. /**
  2159. * @cfg {Boolean} grow @hide
  2160. */
  2161. /**
  2162. * @cfg {Number} growMin @hide
  2163. */
  2164. /**
  2165. * @cfg {Number} growMax @hide
  2166. */
  2167. /**
  2168. * @hide
  2169. * @method autoSize
  2170. */
  2171. });
  2172. Ext.reg('datefield', Ext.form.DateField);/**
  2173. * @class Ext.form.DisplayField
  2174. * @extends Ext.form.Field
  2175. * A display-only text field which is not validated and not submitted.
  2176. * @constructor
  2177. * Creates a new DisplayField.
  2178. * @param {Object} config Configuration options
  2179. * @xtype displayfield
  2180. */
  2181. Ext.form.DisplayField = Ext.extend(Ext.form.Field, {
  2182. validationEvent : false,
  2183. validateOnBlur : false,
  2184. defaultAutoCreate : {tag: "div"},
  2185. /**
  2186. * @cfg {String} fieldClass The default CSS class for the field (defaults to <tt>"x-form-display-field"</tt>)
  2187. */
  2188. fieldClass : "x-form-display-field",
  2189. /**
  2190. * @cfg {Boolean} htmlEncode <tt>false</tt> to skip HTML-encoding the text when rendering it (defaults to
  2191. * <tt>false</tt>). This might be useful if you want to include tags in the field's innerHTML rather than
  2192. * rendering them as string literals per the default logic.
  2193. */
  2194. htmlEncode: false,
  2195. // private
  2196. initEvents : Ext.emptyFn,
  2197. isValid : function(){
  2198. return true;
  2199. },
  2200. validate : function(){
  2201. return true;
  2202. },
  2203. getRawValue : function(){
  2204. var v = this.rendered ? this.el.dom.innerHTML : Ext.value(this.value, '');
  2205. if(v === this.emptyText){
  2206. v = '';
  2207. }
  2208. if(this.htmlEncode){
  2209. v = Ext.util.Format.htmlDecode(v);
  2210. }
  2211. return v;
  2212. },
  2213. getValue : function(){
  2214. return this.getRawValue();
  2215. },
  2216. getName: function() {
  2217. return this.name;
  2218. },
  2219. setRawValue : function(v){
  2220. if(this.htmlEncode){
  2221. v = Ext.util.Format.htmlEncode(v);
  2222. }
  2223. return this.rendered ? (this.el.dom.innerHTML = (Ext.isEmpty(v) ? '' : v)) : (this.value = v);
  2224. },
  2225. setValue : function(v){
  2226. this.setRawValue(v);
  2227. return this;
  2228. }
  2229. /**
  2230. * @cfg {String} inputType
  2231. * @hide
  2232. */
  2233. /**
  2234. * @cfg {Boolean} disabled
  2235. * @hide
  2236. */
  2237. /**
  2238. * @cfg {Boolean} readOnly
  2239. * @hide
  2240. */
  2241. /**
  2242. * @cfg {Boolean} validateOnBlur
  2243. * @hide
  2244. */
  2245. /**
  2246. * @cfg {Number} validationDelay
  2247. * @hide
  2248. */
  2249. /**
  2250. * @cfg {String/Boolean} validationEvent
  2251. * @hide
  2252. */
  2253. });
  2254. Ext.reg('displayfield', Ext.form.DisplayField);
  2255. /**
  2256. * @class Ext.form.ComboBox
  2257. * @extends Ext.form.TriggerField
  2258. * <p>A combobox control with support for autocomplete, remote-loading, paging and many other features.</p>
  2259. * <p>A ComboBox works in a similar manner to a traditional HTML &lt;select> field. The difference is
  2260. * that to submit the {@link #valueField}, you must specify a {@link #hiddenName} to create a hidden input
  2261. * field to hold the value of the valueField. The <i>{@link #displayField}</i> is shown in the text field
  2262. * which is named according to the {@link #name}.</p>
  2263. * <p><b><u>Events</u></b></p>
  2264. * <p>To do something when something in ComboBox is selected, configure the select event:<pre><code>
  2265. var cb = new Ext.form.ComboBox({
  2266. // all of your config options
  2267. listeners:{
  2268. scope: yourScope,
  2269. 'select': yourFunction
  2270. }
  2271. });
  2272. // Alternatively, you can assign events after the object is created:
  2273. var cb = new Ext.form.ComboBox(yourOptions);
  2274. cb.on('select', yourFunction, yourScope);
  2275. * </code></pre></p>
  2276. *
  2277. * <p><b><u>ComboBox in Grid</u></b></p>
  2278. * <p>If using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a {@link Ext.grid.Column#renderer renderer}
  2279. * will be needed to show the displayField when the editor is not active. Set up the renderer manually, or implement
  2280. * a reusable render, for example:<pre><code>
  2281. // create reusable renderer
  2282. Ext.util.Format.comboRenderer = function(combo){
  2283. return function(value){
  2284. var record = combo.findRecord(combo.{@link #valueField}, value);
  2285. return record ? record.get(combo.{@link #displayField}) : combo.{@link #valueNotFoundText};
  2286. }
  2287. }
  2288. // create the combo instance
  2289. var combo = new Ext.form.ComboBox({
  2290. {@link #typeAhead}: true,
  2291. {@link #triggerAction}: 'all',
  2292. {@link #lazyRender}:true,
  2293. {@link #mode}: 'local',
  2294. {@link #store}: new Ext.data.ArrayStore({
  2295. id: 0,
  2296. fields: [
  2297. 'myId',
  2298. 'displayText'
  2299. ],
  2300. data: [[1, 'item1'], [2, 'item2']]
  2301. }),
  2302. {@link #valueField}: 'myId',
  2303. {@link #displayField}: 'displayText'
  2304. });
  2305. // snippet of column model used within grid
  2306. var cm = new Ext.grid.ColumnModel([{
  2307. ...
  2308. },{
  2309. header: "Some Header",
  2310. dataIndex: 'whatever',
  2311. width: 130,
  2312. editor: combo, // specify reference to combo instance
  2313. renderer: Ext.util.Format.comboRenderer(combo) // pass combo instance to reusable renderer
  2314. },
  2315. ...
  2316. ]);
  2317. * </code></pre></p>
  2318. *
  2319. * <p><b><u>Filtering</u></b></p>
  2320. * <p>A ComboBox {@link #doQuery uses filtering itself}, for information about filtering the ComboBox
  2321. * store manually see <tt>{@link #lastQuery}</tt>.</p>
  2322. * @constructor
  2323. * Create a new ComboBox.
  2324. * @param {Object} config Configuration options
  2325. * @xtype combo
  2326. */
  2327. Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, {
  2328. /**
  2329. * @cfg {Mixed} transform The id, DOM node or element of an existing HTML SELECT to convert to a ComboBox.
  2330. * Note that if you specify this and the combo is going to be in an {@link Ext.form.BasicForm} or
  2331. * {@link Ext.form.FormPanel}, you must also set <tt>{@link #lazyRender} = true</tt>.
  2332. */
  2333. /**
  2334. * @cfg {Boolean} lazyRender <tt>true</tt> to prevent the ComboBox from rendering until requested
  2335. * (should always be used when rendering into an {@link Ext.Editor} (e.g. {@link Ext.grid.EditorGridPanel Grids}),
  2336. * defaults to <tt>false</tt>).
  2337. */
  2338. /**
  2339. * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or <tt>true</tt> for a default
  2340. * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
  2341. * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
  2342. * <pre><code>{tag: "input", type: "text", size: "24", autocomplete: "off"}</code></pre>
  2343. */
  2344. /**
  2345. * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to <tt>undefined</tt>).
  2346. * Acceptable values for this property are:
  2347. * <div class="mdetail-params"><ul>
  2348. * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
  2349. * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally,
  2350. * automatically generating {@link Ext.data.Field#name field names} to work with all data components.
  2351. * <div class="mdetail-params"><ul>
  2352. * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
  2353. * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
  2354. * {@link #valueField} and {@link #displayField})</div></li>
  2355. * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
  2356. * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
  2357. * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
  2358. * </div></li></ul></div></li></ul></div>
  2359. * <p>See also <tt>{@link #mode}</tt>.</p>
  2360. */
  2361. /**
  2362. * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
  2363. * the dropdown list (defaults to undefined, with no header element)
  2364. */
  2365. // private
  2366. defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"},
  2367. /**
  2368. * @cfg {Number} listWidth The width (used as a parameter to {@link Ext.Element#setWidth}) of the dropdown
  2369. * list (defaults to the width of the ComboBox field). See also <tt>{@link #minListWidth}
  2370. */
  2371. /**
  2372. * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this
  2373. * ComboBox (defaults to undefined if <tt>{@link #mode} = 'remote'</tt> or <tt>'field1'</tt> if
  2374. * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on
  2375. * the store configuration}).
  2376. * <p>See also <tt>{@link #valueField}</tt>.</p>
  2377. * <p><b>Note</b>: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a
  2378. * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not
  2379. * active.</p>
  2380. */
  2381. /**
  2382. * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this
  2383. * ComboBox (defaults to undefined if <tt>{@link #mode} = 'remote'</tt> or <tt>'field2'</tt> if
  2384. * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on
  2385. * the store configuration}).
  2386. * <p><b>Note</b>: use of a <tt>valueField</tt> requires the user to make a selection in order for a value to be
  2387. * mapped. See also <tt>{@link #hiddenName}</tt>, <tt>{@link #hiddenValue}</tt>, and <tt>{@link #displayField}</tt>.</p>
  2388. */
  2389. /**
  2390. * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
  2391. * field's data value (defaults to the underlying DOM element's name). Required for the combo's value to automatically
  2392. * post during a form submission. See also {@link #valueField}.
  2393. * <p><b>Note</b>: the hidden field's id will also default to this name if {@link #hiddenId} is not specified.
  2394. * The ComboBox {@link Ext.Component#id id} and the <tt>{@link #hiddenId}</tt> <b>should be different</b>, since
  2395. * no two DOM nodes should share the same id. So, if the ComboBox <tt>{@link Ext.form.Field#name name}</tt> and
  2396. * <tt>hiddenName</tt> are the same, you should specify a unique <tt>{@link #hiddenId}</tt>.</p>
  2397. */
  2398. /**
  2399. * @cfg {String} hiddenId If <tt>{@link #hiddenName}</tt> is specified, <tt>hiddenId</tt> can also be provided
  2400. * to give the hidden field a unique id (defaults to the <tt>{@link #hiddenName}</tt>). The <tt>hiddenId</tt>
  2401. * and combo {@link Ext.Component#id id} should be different, since no two DOM
  2402. * nodes should share the same id.
  2403. */
  2404. /**
  2405. * @cfg {String} hiddenValue Sets the initial value of the hidden field if {@link #hiddenName} is
  2406. * specified to contain the selected {@link #valueField}, from the Store. Defaults to the configured
  2407. * <tt>{@link Ext.form.Field#value value}</tt>.
  2408. */
  2409. /**
  2410. * @cfg {String} listClass The CSS class to add to the predefined <tt>'x-combo-list'</tt> class
  2411. * applied the dropdown list element (defaults to '').
  2412. */
  2413. listClass : '',
  2414. /**
  2415. * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list
  2416. * (defaults to <tt>'x-combo-selected'</tt>)
  2417. */
  2418. selectedClass : 'x-combo-selected',
  2419. /**
  2420. * @cfg {String} listEmptyText The empty text to display in the data view if no items are found.
  2421. * (defaults to '')
  2422. */
  2423. listEmptyText: '',
  2424. /**
  2425. * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always
  2426. * get the class <tt>'x-form-trigger'</tt> and <tt>triggerClass</tt> will be <b>appended</b> if specified
  2427. * (defaults to <tt>'x-form-arrow-trigger'</tt> which displays a downward arrow icon).
  2428. */
  2429. triggerClass : 'x-form-arrow-trigger',
  2430. /**
  2431. * @cfg {Boolean/String} shadow <tt>true</tt> or <tt>"sides"</tt> for the default effect, <tt>"frame"</tt> for
  2432. * 4-way shadow, and <tt>"drop"</tt> for bottom-right
  2433. */
  2434. shadow : 'sides',
  2435. /**
  2436. * @cfg {String/Array} listAlign A valid anchor position value. See <tt>{@link Ext.Element#alignTo}</tt> for details
  2437. * on supported anchor positions and offsets. To specify x/y offsets as well, this value
  2438. * may be specified as an Array of <tt>{@link Ext.Element#alignTo}</tt> method arguments.</p>
  2439. * <pre><code>[ 'tl-bl?', [6,0] ]</code></pre>(defaults to <tt>'tl-bl?'</tt>)
  2440. */
  2441. listAlign : 'tl-bl?',
  2442. /**
  2443. * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown
  2444. * (defaults to <tt>300</tt>)
  2445. */
  2446. maxHeight : 300,
  2447. /**
  2448. * @cfg {Number} minHeight The minimum height in pixels of the dropdown list when the list is constrained by its
  2449. * distance to the viewport edges (defaults to <tt>90</tt>)
  2450. */
  2451. minHeight : 90,
  2452. /**
  2453. * @cfg {String} triggerAction The action to execute when the trigger is clicked.
  2454. * <div class="mdetail-params"><ul>
  2455. * <li><b><tt>'query'</tt></b> : <b>Default</b>
  2456. * <p class="sub-desc">{@link #doQuery run the query} using the {@link Ext.form.Field#getRawValue raw value}.</p></li>
  2457. * <li><b><tt>'all'</tt></b> :
  2458. * <p class="sub-desc">{@link #doQuery run the query} specified by the <tt>{@link #allQuery}</tt> config option</p></li>
  2459. * </ul></div>
  2460. * <p>See also <code>{@link #queryParam}</code>.</p>
  2461. */
  2462. triggerAction : 'query',
  2463. /**
  2464. * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and
  2465. * {@link #typeAhead} activate (defaults to <tt>4</tt> if <tt>{@link #mode} = 'remote'</tt> or <tt>0</tt> if
  2466. * <tt>{@link #mode} = 'local'</tt>, does not apply if
  2467. * <tt>{@link Ext.form.TriggerField#editable editable} = false</tt>).
  2468. */
  2469. minChars : 4,
  2470. /**
  2471. * @cfg {Boolean} autoSelect <tt>true</tt> to select the first result gathered by the data store (defaults
  2472. * to <tt>true</tt>). A false value would require a manual selection from the dropdown list to set the components value
  2473. * unless the value of ({@link #typeAheadDelay}) were true.
  2474. */
  2475. autoSelect : true,
  2476. /**
  2477. * @cfg {Boolean} typeAhead <tt>true</tt> to populate and autoselect the remainder of the text being
  2478. * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
  2479. * to <tt>false</tt>)
  2480. */
  2481. typeAhead : false,
  2482. /**
  2483. * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and
  2484. * sending the query to filter the dropdown list (defaults to <tt>500</tt> if <tt>{@link #mode} = 'remote'</tt>
  2485. * or <tt>10</tt> if <tt>{@link #mode} = 'local'</tt>)
  2486. */
  2487. queryDelay : 500,
  2488. /**
  2489. * @cfg {Number} pageSize If greater than <tt>0</tt>, a {@link Ext.PagingToolbar} is displayed in the
  2490. * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and
  2491. * {@link Ext.PagingToolbar#pageSize limit} parameters. Only applies when <tt>{@link #mode} = 'remote'</tt>
  2492. * (defaults to <tt>0</tt>).
  2493. */
  2494. pageSize : 0,
  2495. /**
  2496. * @cfg {Boolean} selectOnFocus <tt>true</tt> to select any existing text in the field immediately on focus.
  2497. * Only applies when <tt>{@link Ext.form.TriggerField#editable editable} = true</tt> (defaults to
  2498. * <tt>false</tt>).
  2499. */
  2500. selectOnFocus : false,
  2501. /**
  2502. * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store)
  2503. * as it will be passed on the querystring (defaults to <tt>'query'</tt>)
  2504. */
  2505. queryParam : 'query',
  2506. /**
  2507. * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies
  2508. * when <tt>{@link #mode} = 'remote'</tt> (defaults to <tt>'Loading...'</tt>)
  2509. */
  2510. loadingText : 'Loading...',
  2511. /**
  2512. * @cfg {Boolean} resizable <tt>true</tt> to add a resize handle to the bottom of the dropdown list
  2513. * (creates an {@link Ext.Resizable} with 'se' {@link Ext.Resizable#pinned pinned} handles).
  2514. * Defaults to <tt>false</tt>.
  2515. */
  2516. resizable : false,
  2517. /**
  2518. * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if
  2519. * <tt>{@link #resizable} = true</tt> (defaults to <tt>8</tt>)
  2520. */
  2521. handleHeight : 8,
  2522. /**
  2523. * @cfg {String} allQuery The text query to send to the server to return all records for the list
  2524. * with no filtering (defaults to '')
  2525. */
  2526. allQuery: '',
  2527. /**
  2528. * @cfg {String} mode Acceptable values are:
  2529. * <div class="mdetail-params"><ul>
  2530. * <li><b><tt>'remote'</tt></b> : <b>Default</b>
  2531. * <p class="sub-desc">Automatically loads the <tt>{@link #store}</tt> the <b>first</b> time the trigger
  2532. * is clicked. If you do not want the store to be automatically loaded the first time the trigger is
  2533. * clicked, set to <tt>'local'</tt> and manually load the store. To force a requery of the store
  2534. * <b>every</b> time the trigger is clicked see <tt>{@link #lastQuery}</tt>.</p></li>
  2535. * <li><b><tt>'local'</tt></b> :
  2536. * <p class="sub-desc">ComboBox loads local data</p>
  2537. * <pre><code>
  2538. var combo = new Ext.form.ComboBox({
  2539. renderTo: document.body,
  2540. mode: 'local',
  2541. store: new Ext.data.ArrayStore({
  2542. id: 0,
  2543. fields: [
  2544. 'myId', // numeric value is the key
  2545. 'displayText'
  2546. ],
  2547. data: [[1, 'item1'], [2, 'item2']] // data is local
  2548. }),
  2549. valueField: 'myId',
  2550. displayField: 'displayText',
  2551. triggerAction: 'all'
  2552. });
  2553. * </code></pre></li>
  2554. * </ul></div>
  2555. */
  2556. mode: 'remote',
  2557. /**
  2558. * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to <tt>70</tt>, will
  2559. * be ignored if <tt>{@link #listWidth}</tt> has a higher value)
  2560. */
  2561. minListWidth : 70,
  2562. /**
  2563. * @cfg {Boolean} forceSelection <tt>true</tt> to restrict the selected value to one of the values in the list,
  2564. * <tt>false</tt> to allow the user to set arbitrary text into the field (defaults to <tt>false</tt>)
  2565. */
  2566. forceSelection : false,
  2567. /**
  2568. * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
  2569. * if <tt>{@link #typeAhead} = true</tt> (defaults to <tt>250</tt>)
  2570. */
  2571. typeAheadDelay : 250,
  2572. /**
  2573. * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
  2574. * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this
  2575. * default text is used, it means there is no value set and no validation will occur on this field.
  2576. */
  2577. /**
  2578. * @cfg {Boolean} lazyInit <tt>true</tt> to not initialize the list for this combo until the field is focused
  2579. * (defaults to <tt>true</tt>)
  2580. */
  2581. lazyInit : true,
  2582. /**
  2583. * @cfg {Boolean} clearFilterOnReset <tt>true</tt> to clear any filters on the store (when in local mode) when reset is called
  2584. * (defaults to <tt>true</tt>)
  2585. */
  2586. clearFilterOnReset : true,
  2587. /**
  2588. * @cfg {Boolean} submitValue False to clear the name attribute on the field so that it is not submitted during a form post.
  2589. * If a hiddenName is specified, setting this to true will cause both the hidden field and the element to be submitted.
  2590. * Defaults to <tt>undefined</tt>.
  2591. */
  2592. submitValue: undefined,
  2593. /**
  2594. * The value of the match string used to filter the store. Delete this property to force a requery.
  2595. * Example use:
  2596. * <pre><code>
  2597. var combo = new Ext.form.ComboBox({
  2598. ...
  2599. mode: 'remote',
  2600. ...
  2601. listeners: {
  2602. // delete the previous query in the beforequery event or set
  2603. // combo.lastQuery = null (this will reload the store the next time it expands)
  2604. beforequery: function(qe){
  2605. delete qe.combo.lastQuery;
  2606. }
  2607. }
  2608. });
  2609. * </code></pre>
  2610. * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
  2611. * configure the combo with <tt>lastQuery=''</tt>. Example use:
  2612. * <pre><code>
  2613. var combo = new Ext.form.ComboBox({
  2614. ...
  2615. mode: 'local',
  2616. triggerAction: 'all',
  2617. lastQuery: ''
  2618. });
  2619. * </code></pre>
  2620. * @property lastQuery
  2621. * @type String
  2622. */
  2623. // private
  2624. initComponent : function(){
  2625. Ext.form.ComboBox.superclass.initComponent.call(this);
  2626. this.addEvents(
  2627. /**
  2628. * @event expand
  2629. * Fires when the dropdown list is expanded
  2630. * @param {Ext.form.ComboBox} combo This combo box
  2631. */
  2632. 'expand',
  2633. /**
  2634. * @event collapse
  2635. * Fires when the dropdown list is collapsed
  2636. * @param {Ext.form.ComboBox} combo This combo box
  2637. */
  2638. 'collapse',
  2639. /**
  2640. * @event beforeselect
  2641. * Fires before a list item is selected. Return false to cancel the selection.
  2642. * @param {Ext.form.ComboBox} combo This combo box
  2643. * @param {Ext.data.Record} record The data record returned from the underlying store
  2644. * @param {Number} index The index of the selected item in the dropdown list
  2645. */
  2646. 'beforeselect',
  2647. /**
  2648. * @event select
  2649. * Fires when a list item is selected
  2650. * @param {Ext.form.ComboBox} combo This combo box
  2651. * @param {Ext.data.Record} record The data record returned from the underlying store
  2652. * @param {Number} index The index of the selected item in the dropdown list
  2653. */
  2654. 'select',
  2655. /**
  2656. * @event beforequery
  2657. * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
  2658. * cancel property to true.
  2659. * @param {Object} queryEvent An object that has these properties:<ul>
  2660. * <li><code>combo</code> : Ext.form.ComboBox <div class="sub-desc">This combo box</div></li>
  2661. * <li><code>query</code> : String <div class="sub-desc">The query</div></li>
  2662. * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
  2663. * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
  2664. * </ul>
  2665. */
  2666. 'beforequery'
  2667. );
  2668. if(this.transform){
  2669. var s = Ext.getDom(this.transform);
  2670. if(!this.hiddenName){
  2671. this.hiddenName = s.name;
  2672. }
  2673. if(!this.store){
  2674. this.mode = 'local';
  2675. var d = [], opts = s.options;
  2676. for(var i = 0, len = opts.length;i < len; i++){
  2677. var o = opts[i],
  2678. value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text;
  2679. if(o.selected && Ext.isEmpty(this.value, true)) {
  2680. this.value = value;
  2681. }
  2682. d.push([value, o.text]);
  2683. }
  2684. this.store = new Ext.data.ArrayStore({
  2685. 'id': 0,
  2686. fields: ['value', 'text'],
  2687. data : d,
  2688. autoDestroy: true
  2689. });
  2690. this.valueField = 'value';
  2691. this.displayField = 'text';
  2692. }
  2693. s.name = Ext.id(); // wipe out the name in case somewhere else they have a reference
  2694. if(!this.lazyRender){
  2695. this.target = true;
  2696. this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate);
  2697. this.render(this.el.parentNode, s);
  2698. }
  2699. Ext.removeNode(s);
  2700. }
  2701. //auto-configure store from local array data
  2702. else if(this.store){
  2703. this.store = Ext.StoreMgr.lookup(this.store);
  2704. if(this.store.autoCreated){
  2705. this.displayField = this.valueField = 'field1';
  2706. if(!this.store.expandData){
  2707. this.displayField = 'field2';
  2708. }
  2709. this.mode = 'local';
  2710. }
  2711. }
  2712. this.selectedIndex = -1;
  2713. if(this.mode == 'local'){
  2714. if(!Ext.isDefined(this.initialConfig.queryDelay)){
  2715. this.queryDelay = 10;
  2716. }
  2717. if(!Ext.isDefined(this.initialConfig.minChars)){
  2718. this.minChars = 0;
  2719. }
  2720. }
  2721. },
  2722. // private
  2723. onRender : function(ct, position){
  2724. if(this.hiddenName && !Ext.isDefined(this.submitValue)){
  2725. this.submitValue = false;
  2726. }
  2727. Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
  2728. if(this.hiddenName){
  2729. this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
  2730. id: (this.hiddenId||this.hiddenName)}, 'before', true);
  2731. }
  2732. if(Ext.isGecko){
  2733. this.el.dom.setAttribute('autocomplete', 'off');
  2734. }
  2735. if(!this.lazyInit){
  2736. this.initList();
  2737. }else{
  2738. this.on('focus', this.initList, this, {single: true});
  2739. }
  2740. },
  2741. // private
  2742. initValue : function(){
  2743. Ext.form.ComboBox.superclass.initValue.call(this);
  2744. if(this.hiddenField){
  2745. this.hiddenField.value =
  2746. Ext.value(Ext.isDefined(this.hiddenValue) ? this.hiddenValue : this.value, '');
  2747. }
  2748. },
  2749. getParentZIndex : function(){
  2750. var zindex;
  2751. if (this.ownerCt){
  2752. this.findParentBy(function(ct){
  2753. zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
  2754. return !!zindex;
  2755. });
  2756. }
  2757. return zindex;
  2758. },
  2759. // private
  2760. initList : function(){
  2761. if(!this.list){
  2762. var cls = 'x-combo-list',
  2763. listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
  2764. zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10);
  2765. if (!zindex) {
  2766. zindex = this.getParentZIndex();
  2767. }
  2768. this.list = new Ext.Layer({
  2769. parentEl: listParent,
  2770. shadow: this.shadow,
  2771. cls: [cls, this.listClass].join(' '),
  2772. constrain:false,
  2773. zindex: (zindex || 12000) + 5
  2774. });
  2775. var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
  2776. this.list.setSize(lw, 0);
  2777. this.list.swallowEvent('mousewheel');
  2778. this.assetHeight = 0;
  2779. if(this.syncFont !== false){
  2780. this.list.setStyle('font-size', this.el.getStyle('font-size'));
  2781. }
  2782. if(this.title){
  2783. this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
  2784. this.assetHeight += this.header.getHeight();
  2785. }
  2786. this.innerList = this.list.createChild({cls:cls+'-inner'});
  2787. this.mon(this.innerList, 'mouseover', this.onViewOver, this);
  2788. this.mon(this.innerList, 'mousemove', this.onViewMove, this);
  2789. this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
  2790. if(this.pageSize){
  2791. this.footer = this.list.createChild({cls:cls+'-ft'});
  2792. this.pageTb = new Ext.PagingToolbar({
  2793. store: this.store,
  2794. pageSize: this.pageSize,
  2795. renderTo:this.footer
  2796. });
  2797. this.assetHeight += this.footer.getHeight();
  2798. }
  2799. if(!this.tpl){
  2800. /**
  2801. * @cfg {String/Ext.XTemplate} tpl <p>The template string, or {@link Ext.XTemplate} instance to
  2802. * use to display each item in the dropdown list. The dropdown list is displayed in a
  2803. * DataView. See {@link #view}.</p>
  2804. * <p>The default template string is:</p><pre><code>
  2805. '&lt;tpl for=".">&lt;div class="x-combo-list-item">{' + this.displayField + '}&lt;/div>&lt;/tpl>'
  2806. * </code></pre>
  2807. * <p>Override the default value to create custom UI layouts for items in the list.
  2808. * For example:</p><pre><code>
  2809. '&lt;tpl for=".">&lt;div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}&lt;/div>&lt;/tpl>'
  2810. * </code></pre>
  2811. * <p>The template <b>must</b> contain one or more substitution parameters using field
  2812. * names from the Combo's</b> {@link #store Store}. In the example above an
  2813. * <pre>ext:qtip</pre> attribute is added to display other fields from the Store.</p>
  2814. * <p>To preserve the default visual look of list items, add the CSS class name
  2815. * <pre>x-combo-list-item</pre> to the template's container element.</p>
  2816. * <p>Also see {@link #itemSelector} for additional details.</p>
  2817. */
  2818. this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
  2819. /**
  2820. * @cfg {String} itemSelector
  2821. * <p>A simple CSS selector (e.g. div.some-class or span:first-child) that will be
  2822. * used to determine what nodes the {@link #view Ext.DataView} which handles the dropdown
  2823. * display will be working with.</p>
  2824. * <p><b>Note</b>: this setting is <b>required</b> if a custom XTemplate has been
  2825. * specified in {@link #tpl} which assigns a class other than <pre>'x-combo-list-item'</pre>
  2826. * to dropdown list items</b>
  2827. */
  2828. }
  2829. /**
  2830. * The {@link Ext.DataView DataView} used to display the ComboBox's options.
  2831. * @type Ext.DataView
  2832. */
  2833. this.view = new Ext.DataView({
  2834. applyTo: this.innerList,
  2835. tpl: this.tpl,
  2836. singleSelect: true,
  2837. selectedClass: this.selectedClass,
  2838. itemSelector: this.itemSelector || '.' + cls + '-item',
  2839. emptyText: this.listEmptyText,
  2840. deferEmptyText: false
  2841. });
  2842. this.mon(this.view, {
  2843. containerclick : this.onViewClick,
  2844. click : this.onViewClick,
  2845. scope :this
  2846. });
  2847. this.bindStore(this.store, true);
  2848. if(this.resizable){
  2849. this.resizer = new Ext.Resizable(this.list, {
  2850. pinned:true, handles:'se'
  2851. });
  2852. this.mon(this.resizer, 'resize', function(r, w, h){
  2853. this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
  2854. this.listWidth = w;
  2855. this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
  2856. this.restrictHeight();
  2857. }, this);
  2858. this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
  2859. }
  2860. }
  2861. },
  2862. /**
  2863. * <p>Returns the element used to house this ComboBox's pop-up list. Defaults to the document body.</p>
  2864. * A custom implementation may be provided as a configuration option if the floating list needs to be rendered
  2865. * to a different Element. An example might be rendering the list inside a Menu so that clicking
  2866. * the list does not hide the Menu:<pre><code>
  2867. var store = new Ext.data.ArrayStore({
  2868. autoDestroy: true,
  2869. fields: ['initials', 'fullname'],
  2870. data : [
  2871. ['FF', 'Fred Flintstone'],
  2872. ['BR', 'Barney Rubble']
  2873. ]
  2874. });
  2875. var combo = new Ext.form.ComboBox({
  2876. store: store,
  2877. displayField: 'fullname',
  2878. emptyText: 'Select a name...',
  2879. forceSelection: true,
  2880. getListParent: function() {
  2881. return this.el.up('.x-menu');
  2882. },
  2883. iconCls: 'no-icon', //use iconCls if placing within menu to shift to right side of menu
  2884. mode: 'local',
  2885. selectOnFocus: true,
  2886. triggerAction: 'all',
  2887. typeAhead: true,
  2888. width: 135
  2889. });
  2890. var menu = new Ext.menu.Menu({
  2891. id: 'mainMenu',
  2892. items: [
  2893. combo // A Field in a Menu
  2894. ]
  2895. });
  2896. </code></pre>
  2897. */
  2898. getListParent : function() {
  2899. return document.body;
  2900. },
  2901. /**
  2902. * Returns the store associated with this combo.
  2903. * @return {Ext.data.Store} The store
  2904. */
  2905. getStore : function(){
  2906. return this.store;
  2907. },
  2908. // private
  2909. bindStore : function(store, initial){
  2910. if(this.store && !initial){
  2911. if(this.store !== store && this.store.autoDestroy){
  2912. this.store.destroy();
  2913. }else{
  2914. this.store.un('beforeload', this.onBeforeLoad, this);
  2915. this.store.un('load', this.onLoad, this);
  2916. this.store.un('exception', this.collapse, this);
  2917. }
  2918. if(!store){
  2919. this.store = null;
  2920. if(this.view){
  2921. this.view.bindStore(null);
  2922. }
  2923. if(this.pageTb){
  2924. this.pageTb.bindStore(null);
  2925. }
  2926. }
  2927. }
  2928. if(store){
  2929. if(!initial) {
  2930. this.lastQuery = null;
  2931. if(this.pageTb) {
  2932. this.pageTb.bindStore(store);
  2933. }
  2934. }
  2935. this.store = Ext.StoreMgr.lookup(store);
  2936. this.store.on({
  2937. scope: this,
  2938. beforeload: this.onBeforeLoad,
  2939. load: this.onLoad,
  2940. exception: this.collapse
  2941. });
  2942. if(this.view){
  2943. this.view.bindStore(store);
  2944. }
  2945. }
  2946. },
  2947. reset : function(){
  2948. Ext.form.ComboBox.superclass.reset.call(this);
  2949. if(this.clearFilterOnReset && this.mode == 'local'){
  2950. this.store.clearFilter();
  2951. }
  2952. },
  2953. // private
  2954. initEvents : function(){
  2955. Ext.form.ComboBox.superclass.initEvents.call(this);
  2956. this.keyNav = new Ext.KeyNav(this.el, {
  2957. "up" : function(e){
  2958. this.inKeyMode = true;
  2959. this.selectPrev();
  2960. },
  2961. "down" : function(e){
  2962. if(!this.isExpanded()){
  2963. this.onTriggerClick();
  2964. }else{
  2965. this.inKeyMode = true;
  2966. this.selectNext();
  2967. }
  2968. },
  2969. "enter" : function(e){
  2970. this.onViewClick();
  2971. },
  2972. "esc" : function(e){
  2973. this.collapse();
  2974. },
  2975. "tab" : function(e){
  2976. if (this.forceSelection === true) {
  2977. this.collapse();
  2978. } else {
  2979. this.onViewClick(false);
  2980. }
  2981. return true;
  2982. },
  2983. scope : this,
  2984. doRelay : function(e, h, hname){
  2985. if(hname == 'down' || this.scope.isExpanded()){
  2986. // this MUST be called before ComboBox#fireKey()
  2987. var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments);
  2988. if(!Ext.isIE && Ext.EventManager.useKeydown){
  2989. // call Combo#fireKey() for browsers which use keydown event (except IE)
  2990. this.scope.fireKey(e);
  2991. }
  2992. return relay;
  2993. }
  2994. return true;
  2995. },
  2996. forceKeyDown : true,
  2997. defaultEventAction: 'stopEvent'
  2998. });
  2999. this.queryDelay = Math.max(this.queryDelay || 10,
  3000. this.mode == 'local' ? 10 : 250);
  3001. this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
  3002. if(this.typeAhead){
  3003. this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
  3004. }
  3005. if(!this.enableKeyEvents){
  3006. this.mon(this.el, 'keyup', this.onKeyUp, this);
  3007. }
  3008. },
  3009. // private
  3010. onDestroy : function(){
  3011. if (this.dqTask){
  3012. this.dqTask.cancel();
  3013. this.dqTask = null;
  3014. }
  3015. this.bindStore(null);
  3016. Ext.destroy(
  3017. this.resizer,
  3018. this.view,
  3019. this.pageTb,
  3020. this.list
  3021. );
  3022. Ext.destroyMembers(this, 'hiddenField');
  3023. Ext.form.ComboBox.superclass.onDestroy.call(this);
  3024. },
  3025. // private
  3026. fireKey : function(e){
  3027. if (!this.isExpanded()) {
  3028. Ext.form.ComboBox.superclass.fireKey.call(this, e);
  3029. }
  3030. },
  3031. // private
  3032. onResize : function(w, h){
  3033. Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
  3034. if(!isNaN(w) && this.isVisible() && this.list){
  3035. this.doResize(w);
  3036. }else{
  3037. this.bufferSize = w;
  3038. }
  3039. },
  3040. doResize: function(w){
  3041. if(!Ext.isDefined(this.listWidth)){
  3042. var lw = Math.max(w, this.minListWidth);
  3043. this.list.setWidth(lw);
  3044. this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
  3045. }
  3046. },
  3047. // private
  3048. onEnable : function(){
  3049. Ext.form.ComboBox.superclass.onEnable.apply(this, arguments);
  3050. if(this.hiddenField){
  3051. this.hiddenField.disabled = false;
  3052. }
  3053. },
  3054. // private
  3055. onDisable : function(){
  3056. Ext.form.ComboBox.superclass.onDisable.apply(this, arguments);
  3057. if(this.hiddenField){
  3058. this.hiddenField.disabled = true;
  3059. }
  3060. },
  3061. // private
  3062. onBeforeLoad : function(){
  3063. if(!this.hasFocus){
  3064. return;
  3065. }
  3066. this.innerList.update(this.loadingText ?
  3067. '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
  3068. this.restrictHeight();
  3069. this.selectedIndex = -1;
  3070. },
  3071. // private
  3072. onLoad : function(){
  3073. if(!this.hasFocus){
  3074. return;
  3075. }
  3076. if(this.store.getCount() > 0 || this.listEmptyText){
  3077. this.expand();
  3078. this.restrictHeight();
  3079. if(this.lastQuery == this.allQuery){
  3080. if(this.editable){
  3081. this.el.dom.select();
  3082. }
  3083. if(this.autoSelect !== false && !this.selectByValue(this.value, true)){
  3084. this.select(0, true);
  3085. }
  3086. }else{
  3087. if(this.autoSelect !== false){
  3088. this.selectNext();
  3089. }
  3090. if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
  3091. this.taTask.delay(this.typeAheadDelay);
  3092. }
  3093. }
  3094. }else{
  3095. this.collapse();
  3096. }
  3097. },
  3098. // private
  3099. onTypeAhead : function(){
  3100. if(this.store.getCount() > 0){
  3101. var r = this.store.getAt(0);
  3102. var newValue = r.data[this.displayField];
  3103. var len = newValue.length;
  3104. var selStart = this.getRawValue().length;
  3105. if(selStart != len){
  3106. this.setRawValue(newValue);
  3107. this.selectText(selStart, newValue.length);
  3108. }
  3109. }
  3110. },
  3111. // private
  3112. assertValue : function(){
  3113. var val = this.getRawValue(),
  3114. rec = this.findRecord(this.displayField, val);
  3115. if(!rec && this.forceSelection){
  3116. if(val.length > 0 && val != this.emptyText){
  3117. this.el.dom.value = Ext.value(this.lastSelectionText, '');
  3118. this.applyEmptyText();
  3119. }else{
  3120. this.clearValue();
  3121. }
  3122. }else{
  3123. if(rec){
  3124. // onSelect may have already set the value and by doing so
  3125. // set the display field properly. Let's not wipe out the
  3126. // valueField here by just sending the displayField.
  3127. if (val == rec.get(this.displayField) && this.value == rec.get(this.valueField)){
  3128. return;
  3129. }
  3130. val = rec.get(this.valueField || this.displayField);
  3131. }
  3132. this.setValue(val);
  3133. }
  3134. },
  3135. // private
  3136. onSelect : function(record, index){
  3137. if(this.fireEvent('beforeselect', this, record, index) !== false){
  3138. this.setValue(record.data[this.valueField || this.displayField]);
  3139. this.collapse();
  3140. this.fireEvent('select', this, record, index);
  3141. }
  3142. },
  3143. // inherit docs
  3144. getName: function(){
  3145. var hf = this.hiddenField;
  3146. return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this);
  3147. },
  3148. /**
  3149. * Returns the currently selected field value or empty string if no value is set.
  3150. * @return {String} value The selected value
  3151. */
  3152. getValue : function(){
  3153. if(this.valueField){
  3154. return Ext.isDefined(this.value) ? this.value : '';
  3155. }else{
  3156. return Ext.form.ComboBox.superclass.getValue.call(this);
  3157. }
  3158. },
  3159. /**
  3160. * Clears any text/value currently set in the field
  3161. */
  3162. clearValue : function(){
  3163. if(this.hiddenField){
  3164. this.hiddenField.value = '';
  3165. }
  3166. this.setRawValue('');
  3167. this.lastSelectionText = '';
  3168. this.applyEmptyText();
  3169. this.value = '';
  3170. },
  3171. /**
  3172. * Sets the specified value into the field. If the value finds a match, the corresponding record text
  3173. * will be displayed in the field. If the value does not match the data value of an existing item,
  3174. * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
  3175. * Otherwise the field will be blank (although the value will still be set).
  3176. * @param {String} value The value to match
  3177. * @return {Ext.form.Field} this
  3178. */
  3179. setValue : function(v){
  3180. var text = v;
  3181. if(this.valueField){
  3182. var r = this.findRecord(this.valueField, v);
  3183. if(r){
  3184. text = r.data[this.displayField];
  3185. }else if(Ext.isDefined(this.valueNotFoundText)){
  3186. text = this.valueNotFoundText;
  3187. }
  3188. }
  3189. this.lastSelectionText = text;
  3190. if(this.hiddenField){
  3191. this.hiddenField.value = Ext.value(v, '');
  3192. }
  3193. Ext.form.ComboBox.superclass.setValue.call(this, text);
  3194. this.value = v;
  3195. return this;
  3196. },
  3197. // private
  3198. findRecord : function(prop, value){
  3199. var record;
  3200. if(this.store.getCount() > 0){
  3201. this.store.each(function(r){
  3202. if(r.data[prop] == value){
  3203. record = r;
  3204. return false;
  3205. }
  3206. });
  3207. }
  3208. return record;
  3209. },
  3210. // private
  3211. onViewMove : function(e, t){
  3212. this.inKeyMode = false;
  3213. },
  3214. // private
  3215. onViewOver : function(e, t){
  3216. if(this.inKeyMode){ // prevent key nav and mouse over conflicts
  3217. return;
  3218. }
  3219. var item = this.view.findItemFromChild(t);
  3220. if(item){
  3221. var index = this.view.indexOf(item);
  3222. this.select(index, false);
  3223. }
  3224. },
  3225. // private
  3226. onViewClick : function(doFocus){
  3227. var index = this.view.getSelectedIndexes()[0],
  3228. s = this.store,
  3229. r = s.getAt(index);
  3230. if(r){
  3231. this.onSelect(r, index);
  3232. }else {
  3233. this.collapse();
  3234. }
  3235. if(doFocus !== false){
  3236. this.el.focus();
  3237. }
  3238. },
  3239. // private
  3240. restrictHeight : function(){
  3241. this.innerList.dom.style.height = '';
  3242. var inner = this.innerList.dom,
  3243. pad = this.list.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight,
  3244. h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
  3245. ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
  3246. hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
  3247. space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5;
  3248. h = Math.min(h, space, this.maxHeight);
  3249. this.innerList.setHeight(h);
  3250. this.list.beginUpdate();
  3251. this.list.setHeight(h+pad);
  3252. this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
  3253. this.list.endUpdate();
  3254. },
  3255. /**
  3256. * Returns true if the dropdown list is expanded, else false.
  3257. */
  3258. isExpanded : function(){
  3259. return this.list && this.list.isVisible();
  3260. },
  3261. /**
  3262. * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
  3263. * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
  3264. * @param {String} value The data value of the item to select
  3265. * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
  3266. * selected item if it is not currently in view (defaults to true)
  3267. * @return {Boolean} True if the value matched an item in the list, else false
  3268. */
  3269. selectByValue : function(v, scrollIntoView){
  3270. if(!Ext.isEmpty(v, true)){
  3271. var r = this.findRecord(this.valueField || this.displayField, v);
  3272. if(r){
  3273. this.select(this.store.indexOf(r), scrollIntoView);
  3274. return true;
  3275. }
  3276. }
  3277. return false;
  3278. },
  3279. /**
  3280. * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
  3281. * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
  3282. * @param {Number} index The zero-based index of the list item to select
  3283. * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
  3284. * selected item if it is not currently in view (defaults to true)
  3285. */
  3286. select : function(index, scrollIntoView){
  3287. this.selectedIndex = index;
  3288. this.view.select(index);
  3289. if(scrollIntoView !== false){
  3290. var el = this.view.getNode(index);
  3291. if(el){
  3292. this.innerList.scrollChildIntoView(el, false);
  3293. }
  3294. }
  3295. },
  3296. // private
  3297. selectNext : function(){
  3298. var ct = this.store.getCount();
  3299. if(ct > 0){
  3300. if(this.selectedIndex == -1){
  3301. this.select(0);
  3302. }else if(this.selectedIndex < ct-1){
  3303. this.select(this.selectedIndex+1);
  3304. }
  3305. }
  3306. },
  3307. // private
  3308. selectPrev : function(){
  3309. var ct = this.store.getCount();
  3310. if(ct > 0){
  3311. if(this.selectedIndex == -1){
  3312. this.select(0);
  3313. }else if(this.selectedIndex !== 0){
  3314. this.select(this.selectedIndex-1);
  3315. }
  3316. }
  3317. },
  3318. // private
  3319. onKeyUp : function(e){
  3320. var k = e.getKey();
  3321. if(this.editable !== false && this.readOnly !== true && (k == e.BACKSPACE || !e.isSpecialKey())){
  3322. this.lastKey = k;
  3323. this.dqTask.delay(this.queryDelay);
  3324. }
  3325. Ext.form.ComboBox.superclass.onKeyUp.call(this, e);
  3326. },
  3327. // private
  3328. validateBlur : function(){
  3329. return !this.list || !this.list.isVisible();
  3330. },
  3331. // private
  3332. initQuery : function(){
  3333. this.doQuery(this.getRawValue());
  3334. },
  3335. // private
  3336. beforeBlur : function(){
  3337. this.assertValue();
  3338. },
  3339. // private
  3340. postBlur : function(){
  3341. Ext.form.ComboBox.superclass.postBlur.call(this);
  3342. this.collapse();
  3343. this.inKeyMode = false;
  3344. },
  3345. /**
  3346. * Execute a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
  3347. * query allowing the query action to be canceled if needed.
  3348. * @param {String} query The SQL query to execute
  3349. * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
  3350. * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option. It
  3351. * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
  3352. */
  3353. doQuery : function(q, forceAll){
  3354. q = Ext.isEmpty(q) ? '' : q;
  3355. var qe = {
  3356. query: q,
  3357. forceAll: forceAll,
  3358. combo: this,
  3359. cancel:false
  3360. };
  3361. if(this.fireEvent('beforequery', qe)===false || qe.cancel){
  3362. return false;
  3363. }
  3364. q = qe.query;
  3365. forceAll = qe.forceAll;
  3366. if(forceAll === true || (q.length >= this.minChars)){
  3367. if(this.lastQuery !== q){
  3368. this.lastQuery = q;
  3369. if(this.mode == 'local'){
  3370. this.selectedIndex = -1;
  3371. if(forceAll){
  3372. this.store.clearFilter();
  3373. }else{
  3374. this.store.filter(this.displayField, q);
  3375. }
  3376. this.onLoad();
  3377. }else{
  3378. this.store.baseParams[this.queryParam] = q;
  3379. this.store.load({
  3380. params: this.getParams(q)
  3381. });
  3382. this.expand();
  3383. }
  3384. }else{
  3385. this.selectedIndex = -1;
  3386. this.onLoad();
  3387. }
  3388. }
  3389. },
  3390. // private
  3391. getParams : function(q){
  3392. var p = {};
  3393. //p[this.queryParam] = q;
  3394. if(this.pageSize){
  3395. p.start = 0;
  3396. p.limit = this.pageSize;
  3397. }
  3398. return p;
  3399. },
  3400. /**
  3401. * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion.
  3402. */
  3403. collapse : function(){
  3404. if(!this.isExpanded()){
  3405. return;
  3406. }
  3407. this.list.hide();
  3408. Ext.getDoc().un('mousewheel', this.collapseIf, this);
  3409. Ext.getDoc().un('mousedown', this.collapseIf, this);
  3410. this.fireEvent('collapse', this);
  3411. },
  3412. // private
  3413. collapseIf : function(e){
  3414. if(!this.isDestroyed && !e.within(this.wrap) && !e.within(this.list)){
  3415. this.collapse();
  3416. }
  3417. },
  3418. /**
  3419. * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion.
  3420. */
  3421. expand : function(){
  3422. if(this.isExpanded() || !this.hasFocus){
  3423. return;
  3424. }
  3425. if(this.title || this.pageSize){
  3426. this.assetHeight = 0;
  3427. if(this.title){
  3428. this.assetHeight += this.header.getHeight();
  3429. }
  3430. if(this.pageSize){
  3431. this.assetHeight += this.footer.getHeight();
  3432. }
  3433. }
  3434. if(this.bufferSize){
  3435. this.doResize(this.bufferSize);
  3436. delete this.bufferSize;
  3437. }
  3438. this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
  3439. // zindex can change, re-check it and set it if necessary
  3440. var listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
  3441. zindex = parseInt(Ext.fly(listParent).getStyle('z-index') ,10);
  3442. if (!zindex){
  3443. zindex = this.getParentZIndex();
  3444. }
  3445. if (zindex) {
  3446. this.list.setZIndex(zindex + 5);
  3447. }
  3448. this.list.show();
  3449. if(Ext.isGecko2){
  3450. this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
  3451. }
  3452. this.mon(Ext.getDoc(), {
  3453. scope: this,
  3454. mousewheel: this.collapseIf,
  3455. mousedown: this.collapseIf
  3456. });
  3457. this.fireEvent('expand', this);
  3458. },
  3459. /**
  3460. * @method onTriggerClick
  3461. * @hide
  3462. */
  3463. // private
  3464. // Implements the default empty TriggerField.onTriggerClick function
  3465. onTriggerClick : function(){
  3466. if(this.readOnly || this.disabled){
  3467. return;
  3468. }
  3469. if(this.isExpanded()){
  3470. this.collapse();
  3471. this.el.focus();
  3472. }else {
  3473. this.onFocus({});
  3474. if(this.triggerAction == 'all') {
  3475. this.doQuery(this.allQuery, true);
  3476. } else {
  3477. this.doQuery(this.getRawValue());
  3478. }
  3479. this.el.focus();
  3480. }
  3481. }
  3482. /**
  3483. * @hide
  3484. * @method autoSize
  3485. */
  3486. /**
  3487. * @cfg {Boolean} grow @hide
  3488. */
  3489. /**
  3490. * @cfg {Number} growMin @hide
  3491. */
  3492. /**
  3493. * @cfg {Number} growMax @hide
  3494. */
  3495. });
  3496. Ext.reg('combo', Ext.form.ComboBox);
  3497. /**
  3498. * @class Ext.form.Checkbox
  3499. * @extends Ext.form.Field
  3500. * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields.
  3501. * @constructor
  3502. * Creates a new Checkbox
  3503. * @param {Object} config Configuration options
  3504. * @xtype checkbox
  3505. */
  3506. Ext.form.Checkbox = Ext.extend(Ext.form.Field, {
  3507. /**
  3508. * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
  3509. */
  3510. focusClass : undefined,
  3511. /**
  3512. * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to 'x-form-field')
  3513. */
  3514. fieldClass : 'x-form-field',
  3515. /**
  3516. * @cfg {Boolean} checked <tt>true</tt> if the checkbox should render initially checked (defaults to <tt>false</tt>)
  3517. */
  3518. checked : false,
  3519. /**
  3520. * @cfg {String} boxLabel The text that appears beside the checkbox
  3521. */
  3522. boxLabel: '&#160;',
  3523. /**
  3524. * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
  3525. * {tag: 'input', type: 'checkbox', autocomplete: 'off'})
  3526. */
  3527. defaultAutoCreate : { tag: 'input', type: 'checkbox', autocomplete: 'off'},
  3528. /**
  3529. * @cfg {String} boxLabel The text that appears beside the checkbox
  3530. */
  3531. /**
  3532. * @cfg {String} inputValue The value that should go into the generated input element's value attribute
  3533. */
  3534. /**
  3535. * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
  3536. * handling the check event). The handler is passed the following parameters:
  3537. * <div class="mdetail-params"><ul>
  3538. * <li><b>checkbox</b> : Ext.form.Checkbox<div class="sub-desc">The Checkbox being toggled.</div></li>
  3539. * <li><b>checked</b> : Boolean<div class="sub-desc">The new checked state of the checkbox.</div></li>
  3540. * </ul></div>
  3541. */
  3542. /**
  3543. * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function
  3544. * (defaults to this Checkbox).
  3545. */
  3546. // private
  3547. actionMode : 'wrap',
  3548. // private
  3549. initComponent : function(){
  3550. Ext.form.Checkbox.superclass.initComponent.call(this);
  3551. this.addEvents(
  3552. /**
  3553. * @event check
  3554. * Fires when the checkbox is checked or unchecked.
  3555. * @param {Ext.form.Checkbox} this This checkbox
  3556. * @param {Boolean} checked The new checked value
  3557. */
  3558. 'check'
  3559. );
  3560. },
  3561. // private
  3562. onResize : function(){
  3563. Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
  3564. if(!this.boxLabel && !this.fieldLabel){
  3565. this.el.alignTo(this.wrap, 'c-c');
  3566. }
  3567. },
  3568. // private
  3569. initEvents : function(){
  3570. Ext.form.Checkbox.superclass.initEvents.call(this);
  3571. this.mon(this.el, {
  3572. scope: this,
  3573. click: this.onClick,
  3574. change: this.onClick
  3575. });
  3576. },
  3577. /**
  3578. * @hide
  3579. * Overridden and disabled. The editor element does not support standard valid/invalid marking.
  3580. * @method
  3581. */
  3582. markInvalid : Ext.emptyFn,
  3583. /**
  3584. * @hide
  3585. * Overridden and disabled. The editor element does not support standard valid/invalid marking.
  3586. * @method
  3587. */
  3588. clearInvalid : Ext.emptyFn,
  3589. // private
  3590. onRender : function(ct, position){
  3591. Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
  3592. if(this.inputValue !== undefined){
  3593. this.el.dom.value = this.inputValue;
  3594. }
  3595. this.wrap = this.el.wrap({cls: 'x-form-check-wrap'});
  3596. if(this.boxLabel){
  3597. this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel});
  3598. }
  3599. if(this.checked){
  3600. this.setValue(true);
  3601. }else{
  3602. this.checked = this.el.dom.checked;
  3603. }
  3604. // Need to repaint for IE, otherwise positioning is broken
  3605. if(Ext.isIE){
  3606. this.wrap.repaint();
  3607. }
  3608. this.resizeEl = this.positionEl = this.wrap;
  3609. },
  3610. // private
  3611. onDestroy : function(){
  3612. Ext.destroy(this.wrap);
  3613. Ext.form.Checkbox.superclass.onDestroy.call(this);
  3614. },
  3615. // private
  3616. initValue : function() {
  3617. this.originalValue = this.getValue();
  3618. },
  3619. /**
  3620. * Returns the checked state of the checkbox.
  3621. * @return {Boolean} True if checked, else false
  3622. */
  3623. getValue : function(){
  3624. if(this.rendered){
  3625. return this.el.dom.checked;
  3626. }
  3627. return this.checked;
  3628. },
  3629. // private
  3630. onClick : function(){
  3631. if(this.el.dom.checked != this.checked){
  3632. this.setValue(this.el.dom.checked);
  3633. }
  3634. },
  3635. /**
  3636. * Sets the checked state of the checkbox, fires the 'check' event, and calls a
  3637. * <code>{@link #handler}</code> (if configured).
  3638. * @param {Boolean/String} checked The following values will check the checkbox:
  3639. * <code>true, 'true', '1', or 'on'</code>. Any other value will uncheck the checkbox.
  3640. * @return {Ext.form.Field} this
  3641. */
  3642. setValue : function(v){
  3643. var checked = this.checked ;
  3644. this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
  3645. if(this.rendered){
  3646. this.el.dom.checked = this.checked;
  3647. this.el.dom.defaultChecked = this.checked;
  3648. }
  3649. if(checked != this.checked){
  3650. this.fireEvent('check', this, this.checked);
  3651. if(this.handler){
  3652. this.handler.call(this.scope || this, this, this.checked);
  3653. }
  3654. }
  3655. return this;
  3656. }
  3657. });
  3658. Ext.reg('checkbox', Ext.form.Checkbox);
  3659. /**
  3660. * @class Ext.form.CheckboxGroup
  3661. * @extends Ext.form.Field
  3662. * <p>A grouping container for {@link Ext.form.Checkbox} controls.</p>
  3663. * <p>Sample usage:</p>
  3664. * <pre><code>
  3665. var myCheckboxGroup = new Ext.form.CheckboxGroup({
  3666. id:'myGroup',
  3667. xtype: 'checkboxgroup',
  3668. fieldLabel: 'Single Column',
  3669. itemCls: 'x-check-group-alt',
  3670. // Put all controls in a single column with width 100%
  3671. columns: 1,
  3672. items: [
  3673. {boxLabel: 'Item 1', name: 'cb-col-1'},
  3674. {boxLabel: 'Item 2', name: 'cb-col-2', checked: true},
  3675. {boxLabel: 'Item 3', name: 'cb-col-3'}
  3676. ]
  3677. });
  3678. * </code></pre>
  3679. * @constructor
  3680. * Creates a new CheckboxGroup
  3681. * @param {Object} config Configuration options
  3682. * @xtype checkboxgroup
  3683. */
  3684. Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
  3685. /**
  3686. * @cfg {Array} items An Array of {@link Ext.form.Checkbox Checkbox}es or Checkbox config objects
  3687. * to arrange in the group.
  3688. */
  3689. /**
  3690. * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped
  3691. * checkbox/radio controls using automatic layout. This config can take several types of values:
  3692. * <ul><li><b>'auto'</b> : <p class="sub-desc">The controls will be rendered one per column on one row and the width
  3693. * of each column will be evenly distributed based on the width of the overall field container. This is the default.</p></li>
  3694. * <li><b>Number</b> : <p class="sub-desc">If you specific a number (e.g., 3) that number of columns will be
  3695. * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.</p></li>
  3696. * <li><b>Array</b> : Object<p class="sub-desc">You can also specify an array of column widths, mixing integer
  3697. * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will
  3698. * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float
  3699. * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field
  3700. * container you should do so.</p></li></ul>
  3701. */
  3702. columns : 'auto',
  3703. /**
  3704. * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
  3705. * top to bottom before starting on the next column. The number of controls in each column will be automatically
  3706. * calculated to keep columns as even as possible. The default value is false, so that controls will be added
  3707. * to columns one at a time, completely filling each row left to right before starting on the next row.
  3708. */
  3709. vertical : false,
  3710. /**
  3711. * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true).
  3712. * If no items are selected at validation time, {@link @blankText} will be used as the error text.
  3713. */
  3714. allowBlank : true,
  3715. /**
  3716. * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
  3717. * select at least one item in this group")
  3718. */
  3719. blankText : "You must select at least one item in this group",
  3720. // private
  3721. defaultType : 'checkbox',
  3722. // private
  3723. groupCls : 'x-form-check-group',
  3724. // private
  3725. initComponent: function(){
  3726. this.addEvents(
  3727. /**
  3728. * @event change
  3729. * Fires when the state of a child checkbox changes.
  3730. * @param {Ext.form.CheckboxGroup} this
  3731. * @param {Array} checked An array containing the checked boxes.
  3732. */
  3733. 'change'
  3734. );
  3735. this.on('change', this.validate, this);
  3736. Ext.form.CheckboxGroup.superclass.initComponent.call(this);
  3737. },
  3738. // private
  3739. onRender : function(ct, position){
  3740. if(!this.el){
  3741. var panelCfg = {
  3742. autoEl: {
  3743. id: this.id
  3744. },
  3745. cls: this.groupCls,
  3746. layout: 'column',
  3747. renderTo: ct,
  3748. bufferResize: false // Default this to false, since it doesn't really have a proper ownerCt.
  3749. };
  3750. var colCfg = {
  3751. xtype: 'container',
  3752. defaultType: this.defaultType,
  3753. layout: 'form',
  3754. defaults: {
  3755. hideLabel: true,
  3756. anchor: '100%'
  3757. }
  3758. };
  3759. if(this.items[0].items){
  3760. // The container has standard ColumnLayout configs, so pass them in directly
  3761. Ext.apply(panelCfg, {
  3762. layoutConfig: {columns: this.items.length},
  3763. defaults: this.defaults,
  3764. items: this.items
  3765. });
  3766. for(var i=0, len=this.items.length; i<len; i++){
  3767. Ext.applyIf(this.items[i], colCfg);
  3768. }
  3769. }else{
  3770. // The container has field item configs, so we have to generate the column
  3771. // panels first then move the items into the columns as needed.
  3772. var numCols, cols = [];
  3773. if(typeof this.columns == 'string'){ // 'auto' so create a col per item
  3774. this.columns = this.items.length;
  3775. }
  3776. if(!Ext.isArray(this.columns)){
  3777. var cs = [];
  3778. for(var i=0; i<this.columns; i++){
  3779. cs.push((100/this.columns)*.01); // distribute by even %
  3780. }
  3781. this.columns = cs;
  3782. }
  3783. numCols = this.columns.length;
  3784. // Generate the column configs with the correct width setting
  3785. for(var i=0; i<numCols; i++){
  3786. var cc = Ext.apply({items:[]}, colCfg);
  3787. cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] = this.columns[i];
  3788. if(this.defaults){
  3789. cc.defaults = Ext.apply(cc.defaults || {}, this.defaults);
  3790. }
  3791. cols.push(cc);
  3792. };
  3793. // Distribute the original items into the columns
  3794. if(this.vertical){
  3795. var rows = Math.ceil(this.items.length / numCols), ri = 0;
  3796. for(var i=0, len=this.items.length; i<len; i++){
  3797. if(i>0 && i%rows==0){
  3798. ri++;
  3799. }
  3800. if(this.items[i].fieldLabel){
  3801. this.items[i].hideLabel = false;
  3802. }
  3803. cols[ri].items.push(this.items[i]);
  3804. };
  3805. }else{
  3806. for(var i=0, len=this.items.length; i<len; i++){
  3807. var ci = i % numCols;
  3808. if(this.items[i].fieldLabel){
  3809. this.items[i].hideLabel = false;
  3810. }
  3811. cols[ci].items.push(this.items[i]);
  3812. };
  3813. }
  3814. Ext.apply(panelCfg, {
  3815. layoutConfig: {columns: numCols},
  3816. items: cols
  3817. });
  3818. }
  3819. this.panel = new Ext.Container(panelCfg);
  3820. this.panel.ownerCt = this;
  3821. this.el = this.panel.getEl();
  3822. if(this.forId && this.itemCls){
  3823. var l = this.el.up(this.itemCls).child('label', true);
  3824. if(l){
  3825. l.setAttribute('htmlFor', this.forId);
  3826. }
  3827. }
  3828. var fields = this.panel.findBy(function(c){
  3829. return c.isFormField;
  3830. }, this);
  3831. this.items = new Ext.util.MixedCollection();
  3832. this.items.addAll(fields);
  3833. }
  3834. Ext.form.CheckboxGroup.superclass.onRender.call(this, ct, position);
  3835. },
  3836. initValue : function(){
  3837. if(this.value){
  3838. this.setValue.apply(this, this.buffered ? this.value : [this.value]);
  3839. delete this.buffered;
  3840. delete this.value;
  3841. }
  3842. },
  3843. afterRender : function(){
  3844. Ext.form.CheckboxGroup.superclass.afterRender.call(this);
  3845. this.eachItem(function(item){
  3846. item.on('check', this.fireChecked, this);
  3847. item.inGroup = true;
  3848. });
  3849. },
  3850. // private
  3851. doLayout: function(){
  3852. //ugly method required to layout hidden items
  3853. if(this.rendered){
  3854. this.panel.forceLayout = this.ownerCt.forceLayout;
  3855. this.panel.doLayout();
  3856. }
  3857. },
  3858. // private
  3859. fireChecked: function(){
  3860. var arr = [];
  3861. this.eachItem(function(item){
  3862. if(item.checked){
  3863. arr.push(item);
  3864. }
  3865. });
  3866. this.fireEvent('change', this, arr);
  3867. },
  3868. /**
  3869. * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default
  3870. * is if allowBlank is set to true and no items are checked.
  3871. * @return {Array} Array of all validation errors
  3872. */
  3873. getErrors: function() {
  3874. var errors = Ext.form.CheckboxGroup.superclass.getErrors.apply(this, arguments);
  3875. if (!this.allowBlank) {
  3876. var blank = true;
  3877. this.eachItem(function(f){
  3878. if (f.checked) {
  3879. return (blank = false);
  3880. }
  3881. });
  3882. if (blank) errors.push(this.blankText);
  3883. }
  3884. return errors;
  3885. },
  3886. // private
  3887. isDirty: function(){
  3888. //override the behaviour to check sub items.
  3889. if (this.disabled || !this.rendered) {
  3890. return false;
  3891. }
  3892. var dirty = false;
  3893. this.eachItem(function(item){
  3894. if(item.isDirty()){
  3895. dirty = true;
  3896. return false;
  3897. }
  3898. });
  3899. return dirty;
  3900. },
  3901. // private
  3902. setReadOnly : function(readOnly){
  3903. if(this.rendered){
  3904. this.eachItem(function(item){
  3905. item.setReadOnly(readOnly);
  3906. });
  3907. }
  3908. this.readOnly = readOnly;
  3909. },
  3910. // private
  3911. onDisable : function(){
  3912. this.eachItem(function(item){
  3913. item.disable();
  3914. });
  3915. },
  3916. // private
  3917. onEnable : function(){
  3918. this.eachItem(function(item){
  3919. item.enable();
  3920. });
  3921. },
  3922. // private
  3923. onResize : function(w, h){
  3924. this.panel.setSize(w, h);
  3925. this.panel.doLayout();
  3926. },
  3927. // inherit docs from Field
  3928. reset : function(){
  3929. if (this.originalValue) {
  3930. // Clear all items
  3931. this.eachItem(function(c){
  3932. if(c.setValue){
  3933. c.setValue(false);
  3934. c.originalValue = c.getValue();
  3935. }
  3936. });
  3937. // Set items stored in originalValue, ugly - set a flag to reset the originalValue
  3938. // during the horrible onSetValue. This will allow trackResetOnLoad to function.
  3939. this.resetOriginal = true;
  3940. this.setValue(this.originalValue);
  3941. delete this.resetOriginal;
  3942. } else {
  3943. this.eachItem(function(c){
  3944. if(c.reset){
  3945. c.reset();
  3946. }
  3947. });
  3948. }
  3949. // Defer the clearInvalid so if BaseForm's collection is being iterated it will be called AFTER it is complete.
  3950. // Important because reset is being called on both the group and the individual items.
  3951. (function() {
  3952. this.clearInvalid();
  3953. }).defer(50, this);
  3954. },
  3955. /**
  3956. * {@link Ext.form.Checkbox#setValue Set the value(s)} of an item or items
  3957. * in the group. Examples illustrating how this method may be called:
  3958. * <pre><code>
  3959. // call with name and value
  3960. myCheckboxGroup.setValue('cb-col-1', true);
  3961. // call with an array of boolean values
  3962. myCheckboxGroup.setValue([true, false, false]);
  3963. // call with an object literal specifying item:value pairs
  3964. myCheckboxGroup.setValue({
  3965. 'cb-col-2': false,
  3966. 'cb-col-3': true
  3967. });
  3968. // use comma separated string to set items with name to true (checked)
  3969. myCheckboxGroup.setValue('cb-col-1,cb-col-3');
  3970. * </code></pre>
  3971. * See {@link Ext.form.Checkbox#setValue} for additional information.
  3972. * @param {Mixed} id The checkbox to check, or as described by example shown.
  3973. * @param {Boolean} value (optional) The value to set the item.
  3974. * @return {Ext.form.CheckboxGroup} this
  3975. */
  3976. setValue: function(){
  3977. if(this.rendered){
  3978. this.onSetValue.apply(this, arguments);
  3979. }else{
  3980. this.buffered = true;
  3981. this.value = arguments;
  3982. }
  3983. return this;
  3984. },
  3985. /**
  3986. * @private
  3987. * Sets the values of one or more of the items within the CheckboxGroup
  3988. * @param {String|Array|Object} id Can take multiple forms. Can be optionally:
  3989. * <ul>
  3990. * <li>An ID string to be used with a second argument</li>
  3991. * <li>An array of the form ['some', 'list', 'of', 'ids', 'to', 'mark', 'checked']</li>
  3992. * <li>An array in the form [true, true, false, true, false] etc, where each item relates to the check status of
  3993. * the checkbox at the same index</li>
  3994. * <li>An object containing ids of the checkboxes as keys and check values as properties</li>
  3995. * </ul>
  3996. * @param {String} value The value to set the field to if the first argument was a string
  3997. */
  3998. onSetValue: function(id, value){
  3999. if(arguments.length == 1){
  4000. if(Ext.isArray(id)){
  4001. Ext.each(id, function(val, idx){
  4002. if (Ext.isObject(val) && val.setValue){ // array of checkbox components to be checked
  4003. val.setValue(true);
  4004. if (this.resetOriginal === true) {
  4005. val.originalValue = val.getValue();
  4006. }
  4007. } else { // an array of boolean values
  4008. var item = this.items.itemAt(idx);
  4009. if(item){
  4010. item.setValue(val);
  4011. }
  4012. }
  4013. }, this);
  4014. }else if(Ext.isObject(id)){
  4015. // set of name/value pairs
  4016. for(var i in id){
  4017. var f = this.getBox(i);
  4018. if(f){
  4019. f.setValue(id[i]);
  4020. }
  4021. }
  4022. }else{
  4023. this.setValueForItem(id);
  4024. }
  4025. }else{
  4026. var f = this.getBox(id);
  4027. if(f){
  4028. f.setValue(value);
  4029. }
  4030. }
  4031. },
  4032. // private
  4033. beforeDestroy: function(){
  4034. Ext.destroy(this.panel);
  4035. Ext.form.CheckboxGroup.superclass.beforeDestroy.call(this);
  4036. },
  4037. setValueForItem : function(val){
  4038. val = String(val).split(',');
  4039. this.eachItem(function(item){
  4040. if(val.indexOf(item.inputValue)> -1){
  4041. item.setValue(true);
  4042. }
  4043. });
  4044. },
  4045. // private
  4046. getBox : function(id){
  4047. var box = null;
  4048. this.eachItem(function(f){
  4049. if(id == f || f.dataIndex == id || f.id == id || f.getName() == id){
  4050. box = f;
  4051. return false;
  4052. }
  4053. });
  4054. return box;
  4055. },
  4056. /**
  4057. * Gets an array of the selected {@link Ext.form.Checkbox} in the group.
  4058. * @return {Array} An array of the selected checkboxes.
  4059. */
  4060. getValue : function(){
  4061. var out = [];
  4062. this.eachItem(function(item){
  4063. if(item.checked){
  4064. out.push(item);
  4065. }
  4066. });
  4067. return out;
  4068. },
  4069. /**
  4070. * @private
  4071. * Convenience function which passes the given function to every item in the composite
  4072. * @param {Function} fn The function to call
  4073. * @param {Object} scope Optional scope object
  4074. */
  4075. eachItem: function(fn, scope) {
  4076. if(this.items && this.items.each){
  4077. this.items.each(fn, scope || this);
  4078. }
  4079. },
  4080. /**
  4081. * @cfg {String} name
  4082. * @hide
  4083. */
  4084. /**
  4085. * @method getRawValue
  4086. * @hide
  4087. */
  4088. getRawValue : Ext.emptyFn,
  4089. /**
  4090. * @method setRawValue
  4091. * @hide
  4092. */
  4093. setRawValue : Ext.emptyFn
  4094. });
  4095. Ext.reg('checkboxgroup', Ext.form.CheckboxGroup);
  4096. /**
  4097. * @class Ext.form.CompositeField
  4098. * @extends Ext.form.Field
  4099. * Composite field allowing a number of form Fields to be rendered on the same row. The fields are rendered
  4100. * using an hbox layout internally, so all of the normal HBox layout config items are available. Example usage:
  4101. * <pre>
  4102. {
  4103. xtype: 'compositefield',
  4104. labelWidth: 120
  4105. items: [
  4106. {
  4107. xtype : 'textfield',
  4108. fieldLabel: 'Title',
  4109. width : 20
  4110. },
  4111. {
  4112. xtype : 'textfield',
  4113. fieldLabel: 'First',
  4114. flex : 1
  4115. },
  4116. {
  4117. xtype : 'textfield',
  4118. fieldLabel: 'Last',
  4119. flex : 1
  4120. }
  4121. ]
  4122. }
  4123. * </pre>
  4124. * In the example above the composite's fieldLabel will be set to 'Title, First, Last' as it groups the fieldLabels
  4125. * of each of its children. This can be overridden by setting a fieldLabel on the compositefield itself:
  4126. * <pre>
  4127. {
  4128. xtype: 'compositefield',
  4129. fieldLabel: 'Custom label',
  4130. items: [...]
  4131. }
  4132. * </pre>
  4133. * Any Ext.form.* component can be placed inside a composite field.
  4134. */
  4135. Ext.form.CompositeField = Ext.extend(Ext.form.Field, {
  4136. /**
  4137. * @property defaultMargins
  4138. * @type String
  4139. * The margins to apply by default to each field in the composite
  4140. */
  4141. defaultMargins: '0 5 0 0',
  4142. /**
  4143. * @property skipLastItemMargin
  4144. * @type Boolean
  4145. * If true, the defaultMargins are not applied to the last item in the composite field set (defaults to true)
  4146. */
  4147. skipLastItemMargin: true,
  4148. /**
  4149. * @property isComposite
  4150. * @type Boolean
  4151. * Signifies that this is a Composite field
  4152. */
  4153. isComposite: true,
  4154. /**
  4155. * @property combineErrors
  4156. * @type Boolean
  4157. * True to combine errors from the individual fields into a single error message at the CompositeField level (defaults to true)
  4158. */
  4159. combineErrors: true,
  4160. //inherit docs
  4161. //Builds the composite field label
  4162. initComponent: function() {
  4163. var labels = [],
  4164. items = this.items,
  4165. item;
  4166. for (var i=0, j = items.length; i < j; i++) {
  4167. item = items[i];
  4168. labels.push(item.fieldLabel);
  4169. //apply any defaults
  4170. Ext.apply(item, this.defaults);
  4171. //apply default margins to each item except the last
  4172. if (!(i == j - 1 && this.skipLastItemMargin)) {
  4173. Ext.applyIf(item, {margins: this.defaultMargins});
  4174. }
  4175. }
  4176. this.fieldLabel = this.fieldLabel || this.buildLabel(labels);
  4177. /**
  4178. * @property fieldErrors
  4179. * @type Ext.util.MixedCollection
  4180. * MixedCollection of current errors on the Composite's subfields. This is used internally to track when
  4181. * to show and hide error messages at the Composite level. Listeners are attached to the MixedCollection's
  4182. * add, remove and replace events to update the error icon in the UI as errors are added or removed.
  4183. */
  4184. this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
  4185. return item.field;
  4186. });
  4187. this.fieldErrors.on({
  4188. scope : this,
  4189. add : this.updateInvalidMark,
  4190. remove : this.updateInvalidMark,
  4191. replace: this.updateInvalidMark
  4192. });
  4193. Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
  4194. },
  4195. /**
  4196. * @private
  4197. * Creates an internal container using hbox and renders the fields to it
  4198. */
  4199. onRender: function(ct, position) {
  4200. if (!this.el) {
  4201. /**
  4202. * @property innerCt
  4203. * @type Ext.Container
  4204. * A container configured with hbox layout which is responsible for laying out the subfields
  4205. */
  4206. var innerCt = this.innerCt = new Ext.Container({
  4207. layout : 'hbox',
  4208. renderTo: ct,
  4209. items : this.items,
  4210. cls : 'x-form-composite',
  4211. defaultMargins: '0 3 0 0'
  4212. });
  4213. this.el = innerCt.getEl();
  4214. var fields = innerCt.findBy(function(c) {
  4215. return c.isFormField;
  4216. }, this);
  4217. /**
  4218. * @property items
  4219. * @type Ext.util.MixedCollection
  4220. * Internal collection of all of the subfields in this Composite
  4221. */
  4222. this.items = new Ext.util.MixedCollection();
  4223. this.items.addAll(fields);
  4224. //if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
  4225. //methods of each subfield and show them at the Composite level instead
  4226. if (this.combineErrors) {
  4227. this.eachItem(function(field) {
  4228. Ext.apply(field, {
  4229. markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
  4230. clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
  4231. });
  4232. });
  4233. }
  4234. //set the label 'for' to the first item
  4235. var l = this.el.parent().parent().child('label', true);
  4236. if (l) {
  4237. l.setAttribute('for', this.items.items[0].id);
  4238. }
  4239. }
  4240. Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
  4241. },
  4242. /**
  4243. * Called if combineErrors is true and a subfield's markInvalid method is called.
  4244. * By default this just adds the subfield's error to the internal fieldErrors MixedCollection
  4245. * @param {Ext.form.Field} field The field that was marked invalid
  4246. * @param {String} message The error message
  4247. */
  4248. onFieldMarkInvalid: function(field, message) {
  4249. var name = field.getName(),
  4250. error = {field: name, error: message};
  4251. this.fieldErrors.replace(name, error);
  4252. field.el.addClass(field.invalidClass);
  4253. },
  4254. /**
  4255. * Called if combineErrors is true and a subfield's clearInvalid method is called.
  4256. * By default this just updates the internal fieldErrors MixedCollection.
  4257. * @param {Ext.form.Field} field The field that was marked invalid
  4258. */
  4259. onFieldClearInvalid: function(field) {
  4260. this.fieldErrors.removeKey(field.getName());
  4261. field.el.removeClass(field.invalidClass);
  4262. },
  4263. /**
  4264. * @private
  4265. * Called after a subfield is marked valid or invalid, this checks to see if any of the subfields are
  4266. * currently invalid. If any subfields are invalid it builds a combined error message marks the composite
  4267. * invalid, otherwise clearInvalid is called
  4268. */
  4269. updateInvalidMark: function() {
  4270. var ieStrict = Ext.isIE6 && Ext.isStrict;
  4271. if (this.fieldErrors.length == 0) {
  4272. this.clearInvalid();
  4273. //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
  4274. if (ieStrict) {
  4275. this.clearInvalid.defer(50, this);
  4276. }
  4277. } else {
  4278. var message = this.buildCombinedErrorMessage(this.fieldErrors.items);
  4279. this.sortErrors();
  4280. this.markInvalid(message);
  4281. //IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
  4282. if (ieStrict) {
  4283. this.markInvalid(message);
  4284. }
  4285. }
  4286. },
  4287. /**
  4288. * Performs validation checks on each subfield and returns false if any of them fail validation.
  4289. * @return {Boolean} False if any subfield failed validation
  4290. */
  4291. validateValue: function() {
  4292. var valid = true;
  4293. this.eachItem(function(field) {
  4294. if (!field.isValid()) valid = false;
  4295. });
  4296. return valid;
  4297. },
  4298. /**
  4299. * Takes an object containing error messages for contained fields, returning a combined error
  4300. * string (defaults to just placing each item on a new line). This can be overridden to provide
  4301. * custom combined error message handling.
  4302. * @param {Array} errors Array of errors in format: [{field: 'title', error: 'some error'}]
  4303. * @return {String} The combined error message
  4304. */
  4305. buildCombinedErrorMessage: function(errors) {
  4306. var combined = [],
  4307. error;
  4308. for (var i = 0, j = errors.length; i < j; i++) {
  4309. error = errors[i];
  4310. combined.push(String.format("{0}: {1}", error.field, error.error));
  4311. }
  4312. return combined.join("<br />");
  4313. },
  4314. /**
  4315. * Sorts the internal fieldErrors MixedCollection by the order in which the fields are defined.
  4316. * This is called before displaying errors to ensure that the errors are presented in the expected order.
  4317. * This function can be overridden to provide a custom sorting order if needed.
  4318. */
  4319. sortErrors: function() {
  4320. var fields = this.items;
  4321. this.fieldErrors.sort("ASC", function(a, b) {
  4322. var findByName = function(key) {
  4323. return function(field) {
  4324. return field.getName() == key;
  4325. };
  4326. };
  4327. var aIndex = fields.findIndexBy(findByName(a.field)),
  4328. bIndex = fields.findIndexBy(findByName(b.field));
  4329. return aIndex < bIndex ? -1 : 1;
  4330. });
  4331. },
  4332. /**
  4333. * Resets each field in the composite to their previous value
  4334. */
  4335. reset: function() {
  4336. this.eachItem(function(item) {
  4337. item.reset();
  4338. });
  4339. // Defer the clearInvalid so if BaseForm's collection is being iterated it will be called AFTER it is complete.
  4340. // Important because reset is being called on both the group and the individual items.
  4341. (function() {
  4342. this.clearInvalid();
  4343. }).defer(50, this);
  4344. },
  4345. /**
  4346. * Calls clearInvalid on all child fields. This is a convenience function and should not often need to be called
  4347. * as fields usually take care of clearing themselves
  4348. */
  4349. clearInvalidChildren: function() {
  4350. this.eachItem(function(item) {
  4351. item.clearInvalid();
  4352. });
  4353. },
  4354. /**
  4355. * Builds a label string from an array of subfield labels.
  4356. * By default this just joins the labels together with a comma
  4357. * @param {Array} segments Array of each of the labels in the composite field's subfields
  4358. * @return {String} The built label
  4359. */
  4360. buildLabel: function(segments) {
  4361. return segments.join(", ");
  4362. },
  4363. /**
  4364. * Checks each field in the composite and returns true if any is dirty
  4365. * @return {Boolean} True if any field is dirty
  4366. */
  4367. isDirty: function(){
  4368. //override the behaviour to check sub items.
  4369. if (this.disabled || !this.rendered) {
  4370. return false;
  4371. }
  4372. var dirty = false;
  4373. this.eachItem(function(item){
  4374. if(item.isDirty()){
  4375. dirty = true;
  4376. return false;
  4377. }
  4378. });
  4379. return dirty;
  4380. },
  4381. /**
  4382. * @private
  4383. * Convenience function which passes the given function to every item in the composite
  4384. * @param {Function} fn The function to call
  4385. * @param {Object} scope Optional scope object
  4386. */
  4387. eachItem: function(fn, scope) {
  4388. if(this.items && this.items.each){
  4389. this.items.each(fn, scope || this);
  4390. }
  4391. },
  4392. /**
  4393. * @private
  4394. * Passes the resize call through to the inner panel
  4395. */
  4396. onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
  4397. var innerCt = this.innerCt;
  4398. if (this.rendered && innerCt.rendered) {
  4399. innerCt.setSize(adjWidth, adjHeight);
  4400. }
  4401. Ext.form.CompositeField.superclass.onResize.apply(this, arguments);
  4402. },
  4403. /**
  4404. * @private
  4405. * Forces the internal container to be laid out again
  4406. */
  4407. doLayout: function(shallow, force) {
  4408. if (this.rendered) {
  4409. var innerCt = this.innerCt;
  4410. innerCt.forceLayout = this.ownerCt.forceLayout;
  4411. innerCt.doLayout(shallow, force);
  4412. }
  4413. },
  4414. /**
  4415. * @private
  4416. */
  4417. beforeDestroy: function(){
  4418. Ext.destroy(this.innerCt);
  4419. Ext.form.CompositeField.superclass.beforeDestroy.call(this);
  4420. },
  4421. //override the behaviour to check sub items.
  4422. setReadOnly : function(readOnly) {
  4423. readOnly = readOnly || true;
  4424. if(this.rendered){
  4425. this.eachItem(function(item){
  4426. item.setReadOnly(readOnly);
  4427. });
  4428. }
  4429. this.readOnly = readOnly;
  4430. },
  4431. onShow : function() {
  4432. Ext.form.CompositeField.superclass.onShow.call(this);
  4433. this.doLayout();
  4434. },
  4435. //override the behaviour to check sub items.
  4436. onDisable : function(){
  4437. this.eachItem(function(item){
  4438. item.disable();
  4439. });
  4440. },
  4441. //override the behaviour to check sub items.
  4442. onEnable : function(){
  4443. this.eachItem(function(item){
  4444. item.enable();
  4445. });
  4446. }
  4447. });
  4448. Ext.reg('compositefield', Ext.form.CompositeField);
  4449. /**
  4450. * @class Ext.form.Radio
  4451. * @extends Ext.form.Checkbox
  4452. * Single radio field. Same as Checkbox, but provided as a convenience for automatically setting the input type.
  4453. * Radio grouping is handled automatically by the browser if you give each radio in a group the same name.
  4454. * @constructor
  4455. * Creates a new Radio
  4456. * @param {Object} config Configuration options
  4457. * @xtype radio
  4458. */
  4459. Ext.form.Radio = Ext.extend(Ext.form.Checkbox, {
  4460. inputType: 'radio',
  4461. /**
  4462. * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
  4463. * @method
  4464. */
  4465. markInvalid : Ext.emptyFn,
  4466. /**
  4467. * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
  4468. * @method
  4469. */
  4470. clearInvalid : Ext.emptyFn,
  4471. /**
  4472. * If this radio is part of a group, it will return the selected value
  4473. * @return {String}
  4474. */
  4475. getGroupValue : function(){
  4476. var p = this.el.up('form') || Ext.getBody();
  4477. var c = p.child('input[name='+this.el.dom.name+']:checked', true);
  4478. return c ? c.value : null;
  4479. },
  4480. // private
  4481. onClick : function(){
  4482. if(this.el.dom.checked != this.checked){
  4483. var els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
  4484. els.each(function(el){
  4485. if(el.dom.id == this.id){
  4486. this.setValue(true);
  4487. }else{
  4488. Ext.getCmp(el.dom.id).setValue(false);
  4489. }
  4490. }, this);
  4491. }
  4492. },
  4493. /**
  4494. * Sets either the checked/unchecked status of this Radio, or, if a string value
  4495. * is passed, checks a sibling Radio of the same name whose value is the value specified.
  4496. * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
  4497. * @return {Ext.form.Field} this
  4498. */
  4499. setValue : function(v){
  4500. if (typeof v == 'boolean') {
  4501. Ext.form.Radio.superclass.setValue.call(this, v);
  4502. } else if (this.rendered) {
  4503. var r = this.getCheckEl().child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
  4504. if(r){
  4505. Ext.getCmp(r.id).setValue(true);
  4506. }
  4507. }
  4508. return this;
  4509. },
  4510. // private
  4511. getCheckEl: function(){
  4512. if(this.inGroup){
  4513. return this.el.up('.x-form-radio-group')
  4514. }
  4515. return this.el.up('form') || Ext.getBody();
  4516. }
  4517. });
  4518. Ext.reg('radio', Ext.form.Radio);
  4519. /**
  4520. * @class Ext.form.RadioGroup
  4521. * @extends Ext.form.CheckboxGroup
  4522. * A grouping container for {@link Ext.form.Radio} controls.
  4523. * @constructor
  4524. * Creates a new RadioGroup
  4525. * @param {Object} config Configuration options
  4526. * @xtype radiogroup
  4527. */
  4528. Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
  4529. /**
  4530. * @cfg {Array} items An Array of {@link Ext.form.Radio Radio}s or Radio config objects
  4531. * to arrange in the group.
  4532. */
  4533. /**
  4534. * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
  4535. * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
  4536. * be used as the error text.
  4537. */
  4538. allowBlank : true,
  4539. /**
  4540. * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
  4541. * (defaults to 'You must select one item in this group')
  4542. */
  4543. blankText : 'You must select one item in this group',
  4544. // private
  4545. defaultType : 'radio',
  4546. // private
  4547. groupCls : 'x-form-radio-group',
  4548. /**
  4549. * @event change
  4550. * Fires when the state of a child radio changes.
  4551. * @param {Ext.form.RadioGroup} this
  4552. * @param {Ext.form.Radio} checked The checked radio
  4553. */
  4554. /**
  4555. * Gets the selected {@link Ext.form.Radio} in the group, if it exists.
  4556. * @return {Ext.form.Radio} The selected radio.
  4557. */
  4558. getValue : function(){
  4559. var out = null;
  4560. this.eachItem(function(item){
  4561. if(item.checked){
  4562. out = item;
  4563. return false;
  4564. }
  4565. });
  4566. return out;
  4567. },
  4568. /**
  4569. * Sets the checked radio in the group.
  4570. * @param {String/Ext.form.Radio} id The radio to check.
  4571. * @param {Boolean} value The value to set the radio.
  4572. * @return {Ext.form.RadioGroup} this
  4573. */
  4574. onSetValue : function(id, value){
  4575. if(arguments.length > 1){
  4576. var f = this.getBox(id);
  4577. if(f){
  4578. f.setValue(value);
  4579. if(f.checked){
  4580. this.eachItem(function(item){
  4581. if (item !== f){
  4582. item.setValue(false);
  4583. }
  4584. });
  4585. }
  4586. }
  4587. }else{
  4588. this.setValueForItem(id);
  4589. }
  4590. },
  4591. setValueForItem : function(val){
  4592. val = String(val).split(',')[0];
  4593. this.eachItem(function(item){
  4594. item.setValue(val == item.inputValue);
  4595. });
  4596. },
  4597. // private
  4598. fireChecked : function(){
  4599. if(!this.checkTask){
  4600. this.checkTask = new Ext.util.DelayedTask(this.bufferChecked, this);
  4601. }
  4602. this.checkTask.delay(10);
  4603. },
  4604. // private
  4605. bufferChecked : function(){
  4606. var out = null;
  4607. this.eachItem(function(item){
  4608. if(item.checked){
  4609. out = item;
  4610. return false;
  4611. }
  4612. });
  4613. this.fireEvent('change', this, out);
  4614. },
  4615. onDestroy : function(){
  4616. if(this.checkTask){
  4617. this.checkTask.cancel();
  4618. this.checkTask = null;
  4619. }
  4620. Ext.form.RadioGroup.superclass.onDestroy.call(this);
  4621. }
  4622. });
  4623. Ext.reg('radiogroup', Ext.form.RadioGroup);
  4624. /**
  4625. * @class Ext.form.Hidden
  4626. * @extends Ext.form.Field
  4627. * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
  4628. * @constructor
  4629. * Create a new Hidden field.
  4630. * @param {Object} config Configuration options
  4631. * @xtype hidden
  4632. */
  4633. Ext.form.Hidden = Ext.extend(Ext.form.Field, {
  4634. // private
  4635. inputType : 'hidden',
  4636. // private
  4637. onRender : function(){
  4638. Ext.form.Hidden.superclass.onRender.apply(this, arguments);
  4639. },
  4640. // private
  4641. initEvents : function(){
  4642. this.originalValue = this.getValue();
  4643. },
  4644. // These are all private overrides
  4645. setSize : Ext.emptyFn,
  4646. setWidth : Ext.emptyFn,
  4647. setHeight : Ext.emptyFn,
  4648. setPosition : Ext.emptyFn,
  4649. setPagePosition : Ext.emptyFn,
  4650. markInvalid : Ext.emptyFn,
  4651. clearInvalid : Ext.emptyFn
  4652. });
  4653. Ext.reg('hidden', Ext.form.Hidden);/**
  4654. * @class Ext.form.BasicForm
  4655. * @extends Ext.util.Observable
  4656. * <p>Encapsulates the DOM &lt;form> element at the heart of the {@link Ext.form.FormPanel FormPanel} class, and provides
  4657. * input field management, validation, submission, and form loading services.</p>
  4658. * <p>By default, Ext Forms are submitted through Ajax, using an instance of {@link Ext.form.Action.Submit}.
  4659. * To enable normal browser submission of an Ext Form, use the {@link #standardSubmit} config option.</p>
  4660. * <p><b><u>File Uploads</u></b></p>
  4661. * <p>{@link #fileUpload File uploads} are not performed using Ajax submission, that
  4662. * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
  4663. * manner with the DOM <tt>&lt;form></tt> element temporarily modified to have its
  4664. * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
  4665. * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
  4666. * but removed after the return data has been gathered.</p>
  4667. * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
  4668. * server is using JSON to send the return object, then the
  4669. * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
  4670. * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
  4671. * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
  4672. * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
  4673. * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
  4674. * is created containing a <tt>responseText</tt> property in order to conform to the
  4675. * requirements of event handlers and callbacks.</p>
  4676. * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
  4677. * and some server technologies (notably JEE) may require some custom processing in order to
  4678. * retrieve parameter names and parameter values from the packet content.</p>
  4679. * @constructor
  4680. * @param {Mixed} el The form element or its id
  4681. * @param {Object} config Configuration options
  4682. */
  4683. Ext.form.BasicForm = Ext.extend(Ext.util.Observable, {
  4684. constructor: function(el, config){
  4685. Ext.apply(this, config);
  4686. if(Ext.isString(this.paramOrder)){
  4687. this.paramOrder = this.paramOrder.split(/[\s,|]/);
  4688. }
  4689. /**
  4690. * A {@link Ext.util.MixedCollection MixedCollection} containing all the Ext.form.Fields in this form.
  4691. * @type MixedCollection
  4692. * @property items
  4693. */
  4694. this.items = new Ext.util.MixedCollection(false, function(o){
  4695. return o.getItemId();
  4696. });
  4697. this.addEvents(
  4698. /**
  4699. * @event beforeaction
  4700. * Fires before any action is performed. Return false to cancel the action.
  4701. * @param {Form} this
  4702. * @param {Action} action The {@link Ext.form.Action} to be performed
  4703. */
  4704. 'beforeaction',
  4705. /**
  4706. * @event actionfailed
  4707. * Fires when an action fails.
  4708. * @param {Form} this
  4709. * @param {Action} action The {@link Ext.form.Action} that failed
  4710. */
  4711. 'actionfailed',
  4712. /**
  4713. * @event actioncomplete
  4714. * Fires when an action is completed.
  4715. * @param {Form} this
  4716. * @param {Action} action The {@link Ext.form.Action} that completed
  4717. */
  4718. 'actioncomplete'
  4719. );
  4720. if(el){
  4721. this.initEl(el);
  4722. }
  4723. Ext.form.BasicForm.superclass.constructor.call(this);
  4724. },
  4725. /**
  4726. * @cfg {String} method
  4727. * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
  4728. */
  4729. /**
  4730. * @cfg {DataReader} reader
  4731. * An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to read
  4732. * data when executing 'load' actions. This is optional as there is built-in
  4733. * support for processing JSON. For additional information on using an XMLReader
  4734. * see the example provided in examples/form/xml-form.html.
  4735. */
  4736. /**
  4737. * @cfg {DataReader} errorReader
  4738. * <p>An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to
  4739. * read field error messages returned from 'submit' actions. This is optional
  4740. * as there is built-in support for processing JSON.</p>
  4741. * <p>The Records which provide messages for the invalid Fields must use the
  4742. * Field name (or id) as the Record ID, and must contain a field called 'msg'
  4743. * which contains the error message.</p>
  4744. * <p>The errorReader does not have to be a full-blown implementation of a
  4745. * DataReader. It simply needs to implement a <tt>read(xhr)</tt> function
  4746. * which returns an Array of Records in an object with the following
  4747. * structure:</p><pre><code>
  4748. {
  4749. records: recordArray
  4750. }
  4751. </code></pre>
  4752. */
  4753. /**
  4754. * @cfg {String} url
  4755. * The URL to use for form actions if one isn't supplied in the
  4756. * <code>{@link #doAction doAction} options</code>.
  4757. */
  4758. /**
  4759. * @cfg {Boolean} fileUpload
  4760. * Set to true if this form is a file upload.
  4761. * <p>File uploads are not performed using normal 'Ajax' techniques, that is they are <b>not</b>
  4762. * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
  4763. * DOM <tt>&lt;form></tt> element temporarily modified to have its
  4764. * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
  4765. * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
  4766. * but removed after the return data has been gathered.</p>
  4767. * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
  4768. * server is using JSON to send the return object, then the
  4769. * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
  4770. * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
  4771. * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
  4772. * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
  4773. * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
  4774. * is created containing a <tt>responseText</tt> property in order to conform to the
  4775. * requirements of event handlers and callbacks.</p>
  4776. * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
  4777. * and some server technologies (notably JEE) may require some custom processing in order to
  4778. * retrieve parameter names and parameter values from the packet content.</p>
  4779. */
  4780. /**
  4781. * @cfg {Object} baseParams
  4782. * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
  4783. * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
  4784. */
  4785. /**
  4786. * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
  4787. */
  4788. timeout: 30,
  4789. /**
  4790. * @cfg {Object} api (Optional) If specified load and submit actions will be handled
  4791. * with {@link Ext.form.Action.DirectLoad} and {@link Ext.form.Action.DirectSubmit}.
  4792. * Methods which have been imported by Ext.Direct can be specified here to load and submit
  4793. * forms.
  4794. * Such as the following:<pre><code>
  4795. api: {
  4796. load: App.ss.MyProfile.load,
  4797. submit: App.ss.MyProfile.submit
  4798. }
  4799. </code></pre>
  4800. * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
  4801. * to customize how the load method is invoked.
  4802. * Submit actions will always use a standard form submit. The formHandler configuration must
  4803. * be set on the associated server-side method which has been imported by Ext.Direct</p>
  4804. */
  4805. /**
  4806. * @cfg {Array/String} paramOrder <p>A list of params to be executed server side.
  4807. * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
  4808. * <code>load</code> configuration.</p>
  4809. * <br><p>Specify the params in the order in which they must be executed on the
  4810. * server-side as either (1) an Array of String values, or (2) a String of params
  4811. * delimited by either whitespace, comma, or pipe. For example,
  4812. * any of the following would be acceptable:</p><pre><code>
  4813. paramOrder: ['param1','param2','param3']
  4814. paramOrder: 'param1 param2 param3'
  4815. paramOrder: 'param1,param2,param3'
  4816. paramOrder: 'param1|param2|param'
  4817. </code></pre>
  4818. */
  4819. paramOrder: undefined,
  4820. /**
  4821. * @cfg {Boolean} paramsAsHash Only used for the <code>{@link #api}</code>
  4822. * <code>load</code> configuration. Send parameters as a collection of named
  4823. * arguments (defaults to <tt>false</tt>). Providing a
  4824. * <tt>{@link #paramOrder}</tt> nullifies this configuration.
  4825. */
  4826. paramsAsHash: false,
  4827. /**
  4828. * @cfg {String} waitTitle
  4829. * The default title to show for the waiting message box (defaults to <tt>'Please Wait...'</tt>)
  4830. */
  4831. waitTitle: 'Please Wait...',
  4832. // private
  4833. activeAction : null,
  4834. /**
  4835. * @cfg {Boolean} trackResetOnLoad If set to <tt>true</tt>, {@link #reset}() resets to the last loaded
  4836. * or {@link #setValues}() data instead of when the form was first created. Defaults to <tt>false</tt>.
  4837. */
  4838. trackResetOnLoad : false,
  4839. /**
  4840. * @cfg {Boolean} standardSubmit
  4841. * <p>If set to <tt>true</tt>, standard HTML form submits are used instead
  4842. * of XHR (Ajax) style form submissions. Defaults to <tt>false</tt>.</p>
  4843. * <br><p><b>Note:</b> When using <code>standardSubmit</code>, the
  4844. * <code>options</code> to <code>{@link #submit}</code> are ignored because
  4845. * Ext's Ajax infrastracture is bypassed. To pass extra parameters (e.g.
  4846. * <code>baseParams</code> and <code>params</code>), utilize hidden fields
  4847. * to submit extra data, for example:</p>
  4848. * <pre><code>
  4849. new Ext.FormPanel({
  4850. standardSubmit: true,
  4851. baseParams: {
  4852. foo: 'bar'
  4853. },
  4854. {@link url}: 'myProcess.php',
  4855. items: [{
  4856. xtype: 'textfield',
  4857. name: 'userName'
  4858. }],
  4859. buttons: [{
  4860. text: 'Save',
  4861. handler: function(){
  4862. var fp = this.ownerCt.ownerCt,
  4863. form = fp.getForm();
  4864. if (form.isValid()) {
  4865. // check if there are baseParams and if
  4866. // hiddent items have been added already
  4867. if (fp.baseParams && !fp.paramsAdded) {
  4868. // add hidden items for all baseParams
  4869. for (i in fp.baseParams) {
  4870. fp.add({
  4871. xtype: 'hidden',
  4872. name: i,
  4873. value: fp.baseParams[i]
  4874. });
  4875. }
  4876. fp.doLayout();
  4877. // set a custom flag to prevent re-adding
  4878. fp.paramsAdded = true;
  4879. }
  4880. form.{@link #submit}();
  4881. }
  4882. }
  4883. }]
  4884. });
  4885. * </code></pre>
  4886. */
  4887. /**
  4888. * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
  4889. * element by passing it or its id or mask the form itself by passing in true.
  4890. * @type Mixed
  4891. * @property waitMsgTarget
  4892. */
  4893. // private
  4894. initEl : function(el){
  4895. this.el = Ext.get(el);
  4896. this.id = this.el.id || Ext.id();
  4897. if(!this.standardSubmit){
  4898. this.el.on('submit', this.onSubmit, this);
  4899. }
  4900. this.el.addClass('x-form');
  4901. },
  4902. /**
  4903. * Get the HTML form Element
  4904. * @return Ext.Element
  4905. */
  4906. getEl: function(){
  4907. return this.el;
  4908. },
  4909. // private
  4910. onSubmit : function(e){
  4911. e.stopEvent();
  4912. },
  4913. /**
  4914. * Destroys this object.
  4915. * @private
  4916. * @param {Boolean} bound true if the object is bound to a form panel. If this is the case
  4917. * the FormPanel will take care of destroying certain things, so we're just doubling up.
  4918. */
  4919. destroy: function(bound){
  4920. if(bound !== true){
  4921. this.items.each(function(f){
  4922. Ext.destroy(f);
  4923. });
  4924. Ext.destroy(this.el);
  4925. }
  4926. this.items.clear();
  4927. this.purgeListeners();
  4928. },
  4929. /**
  4930. * Returns true if client-side validation on the form is successful.
  4931. * @return Boolean
  4932. */
  4933. isValid : function(){
  4934. var valid = true;
  4935. this.items.each(function(f){
  4936. if(!f.validate()){
  4937. valid = false;
  4938. }
  4939. });
  4940. return valid;
  4941. },
  4942. /**
  4943. * <p>Returns true if any fields in this form have changed from their original values.</p>
  4944. * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
  4945. * Fields' <i>original values</i> are updated when the values are loaded by {@link #setValues}
  4946. * or {@link #loadRecord}.</p>
  4947. * @return Boolean
  4948. */
  4949. isDirty : function(){
  4950. var dirty = false;
  4951. this.items.each(function(f){
  4952. if(f.isDirty()){
  4953. dirty = true;
  4954. return false;
  4955. }
  4956. });
  4957. return dirty;
  4958. },
  4959. /**
  4960. * Performs a predefined action ({@link Ext.form.Action.Submit} or
  4961. * {@link Ext.form.Action.Load}) or a custom extension of {@link Ext.form.Action}
  4962. * to perform application-specific processing.
  4963. * @param {String/Object} actionName The name of the predefined action type,
  4964. * or instance of {@link Ext.form.Action} to perform.
  4965. * @param {Object} options (optional) The options to pass to the {@link Ext.form.Action}.
  4966. * All of the config options listed below are supported by both the
  4967. * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
  4968. * actions unless otherwise noted (custom actions could also accept
  4969. * other config options):<ul>
  4970. *
  4971. * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
  4972. * to the form's {@link #url}.)</div></li>
  4973. *
  4974. * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
  4975. * to the form's method, or POST if not defined)</div></li>
  4976. *
  4977. * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
  4978. * (defaults to the form's baseParams, or none if not defined)</p>
  4979. * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
  4980. *
  4981. * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action
  4982. * (defaults to the form's default headers)</div></li>
  4983. *
  4984. * <li><b>success</b> : Function<div class="sub-desc">The callback that will
  4985. * be invoked after a successful response (see top of
  4986. * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
  4987. * for a description of what constitutes a successful response).
  4988. * The function is passed the following parameters:<ul>
  4989. * <li><tt>form</tt> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
  4990. * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
  4991. * <div class="sub-desc">The action object contains these properties of interest:<ul>
  4992. * <li><tt>{@link Ext.form.Action#response response}</tt></li>
  4993. * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
  4994. * <li><tt>{@link Ext.form.Action#type type}</tt></li>
  4995. * </ul></div></li></ul></div></li>
  4996. *
  4997. * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
  4998. * failed transaction attempt. The function is passed the following parameters:<ul>
  4999. * <li><tt>form</tt> : The {@link Ext.form.BasicForm} that requested the action.</li>
  5000. * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
  5001. * <div class="sub-desc">The action object contains these properties of interest:<ul>
  5002. * <li><tt>{@link Ext.form.Action#failureType failureType}</tt></li>
  5003. * <li><tt>{@link Ext.form.Action#response response}</tt></li>
  5004. * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
  5005. * <li><tt>{@link Ext.form.Action#type type}</tt></li>
  5006. * </ul></div></li></ul></div></li>
  5007. *
  5008. * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
  5009. * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
  5010. *
  5011. * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
  5012. * Determines whether a Form's fields are validated in a final call to
  5013. * {@link Ext.form.BasicForm#isValid isValid} prior to submission. Set to <tt>false</tt>
  5014. * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
  5015. *
  5016. * @return {BasicForm} this
  5017. */
  5018. doAction : function(action, options){
  5019. if(Ext.isString(action)){
  5020. action = new Ext.form.Action.ACTION_TYPES[action](this, options);
  5021. }
  5022. if(this.fireEvent('beforeaction', this, action) !== false){
  5023. this.beforeAction(action);
  5024. action.run.defer(100, action);
  5025. }
  5026. return this;
  5027. },
  5028. /**
  5029. * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Submit submit action}.
  5030. * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
  5031. * <p><b>Note:</b> this is ignored when using the {@link #standardSubmit} option.</p>
  5032. * <p>The following code:</p><pre><code>
  5033. myFormPanel.getForm().submit({
  5034. clientValidation: true,
  5035. url: 'updateConsignment.php',
  5036. params: {
  5037. newStatus: 'delivered'
  5038. },
  5039. success: function(form, action) {
  5040. Ext.Msg.alert('Success', action.result.msg);
  5041. },
  5042. failure: function(form, action) {
  5043. switch (action.failureType) {
  5044. case Ext.form.Action.CLIENT_INVALID:
  5045. Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
  5046. break;
  5047. case Ext.form.Action.CONNECT_FAILURE:
  5048. Ext.Msg.alert('Failure', 'Ajax communication failed');
  5049. break;
  5050. case Ext.form.Action.SERVER_INVALID:
  5051. Ext.Msg.alert('Failure', action.result.msg);
  5052. }
  5053. }
  5054. });
  5055. </code></pre>
  5056. * would process the following server response for a successful submission:<pre><code>
  5057. {
  5058. "success":true, // note this is Boolean, not string
  5059. "msg":"Consignment updated"
  5060. }
  5061. </code></pre>
  5062. * and the following server response for a failed submission:<pre><code>
  5063. {
  5064. "success":false, // note this is Boolean, not string
  5065. "msg":"You do not have permission to perform this operation"
  5066. }
  5067. </code></pre>
  5068. * @return {BasicForm} this
  5069. */
  5070. submit : function(options){
  5071. options = options || {};
  5072. if(this.standardSubmit){
  5073. var v = options.clientValidation === false || this.isValid();
  5074. if(v){
  5075. var el = this.el.dom;
  5076. if(this.url && Ext.isEmpty(el.action)){
  5077. el.action = this.url;
  5078. }
  5079. el.submit();
  5080. }
  5081. return v;
  5082. }
  5083. var submitAction = String.format('{0}submit', this.api ? 'direct' : '');
  5084. this.doAction(submitAction, options);
  5085. return this;
  5086. },
  5087. /**
  5088. * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Load load action}.
  5089. * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
  5090. * @return {BasicForm} this
  5091. */
  5092. load : function(options){
  5093. var loadAction = String.format('{0}load', this.api ? 'direct' : '');
  5094. this.doAction(loadAction, options);
  5095. return this;
  5096. },
  5097. /**
  5098. * Persists the values in this form into the passed {@link Ext.data.Record} object in a beginEdit/endEdit block.
  5099. * @param {Record} record The record to edit
  5100. * @return {BasicForm} this
  5101. */
  5102. updateRecord : function(record){
  5103. record.beginEdit();
  5104. var fs = record.fields;
  5105. fs.each(function(f){
  5106. var field = this.findField(f.name);
  5107. if(field){
  5108. record.set(f.name, field.getValue());
  5109. }
  5110. }, this);
  5111. record.endEdit();
  5112. return this;
  5113. },
  5114. /**
  5115. * Loads an {@link Ext.data.Record} into this form by calling {@link #setValues} with the
  5116. * {@link Ext.data.Record#data record data}.
  5117. * See also {@link #trackResetOnLoad}.
  5118. * @param {Record} record The record to load
  5119. * @return {BasicForm} this
  5120. */
  5121. loadRecord : function(record){
  5122. this.setValues(record.data);
  5123. return this;
  5124. },
  5125. // private
  5126. beforeAction : function(action){
  5127. // Call HtmlEditor's syncValue before actions
  5128. this.items.each(function(f){
  5129. if(f.isFormField && f.syncValue){
  5130. f.syncValue();
  5131. }
  5132. });
  5133. var o = action.options;
  5134. if(o.waitMsg){
  5135. if(this.waitMsgTarget === true){
  5136. this.el.mask(o.waitMsg, 'x-mask-loading');
  5137. }else if(this.waitMsgTarget){
  5138. this.waitMsgTarget = Ext.get(this.waitMsgTarget);
  5139. this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
  5140. }else{
  5141. Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle);
  5142. }
  5143. }
  5144. },
  5145. // private
  5146. afterAction : function(action, success){
  5147. this.activeAction = null;
  5148. var o = action.options;
  5149. if(o.waitMsg){
  5150. if(this.waitMsgTarget === true){
  5151. this.el.unmask();
  5152. }else if(this.waitMsgTarget){
  5153. this.waitMsgTarget.unmask();
  5154. }else{
  5155. Ext.MessageBox.updateProgress(1);
  5156. Ext.MessageBox.hide();
  5157. }
  5158. }
  5159. if(success){
  5160. if(o.reset){
  5161. this.reset();
  5162. }
  5163. Ext.callback(o.success, o.scope, [this, action]);
  5164. this.fireEvent('actioncomplete', this, action);
  5165. }else{
  5166. Ext.callback(o.failure, o.scope, [this, action]);
  5167. this.fireEvent('actionfailed', this, action);
  5168. }
  5169. },
  5170. /**
  5171. * Find a {@link Ext.form.Field} in this form.
  5172. * @param {String} id The value to search for (specify either a {@link Ext.Component#id id},
  5173. * {@link Ext.grid.Column#dataIndex dataIndex}, {@link Ext.form.Field#getName name or hiddenName}).
  5174. * @return Field
  5175. */
  5176. findField : function(id) {
  5177. var field = this.items.get(id);
  5178. if (!Ext.isObject(field)) {
  5179. //searches for the field corresponding to the given id. Used recursively for composite fields
  5180. var findMatchingField = function(f) {
  5181. if (f.isFormField) {
  5182. if (f.dataIndex == id || f.id == id || f.getName() == id) {
  5183. field = f;
  5184. return false;
  5185. } else if (f.isComposite) {
  5186. return f.items.each(findMatchingField);
  5187. }
  5188. }
  5189. };
  5190. this.items.each(findMatchingField);
  5191. }
  5192. return field || null;
  5193. },
  5194. /**
  5195. * Mark fields in this form invalid in bulk.
  5196. * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
  5197. * @return {BasicForm} this
  5198. */
  5199. markInvalid : function(errors){
  5200. if (Ext.isArray(errors)) {
  5201. for(var i = 0, len = errors.length; i < len; i++){
  5202. var fieldError = errors[i];
  5203. var f = this.findField(fieldError.id);
  5204. if(f){
  5205. f.markInvalid(fieldError.msg);
  5206. }
  5207. }
  5208. } else {
  5209. var field, id;
  5210. for(id in errors){
  5211. if(!Ext.isFunction(errors[id]) && (field = this.findField(id))){
  5212. field.markInvalid(errors[id]);
  5213. }
  5214. }
  5215. }
  5216. return this;
  5217. },
  5218. /**
  5219. * Set values for fields in this form in bulk.
  5220. * @param {Array/Object} values Either an array in the form:<pre><code>
  5221. [{id:'clientName', value:'Fred. Olsen Lines'},
  5222. {id:'portOfLoading', value:'FXT'},
  5223. {id:'portOfDischarge', value:'OSL'} ]</code></pre>
  5224. * or an object hash of the form:<pre><code>
  5225. {
  5226. clientName: 'Fred. Olsen Lines',
  5227. portOfLoading: 'FXT',
  5228. portOfDischarge: 'OSL'
  5229. }</code></pre>
  5230. * @return {BasicForm} this
  5231. */
  5232. setValues : function(values){
  5233. if(Ext.isArray(values)){ // array of objects
  5234. for(var i = 0, len = values.length; i < len; i++){
  5235. var v = values[i];
  5236. var f = this.findField(v.id);
  5237. if(f){
  5238. f.setValue(v.value);
  5239. if(this.trackResetOnLoad){
  5240. f.originalValue = f.getValue();
  5241. }
  5242. }
  5243. }
  5244. }else{ // object hash
  5245. var field, id;
  5246. for(id in values){
  5247. if(!Ext.isFunction(values[id]) && (field = this.findField(id))){
  5248. field.setValue(values[id]);
  5249. if(this.trackResetOnLoad){
  5250. field.originalValue = field.getValue();
  5251. }
  5252. }
  5253. }
  5254. }
  5255. return this;
  5256. },
  5257. /**
  5258. * <p>Returns the fields in this form as an object with key/value pairs as they would be submitted using a standard form submit.
  5259. * If multiple fields exist with the same name they are returned as an array.</p>
  5260. * <p><b>Note:</b> The values are collected from all enabled HTML input elements within the form, <u>not</u> from
  5261. * the Ext Field objects. This means that all returned values are Strings (or Arrays of Strings) and that the
  5262. * value can potentially be the emptyText of a field.</p>
  5263. * @param {Boolean} asString (optional) Pass true to return the values as a string. (defaults to false, returning an Object)
  5264. * @return {String/Object}
  5265. */
  5266. getValues : function(asString){
  5267. var fs = Ext.lib.Ajax.serializeForm(this.el.dom);
  5268. if(asString === true){
  5269. return fs;
  5270. }
  5271. return Ext.urlDecode(fs);
  5272. },
  5273. /**
  5274. * Retrieves the fields in the form as a set of key/value pairs, using the {@link Ext.form.Field#getValue getValue()} method.
  5275. * If multiple fields exist with the same name they are returned as an array.
  5276. * @param {Boolean} dirtyOnly (optional) True to return only fields that are dirty.
  5277. * @return {Object} The values in the form
  5278. */
  5279. getFieldValues : function(dirtyOnly){
  5280. var o = {},
  5281. n,
  5282. key,
  5283. val;
  5284. this.items.each(function(f) {
  5285. if (dirtyOnly !== true || f.isDirty()) {
  5286. n = f.getName();
  5287. key = o[n];
  5288. val = f.getValue();
  5289. if(Ext.isDefined(key)){
  5290. if(Ext.isArray(key)){
  5291. o[n].push(val);
  5292. }else{
  5293. o[n] = [key, val];
  5294. }
  5295. }else{
  5296. o[n] = val;
  5297. }
  5298. }
  5299. });
  5300. return o;
  5301. },
  5302. /**
  5303. * Clears all invalid messages in this form.
  5304. * @return {BasicForm} this
  5305. */
  5306. clearInvalid : function(){
  5307. this.items.each(function(f){
  5308. f.clearInvalid();
  5309. });
  5310. return this;
  5311. },
  5312. /**
  5313. * Resets this form.
  5314. * @return {BasicForm} this
  5315. */
  5316. reset : function(){
  5317. this.items.each(function(f){
  5318. f.reset();
  5319. });
  5320. return this;
  5321. },
  5322. /**
  5323. * Add Ext.form Components to this form's Collection. This does not result in rendering of
  5324. * the passed Component, it just enables the form to validate Fields, and distribute values to
  5325. * Fields.
  5326. * <p><b>You will not usually call this function. In order to be rendered, a Field must be added
  5327. * to a {@link Ext.Container Container}, usually an {@link Ext.form.FormPanel FormPanel}.
  5328. * The FormPanel to which the field is added takes care of adding the Field to the BasicForm's
  5329. * collection.</b></p>
  5330. * @param {Field} field1
  5331. * @param {Field} field2 (optional)
  5332. * @param {Field} etc (optional)
  5333. * @return {BasicForm} this
  5334. */
  5335. add : function(){
  5336. this.items.addAll(Array.prototype.slice.call(arguments, 0));
  5337. return this;
  5338. },
  5339. /**
  5340. * Removes a field from the items collection (does NOT remove its markup).
  5341. * @param {Field} field
  5342. * @return {BasicForm} this
  5343. */
  5344. remove : function(field){
  5345. this.items.remove(field);
  5346. return this;
  5347. },
  5348. /**
  5349. * Iterates through the {@link Ext.form.Field Field}s which have been {@link #add add}ed to this BasicForm,
  5350. * checks them for an id attribute, and calls {@link Ext.form.Field#applyToMarkup} on the existing dom element with that id.
  5351. * @return {BasicForm} this
  5352. */
  5353. render : function(){
  5354. this.items.each(function(f){
  5355. if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
  5356. f.applyToMarkup(f.id);
  5357. }
  5358. });
  5359. return this;
  5360. },
  5361. /**
  5362. * Calls {@link Ext#apply} for all fields in this form with the passed object.
  5363. * @param {Object} values
  5364. * @return {BasicForm} this
  5365. */
  5366. applyToFields : function(o){
  5367. this.items.each(function(f){
  5368. Ext.apply(f, o);
  5369. });
  5370. return this;
  5371. },
  5372. /**
  5373. * Calls {@link Ext#applyIf} for all field in this form with the passed object.
  5374. * @param {Object} values
  5375. * @return {BasicForm} this
  5376. */
  5377. applyIfToFields : function(o){
  5378. this.items.each(function(f){
  5379. Ext.applyIf(f, o);
  5380. });
  5381. return this;
  5382. },
  5383. callFieldMethod : function(fnName, args){
  5384. args = args || [];
  5385. this.items.each(function(f){
  5386. if(Ext.isFunction(f[fnName])){
  5387. f[fnName].apply(f, args);
  5388. }
  5389. });
  5390. return this;
  5391. }
  5392. });
  5393. // back compat
  5394. Ext.BasicForm = Ext.form.BasicForm;
  5395. /**
  5396. * @class Ext.form.FormPanel
  5397. * @extends Ext.Panel
  5398. * <p>Standard form container.</p>
  5399. *
  5400. * <p><b><u>Layout</u></b></p>
  5401. * <p>By default, FormPanel is configured with <tt>layout:'form'</tt> to use an {@link Ext.layout.FormLayout}
  5402. * layout manager, which styles and renders fields and labels correctly. When nesting additional Containers
  5403. * within a FormPanel, you should ensure that any descendant Containers which host input Fields use the
  5404. * {@link Ext.layout.FormLayout} layout manager.</p>
  5405. *
  5406. * <p><b><u>BasicForm</u></b></p>
  5407. * <p>Although <b>not listed</b> as configuration options of FormPanel, the FormPanel class accepts all
  5408. * of the config options required to configure its internal {@link Ext.form.BasicForm} for:
  5409. * <div class="mdetail-params"><ul>
  5410. * <li>{@link Ext.form.BasicForm#fileUpload file uploads}</li>
  5411. * <li>functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form</li>
  5412. * </ul></div>
  5413. *
  5414. * <p><b>Note</b>: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
  5415. * the <tt><b>initialConfig</b></tt> property of the FormPanel. Applying {@link Ext.form.BasicForm BasicForm}
  5416. * configuration settings to <b><tt>this</tt></b> will <b>not</b> affect the BasicForm's configuration.</p>
  5417. *
  5418. * <p><b><u>Form Validation</u></b></p>
  5419. * <p>For information on form validation see the following:</p>
  5420. * <div class="mdetail-params"><ul>
  5421. * <li>{@link Ext.form.TextField}</li>
  5422. * <li>{@link Ext.form.VTypes}</li>
  5423. * <li>{@link Ext.form.BasicForm#doAction BasicForm.doAction <b>clientValidation</b> notes}</li>
  5424. * <li><tt>{@link Ext.form.FormPanel#monitorValid monitorValid}</tt></li>
  5425. * </ul></div>
  5426. *
  5427. * <p><b><u>Form Submission</u></b></p>
  5428. * <p>By default, Ext Forms are submitted through Ajax, using {@link Ext.form.Action}. To enable normal browser
  5429. * submission of the {@link Ext.form.BasicForm BasicForm} contained in this FormPanel, see the
  5430. * <tt><b>{@link Ext.form.BasicForm#standardSubmit standardSubmit}</b></tt> option.</p>
  5431. *
  5432. * @constructor
  5433. * @param {Object} config Configuration options
  5434. * @xtype form
  5435. */
  5436. Ext.FormPanel = Ext.extend(Ext.Panel, {
  5437. /**
  5438. * @cfg {String} formId (optional) The id of the FORM tag (defaults to an auto-generated id).
  5439. */
  5440. /**
  5441. * @cfg {Boolean} hideLabels
  5442. * <p><tt>true</tt> to hide field labels by default (sets <tt>display:none</tt>). Defaults to
  5443. * <tt>false</tt>.</p>
  5444. * <p>Also see {@link Ext.Component}.<tt>{@link Ext.Component#hideLabel hideLabel}</tt>.
  5445. */
  5446. /**
  5447. * @cfg {Number} labelPad
  5448. * The default padding in pixels for field labels (defaults to <tt>5</tt>). <tt>labelPad</tt> only
  5449. * applies if <tt>{@link #labelWidth}</tt> is also specified, otherwise it will be ignored.
  5450. */
  5451. /**
  5452. * @cfg {String} labelSeparator
  5453. * See {@link Ext.Component}.<tt>{@link Ext.Component#labelSeparator labelSeparator}</tt>
  5454. */
  5455. /**
  5456. * @cfg {Number} labelWidth The width of labels in pixels. This property cascades to child containers
  5457. * and can be overridden on any child container (e.g., a fieldset can specify a different <tt>labelWidth</tt>
  5458. * for its fields) (defaults to <tt>100</tt>).
  5459. */
  5460. /**
  5461. * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
  5462. */
  5463. /**
  5464. * @cfg {Array} buttons
  5465. * An array of {@link Ext.Button}s or {@link Ext.Button} configs used to add buttons to the footer of this FormPanel.<br>
  5466. * <p>Buttons in the footer of a FormPanel may be configured with the option <tt>formBind: true</tt>. This causes
  5467. * the form's {@link #monitorValid valid state monitor task} to enable/disable those Buttons depending on
  5468. * the form's valid/invalid state.</p>
  5469. */
  5470. /**
  5471. * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to <tt>75</tt>).
  5472. */
  5473. minButtonWidth : 75,
  5474. /**
  5475. * @cfg {String} labelAlign The label alignment value used for the <tt>text-align</tt> specification
  5476. * for the <b>container</b>. Valid values are <tt>"left</tt>", <tt>"top"</tt> or <tt>"right"</tt>
  5477. * (defaults to <tt>"left"</tt>). This property cascades to child <b>containers</b> and can be
  5478. * overridden on any child <b>container</b> (e.g., a fieldset can specify a different <tt>labelAlign</tt>
  5479. * for its fields).
  5480. */
  5481. labelAlign : 'left',
  5482. /**
  5483. * @cfg {Boolean} monitorValid If <tt>true</tt>, the form monitors its valid state <b>client-side</b> and
  5484. * regularly fires the {@link #clientvalidation} event passing that state.<br>
  5485. * <p>When monitoring valid state, the FormPanel enables/disables any of its configured
  5486. * {@link #buttons} which have been configured with <code>formBind: true</code> depending
  5487. * on whether the {@link Ext.form.BasicForm#isValid form is valid} or not. Defaults to <tt>false</tt></p>
  5488. */
  5489. monitorValid : false,
  5490. /**
  5491. * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
  5492. */
  5493. monitorPoll : 200,
  5494. /**
  5495. * @cfg {String} layout Defaults to <tt>'form'</tt>. Normally this configuration property should not be altered.
  5496. * For additional details see {@link Ext.layout.FormLayout} and {@link Ext.Container#layout Ext.Container.layout}.
  5497. */
  5498. layout : 'form',
  5499. // private
  5500. initComponent : function(){
  5501. this.form = this.createForm();
  5502. Ext.FormPanel.superclass.initComponent.call(this);
  5503. this.bodyCfg = {
  5504. tag: 'form',
  5505. cls: this.baseCls + '-body',
  5506. method : this.method || 'POST',
  5507. id : this.formId || Ext.id()
  5508. };
  5509. if(this.fileUpload) {
  5510. this.bodyCfg.enctype = 'multipart/form-data';
  5511. }
  5512. this.initItems();
  5513. this.addEvents(
  5514. /**
  5515. * @event clientvalidation
  5516. * If the monitorValid config option is true, this event fires repetitively to notify of valid state
  5517. * @param {Ext.form.FormPanel} this
  5518. * @param {Boolean} valid true if the form has passed client-side validation
  5519. */
  5520. 'clientvalidation'
  5521. );
  5522. this.relayEvents(this.form, ['beforeaction', 'actionfailed', 'actioncomplete']);
  5523. },
  5524. // private
  5525. createForm : function(){
  5526. var config = Ext.applyIf({listeners: {}}, this.initialConfig);
  5527. return new Ext.form.BasicForm(null, config);
  5528. },
  5529. // private
  5530. initFields : function(){
  5531. var f = this.form;
  5532. var formPanel = this;
  5533. var fn = function(c){
  5534. if(formPanel.isField(c)){
  5535. f.add(c);
  5536. }else if(c.findBy && c != formPanel){
  5537. formPanel.applySettings(c);
  5538. //each check required for check/radio groups.
  5539. if(c.items && c.items.each){
  5540. c.items.each(fn, this);
  5541. }
  5542. }
  5543. };
  5544. this.items.each(fn, this);
  5545. },
  5546. // private
  5547. applySettings: function(c){
  5548. var ct = c.ownerCt;
  5549. Ext.applyIf(c, {
  5550. labelAlign: ct.labelAlign,
  5551. labelWidth: ct.labelWidth,
  5552. itemCls: ct.itemCls
  5553. });
  5554. },
  5555. // private
  5556. getLayoutTarget : function(){
  5557. return this.form.el;
  5558. },
  5559. /**
  5560. * Provides access to the {@link Ext.form.BasicForm Form} which this Panel contains.
  5561. * @return {Ext.form.BasicForm} The {@link Ext.form.BasicForm Form} which this Panel contains.
  5562. */
  5563. getForm : function(){
  5564. return this.form;
  5565. },
  5566. // private
  5567. onRender : function(ct, position){
  5568. this.initFields();
  5569. Ext.FormPanel.superclass.onRender.call(this, ct, position);
  5570. this.form.initEl(this.body);
  5571. },
  5572. // private
  5573. beforeDestroy : function(){
  5574. this.stopMonitoring();
  5575. this.form.destroy(true);
  5576. Ext.FormPanel.superclass.beforeDestroy.call(this);
  5577. },
  5578. // Determine if a Component is usable as a form Field.
  5579. isField : function(c) {
  5580. return !!c.setValue && !!c.getValue && !!c.markInvalid && !!c.clearInvalid;
  5581. },
  5582. // private
  5583. initEvents : function(){
  5584. Ext.FormPanel.superclass.initEvents.call(this);
  5585. // Listeners are required here to catch bubbling events from children.
  5586. this.on({
  5587. scope: this,
  5588. add: this.onAddEvent,
  5589. remove: this.onRemoveEvent
  5590. });
  5591. if(this.monitorValid){ // initialize after render
  5592. this.startMonitoring();
  5593. }
  5594. },
  5595. // private
  5596. onAdd: function(c){
  5597. Ext.FormPanel.superclass.onAdd.call(this, c);
  5598. this.processAdd(c);
  5599. },
  5600. // private
  5601. onAddEvent: function(ct, c){
  5602. if(ct !== this){
  5603. this.processAdd(c);
  5604. }
  5605. },
  5606. // private
  5607. processAdd : function(c){
  5608. // If a single form Field, add it
  5609. if(this.isField(c)){
  5610. this.form.add(c);
  5611. // If a Container, add any Fields it might contain
  5612. }else if(c.findBy){
  5613. this.applySettings(c);
  5614. this.form.add.apply(this.form, c.findBy(this.isField));
  5615. }
  5616. },
  5617. // private
  5618. onRemove: function(c){
  5619. Ext.FormPanel.superclass.onRemove.call(this, c);
  5620. this.processRemove(c);
  5621. },
  5622. onRemoveEvent: function(ct, c){
  5623. if(ct !== this){
  5624. this.processRemove(c);
  5625. }
  5626. },
  5627. // private
  5628. processRemove: function(c){
  5629. if(!this.destroying){
  5630. // If a single form Field, remove it
  5631. if(this.isField(c)){
  5632. this.form.remove(c);
  5633. // If a Container, its already destroyed by the time it gets here. Remove any references to destroyed fields.
  5634. }else if (c.findBy){
  5635. Ext.each(c.findBy(this.isField), this.form.remove, this.form);
  5636. }
  5637. }
  5638. },
  5639. /**
  5640. * Starts monitoring of the valid state of this form. Usually this is done by passing the config
  5641. * option "monitorValid"
  5642. */
  5643. startMonitoring : function(){
  5644. if(!this.validTask){
  5645. this.validTask = new Ext.util.TaskRunner();
  5646. this.validTask.start({
  5647. run : this.bindHandler,
  5648. interval : this.monitorPoll || 200,
  5649. scope: this
  5650. });
  5651. }
  5652. },
  5653. /**
  5654. * Stops monitoring of the valid state of this form
  5655. */
  5656. stopMonitoring : function(){
  5657. if(this.validTask){
  5658. this.validTask.stopAll();
  5659. this.validTask = null;
  5660. }
  5661. },
  5662. /**
  5663. * This is a proxy for the underlying BasicForm's {@link Ext.form.BasicForm#load} call.
  5664. * @param {Object} options The options to pass to the action (see {@link Ext.form.BasicForm#doAction} for details)
  5665. */
  5666. load : function(){
  5667. this.form.load.apply(this.form, arguments);
  5668. },
  5669. // private
  5670. onDisable : function(){
  5671. Ext.FormPanel.superclass.onDisable.call(this);
  5672. if(this.form){
  5673. this.form.items.each(function(){
  5674. this.disable();
  5675. });
  5676. }
  5677. },
  5678. // private
  5679. onEnable : function(){
  5680. Ext.FormPanel.superclass.onEnable.call(this);
  5681. if(this.form){
  5682. this.form.items.each(function(){
  5683. this.enable();
  5684. });
  5685. }
  5686. },
  5687. // private
  5688. bindHandler : function(){
  5689. var valid = true;
  5690. this.form.items.each(function(f){
  5691. if(!f.isValid(true)){
  5692. valid = false;
  5693. return false;
  5694. }
  5695. });
  5696. if(this.fbar){
  5697. var fitems = this.fbar.items.items;
  5698. for(var i = 0, len = fitems.length; i < len; i++){
  5699. var btn = fitems[i];
  5700. if(btn.formBind === true && btn.disabled === valid){
  5701. btn.setDisabled(!valid);
  5702. }
  5703. }
  5704. }
  5705. this.fireEvent('clientvalidation', this, valid);
  5706. }
  5707. });
  5708. Ext.reg('form', Ext.FormPanel);
  5709. Ext.form.FormPanel = Ext.FormPanel;/**
  5710. * @class Ext.form.FieldSet
  5711. * @extends Ext.Panel
  5712. * Standard container used for grouping items within a {@link Ext.form.FormPanel form}.
  5713. * <pre><code>
  5714. var form = new Ext.FormPanel({
  5715. title: 'Simple Form with FieldSets',
  5716. labelWidth: 75, // label settings here cascade unless overridden
  5717. url: 'save-form.php',
  5718. frame:true,
  5719. bodyStyle:'padding:5px 5px 0',
  5720. width: 700,
  5721. renderTo: document.body,
  5722. layout:'column', // arrange items in columns
  5723. defaults: { // defaults applied to items
  5724. layout: 'form',
  5725. border: false,
  5726. bodyStyle: 'padding:4px'
  5727. },
  5728. items: [{
  5729. // Fieldset in Column 1
  5730. xtype:'fieldset',
  5731. columnWidth: 0.5,
  5732. title: 'Fieldset 1',
  5733. collapsible: true,
  5734. autoHeight:true,
  5735. defaults: {
  5736. anchor: '-20' // leave room for error icon
  5737. },
  5738. defaultType: 'textfield',
  5739. items :[{
  5740. fieldLabel: 'Field 1'
  5741. }, {
  5742. fieldLabel: 'Field 2'
  5743. }, {
  5744. fieldLabel: 'Field 3'
  5745. }
  5746. ]
  5747. },{
  5748. // Fieldset in Column 2 - Panel inside
  5749. xtype:'fieldset',
  5750. title: 'Show Panel', // title, header, or checkboxToggle creates fieldset header
  5751. autoHeight:true,
  5752. columnWidth: 0.5,
  5753. checkboxToggle: true,
  5754. collapsed: true, // fieldset initially collapsed
  5755. layout:'anchor',
  5756. items :[{
  5757. xtype: 'panel',
  5758. anchor: '100%',
  5759. title: 'Panel inside a fieldset',
  5760. frame: true,
  5761. height: 100
  5762. }]
  5763. }]
  5764. });
  5765. * </code></pre>
  5766. * @constructor
  5767. * @param {Object} config Configuration options
  5768. * @xtype fieldset
  5769. */
  5770. Ext.form.FieldSet = Ext.extend(Ext.Panel, {
  5771. /**
  5772. * @cfg {Mixed} checkboxToggle <tt>true</tt> to render a checkbox into the fieldset frame just
  5773. * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
  5774. * to <tt>false</tt>).
  5775. * <p>A {@link Ext.DomHelper DomHelper} element spec may also be specified to create the checkbox.
  5776. * If <tt>true</tt> is specified, the default DomHelper config object used to create the element
  5777. * is:</p><pre><code>
  5778. * {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}
  5779. * </code></pre>
  5780. */
  5781. /**
  5782. * @cfg {String} checkboxName The name to assign to the fieldset's checkbox if <tt>{@link #checkboxToggle} = true</tt>
  5783. * (defaults to <tt>'[checkbox id]-checkbox'</tt>).
  5784. */
  5785. /**
  5786. * @cfg {Boolean} collapsible
  5787. * <tt>true</tt> to make the fieldset collapsible and have the expand/collapse toggle button automatically
  5788. * rendered into the legend element, <tt>false</tt> to keep the fieldset statically sized with no collapse
  5789. * button (defaults to <tt>false</tt>). Another option is to configure <tt>{@link #checkboxToggle}</tt>.
  5790. */
  5791. /**
  5792. * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
  5793. */
  5794. /**
  5795. * @cfg {String} itemCls A css class to apply to the <tt>x-form-item</tt> of fields (see
  5796. * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} for details).
  5797. * This property cascades to child containers.
  5798. */
  5799. /**
  5800. * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to <tt>'x-fieldset'</tt>).
  5801. */
  5802. baseCls : 'x-fieldset',
  5803. /**
  5804. * @cfg {String} layout The {@link Ext.Container#layout} to use inside the fieldset (defaults to <tt>'form'</tt>).
  5805. */
  5806. layout : 'form',
  5807. /**
  5808. * @cfg {Boolean} animCollapse
  5809. * <tt>true</tt> to animate the transition when the panel is collapsed, <tt>false</tt> to skip the
  5810. * animation (defaults to <tt>false</tt>).
  5811. */
  5812. animCollapse : false,
  5813. // private
  5814. onRender : function(ct, position){
  5815. if(!this.el){
  5816. this.el = document.createElement('fieldset');
  5817. this.el.id = this.id;
  5818. if (this.title || this.header || this.checkboxToggle) {
  5819. this.el.appendChild(document.createElement('legend')).className = this.baseCls + '-header';
  5820. }
  5821. }
  5822. Ext.form.FieldSet.superclass.onRender.call(this, ct, position);
  5823. if(this.checkboxToggle){
  5824. var o = typeof this.checkboxToggle == 'object' ?
  5825. this.checkboxToggle :
  5826. {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'};
  5827. this.checkbox = this.header.insertFirst(o);
  5828. this.checkbox.dom.checked = !this.collapsed;
  5829. this.mon(this.checkbox, 'click', this.onCheckClick, this);
  5830. }
  5831. },
  5832. // private
  5833. onCollapse : function(doAnim, animArg){
  5834. if(this.checkbox){
  5835. this.checkbox.dom.checked = false;
  5836. }
  5837. Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg);
  5838. },
  5839. // private
  5840. onExpand : function(doAnim, animArg){
  5841. if(this.checkbox){
  5842. this.checkbox.dom.checked = true;
  5843. }
  5844. Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg);
  5845. },
  5846. /**
  5847. * This function is called by the fieldset's checkbox when it is toggled (only applies when
  5848. * checkboxToggle = true). This method should never be called externally, but can be
  5849. * overridden to provide custom behavior when the checkbox is toggled if needed.
  5850. */
  5851. onCheckClick : function(){
  5852. this[this.checkbox.dom.checked ? 'expand' : 'collapse']();
  5853. }
  5854. /**
  5855. * @cfg {String/Number} activeItem
  5856. * @hide
  5857. */
  5858. /**
  5859. * @cfg {Mixed} applyTo
  5860. * @hide
  5861. */
  5862. /**
  5863. * @cfg {Boolean} bodyBorder
  5864. * @hide
  5865. */
  5866. /**
  5867. * @cfg {Boolean} border
  5868. * @hide
  5869. */
  5870. /**
  5871. * @cfg {Boolean/Number} bufferResize
  5872. * @hide
  5873. */
  5874. /**
  5875. * @cfg {Boolean} collapseFirst
  5876. * @hide
  5877. */
  5878. /**
  5879. * @cfg {String} defaultType
  5880. * @hide
  5881. */
  5882. /**
  5883. * @cfg {String} disabledClass
  5884. * @hide
  5885. */
  5886. /**
  5887. * @cfg {String} elements
  5888. * @hide
  5889. */
  5890. /**
  5891. * @cfg {Boolean} floating
  5892. * @hide
  5893. */
  5894. /**
  5895. * @cfg {Boolean} footer
  5896. * @hide
  5897. */
  5898. /**
  5899. * @cfg {Boolean} frame
  5900. * @hide
  5901. */
  5902. /**
  5903. * @cfg {Boolean} header
  5904. * @hide
  5905. */
  5906. /**
  5907. * @cfg {Boolean} headerAsText
  5908. * @hide
  5909. */
  5910. /**
  5911. * @cfg {Boolean} hideCollapseTool
  5912. * @hide
  5913. */
  5914. /**
  5915. * @cfg {String} iconCls
  5916. * @hide
  5917. */
  5918. /**
  5919. * @cfg {Boolean/String} shadow
  5920. * @hide
  5921. */
  5922. /**
  5923. * @cfg {Number} shadowOffset
  5924. * @hide
  5925. */
  5926. /**
  5927. * @cfg {Boolean} shim
  5928. * @hide
  5929. */
  5930. /**
  5931. * @cfg {Object/Array} tbar
  5932. * @hide
  5933. */
  5934. /**
  5935. * @cfg {Array} tools
  5936. * @hide
  5937. */
  5938. /**
  5939. * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
  5940. * @hide
  5941. */
  5942. /**
  5943. * @cfg {String} xtype
  5944. * @hide
  5945. */
  5946. /**
  5947. * @property header
  5948. * @hide
  5949. */
  5950. /**
  5951. * @property footer
  5952. * @hide
  5953. */
  5954. /**
  5955. * @method focus
  5956. * @hide
  5957. */
  5958. /**
  5959. * @method getBottomToolbar
  5960. * @hide
  5961. */
  5962. /**
  5963. * @method getTopToolbar
  5964. * @hide
  5965. */
  5966. /**
  5967. * @method setIconClass
  5968. * @hide
  5969. */
  5970. /**
  5971. * @event activate
  5972. * @hide
  5973. */
  5974. /**
  5975. * @event beforeclose
  5976. * @hide
  5977. */
  5978. /**
  5979. * @event bodyresize
  5980. * @hide
  5981. */
  5982. /**
  5983. * @event close
  5984. * @hide
  5985. */
  5986. /**
  5987. * @event deactivate
  5988. * @hide
  5989. */
  5990. });
  5991. Ext.reg('fieldset', Ext.form.FieldSet);/**
  5992. * @class Ext.form.HtmlEditor
  5993. * @extends Ext.form.Field
  5994. * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
  5995. * automatically hidden when needed. These are noted in the config options where appropriate.
  5996. * <br><br>The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
  5997. * enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}.
  5998. * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
  5999. * supported by this editor.</b>
  6000. * <br><br>An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
  6001. * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.
  6002. * <br><br>Example usage:
  6003. * <pre><code>
  6004. // Simple example rendered with default options:
  6005. Ext.QuickTips.init(); // enable tooltips
  6006. new Ext.form.HtmlEditor({
  6007. renderTo: Ext.getBody(),
  6008. width: 800,
  6009. height: 300
  6010. });
  6011. // Passed via xtype into a container and with custom options:
  6012. Ext.QuickTips.init(); // enable tooltips
  6013. new Ext.Panel({
  6014. title: 'HTML Editor',
  6015. renderTo: Ext.getBody(),
  6016. width: 600,
  6017. height: 300,
  6018. frame: true,
  6019. layout: 'fit',
  6020. items: {
  6021. xtype: 'htmleditor',
  6022. enableColors: false,
  6023. enableAlignments: false
  6024. }
  6025. });
  6026. </code></pre>
  6027. * @constructor
  6028. * Create a new HtmlEditor
  6029. * @param {Object} config
  6030. * @xtype htmleditor
  6031. */
  6032. Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
  6033. /**
  6034. * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
  6035. */
  6036. enableFormat : true,
  6037. /**
  6038. * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
  6039. */
  6040. enableFontSize : true,
  6041. /**
  6042. * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
  6043. */
  6044. enableColors : true,
  6045. /**
  6046. * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
  6047. */
  6048. enableAlignments : true,
  6049. /**
  6050. * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
  6051. */
  6052. enableLists : true,
  6053. /**
  6054. * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
  6055. */
  6056. enableSourceEdit : true,
  6057. /**
  6058. * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
  6059. */
  6060. enableLinks : true,
  6061. /**
  6062. * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
  6063. */
  6064. enableFont : true,
  6065. /**
  6066. * @cfg {String} createLinkText The default text for the create link prompt
  6067. */
  6068. createLinkText : 'Please enter the URL for the link:',
  6069. /**
  6070. * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
  6071. */
  6072. defaultLinkValue : 'http:/'+'/',
  6073. /**
  6074. * @cfg {Array} fontFamilies An array of available font families
  6075. */
  6076. fontFamilies : [
  6077. 'Arial',
  6078. 'Courier New',
  6079. 'Tahoma',
  6080. 'Times New Roman',
  6081. 'Verdana'
  6082. ],
  6083. defaultFont: 'tahoma',
  6084. /**
  6085. * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to &#160; (Non-breaking space) in Opera and IE6, &#8203; (Zero-width space) in all other browsers).
  6086. */
  6087. defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
  6088. // private properties
  6089. actionMode: 'wrap',
  6090. validationEvent : false,
  6091. deferHeight: true,
  6092. initialized : false,
  6093. activated : false,
  6094. sourceEditMode : false,
  6095. onFocus : Ext.emptyFn,
  6096. iframePad:3,
  6097. hideMode:'offsets',
  6098. defaultAutoCreate : {
  6099. tag: "textarea",
  6100. style:"width:500px;height:300px;",
  6101. autocomplete: "off"
  6102. },
  6103. // private
  6104. initComponent : function(){
  6105. this.addEvents(
  6106. /**
  6107. * @event initialize
  6108. * Fires when the editor is fully initialized (including the iframe)
  6109. * @param {HtmlEditor} this
  6110. */
  6111. 'initialize',
  6112. /**
  6113. * @event activate
  6114. * Fires when the editor is first receives the focus. Any insertion must wait
  6115. * until after this event.
  6116. * @param {HtmlEditor} this
  6117. */
  6118. 'activate',
  6119. /**
  6120. * @event beforesync
  6121. * Fires before the textarea is updated with content from the editor iframe. Return false
  6122. * to cancel the sync.
  6123. * @param {HtmlEditor} this
  6124. * @param {String} html
  6125. */
  6126. 'beforesync',
  6127. /**
  6128. * @event beforepush
  6129. * Fires before the iframe editor is updated with content from the textarea. Return false
  6130. * to cancel the push.
  6131. * @param {HtmlEditor} this
  6132. * @param {String} html
  6133. */
  6134. 'beforepush',
  6135. /**
  6136. * @event sync
  6137. * Fires when the textarea is updated with content from the editor iframe.
  6138. * @param {HtmlEditor} this
  6139. * @param {String} html
  6140. */
  6141. 'sync',
  6142. /**
  6143. * @event push
  6144. * Fires when the iframe editor is updated with content from the textarea.
  6145. * @param {HtmlEditor} this
  6146. * @param {String} html
  6147. */
  6148. 'push',
  6149. /**
  6150. * @event editmodechange
  6151. * Fires when the editor switches edit modes
  6152. * @param {HtmlEditor} this
  6153. * @param {Boolean} sourceEdit True if source edit, false if standard editing.
  6154. */
  6155. 'editmodechange'
  6156. );
  6157. },
  6158. // private
  6159. createFontOptions : function(){
  6160. var buf = [], fs = this.fontFamilies, ff, lc;
  6161. for(var i = 0, len = fs.length; i< len; i++){
  6162. ff = fs[i];
  6163. lc = ff.toLowerCase();
  6164. buf.push(
  6165. '<option value="',lc,'" style="font-family:',ff,';"',
  6166. (this.defaultFont == lc ? ' selected="true">' : '>'),
  6167. ff,
  6168. '</option>'
  6169. );
  6170. }
  6171. return buf.join('');
  6172. },
  6173. /*
  6174. * Protected method that will not generally be called directly. It
  6175. * is called when the editor creates its toolbar. Override this method if you need to
  6176. * add custom toolbar buttons.
  6177. * @param {HtmlEditor} editor
  6178. */
  6179. createToolbar : function(editor){
  6180. var items = [];
  6181. var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
  6182. function btn(id, toggle, handler){
  6183. return {
  6184. itemId : id,
  6185. cls : 'x-btn-icon',
  6186. iconCls: 'x-edit-'+id,
  6187. enableToggle:toggle !== false,
  6188. scope: editor,
  6189. handler:handler||editor.relayBtnCmd,
  6190. clickEvent:'mousedown',
  6191. tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
  6192. overflowText: editor.buttonTips[id].title || undefined,
  6193. tabIndex:-1
  6194. };
  6195. }
  6196. if(this.enableFont && !Ext.isSafari2){
  6197. var fontSelectItem = new Ext.Toolbar.Item({
  6198. autoEl: {
  6199. tag:'select',
  6200. cls:'x-font-select',
  6201. html: this.createFontOptions()
  6202. }
  6203. });
  6204. items.push(
  6205. fontSelectItem,
  6206. '-'
  6207. );
  6208. }
  6209. if(this.enableFormat){
  6210. items.push(
  6211. btn('bold'),
  6212. btn('italic'),
  6213. btn('underline')
  6214. );
  6215. }
  6216. if(this.enableFontSize){
  6217. items.push(
  6218. '-',
  6219. btn('increasefontsize', false, this.adjustFont),
  6220. btn('decreasefontsize', false, this.adjustFont)
  6221. );
  6222. }
  6223. if(this.enableColors){
  6224. items.push(
  6225. '-', {
  6226. itemId:'forecolor',
  6227. cls:'x-btn-icon',
  6228. iconCls: 'x-edit-forecolor',
  6229. clickEvent:'mousedown',
  6230. tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
  6231. tabIndex:-1,
  6232. menu : new Ext.menu.ColorMenu({
  6233. allowReselect: true,
  6234. focus: Ext.emptyFn,
  6235. value:'000000',
  6236. plain:true,
  6237. listeners: {
  6238. scope: this,
  6239. select: function(cp, color){
  6240. this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
  6241. this.deferFocus();
  6242. }
  6243. },
  6244. clickEvent:'mousedown'
  6245. })
  6246. }, {
  6247. itemId:'backcolor',
  6248. cls:'x-btn-icon',
  6249. iconCls: 'x-edit-backcolor',
  6250. clickEvent:'mousedown',
  6251. tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
  6252. tabIndex:-1,
  6253. menu : new Ext.menu.ColorMenu({
  6254. focus: Ext.emptyFn,
  6255. value:'FFFFFF',
  6256. plain:true,
  6257. allowReselect: true,
  6258. listeners: {
  6259. scope: this,
  6260. select: function(cp, color){
  6261. if(Ext.isGecko){
  6262. this.execCmd('useCSS', false);
  6263. this.execCmd('hilitecolor', color);
  6264. this.execCmd('useCSS', true);
  6265. this.deferFocus();
  6266. }else{
  6267. this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
  6268. this.deferFocus();
  6269. }
  6270. }
  6271. },
  6272. clickEvent:'mousedown'
  6273. })
  6274. }
  6275. );
  6276. }
  6277. if(this.enableAlignments){
  6278. items.push(
  6279. '-',
  6280. btn('justifyleft'),
  6281. btn('justifycenter'),
  6282. btn('justifyright')
  6283. );
  6284. }
  6285. if(!Ext.isSafari2){
  6286. if(this.enableLinks){
  6287. items.push(
  6288. '-',
  6289. btn('createlink', false, this.createLink)
  6290. );
  6291. }
  6292. if(this.enableLists){
  6293. items.push(
  6294. '-',
  6295. btn('insertorderedlist'),
  6296. btn('insertunorderedlist')
  6297. );
  6298. }
  6299. if(this.enableSourceEdit){
  6300. items.push(
  6301. '-',
  6302. btn('sourceedit', true, function(btn){
  6303. this.toggleSourceEdit(!this.sourceEditMode);
  6304. })
  6305. );
  6306. }
  6307. }
  6308. // build the toolbar
  6309. var tb = new Ext.Toolbar({
  6310. renderTo: this.wrap.dom.firstChild,
  6311. items: items
  6312. });
  6313. if (fontSelectItem) {
  6314. this.fontSelect = fontSelectItem.el;
  6315. this.mon(this.fontSelect, 'change', function(){
  6316. var font = this.fontSelect.dom.value;
  6317. this.relayCmd('fontname', font);
  6318. this.deferFocus();
  6319. }, this);
  6320. }
  6321. // stop form submits
  6322. this.mon(tb.el, 'click', function(e){
  6323. e.preventDefault();
  6324. });
  6325. this.tb = tb;
  6326. this.tb.doLayout();
  6327. },
  6328. onDisable: function(){
  6329. this.wrap.mask();
  6330. Ext.form.HtmlEditor.superclass.onDisable.call(this);
  6331. },
  6332. onEnable: function(){
  6333. this.wrap.unmask();
  6334. Ext.form.HtmlEditor.superclass.onEnable.call(this);
  6335. },
  6336. setReadOnly: function(readOnly){
  6337. Ext.form.HtmlEditor.superclass.setReadOnly.call(this, readOnly);
  6338. if(this.initialized){
  6339. this.setDesignMode(!readOnly);
  6340. var bd = this.getEditorBody();
  6341. if(bd){
  6342. bd.style.cursor = this.readOnly ? 'default' : 'text';
  6343. }
  6344. this.disableItems(readOnly);
  6345. }
  6346. },
  6347. /**
  6348. * Protected method that will not generally be called directly. It
  6349. * is called when the editor initializes the iframe with HTML contents. Override this method if you
  6350. * want to change the initialization markup of the iframe (e.g. to add stylesheets).
  6351. *
  6352. * Note: IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility
  6353. */
  6354. getDocMarkup : function(){
  6355. var h = Ext.fly(this.iframe).getHeight() - this.iframePad * 2;
  6356. return String.format('<html><head><style type="text/css">body{border: 0; margin: 0; padding: {0}px; height: {1}px; cursor: text}</style></head><body></body></html>', this.iframePad, h);
  6357. },
  6358. // private
  6359. getEditorBody : function(){
  6360. var doc = this.getDoc();
  6361. return doc.body || doc.documentElement;
  6362. },
  6363. // private
  6364. getDoc : function(){
  6365. return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
  6366. },
  6367. // private
  6368. getWin : function(){
  6369. return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
  6370. },
  6371. // private
  6372. onRender : function(ct, position){
  6373. Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
  6374. this.el.dom.style.border = '0 none';
  6375. this.el.dom.setAttribute('tabIndex', -1);
  6376. this.el.addClass('x-hidden');
  6377. if(Ext.isIE){ // fix IE 1px bogus margin
  6378. this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;');
  6379. }
  6380. this.wrap = this.el.wrap({
  6381. cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
  6382. });
  6383. this.createToolbar(this);
  6384. this.disableItems(true);
  6385. this.tb.doLayout();
  6386. this.createIFrame();
  6387. if(!this.width){
  6388. var sz = this.el.getSize();
  6389. this.setSize(sz.width, this.height || sz.height);
  6390. }
  6391. this.resizeEl = this.positionEl = this.wrap;
  6392. },
  6393. createIFrame: function(){
  6394. var iframe = document.createElement('iframe');
  6395. iframe.name = Ext.id();
  6396. iframe.frameBorder = '0';
  6397. iframe.style.overflow = 'auto';
  6398. this.wrap.dom.appendChild(iframe);
  6399. this.iframe = iframe;
  6400. this.monitorTask = Ext.TaskMgr.start({
  6401. run: this.checkDesignMode,
  6402. scope: this,
  6403. interval:100
  6404. });
  6405. },
  6406. initFrame : function(){
  6407. Ext.TaskMgr.stop(this.monitorTask);
  6408. var doc = this.getDoc();
  6409. this.win = this.getWin();
  6410. doc.open();
  6411. doc.write(this.getDocMarkup());
  6412. doc.close();
  6413. var task = { // must defer to wait for browser to be ready
  6414. run : function(){
  6415. var doc = this.getDoc();
  6416. if(doc.body || doc.readyState == 'complete'){
  6417. Ext.TaskMgr.stop(task);
  6418. this.setDesignMode(true);
  6419. this.initEditor.defer(10, this);
  6420. }
  6421. },
  6422. interval : 10,
  6423. duration:10000,
  6424. scope: this
  6425. };
  6426. Ext.TaskMgr.start(task);
  6427. },
  6428. checkDesignMode : function(){
  6429. if(this.wrap && this.wrap.dom.offsetWidth){
  6430. var doc = this.getDoc();
  6431. if(!doc){
  6432. return;
  6433. }
  6434. if(!doc.editorInitialized || this.getDesignMode() != 'on'){
  6435. this.initFrame();
  6436. }
  6437. }
  6438. },
  6439. /* private
  6440. * set current design mode. To enable, mode can be true or 'on', off otherwise
  6441. */
  6442. setDesignMode : function(mode){
  6443. var doc ;
  6444. if(doc = this.getDoc()){
  6445. if(this.readOnly){
  6446. mode = false;
  6447. }
  6448. doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
  6449. }
  6450. },
  6451. // private
  6452. getDesignMode : function(){
  6453. var doc = this.getDoc();
  6454. if(!doc){ return ''; }
  6455. return String(doc.designMode).toLowerCase();
  6456. },
  6457. disableItems: function(disabled){
  6458. if(this.fontSelect){
  6459. this.fontSelect.dom.disabled = disabled;
  6460. }
  6461. this.tb.items.each(function(item){
  6462. if(item.getItemId() != 'sourceedit'){
  6463. item.setDisabled(disabled);
  6464. }
  6465. });
  6466. },
  6467. // private
  6468. onResize : function(w, h){
  6469. Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
  6470. if(this.el && this.iframe){
  6471. if(Ext.isNumber(w)){
  6472. var aw = w - this.wrap.getFrameWidth('lr');
  6473. this.el.setWidth(aw);
  6474. this.tb.setWidth(aw);
  6475. this.iframe.style.width = Math.max(aw, 0) + 'px';
  6476. }
  6477. if(Ext.isNumber(h)){
  6478. var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
  6479. this.el.setHeight(ah);
  6480. this.iframe.style.height = Math.max(ah, 0) + 'px';
  6481. var bd = this.getEditorBody();
  6482. if(bd){
  6483. bd.style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
  6484. }
  6485. }
  6486. }
  6487. },
  6488. /**
  6489. * Toggles the editor between standard and source edit mode.
  6490. * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
  6491. */
  6492. toggleSourceEdit : function(sourceEditMode){
  6493. var iframeHeight, elHeight;
  6494. if(sourceEditMode === undefined){
  6495. sourceEditMode = !this.sourceEditMode;
  6496. }
  6497. this.sourceEditMode = sourceEditMode === true;
  6498. var btn = this.tb.getComponent('sourceedit');
  6499. if(btn.pressed !== this.sourceEditMode){
  6500. btn.toggle(this.sourceEditMode);
  6501. if(!btn.xtbHidden){
  6502. return;
  6503. }
  6504. }
  6505. if(this.sourceEditMode){
  6506. // grab the height of the containing panel before we hide the iframe
  6507. ls = this.getSize();
  6508. iframeHeight = Ext.get(this.iframe).getHeight();
  6509. this.disableItems(true);
  6510. this.syncValue();
  6511. this.iframe.className = 'x-hidden';
  6512. this.el.removeClass('x-hidden');
  6513. this.el.dom.removeAttribute('tabIndex');
  6514. this.el.focus();
  6515. this.el.dom.style.height = iframeHeight + 'px';
  6516. }else{
  6517. elHeight = parseInt(this.el.dom.style.height, 10);
  6518. if(this.initialized){
  6519. this.disableItems(this.readOnly);
  6520. }
  6521. this.pushValue();
  6522. this.iframe.className = '';
  6523. this.el.addClass('x-hidden');
  6524. this.el.dom.setAttribute('tabIndex', -1);
  6525. this.deferFocus();
  6526. this.setSize(ls);
  6527. this.iframe.style.height = elHeight + 'px';
  6528. }
  6529. this.fireEvent('editmodechange', this, this.sourceEditMode);
  6530. },
  6531. // private used internally
  6532. createLink : function(){
  6533. var url = prompt(this.createLinkText, this.defaultLinkValue);
  6534. if(url && url != 'http:/'+'/'){
  6535. this.relayCmd('createlink', url);
  6536. }
  6537. },
  6538. // private
  6539. initEvents : function(){
  6540. this.originalValue = this.getValue();
  6541. },
  6542. /**
  6543. * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
  6544. * @method
  6545. */
  6546. markInvalid : Ext.emptyFn,
  6547. /**
  6548. * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
  6549. * @method
  6550. */
  6551. clearInvalid : Ext.emptyFn,
  6552. // docs inherit from Field
  6553. setValue : function(v){
  6554. Ext.form.HtmlEditor.superclass.setValue.call(this, v);
  6555. this.pushValue();
  6556. return this;
  6557. },
  6558. /**
  6559. * Protected method that will not generally be called directly. If you need/want
  6560. * custom HTML cleanup, this is the method you should override.
  6561. * @param {String} html The HTML to be cleaned
  6562. * @return {String} The cleaned HTML
  6563. */
  6564. cleanHtml: function(html) {
  6565. html = String(html);
  6566. if(Ext.isWebKit){ // strip safari nonsense
  6567. html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
  6568. }
  6569. /*
  6570. * Neat little hack. Strips out all the non-digit characters from the default
  6571. * value and compares it to the character code of the first character in the string
  6572. * because it can cause encoding issues when posted to the server.
  6573. */
  6574. if(html.charCodeAt(0) == this.defaultValue.replace(/\D/g, '')){
  6575. html = html.substring(1);
  6576. }
  6577. return html;
  6578. },
  6579. /**
  6580. * Protected method that will not generally be called directly. Syncs the contents
  6581. * of the editor iframe with the textarea.
  6582. */
  6583. syncValue : function(){
  6584. if(this.initialized){
  6585. var bd = this.getEditorBody();
  6586. var html = bd.innerHTML;
  6587. if(Ext.isWebKit){
  6588. var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
  6589. var m = bs.match(/text-align:(.*?);/i);
  6590. if(m && m[1]){
  6591. html = '<div style="'+m[0]+'">' + html + '</div>';
  6592. }
  6593. }
  6594. html = this.cleanHtml(html);
  6595. if(this.fireEvent('beforesync', this, html) !== false){
  6596. this.el.dom.value = html;
  6597. this.fireEvent('sync', this, html);
  6598. }
  6599. }
  6600. },
  6601. //docs inherit from Field
  6602. getValue : function() {
  6603. this[this.sourceEditMode ? 'pushValue' : 'syncValue']();
  6604. return Ext.form.HtmlEditor.superclass.getValue.call(this);
  6605. },
  6606. /**
  6607. * Protected method that will not generally be called directly. Pushes the value of the textarea
  6608. * into the iframe editor.
  6609. */
  6610. pushValue : function(){
  6611. if(this.initialized){
  6612. var v = this.el.dom.value;
  6613. if(!this.activated && v.length < 1){
  6614. v = this.defaultValue;
  6615. }
  6616. if(this.fireEvent('beforepush', this, v) !== false){
  6617. this.getEditorBody().innerHTML = v;
  6618. if(Ext.isGecko){
  6619. // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
  6620. this.setDesignMode(false); //toggle off first
  6621. }
  6622. this.setDesignMode(true);
  6623. this.fireEvent('push', this, v);
  6624. }
  6625. }
  6626. },
  6627. // private
  6628. deferFocus : function(){
  6629. this.focus.defer(10, this);
  6630. },
  6631. // docs inherit from Field
  6632. focus : function(){
  6633. if(this.win && !this.sourceEditMode){
  6634. this.win.focus();
  6635. }else{
  6636. this.el.focus();
  6637. }
  6638. },
  6639. // private
  6640. initEditor : function(){
  6641. //Destroying the component during/before initEditor can cause issues.
  6642. try{
  6643. var dbody = this.getEditorBody(),
  6644. ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
  6645. doc,
  6646. fn;
  6647. ss['background-attachment'] = 'fixed'; // w3c
  6648. dbody.bgProperties = 'fixed'; // ie
  6649. Ext.DomHelper.applyStyles(dbody, ss);
  6650. doc = this.getDoc();
  6651. if(doc){
  6652. try{
  6653. Ext.EventManager.removeAll(doc);
  6654. }catch(e){}
  6655. }
  6656. /*
  6657. * We need to use createDelegate here, because when using buffer, the delayed task is added
  6658. * as a property to the function. When the listener is removed, the task is deleted from the function.
  6659. * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
  6660. * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
  6661. */
  6662. fn = this.onEditorEvent.createDelegate(this);
  6663. Ext.EventManager.on(doc, {
  6664. mousedown: fn,
  6665. dblclick: fn,
  6666. click: fn,
  6667. keyup: fn,
  6668. buffer:100
  6669. });
  6670. if(Ext.isGecko){
  6671. Ext.EventManager.on(doc, 'keypress', this.applyCommand, this);
  6672. }
  6673. if(Ext.isIE || Ext.isWebKit || Ext.isOpera){
  6674. Ext.EventManager.on(doc, 'keydown', this.fixKeys, this);
  6675. }
  6676. doc.editorInitialized = true;
  6677. this.initialized = true;
  6678. this.pushValue();
  6679. this.setReadOnly(this.readOnly);
  6680. this.fireEvent('initialize', this);
  6681. }catch(e){}
  6682. },
  6683. // private
  6684. onDestroy : function(){
  6685. if(this.monitorTask){
  6686. Ext.TaskMgr.stop(this.monitorTask);
  6687. }
  6688. if(this.rendered){
  6689. Ext.destroy(this.tb);
  6690. var doc = this.getDoc();
  6691. if(doc){
  6692. try{
  6693. Ext.EventManager.removeAll(doc);
  6694. for (var prop in doc){
  6695. delete doc[prop];
  6696. }
  6697. }catch(e){}
  6698. }
  6699. if(this.wrap){
  6700. this.wrap.dom.innerHTML = '';
  6701. this.wrap.remove();
  6702. }
  6703. }
  6704. if(this.el){
  6705. this.el.removeAllListeners();
  6706. this.el.remove();
  6707. }
  6708. this.purgeListeners();
  6709. },
  6710. // private
  6711. onFirstFocus : function(){
  6712. this.activated = true;
  6713. this.disableItems(this.readOnly);
  6714. if(Ext.isGecko){ // prevent silly gecko errors
  6715. this.win.focus();
  6716. var s = this.win.getSelection();
  6717. if(!s.focusNode || s.focusNode.nodeType != 3){
  6718. var r = s.getRangeAt(0);
  6719. r.selectNodeContents(this.getEditorBody());
  6720. r.collapse(true);
  6721. this.deferFocus();
  6722. }
  6723. try{
  6724. this.execCmd('useCSS', true);
  6725. this.execCmd('styleWithCSS', false);
  6726. }catch(e){}
  6727. }
  6728. this.fireEvent('activate', this);
  6729. },
  6730. // private
  6731. adjustFont: function(btn){
  6732. var adjust = btn.getItemId() == 'increasefontsize' ? 1 : -1,
  6733. doc = this.getDoc(),
  6734. v = parseInt(doc.queryCommandValue('FontSize') || 2, 10);
  6735. if((Ext.isSafari && !Ext.isSafari2) || Ext.isChrome || Ext.isAir){
  6736. // Safari 3 values
  6737. // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
  6738. if(v <= 10){
  6739. v = 1 + adjust;
  6740. }else if(v <= 13){
  6741. v = 2 + adjust;
  6742. }else if(v <= 16){
  6743. v = 3 + adjust;
  6744. }else if(v <= 18){
  6745. v = 4 + adjust;
  6746. }else if(v <= 24){
  6747. v = 5 + adjust;
  6748. }else {
  6749. v = 6 + adjust;
  6750. }
  6751. v = v.constrain(1, 6);
  6752. }else{
  6753. if(Ext.isSafari){ // safari
  6754. adjust *= 2;
  6755. }
  6756. v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0);
  6757. }
  6758. this.execCmd('FontSize', v);
  6759. },
  6760. // private
  6761. onEditorEvent : function(e){
  6762. this.updateToolbar();
  6763. },
  6764. /**
  6765. * Protected method that will not generally be called directly. It triggers
  6766. * a toolbar update by reading the markup state of the current selection in the editor.
  6767. */
  6768. updateToolbar: function(){
  6769. if(this.readOnly){
  6770. return;
  6771. }
  6772. if(!this.activated){
  6773. this.onFirstFocus();
  6774. return;
  6775. }
  6776. var btns = this.tb.items.map,
  6777. doc = this.getDoc();
  6778. if(this.enableFont && !Ext.isSafari2){
  6779. var name = (doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase();
  6780. if(name != this.fontSelect.dom.value){
  6781. this.fontSelect.dom.value = name;
  6782. }
  6783. }
  6784. if(this.enableFormat){
  6785. btns.bold.toggle(doc.queryCommandState('bold'));
  6786. btns.italic.toggle(doc.queryCommandState('italic'));
  6787. btns.underline.toggle(doc.queryCommandState('underline'));
  6788. }
  6789. if(this.enableAlignments){
  6790. btns.justifyleft.toggle(doc.queryCommandState('justifyleft'));
  6791. btns.justifycenter.toggle(doc.queryCommandState('justifycenter'));
  6792. btns.justifyright.toggle(doc.queryCommandState('justifyright'));
  6793. }
  6794. if(!Ext.isSafari2 && this.enableLists){
  6795. btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist'));
  6796. btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist'));
  6797. }
  6798. Ext.menu.MenuMgr.hideAll();
  6799. this.syncValue();
  6800. },
  6801. // private
  6802. relayBtnCmd : function(btn){
  6803. this.relayCmd(btn.getItemId());
  6804. },
  6805. /**
  6806. * Executes a Midas editor command on the editor document and performs necessary focus and
  6807. * toolbar updates. <b>This should only be called after the editor is initialized.</b>
  6808. * @param {String} cmd The Midas command
  6809. * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
  6810. */
  6811. relayCmd : function(cmd, value){
  6812. (function(){
  6813. this.focus();
  6814. this.execCmd(cmd, value);
  6815. this.updateToolbar();
  6816. }).defer(10, this);
  6817. },
  6818. /**
  6819. * Executes a Midas editor command directly on the editor document.
  6820. * For visual commands, you should use {@link #relayCmd} instead.
  6821. * <b>This should only be called after the editor is initialized.</b>
  6822. * @param {String} cmd The Midas command
  6823. * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
  6824. */
  6825. execCmd : function(cmd, value){
  6826. var doc = this.getDoc();
  6827. doc.execCommand(cmd, false, value === undefined ? null : value);
  6828. this.syncValue();
  6829. },
  6830. // private
  6831. applyCommand : function(e){
  6832. if(e.ctrlKey){
  6833. var c = e.getCharCode(), cmd;
  6834. if(c > 0){
  6835. c = String.fromCharCode(c);
  6836. switch(c){
  6837. case 'b':
  6838. cmd = 'bold';
  6839. break;
  6840. case 'i':
  6841. cmd = 'italic';
  6842. break;
  6843. case 'u':
  6844. cmd = 'underline';
  6845. break;
  6846. }
  6847. if(cmd){
  6848. this.win.focus();
  6849. this.execCmd(cmd);
  6850. this.deferFocus();
  6851. e.preventDefault();
  6852. }
  6853. }
  6854. }
  6855. },
  6856. /**
  6857. * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
  6858. * to insert text.
  6859. * @param {String} text
  6860. */
  6861. insertAtCursor : function(text){
  6862. if(!this.activated){
  6863. return;
  6864. }
  6865. if(Ext.isIE){
  6866. this.win.focus();
  6867. var doc = this.getDoc(),
  6868. r = doc.selection.createRange();
  6869. if(r){
  6870. r.pasteHTML(text);
  6871. this.syncValue();
  6872. this.deferFocus();
  6873. }
  6874. }else{
  6875. this.win.focus();
  6876. this.execCmd('InsertHTML', text);
  6877. this.deferFocus();
  6878. }
  6879. },
  6880. // private
  6881. fixKeys : function(){ // load time branching for fastest keydown performance
  6882. if(Ext.isIE){
  6883. return function(e){
  6884. var k = e.getKey(),
  6885. doc = this.getDoc(),
  6886. r;
  6887. if(k == e.TAB){
  6888. e.stopEvent();
  6889. r = doc.selection.createRange();
  6890. if(r){
  6891. r.collapse(true);
  6892. r.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
  6893. this.deferFocus();
  6894. }
  6895. }else if(k == e.ENTER){
  6896. r = doc.selection.createRange();
  6897. if(r){
  6898. var target = r.parentElement();
  6899. if(!target || target.tagName.toLowerCase() != 'li'){
  6900. e.stopEvent();
  6901. r.pasteHTML('<br />');
  6902. r.collapse(false);
  6903. r.select();
  6904. }
  6905. }
  6906. }
  6907. };
  6908. }else if(Ext.isOpera){
  6909. return function(e){
  6910. var k = e.getKey();
  6911. if(k == e.TAB){
  6912. e.stopEvent();
  6913. this.win.focus();
  6914. this.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
  6915. this.deferFocus();
  6916. }
  6917. };
  6918. }else if(Ext.isWebKit){
  6919. return function(e){
  6920. var k = e.getKey();
  6921. if(k == e.TAB){
  6922. e.stopEvent();
  6923. this.execCmd('InsertText','\t');
  6924. this.deferFocus();
  6925. }else if(k == e.ENTER){
  6926. e.stopEvent();
  6927. this.execCmd('InsertHtml','<br /><br />');
  6928. this.deferFocus();
  6929. }
  6930. };
  6931. }
  6932. }(),
  6933. /**
  6934. * Returns the editor's toolbar. <b>This is only available after the editor has been rendered.</b>
  6935. * @return {Ext.Toolbar}
  6936. */
  6937. getToolbar : function(){
  6938. return this.tb;
  6939. },
  6940. /**
  6941. * Object collection of toolbar tooltips for the buttons in the editor. The key
  6942. * is the command id associated with that button and the value is a valid QuickTips object.
  6943. * For example:
  6944. <pre><code>
  6945. {
  6946. bold : {
  6947. title: 'Bold (Ctrl+B)',
  6948. text: 'Make the selected text bold.',
  6949. cls: 'x-html-editor-tip'
  6950. },
  6951. italic : {
  6952. title: 'Italic (Ctrl+I)',
  6953. text: 'Make the selected text italic.',
  6954. cls: 'x-html-editor-tip'
  6955. },
  6956. ...
  6957. </code></pre>
  6958. * @type Object
  6959. */
  6960. buttonTips : {
  6961. bold : {
  6962. title: 'Bold (Ctrl+B)',
  6963. text: 'Make the selected text bold.',
  6964. cls: 'x-html-editor-tip'
  6965. },
  6966. italic : {
  6967. title: 'Italic (Ctrl+I)',
  6968. text: 'Make the selected text italic.',
  6969. cls: 'x-html-editor-tip'
  6970. },
  6971. underline : {
  6972. title: 'Underline (Ctrl+U)',
  6973. text: 'Underline the selected text.',
  6974. cls: 'x-html-editor-tip'
  6975. },
  6976. increasefontsize : {
  6977. title: 'Grow Text',
  6978. text: 'Increase the font size.',
  6979. cls: 'x-html-editor-tip'
  6980. },
  6981. decreasefontsize : {
  6982. title: 'Shrink Text',
  6983. text: 'Decrease the font size.',
  6984. cls: 'x-html-editor-tip'
  6985. },
  6986. backcolor : {
  6987. title: 'Text Highlight Color',
  6988. text: 'Change the background color of the selected text.',
  6989. cls: 'x-html-editor-tip'
  6990. },
  6991. forecolor : {
  6992. title: 'Font Color',
  6993. text: 'Change the color of the selected text.',
  6994. cls: 'x-html-editor-tip'
  6995. },
  6996. justifyleft : {
  6997. title: 'Align Text Left',
  6998. text: 'Align text to the left.',
  6999. cls: 'x-html-editor-tip'
  7000. },
  7001. justifycenter : {
  7002. title: 'Center Text',
  7003. text: 'Center text in the editor.',
  7004. cls: 'x-html-editor-tip'
  7005. },
  7006. justifyright : {
  7007. title: 'Align Text Right',
  7008. text: 'Align text to the right.',
  7009. cls: 'x-html-editor-tip'
  7010. },
  7011. insertunorderedlist : {
  7012. title: 'Bullet List',
  7013. text: 'Start a bulleted list.',
  7014. cls: 'x-html-editor-tip'
  7015. },
  7016. insertorderedlist : {
  7017. title: 'Numbered List',
  7018. text: 'Start a numbered list.',
  7019. cls: 'x-html-editor-tip'
  7020. },
  7021. createlink : {
  7022. title: 'Hyperlink',
  7023. text: 'Make the selected text a hyperlink.',
  7024. cls: 'x-html-editor-tip'
  7025. },
  7026. sourceedit : {
  7027. title: 'Source Edit',
  7028. text: 'Switch to source editing mode.',
  7029. cls: 'x-html-editor-tip'
  7030. }
  7031. }
  7032. // hide stuff that is not compatible
  7033. /**
  7034. * @event blur
  7035. * @hide
  7036. */
  7037. /**
  7038. * @event change
  7039. * @hide
  7040. */
  7041. /**
  7042. * @event focus
  7043. * @hide
  7044. */
  7045. /**
  7046. * @event specialkey
  7047. * @hide
  7048. */
  7049. /**
  7050. * @cfg {String} fieldClass @hide
  7051. */
  7052. /**
  7053. * @cfg {String} focusClass @hide
  7054. */
  7055. /**
  7056. * @cfg {String} autoCreate @hide
  7057. */
  7058. /**
  7059. * @cfg {String} inputType @hide
  7060. */
  7061. /**
  7062. * @cfg {String} invalidClass @hide
  7063. */
  7064. /**
  7065. * @cfg {String} invalidText @hide
  7066. */
  7067. /**
  7068. * @cfg {String} msgFx @hide
  7069. */
  7070. /**
  7071. * @cfg {String} validateOnBlur @hide
  7072. */
  7073. /**
  7074. * @cfg {Boolean} allowDomMove @hide
  7075. */
  7076. /**
  7077. * @cfg {String} applyTo @hide
  7078. */
  7079. /**
  7080. * @cfg {String} autoHeight @hide
  7081. */
  7082. /**
  7083. * @cfg {String} autoWidth @hide
  7084. */
  7085. /**
  7086. * @cfg {String} cls @hide
  7087. */
  7088. /**
  7089. * @cfg {String} disabled @hide
  7090. */
  7091. /**
  7092. * @cfg {String} disabledClass @hide
  7093. */
  7094. /**
  7095. * @cfg {String} msgTarget @hide
  7096. */
  7097. /**
  7098. * @cfg {String} readOnly @hide
  7099. */
  7100. /**
  7101. * @cfg {String} style @hide
  7102. */
  7103. /**
  7104. * @cfg {String} validationDelay @hide
  7105. */
  7106. /**
  7107. * @cfg {String} validationEvent @hide
  7108. */
  7109. /**
  7110. * @cfg {String} tabIndex @hide
  7111. */
  7112. /**
  7113. * @property disabled
  7114. * @hide
  7115. */
  7116. /**
  7117. * @method applyToMarkup
  7118. * @hide
  7119. */
  7120. /**
  7121. * @method disable
  7122. * @hide
  7123. */
  7124. /**
  7125. * @method enable
  7126. * @hide
  7127. */
  7128. /**
  7129. * @method validate
  7130. * @hide
  7131. */
  7132. /**
  7133. * @event valid
  7134. * @hide
  7135. */
  7136. /**
  7137. * @method setDisabled
  7138. * @hide
  7139. */
  7140. /**
  7141. * @cfg keys
  7142. * @hide
  7143. */
  7144. });
  7145. Ext.reg('htmleditor', Ext.form.HtmlEditor);/**
  7146. * @class Ext.form.TimeField
  7147. * @extends Ext.form.ComboBox
  7148. * Provides a time input field with a time dropdown and automatic time validation. Example usage:
  7149. * <pre><code>
  7150. new Ext.form.TimeField({
  7151. minValue: '9:00 AM',
  7152. maxValue: '6:00 PM',
  7153. increment: 30
  7154. });
  7155. </code></pre>
  7156. * @constructor
  7157. * Create a new TimeField
  7158. * @param {Object} config
  7159. * @xtype timefield
  7160. */
  7161. Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, {
  7162. /**
  7163. * @cfg {Date/String} minValue
  7164. * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string
  7165. * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
  7166. */
  7167. minValue : undefined,
  7168. /**
  7169. * @cfg {Date/String} maxValue
  7170. * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string
  7171. * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to undefined).
  7172. */
  7173. maxValue : undefined,
  7174. /**
  7175. * @cfg {String} minText
  7176. * The error text to display when the date in the cell is before minValue (defaults to
  7177. * 'The time in this field must be equal to or after {0}').
  7178. */
  7179. minText : "The time in this field must be equal to or after {0}",
  7180. /**
  7181. * @cfg {String} maxText
  7182. * The error text to display when the time is after maxValue (defaults to
  7183. * 'The time in this field must be equal to or before {0}').
  7184. */
  7185. maxText : "The time in this field must be equal to or before {0}",
  7186. /**
  7187. * @cfg {String} invalidText
  7188. * The error text to display when the time in the field is invalid (defaults to
  7189. * '{value} is not a valid time').
  7190. */
  7191. invalidText : "{0} is not a valid time",
  7192. /**
  7193. * @cfg {String} format
  7194. * The default time format string which can be overriden for localization support. The format must be
  7195. * valid according to {@link Date#parseDate} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time
  7196. * format try 'H:i' instead.
  7197. */
  7198. format : "g:i A",
  7199. /**
  7200. * @cfg {String} altFormats
  7201. * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
  7202. * 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').
  7203. */
  7204. 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",
  7205. /**
  7206. * @cfg {Number} increment
  7207. * The number of minutes between each time value in the list (defaults to 15).
  7208. */
  7209. increment: 15,
  7210. // private override
  7211. mode: 'local',
  7212. // private override
  7213. triggerAction: 'all',
  7214. // private override
  7215. typeAhead: false,
  7216. // private - This is the date to use when generating time values in the absence of either minValue
  7217. // or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an
  7218. // arbitrary "safe" date that can be any date aside from DST boundary dates.
  7219. initDate: '1/1/2008',
  7220. initDateFormat: 'j/n/Y',
  7221. // private
  7222. initComponent : function(){
  7223. if(Ext.isDefined(this.minValue)){
  7224. this.setMinValue(this.minValue, true);
  7225. }
  7226. if(Ext.isDefined(this.maxValue)){
  7227. this.setMaxValue(this.maxValue, true);
  7228. }
  7229. if(!this.store){
  7230. this.generateStore(true);
  7231. }
  7232. Ext.form.TimeField.superclass.initComponent.call(this);
  7233. },
  7234. /**
  7235. * Replaces any existing {@link #minValue} with the new time and refreshes the store.
  7236. * @param {Date/String} value The minimum time that can be selected
  7237. */
  7238. setMinValue: function(value, /* private */ initial){
  7239. this.setLimit(value, true, initial);
  7240. return this;
  7241. },
  7242. /**
  7243. * Replaces any existing {@link #maxValue} with the new time and refreshes the store.
  7244. * @param {Date/String} value The maximum time that can be selected
  7245. */
  7246. setMaxValue: function(value, /* private */ initial){
  7247. this.setLimit(value, false, initial);
  7248. return this;
  7249. },
  7250. // private
  7251. generateStore: function(initial){
  7252. var min = this.minValue || new Date(this.initDate).clearTime(),
  7253. max = this.maxValue || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1),
  7254. times = [];
  7255. while(min <= max){
  7256. times.push(min.dateFormat(this.format));
  7257. min = min.add('mi', this.increment);
  7258. }
  7259. this.bindStore(times, initial);
  7260. },
  7261. // private
  7262. setLimit: function(value, isMin, initial){
  7263. var d;
  7264. if(Ext.isString(value)){
  7265. d = this.parseDate(value);
  7266. }else if(Ext.isDate(value)){
  7267. d = value;
  7268. }
  7269. if(d){
  7270. var val = new Date(this.initDate).clearTime();
  7271. val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
  7272. this[isMin ? 'minValue' : 'maxValue'] = val;
  7273. if(!initial){
  7274. this.generateStore();
  7275. }
  7276. }
  7277. },
  7278. // inherited docs
  7279. getValue : function(){
  7280. var v = Ext.form.TimeField.superclass.getValue.call(this);
  7281. return this.formatDate(this.parseDate(v)) || '';
  7282. },
  7283. // inherited docs
  7284. setValue : function(value){
  7285. return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value)));
  7286. },
  7287. // private overrides
  7288. validateValue : Ext.form.DateField.prototype.validateValue,
  7289. formatDate : Ext.form.DateField.prototype.formatDate,
  7290. parseDate: function(value) {
  7291. if (!value || Ext.isDate(value)) {
  7292. return value;
  7293. }
  7294. var id = this.initDate + ' ',
  7295. idf = this.initDateFormat + ' ',
  7296. v = Date.parseDate(id + value, idf + this.format), // *** handle DST. note: this.format is a TIME-only format
  7297. af = this.altFormats;
  7298. if (!v && af) {
  7299. if (!this.altFormatsArray) {
  7300. this.altFormatsArray = af.split("|");
  7301. }
  7302. for (var i = 0, afa = this.altFormatsArray, len = afa.length; i < len && !v; i++) {
  7303. v = Date.parseDate(id + value, idf + afa[i]);
  7304. }
  7305. }
  7306. return v;
  7307. }
  7308. });
  7309. Ext.reg('timefield', Ext.form.TimeField);/**
  7310. * @class Ext.form.SliderField
  7311. * @extends Ext.form.Field
  7312. * Wraps a {@link Ext.Slider Slider} so it can be used as a form field.
  7313. * @constructor
  7314. * Creates a new SliderField
  7315. * @param {Object} config Configuration options. Note that you can pass in any slider configuration options, as well as
  7316. * as any field configuration options.
  7317. * @xtype sliderfield
  7318. */
  7319. Ext.form.SliderField = Ext.extend(Ext.form.Field, {
  7320. /**
  7321. * @cfg {Boolean} useTips
  7322. * True to use an Ext.slider.Tip to display tips for the value. Defaults to <tt>true</tt>.
  7323. */
  7324. useTips : true,
  7325. /**
  7326. * @cfg {Function} tipText
  7327. * A function used to display custom text for the slider tip. Defaults to <tt>null</tt>, which will
  7328. * use the default on the plugin.
  7329. */
  7330. tipText : null,
  7331. // private override
  7332. actionMode: 'wrap',
  7333. /**
  7334. * Initialize the component.
  7335. * @private
  7336. */
  7337. initComponent : function() {
  7338. var cfg = Ext.copyTo({
  7339. id: this.id + '-slider'
  7340. }, this.initialConfig, ['vertical', 'minValue', 'maxValue', 'decimalPrecision', 'keyIncrement', 'increment', 'clickToChange', 'animate']);
  7341. // only can use it if it exists.
  7342. if (this.useTips) {
  7343. var plug = this.tipText ? {getText: this.tipText} : {};
  7344. cfg.plugins = [new Ext.slider.Tip(plug)];
  7345. }
  7346. this.slider = new Ext.Slider(cfg);
  7347. Ext.form.SliderField.superclass.initComponent.call(this);
  7348. },
  7349. /**
  7350. * Set up the hidden field
  7351. * @param {Object} ct The container to render to.
  7352. * @param {Object} position The position in the container to render to.
  7353. * @private
  7354. */
  7355. onRender : function(ct, position){
  7356. this.autoCreate = {
  7357. id: this.id,
  7358. name: this.name,
  7359. type: 'hidden',
  7360. tag: 'input'
  7361. };
  7362. Ext.form.SliderField.superclass.onRender.call(this, ct, position);
  7363. this.wrap = this.el.wrap({cls: 'x-form-field-wrap'});
  7364. this.slider.render(this.wrap);
  7365. },
  7366. /**
  7367. * Ensure that the slider size is set automatically when the field resizes.
  7368. * @param {Object} w The width
  7369. * @param {Object} h The height
  7370. * @param {Object} aw The adjusted width
  7371. * @param {Object} ah The adjusted height
  7372. * @private
  7373. */
  7374. onResize : function(w, h, aw, ah){
  7375. Ext.form.SliderField.superclass.onResize.call(this, w, h, aw, ah);
  7376. this.slider.setSize(w, h);
  7377. },
  7378. /**
  7379. * Initialize any events for this class.
  7380. * @private
  7381. */
  7382. initEvents : function(){
  7383. Ext.form.SliderField.superclass.initEvents.call(this);
  7384. this.slider.on('change', this.onChange, this);
  7385. },
  7386. /**
  7387. * Utility method to set the value of the field when the slider changes.
  7388. * @param {Object} slider The slider object.
  7389. * @param {Object} v The new value.
  7390. * @private
  7391. */
  7392. onChange : function(slider, v){
  7393. this.setValue(v, undefined, true);
  7394. },
  7395. /**
  7396. * Enable the slider when the field is enabled.
  7397. * @private
  7398. */
  7399. onEnable : function(){
  7400. Ext.form.SliderField.superclass.onEnable.call(this);
  7401. this.slider.enable();
  7402. },
  7403. /**
  7404. * Disable the slider when the field is disabled.
  7405. * @private
  7406. */
  7407. onDisable : function(){
  7408. Ext.form.SliderField.superclass.onDisable.call(this);
  7409. this.slider.disable();
  7410. },
  7411. /**
  7412. * Ensure the slider is destroyed when the field is destroyed.
  7413. * @private
  7414. */
  7415. beforeDestroy : function(){
  7416. Ext.destroy(this.slider);
  7417. Ext.form.SliderField.superclass.beforeDestroy.call(this);
  7418. },
  7419. /**
  7420. * If a side icon is shown, do alignment to the slider
  7421. * @private
  7422. */
  7423. alignErrorIcon : function(){
  7424. this.errorIcon.alignTo(this.slider.el, 'tl-tr', [2, 0]);
  7425. },
  7426. /**
  7427. * Sets the minimum field value.
  7428. * @param {Number} v The new minimum value.
  7429. * @return {Ext.form.SliderField} this
  7430. */
  7431. setMinValue : function(v){
  7432. this.slider.setMinValue(v);
  7433. return this;
  7434. },
  7435. /**
  7436. * Sets the maximum field value.
  7437. * @param {Number} v The new maximum value.
  7438. * @return {Ext.form.SliderField} this
  7439. */
  7440. setMaxValue : function(v){
  7441. this.slider.setMaxValue(v);
  7442. return this;
  7443. },
  7444. /**
  7445. * Sets the value for this field.
  7446. * @param {Number} v The new value.
  7447. * @param {Boolean} animate (optional) Whether to animate the transition. If not specified, it will default to the animate config.
  7448. * @return {Ext.form.SliderField} this
  7449. */
  7450. setValue : function(v, animate, /* private */ silent){
  7451. // silent is used if the setValue method is invoked by the slider
  7452. // which means we don't need to set the value on the slider.
  7453. if(!silent){
  7454. this.slider.setValue(v, animate);
  7455. }
  7456. return Ext.form.SliderField.superclass.setValue.call(this, this.slider.getValue());
  7457. },
  7458. /**
  7459. * Gets the current value for this field.
  7460. * @return {Number} The current value.
  7461. */
  7462. getValue : function(){
  7463. return this.slider.getValue();
  7464. }
  7465. });
  7466. Ext.reg('sliderfield', Ext.form.SliderField);/**
  7467. * @class Ext.form.Label
  7468. * @extends Ext.BoxComponent
  7469. * Basic Label field.
  7470. * @constructor
  7471. * Creates a new Label
  7472. * @param {Ext.Element/String/Object} config The configuration options. If an element is passed, it is set as the internal
  7473. * element and its id used as the component id. If a string is passed, it is assumed to be the id of an existing element
  7474. * and is used as the component id. Otherwise, it is assumed to be a standard config object and is applied to the component.
  7475. * @xtype label
  7476. */
  7477. Ext.form.Label = Ext.extend(Ext.BoxComponent, {
  7478. /**
  7479. * @cfg {String} text The plain text to display within the label (defaults to ''). If you need to include HTML
  7480. * tags within the label's innerHTML, use the {@link #html} config instead.
  7481. */
  7482. /**
  7483. * @cfg {String} forId The id of the input element to which this label will be bound via the standard HTML 'for'
  7484. * attribute. If not specified, the attribute will not be added to the label.
  7485. */
  7486. /**
  7487. * @cfg {String} html An HTML fragment that will be used as the label's innerHTML (defaults to '').
  7488. * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
  7489. */
  7490. // private
  7491. onRender : function(ct, position){
  7492. if(!this.el){
  7493. this.el = document.createElement('label');
  7494. this.el.id = this.getId();
  7495. this.el.innerHTML = this.text ? Ext.util.Format.htmlEncode(this.text) : (this.html || '');
  7496. if(this.forId){
  7497. this.el.setAttribute('for', this.forId);
  7498. }
  7499. }
  7500. Ext.form.Label.superclass.onRender.call(this, ct, position);
  7501. },
  7502. /**
  7503. * Updates the label's innerHTML with the specified string.
  7504. * @param {String} text The new label text
  7505. * @param {Boolean} encode (optional) False to skip HTML-encoding the text when rendering it
  7506. * to the label (defaults to true which encodes the value). This might be useful if you want to include
  7507. * tags in the label's innerHTML rather than rendering them as string literals per the default logic.
  7508. * @return {Label} this
  7509. */
  7510. setText : function(t, encode){
  7511. var e = encode === false;
  7512. this[!e ? 'text' : 'html'] = t;
  7513. delete this[e ? 'text' : 'html'];
  7514. if(this.rendered){
  7515. this.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(t) : t;
  7516. }
  7517. return this;
  7518. }
  7519. });
  7520. Ext.reg('label', Ext.form.Label);/**
  7521. * @class Ext.form.Action
  7522. * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.BasicForm Form}s.</p>
  7523. * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
  7524. * the Form needs to perform an action such as submit or load. The Configuration options
  7525. * listed for this class are set through the Form's action methods: {@link Ext.form.BasicForm#submit submit},
  7526. * {@link Ext.form.BasicForm#load load} and {@link Ext.form.BasicForm#doAction doAction}</p>
  7527. * <p>The instance of Action which performed the action is passed to the success
  7528. * and failure callbacks of the Form's action methods ({@link Ext.form.BasicForm#submit submit},
  7529. * {@link Ext.form.BasicForm#load load} and {@link Ext.form.BasicForm#doAction doAction}),
  7530. * and to the {@link Ext.form.BasicForm#actioncomplete actioncomplete} and
  7531. * {@link Ext.form.BasicForm#actionfailed actionfailed} event handlers.</p>
  7532. */
  7533. Ext.form.Action = function(form, options){
  7534. this.form = form;
  7535. this.options = options || {};
  7536. };
  7537. /**
  7538. * Failure type returned when client side validation of the Form fails
  7539. * thus aborting a submit action. Client side validation is performed unless
  7540. * {@link #clientValidation} is explicitly set to <tt>false</tt>.
  7541. * @type {String}
  7542. * @static
  7543. */
  7544. Ext.form.Action.CLIENT_INVALID = 'client';
  7545. /**
  7546. * <p>Failure type returned when server side processing fails and the {@link #result}'s
  7547. * <tt style="font-weight:bold">success</tt> property is set to <tt>false</tt>.</p>
  7548. * <p>In the case of a form submission, field-specific error messages may be returned in the
  7549. * {@link #result}'s <tt style="font-weight:bold">errors</tt> property.</p>
  7550. * @type {String}
  7551. * @static
  7552. */
  7553. Ext.form.Action.SERVER_INVALID = 'server';
  7554. /**
  7555. * Failure type returned when a communication error happens when attempting
  7556. * to send a request to the remote server. The {@link #response} may be examined to
  7557. * provide further information.
  7558. * @type {String}
  7559. * @static
  7560. */
  7561. Ext.form.Action.CONNECT_FAILURE = 'connect';
  7562. /**
  7563. * Failure type returned when the response's <tt style="font-weight:bold">success</tt>
  7564. * property is set to <tt>false</tt>, or no field values are returned in the response's
  7565. * <tt style="font-weight:bold">data</tt> property.
  7566. * @type {String}
  7567. * @static
  7568. */
  7569. Ext.form.Action.LOAD_FAILURE = 'load';
  7570. Ext.form.Action.prototype = {
  7571. /**
  7572. * @cfg {String} url The URL that the Action is to invoke.
  7573. */
  7574. /**
  7575. * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
  7576. * {@link Ext.form.BasicForm.reset reset} on Action success. If specified, this happens
  7577. * <b>before</b> the {@link #success} callback is called and before the Form's
  7578. * {@link Ext.form.BasicForm.actioncomplete actioncomplete} event fires.
  7579. */
  7580. /**
  7581. * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
  7582. * {@link Ext.form.BasicForm}'s method, or if that is not specified, the underlying DOM form's method.
  7583. */
  7584. /**
  7585. * @cfg {Mixed} params <p>Extra parameter values to pass. These are added to the Form's
  7586. * {@link Ext.form.BasicForm#baseParams} and passed to the specified URL along with the Form's
  7587. * input fields.</p>
  7588. * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
  7589. */
  7590. /**
  7591. * @cfg {Number} timeout The number of seconds to wait for a server response before
  7592. * failing with the {@link #failureType} as {@link #Action.CONNECT_FAILURE}. If not specified,
  7593. * defaults to the configured <tt>{@link Ext.form.BasicForm#timeout timeout}</tt> of the
  7594. * {@link Ext.form.BasicForm form}.
  7595. */
  7596. /**
  7597. * @cfg {Function} success The function to call when a valid success return packet is recieved.
  7598. * The function is passed the following parameters:<ul class="mdetail-params">
  7599. * <li><b>form</b> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
  7600. * <li><b>action</b> : Ext.form.Action<div class="sub-desc">The Action class. The {@link #result}
  7601. * property of this object may be examined to perform custom postprocessing.</div></li>
  7602. * </ul>
  7603. */
  7604. /**
  7605. * @cfg {Function} failure The function to call when a failure packet was recieved, or when an
  7606. * error ocurred in the Ajax communication.
  7607. * The function is passed the following parameters:<ul class="mdetail-params">
  7608. * <li><b>form</b> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
  7609. * <li><b>action</b> : Ext.form.Action<div class="sub-desc">The Action class. If an Ajax
  7610. * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
  7611. * property of this object may be examined to perform custom postprocessing.</div></li>
  7612. * </ul>
  7613. */
  7614. /**
  7615. * @cfg {Object} scope The scope in which to call the callback functions (The <tt>this</tt> reference
  7616. * for the callback functions).
  7617. */
  7618. /**
  7619. * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.MessageBox#wait}
  7620. * during the time the action is being processed.
  7621. */
  7622. /**
  7623. * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.MessageBox#wait}
  7624. * during the time the action is being processed.
  7625. */
  7626. /**
  7627. * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
  7628. * when it is submitted. Defaults to <tt>true</tt>.
  7629. */
  7630. /**
  7631. * The type of action this Action instance performs.
  7632. * Currently only "submit" and "load" are supported.
  7633. * @type {String}
  7634. */
  7635. type : 'default',
  7636. /**
  7637. * The type of failure detected will be one of these: {@link #CLIENT_INVALID},
  7638. * {@link #SERVER_INVALID}, {@link #CONNECT_FAILURE}, or {@link #LOAD_FAILURE}. Usage:
  7639. * <pre><code>
  7640. var fp = new Ext.form.FormPanel({
  7641. ...
  7642. buttons: [{
  7643. text: 'Save',
  7644. formBind: true,
  7645. handler: function(){
  7646. if(fp.getForm().isValid()){
  7647. fp.getForm().submit({
  7648. url: 'form-submit.php',
  7649. waitMsg: 'Submitting your data...',
  7650. success: function(form, action){
  7651. // server responded with success = true
  7652. var result = action.{@link #result};
  7653. },
  7654. failure: function(form, action){
  7655. if (action.{@link #failureType} === Ext.form.Action.{@link #CONNECT_FAILURE}) {
  7656. Ext.Msg.alert('Error',
  7657. 'Status:'+action.{@link #response}.status+': '+
  7658. action.{@link #response}.statusText);
  7659. }
  7660. if (action.failureType === Ext.form.Action.{@link #SERVER_INVALID}){
  7661. // server responded with success = false
  7662. Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
  7663. }
  7664. }
  7665. });
  7666. }
  7667. }
  7668. },{
  7669. text: 'Reset',
  7670. handler: function(){
  7671. fp.getForm().reset();
  7672. }
  7673. }]
  7674. * </code></pre>
  7675. * @property failureType
  7676. * @type {String}
  7677. */
  7678. /**
  7679. * The XMLHttpRequest object used to perform the action.
  7680. * @property response
  7681. * @type {Object}
  7682. */
  7683. /**
  7684. * The decoded response object containing a boolean <tt style="font-weight:bold">success</tt> property and
  7685. * other, action-specific properties.
  7686. * @property result
  7687. * @type {Object}
  7688. */
  7689. // interface method
  7690. run : function(options){
  7691. },
  7692. // interface method
  7693. success : function(response){
  7694. },
  7695. // interface method
  7696. handleResponse : function(response){
  7697. },
  7698. // default connection failure
  7699. failure : function(response){
  7700. this.response = response;
  7701. this.failureType = Ext.form.Action.CONNECT_FAILURE;
  7702. this.form.afterAction(this, false);
  7703. },
  7704. // private
  7705. // shared code among all Actions to validate that there was a response
  7706. // with either responseText or responseXml
  7707. processResponse : function(response){
  7708. this.response = response;
  7709. if(!response.responseText && !response.responseXML){
  7710. return true;
  7711. }
  7712. this.result = this.handleResponse(response);
  7713. return this.result;
  7714. },
  7715. // utility functions used internally
  7716. getUrl : function(appendParams){
  7717. var url = this.options.url || this.form.url || this.form.el.dom.action;
  7718. if(appendParams){
  7719. var p = this.getParams();
  7720. if(p){
  7721. url = Ext.urlAppend(url, p);
  7722. }
  7723. }
  7724. return url;
  7725. },
  7726. // private
  7727. getMethod : function(){
  7728. return (this.options.method || this.form.method || this.form.el.dom.method || 'POST').toUpperCase();
  7729. },
  7730. // private
  7731. getParams : function(){
  7732. var bp = this.form.baseParams;
  7733. var p = this.options.params;
  7734. if(p){
  7735. if(typeof p == "object"){
  7736. p = Ext.urlEncode(Ext.applyIf(p, bp));
  7737. }else if(typeof p == 'string' && bp){
  7738. p += '&' + Ext.urlEncode(bp);
  7739. }
  7740. }else if(bp){
  7741. p = Ext.urlEncode(bp);
  7742. }
  7743. return p;
  7744. },
  7745. // private
  7746. createCallback : function(opts){
  7747. var opts = opts || {};
  7748. return {
  7749. success: this.success,
  7750. failure: this.failure,
  7751. scope: this,
  7752. timeout: (opts.timeout*1000) || (this.form.timeout*1000),
  7753. upload: this.form.fileUpload ? this.success : undefined
  7754. };
  7755. }
  7756. };
  7757. /**
  7758. * @class Ext.form.Action.Submit
  7759. * @extends Ext.form.Action
  7760. * <p>A class which handles submission of data from {@link Ext.form.BasicForm Form}s
  7761. * and processes the returned response.</p>
  7762. * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
  7763. * {@link Ext.form.BasicForm#submit submit}ting.</p>
  7764. * <p><u><b>Response Packet Criteria</b></u></p>
  7765. * <p>A response packet may contain:
  7766. * <div class="mdetail-params"><ul>
  7767. * <li><b><code>success</code></b> property : Boolean
  7768. * <div class="sub-desc">The <code>success</code> property is required.</div></li>
  7769. * <li><b><code>errors</code></b> property : Object
  7770. * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
  7771. * which is optional, contains error messages for invalid fields.</div></li>
  7772. * </ul></div>
  7773. * <p><u><b>JSON Packets</b></u></p>
  7774. * <p>By default, response packets are assumed to be JSON, so a typical response
  7775. * packet may look like this:</p><pre><code>
  7776. {
  7777. success: false,
  7778. errors: {
  7779. clientCode: "Client not found",
  7780. portOfLoading: "This field must not be null"
  7781. }
  7782. }</code></pre>
  7783. * <p>Other data may be placed into the response for processing by the {@link Ext.form.BasicForm}'s callback
  7784. * or event handler methods. The object decoded from this JSON is available in the
  7785. * {@link Ext.form.Action#result result} property.</p>
  7786. * <p>Alternatively, if an {@link #errorReader} is specified as an {@link Ext.data.XmlReader XmlReader}:</p><pre><code>
  7787. errorReader: new Ext.data.XmlReader({
  7788. record : 'field',
  7789. success: '@success'
  7790. }, [
  7791. 'id', 'msg'
  7792. ]
  7793. )
  7794. </code></pre>
  7795. * <p>then the results may be sent back in XML format:</p><pre><code>
  7796. &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  7797. &lt;message success="false"&gt;
  7798. &lt;errors&gt;
  7799. &lt;field&gt;
  7800. &lt;id&gt;clientCode&lt;/id&gt;
  7801. &lt;msg&gt;&lt;![CDATA[Code not found. &lt;br /&gt;&lt;i&gt;This is a test validation message from the server &lt;/i&gt;]]&gt;&lt;/msg&gt;
  7802. &lt;/field&gt;
  7803. &lt;field&gt;
  7804. &lt;id&gt;portOfLoading&lt;/id&gt;
  7805. &lt;msg&gt;&lt;![CDATA[Port not found. &lt;br /&gt;&lt;i&gt;This is a test validation message from the server &lt;/i&gt;]]&gt;&lt;/msg&gt;
  7806. &lt;/field&gt;
  7807. &lt;/errors&gt;
  7808. &lt;/message&gt;
  7809. </code></pre>
  7810. * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.BasicForm}'s callback
  7811. * or event handler methods. The XML document is available in the {@link #errorReader}'s {@link Ext.data.XmlReader#xmlData xmlData} property.</p>
  7812. */
  7813. Ext.form.Action.Submit = function(form, options){
  7814. Ext.form.Action.Submit.superclass.constructor.call(this, form, options);
  7815. };
  7816. Ext.extend(Ext.form.Action.Submit, Ext.form.Action, {
  7817. /**
  7818. * @cfg {Ext.data.DataReader} errorReader <p><b>Optional. JSON is interpreted with
  7819. * no need for an errorReader.</b></p>
  7820. * <p>A Reader which reads a single record from the returned data. The DataReader's
  7821. * <b>success</b> property specifies how submission success is determined. The Record's
  7822. * data provides the error messages to apply to any invalid form Fields.</p>
  7823. */
  7824. /**
  7825. * @cfg {boolean} clientValidation Determines whether a Form's fields are validated
  7826. * in a final call to {@link Ext.form.BasicForm#isValid isValid} prior to submission.
  7827. * Pass <tt>false</tt> in the Form's submit options to prevent this. If not defined, pre-submission field validation
  7828. * is performed.
  7829. */
  7830. type : 'submit',
  7831. // private
  7832. run : function(){
  7833. var o = this.options,
  7834. method = this.getMethod(),
  7835. isGet = method == 'GET';
  7836. if(o.clientValidation === false || this.form.isValid()){
  7837. if (o.submitEmptyText === false) {
  7838. var fields = this.form.items,
  7839. emptyFields = [];
  7840. fields.each(function(f) {
  7841. if (f.el.getValue() == f.emptyText) {
  7842. emptyFields.push(f);
  7843. f.el.dom.value = "";
  7844. }
  7845. });
  7846. }
  7847. Ext.Ajax.request(Ext.apply(this.createCallback(o), {
  7848. form:this.form.el.dom,
  7849. url:this.getUrl(isGet),
  7850. method: method,
  7851. headers: o.headers,
  7852. params:!isGet ? this.getParams() : null,
  7853. isUpload: this.form.fileUpload
  7854. }));
  7855. if (o.submitEmptyText === false) {
  7856. Ext.each(emptyFields, function(f) {
  7857. if (f.applyEmptyText) {
  7858. f.applyEmptyText();
  7859. }
  7860. });
  7861. }
  7862. }else if (o.clientValidation !== false){ // client validation failed
  7863. this.failureType = Ext.form.Action.CLIENT_INVALID;
  7864. this.form.afterAction(this, false);
  7865. }
  7866. },
  7867. // private
  7868. success : function(response){
  7869. var result = this.processResponse(response);
  7870. if(result === true || result.success){
  7871. this.form.afterAction(this, true);
  7872. return;
  7873. }
  7874. if(result.errors){
  7875. this.form.markInvalid(result.errors);
  7876. }
  7877. this.failureType = Ext.form.Action.SERVER_INVALID;
  7878. this.form.afterAction(this, false);
  7879. },
  7880. // private
  7881. handleResponse : function(response){
  7882. if(this.form.errorReader){
  7883. var rs = this.form.errorReader.read(response);
  7884. var errors = [];
  7885. if(rs.records){
  7886. for(var i = 0, len = rs.records.length; i < len; i++) {
  7887. var r = rs.records[i];
  7888. errors[i] = r.data;
  7889. }
  7890. }
  7891. if(errors.length < 1){
  7892. errors = null;
  7893. }
  7894. return {
  7895. success : rs.success,
  7896. errors : errors
  7897. };
  7898. }
  7899. return Ext.decode(response.responseText);
  7900. }
  7901. });
  7902. /**
  7903. * @class Ext.form.Action.Load
  7904. * @extends Ext.form.Action
  7905. * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.BasicForm}.</p>
  7906. * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
  7907. * {@link Ext.form.BasicForm#load load}ing.</p>
  7908. * <p><u><b>Response Packet Criteria</b></u></p>
  7909. * <p>A response packet <b>must</b> contain:
  7910. * <div class="mdetail-params"><ul>
  7911. * <li><b><code>success</code></b> property : Boolean</li>
  7912. * <li><b><code>data</code></b> property : Object</li>
  7913. * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
  7914. * The individual value object for each Field is passed to the Field's
  7915. * {@link Ext.form.Field#setValue setValue} method.</div></li>
  7916. * </ul></div>
  7917. * <p><u><b>JSON Packets</b></u></p>
  7918. * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
  7919. var myFormPanel = new Ext.form.FormPanel({
  7920. title: 'Client and routing info',
  7921. items: [{
  7922. fieldLabel: 'Client',
  7923. name: 'clientName'
  7924. }, {
  7925. fieldLabel: 'Port of loading',
  7926. name: 'portOfLoading'
  7927. }, {
  7928. fieldLabel: 'Port of discharge',
  7929. name: 'portOfDischarge'
  7930. }]
  7931. });
  7932. myFormPanel.{@link Ext.form.FormPanel#getForm getForm}().{@link Ext.form.BasicForm#load load}({
  7933. url: '/getRoutingInfo.php',
  7934. params: {
  7935. consignmentRef: myConsignmentRef
  7936. },
  7937. failure: function(form, action) {
  7938. Ext.Msg.alert("Load failed", action.result.errorMessage);
  7939. }
  7940. });
  7941. </code></pre>
  7942. * a <b>success response</b> packet may look like this:</p><pre><code>
  7943. {
  7944. success: true,
  7945. data: {
  7946. clientName: "Fred. Olsen Lines",
  7947. portOfLoading: "FXT",
  7948. portOfDischarge: "OSL"
  7949. }
  7950. }</code></pre>
  7951. * while a <b>failure response</b> packet may look like this:</p><pre><code>
  7952. {
  7953. success: false,
  7954. errorMessage: "Consignment reference not found"
  7955. }</code></pre>
  7956. * <p>Other data may be placed into the response for processing the {@link Ext.form.BasicForm Form}'s
  7957. * callback or event handler methods. The object decoded from this JSON is available in the
  7958. * {@link Ext.form.Action#result result} property.</p>
  7959. */
  7960. Ext.form.Action.Load = function(form, options){
  7961. Ext.form.Action.Load.superclass.constructor.call(this, form, options);
  7962. this.reader = this.form.reader;
  7963. };
  7964. Ext.extend(Ext.form.Action.Load, Ext.form.Action, {
  7965. // private
  7966. type : 'load',
  7967. // private
  7968. run : function(){
  7969. Ext.Ajax.request(Ext.apply(
  7970. this.createCallback(this.options), {
  7971. method:this.getMethod(),
  7972. url:this.getUrl(false),
  7973. headers: this.options.headers,
  7974. params:this.getParams()
  7975. }));
  7976. },
  7977. // private
  7978. success : function(response){
  7979. var result = this.processResponse(response);
  7980. if(result === true || !result.success || !result.data){
  7981. this.failureType = Ext.form.Action.LOAD_FAILURE;
  7982. this.form.afterAction(this, false);
  7983. return;
  7984. }
  7985. this.form.clearInvalid();
  7986. this.form.setValues(result.data);
  7987. this.form.afterAction(this, true);
  7988. },
  7989. // private
  7990. handleResponse : function(response){
  7991. if(this.form.reader){
  7992. var rs = this.form.reader.read(response);
  7993. var data = rs.records && rs.records[0] ? rs.records[0].data : null;
  7994. return {
  7995. success : rs.success,
  7996. data : data
  7997. };
  7998. }
  7999. return Ext.decode(response.responseText);
  8000. }
  8001. });
  8002. /**
  8003. * @class Ext.form.Action.DirectLoad
  8004. * @extends Ext.form.Action.Load
  8005. * <p>Provides Ext.direct support for loading form data.</p>
  8006. * <p>This example illustrates usage of Ext.Direct to <b>load</b> a form through Ext.Direct.</p>
  8007. * <pre><code>
  8008. var myFormPanel = new Ext.form.FormPanel({
  8009. // configs for FormPanel
  8010. title: 'Basic Information',
  8011. renderTo: document.body,
  8012. width: 300, height: 160,
  8013. padding: 10,
  8014. // configs apply to child items
  8015. defaults: {anchor: '100%'},
  8016. defaultType: 'textfield',
  8017. items: [{
  8018. fieldLabel: 'Name',
  8019. name: 'name'
  8020. },{
  8021. fieldLabel: 'Email',
  8022. name: 'email'
  8023. },{
  8024. fieldLabel: 'Company',
  8025. name: 'company'
  8026. }],
  8027. // configs for BasicForm
  8028. api: {
  8029. // The server-side method to call for load() requests
  8030. load: Profile.getBasicInfo,
  8031. // The server-side must mark the submit handler as a 'formHandler'
  8032. submit: Profile.updateBasicInfo
  8033. },
  8034. // specify the order for the passed params
  8035. paramOrder: ['uid', 'foo']
  8036. });
  8037. // load the form
  8038. myFormPanel.getForm().load({
  8039. // pass 2 arguments to server side getBasicInfo method (len=2)
  8040. params: {
  8041. foo: 'bar',
  8042. uid: 34
  8043. }
  8044. });
  8045. * </code></pre>
  8046. * The data packet sent to the server will resemble something like:
  8047. * <pre><code>
  8048. [
  8049. {
  8050. "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
  8051. "data":[34,"bar"] // note the order of the params
  8052. }
  8053. ]
  8054. * </code></pre>
  8055. * The form will process a data packet returned by the server that is similar
  8056. * to the following format:
  8057. * <pre><code>
  8058. [
  8059. {
  8060. "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
  8061. "result":{
  8062. "success":true,
  8063. "data":{
  8064. "name":"Fred Flintstone",
  8065. "company":"Slate Rock and Gravel",
  8066. "email":"fred.flintstone@slaterg.com"
  8067. }
  8068. }
  8069. }
  8070. ]
  8071. * </code></pre>
  8072. */
  8073. Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, {
  8074. constructor: function(form, opts) {
  8075. Ext.form.Action.DirectLoad.superclass.constructor.call(this, form, opts);
  8076. },
  8077. type : 'directload',
  8078. run : function(){
  8079. var args = this.getParams();
  8080. args.push(this.success, this);
  8081. this.form.api.load.apply(window, args);
  8082. },
  8083. getParams : function() {
  8084. var buf = [], o = {};
  8085. var bp = this.form.baseParams;
  8086. var p = this.options.params;
  8087. Ext.apply(o, p, bp);
  8088. var paramOrder = this.form.paramOrder;
  8089. if(paramOrder){
  8090. for(var i = 0, len = paramOrder.length; i < len; i++){
  8091. buf.push(o[paramOrder[i]]);
  8092. }
  8093. }else if(this.form.paramsAsHash){
  8094. buf.push(o);
  8095. }
  8096. return buf;
  8097. },
  8098. // Direct actions have already been processed and therefore
  8099. // we can directly set the result; Direct Actions do not have
  8100. // a this.response property.
  8101. processResponse : function(result) {
  8102. this.result = result;
  8103. return result;
  8104. },
  8105. success : function(response, trans){
  8106. if(trans.type == Ext.Direct.exceptions.SERVER){
  8107. response = {};
  8108. }
  8109. Ext.form.Action.DirectLoad.superclass.success.call(this, response);
  8110. }
  8111. });
  8112. /**
  8113. * @class Ext.form.Action.DirectSubmit
  8114. * @extends Ext.form.Action.Submit
  8115. * <p>Provides Ext.direct support for submitting form data.</p>
  8116. * <p>This example illustrates usage of Ext.Direct to <b>submit</b> a form through Ext.Direct.</p>
  8117. * <pre><code>
  8118. var myFormPanel = new Ext.form.FormPanel({
  8119. // configs for FormPanel
  8120. title: 'Basic Information',
  8121. renderTo: document.body,
  8122. width: 300, height: 160,
  8123. padding: 10,
  8124. buttons:[{
  8125. text: 'Submit',
  8126. handler: function(){
  8127. myFormPanel.getForm().submit({
  8128. params: {
  8129. foo: 'bar',
  8130. uid: 34
  8131. }
  8132. });
  8133. }
  8134. }],
  8135. // configs apply to child items
  8136. defaults: {anchor: '100%'},
  8137. defaultType: 'textfield',
  8138. items: [{
  8139. fieldLabel: 'Name',
  8140. name: 'name'
  8141. },{
  8142. fieldLabel: 'Email',
  8143. name: 'email'
  8144. },{
  8145. fieldLabel: 'Company',
  8146. name: 'company'
  8147. }],
  8148. // configs for BasicForm
  8149. api: {
  8150. // The server-side method to call for load() requests
  8151. load: Profile.getBasicInfo,
  8152. // The server-side must mark the submit handler as a 'formHandler'
  8153. submit: Profile.updateBasicInfo
  8154. },
  8155. // specify the order for the passed params
  8156. paramOrder: ['uid', 'foo']
  8157. });
  8158. * </code></pre>
  8159. * The data packet sent to the server will resemble something like:
  8160. * <pre><code>
  8161. {
  8162. "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
  8163. "result":{
  8164. "success":true,
  8165. "id":{
  8166. "extAction":"Profile","extMethod":"updateBasicInfo",
  8167. "extType":"rpc","extTID":"6","extUpload":"false",
  8168. "name":"Aaron Conran","email":"aaron@extjs.com","company":"Ext JS, LLC"
  8169. }
  8170. }
  8171. }
  8172. * </code></pre>
  8173. * The form will process a data packet returned by the server that is similar
  8174. * to the following:
  8175. * <pre><code>
  8176. // sample success packet (batched requests)
  8177. [
  8178. {
  8179. "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
  8180. "result":{
  8181. "success":true
  8182. }
  8183. }
  8184. ]
  8185. // sample failure packet (one request)
  8186. {
  8187. "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
  8188. "result":{
  8189. "errors":{
  8190. "email":"already taken"
  8191. },
  8192. "success":false,
  8193. "foo":"bar"
  8194. }
  8195. }
  8196. * </code></pre>
  8197. * Also see the discussion in {@link Ext.form.Action.DirectLoad}.
  8198. */
  8199. Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, {
  8200. constructor : function(form, opts) {
  8201. Ext.form.Action.DirectSubmit.superclass.constructor.call(this, form, opts);
  8202. },
  8203. type : 'directsubmit',
  8204. // override of Submit
  8205. run : function(){
  8206. var o = this.options;
  8207. if(o.clientValidation === false || this.form.isValid()){
  8208. // tag on any additional params to be posted in the
  8209. // form scope
  8210. this.success.params = this.getParams();
  8211. this.form.api.submit(this.form.el.dom, this.success, this);
  8212. }else if (o.clientValidation !== false){ // client validation failed
  8213. this.failureType = Ext.form.Action.CLIENT_INVALID;
  8214. this.form.afterAction(this, false);
  8215. }
  8216. },
  8217. getParams : function() {
  8218. var o = {};
  8219. var bp = this.form.baseParams;
  8220. var p = this.options.params;
  8221. Ext.apply(o, p, bp);
  8222. return o;
  8223. },
  8224. // Direct actions have already been processed and therefore
  8225. // we can directly set the result; Direct Actions do not have
  8226. // a this.response property.
  8227. processResponse : function(result) {
  8228. this.result = result;
  8229. return result;
  8230. },
  8231. success : function(response, trans){
  8232. if(trans.type == Ext.Direct.exceptions.SERVER){
  8233. response = {};
  8234. }
  8235. Ext.form.Action.DirectSubmit.superclass.success.call(this, response);
  8236. }
  8237. });
  8238. Ext.form.Action.ACTION_TYPES = {
  8239. 'load' : Ext.form.Action.Load,
  8240. 'submit' : Ext.form.Action.Submit,
  8241. 'directload' : Ext.form.Action.DirectLoad,
  8242. 'directsubmit' : Ext.form.Action.DirectSubmit
  8243. };
  8244. /**
  8245. * @class Ext.form.VTypes
  8246. * <p>This is a singleton object which contains a set of commonly used field validation functions.
  8247. * The validations provided are basic and intended to be easily customizable and extended.</p>
  8248. * <p>To add custom VTypes specify the <code>{@link Ext.form.TextField#vtype vtype}</code> validation
  8249. * test function, and optionally specify any corresponding error text to display and any keystroke
  8250. * filtering mask to apply. For example:</p>
  8251. * <pre><code>
  8252. // custom Vtype for vtype:'time'
  8253. var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
  8254. Ext.apply(Ext.form.VTypes, {
  8255. // vtype validation function
  8256. time: function(val, field) {
  8257. return timeTest.test(val);
  8258. },
  8259. // vtype Text property: The error text to display when the validation function returns false
  8260. timeText: 'Not a valid time. Must be in the format "12:34 PM".',
  8261. // vtype Mask property: The keystroke filter mask
  8262. timeMask: /[\d\s:amp]/i
  8263. });
  8264. * </code></pre>
  8265. * Another example:
  8266. * <pre><code>
  8267. // custom Vtype for vtype:'IPAddress'
  8268. Ext.apply(Ext.form.VTypes, {
  8269. IPAddress: function(v) {
  8270. return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
  8271. },
  8272. IPAddressText: 'Must be a numeric IP address',
  8273. IPAddressMask: /[\d\.]/i
  8274. });
  8275. * </code></pre>
  8276. * @singleton
  8277. */
  8278. Ext.form.VTypes = function(){
  8279. // closure these in so they are only created once.
  8280. var alpha = /^[a-zA-Z_]+$/,
  8281. alphanum = /^[a-zA-Z0-9_]+$/,
  8282. email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
  8283. url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
  8284. // All these messages and functions are configurable
  8285. return {
  8286. /**
  8287. * The function used to validate email addresses. Note that this is a very basic validation -- complete
  8288. * validation per the email RFC specifications is very complex and beyond the scope of this class, although
  8289. * this function can be overridden if a more comprehensive validation scheme is desired. See the validation
  8290. * section of the <a href="http://en.wikipedia.org/wiki/E-mail_address">Wikipedia article on email addresses</a>
  8291. * for additional information. This implementation is intended to validate the following emails:<tt>
  8292. * 'barney@example.de', 'barney.rubble@example.com', 'barney-rubble@example.coop', 'barney+rubble@example.com'
  8293. * </tt>.
  8294. * @param {String} value The email address
  8295. * @return {Boolean} true if the RegExp test passed, and false if not.
  8296. */
  8297. 'email' : function(v){
  8298. return email.test(v);
  8299. },
  8300. /**
  8301. * The error text to display when the email validation function returns false. Defaults to:
  8302. * <tt>'This field should be an e-mail address in the format "user@example.com"'</tt>
  8303. * @type String
  8304. */
  8305. 'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
  8306. /**
  8307. * The keystroke filter mask to be applied on email input. See the {@link #email} method for
  8308. * information about more complex email validation. Defaults to:
  8309. * <tt>/[a-z0-9_\.\-@]/i</tt>
  8310. * @type RegExp
  8311. */
  8312. 'emailMask' : /[a-z0-9_\.\-@]/i,
  8313. /**
  8314. * The function used to validate URLs
  8315. * @param {String} value The URL
  8316. * @return {Boolean} true if the RegExp test passed, and false if not.
  8317. */
  8318. 'url' : function(v){
  8319. return url.test(v);
  8320. },
  8321. /**
  8322. * The error text to display when the url validation function returns false. Defaults to:
  8323. * <tt>'This field should be a URL in the format "http:/'+'/www.example.com"'</tt>
  8324. * @type String
  8325. */
  8326. 'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
  8327. /**
  8328. * The function used to validate alpha values
  8329. * @param {String} value The value
  8330. * @return {Boolean} true if the RegExp test passed, and false if not.
  8331. */
  8332. 'alpha' : function(v){
  8333. return alpha.test(v);
  8334. },
  8335. /**
  8336. * The error text to display when the alpha validation function returns false. Defaults to:
  8337. * <tt>'This field should only contain letters and _'</tt>
  8338. * @type String
  8339. */
  8340. 'alphaText' : 'This field should only contain letters and _',
  8341. /**
  8342. * The keystroke filter mask to be applied on alpha input. Defaults to:
  8343. * <tt>/[a-z_]/i</tt>
  8344. * @type RegExp
  8345. */
  8346. 'alphaMask' : /[a-z_]/i,
  8347. /**
  8348. * The function used to validate alphanumeric values
  8349. * @param {String} value The value
  8350. * @return {Boolean} true if the RegExp test passed, and false if not.
  8351. */
  8352. 'alphanum' : function(v){
  8353. return alphanum.test(v);
  8354. },
  8355. /**
  8356. * The error text to display when the alphanumeric validation function returns false. Defaults to:
  8357. * <tt>'This field should only contain letters, numbers and _'</tt>
  8358. * @type String
  8359. */
  8360. 'alphanumText' : 'This field should only contain letters, numbers and _',
  8361. /**
  8362. * The keystroke filter mask to be applied on alphanumeric input. Defaults to:
  8363. * <tt>/[a-z0-9_]/i</tt>
  8364. * @type RegExp
  8365. */
  8366. 'alphanumMask' : /[a-z0-9_]/i
  8367. };
  8368. }();