/ext-4.0.7/pkgs/classes.js

https://bitbucket.org/srogerf/javascript · JavaScript · 29252 lines · 13512 code · 3108 blank · 12632 comment · 2962 complexity · a081e388c7e0f979768b033e444b0e49 MD5 · raw file

  1. /*
  2. This file is part of Ext JS 4
  3. Copyright (c) 2011 Sencha Inc
  4. Contact: http://www.sencha.com/contact
  5. GNU General Public License Usage
  6. This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
  7. If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
  8. */
  9. /**
  10. * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
  11. * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
  12. *
  13. * For example:
  14. *
  15. * Ext.define('Employee', {
  16. * extend: 'Ext.util.Observable',
  17. * constructor: function(config){
  18. * this.name = config.name;
  19. * this.addEvents({
  20. * "fired" : true,
  21. * "quit" : true
  22. * });
  23. *
  24. * // Copy configured listeners into *this* object so that the base class's
  25. * // constructor will add them.
  26. * this.listeners = config.listeners;
  27. *
  28. * // Call our superclass constructor to complete construction process.
  29. * this.callParent(arguments)
  30. * }
  31. * });
  32. *
  33. * This could then be used like this:
  34. *
  35. * var newEmployee = new Employee({
  36. * name: employeeName,
  37. * listeners: {
  38. * quit: function() {
  39. * // By default, "this" will be the object that fired the event.
  40. * alert(this.name + " has quit!");
  41. * }
  42. * }
  43. * });
  44. */
  45. Ext.define('Ext.util.Observable', {
  46. /* Begin Definitions */
  47. requires: ['Ext.util.Event'],
  48. statics: {
  49. /**
  50. * Removes **all** added captures from the Observable.
  51. *
  52. * @param {Ext.util.Observable} o The Observable to release
  53. * @static
  54. */
  55. releaseCapture: function(o) {
  56. o.fireEvent = this.prototype.fireEvent;
  57. },
  58. /**
  59. * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
  60. * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
  61. * the event will not fire.
  62. *
  63. * @param {Ext.util.Observable} o The Observable to capture events from.
  64. * @param {Function} fn The function to call when an event is fired.
  65. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
  66. * the Observable firing the event.
  67. * @static
  68. */
  69. capture: function(o, fn, scope) {
  70. o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
  71. },
  72. /**
  73. * Sets observability on the passed class constructor.
  74. *
  75. * This makes any event fired on any instance of the passed class also fire a single event through
  76. * the **class** allowing for central handling of events on many instances at once.
  77. *
  78. * Usage:
  79. *
  80. * Ext.util.Observable.observe(Ext.data.Connection);
  81. * Ext.data.Connection.on('beforerequest', function(con, options) {
  82. * console.log('Ajax request made to ' + options.url);
  83. * });
  84. *
  85. * @param {Function} c The class constructor to make observable.
  86. * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
  87. * @static
  88. */
  89. observe: function(cls, listeners) {
  90. if (cls) {
  91. if (!cls.isObservable) {
  92. Ext.applyIf(cls, new this());
  93. this.capture(cls.prototype, cls.fireEvent, cls);
  94. }
  95. if (Ext.isObject(listeners)) {
  96. cls.on(listeners);
  97. }
  98. return cls;
  99. }
  100. }
  101. },
  102. /* End Definitions */
  103. /**
  104. * @cfg {Object} listeners
  105. *
  106. * A config object containing one or more event handlers to be added to this object during initialization. This
  107. * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
  108. * handlers at once.
  109. *
  110. * **DOM events from Ext JS {@link Ext.Component Components}**
  111. *
  112. * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
  113. * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
  114. * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
  115. * child element of a Component, we need to specify the `element` option to identify the Component property to add a
  116. * DOM listener to:
  117. *
  118. * new Ext.panel.Panel({
  119. * width: 400,
  120. * height: 200,
  121. * dockedItems: [{
  122. * xtype: 'toolbar'
  123. * }],
  124. * listeners: {
  125. * click: {
  126. * element: 'el', //bind to the underlying el property on the panel
  127. * fn: function(){ console.log('click el'); }
  128. * },
  129. * dblclick: {
  130. * element: 'body', //bind to the underlying body property on the panel
  131. * fn: function(){ console.log('dblclick body'); }
  132. * }
  133. * }
  134. * });
  135. */
  136. // @private
  137. isObservable: true,
  138. constructor: function(config) {
  139. var me = this;
  140. Ext.apply(me, config);
  141. if (me.listeners) {
  142. me.on(me.listeners);
  143. delete me.listeners;
  144. }
  145. me.events = me.events || {};
  146. if (me.bubbleEvents) {
  147. me.enableBubble(me.bubbleEvents);
  148. }
  149. },
  150. // @private
  151. eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
  152. /**
  153. * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
  154. * destroyed.
  155. *
  156. * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
  157. * @param {Object/String} ename The event name, or an object containing event name properties.
  158. * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
  159. * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
  160. * in which the handler function is executed.
  161. * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
  162. * {@link Ext.util.Observable#addListener addListener} options.
  163. */
  164. addManagedListener : function(item, ename, fn, scope, options) {
  165. var me = this,
  166. managedListeners = me.managedListeners = me.managedListeners || [],
  167. config;
  168. if (typeof ename !== 'string') {
  169. options = ename;
  170. for (ename in options) {
  171. if (options.hasOwnProperty(ename)) {
  172. config = options[ename];
  173. if (!me.eventOptionsRe.test(ename)) {
  174. me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
  175. }
  176. }
  177. }
  178. }
  179. else {
  180. managedListeners.push({
  181. item: item,
  182. ename: ename,
  183. fn: fn,
  184. scope: scope,
  185. options: options
  186. });
  187. item.on(ename, fn, scope, options);
  188. }
  189. },
  190. /**
  191. * Removes listeners that were added by the {@link #mon} method.
  192. *
  193. * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
  194. * @param {Object/String} ename The event name, or an object containing event name properties.
  195. * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
  196. * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
  197. * in which the handler function is executed.
  198. */
  199. removeManagedListener : function(item, ename, fn, scope) {
  200. var me = this,
  201. options,
  202. config,
  203. managedListeners,
  204. length,
  205. i;
  206. if (typeof ename !== 'string') {
  207. options = ename;
  208. for (ename in options) {
  209. if (options.hasOwnProperty(ename)) {
  210. config = options[ename];
  211. if (!me.eventOptionsRe.test(ename)) {
  212. me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
  213. }
  214. }
  215. }
  216. }
  217. managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
  218. for (i = 0, length = managedListeners.length; i < length; i++) {
  219. me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
  220. }
  221. },
  222. /**
  223. * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
  224. * to {@link #addListener}).
  225. *
  226. * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
  227. * calling {@link #enableBubble}.
  228. *
  229. * @param {String} eventName The name of the event to fire.
  230. * @param {Object...} args Variable number of parameters are passed to handlers.
  231. * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
  232. */
  233. fireEvent: function(eventName) {
  234. var name = eventName.toLowerCase(),
  235. events = this.events,
  236. event = events && events[name],
  237. bubbles = event && event.bubble;
  238. return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
  239. },
  240. /**
  241. * Continue to fire event.
  242. * @private
  243. *
  244. * @param {String} eventName
  245. * @param {Array} args
  246. * @param {Boolean} bubbles
  247. */
  248. continueFireEvent: function(eventName, args, bubbles) {
  249. var target = this,
  250. queue, event,
  251. ret = true;
  252. do {
  253. if (target.eventsSuspended === true) {
  254. if ((queue = target.eventQueue)) {
  255. queue.push([eventName, args, bubbles]);
  256. }
  257. return ret;
  258. } else {
  259. event = target.events[eventName];
  260. // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
  261. // configure to bubble.
  262. if (event && event != true) {
  263. if ((ret = event.fire.apply(event, args)) === false) {
  264. break;
  265. }
  266. }
  267. }
  268. } while (bubbles && (target = target.getBubbleParent()));
  269. return ret;
  270. },
  271. /**
  272. * Gets the bubbling parent for an Observable
  273. * @private
  274. * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
  275. */
  276. getBubbleParent: function(){
  277. var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
  278. if (parent && parent.isObservable) {
  279. return parent;
  280. }
  281. return null;
  282. },
  283. /**
  284. * Appends an event handler to this object.
  285. *
  286. * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
  287. * event names.
  288. * @param {Function} fn The method the event invokes. Will be called with arguments given to
  289. * {@link #fireEvent} plus the `options` parameter described below.
  290. * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
  291. * omitted, defaults to the object which fired the event.**
  292. * @param {Object} [options] An object containing handler configuration.
  293. *
  294. * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
  295. *
  296. * This object may contain any of the following properties:
  297. *
  298. * - **scope** : Object
  299. *
  300. * The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
  301. * which fired the event.**
  302. *
  303. * - **delay** : Number
  304. *
  305. * The number of milliseconds to delay the invocation of the handler after the event fires.
  306. *
  307. * - **single** : Boolean
  308. *
  309. * True to add a handler to handle just the next firing of the event, and then remove itself.
  310. *
  311. * - **buffer** : Number
  312. *
  313. * Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
  314. * milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
  315. * handler is scheduled in its place.
  316. *
  317. * - **target** : Observable
  318. *
  319. * Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
  320. * child Observable.
  321. *
  322. * - **element** : String
  323. *
  324. * **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
  325. * property which references an element to add a listener to.
  326. *
  327. * This option is useful during Component construction to add DOM event listeners to elements of
  328. * {@link Ext.Component Components} which will exist only after the Component is rendered.
  329. * For example, to add a click listener to a Panel's body:
  330. *
  331. * new Ext.panel.Panel({
  332. * title: 'The title',
  333. * listeners: {
  334. * click: this.handlePanelClick,
  335. * element: 'body'
  336. * }
  337. * });
  338. *
  339. * **Combining Options**
  340. *
  341. * Using the options argument, it is possible to combine different types of listeners:
  342. *
  343. * A delayed, one-time listener.
  344. *
  345. * myPanel.on('hide', this.handleClick, this, {
  346. * single: true,
  347. * delay: 100
  348. * });
  349. *
  350. * **Attaching multiple handlers in 1 call**
  351. *
  352. * The method also allows for a single argument to be passed which is a config object containing properties which
  353. * specify multiple events. For example:
  354. *
  355. * myGridPanel.on({
  356. * cellClick: this.onCellClick,
  357. * mouseover: this.onMouseOver,
  358. * mouseout: this.onMouseOut,
  359. * scope: this // Important. Ensure "this" is correct during handler execution
  360. * });
  361. *
  362. * One can also specify options for each event handler separately:
  363. *
  364. * myGridPanel.on({
  365. * cellClick: {fn: this.onCellClick, scope: this, single: true},
  366. * mouseover: {fn: panel.onMouseOver, scope: panel}
  367. * });
  368. *
  369. */
  370. addListener: function(ename, fn, scope, options) {
  371. var me = this,
  372. config,
  373. event;
  374. if (typeof ename !== 'string') {
  375. options = ename;
  376. for (ename in options) {
  377. if (options.hasOwnProperty(ename)) {
  378. config = options[ename];
  379. if (!me.eventOptionsRe.test(ename)) {
  380. me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
  381. }
  382. }
  383. }
  384. }
  385. else {
  386. ename = ename.toLowerCase();
  387. me.events[ename] = me.events[ename] || true;
  388. event = me.events[ename] || true;
  389. if (Ext.isBoolean(event)) {
  390. me.events[ename] = event = new Ext.util.Event(me, ename);
  391. }
  392. event.addListener(fn, scope, Ext.isObject(options) ? options : {});
  393. }
  394. },
  395. /**
  396. * Removes an event handler.
  397. *
  398. * @param {String} eventName The type of event the handler was associated with.
  399. * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
  400. * {@link #addListener} call.**
  401. * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
  402. * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
  403. */
  404. removeListener: function(ename, fn, scope) {
  405. var me = this,
  406. config,
  407. event,
  408. options;
  409. if (typeof ename !== 'string') {
  410. options = ename;
  411. for (ename in options) {
  412. if (options.hasOwnProperty(ename)) {
  413. config = options[ename];
  414. if (!me.eventOptionsRe.test(ename)) {
  415. me.removeListener(ename, config.fn || config, config.scope || options.scope);
  416. }
  417. }
  418. }
  419. } else {
  420. ename = ename.toLowerCase();
  421. event = me.events[ename];
  422. if (event && event.isEvent) {
  423. event.removeListener(fn, scope);
  424. }
  425. }
  426. },
  427. /**
  428. * Removes all listeners for this object including the managed listeners
  429. */
  430. clearListeners: function() {
  431. var events = this.events,
  432. event,
  433. key;
  434. for (key in events) {
  435. if (events.hasOwnProperty(key)) {
  436. event = events[key];
  437. if (event.isEvent) {
  438. event.clearListeners();
  439. }
  440. }
  441. }
  442. this.clearManagedListeners();
  443. },
  444. //<debug>
  445. purgeListeners : function() {
  446. if (Ext.global.console) {
  447. Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
  448. }
  449. return this.clearListeners.apply(this, arguments);
  450. },
  451. //</debug>
  452. /**
  453. * Removes all managed listeners for this object.
  454. */
  455. clearManagedListeners : function() {
  456. var managedListeners = this.managedListeners || [],
  457. i = 0,
  458. len = managedListeners.length;
  459. for (; i < len; i++) {
  460. this.removeManagedListenerItem(true, managedListeners[i]);
  461. }
  462. this.managedListeners = [];
  463. },
  464. /**
  465. * Remove a single managed listener item
  466. * @private
  467. * @param {Boolean} isClear True if this is being called during a clear
  468. * @param {Object} managedListener The managed listener item
  469. * See removeManagedListener for other args
  470. */
  471. removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
  472. if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
  473. managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
  474. if (!isClear) {
  475. Ext.Array.remove(this.managedListeners, managedListener);
  476. }
  477. }
  478. },
  479. //<debug>
  480. purgeManagedListeners : function() {
  481. if (Ext.global.console) {
  482. Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
  483. }
  484. return this.clearManagedListeners.apply(this, arguments);
  485. },
  486. //</debug>
  487. /**
  488. * Adds the specified events to the list of events which this Observable may fire.
  489. *
  490. * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
  491. * event name string if multiple event names are being passed as separate parameters. Usage:
  492. *
  493. * this.addEvents({
  494. * storeloaded: true,
  495. * storecleared: true
  496. * });
  497. *
  498. * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
  499. * parameters. Usage:
  500. *
  501. * this.addEvents('storeloaded', 'storecleared');
  502. *
  503. */
  504. addEvents: function(o) {
  505. var me = this,
  506. args,
  507. len,
  508. i;
  509. me.events = me.events || {};
  510. if (Ext.isString(o)) {
  511. args = arguments;
  512. i = args.length;
  513. while (i--) {
  514. me.events[args[i]] = me.events[args[i]] || true;
  515. }
  516. } else {
  517. Ext.applyIf(me.events, o);
  518. }
  519. },
  520. /**
  521. * Checks to see if this object has any listeners for a specified event
  522. *
  523. * @param {String} eventName The name of the event to check for
  524. * @return {Boolean} True if the event is being listened for, else false
  525. */
  526. hasListener: function(ename) {
  527. var event = this.events[ename.toLowerCase()];
  528. return event && event.isEvent === true && event.listeners.length > 0;
  529. },
  530. /**
  531. * Suspends the firing of all events. (see {@link #resumeEvents})
  532. *
  533. * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
  534. * after the {@link #resumeEvents} call instead of discarding all suspended events.
  535. */
  536. suspendEvents: function(queueSuspended) {
  537. this.eventsSuspended = true;
  538. if (queueSuspended && !this.eventQueue) {
  539. this.eventQueue = [];
  540. }
  541. },
  542. /**
  543. * Resumes firing events (see {@link #suspendEvents}).
  544. *
  545. * If events were suspended using the `queueSuspended` parameter, then all events fired
  546. * during event suspension will be sent to any listeners now.
  547. */
  548. resumeEvents: function() {
  549. var me = this,
  550. queued = me.eventQueue;
  551. me.eventsSuspended = false;
  552. delete me.eventQueue;
  553. if (queued) {
  554. Ext.each(queued, function(e) {
  555. me.continueFireEvent.apply(me, e);
  556. });
  557. }
  558. },
  559. /**
  560. * Relays selected events from the specified Observable as if the events were fired by `this`.
  561. *
  562. * @param {Object} origin The Observable whose events this object is to relay.
  563. * @param {String[]} events Array of event names to relay.
  564. * @param {String} prefix
  565. */
  566. relayEvents : function(origin, events, prefix) {
  567. prefix = prefix || '';
  568. var me = this,
  569. len = events.length,
  570. i = 0,
  571. oldName,
  572. newName;
  573. for (; i < len; i++) {
  574. oldName = events[i].substr(prefix.length);
  575. newName = prefix + oldName;
  576. me.events[newName] = me.events[newName] || true;
  577. origin.on(oldName, me.createRelayer(newName));
  578. }
  579. },
  580. /**
  581. * @private
  582. * Creates an event handling function which refires the event from this object as the passed event name.
  583. * @param newName
  584. * @returns {Function}
  585. */
  586. createRelayer: function(newName){
  587. var me = this;
  588. return function(){
  589. return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
  590. };
  591. },
  592. /**
  593. * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
  594. * present. There is no implementation in the Observable base class.
  595. *
  596. * This is commonly used by Ext.Components to bubble events to owner Containers.
  597. * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
  598. * Component's immediate owner. But if a known target is required, this can be overridden to access the
  599. * required target more quickly.
  600. *
  601. * Example:
  602. *
  603. * Ext.override(Ext.form.field.Base, {
  604. * // Add functionality to Field's initComponent to enable the change event to bubble
  605. * initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
  606. * this.enableBubble('change');
  607. * }),
  608. *
  609. * // We know that we want Field's events to bubble directly to the FormPanel.
  610. * getBubbleTarget : function() {
  611. * if (!this.formPanel) {
  612. * this.formPanel = this.findParentByType('form');
  613. * }
  614. * return this.formPanel;
  615. * }
  616. * });
  617. *
  618. * var myForm = new Ext.formPanel({
  619. * title: 'User Details',
  620. * items: [{
  621. * ...
  622. * }],
  623. * listeners: {
  624. * change: function() {
  625. * // Title goes red if form has been modified.
  626. * myForm.header.setStyle('color', 'red');
  627. * }
  628. * }
  629. * });
  630. *
  631. * @param {String/String[]} events The event name to bubble, or an Array of event names.
  632. */
  633. enableBubble: function(events) {
  634. var me = this;
  635. if (!Ext.isEmpty(events)) {
  636. events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
  637. Ext.each(events,
  638. function(ename) {
  639. ename = ename.toLowerCase();
  640. var ce = me.events[ename] || true;
  641. if (Ext.isBoolean(ce)) {
  642. ce = new Ext.util.Event(me, ename);
  643. me.events[ename] = ce;
  644. }
  645. ce.bubble = true;
  646. });
  647. }
  648. }
  649. }, function() {
  650. this.createAlias({
  651. /**
  652. * @method
  653. * Shorthand for {@link #addListener}.
  654. * @alias Ext.util.Observable#addListener
  655. */
  656. on: 'addListener',
  657. /**
  658. * @method
  659. * Shorthand for {@link #removeListener}.
  660. * @alias Ext.util.Observable#removeListener
  661. */
  662. un: 'removeListener',
  663. /**
  664. * @method
  665. * Shorthand for {@link #addManagedListener}.
  666. * @alias Ext.util.Observable#addManagedListener
  667. */
  668. mon: 'addManagedListener',
  669. /**
  670. * @method
  671. * Shorthand for {@link #removeManagedListener}.
  672. * @alias Ext.util.Observable#removeManagedListener
  673. */
  674. mun: 'removeManagedListener'
  675. });
  676. //deprecated, will be removed in 5.0
  677. this.observeClass = this.observe;
  678. Ext.apply(Ext.util.Observable.prototype, function(){
  679. // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
  680. // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
  681. // private
  682. function getMethodEvent(method){
  683. var e = (this.methodEvents = this.methodEvents || {})[method],
  684. returnValue,
  685. v,
  686. cancel,
  687. obj = this;
  688. if (!e) {
  689. this.methodEvents[method] = e = {};
  690. e.originalFn = this[method];
  691. e.methodName = method;
  692. e.before = [];
  693. e.after = [];
  694. var makeCall = function(fn, scope, args){
  695. if((v = fn.apply(scope || obj, args)) !== undefined){
  696. if (typeof v == 'object') {
  697. if(v.returnValue !== undefined){
  698. returnValue = v.returnValue;
  699. }else{
  700. returnValue = v;
  701. }
  702. cancel = !!v.cancel;
  703. }
  704. else
  705. if (v === false) {
  706. cancel = true;
  707. }
  708. else {
  709. returnValue = v;
  710. }
  711. }
  712. };
  713. this[method] = function(){
  714. var args = Array.prototype.slice.call(arguments, 0),
  715. b, i, len;
  716. returnValue = v = undefined;
  717. cancel = false;
  718. for(i = 0, len = e.before.length; i < len; i++){
  719. b = e.before[i];
  720. makeCall(b.fn, b.scope, args);
  721. if (cancel) {
  722. return returnValue;
  723. }
  724. }
  725. if((v = e.originalFn.apply(obj, args)) !== undefined){
  726. returnValue = v;
  727. }
  728. for(i = 0, len = e.after.length; i < len; i++){
  729. b = e.after[i];
  730. makeCall(b.fn, b.scope, args);
  731. if (cancel) {
  732. return returnValue;
  733. }
  734. }
  735. return returnValue;
  736. };
  737. }
  738. return e;
  739. }
  740. return {
  741. // these are considered experimental
  742. // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
  743. // adds an 'interceptor' called before the original method
  744. beforeMethod : function(method, fn, scope){
  745. getMethodEvent.call(this, method).before.push({
  746. fn: fn,
  747. scope: scope
  748. });
  749. },
  750. // adds a 'sequence' called after the original method
  751. afterMethod : function(method, fn, scope){
  752. getMethodEvent.call(this, method).after.push({
  753. fn: fn,
  754. scope: scope
  755. });
  756. },
  757. removeMethodListener: function(method, fn, scope){
  758. var e = this.getMethodEvent(method),
  759. i, len;
  760. for(i = 0, len = e.before.length; i < len; i++){
  761. if(e.before[i].fn == fn && e.before[i].scope == scope){
  762. Ext.Array.erase(e.before, i, 1);
  763. return;
  764. }
  765. }
  766. for(i = 0, len = e.after.length; i < len; i++){
  767. if(e.after[i].fn == fn && e.after[i].scope == scope){
  768. Ext.Array.erase(e.after, i, 1);
  769. return;
  770. }
  771. }
  772. },
  773. toggleEventLogging: function(toggle) {
  774. Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
  775. if (Ext.isDefined(Ext.global.console)) {
  776. Ext.global.console.log(en, arguments);
  777. }
  778. });
  779. }
  780. };
  781. }());
  782. });
  783. /**
  784. * @class Ext.util.Animate
  785. * This animation class is a mixin.
  786. *
  787. * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.
  788. * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement},
  789. * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}. Note that Components
  790. * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and
  791. * opacity (color, paddings, and margins can not be animated).
  792. *
  793. * ## Animation Basics
  794. *
  795. * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property)
  796. * you wish to animate. Easing and duration are defaulted values specified below.
  797. * Easing describes how the intermediate values used during a transition will be calculated.
  798. * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
  799. * You may use the defaults for easing and duration, but you must always set a
  800. * {@link Ext.fx.Anim#to to} property which is the end value for all animations.
  801. *
  802. * Popular element 'to' configurations are:
  803. *
  804. * - opacity
  805. * - x
  806. * - y
  807. * - color
  808. * - height
  809. * - width
  810. *
  811. * Popular sprite 'to' configurations are:
  812. *
  813. * - translation
  814. * - path
  815. * - scale
  816. * - stroke
  817. * - rotation
  818. *
  819. * The default duration for animations is 250 (which is a 1/4 of a second). Duration is denoted in
  820. * milliseconds. Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve
  821. * used for all animations is 'ease'. Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
  822. *
  823. * For example, a simple animation to fade out an element with a default easing and duration:
  824. *
  825. * var p1 = Ext.get('myElementId');
  826. *
  827. * p1.animate({
  828. * to: {
  829. * opacity: 0
  830. * }
  831. * });
  832. *
  833. * To make this animation fade out in a tenth of a second:
  834. *
  835. * var p1 = Ext.get('myElementId');
  836. *
  837. * p1.animate({
  838. * duration: 100,
  839. * to: {
  840. * opacity: 0
  841. * }
  842. * });
  843. *
  844. * ## Animation Queues
  845. *
  846. * By default all animations are added to a queue which allows for animation via a chain-style API.
  847. * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
  848. *
  849. * p1.animate({
  850. * to: {
  851. * x: 500
  852. * }
  853. * }).animate({
  854. * to: {
  855. * y: 150
  856. * }
  857. * }).animate({
  858. * to: {
  859. * backgroundColor: '#f00' //red
  860. * }
  861. * }).animate({
  862. * to: {
  863. * opacity: 0
  864. * }
  865. * });
  866. *
  867. * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all
  868. * subsequent animations for the specified target will be run concurrently (at the same time).
  869. *
  870. * p1.syncFx(); //this will make all animations run at the same time
  871. *
  872. * p1.animate({
  873. * to: {
  874. * x: 500
  875. * }
  876. * }).animate({
  877. * to: {
  878. * y: 150
  879. * }
  880. * }).animate({
  881. * to: {
  882. * backgroundColor: '#f00' //red
  883. * }
  884. * }).animate({
  885. * to: {
  886. * opacity: 0
  887. * }
  888. * });
  889. *
  890. * This works the same as:
  891. *
  892. * p1.animate({
  893. * to: {
  894. * x: 500,
  895. * y: 150,
  896. * backgroundColor: '#f00' //red
  897. * opacity: 0
  898. * }
  899. * });
  900. *
  901. * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any
  902. * currently running animations and clear any queued animations.
  903. *
  904. * ## Animation Keyframes
  905. *
  906. * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the
  907. * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites.
  908. * The previous example can be written with the following syntax:
  909. *
  910. * p1.animate({
  911. * duration: 1000, //one second total
  912. * keyframes: {
  913. * 25: { //from 0 to 250ms (25%)
  914. * x: 0
  915. * },
  916. * 50: { //from 250ms to 500ms (50%)
  917. * y: 0
  918. * },
  919. * 75: { //from 500ms to 750ms (75%)
  920. * backgroundColor: '#f00' //red
  921. * },
  922. * 100: { //from 750ms to 1sec
  923. * opacity: 0
  924. * }
  925. * }
  926. * });
  927. *
  928. * ## Animation Events
  929. *
  930. * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate},
  931. * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.
  932. * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which
  933. * fires for each keyframe in your animation.
  934. *
  935. * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
  936. *
  937. * startAnimate: function() {
  938. * var p1 = Ext.get('myElementId');
  939. * p1.animate({
  940. * duration: 100,
  941. * to: {
  942. * opacity: 0
  943. * },
  944. * listeners: {
  945. * beforeanimate: function() {
  946. * // Execute my custom method before the animation
  947. * this.myBeforeAnimateFn();
  948. * },
  949. * afteranimate: function() {
  950. * // Execute my custom method after the animation
  951. * this.myAfterAnimateFn();
  952. * },
  953. * scope: this
  954. * });
  955. * },
  956. * myBeforeAnimateFn: function() {
  957. * // My custom logic
  958. * },
  959. * myAfterAnimateFn: function() {
  960. * // My custom logic
  961. * }
  962. *
  963. * Due to the fact that animations run asynchronously, you can determine if an animation is currently
  964. * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation}
  965. * method. This method will return false if there are no active animations or return the currently
  966. * running {@link Ext.fx.Anim} instance.
  967. *
  968. * In this example, we're going to wait for the current animation to finish, then stop any other
  969. * queued animations before we fade our element's opacity to 0:
  970. *
  971. * var curAnim = p1.getActiveAnimation();
  972. * if (curAnim) {
  973. * curAnim.on('afteranimate', function() {
  974. * p1.stopAnimation();
  975. * p1.animate({
  976. * to: {
  977. * opacity: 0
  978. * }
  979. * });
  980. * });
  981. * }
  982. *
  983. * @docauthor Jamie Avins <jamie@sencha.com>
  984. */
  985. Ext.define('Ext.util.Animate', {
  986. uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
  987. /**
  988. * <p>Perform custom animation on this object.<p>
  989. * <p>This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element} class.
  990. * It performs animated transitions of certain properties of this object over a specified timeline.</p>
  991. * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
  992. * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
  993. * <p>Properties include<ul>
  994. * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
  995. * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
  996. * ths object being animated. See the sections below on Element and Component animation.<div></li>
  997. * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
  998. * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
  999. * <li><code>easing</code> <div class="sub-desc">A string value describing an easing type to modify the rate of change from the default linear to non-linear. Values may be one of:<code><ul>
  1000. * <li>ease</li>
  1001. * <li>easeIn</li>
  1002. * <li>easeOut</li>
  1003. * <li>easeInOut</li>
  1004. * <li>backIn</li>
  1005. * <li>backOut</li>
  1006. * <li>elasticIn</li>
  1007. * <li>elasticOut</li>
  1008. * <li>bounceIn</li>
  1009. * <li>bounceOut</li>
  1010. * </ul></code></div></li>
  1011. * <li><code>keyframes</code> <div class="sub-desc">This is an object which describes the state of animated properties at certain points along the timeline.
  1012. * it is an object containing properties who's names are the percentage along the timeline being described and who's values specify the animation state at that point.</div></li>
  1013. * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
  1014. * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
  1015. * </ul></p>
  1016. * <h3>Animating an {@link Ext.Element Element}</h3>
  1017. * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
  1018. * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
  1019. * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
  1020. * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
  1021. * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
  1022. * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
  1023. * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
  1024. * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
  1025. * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
  1026. * <li><code>opacity</code> <div class="sub-desc">The element's <code>opacity</code> value. This must be a value between <code>0</code> and <code>1</code>.</div></li>
  1027. * </ul>
  1028. * <p><b>Be aware than animating an Element which is being used by an Ext Component without in some way informing the Component about the changed element state
  1029. * will result in incorrect Component behaviour. This is because the Component will be using the old state of the element. To avoid this problem, it is now possible to
  1030. * directly animate certain properties of Components.</b></p>
  1031. * <h3>Animating a {@link Ext.Component Component}</h3>
  1032. * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
  1033. * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
  1034. * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
  1035. * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
  1036. * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
  1037. * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
  1038. * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
  1039. * <li><code>dynamic</code> <div class="sub-desc">Specify as true to update the Component's layout (if it is a Container) at every frame
  1040. * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
  1041. * </ul>
  1042. * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
  1043. * <pre><code>
  1044. myWindow = Ext.create('Ext.window.Window', {
  1045. title: 'Test Component animation',
  1046. width: 500,
  1047. height: 300,
  1048. layout: {
  1049. type: 'hbox',
  1050. align: 'stretch'
  1051. },
  1052. items: [{
  1053. title: 'Left: 33%',
  1054. margins: '5 0 5 5',
  1055. flex: 1
  1056. }, {
  1057. title: 'Left: 66%',
  1058. margins: '5 5 5 5',
  1059. flex: 2
  1060. }]
  1061. });
  1062. myWindow.show();
  1063. myWindow.header.el.on('click', function() {
  1064. myWindow.animate({
  1065. to: {
  1066. width: (myWindow.getWidth() == 500) ? 700 : 500,
  1067. height: (myWindow.getHeight() == 300) ? 400 : 300,
  1068. }
  1069. });
  1070. });
  1071. </code></pre>
  1072. * <p>For performance reasons, by default, the internal layout is only updated when the Window reaches its final <code>"to"</code> size. If dynamic updating of the Window's child
  1073. * Components is required, then configure the animation with <code>dynamic: true</code> and the two child items will maintain their proportions during the animation.</p>
  1074. * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
  1075. * @return {Object} this
  1076. */
  1077. animate: function(animObj) {
  1078. var me = this;
  1079. if (Ext.fx.Manager.hasFxBlock(me.id)) {
  1080. return me;
  1081. }
  1082. Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
  1083. return this;
  1084. },
  1085. // @private - process the passed fx configuration.
  1086. anim: function(config) {
  1087. if (!Ext.isObject(config)) {
  1088. return (config) ? {} : false;
  1089. }
  1090. var me = this;
  1091. if (config.stopAnimation) {
  1092. me.stopAnimation();
  1093. }
  1094. Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
  1095. return Ext.apply({
  1096. target: me,
  1097. paused: true
  1098. }, config);
  1099. },
  1100. /**
  1101. * @deprecated 4.0 Replaced by {@link #stopAnimation}
  1102. * Stops any running effects and clears this object's internal effects queue if it contains
  1103. * any additional effects that haven't started yet.
  1104. * @return {Ext.Element} The Element
  1105. * @method
  1106. */
  1107. stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
  1108. /**
  1109. * Stops any running effects and clears this object's internal effects queue if it contains
  1110. * any additional effects that haven't started yet.
  1111. * @return {Ext.Element} The Element
  1112. */
  1113. stopAnimation: function() {
  1114. Ext.fx.Manager.stopAnimation(this.id);
  1115. return this;
  1116. },
  1117. /**
  1118. * Ensures that all effects queued after syncFx is called on this object are
  1119. * run concurrently. This is the opposite of {@link #sequenceFx}.
  1120. * @return {Object} this
  1121. */
  1122. syncFx: function() {
  1123. Ext.fx.Manager.setFxDefaults(this.id, {
  1124. concurrent: true
  1125. });
  1126. return this;
  1127. },
  1128. /**
  1129. * Ensures that all effects queued after sequenceFx is called on this object are
  1130. * run in sequence. This is the opposite of {@link #syncFx}.
  1131. * @return {Object} this
  1132. */
  1133. sequenceFx: function() {
  1134. Ext.fx.Manager.setFxDefaults(this.id, {
  1135. concurrent: false
  1136. });
  1137. return this;
  1138. },
  1139. /**
  1140. * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
  1141. * @alias Ext.util.Animate#getActiveAnimation
  1142. * @method
  1143. */
  1144. hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
  1145. /**
  1146. * Returns the current animation if this object has any effects actively running or queued, else returns false.
  1147. * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
  1148. */
  1149. getActiveAnimation: function() {
  1150. return Ext.fx.Manager.getActiveAnimation(this.id);
  1151. }
  1152. }, function(){
  1153. // Apply Animate mixin manually until Element is defined in the proper 4.x way
  1154. Ext.applyIf(Ext.Element.prototype, this.prototype);
  1155. // We need to call this again so the animation methods get copied over to CE
  1156. Ext.CompositeElementLite.importElementMethods();
  1157. });
  1158. /**
  1159. * @class Ext.state.Provider
  1160. * <p>Abstract base class for state provider implementations. The provider is responsible
  1161. * for setting values and extracting values to/from the underlying storage source. The
  1162. * storage source can vary and the details should be implemented in a subclass. For example
  1163. * a provider could use a server side database or the browser localstorage where supported.</p>
  1164. *
  1165. * <p>This class provides methods for encoding and decoding <b>typed</b> variables including
  1166. * dates and defines the Provider interface. By default these methods put the value and the
  1167. * type information into a delimited string that can be stored. These should be overridden in
  1168. * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
  1169. */
  1170. Ext.define('Ext.state.Provider', {
  1171. mixins: {
  1172. observable: 'Ext.util.Observable'
  1173. },
  1174. /**
  1175. * @cfg {String} prefix A string to prefix to items stored in the underlying state store.
  1176. * Defaults to <tt>'ext-'</tt>
  1177. */
  1178. prefix: 'ext-',
  1179. constructor : function(config){
  1180. config = config || {};
  1181. var me = this;
  1182. Ext.apply(me, config);
  1183. /**
  1184. * @event statechange
  1185. * Fires when a state change occurs.
  1186. * @param {Ext.state.Provider} this This state provider
  1187. * @param {String} key The state key which was changed
  1188. * @param {String} value The encoded value for the state
  1189. */
  1190. me.addEvents("statechange");
  1191. me.state = {};
  1192. me.mixins.observable.constructor.call(me);
  1193. },
  1194. /**
  1195. * Returns the current value for a key
  1196. * @param {String} name The key name
  1197. * @param {Object} defaultValue A default value to return if the key's value is not found
  1198. * @return {Object} The state data
  1199. */
  1200. get : function(name, defaultValue){
  1201. return typeof this.state[name] == "undefined" ?
  1202. defaultValue : this.state[name];
  1203. },
  1204. /**
  1205. * Clears a value from the state
  1206. * @param {String} name The key name
  1207. */
  1208. clear : function(name){
  1209. var me = this;
  1210. delete me.state[name];
  1211. me.fireEvent("statechange", me, name, null);
  1212. },
  1213. /**
  1214. * Sets the value for a key
  1215. * @param {String} name The key name
  1216. * @param {Object} value The value to set
  1217. */
  1218. set : function(name, value){
  1219. var me = this;
  1220. me.state[name] = value;
  1221. me.fireEvent("statechange", me, name, value);
  1222. },
  1223. /**
  1224. * Decodes a string previously encoded with {@link #encodeValue}.
  1225. * @param {String} value The value to decode
  1226. * @return {Object} The decoded value
  1227. */
  1228. decodeValue : function(value){
  1229. // a -> Array
  1230. // n -> Number
  1231. // d -> Date
  1232. // b -> Boolean
  1233. // s -> String
  1234. // o -> Object
  1235. // -> Empty (null)
  1236. var me = this,
  1237. re = /^(a|n|d|b|s|o|e)\:(.*)$/,
  1238. matches = re.exec(unescape(value)),
  1239. all,
  1240. type,
  1241. value,
  1242. keyValue;
  1243. if(!matches || !matches[1]){
  1244. return; // non state
  1245. }
  1246. type = matches[1];
  1247. value = matches[2];
  1248. switch (type) {
  1249. case 'e':
  1250. return null;
  1251. case 'n':
  1252. return parseFloat(value);
  1253. case 'd':
  1254. return new Date(Date.parse(value));
  1255. case 'b':
  1256. return (value == '1');
  1257. case 'a':
  1258. all = [];
  1259. if(value != ''){
  1260. Ext.each(value.split('^'), function(val){
  1261. all.push(me.decodeValue(val));
  1262. }, me);
  1263. }
  1264. return all;
  1265. case 'o':
  1266. all = {};
  1267. if(value != ''){
  1268. Ext.each(value.split('^'), function(val){
  1269. keyValue = val.split('=');
  1270. all[keyValue[0]] = me.decodeValue(keyValue[1]);
  1271. }, me);
  1272. }
  1273. return all;
  1274. default:
  1275. return value;
  1276. }
  1277. },
  1278. /**
  1279. * Encodes a value including type information. Decode with {@link #decodeValue}.
  1280. * @param {Object} value The value to encode
  1281. * @return {String} The encoded value
  1282. */
  1283. encodeValue : function(value){
  1284. var flat = '',
  1285. i = 0,
  1286. enc,
  1287. len,
  1288. key;
  1289. if (value == null) {
  1290. return 'e:1';
  1291. } else if(typeof value == 'number') {
  1292. enc = 'n:' + value;
  1293. } else if(typeof value == 'boolean') {
  1294. enc = 'b:' + (value ? '1' : '0');
  1295. } else if(Ext.isDate(value)) {
  1296. enc = 'd:' + value.toGMTString();
  1297. } else if(Ext.isArray(value)) {
  1298. for (len = value.length; i < len; i++) {
  1299. flat += this.encodeValue(value[i]);
  1300. if (i != len - 1) {
  1301. flat += '^';
  1302. }
  1303. }
  1304. enc = 'a:' + flat;
  1305. } else if (typeof value == 'object') {
  1306. for (key in value) {
  1307. if (typeof value[key] != 'function' && value[key] !== undefined) {
  1308. flat += key + '=' + this.encodeValue(value[key]) + '^';
  1309. }
  1310. }
  1311. enc = 'o:' + flat.substring(0, flat.length-1);
  1312. } else {
  1313. enc = 's:' + value;
  1314. }
  1315. return escape(enc);
  1316. }
  1317. });
  1318. /**
  1319. * Provides searching of Components within Ext.ComponentManager (globally) or a specific
  1320. * Ext.container.Container on the document with a similar syntax to a CSS selector.
  1321. *
  1322. * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
  1323. *
  1324. * - `component` or `.component`
  1325. * - `gridpanel` or `.gridpanel`
  1326. *
  1327. * An itemId or id must be prefixed with a #
  1328. *
  1329. * - `#myContainer`
  1330. *
  1331. * Attributes must be wrapped in brackets
  1332. *
  1333. * - `component[autoScroll]`
  1334. * - `panel[title="Test"]`
  1335. *
  1336. * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
  1337. * the candidate Component will be included in the query:
  1338. *
  1339. * var disabledFields = myFormPanel.query("{isDisabled()}");
  1340. *
  1341. * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
  1342. *
  1343. * // Function receives array and returns a filtered array.
  1344. * Ext.ComponentQuery.pseudos.invalid = function(items) {
  1345. * var i = 0, l = items.length, c, result = [];
  1346. * for (; i < l; i++) {
  1347. * if (!(c = items[i]).isValid()) {
  1348. * result.push(c);
  1349. * }
  1350. * }
  1351. * return result;
  1352. * };
  1353. *
  1354. * var invalidFields = myFormPanel.query('field:invalid');
  1355. * if (invalidFields.length) {
  1356. * invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
  1357. * for (var i = 0, l = invalidFields.length; i < l; i++) {
  1358. * invalidFields[i].getEl().frame("red");
  1359. * }
  1360. * }
  1361. *
  1362. * Default pseudos include:
  1363. *
  1364. * - not
  1365. * - last
  1366. *
  1367. * Queries return an array of components.
  1368. * Here are some example queries.
  1369. *
  1370. * // retrieve all Ext.Panels in the document by xtype
  1371. * var panelsArray = Ext.ComponentQuery.query('panel');
  1372. *
  1373. * // retrieve all Ext.Panels within the container with an id myCt
  1374. * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
  1375. *
  1376. * // retrieve all direct children which are Ext.Panels within myCt
  1377. * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
  1378. *
  1379. * // retrieve all grids and trees
  1380. * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
  1381. *
  1382. * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
  1383. * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
  1384. * {@link Ext.Component#up}.
  1385. */
  1386. Ext.define('Ext.ComponentQuery', {
  1387. singleton: true,
  1388. uses: ['Ext.ComponentManager']
  1389. }, function() {
  1390. var cq = this,
  1391. // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
  1392. // as a member on each item in the passed array.
  1393. filterFnPattern = [
  1394. 'var r = [],',
  1395. 'i = 0,',
  1396. 'it = items,',
  1397. 'l = it.length,',
  1398. 'c;',
  1399. 'for (; i < l; i++) {',
  1400. 'c = it[i];',
  1401. 'if (c.{0}) {',
  1402. 'r.push(c);',
  1403. '}',
  1404. '}',
  1405. 'return r;'
  1406. ].join(''),
  1407. filterItems = function(items, operation) {
  1408. // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
  1409. // The operation's method loops over each item in the candidate array and
  1410. // returns an array of items which match its criteria
  1411. return operation.method.apply(this, [ items ].concat(operation.args));
  1412. },
  1413. getItems = function(items, mode) {
  1414. var result = [],
  1415. i = 0,
  1416. length = items.length,
  1417. candidate,
  1418. deep = mode !== '>';
  1419. for (; i < length; i++) {
  1420. candidate = items[i];
  1421. if (candidate.getRefItems) {
  1422. result = result.concat(candidate.getRefItems(deep));
  1423. }
  1424. }
  1425. return result;
  1426. },
  1427. getAncestors = function(items) {
  1428. var result = [],
  1429. i = 0,
  1430. length = items.length,
  1431. candidate;
  1432. for (; i < length; i++) {
  1433. candidate = items[i];
  1434. while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
  1435. result.push(candidate);
  1436. }
  1437. }
  1438. return result;
  1439. },
  1440. // Filters the passed candidate array and returns only items which match the passed xtype
  1441. filterByXType = function(items, xtype, shallow) {
  1442. if (xtype === '*') {
  1443. return items.slice();
  1444. }
  1445. else {
  1446. var result = [],
  1447. i = 0,
  1448. length = items.length,
  1449. candidate;
  1450. for (; i < length; i++) {
  1451. candidate = items[i];
  1452. if (candidate.isXType(xtype, shallow)) {
  1453. result.push(candidate);
  1454. }
  1455. }
  1456. return result;
  1457. }
  1458. },
  1459. // Filters the passed candidate array and returns only items which have the passed className
  1460. filterByClassName = function(items, className) {
  1461. var EA = Ext.Array,
  1462. result = [],
  1463. i = 0,
  1464. length = items.length,
  1465. candidate;
  1466. for (; i < length; i++) {
  1467. candidate = items[i];
  1468. if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
  1469. result.push(candidate);
  1470. }
  1471. }
  1472. return result;
  1473. },
  1474. // Filters the passed candidate array and returns only items which have the specified property match
  1475. filterByAttribute = function(items, property, operator, value) {
  1476. var result = [],
  1477. i = 0,
  1478. length = items.length,
  1479. candidate;
  1480. for (; i < length; i++) {
  1481. candidate = items[i];
  1482. if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
  1483. result.push(candidate);
  1484. }
  1485. }
  1486. return result;
  1487. },
  1488. // Filters the passed candidate array and returns only items which have the specified itemId or id
  1489. filterById = function(items, id) {
  1490. var result = [],
  1491. i = 0,
  1492. length = items.length,
  1493. candidate;
  1494. for (; i < length; i++) {
  1495. candidate = items[i];
  1496. if (candidate.getItemId() === id) {
  1497. result.push(candidate);
  1498. }
  1499. }
  1500. return result;
  1501. },
  1502. // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
  1503. filterByPseudo = function(items, name, value) {
  1504. return cq.pseudos[name](items, value);
  1505. },
  1506. // Determines leading mode
  1507. // > for direct child, and ^ to switch to ownerCt axis
  1508. modeRe = /^(\s?([>\^])\s?|\s|$)/,
  1509. // Matches a token with possibly (true|false) appended for the "shallow" parameter
  1510. tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
  1511. matchers = [{
  1512. // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
  1513. re: /^\.([\w\-]+)(?:\((true|false)\))?/,
  1514. method: filterByXType
  1515. },{
  1516. // checks for [attribute=value]
  1517. re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
  1518. method: filterByAttribute
  1519. }, {
  1520. // checks for #cmpItemId
  1521. re: /^#([\w\-]+)/,
  1522. method: filterById
  1523. }, {
  1524. // checks for :<pseudo_class>(<selector>)
  1525. re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
  1526. method: filterByPseudo
  1527. }, {
  1528. // checks for {<member_expression>}
  1529. re: /^(?:\{([^\}]+)\})/,
  1530. method: filterFnPattern
  1531. }];
  1532. // @class Ext.ComponentQuery.Query
  1533. // This internal class is completely hidden in documentation.
  1534. cq.Query = Ext.extend(Object, {
  1535. constructor: function(cfg) {
  1536. cfg = cfg || {};
  1537. Ext.apply(this, cfg);
  1538. },
  1539. // Executes this Query upon the selected root.
  1540. // The root provides the initial source of candidate Component matches which are progressively
  1541. // filtered by iterating through this Query's operations cache.
  1542. // If no root is provided, all registered Components are searched via the ComponentManager.
  1543. // root may be a Container who's descendant Components are filtered
  1544. // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
  1545. // docked items within a Panel.
  1546. // root may be an array of candidate Components to filter using this Query.
  1547. execute : function(root) {
  1548. var operations = this.operations,
  1549. i = 0,
  1550. length = operations.length,
  1551. operation,
  1552. workingItems;
  1553. // no root, use all Components in the document
  1554. if (!root) {
  1555. workingItems = Ext.ComponentManager.all.getArray();
  1556. }
  1557. // Root is a candidate Array
  1558. else if (Ext.isArray(root)) {
  1559. workingItems = root;
  1560. }
  1561. // We are going to loop over our operations and take care of them
  1562. // one by one.
  1563. for (; i < length; i++) {
  1564. operation = operations[i];
  1565. // The mode operation requires some custom handling.
  1566. // All other operations essentially filter down our current
  1567. // working items, while mode replaces our current working
  1568. // items by getting children from each one of our current
  1569. // working items. The type of mode determines the type of
  1570. // children we get. (e.g. > only gets direct children)
  1571. if (operation.mode === '^') {
  1572. workingItems = getAncestors(workingItems || [root]);
  1573. }
  1574. else if (operation.mode) {
  1575. workingItems = getItems(workingItems || [root], operation.mode);
  1576. }
  1577. else {
  1578. workingItems = filterItems(workingItems || getItems([root]), operation);
  1579. }
  1580. // If this is the last operation, it means our current working
  1581. // items are the final matched items. Thus return them!
  1582. if (i === length -1) {
  1583. return workingItems;
  1584. }
  1585. }
  1586. return [];
  1587. },
  1588. is: function(component) {
  1589. var operations = this.operations,
  1590. components = Ext.isArray(component) ? component : [component],
  1591. originalLength = components.length,
  1592. lastOperation = operations[operations.length-1],
  1593. ln, i;
  1594. components = filterItems(components, lastOperation);
  1595. if (components.length === originalLength) {
  1596. if (operations.length > 1) {
  1597. for (i = 0, ln = components.length; i < ln; i++) {
  1598. if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
  1599. return false;
  1600. }
  1601. }
  1602. }
  1603. return true;
  1604. }
  1605. return false;
  1606. }
  1607. });
  1608. Ext.apply(this, {
  1609. // private cache of selectors and matching ComponentQuery.Query objects
  1610. cache: {},
  1611. // private cache of pseudo class filter functions
  1612. pseudos: {
  1613. not: function(components, selector){
  1614. var CQ = Ext.ComponentQuery,
  1615. i = 0,
  1616. length = components.length,
  1617. results = [],
  1618. index = -1,
  1619. component;
  1620. for(; i < length; ++i) {
  1621. component = components[i];
  1622. if (!CQ.is(component, selector)) {
  1623. results[++index] = component;
  1624. }
  1625. }
  1626. return results;
  1627. },
  1628. last: function(components) {
  1629. return components[components.length - 1];
  1630. }
  1631. },
  1632. /**
  1633. * Returns an array of matched Components from within the passed root object.
  1634. *
  1635. * This method filters returned Components in a similar way to how CSS selector based DOM
  1636. * queries work using a textual selector string.
  1637. *
  1638. * See class summary for details.
  1639. *
  1640. * @param {String} selector The selector string to filter returned Components
  1641. * @param {Ext.container.Container} root The Container within which to perform the query.
  1642. * If omitted, all Components within the document are included in the search.
  1643. *
  1644. * This parameter may also be an array of Components to filter according to the selector.</p>
  1645. * @returns {Ext.Component[]} The matched Components.
  1646. *
  1647. * @member Ext.ComponentQuery
  1648. */
  1649. query: function(selector, root) {
  1650. var selectors = selector.split(','),
  1651. length = selectors.length,
  1652. i = 0,
  1653. results = [],
  1654. noDupResults = [],
  1655. dupMatcher = {},
  1656. query, resultsLn, cmp;
  1657. for (; i < length; i++) {
  1658. selector = Ext.String.trim(selectors[i]);
  1659. query = this.cache[selector];
  1660. if (!query) {
  1661. this.cache[selector] = query = this.parse(selector);
  1662. }
  1663. results = results.concat(query.execute(root));
  1664. }
  1665. // multiple selectors, potential to find duplicates
  1666. // lets filter them out.
  1667. if (length > 1) {
  1668. resultsLn = results.length;
  1669. for (i = 0; i < resultsLn; i++) {
  1670. cmp = results[i];
  1671. if (!dupMatcher[cmp.id]) {
  1672. noDupResults.push(cmp);
  1673. dupMatcher[cmp.id] = true;
  1674. }
  1675. }
  1676. results = noDupResults;
  1677. }
  1678. return results;
  1679. },
  1680. /**
  1681. * Tests whether the passed Component matches the selector string.
  1682. * @param {Ext.Component} component The Component to test
  1683. * @param {String} selector The selector string to test against.
  1684. * @return {Boolean} True if the Component matches the selector.
  1685. * @member Ext.ComponentQuery
  1686. */
  1687. is: function(component, selector) {
  1688. if (!selector) {
  1689. return true;
  1690. }
  1691. var query = this.cache[selector];
  1692. if (!query) {
  1693. this.cache[selector] = query = this.parse(selector);
  1694. }
  1695. return query.is(component);
  1696. },
  1697. parse: function(selector) {
  1698. var operations = [],
  1699. length = matchers.length,
  1700. lastSelector,
  1701. tokenMatch,
  1702. matchedChar,
  1703. modeMatch,
  1704. selectorMatch,
  1705. i, matcher, method;
  1706. // We are going to parse the beginning of the selector over and
  1707. // over again, slicing off the selector any portions we converted into an
  1708. // operation, until it is an empty string.
  1709. while (selector && lastSelector !== selector) {
  1710. lastSelector = selector;
  1711. // First we check if we are dealing with a token like #, * or an xtype
  1712. tokenMatch = selector.match(tokenRe);
  1713. if (tokenMatch) {
  1714. matchedChar = tokenMatch[1];
  1715. // If the token is prefixed with a # we push a filterById operation to our stack
  1716. if (matchedChar === '#') {
  1717. operations.push({
  1718. method: filterById,
  1719. args: [Ext.String.trim(tokenMatch[2])]
  1720. });
  1721. }
  1722. // If the token is prefixed with a . we push a filterByClassName operation to our stack
  1723. // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
  1724. else if (matchedChar === '.') {
  1725. operations.push({
  1726. method: filterByClassName,
  1727. args: [Ext.String.trim(tokenMatch[2])]
  1728. });
  1729. }
  1730. // If the token is a * or an xtype string, we push a filterByXType
  1731. // operation to the stack.
  1732. else {
  1733. operations.push({
  1734. method: filterByXType,
  1735. args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
  1736. });
  1737. }
  1738. // Now we slice of the part we just converted into an operation
  1739. selector = selector.replace(tokenMatch[0], '');
  1740. }
  1741. // If the next part of the query is not a space or > or ^, it means we
  1742. // are going to check for more things that our current selection
  1743. // has to comply to.
  1744. while (!(modeMatch = selector.match(modeRe))) {
  1745. // Lets loop over each type of matcher and execute it
  1746. // on our current selector.
  1747. for (i = 0; selector && i < length; i++) {
  1748. matcher = matchers[i];
  1749. selectorMatch = selector.match(matcher.re);
  1750. method = matcher.method;
  1751. // If we have a match, add an operation with the method
  1752. // associated with this matcher, and pass the regular
  1753. // expression matches are arguments to the operation.
  1754. if (selectorMatch) {
  1755. operations.push({
  1756. method: Ext.isString(matcher.method)
  1757. // Turn a string method into a function by formatting the string with our selector matche expression
  1758. // A new method is created for different match expressions, eg {id=='textfield-1024'}
  1759. // Every expression may be different in different selectors.
  1760. ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
  1761. : matcher.method,
  1762. args: selectorMatch.slice(1)
  1763. });
  1764. selector = selector.replace(selectorMatch[0], '');
  1765. break; // Break on match
  1766. }
  1767. //<debug>
  1768. // Exhausted all matches: It's an error
  1769. if (i === (length - 1)) {
  1770. Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
  1771. }
  1772. //</debug>
  1773. }
  1774. }
  1775. // Now we are going to check for a mode change. This means a space
  1776. // or a > to determine if we are going to select all the children
  1777. // of the currently matched items, or a ^ if we are going to use the
  1778. // ownerCt axis as the candidate source.
  1779. if (modeMatch[1]) { // Assignment, and test for truthiness!
  1780. operations.push({
  1781. mode: modeMatch[2]||modeMatch[1]
  1782. });
  1783. selector = selector.replace(modeMatch[0], '');
  1784. }
  1785. }
  1786. // Now that we have all our operations in an array, we are going
  1787. // to create a new Query using these operations.
  1788. return new cq.Query({
  1789. operations: operations
  1790. });
  1791. }
  1792. });
  1793. });
  1794. /**
  1795. * @class Ext.util.HashMap
  1796. * <p>
  1797. * Represents a collection of a set of key and value pairs. Each key in the HashMap
  1798. * must be unique, the same key cannot exist twice. Access to items is provided via
  1799. * the key only. Sample usage:
  1800. * <pre><code>
  1801. var map = new Ext.util.HashMap();
  1802. map.add('key1', 1);
  1803. map.add('key2', 2);
  1804. map.add('key3', 3);
  1805. map.each(function(key, value, length){
  1806. console.log(key, value, length);
  1807. });
  1808. * </code></pre>
  1809. * </p>
  1810. *
  1811. * <p>The HashMap is an unordered class,
  1812. * there is no guarantee when iterating over the items that they will be in any particular
  1813. * order. If this is required, then use a {@link Ext.util.MixedCollection}.
  1814. * </p>
  1815. */
  1816. Ext.define('Ext.util.HashMap', {
  1817. mixins: {
  1818. observable: 'Ext.util.Observable'
  1819. },
  1820. /**
  1821. * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
  1822. * A default is provided that returns the <b>id</b> property on the object. This function is only used
  1823. * if the add method is called with a single argument.
  1824. */
  1825. /**
  1826. * Creates new HashMap.
  1827. * @param {Object} config (optional) Config object.
  1828. */
  1829. constructor: function(config) {
  1830. config = config || {};
  1831. var me = this,
  1832. keyFn = config.keyFn;
  1833. me.addEvents(
  1834. /**
  1835. * @event add
  1836. * Fires when a new item is added to the hash
  1837. * @param {Ext.util.HashMap} this.
  1838. * @param {String} key The key of the added item.
  1839. * @param {Object} value The value of the added item.
  1840. */
  1841. 'add',
  1842. /**
  1843. * @event clear
  1844. * Fires when the hash is cleared.
  1845. * @param {Ext.util.HashMap} this.
  1846. */
  1847. 'clear',
  1848. /**
  1849. * @event remove
  1850. * Fires when an item is removed from the hash.
  1851. * @param {Ext.util.HashMap} this.
  1852. * @param {String} key The key of the removed item.
  1853. * @param {Object} value The value of the removed item.
  1854. */
  1855. 'remove',
  1856. /**
  1857. * @event replace
  1858. * Fires when an item is replaced in the hash.
  1859. * @param {Ext.util.HashMap} this.
  1860. * @param {String} key The key of the replaced item.
  1861. * @param {Object} value The new value for the item.
  1862. * @param {Object} old The old value for the item.
  1863. */
  1864. 'replace'
  1865. );
  1866. me.mixins.observable.constructor.call(me, config);
  1867. me.clear(true);
  1868. if (keyFn) {
  1869. me.getKey = keyFn;
  1870. }
  1871. },
  1872. /**
  1873. * Gets the number of items in the hash.
  1874. * @return {Number} The number of items in the hash.
  1875. */
  1876. getCount: function() {
  1877. return this.length;
  1878. },
  1879. /**
  1880. * Implementation for being able to extract the key from an object if only
  1881. * a single argument is passed.
  1882. * @private
  1883. * @param {String} key The key
  1884. * @param {Object} value The value
  1885. * @return {Array} [key, value]
  1886. */
  1887. getData: function(key, value) {
  1888. // if we have no value, it means we need to get the key from the object
  1889. if (value === undefined) {
  1890. value = key;
  1891. key = this.getKey(value);
  1892. }
  1893. return [key, value];
  1894. },
  1895. /**
  1896. * Extracts the key from an object. This is a default implementation, it may be overridden
  1897. * @param {Object} o The object to get the key from
  1898. * @return {String} The key to use.
  1899. */
  1900. getKey: function(o) {
  1901. return o.id;
  1902. },
  1903. /**
  1904. * Adds an item to the collection. Fires the {@link #add} event when complete.
  1905. * @param {String} key <p>The key to associate with the item, or the new item.</p>
  1906. * <p>If a {@link #getKey} implementation was specified for this HashMap,
  1907. * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
  1908. * the HashMap will be able to <i>derive</i> the key for the new item.
  1909. * In this case just pass the new item in this parameter.</p>
  1910. * @param {Object} o The item to add.
  1911. * @return {Object} The item added.
  1912. */
  1913. add: function(key, value) {
  1914. var me = this,
  1915. data;
  1916. if (arguments.length === 1) {
  1917. value = key;
  1918. key = me.getKey(value);
  1919. }
  1920. if (me.containsKey(key)) {
  1921. return me.replace(key, value);
  1922. }
  1923. data = me.getData(key, value);
  1924. key = data[0];
  1925. value = data[1];
  1926. me.map[key] = value;
  1927. ++me.length;
  1928. me.fireEvent('add', me, key, value);
  1929. return value;
  1930. },
  1931. /**
  1932. * Replaces an item in the hash. If the key doesn't exist, the
  1933. * {@link #add} method will be used.
  1934. * @param {String} key The key of the item.
  1935. * @param {Object} value The new value for the item.
  1936. * @return {Object} The new value of the item.
  1937. */
  1938. replace: function(key, value) {
  1939. var me = this,
  1940. map = me.map,
  1941. old;
  1942. if (!me.containsKey(key)) {
  1943. me.add(key, value);
  1944. }
  1945. old = map[key];
  1946. map[key] = value;
  1947. me.fireEvent('replace', me, key, value, old);
  1948. return value;
  1949. },
  1950. /**
  1951. * Remove an item from the hash.
  1952. * @param {Object} o The value of the item to remove.
  1953. * @return {Boolean} True if the item was successfully removed.
  1954. */
  1955. remove: function(o) {
  1956. var key = this.findKey(o);
  1957. if (key !== undefined) {
  1958. return this.removeAtKey(key);
  1959. }
  1960. return false;
  1961. },
  1962. /**
  1963. * Remove an item from the hash.
  1964. * @param {String} key The key to remove.
  1965. * @return {Boolean} True if the item was successfully removed.
  1966. */
  1967. removeAtKey: function(key) {
  1968. var me = this,
  1969. value;
  1970. if (me.containsKey(key)) {
  1971. value = me.map[key];
  1972. delete me.map[key];
  1973. --me.length;
  1974. me.fireEvent('remove', me, key, value);
  1975. return true;
  1976. }
  1977. return false;
  1978. },
  1979. /**
  1980. * Retrieves an item with a particular key.
  1981. * @param {String} key The key to lookup.
  1982. * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
  1983. */
  1984. get: function(key) {
  1985. return this.map[key];
  1986. },
  1987. /**
  1988. * Removes all items from the hash.
  1989. * @return {Ext.util.HashMap} this
  1990. */
  1991. clear: function(/* private */ initial) {
  1992. var me = this;
  1993. me.map = {};
  1994. me.length = 0;
  1995. if (initial !== true) {
  1996. me.fireEvent('clear', me);
  1997. }
  1998. return me;
  1999. },
  2000. /**
  2001. * Checks whether a key exists in the hash.
  2002. * @param {String} key The key to check for.
  2003. * @return {Boolean} True if they key exists in the hash.
  2004. */
  2005. containsKey: function(key) {
  2006. return this.map[key] !== undefined;
  2007. },
  2008. /**
  2009. * Checks whether a value exists in the hash.
  2010. * @param {Object} value The value to check for.
  2011. * @return {Boolean} True if the value exists in the dictionary.
  2012. */
  2013. contains: function(value) {
  2014. return this.containsKey(this.findKey(value));
  2015. },
  2016. /**
  2017. * Return all of the keys in the hash.
  2018. * @return {Array} An array of keys.
  2019. */
  2020. getKeys: function() {
  2021. return this.getArray(true);
  2022. },
  2023. /**
  2024. * Return all of the values in the hash.
  2025. * @return {Array} An array of values.
  2026. */
  2027. getValues: function() {
  2028. return this.getArray(false);
  2029. },
  2030. /**
  2031. * Gets either the keys/values in an array from the hash.
  2032. * @private
  2033. * @param {Boolean} isKey True to extract the keys, otherwise, the value
  2034. * @return {Array} An array of either keys/values from the hash.
  2035. */
  2036. getArray: function(isKey) {
  2037. var arr = [],
  2038. key,
  2039. map = this.map;
  2040. for (key in map) {
  2041. if (map.hasOwnProperty(key)) {
  2042. arr.push(isKey ? key: map[key]);
  2043. }
  2044. }
  2045. return arr;
  2046. },
  2047. /**
  2048. * Executes the specified function once for each item in the hash.
  2049. * Returning false from the function will cease iteration.
  2050. *
  2051. * The paramaters passed to the function are:
  2052. * <div class="mdetail-params"><ul>
  2053. * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
  2054. * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
  2055. * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
  2056. * </ul></div>
  2057. * @param {Function} fn The function to execute.
  2058. * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
  2059. * @return {Ext.util.HashMap} this
  2060. */
  2061. each: function(fn, scope) {
  2062. // copy items so they may be removed during iteration.
  2063. var items = Ext.apply({}, this.map),
  2064. key,
  2065. length = this.length;
  2066. scope = scope || this;
  2067. for (key in items) {
  2068. if (items.hasOwnProperty(key)) {
  2069. if (fn.call(scope, key, items[key], length) === false) {
  2070. break;
  2071. }
  2072. }
  2073. }
  2074. return this;
  2075. },
  2076. /**
  2077. * Performs a shallow copy on this hash.
  2078. * @return {Ext.util.HashMap} The new hash object.
  2079. */
  2080. clone: function() {
  2081. var hash = new this.self(),
  2082. map = this.map,
  2083. key;
  2084. hash.suspendEvents();
  2085. for (key in map) {
  2086. if (map.hasOwnProperty(key)) {
  2087. hash.add(key, map[key]);
  2088. }
  2089. }
  2090. hash.resumeEvents();
  2091. return hash;
  2092. },
  2093. /**
  2094. * @private
  2095. * Find the key for a value.
  2096. * @param {Object} value The value to find.
  2097. * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
  2098. */
  2099. findKey: function(value) {
  2100. var key,
  2101. map = this.map;
  2102. for (key in map) {
  2103. if (map.hasOwnProperty(key) && map[key] === value) {
  2104. return key;
  2105. }
  2106. }
  2107. return undefined;
  2108. }
  2109. });
  2110. /**
  2111. * @class Ext.state.Manager
  2112. * This is the global state manager. By default all components that are "state aware" check this class
  2113. * for state information if you don't pass them a custom state provider. In order for this class
  2114. * to be useful, it must be initialized with a provider when your application initializes. Example usage:
  2115. <pre><code>
  2116. // in your initialization function
  2117. init : function(){
  2118. Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
  2119. var win = new Window(...);
  2120. win.restoreState();
  2121. }
  2122. </code></pre>
  2123. * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
  2124. * there is a common interface that can be used without needing to refer to a specific provider instance
  2125. * in every component.
  2126. * @singleton
  2127. * @docauthor Evan Trimboli <evan@sencha.com>
  2128. */
  2129. Ext.define('Ext.state.Manager', {
  2130. singleton: true,
  2131. requires: ['Ext.state.Provider'],
  2132. constructor: function() {
  2133. this.provider = Ext.create('Ext.state.Provider');
  2134. },
  2135. /**
  2136. * Configures the default state provider for your application
  2137. * @param {Ext.state.Provider} stateProvider The state provider to set
  2138. */
  2139. setProvider : function(stateProvider){
  2140. this.provider = stateProvider;
  2141. },
  2142. /**
  2143. * Returns the current value for a key
  2144. * @param {String} name The key name
  2145. * @param {Object} defaultValue The default value to return if the key lookup does not match
  2146. * @return {Object} The state data
  2147. */
  2148. get : function(key, defaultValue){
  2149. return this.provider.get(key, defaultValue);
  2150. },
  2151. /**
  2152. * Sets the value for a key
  2153. * @param {String} name The key name
  2154. * @param {Object} value The state data
  2155. */
  2156. set : function(key, value){
  2157. this.provider.set(key, value);
  2158. },
  2159. /**
  2160. * Clears a value from the state
  2161. * @param {String} name The key name
  2162. */
  2163. clear : function(key){
  2164. this.provider.clear(key);
  2165. },
  2166. /**
  2167. * Gets the currently configured state provider
  2168. * @return {Ext.state.Provider} The state provider
  2169. */
  2170. getProvider : function(){
  2171. return this.provider;
  2172. }
  2173. });
  2174. /**
  2175. * @class Ext.state.Stateful
  2176. * A mixin for being able to save the state of an object to an underlying
  2177. * {@link Ext.state.Provider}.
  2178. */
  2179. Ext.define('Ext.state.Stateful', {
  2180. /* Begin Definitions */
  2181. mixins: {
  2182. observable: 'Ext.util.Observable'
  2183. },
  2184. requires: ['Ext.state.Manager'],
  2185. /* End Definitions */
  2186. /**
  2187. * @cfg {Boolean} stateful
  2188. * <p>A flag which causes the object to attempt to restore the state of
  2189. * internal properties from a saved state on startup. The object must have
  2190. * a <code>{@link #stateId}</code> for state to be managed.
  2191. * Auto-generated ids are not guaranteed to be stable across page loads and
  2192. * cannot be relied upon to save and restore the same state for a object.<p>
  2193. * <p>For state saving to work, the state manager's provider must have been
  2194. * set to an implementation of {@link Ext.state.Provider} which overrides the
  2195. * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
  2196. * methods to save and recall name/value pairs. A built-in implementation,
  2197. * {@link Ext.state.CookieProvider} is available.</p>
  2198. * <p>To set the state provider for the current page:</p>
  2199. * <pre><code>
  2200. Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
  2201. expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
  2202. }));
  2203. * </code></pre>
  2204. * <p>A stateful object attempts to save state when one of the events
  2205. * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
  2206. * <p>To save state, a stateful object first serializes its state by
  2207. * calling <b><code>{@link #getState}</code></b>. By default, this function does
  2208. * nothing. The developer must provide an implementation which returns an
  2209. * object hash which represents the restorable state of the object.</p>
  2210. * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
  2211. * which uses the configured {@link Ext.state.Provider} to save the object
  2212. * keyed by the <code>{@link #stateId}</code>.</p>
  2213. * <p>During construction, a stateful object attempts to <i>restore</i>
  2214. * its state by calling {@link Ext.state.Manager#get} passing the
  2215. * <code>{@link #stateId}</code></p>
  2216. * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
  2217. * The default implementation of <code>{@link #applyState}</code> simply copies
  2218. * properties into the object, but a developer may override this to support
  2219. * more behaviour.</p>
  2220. * <p>You can perform extra processing on state save and restore by attaching
  2221. * handlers to the {@link #beforestaterestore}, {@link #staterestore},
  2222. * {@link #beforestatesave} and {@link #statesave} events.</p>
  2223. */
  2224. stateful: true,
  2225. /**
  2226. * @cfg {String} stateId
  2227. * The unique id for this object to use for state management purposes.
  2228. * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
  2229. */
  2230. /**
  2231. * @cfg {String[]} stateEvents
  2232. * <p>An array of events that, when fired, should trigger this object to
  2233. * save its state. Defaults to none. <code>stateEvents</code> may be any type
  2234. * of event supported by this object, including browser or custom events
  2235. * (e.g., <tt>['click', 'customerchange']</tt>).</p>
  2236. * <p>See <code>{@link #stateful}</code> for an explanation of saving and
  2237. * restoring object state.</p>
  2238. */
  2239. /**
  2240. * @cfg {Number} saveDelay
  2241. * A buffer to be applied if many state events are fired within a short period.
  2242. */
  2243. saveDelay: 100,
  2244. autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
  2245. constructor: function(config) {
  2246. var me = this;
  2247. config = config || {};
  2248. if (Ext.isDefined(config.stateful)) {
  2249. me.stateful = config.stateful;
  2250. }
  2251. if (Ext.isDefined(config.saveDelay)) {
  2252. me.saveDelay = config.saveDelay;
  2253. }
  2254. me.stateId = me.stateId || config.stateId;
  2255. if (!me.stateEvents) {
  2256. me.stateEvents = [];
  2257. }
  2258. if (config.stateEvents) {
  2259. me.stateEvents.concat(config.stateEvents);
  2260. }
  2261. this.addEvents(
  2262. /**
  2263. * @event beforestaterestore
  2264. * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
  2265. * @param {Ext.state.Stateful} this
  2266. * @param {Object} state The hash of state values returned from the StateProvider. If this
  2267. * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
  2268. * that simply copies property values into this object. The method maybe overriden to
  2269. * provide custom state restoration.
  2270. */
  2271. 'beforestaterestore',
  2272. /**
  2273. * @event staterestore
  2274. * Fires after the state of the object is restored.
  2275. * @param {Ext.state.Stateful} this
  2276. * @param {Object} state The hash of state values returned from the StateProvider. This is passed
  2277. * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
  2278. * object. The method maybe overriden to provide custom state restoration.
  2279. */
  2280. 'staterestore',
  2281. /**
  2282. * @event beforestatesave
  2283. * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
  2284. * @param {Ext.state.Stateful} this
  2285. * @param {Object} state The hash of state values. This is determined by calling
  2286. * <b><tt>getState()</tt></b> on the object. This method must be provided by the
  2287. * developer to return whetever representation of state is required, by default, Ext.state.Stateful
  2288. * has a null implementation.
  2289. */
  2290. 'beforestatesave',
  2291. /**
  2292. * @event statesave
  2293. * Fires after the state of the object is saved to the configured state provider.
  2294. * @param {Ext.state.Stateful} this
  2295. * @param {Object} state The hash of state values. This is determined by calling
  2296. * <b><tt>getState()</tt></b> on the object. This method must be provided by the
  2297. * developer to return whetever representation of state is required, by default, Ext.state.Stateful
  2298. * has a null implementation.
  2299. */
  2300. 'statesave'
  2301. );
  2302. me.mixins.observable.constructor.call(me);
  2303. if (me.stateful !== false) {
  2304. me.initStateEvents();
  2305. me.initState();
  2306. }
  2307. },
  2308. /**
  2309. * Initializes any state events for this object.
  2310. * @private
  2311. */
  2312. initStateEvents: function() {
  2313. this.addStateEvents(this.stateEvents);
  2314. },
  2315. /**
  2316. * Add events that will trigger the state to be saved.
  2317. * @param {String/String[]} events The event name or an array of event names.
  2318. */
  2319. addStateEvents: function(events){
  2320. if (!Ext.isArray(events)) {
  2321. events = [events];
  2322. }
  2323. var me = this,
  2324. i = 0,
  2325. len = events.length;
  2326. for (; i < len; ++i) {
  2327. me.on(events[i], me.onStateChange, me);
  2328. }
  2329. },
  2330. /**
  2331. * This method is called when any of the {@link #stateEvents} are fired.
  2332. * @private
  2333. */
  2334. onStateChange: function(){
  2335. var me = this,
  2336. delay = me.saveDelay;
  2337. if (delay > 0) {
  2338. if (!me.stateTask) {
  2339. me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
  2340. }
  2341. me.stateTask.delay(me.saveDelay);
  2342. } else {
  2343. me.saveState();
  2344. }
  2345. },
  2346. /**
  2347. * Saves the state of the object to the persistence store.
  2348. * @private
  2349. */
  2350. saveState: function() {
  2351. var me = this,
  2352. id,
  2353. state;
  2354. if (me.stateful !== false) {
  2355. id = me.getStateId();
  2356. if (id) {
  2357. state = me.getState();
  2358. if (me.fireEvent('beforestatesave', me, state) !== false) {
  2359. Ext.state.Manager.set(id, state);
  2360. me.fireEvent('statesave', me, state);
  2361. }
  2362. }
  2363. }
  2364. },
  2365. /**
  2366. * Gets the current state of the object. By default this function returns null,
  2367. * it should be overridden in subclasses to implement methods for getting the state.
  2368. * @return {Object} The current state
  2369. */
  2370. getState: function(){
  2371. return null;
  2372. },
  2373. /**
  2374. * Applies the state to the object. This should be overridden in subclasses to do
  2375. * more complex state operations. By default it applies the state properties onto
  2376. * the current object.
  2377. * @param {Object} state The state
  2378. */
  2379. applyState: function(state) {
  2380. if (state) {
  2381. Ext.apply(this, state);
  2382. }
  2383. },
  2384. /**
  2385. * Gets the state id for this object.
  2386. * @return {String} The state id, null if not found.
  2387. */
  2388. getStateId: function() {
  2389. var me = this,
  2390. id = me.stateId;
  2391. if (!id) {
  2392. id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
  2393. }
  2394. return id;
  2395. },
  2396. /**
  2397. * Initializes the state of the object upon construction.
  2398. * @private
  2399. */
  2400. initState: function(){
  2401. var me = this,
  2402. id = me.getStateId(),
  2403. state;
  2404. if (me.stateful !== false) {
  2405. if (id) {
  2406. state = Ext.state.Manager.get(id);
  2407. if (state) {
  2408. state = Ext.apply({}, state);
  2409. if (me.fireEvent('beforestaterestore', me, state) !== false) {
  2410. me.applyState(state);
  2411. me.fireEvent('staterestore', me, state);
  2412. }
  2413. }
  2414. }
  2415. }
  2416. },
  2417. /**
  2418. * Conditionally saves a single property from this object to the given state object.
  2419. * The idea is to only save state which has changed from the initial state so that
  2420. * current software settings do not override future software settings. Only those
  2421. * values that are user-changed state should be saved.
  2422. *
  2423. * @param {String} propName The name of the property to save.
  2424. * @param {Object} state The state object in to which to save the property.
  2425. * @param {String} stateName (optional) The name to use for the property in state.
  2426. * @return {Boolean} True if the property was saved, false if not.
  2427. */
  2428. savePropToState: function (propName, state, stateName) {
  2429. var me = this,
  2430. value = me[propName],
  2431. config = me.initialConfig;
  2432. if (me.hasOwnProperty(propName)) {
  2433. if (!config || config[propName] !== value) {
  2434. if (state) {
  2435. state[stateName || propName] = value;
  2436. }
  2437. return true;
  2438. }
  2439. }
  2440. return false;
  2441. },
  2442. savePropsToState: function (propNames, state) {
  2443. var me = this;
  2444. Ext.each(propNames, function (propName) {
  2445. me.savePropToState(propName, state);
  2446. });
  2447. return state;
  2448. },
  2449. /**
  2450. * Destroys this stateful object.
  2451. */
  2452. destroy: function(){
  2453. var task = this.stateTask;
  2454. if (task) {
  2455. task.cancel();
  2456. }
  2457. this.clearListeners();
  2458. }
  2459. });
  2460. /**
  2461. * Base Manager class
  2462. */
  2463. Ext.define('Ext.AbstractManager', {
  2464. /* Begin Definitions */
  2465. requires: ['Ext.util.HashMap'],
  2466. /* End Definitions */
  2467. typeName: 'type',
  2468. constructor: function(config) {
  2469. Ext.apply(this, config || {});
  2470. /**
  2471. * @property {Ext.util.HashMap} all
  2472. * Contains all of the items currently managed
  2473. */
  2474. this.all = Ext.create('Ext.util.HashMap');
  2475. this.types = {};
  2476. },
  2477. /**
  2478. * Returns an item by id.
  2479. * For additional details see {@link Ext.util.HashMap#get}.
  2480. * @param {String} id The id of the item
  2481. * @return {Object} The item, undefined if not found.
  2482. */
  2483. get : function(id) {
  2484. return this.all.get(id);
  2485. },
  2486. /**
  2487. * Registers an item to be managed
  2488. * @param {Object} item The item to register
  2489. */
  2490. register: function(item) {
  2491. //<debug>
  2492. var all = this.all,
  2493. key = all.getKey(item);
  2494. if (all.containsKey(key)) {
  2495. Ext.Error.raise('Registering duplicate id "' + key + '" with this manager');
  2496. }
  2497. //</debug>
  2498. this.all.add(item);
  2499. },
  2500. /**
  2501. * Unregisters an item by removing it from this manager
  2502. * @param {Object} item The item to unregister
  2503. */
  2504. unregister: function(item) {
  2505. this.all.remove(item);
  2506. },
  2507. /**
  2508. * Registers a new item constructor, keyed by a type key.
  2509. * @param {String} type The mnemonic string by which the class may be looked up.
  2510. * @param {Function} cls The new instance class.
  2511. */
  2512. registerType : function(type, cls) {
  2513. this.types[type] = cls;
  2514. cls[this.typeName] = type;
  2515. },
  2516. /**
  2517. * Checks if an item type is registered.
  2518. * @param {String} type The mnemonic string by which the class may be looked up
  2519. * @return {Boolean} Whether the type is registered.
  2520. */
  2521. isRegistered : function(type){
  2522. return this.types[type] !== undefined;
  2523. },
  2524. /**
  2525. * Creates and returns an instance of whatever this manager manages, based on the supplied type and
  2526. * config object.
  2527. * @param {Object} config The config object
  2528. * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
  2529. * @return {Object} The instance of whatever this manager is managing
  2530. */
  2531. create: function(config, defaultType) {
  2532. var type = config[this.typeName] || config.type || defaultType,
  2533. Constructor = this.types[type];
  2534. //<debug>
  2535. if (Constructor === undefined) {
  2536. Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
  2537. }
  2538. //</debug>
  2539. return new Constructor(config);
  2540. },
  2541. /**
  2542. * Registers a function that will be called when an item with the specified id is added to the manager.
  2543. * This will happen on instantiation.
  2544. * @param {String} id The item id
  2545. * @param {Function} fn The callback function. Called with a single parameter, the item.
  2546. * @param {Object} scope The scope (this reference) in which the callback is executed.
  2547. * Defaults to the item.
  2548. */
  2549. onAvailable : function(id, fn, scope){
  2550. var all = this.all,
  2551. item;
  2552. if (all.containsKey(id)) {
  2553. item = all.get(id);
  2554. fn.call(scope || item, item);
  2555. } else {
  2556. all.on('add', function(map, key, item){
  2557. if (key == id) {
  2558. fn.call(scope || item, item);
  2559. all.un('add', fn, scope);
  2560. }
  2561. });
  2562. }
  2563. },
  2564. /**
  2565. * Executes the specified function once for each item in the collection.
  2566. * @param {Function} fn The function to execute.
  2567. * @param {String} fn.key The key of the item
  2568. * @param {Number} fn.value The value of the item
  2569. * @param {Number} fn.length The total number of items in the collection
  2570. * @param {Boolean} fn.return False to cease iteration.
  2571. * @param {Object} scope The scope to execute in. Defaults to `this`.
  2572. */
  2573. each: function(fn, scope){
  2574. this.all.each(fn, scope || this);
  2575. },
  2576. /**
  2577. * Gets the number of items in the collection.
  2578. * @return {Number} The number of items in the collection.
  2579. */
  2580. getCount: function(){
  2581. return this.all.getCount();
  2582. }
  2583. });
  2584. /**
  2585. * @class Ext.ComponentManager
  2586. * @extends Ext.AbstractManager
  2587. * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
  2588. * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
  2589. * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
  2590. * <p>This object also provides a registry of available Component <i>classes</i>
  2591. * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
  2592. * The <code>xtype</code> provides a way to avoid instantiating child Components
  2593. * when creating a full, nested config object for a complete Ext page.</p>
  2594. * <p>A child Component may be specified simply as a <i>config object</i>
  2595. * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
  2596. * needs rendering, the correct type can be looked up for lazy instantiation.</p>
  2597. * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
  2598. * @singleton
  2599. */
  2600. Ext.define('Ext.ComponentManager', {
  2601. extend: 'Ext.AbstractManager',
  2602. alternateClassName: 'Ext.ComponentMgr',
  2603. singleton: true,
  2604. typeName: 'xtype',
  2605. /**
  2606. * Creates a new Component from the specified config object using the
  2607. * config object's xtype to determine the class to instantiate.
  2608. * @param {Object} config A configuration object for the Component you wish to create.
  2609. * @param {Function} defaultType (optional) The constructor to provide the default Component type if
  2610. * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
  2611. * @return {Ext.Component} The newly instantiated Component.
  2612. */
  2613. create: function(component, defaultType){
  2614. if (component instanceof Ext.AbstractComponent) {
  2615. return component;
  2616. }
  2617. else if (Ext.isString(component)) {
  2618. return Ext.createByAlias('widget.' + component);
  2619. }
  2620. else {
  2621. var type = component.xtype || defaultType,
  2622. config = component;
  2623. return Ext.createByAlias('widget.' + type, config);
  2624. }
  2625. },
  2626. registerType: function(type, cls) {
  2627. this.types[type] = cls;
  2628. cls[this.typeName] = type;
  2629. cls.prototype[this.typeName] = type;
  2630. }
  2631. });
  2632. /**
  2633. * An abstract base class which provides shared methods for Components across the Sencha product line.
  2634. *
  2635. * Please refer to sub class's documentation
  2636. * @private
  2637. */
  2638. Ext.define('Ext.AbstractComponent', {
  2639. /* Begin Definitions */
  2640. requires: [
  2641. 'Ext.ComponentQuery',
  2642. 'Ext.ComponentManager'
  2643. ],
  2644. mixins: {
  2645. observable: 'Ext.util.Observable',
  2646. animate: 'Ext.util.Animate',
  2647. state: 'Ext.state.Stateful'
  2648. },
  2649. // The "uses" property specifies class which are used in an instantiated AbstractComponent.
  2650. // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
  2651. uses: [
  2652. 'Ext.PluginManager',
  2653. 'Ext.ComponentManager',
  2654. 'Ext.Element',
  2655. 'Ext.DomHelper',
  2656. 'Ext.XTemplate',
  2657. 'Ext.ComponentQuery',
  2658. 'Ext.ComponentLoader',
  2659. 'Ext.EventManager',
  2660. 'Ext.layout.Layout',
  2661. 'Ext.layout.component.Auto',
  2662. 'Ext.LoadMask',
  2663. 'Ext.ZIndexManager'
  2664. ],
  2665. statics: {
  2666. AUTO_ID: 1000
  2667. },
  2668. /* End Definitions */
  2669. isComponent: true,
  2670. getAutoId: function() {
  2671. return ++Ext.AbstractComponent.AUTO_ID;
  2672. },
  2673. /**
  2674. * @cfg {String} id
  2675. * The **unique id of this component instance.**
  2676. *
  2677. * It should not be necessary to use this configuration except for singleton objects in your application. Components
  2678. * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
  2679. *
  2680. * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
  2681. * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
  2682. * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
  2683. * its descendant Components by selector.
  2684. *
  2685. * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
  2686. * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
  2687. * component uniquely, and also to select sub-elements using this component's id as the parent.
  2688. *
  2689. * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
  2690. *
  2691. * **Note**: to access the container of a Component see `{@link #ownerCt}`.
  2692. *
  2693. * Defaults to an {@link #getId auto-assigned id}.
  2694. */
  2695. /**
  2696. * @cfg {String} itemId
  2697. * An itemId can be used as an alternative way to get a reference to a component when no object reference is
  2698. * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
  2699. * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
  2700. * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
  2701. * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
  2702. * which requires a **unique** `{@link #id}`.
  2703. *
  2704. * var c = new Ext.panel.Panel({ //
  2705. * {@link Ext.Component#height height}: 300,
  2706. * {@link #renderTo}: document.body,
  2707. * {@link Ext.container.Container#layout layout}: 'auto',
  2708. * {@link Ext.container.Container#items items}: [
  2709. * {
  2710. * itemId: 'p1',
  2711. * {@link Ext.panel.Panel#title title}: 'Panel 1',
  2712. * {@link Ext.Component#height height}: 150
  2713. * },
  2714. * {
  2715. * itemId: 'p2',
  2716. * {@link Ext.panel.Panel#title title}: 'Panel 2',
  2717. * {@link Ext.Component#height height}: 150
  2718. * }
  2719. * ]
  2720. * })
  2721. * p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
  2722. * p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
  2723. *
  2724. * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
  2725. * `{@link Ext.container.Container#child}`.
  2726. *
  2727. * **Note**: to access the container of an item see {@link #ownerCt}.
  2728. */
  2729. /**
  2730. * @property {Ext.Container} ownerCt
  2731. * This Component's owner {@link Ext.container.Container Container} (is set automatically
  2732. * when this Component is added to a Container). Read-only.
  2733. *
  2734. * **Note**: to access items within the Container see {@link #itemId}.
  2735. */
  2736. /**
  2737. * @property {Boolean} layoutManagedWidth
  2738. * @private
  2739. * Flag set by the container layout to which this Component is added.
  2740. * If the layout manages this Component's width, it sets the value to 1.
  2741. * If it does NOT manage the width, it sets it to 2.
  2742. * If the layout MAY affect the width, but only if the owning Container has a fixed width, this is set to 0.
  2743. */
  2744. /**
  2745. * @property {Boolean} layoutManagedHeight
  2746. * @private
  2747. * Flag set by the container layout to which this Component is added.
  2748. * If the layout manages this Component's height, it sets the value to 1.
  2749. * If it does NOT manage the height, it sets it to 2.
  2750. * If the layout MAY affect the height, but only if the owning Container has a fixed height, this is set to 0.
  2751. */
  2752. /**
  2753. * @cfg {String/Object} autoEl
  2754. * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
  2755. * encapsulate this Component.
  2756. *
  2757. * You do not normally need to specify this. For the base classes {@link Ext.Component} and
  2758. * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
  2759. * complex DOM structure specified by their own {@link #renderTpl}s.
  2760. *
  2761. * This is intended to allow the developer to create application-specific utility Components encapsulated by
  2762. * different DOM elements. Example usage:
  2763. *
  2764. * {
  2765. * xtype: 'component',
  2766. * autoEl: {
  2767. * tag: 'img',
  2768. * src: 'http://www.example.com/example.jpg'
  2769. * }
  2770. * }, {
  2771. * xtype: 'component',
  2772. * autoEl: {
  2773. * tag: 'blockquote',
  2774. * html: 'autoEl is cool!'
  2775. * }
  2776. * }, {
  2777. * xtype: 'container',
  2778. * autoEl: 'ul',
  2779. * cls: 'ux-unordered-list',
  2780. * items: {
  2781. * xtype: 'component',
  2782. * autoEl: 'li',
  2783. * html: 'First list item'
  2784. * }
  2785. * }
  2786. */
  2787. /**
  2788. * @cfg {Ext.XTemplate/String/String[]} renderTpl
  2789. * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
  2790. * {@link #getEl Element}.
  2791. *
  2792. * You do not normally need to specify this. For the base classes {@link Ext.Component} and
  2793. * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
  2794. * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
  2795. * classes which use a more complex DOM structure, provide their own template definitions.
  2796. *
  2797. * This is intended to allow the developer to create application-specific utility Components with customized
  2798. * internal structure.
  2799. *
  2800. * Upon rendering, any created child elements may be automatically imported into object properties using the
  2801. * {@link #renderSelectors} and {@link #childEls} options.
  2802. */
  2803. renderTpl: null,
  2804. /**
  2805. * @cfg {Object} renderData
  2806. *
  2807. * The data used by {@link #renderTpl} in addition to the following property values of the component:
  2808. *
  2809. * - id
  2810. * - ui
  2811. * - uiCls
  2812. * - baseCls
  2813. * - componentCls
  2814. * - frame
  2815. *
  2816. * See {@link #renderSelectors} and {@link #childEls} for usage examples.
  2817. */
  2818. /**
  2819. * @cfg {Object} renderSelectors
  2820. * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
  2821. * created by the render process.
  2822. *
  2823. * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
  2824. * and the found Elements are added as properties to the Component using the `renderSelector` property name.
  2825. *
  2826. * For example, a Component which renderes a title and description into its element:
  2827. *
  2828. * Ext.create('Ext.Component', {
  2829. * renderTo: Ext.getBody(),
  2830. * renderTpl: [
  2831. * '<h1 class="title">{title}</h1>',
  2832. * '<p>{desc}</p>'
  2833. * ],
  2834. * renderData: {
  2835. * title: "Error",
  2836. * desc: "Something went wrong"
  2837. * },
  2838. * renderSelectors: {
  2839. * titleEl: 'h1.title',
  2840. * descEl: 'p'
  2841. * },
  2842. * listeners: {
  2843. * afterrender: function(cmp){
  2844. * // After rendering the component will have a titleEl and descEl properties
  2845. * cmp.titleEl.setStyle({color: "red"});
  2846. * }
  2847. * }
  2848. * });
  2849. *
  2850. * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
  2851. * Component after render), see {@link #childEls} and {@link #addChildEls}.
  2852. */
  2853. /**
  2854. * @cfg {Object[]} childEls
  2855. * An array describing the child elements of the Component. Each member of the array
  2856. * is an object with these properties:
  2857. *
  2858. * - `name` - The property name on the Component for the child element.
  2859. * - `itemId` - The id to combine with the Component's id that is the id of the child element.
  2860. * - `id` - The id of the child element.
  2861. *
  2862. * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
  2863. *
  2864. * For example, a Component which renders a title and body text:
  2865. *
  2866. * Ext.create('Ext.Component', {
  2867. * renderTo: Ext.getBody(),
  2868. * renderTpl: [
  2869. * '<h1 id="{id}-title">{title}</h1>',
  2870. * '<p>{msg}</p>',
  2871. * ],
  2872. * renderData: {
  2873. * title: "Error",
  2874. * msg: "Something went wrong"
  2875. * },
  2876. * childEls: ["title"],
  2877. * listeners: {
  2878. * afterrender: function(cmp){
  2879. * // After rendering the component will have a title property
  2880. * cmp.title.setStyle({color: "red"});
  2881. * }
  2882. * }
  2883. * });
  2884. *
  2885. * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
  2886. */
  2887. /**
  2888. * @cfg {String/HTMLElement/Ext.Element} renderTo
  2889. * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
  2890. *
  2891. * **Notes:**
  2892. *
  2893. * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
  2894. * It is the responsibility of the {@link Ext.container.Container Container}'s
  2895. * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
  2896. *
  2897. * When using this config, a call to render() is not required.
  2898. *
  2899. * See `{@link #render}` also.
  2900. */
  2901. /**
  2902. * @cfg {Boolean} frame
  2903. * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
  2904. * graphical rounded frame around the Component content.
  2905. *
  2906. * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
  2907. * Explorer prior to version 9 which do not support rounded corners natively.
  2908. *
  2909. * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
  2910. */
  2911. /**
  2912. * @property {Object} frameSize
  2913. * Read-only property indicating the width of any framing elements which were added within the encapsulating element
  2914. * to provide graphical, rounded borders. See the {@link #frame} config.
  2915. *
  2916. * This is an object containing the frame width in pixels for all four sides of the Component containing the
  2917. * following properties:
  2918. *
  2919. * @property {Number} frameSize.top The width of the top framing element in pixels.
  2920. * @property {Number} frameSize.right The width of the right framing element in pixels.
  2921. * @property {Number} frameSize.bottom The width of the bottom framing element in pixels.
  2922. * @property {Number} frameSize.left The width of the left framing element in pixels.
  2923. */
  2924. /**
  2925. * @cfg {String/Object} componentLayout
  2926. * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
  2927. * manager which sizes a Component's internal structure in response to the Component being sized.
  2928. *
  2929. * Generally, developers will not use this configuration as all provided Components which need their internal
  2930. * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
  2931. *
  2932. * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
  2933. * class which simply sizes the Component's encapsulating element to the height and width specified in the
  2934. * {@link #setSize} method.
  2935. */
  2936. /**
  2937. * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
  2938. * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
  2939. * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
  2940. */
  2941. /**
  2942. * @cfg {Object} data
  2943. * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
  2944. */
  2945. /**
  2946. * @cfg {String} xtype
  2947. * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
  2948. * shortcut to the full componet name. For example, the component `Ext.button.Button` has an xtype of `button`.
  2949. *
  2950. * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
  2951. * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
  2952. *
  2953. * Ext.define('PressMeButton', {
  2954. * extend: 'Ext.button.Button',
  2955. * alias: 'widget.pressmebutton',
  2956. * text: 'Press Me'
  2957. * })
  2958. *
  2959. * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
  2960. * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
  2961. * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
  2962. * until they are actually needed. In complex, nested layouts containing many Components, this can make a
  2963. * noticeable improvement in performance.
  2964. *
  2965. * // Explicit creation of contained Components:
  2966. * var panel = new Ext.Panel({
  2967. * ...
  2968. * items: [
  2969. * Ext.create('Ext.button.Button', {
  2970. * text: 'OK'
  2971. * })
  2972. * ]
  2973. * };
  2974. *
  2975. * // Implicit creation using xtype:
  2976. * var panel = new Ext.Panel({
  2977. * ...
  2978. * items: [{
  2979. * xtype: 'button',
  2980. * text: 'OK'
  2981. * }]
  2982. * };
  2983. *
  2984. * In the first example, the button will always be created immediately during the panel's initialization. With
  2985. * many added Components, this approach could potentially slow the rendering of the page. In the second example,
  2986. * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
  2987. * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
  2988. * will never consume any resources whatsoever.
  2989. */
  2990. /**
  2991. * @cfg {String} tplWriteMode
  2992. * The Ext.(X)Template method to use when updating the content area of the Component.
  2993. * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
  2994. */
  2995. tplWriteMode: 'overwrite',
  2996. /**
  2997. * @cfg {String} [baseCls='x-component']
  2998. * The base CSS class to apply to this components's element. This will also be prepended to elements within this
  2999. * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
  3000. * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
  3001. * componentCls to add specific styling for this component.
  3002. */
  3003. baseCls: Ext.baseCSSPrefix + 'component',
  3004. /**
  3005. * @cfg {String} componentCls
  3006. * CSS Class to be added to a components root level element to give distinction to it via styling.
  3007. */
  3008. /**
  3009. * @cfg {String} [cls='']
  3010. * An optional extra CSS class that will be added to this component's Element. This can be useful
  3011. * for adding customized styles to the component or any of its children using standard CSS rules.
  3012. */
  3013. /**
  3014. * @cfg {String} [overCls='']
  3015. * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
  3016. * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
  3017. * component or any of its children using standard CSS rules.
  3018. */
  3019. /**
  3020. * @cfg {String} [disabledCls='x-item-disabled']
  3021. * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
  3022. */
  3023. disabledCls: Ext.baseCSSPrefix + 'item-disabled',
  3024. /**
  3025. * @cfg {String/String[]} ui
  3026. * A set style for a component. Can be a string or an Array of multiple strings (UIs)
  3027. */
  3028. ui: 'default',
  3029. /**
  3030. * @cfg {String[]} uiCls
  3031. * An array of of classNames which are currently applied to this component
  3032. * @private
  3033. */
  3034. uiCls: [],
  3035. /**
  3036. * @cfg {String} style
  3037. * A custom style specification to be applied to this component's Element. Should be a valid argument to
  3038. * {@link Ext.Element#applyStyles}.
  3039. *
  3040. * new Ext.panel.Panel({
  3041. * title: 'Some Title',
  3042. * renderTo: Ext.getBody(),
  3043. * width: 400, height: 300,
  3044. * layout: 'form',
  3045. * items: [{
  3046. * xtype: 'textarea',
  3047. * style: {
  3048. * width: '95%',
  3049. * marginBottom: '10px'
  3050. * }
  3051. * },
  3052. * new Ext.button.Button({
  3053. * text: 'Send',
  3054. * minWidth: '100',
  3055. * style: {
  3056. * marginBottom: '10px'
  3057. * }
  3058. * })
  3059. * ]
  3060. * });
  3061. */
  3062. /**
  3063. * @cfg {Number} width
  3064. * The width of this component in pixels.
  3065. */
  3066. /**
  3067. * @cfg {Number} height
  3068. * The height of this component in pixels.
  3069. */
  3070. /**
  3071. * @cfg {Number/String} border
  3072. * Specifies the border for this component. The border can be a single numeric value to apply to all sides or it can
  3073. * be a CSS style specification for each style, for example: '10 5 3 10'.
  3074. */
  3075. /**
  3076. * @cfg {Number/String} padding
  3077. * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
  3078. * can be a CSS style specification for each style, for example: '10 5 3 10'.
  3079. */
  3080. /**
  3081. * @cfg {Number/String} margin
  3082. * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
  3083. * be a CSS style specification for each style, for example: '10 5 3 10'.
  3084. */
  3085. /**
  3086. * @cfg {Boolean} hidden
  3087. * True to hide the component.
  3088. */
  3089. hidden: false,
  3090. /**
  3091. * @cfg {Boolean} disabled
  3092. * True to disable the component.
  3093. */
  3094. disabled: false,
  3095. /**
  3096. * @cfg {Boolean} [draggable=false]
  3097. * Allows the component to be dragged.
  3098. */
  3099. /**
  3100. * @property {Boolean} draggable
  3101. * Read-only property indicating whether or not the component can be dragged
  3102. */
  3103. draggable: false,
  3104. /**
  3105. * @cfg {Boolean} floating
  3106. * Create the Component as a floating and use absolute positioning.
  3107. *
  3108. * The z-index of floating Components is handled by a ZIndexManager. If you simply render a floating Component into the DOM, it will be managed
  3109. * by the global {@link Ext.WindowManager WindowManager}.
  3110. *
  3111. * If you include a floating Component as a child item of a Container, then upon render, ExtJS will seek an ancestor floating Component to house a new
  3112. * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
  3113. *
  3114. * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
  3115. */
  3116. floating: false,
  3117. /**
  3118. * @cfg {String} hideMode
  3119. * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
  3120. *
  3121. * - `'display'` : The Component will be hidden using the `display: none` style.
  3122. * - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
  3123. * - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
  3124. * This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
  3125. * Component having zero dimensions.
  3126. */
  3127. hideMode: 'display',
  3128. /**
  3129. * @cfg {String} contentEl
  3130. * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
  3131. *
  3132. * This config option is used to take an existing HTML element and place it in the layout element of a new component
  3133. * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
  3134. *
  3135. * **Notes:**
  3136. *
  3137. * The specified HTML element is appended to the layout element of the component _after any configured
  3138. * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
  3139. * the {@link #render} event is fired.
  3140. *
  3141. * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
  3142. * scheme that the Component may use. It is just HTML. Layouts operate on child
  3143. * **`{@link Ext.container.Container#items items}`**.
  3144. *
  3145. * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
  3146. * is rendered to the panel.
  3147. */
  3148. /**
  3149. * @cfg {String/Object} [html='']
  3150. * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
  3151. * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
  3152. * the {@link #render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
  3153. * is appended.
  3154. */
  3155. /**
  3156. * @cfg {Boolean} styleHtmlContent
  3157. * True to automatically style the html inside the content target of this component (body for panels).
  3158. */
  3159. styleHtmlContent: false,
  3160. /**
  3161. * @cfg {String} [styleHtmlCls='x-html']
  3162. * The class that is added to the content target when you set styleHtmlContent to true.
  3163. */
  3164. styleHtmlCls: Ext.baseCSSPrefix + 'html',
  3165. /**
  3166. * @cfg {Number} minHeight
  3167. * The minimum value in pixels which this Component will set its height to.
  3168. *
  3169. * **Warning:** This will override any size management applied by layout managers.
  3170. */
  3171. /**
  3172. * @cfg {Number} minWidth
  3173. * The minimum value in pixels which this Component will set its width to.
  3174. *
  3175. * **Warning:** This will override any size management applied by layout managers.
  3176. */
  3177. /**
  3178. * @cfg {Number} maxHeight
  3179. * The maximum value in pixels which this Component will set its height to.
  3180. *
  3181. * **Warning:** This will override any size management applied by layout managers.
  3182. */
  3183. /**
  3184. * @cfg {Number} maxWidth
  3185. * The maximum value in pixels which this Component will set its width to.
  3186. *
  3187. * **Warning:** This will override any size management applied by layout managers.
  3188. */
  3189. /**
  3190. * @cfg {Ext.ComponentLoader/Object} loader
  3191. * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content for this Component.
  3192. */
  3193. /**
  3194. * @cfg {Boolean} autoShow
  3195. * True to automatically show the component upon creation. This config option may only be used for
  3196. * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
  3197. */
  3198. autoShow: false,
  3199. /**
  3200. * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
  3201. * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
  3202. * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
  3203. * upon first _{@link #show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
  3204. *
  3205. * Specify as `true` to have this Component render to the document body upon first show.
  3206. *
  3207. * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
  3208. * show.
  3209. *
  3210. * **This defaults to `true` for the {@link Ext.window.Window Window} class.**
  3211. */
  3212. autoRender: false,
  3213. needsLayout: false,
  3214. // @private
  3215. allowDomMove: true,
  3216. /**
  3217. * @cfg {Object/Object[]} plugins
  3218. * An object or array of objects that will provide custom functionality for this component. The only requirement for
  3219. * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
  3220. * is created, if any plugins are available, the component will call the init method on each plugin, passing a
  3221. * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
  3222. * its functionality.
  3223. */
  3224. /**
  3225. * @property {Boolean} rendered
  3226. * Read-only property indicating whether or not the component has been rendered.
  3227. */
  3228. rendered: false,
  3229. /**
  3230. * @property {Number} componentLayoutCounter
  3231. * @private
  3232. * The number of component layout calls made on this object.
  3233. */
  3234. componentLayoutCounter: 0,
  3235. weight: 0,
  3236. trimRe: /^\s+|\s+$/g,
  3237. spacesRe: /\s+/,
  3238. /**
  3239. * @property {Boolean} maskOnDisable
  3240. * This is an internal flag that you use when creating custom components. By default this is set to true which means
  3241. * that every component gets a mask when its disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
  3242. * override this property to false since they want to implement custom disable logic.
  3243. */
  3244. maskOnDisable: true,
  3245. /**
  3246. * Creates new Component.
  3247. * @param {Object} config (optional) Config object.
  3248. */
  3249. constructor : function(config) {
  3250. var me = this,
  3251. i, len;
  3252. config = config || {};
  3253. me.initialConfig = config;
  3254. Ext.apply(me, config);
  3255. me.addEvents(
  3256. /**
  3257. * @event beforeactivate
  3258. * Fires before a Component has been visually activated. Returning false from an event listener can prevent
  3259. * the activate from occurring.
  3260. * @param {Ext.Component} this
  3261. */
  3262. 'beforeactivate',
  3263. /**
  3264. * @event activate
  3265. * Fires after a Component has been visually activated.
  3266. * @param {Ext.Component} this
  3267. */
  3268. 'activate',
  3269. /**
  3270. * @event beforedeactivate
  3271. * Fires before a Component has been visually deactivated. Returning false from an event listener can
  3272. * prevent the deactivate from occurring.
  3273. * @param {Ext.Component} this
  3274. */
  3275. 'beforedeactivate',
  3276. /**
  3277. * @event deactivate
  3278. * Fires after a Component has been visually deactivated.
  3279. * @param {Ext.Component} this
  3280. */
  3281. 'deactivate',
  3282. /**
  3283. * @event added
  3284. * Fires after a Component had been added to a Container.
  3285. * @param {Ext.Component} this
  3286. * @param {Ext.container.Container} container Parent Container
  3287. * @param {Number} pos position of Component
  3288. */
  3289. 'added',
  3290. /**
  3291. * @event disable
  3292. * Fires after the component is disabled.
  3293. * @param {Ext.Component} this
  3294. */
  3295. 'disable',
  3296. /**
  3297. * @event enable
  3298. * Fires after the component is enabled.
  3299. * @param {Ext.Component} this
  3300. */
  3301. 'enable',
  3302. /**
  3303. * @event beforeshow
  3304. * Fires before the component is shown when calling the {@link #show} method. Return false from an event
  3305. * handler to stop the show.
  3306. * @param {Ext.Component} this
  3307. */
  3308. 'beforeshow',
  3309. /**
  3310. * @event show
  3311. * Fires after the component is shown when calling the {@link #show} method.
  3312. * @param {Ext.Component} this
  3313. */
  3314. 'show',
  3315. /**
  3316. * @event beforehide
  3317. * Fires before the component is hidden when calling the {@link #hide} method. Return false from an event
  3318. * handler to stop the hide.
  3319. * @param {Ext.Component} this
  3320. */
  3321. 'beforehide',
  3322. /**
  3323. * @event hide
  3324. * Fires after the component is hidden. Fires after the component is hidden when calling the {@link #hide}
  3325. * method.
  3326. * @param {Ext.Component} this
  3327. */
  3328. 'hide',
  3329. /**
  3330. * @event removed
  3331. * Fires when a component is removed from an Ext.container.Container
  3332. * @param {Ext.Component} this
  3333. * @param {Ext.container.Container} ownerCt Container which holds the component
  3334. */
  3335. 'removed',
  3336. /**
  3337. * @event beforerender
  3338. * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
  3339. * {@link #render}.
  3340. * @param {Ext.Component} this
  3341. */
  3342. 'beforerender',
  3343. /**
  3344. * @event render
  3345. * Fires after the component markup is {@link #rendered}.
  3346. * @param {Ext.Component} this
  3347. */
  3348. 'render',
  3349. /**
  3350. * @event afterrender
  3351. * Fires after the component rendering is finished.
  3352. *
  3353. * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
  3354. * afterRender method defined for the Component.
  3355. * @param {Ext.Component} this
  3356. */
  3357. 'afterrender',
  3358. /**
  3359. * @event beforedestroy
  3360. * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the
  3361. * {@link #destroy}.
  3362. * @param {Ext.Component} this
  3363. */
  3364. 'beforedestroy',
  3365. /**
  3366. * @event destroy
  3367. * Fires after the component is {@link #destroy}ed.
  3368. * @param {Ext.Component} this
  3369. */
  3370. 'destroy',
  3371. /**
  3372. * @event resize
  3373. * Fires after the component is resized.
  3374. * @param {Ext.Component} this
  3375. * @param {Number} adjWidth The box-adjusted width that was set
  3376. * @param {Number} adjHeight The box-adjusted height that was set
  3377. */
  3378. 'resize',
  3379. /**
  3380. * @event move
  3381. * Fires after the component is moved.
  3382. * @param {Ext.Component} this
  3383. * @param {Number} x The new x position
  3384. * @param {Number} y The new y position
  3385. */
  3386. 'move'
  3387. );
  3388. me.getId();
  3389. me.mons = [];
  3390. me.additionalCls = [];
  3391. me.renderData = me.renderData || {};
  3392. me.renderSelectors = me.renderSelectors || {};
  3393. if (me.plugins) {
  3394. me.plugins = [].concat(me.plugins);
  3395. me.constructPlugins();
  3396. }
  3397. me.initComponent();
  3398. // ititComponent gets a chance to change the id property before registering
  3399. Ext.ComponentManager.register(me);
  3400. // Dont pass the config so that it is not applied to 'this' again
  3401. me.mixins.observable.constructor.call(me);
  3402. me.mixins.state.constructor.call(me, config);
  3403. // Save state on resize.
  3404. this.addStateEvents('resize');
  3405. // Move this into Observable?
  3406. if (me.plugins) {
  3407. me.plugins = [].concat(me.plugins);
  3408. for (i = 0, len = me.plugins.length; i < len; i++) {
  3409. me.plugins[i] = me.initPlugin(me.plugins[i]);
  3410. }
  3411. }
  3412. me.loader = me.getLoader();
  3413. if (me.renderTo) {
  3414. me.render(me.renderTo);
  3415. // EXTJSIV-1935 - should be a way to do afterShow or something, but that
  3416. // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
  3417. // implications to afterRender so we cannot do that.
  3418. }
  3419. if (me.autoShow) {
  3420. me.show();
  3421. }
  3422. //<debug>
  3423. if (Ext.isDefined(me.disabledClass)) {
  3424. if (Ext.isDefined(Ext.global.console)) {
  3425. Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
  3426. }
  3427. me.disabledCls = me.disabledClass;
  3428. delete me.disabledClass;
  3429. }
  3430. //</debug>
  3431. },
  3432. initComponent: function () {
  3433. // This is called again here to allow derived classes to add plugin configs to the
  3434. // plugins array before calling down to this, the base initComponent.
  3435. this.constructPlugins();
  3436. },
  3437. /**
  3438. * The supplied default state gathering method for the AbstractComponent class.
  3439. *
  3440. * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
  3441. * state.
  3442. *
  3443. * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
  3444. * to the result if this basic state is to be saved.
  3445. *
  3446. * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
  3447. * configured for the document.
  3448. *
  3449. * @return {Object}
  3450. */
  3451. getState: function() {
  3452. var me = this,
  3453. layout = me.ownerCt ? (me.shadowOwnerCt || me.ownerCt).getLayout() : null,
  3454. state = {
  3455. collapsed: me.collapsed
  3456. },
  3457. width = me.width,
  3458. height = me.height,
  3459. cm = me.collapseMemento,
  3460. anchors;
  3461. // If a Panel-local collapse has taken place, use remembered values as the dimensions.
  3462. // TODO: remove this coupling with Panel's privates! All collapse/expand logic should be refactored into one place.
  3463. if (me.collapsed && cm) {
  3464. if (Ext.isDefined(cm.data.width)) {
  3465. width = cm.width;
  3466. }
  3467. if (Ext.isDefined(cm.data.height)) {
  3468. height = cm.height;
  3469. }
  3470. }
  3471. // If we have flex, only store the perpendicular dimension.
  3472. if (layout && me.flex) {
  3473. state.flex = me.flex;
  3474. if (layout.perpendicularPrefix) {
  3475. state[layout.perpendicularPrefix] = me['get' + layout.perpendicularPrefixCap]();
  3476. } else {
  3477. //<debug>
  3478. if (Ext.isDefined(Ext.global.console)) {
  3479. Ext.global.console.warn('Ext.Component: Specified a flex value on a component not inside a Box layout');
  3480. }
  3481. //</debug>
  3482. }
  3483. }
  3484. // If we have anchor, only store dimensions which are *not* being anchored
  3485. else if (layout && me.anchor) {
  3486. state.anchor = me.anchor;
  3487. anchors = me.anchor.split(' ').concat(null);
  3488. if (!anchors[0]) {
  3489. if (me.width) {
  3490. state.width = width;
  3491. }
  3492. }
  3493. if (!anchors[1]) {
  3494. if (me.height) {
  3495. state.height = height;
  3496. }
  3497. }
  3498. }
  3499. // Store dimensions.
  3500. else {
  3501. if (me.width) {
  3502. state.width = width;
  3503. }
  3504. if (me.height) {
  3505. state.height = height;
  3506. }
  3507. }
  3508. // Don't save dimensions if they are unchanged from the original configuration.
  3509. if (state.width == me.initialConfig.width) {
  3510. delete state.width;
  3511. }
  3512. if (state.height == me.initialConfig.height) {
  3513. delete state.height;
  3514. }
  3515. // If a Box layout was managing the perpendicular dimension, don't save that dimension
  3516. if (layout && layout.align && (layout.align.indexOf('stretch') !== -1)) {
  3517. delete state[layout.perpendicularPrefix];
  3518. }
  3519. return state;
  3520. },
  3521. show: Ext.emptyFn,
  3522. animate: function(animObj) {
  3523. var me = this,
  3524. to;
  3525. animObj = animObj || {};
  3526. to = animObj.to || {};
  3527. if (Ext.fx.Manager.hasFxBlock(me.id)) {
  3528. return me;
  3529. }
  3530. // Special processing for animating Component dimensions.
  3531. if (!animObj.dynamic && (to.height || to.width)) {
  3532. var curWidth = me.getWidth(),
  3533. w = curWidth,
  3534. curHeight = me.getHeight(),
  3535. h = curHeight,
  3536. needsResize = false;
  3537. if (to.height && to.height > curHeight) {
  3538. h = to.height;
  3539. needsResize = true;
  3540. }
  3541. if (to.width && to.width > curWidth) {
  3542. w = to.width;
  3543. needsResize = true;
  3544. }
  3545. // If any dimensions are being increased, we must resize the internal structure
  3546. // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
  3547. // The animation will then progressively reveal the larger content.
  3548. if (needsResize) {
  3549. var clearWidth = !Ext.isNumber(me.width),
  3550. clearHeight = !Ext.isNumber(me.height);
  3551. me.componentLayout.childrenChanged = true;
  3552. me.setSize(w, h, me.ownerCt);
  3553. me.el.setSize(curWidth, curHeight);
  3554. if (clearWidth) {
  3555. delete me.width;
  3556. }
  3557. if (clearHeight) {
  3558. delete me.height;
  3559. }
  3560. }
  3561. }
  3562. return me.mixins.animate.animate.apply(me, arguments);
  3563. },
  3564. /**
  3565. * This method finds the topmost active layout who's processing will eventually determine the size and position of
  3566. * this Component.
  3567. *
  3568. * This method is useful when dynamically adding Components into Containers, and some processing must take place
  3569. * after the final sizing and positioning of the Component has been performed.
  3570. *
  3571. * @return {Ext.Component}
  3572. */
  3573. findLayoutController: function() {
  3574. return this.findParentBy(function(c) {
  3575. // Return true if we are at the root of the Container tree
  3576. // or this Container's layout is busy but the next one up is not.
  3577. return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
  3578. });
  3579. },
  3580. onShow : function() {
  3581. // Layout if needed
  3582. var needsLayout = this.needsLayout;
  3583. if (Ext.isObject(needsLayout)) {
  3584. this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
  3585. }
  3586. },
  3587. constructPlugin: function(plugin) {
  3588. if (plugin.ptype && typeof plugin.init != 'function') {
  3589. plugin.cmp = this;
  3590. plugin = Ext.PluginManager.create(plugin);
  3591. }
  3592. else if (typeof plugin == 'string') {
  3593. plugin = Ext.PluginManager.create({
  3594. ptype: plugin,
  3595. cmp: this
  3596. });
  3597. }
  3598. return plugin;
  3599. },
  3600. /**
  3601. * Ensures that the plugins array contains fully constructed plugin instances. This converts any configs into their
  3602. * appropriate instances.
  3603. */
  3604. constructPlugins: function() {
  3605. var me = this,
  3606. plugins = me.plugins,
  3607. i, len;
  3608. if (plugins) {
  3609. for (i = 0, len = plugins.length; i < len; i++) {
  3610. // this just returns already-constructed plugin instances...
  3611. plugins[i] = me.constructPlugin(plugins[i]);
  3612. }
  3613. }
  3614. },
  3615. // @private
  3616. initPlugin : function(plugin) {
  3617. plugin.init(this);
  3618. return plugin;
  3619. },
  3620. /**
  3621. * Handles autoRender. Floating Components may have an ownerCt. If they are asking to be constrained, constrain them
  3622. * within that ownerCt, and have their z-index managed locally. Floating Components are always rendered to
  3623. * document.body
  3624. */
  3625. doAutoRender: function() {
  3626. var me = this;
  3627. if (me.floating) {
  3628. me.render(document.body);
  3629. } else {
  3630. me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
  3631. }
  3632. },
  3633. // @private
  3634. render : function(container, position) {
  3635. var me = this;
  3636. if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
  3637. // Flag set during the render process.
  3638. // It can be used to inhibit event-driven layout calls during the render phase
  3639. me.rendering = true;
  3640. // If this.el is defined, we want to make sure we are dealing with
  3641. // an Ext Element.
  3642. if (me.el) {
  3643. me.el = Ext.get(me.el);
  3644. }
  3645. // Perform render-time processing for floating Components
  3646. if (me.floating) {
  3647. me.onFloatRender();
  3648. }
  3649. container = me.initContainer(container);
  3650. me.onRender(container, position);
  3651. // Tell the encapsulating element to hide itself in the way the Component is configured to hide
  3652. // This means DISPLAY, VISIBILITY or OFFSETS.
  3653. me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
  3654. if (me.overCls) {
  3655. me.el.hover(me.addOverCls, me.removeOverCls, me);
  3656. }
  3657. me.fireEvent('render', me);
  3658. me.initContent();
  3659. me.afterRender(container);
  3660. me.fireEvent('afterrender', me);
  3661. me.initEvents();
  3662. if (me.hidden) {
  3663. // Hiding during the render process should not perform any ancillary
  3664. // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
  3665. // So just make the element hidden according to the configured hideMode
  3666. me.el.hide();
  3667. }
  3668. if (me.disabled) {
  3669. // pass silent so the event doesn't fire the first time.
  3670. me.disable(true);
  3671. }
  3672. // Delete the flag once the rendering is done.
  3673. delete me.rendering;
  3674. }
  3675. return me;
  3676. },
  3677. // @private
  3678. onRender : function(container, position) {
  3679. var me = this,
  3680. el = me.el,
  3681. styles = me.initStyles(),
  3682. renderTpl, renderData, i;
  3683. position = me.getInsertPosition(position);
  3684. if (!el) {
  3685. if (position) {
  3686. el = Ext.DomHelper.insertBefore(position, me.getElConfig(), true);
  3687. }
  3688. else {
  3689. el = Ext.DomHelper.append(container, me.getElConfig(), true);
  3690. }
  3691. }
  3692. else if (me.allowDomMove !== false) {
  3693. if (position) {
  3694. container.dom.insertBefore(el.dom, position);
  3695. } else {
  3696. container.dom.appendChild(el.dom);
  3697. }
  3698. }
  3699. if (Ext.scopeResetCSS && !me.ownerCt) {
  3700. // If this component's el is the body element, we add the reset class to the html tag
  3701. if (el.dom == Ext.getBody().dom) {
  3702. el.parent().addCls(Ext.baseCSSPrefix + 'reset');
  3703. }
  3704. else {
  3705. // Else we wrap this element in an element that adds the reset class.
  3706. me.resetEl = el.wrap({
  3707. cls: Ext.baseCSSPrefix + 'reset'
  3708. });
  3709. }
  3710. }
  3711. me.setUI(me.ui);
  3712. el.addCls(me.initCls());
  3713. el.setStyle(styles);
  3714. // Here we check if the component has a height set through style or css.
  3715. // If it does then we set the this.height to that value and it won't be
  3716. // considered an auto height component
  3717. // if (this.height === undefined) {
  3718. // var height = el.getHeight();
  3719. // // This hopefully means that the panel has an explicit height set in style or css
  3720. // if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
  3721. // this.height = height;
  3722. // }
  3723. // }
  3724. me.el = el;
  3725. me.initFrame();
  3726. renderTpl = me.initRenderTpl();
  3727. if (renderTpl) {
  3728. renderData = me.initRenderData();
  3729. renderTpl.append(me.getTargetEl(), renderData);
  3730. }
  3731. me.applyRenderSelectors();
  3732. me.rendered = true;
  3733. },
  3734. // @private
  3735. afterRender : function() {
  3736. var me = this,
  3737. pos,
  3738. xy;
  3739. me.getComponentLayout();
  3740. // Set the size if a size is configured, or if this is the outermost Container.
  3741. // Also, if this is a collapsed Panel, it needs an initial component layout
  3742. // to lay out its header so that it can have a height determined.
  3743. if (me.collapsed || (!me.ownerCt || (me.height || me.width))) {
  3744. me.setSize(me.width, me.height);
  3745. } else {
  3746. // It is expected that child items be rendered before this method returns and
  3747. // the afterrender event fires. Since we aren't going to do the layout now, we
  3748. // must render the child items. This is handled implicitly above in the layout
  3749. // caused by setSize.
  3750. me.renderChildren();
  3751. }
  3752. // For floaters, calculate x and y if they aren't defined by aligning
  3753. // the sized element to the center of either the container or the ownerCt
  3754. if (me.floating && (me.x === undefined || me.y === undefined)) {
  3755. if (me.floatParent) {
  3756. xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
  3757. pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
  3758. } else {
  3759. xy = me.el.getAlignToXY(me.container, 'c-c');
  3760. pos = me.container.translatePoints(xy[0], xy[1]);
  3761. }
  3762. me.x = me.x === undefined ? pos.left: me.x;
  3763. me.y = me.y === undefined ? pos.top: me.y;
  3764. }
  3765. if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
  3766. me.setPosition(me.x, me.y);
  3767. }
  3768. if (me.styleHtmlContent) {
  3769. me.getTargetEl().addCls(me.styleHtmlCls);
  3770. }
  3771. },
  3772. /**
  3773. * @private
  3774. * Called by Component#doAutoRender
  3775. *
  3776. * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
  3777. *
  3778. * Components added in ths way will not participate in any layout, but will be rendered
  3779. * upon first show in the way that {@link Ext.window.Window Window}s are.
  3780. */
  3781. registerFloatingItem: function(cmp) {
  3782. var me = this;
  3783. if (!me.floatingItems) {
  3784. me.floatingItems = Ext.create('Ext.ZIndexManager', me);
  3785. }
  3786. me.floatingItems.register(cmp);
  3787. },
  3788. renderChildren: function () {
  3789. var me = this,
  3790. layout = me.getComponentLayout();
  3791. me.suspendLayout = true;
  3792. layout.renderChildren();
  3793. delete me.suspendLayout;
  3794. },
  3795. frameCls: Ext.baseCSSPrefix + 'frame',
  3796. frameIdRegex: /[-]frame\d+[TMB][LCR]$/,
  3797. frameElementCls: {
  3798. tl: [],
  3799. tc: [],
  3800. tr: [],
  3801. ml: [],
  3802. mc: [],
  3803. mr: [],
  3804. bl: [],
  3805. bc: [],
  3806. br: []
  3807. },
  3808. frameTpl: [
  3809. '<tpl if="top">',
  3810. '<tpl if="left"><div id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
  3811. '<tpl if="right"><div id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
  3812. '<div id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></div>',
  3813. '<tpl if="right"></div></tpl>',
  3814. '<tpl if="left"></div></tpl>',
  3815. '</tpl>',
  3816. '<tpl if="left"><div id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></tpl>',
  3817. '<tpl if="right"><div id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
  3818. '<div id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" role="presentation"></div>',
  3819. '<tpl if="right"></div></tpl>',
  3820. '<tpl if="left"></div></tpl>',
  3821. '<tpl if="bottom">',
  3822. '<tpl if="left"><div id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
  3823. '<tpl if="right"><div id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-right: {frameWidth}px" role="presentation"></tpl>',
  3824. '<div id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></div>',
  3825. '<tpl if="right"></div></tpl>',
  3826. '<tpl if="left"></div></tpl>',
  3827. '</tpl>'
  3828. ],
  3829. frameTableTpl: [
  3830. '<table><tbody>',
  3831. '<tpl if="top">',
  3832. '<tr>',
  3833. '<tpl if="left"><td id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left:{frameWidth}px" role="presentation"></td></tpl>',
  3834. '<td id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></td>',
  3835. '<tpl if="right"><td id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  3836. '</tr>',
  3837. '</tpl>',
  3838. '<tr>',
  3839. '<tpl if="left"><td id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  3840. '<td id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" style="background-position: 0 0;" role="presentation"></td>',
  3841. '<tpl if="right"><td id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  3842. '</tr>',
  3843. '<tpl if="bottom">',
  3844. '<tr>',
  3845. '<tpl if="left"><td id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  3846. '<td id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></td>',
  3847. '<tpl if="right"><td id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
  3848. '</tr>',
  3849. '</tpl>',
  3850. '</tbody></table>'
  3851. ],
  3852. /**
  3853. * @private
  3854. */
  3855. initFrame : function() {
  3856. if (Ext.supports.CSS3BorderRadius) {
  3857. return false;
  3858. }
  3859. var me = this,
  3860. frameInfo = me.getFrameInfo(),
  3861. frameWidth = frameInfo.width,
  3862. frameTpl = me.getFrameTpl(frameInfo.table),
  3863. frameGenId;
  3864. if (me.frame) {
  3865. // since we render id's into the markup and id's NEED to be unique, we have a
  3866. // simple strategy for numbering their generations.
  3867. me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
  3868. frameGenId = me.id + '-frame' + frameGenId;
  3869. // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
  3870. frameTpl.insertFirst(me.el, Ext.apply({}, {
  3871. fgid: frameGenId,
  3872. ui: me.ui,
  3873. uiCls: me.uiCls,
  3874. frameCls: me.frameCls,
  3875. baseCls: me.baseCls,
  3876. frameWidth: frameWidth,
  3877. top: !!frameInfo.top,
  3878. left: !!frameInfo.left,
  3879. right: !!frameInfo.right,
  3880. bottom: !!frameInfo.bottom
  3881. }, me.getFramePositions(frameInfo)));
  3882. // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
  3883. me.frameBody = me.el.down('.' + me.frameCls + '-mc');
  3884. // Clean out the childEls for the old frame elements (the majority of the els)
  3885. me.removeChildEls(function (c) {
  3886. return c.id && me.frameIdRegex.test(c.id);
  3887. });
  3888. // Add the childEls for each of the new frame elements
  3889. Ext.each(['TL','TC','TR','ML','MC','MR','BL','BC','BR'], function (suffix) {
  3890. me.childEls.push({ name: 'frame' + suffix, id: frameGenId + suffix });
  3891. });
  3892. }
  3893. },
  3894. updateFrame: function() {
  3895. if (Ext.supports.CSS3BorderRadius) {
  3896. return false;
  3897. }
  3898. var me = this,
  3899. wasTable = this.frameSize && this.frameSize.table,
  3900. oldFrameTL = this.frameTL,
  3901. oldFrameBL = this.frameBL,
  3902. oldFrameML = this.frameML,
  3903. oldFrameMC = this.frameMC,
  3904. newMCClassName;
  3905. this.initFrame();
  3906. if (oldFrameMC) {
  3907. if (me.frame) {
  3908. // Reapply render selectors
  3909. delete me.frameTL;
  3910. delete me.frameTC;
  3911. delete me.frameTR;
  3912. delete me.frameML;
  3913. delete me.frameMC;
  3914. delete me.frameMR;
  3915. delete me.frameBL;
  3916. delete me.frameBC;
  3917. delete me.frameBR;
  3918. this.applyRenderSelectors();
  3919. // Store the class names set on the new mc
  3920. newMCClassName = this.frameMC.dom.className;
  3921. // Replace the new mc with the old mc
  3922. oldFrameMC.insertAfter(this.frameMC);
  3923. this.frameMC.remove();
  3924. // Restore the reference to the old frame mc as the framebody
  3925. this.frameBody = this.frameMC = oldFrameMC;
  3926. // Apply the new mc classes to the old mc element
  3927. oldFrameMC.dom.className = newMCClassName;
  3928. // Remove the old framing
  3929. if (wasTable) {
  3930. me.el.query('> table')[1].remove();
  3931. }
  3932. else {
  3933. if (oldFrameTL) {
  3934. oldFrameTL.remove();
  3935. }
  3936. if (oldFrameBL) {
  3937. oldFrameBL.remove();
  3938. }
  3939. oldFrameML.remove();
  3940. }
  3941. }
  3942. else {
  3943. // We were framed but not anymore. Move all content from the old frame to the body
  3944. }
  3945. }
  3946. else if (me.frame) {
  3947. this.applyRenderSelectors();
  3948. }
  3949. },
  3950. getFrameInfo: function() {
  3951. if (Ext.supports.CSS3BorderRadius) {
  3952. return false;
  3953. }
  3954. var me = this,
  3955. left = me.el.getStyle('background-position-x'),
  3956. top = me.el.getStyle('background-position-y'),
  3957. info, frameInfo = false, max;
  3958. // Some browsers dont support background-position-x and y, so for those
  3959. // browsers let's split background-position into two parts.
  3960. if (!left && !top) {
  3961. info = me.el.getStyle('background-position').split(' ');
  3962. left = info[0];
  3963. top = info[1];
  3964. }
  3965. // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
  3966. // the background position of this.el from the css to indicate to IE that this component needs
  3967. // framing. We parse it here and change the markup accordingly.
  3968. if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
  3969. max = Math.max;
  3970. frameInfo = {
  3971. // Table markup starts with 110, div markup with 100.
  3972. table: left.substr(0, 3) == '110',
  3973. // Determine if we are dealing with a horizontal or vertical component
  3974. vertical: top.substr(0, 3) == '110',
  3975. // Get and parse the different border radius sizes
  3976. top: max(left.substr(3, 2), left.substr(5, 2)),
  3977. right: max(left.substr(5, 2), top.substr(3, 2)),
  3978. bottom: max(top.substr(3, 2), top.substr(5, 2)),
  3979. left: max(top.substr(5, 2), left.substr(3, 2))
  3980. };
  3981. frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
  3982. // Just to be sure we set the background image of the el to none.
  3983. me.el.setStyle('background-image', 'none');
  3984. }
  3985. // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
  3986. // This way IE can't figure out what sizes to use and thus framing can't work.
  3987. if (me.frame === true && !frameInfo) {
  3988. //<debug error>
  3989. Ext.Error.raise("You have set frame: true explicity on this component while it doesn't have any " +
  3990. "framing defined in the CSS template. In this case IE can't figure out what sizes " +
  3991. "to use and thus framing on this component will be disabled.");
  3992. //</debug>
  3993. }
  3994. me.frame = me.frame || !!frameInfo;
  3995. me.frameSize = frameInfo || false;
  3996. return frameInfo;
  3997. },
  3998. getFramePositions: function(frameInfo) {
  3999. var me = this,
  4000. frameWidth = frameInfo.width,
  4001. dock = me.dock,
  4002. positions, tc, bc, ml, mr;
  4003. if (frameInfo.vertical) {
  4004. tc = '0 -' + (frameWidth * 0) + 'px';
  4005. bc = '0 -' + (frameWidth * 1) + 'px';
  4006. if (dock && dock == "right") {
  4007. tc = 'right -' + (frameWidth * 0) + 'px';
  4008. bc = 'right -' + (frameWidth * 1) + 'px';
  4009. }
  4010. positions = {
  4011. tl: '0 -' + (frameWidth * 0) + 'px',
  4012. tr: '0 -' + (frameWidth * 1) + 'px',
  4013. bl: '0 -' + (frameWidth * 2) + 'px',
  4014. br: '0 -' + (frameWidth * 3) + 'px',
  4015. ml: '-' + (frameWidth * 1) + 'px 0',
  4016. mr: 'right 0',
  4017. tc: tc,
  4018. bc: bc
  4019. };
  4020. } else {
  4021. ml = '-' + (frameWidth * 0) + 'px 0';
  4022. mr = 'right 0';
  4023. if (dock && dock == "bottom") {
  4024. ml = 'left bottom';
  4025. mr = 'right bottom';
  4026. }
  4027. positions = {
  4028. tl: '0 -' + (frameWidth * 2) + 'px',
  4029. tr: 'right -' + (frameWidth * 3) + 'px',
  4030. bl: '0 -' + (frameWidth * 4) + 'px',
  4031. br: 'right -' + (frameWidth * 5) + 'px',
  4032. ml: ml,
  4033. mr: mr,
  4034. tc: '0 -' + (frameWidth * 0) + 'px',
  4035. bc: '0 -' + (frameWidth * 1) + 'px'
  4036. };
  4037. }
  4038. return positions;
  4039. },
  4040. /**
  4041. * @private
  4042. */
  4043. getFrameTpl : function(table) {
  4044. return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
  4045. },
  4046. /**
  4047. * Creates an array of class names from the configurations to add to this Component's `el` on render.
  4048. *
  4049. * Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.
  4050. *
  4051. * @return {String[]} An array of class names with which the Component's element will be rendered.
  4052. * @private
  4053. */
  4054. initCls: function() {
  4055. var me = this,
  4056. cls = [];
  4057. cls.push(me.baseCls);
  4058. //<deprecated since=0.99>
  4059. if (Ext.isDefined(me.cmpCls)) {
  4060. if (Ext.isDefined(Ext.global.console)) {
  4061. Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
  4062. }
  4063. me.componentCls = me.cmpCls;
  4064. delete me.cmpCls;
  4065. }
  4066. //</deprecated>
  4067. if (me.componentCls) {
  4068. cls.push(me.componentCls);
  4069. } else {
  4070. me.componentCls = me.baseCls;
  4071. }
  4072. if (me.cls) {
  4073. cls.push(me.cls);
  4074. delete me.cls;
  4075. }
  4076. return cls.concat(me.additionalCls);
  4077. },
  4078. /**
  4079. * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
  4080. * uiCls set on the component and rename them so they include the new UI
  4081. * @param {String} ui The new UI for the component
  4082. */
  4083. setUI: function(ui) {
  4084. var me = this,
  4085. oldUICls = Ext.Array.clone(me.uiCls),
  4086. newUICls = [],
  4087. classes = [],
  4088. cls,
  4089. i;
  4090. //loop through all exisiting uiCls and update the ui in them
  4091. for (i = 0; i < oldUICls.length; i++) {
  4092. cls = oldUICls[i];
  4093. classes = classes.concat(me.removeClsWithUI(cls, true));
  4094. newUICls.push(cls);
  4095. }
  4096. if (classes.length) {
  4097. me.removeCls(classes);
  4098. }
  4099. //remove the UI from the element
  4100. me.removeUIFromElement();
  4101. //set the UI
  4102. me.ui = ui;
  4103. //add the new UI to the elemend
  4104. me.addUIToElement();
  4105. //loop through all exisiting uiCls and update the ui in them
  4106. classes = [];
  4107. for (i = 0; i < newUICls.length; i++) {
  4108. cls = newUICls[i];
  4109. classes = classes.concat(me.addClsWithUI(cls, true));
  4110. }
  4111. if (classes.length) {
  4112. me.addCls(classes);
  4113. }
  4114. },
  4115. /**
  4116. * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
  4117. * component.
  4118. * @param {String/String[]} cls A string or an array of strings to add to the uiCls
  4119. * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
  4120. */
  4121. addClsWithUI: function(cls, skip) {
  4122. var me = this,
  4123. classes = [],
  4124. i;
  4125. if (!Ext.isArray(cls)) {
  4126. cls = [cls];
  4127. }
  4128. for (i = 0; i < cls.length; i++) {
  4129. if (cls[i] && !me.hasUICls(cls[i])) {
  4130. me.uiCls = Ext.Array.clone(me.uiCls);
  4131. me.uiCls.push(cls[i]);
  4132. classes = classes.concat(me.addUIClsToElement(cls[i]));
  4133. }
  4134. }
  4135. if (skip !== true) {
  4136. me.addCls(classes);
  4137. }
  4138. return classes;
  4139. },
  4140. /**
  4141. * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
  4142. * elements of this component.
  4143. * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
  4144. */
  4145. removeClsWithUI: function(cls, skip) {
  4146. var me = this,
  4147. classes = [],
  4148. i;
  4149. if (!Ext.isArray(cls)) {
  4150. cls = [cls];
  4151. }
  4152. for (i = 0; i < cls.length; i++) {
  4153. if (cls[i] && me.hasUICls(cls[i])) {
  4154. me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
  4155. classes = classes.concat(me.removeUIClsFromElement(cls[i]));
  4156. }
  4157. }
  4158. if (skip !== true) {
  4159. me.removeCls(classes);
  4160. }
  4161. return classes;
  4162. },
  4163. /**
  4164. * Checks if there is currently a specified uiCls
  4165. * @param {String} cls The cls to check
  4166. */
  4167. hasUICls: function(cls) {
  4168. var me = this,
  4169. uiCls = me.uiCls || [];
  4170. return Ext.Array.contains(uiCls, cls);
  4171. },
  4172. /**
  4173. * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
  4174. * than just the components element.
  4175. * @param {String} ui The UI to remove from the element
  4176. */
  4177. addUIClsToElement: function(cls, force) {
  4178. var me = this,
  4179. result = [],
  4180. frameElementCls = me.frameElementCls;
  4181. result.push(Ext.baseCSSPrefix + cls);
  4182. result.push(me.baseCls + '-' + cls);
  4183. result.push(me.baseCls + '-' + me.ui + '-' + cls);
  4184. if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
  4185. // define each element of the frame
  4186. var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
  4187. classes, i, j, el;
  4188. // loop through each of them, and if they are defined add the ui
  4189. for (i = 0; i < els.length; i++) {
  4190. el = me['frame' + els[i].toUpperCase()];
  4191. classes = [me.baseCls + '-' + me.ui + '-' + els[i], me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]];
  4192. if (el && el.dom) {
  4193. el.addCls(classes);
  4194. } else {
  4195. for (j = 0; j < classes.length; j++) {
  4196. if (Ext.Array.indexOf(frameElementCls[els[i]], classes[j]) == -1) {
  4197. frameElementCls[els[i]].push(classes[j]);
  4198. }
  4199. }
  4200. }
  4201. }
  4202. }
  4203. me.frameElementCls = frameElementCls;
  4204. return result;
  4205. },
  4206. /**
  4207. * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
  4208. * will be: `this.baseCls + '-' + ui`
  4209. * @param {String} ui The UI to add to the element
  4210. */
  4211. removeUIClsFromElement: function(cls, force) {
  4212. var me = this,
  4213. result = [],
  4214. frameElementCls = me.frameElementCls;
  4215. result.push(Ext.baseCSSPrefix + cls);
  4216. result.push(me.baseCls + '-' + cls);
  4217. result.push(me.baseCls + '-' + me.ui + '-' + cls);
  4218. if (!force && me.frame && !Ext.supports.CSS3BorderRadius) {
  4219. // define each element of the frame
  4220. var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
  4221. i, el;
  4222. cls = me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i];
  4223. // loop through each of them, and if they are defined add the ui
  4224. for (i = 0; i < els.length; i++) {
  4225. el = me['frame' + els[i].toUpperCase()];
  4226. if (el && el.dom) {
  4227. el.removeCls(cls);
  4228. } else {
  4229. Ext.Array.remove(frameElementCls[els[i]], cls);
  4230. }
  4231. }
  4232. }
  4233. me.frameElementCls = frameElementCls;
  4234. return result;
  4235. },
  4236. /**
  4237. * Method which adds a specified UI to the components element.
  4238. * @private
  4239. */
  4240. addUIToElement: function(force) {
  4241. var me = this,
  4242. frameElementCls = me.frameElementCls;
  4243. me.addCls(me.baseCls + '-' + me.ui);
  4244. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  4245. // define each element of the frame
  4246. var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
  4247. i, el, cls;
  4248. // loop through each of them, and if they are defined add the ui
  4249. for (i = 0; i < els.length; i++) {
  4250. el = me['frame' + els[i].toUpperCase()];
  4251. cls = me.baseCls + '-' + me.ui + '-' + els[i];
  4252. if (el) {
  4253. el.addCls(cls);
  4254. } else {
  4255. if (!Ext.Array.contains(frameElementCls[els[i]], cls)) {
  4256. frameElementCls[els[i]].push(cls);
  4257. }
  4258. }
  4259. }
  4260. }
  4261. },
  4262. /**
  4263. * Method which removes a specified UI from the components element.
  4264. * @private
  4265. */
  4266. removeUIFromElement: function() {
  4267. var me = this,
  4268. frameElementCls = me.frameElementCls;
  4269. me.removeCls(me.baseCls + '-' + me.ui);
  4270. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  4271. // define each element of the frame
  4272. var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
  4273. i, j, el, cls;
  4274. // loop through each of them, and if they are defined add the ui
  4275. for (i = 0; i < els.length; i++) {
  4276. el = me['frame' + els[i].toUpperCase()];
  4277. cls = me.baseCls + '-' + me.ui + '-' + els[i];
  4278. if (el) {
  4279. el.removeCls(cls);
  4280. } else {
  4281. Ext.Array.remove(frameElementCls[els[i]], cls);
  4282. }
  4283. }
  4284. }
  4285. },
  4286. getElConfig : function() {
  4287. if (Ext.isString(this.autoEl)) {
  4288. this.autoEl = {
  4289. tag: this.autoEl
  4290. };
  4291. }
  4292. var result = this.autoEl || {tag: 'div'};
  4293. result.id = this.id;
  4294. return result;
  4295. },
  4296. /**
  4297. * This function takes the position argument passed to onRender and returns a DOM element that you can use in the
  4298. * insertBefore.
  4299. * @param {String/Number/Ext.Element/HTMLElement} position Index, element id or element you want to put this
  4300. * component before.
  4301. * @return {HTMLElement} DOM element that you can use in the insertBefore
  4302. */
  4303. getInsertPosition: function(position) {
  4304. // Convert the position to an element to insert before
  4305. if (position !== undefined) {
  4306. if (Ext.isNumber(position)) {
  4307. position = this.container.dom.childNodes[position];
  4308. }
  4309. else {
  4310. position = Ext.getDom(position);
  4311. }
  4312. }
  4313. return position;
  4314. },
  4315. /**
  4316. * Adds ctCls to container.
  4317. * @return {Ext.Element} The initialized container
  4318. * @private
  4319. */
  4320. initContainer: function(container) {
  4321. var me = this;
  4322. // If you render a component specifying the el, we get the container
  4323. // of the el, and make sure we dont move the el around in the dom
  4324. // during the render
  4325. if (!container && me.el) {
  4326. container = me.el.dom.parentNode;
  4327. me.allowDomMove = false;
  4328. }
  4329. me.container = Ext.get(container);
  4330. if (me.ctCls) {
  4331. me.container.addCls(me.ctCls);
  4332. }
  4333. return me.container;
  4334. },
  4335. /**
  4336. * Initialized the renderData to be used when rendering the renderTpl.
  4337. * @return {Object} Object with keys and values that are going to be applied to the renderTpl
  4338. * @private
  4339. */
  4340. initRenderData: function() {
  4341. var me = this;
  4342. return Ext.applyIf(me.renderData, {
  4343. id: me.id,
  4344. ui: me.ui,
  4345. uiCls: me.uiCls,
  4346. baseCls: me.baseCls,
  4347. componentCls: me.componentCls,
  4348. frame: me.frame
  4349. });
  4350. },
  4351. /**
  4352. * @private
  4353. */
  4354. getTpl: function(name) {
  4355. var me = this,
  4356. prototype = me.self.prototype,
  4357. ownerPrototype,
  4358. tpl;
  4359. if (me.hasOwnProperty(name)) {
  4360. tpl = me[name];
  4361. if (tpl && !(tpl instanceof Ext.XTemplate)) {
  4362. me[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
  4363. }
  4364. return me[name];
  4365. }
  4366. if (!(prototype[name] instanceof Ext.XTemplate)) {
  4367. ownerPrototype = prototype;
  4368. do {
  4369. if (ownerPrototype.hasOwnProperty(name)) {
  4370. tpl = ownerPrototype[name];
  4371. if (tpl && !(tpl instanceof Ext.XTemplate)) {
  4372. ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
  4373. break;
  4374. }
  4375. }
  4376. ownerPrototype = ownerPrototype.superclass;
  4377. } while (ownerPrototype);
  4378. }
  4379. return prototype[name];
  4380. },
  4381. /**
  4382. * Initializes the renderTpl.
  4383. * @return {Ext.XTemplate} The renderTpl XTemplate instance.
  4384. * @private
  4385. */
  4386. initRenderTpl: function() {
  4387. return this.getTpl('renderTpl');
  4388. },
  4389. /**
  4390. * Converts style definitions to String.
  4391. * @return {String} A CSS style string with style, padding, margin and border.
  4392. * @private
  4393. */
  4394. initStyles: function() {
  4395. var style = {},
  4396. me = this,
  4397. Element = Ext.Element;
  4398. if (Ext.isString(me.style)) {
  4399. style = Element.parseStyles(me.style);
  4400. } else {
  4401. style = Ext.apply({}, me.style);
  4402. }
  4403. // Convert the padding, margin and border properties from a space seperated string
  4404. // into a proper style string
  4405. if (me.padding !== undefined) {
  4406. style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
  4407. }
  4408. if (me.margin !== undefined) {
  4409. style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
  4410. }
  4411. delete me.style;
  4412. return style;
  4413. },
  4414. /**
  4415. * Initializes this components contents. It checks for the properties html, contentEl and tpl/data.
  4416. * @private
  4417. */
  4418. initContent: function() {
  4419. var me = this,
  4420. target = me.getTargetEl(),
  4421. contentEl,
  4422. pre;
  4423. if (me.html) {
  4424. target.update(Ext.DomHelper.markup(me.html));
  4425. delete me.html;
  4426. }
  4427. if (me.contentEl) {
  4428. contentEl = Ext.get(me.contentEl);
  4429. pre = Ext.baseCSSPrefix;
  4430. contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
  4431. target.appendChild(contentEl.dom);
  4432. }
  4433. if (me.tpl) {
  4434. // Make sure this.tpl is an instantiated XTemplate
  4435. if (!me.tpl.isTemplate) {
  4436. me.tpl = Ext.create('Ext.XTemplate', me.tpl);
  4437. }
  4438. if (me.data) {
  4439. me.tpl[me.tplWriteMode](target, me.data);
  4440. delete me.data;
  4441. }
  4442. }
  4443. },
  4444. // @private
  4445. initEvents : function() {
  4446. var me = this,
  4447. afterRenderEvents = me.afterRenderEvents,
  4448. el,
  4449. property,
  4450. fn = function(listeners){
  4451. me.mon(el, listeners);
  4452. };
  4453. if (afterRenderEvents) {
  4454. for (property in afterRenderEvents) {
  4455. if (afterRenderEvents.hasOwnProperty(property)) {
  4456. el = me[property];
  4457. if (el && el.on) {
  4458. Ext.each(afterRenderEvents[property], fn);
  4459. }
  4460. }
  4461. }
  4462. }
  4463. },
  4464. /**
  4465. * Adds each argument passed to this method to the {@link #childEls} array.
  4466. */
  4467. addChildEls: function () {
  4468. var me = this,
  4469. childEls = me.childEls || (me.childEls = []);
  4470. childEls.push.apply(childEls, arguments);
  4471. },
  4472. /**
  4473. * Removes items in the childEls array based on the return value of a supplied test function. The function is called
  4474. * with a entry in childEls and if the test function return true, that entry is removed. If false, that entry is
  4475. * kept.
  4476. * @param {Function} testFn The test function.
  4477. */
  4478. removeChildEls: function (testFn) {
  4479. var me = this,
  4480. old = me.childEls,
  4481. keepers = (me.childEls = []),
  4482. n, i, cel;
  4483. for (i = 0, n = old.length; i < n; ++i) {
  4484. cel = old[i];
  4485. if (!testFn(cel)) {
  4486. keepers.push(cel);
  4487. }
  4488. }
  4489. },
  4490. /**
  4491. * Sets references to elements inside the component. This applies {@link #renderSelectors}
  4492. * as well as {@link #childEls}.
  4493. * @private
  4494. */
  4495. applyRenderSelectors: function() {
  4496. var me = this,
  4497. childEls = me.childEls,
  4498. selectors = me.renderSelectors,
  4499. el = me.el,
  4500. dom = el.dom,
  4501. baseId, childName, childId, i, selector;
  4502. if (childEls) {
  4503. baseId = me.id + '-';
  4504. for (i = childEls.length; i--; ) {
  4505. childName = childId = childEls[i];
  4506. if (typeof(childName) != 'string') {
  4507. childId = childName.id || (baseId + childName.itemId);
  4508. childName = childName.name;
  4509. } else {
  4510. childId = baseId + childId;
  4511. }
  4512. // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
  4513. // we know the el's are children of our el we use getById instead:
  4514. me[childName] = el.getById(childId);
  4515. }
  4516. }
  4517. // We still support renderSelectors. There are a few places in the framework that
  4518. // need them and they are a documented part of the API. In fact, we support mixing
  4519. // childEls and renderSelectors (no reason not to).
  4520. if (selectors) {
  4521. for (selector in selectors) {
  4522. if (selectors.hasOwnProperty(selector) && selectors[selector]) {
  4523. me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
  4524. }
  4525. }
  4526. }
  4527. },
  4528. /**
  4529. * Tests whether this Component matches the selector string.
  4530. * @param {String} selector The selector string to test against.
  4531. * @return {Boolean} True if this Component matches the selector.
  4532. */
  4533. is: function(selector) {
  4534. return Ext.ComponentQuery.is(this, selector);
  4535. },
  4536. /**
  4537. * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
  4538. *
  4539. * Example:
  4540. *
  4541. * var owningTabPanel = grid.up('tabpanel');
  4542. *
  4543. * @param {String} [selector] The simple selector to test.
  4544. * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
  4545. */
  4546. up: function(selector) {
  4547. var result = this.ownerCt;
  4548. if (selector) {
  4549. for (; result; result = result.ownerCt) {
  4550. if (Ext.ComponentQuery.is(result, selector)) {
  4551. return result;
  4552. }
  4553. }
  4554. }
  4555. return result;
  4556. },
  4557. /**
  4558. * Returns the next sibling of this Component.
  4559. *
  4560. * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
  4561. *
  4562. * May also be refered to as **`next()`**
  4563. *
  4564. * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
  4565. * {@link #nextNode}
  4566. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
  4567. * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
  4568. * Returns null if there is no matching sibling.
  4569. */
  4570. nextSibling: function(selector) {
  4571. var o = this.ownerCt, it, last, idx, c;
  4572. if (o) {
  4573. it = o.items;
  4574. idx = it.indexOf(this) + 1;
  4575. if (idx) {
  4576. if (selector) {
  4577. for (last = it.getCount(); idx < last; idx++) {
  4578. if ((c = it.getAt(idx)).is(selector)) {
  4579. return c;
  4580. }
  4581. }
  4582. } else {
  4583. if (idx < it.getCount()) {
  4584. return it.getAt(idx);
  4585. }
  4586. }
  4587. }
  4588. }
  4589. return null;
  4590. },
  4591. /**
  4592. * Returns the previous sibling of this Component.
  4593. *
  4594. * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
  4595. * selector.
  4596. *
  4597. * May also be refered to as **`prev()`**
  4598. *
  4599. * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
  4600. * {@link #previousNode}
  4601. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
  4602. * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
  4603. * Returns null if there is no matching sibling.
  4604. */
  4605. previousSibling: function(selector) {
  4606. var o = this.ownerCt, it, idx, c;
  4607. if (o) {
  4608. it = o.items;
  4609. idx = it.indexOf(this);
  4610. if (idx != -1) {
  4611. if (selector) {
  4612. for (--idx; idx >= 0; idx--) {
  4613. if ((c = it.getAt(idx)).is(selector)) {
  4614. return c;
  4615. }
  4616. }
  4617. } else {
  4618. if (idx) {
  4619. return it.getAt(--idx);
  4620. }
  4621. }
  4622. }
  4623. }
  4624. return null;
  4625. },
  4626. /**
  4627. * Returns the previous node in the Component tree in tree traversal order.
  4628. *
  4629. * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
  4630. * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
  4631. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
  4632. * @return {Ext.Component} The previous node (or the previous node which matches the selector).
  4633. * Returns null if there is no matching node.
  4634. */
  4635. previousNode: function(selector, includeSelf) {
  4636. var node = this,
  4637. result,
  4638. it, len, i;
  4639. // If asked to include self, test me
  4640. if (includeSelf && node.is(selector)) {
  4641. return node;
  4642. }
  4643. result = this.prev(selector);
  4644. if (result) {
  4645. return result;
  4646. }
  4647. if (node.ownerCt) {
  4648. for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
  4649. if (it[i].query) {
  4650. result = it[i].query(selector);
  4651. result = result[result.length - 1];
  4652. if (result) {
  4653. return result;
  4654. }
  4655. }
  4656. }
  4657. return node.ownerCt.previousNode(selector, true);
  4658. }
  4659. },
  4660. /**
  4661. * Returns the next node in the Component tree in tree traversal order.
  4662. *
  4663. * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
  4664. * tree to attempt to find a match. Contrast with {@link #nextSibling}.
  4665. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
  4666. * @return {Ext.Component} The next node (or the next node which matches the selector).
  4667. * Returns null if there is no matching node.
  4668. */
  4669. nextNode: function(selector, includeSelf) {
  4670. var node = this,
  4671. result,
  4672. it, len, i;
  4673. // If asked to include self, test me
  4674. if (includeSelf && node.is(selector)) {
  4675. return node;
  4676. }
  4677. result = this.next(selector);
  4678. if (result) {
  4679. return result;
  4680. }
  4681. if (node.ownerCt) {
  4682. for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
  4683. if (it[i].down) {
  4684. result = it[i].down(selector);
  4685. if (result) {
  4686. return result;
  4687. }
  4688. }
  4689. }
  4690. return node.ownerCt.nextNode(selector);
  4691. }
  4692. },
  4693. /**
  4694. * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
  4695. * @return {String}
  4696. */
  4697. getId : function() {
  4698. return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
  4699. },
  4700. getItemId : function() {
  4701. return this.itemId || this.id;
  4702. },
  4703. /**
  4704. * Retrieves the top level element representing this component.
  4705. * @return {Ext.core.Element}
  4706. */
  4707. getEl : function() {
  4708. return this.el;
  4709. },
  4710. /**
  4711. * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
  4712. * @private
  4713. */
  4714. getTargetEl: function() {
  4715. return this.frameBody || this.el;
  4716. },
  4717. /**
  4718. * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
  4719. * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
  4720. *
  4721. * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
  4722. * determination of inherited xtypes.**
  4723. *
  4724. * For a list of all available xtypes, see the {@link Ext.Component} header.
  4725. *
  4726. * Example usage:
  4727. *
  4728. * var t = new Ext.form.field.Text();
  4729. * var isText = t.isXType('textfield'); // true
  4730. * var isBoxSubclass = t.isXType('field'); // true, descended from Ext.form.field.Base
  4731. * var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
  4732. *
  4733. * @param {String} xtype The xtype to check for this Component
  4734. * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
  4735. * check whether this Component is descended from the xtype.
  4736. * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
  4737. */
  4738. isXType: function(xtype, shallow) {
  4739. //assume a string by default
  4740. if (Ext.isFunction(xtype)) {
  4741. xtype = xtype.xtype;
  4742. //handle being passed the class, e.g. Ext.Component
  4743. } else if (Ext.isObject(xtype)) {
  4744. xtype = xtype.statics().xtype;
  4745. //handle being passed an instance
  4746. }
  4747. return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
  4748. },
  4749. /**
  4750. * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
  4751. * {@link Ext.Component} header.
  4752. *
  4753. * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
  4754. * determination of inherited xtypes.**
  4755. *
  4756. * Example usage:
  4757. *
  4758. * var t = new Ext.form.field.Text();
  4759. * alert(t.getXTypes()); // alerts 'component/field/textfield'
  4760. *
  4761. * @return {String} The xtype hierarchy string
  4762. */
  4763. getXTypes: function() {
  4764. var self = this.self,
  4765. xtypes, parentPrototype, parentXtypes;
  4766. if (!self.xtypes) {
  4767. xtypes = [];
  4768. parentPrototype = this;
  4769. while (parentPrototype) {
  4770. parentXtypes = parentPrototype.xtypes;
  4771. if (parentXtypes !== undefined) {
  4772. xtypes.unshift.apply(xtypes, parentXtypes);
  4773. }
  4774. parentPrototype = parentPrototype.superclass;
  4775. }
  4776. self.xtypeChain = xtypes;
  4777. self.xtypes = xtypes.join('/');
  4778. }
  4779. return self.xtypes;
  4780. },
  4781. /**
  4782. * Update the content area of a component.
  4783. * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
  4784. * it will use this argument as data to populate the template. If this component was not configured with a template,
  4785. * the components content area will be updated via Ext.Element update
  4786. * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
  4787. * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
  4788. * scripts have finished loading
  4789. */
  4790. update : function(htmlOrData, loadScripts, cb) {
  4791. var me = this;
  4792. if (me.tpl && !Ext.isString(htmlOrData)) {
  4793. me.data = htmlOrData;
  4794. if (me.rendered) {
  4795. me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
  4796. }
  4797. } else {
  4798. me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
  4799. if (me.rendered) {
  4800. me.getTargetEl().update(me.html, loadScripts, cb);
  4801. }
  4802. }
  4803. if (me.rendered) {
  4804. me.doComponentLayout();
  4805. }
  4806. },
  4807. /**
  4808. * Convenience function to hide or show this component by boolean.
  4809. * @param {Boolean} visible True to show, false to hide
  4810. * @return {Ext.Component} this
  4811. */
  4812. setVisible : function(visible) {
  4813. return this[visible ? 'show': 'hide']();
  4814. },
  4815. /**
  4816. * Returns true if this component is visible.
  4817. *
  4818. * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
  4819. * determine whether this Component is truly visible to the user.
  4820. *
  4821. * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
  4822. * dynamically laid out UIs in a hidden Container before showing them.
  4823. *
  4824. * @return {Boolean} True if this component is visible, false otherwise.
  4825. */
  4826. isVisible: function(deep) {
  4827. var me = this,
  4828. child = me,
  4829. visible = !me.hidden,
  4830. ancestor = me.ownerCt;
  4831. // Clear hiddenOwnerCt property
  4832. me.hiddenAncestor = false;
  4833. if (me.destroyed) {
  4834. return false;
  4835. }
  4836. if (deep && visible && me.rendered && ancestor) {
  4837. while (ancestor) {
  4838. // If any ancestor is hidden, then this is hidden.
  4839. // If an ancestor Panel (only Panels have a collapse method) is collapsed,
  4840. // then its layoutTarget (body) is hidden, so this is hidden unless its within a
  4841. // docked item; they are still visible when collapsed (Unless they themseves are hidden)
  4842. if (ancestor.hidden || (ancestor.collapsed &&
  4843. !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
  4844. // Store hiddenOwnerCt property if needed
  4845. me.hiddenAncestor = ancestor;
  4846. visible = false;
  4847. break;
  4848. }
  4849. child = ancestor;
  4850. ancestor = ancestor.ownerCt;
  4851. }
  4852. }
  4853. return visible;
  4854. },
  4855. /**
  4856. * Enable the component
  4857. * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
  4858. */
  4859. enable: function(silent) {
  4860. var me = this;
  4861. if (me.rendered) {
  4862. me.el.removeCls(me.disabledCls);
  4863. me.el.dom.disabled = false;
  4864. me.onEnable();
  4865. }
  4866. me.disabled = false;
  4867. if (silent !== true) {
  4868. me.fireEvent('enable', me);
  4869. }
  4870. return me;
  4871. },
  4872. /**
  4873. * Disable the component.
  4874. * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
  4875. */
  4876. disable: function(silent) {
  4877. var me = this;
  4878. if (me.rendered) {
  4879. me.el.addCls(me.disabledCls);
  4880. me.el.dom.disabled = true;
  4881. me.onDisable();
  4882. }
  4883. me.disabled = true;
  4884. if (silent !== true) {
  4885. me.fireEvent('disable', me);
  4886. }
  4887. return me;
  4888. },
  4889. // @private
  4890. onEnable: function() {
  4891. if (this.maskOnDisable) {
  4892. this.el.unmask();
  4893. }
  4894. },
  4895. // @private
  4896. onDisable : function() {
  4897. if (this.maskOnDisable) {
  4898. this.el.mask();
  4899. }
  4900. },
  4901. /**
  4902. * Method to determine whether this Component is currently disabled.
  4903. * @return {Boolean} the disabled state of this Component.
  4904. */
  4905. isDisabled : function() {
  4906. return this.disabled;
  4907. },
  4908. /**
  4909. * Enable or disable the component.
  4910. * @param {Boolean} disabled True to disable.
  4911. */
  4912. setDisabled : function(disabled) {
  4913. return this[disabled ? 'disable': 'enable']();
  4914. },
  4915. /**
  4916. * Method to determine whether this Component is currently set to hidden.
  4917. * @return {Boolean} the hidden state of this Component.
  4918. */
  4919. isHidden : function() {
  4920. return this.hidden;
  4921. },
  4922. /**
  4923. * Adds a CSS class to the top level element representing this component.
  4924. * @param {String} cls The CSS class name to add
  4925. * @return {Ext.Component} Returns the Component to allow method chaining.
  4926. */
  4927. addCls : function(className) {
  4928. var me = this;
  4929. if (!className) {
  4930. return me;
  4931. }
  4932. if (!Ext.isArray(className)){
  4933. className = className.replace(me.trimRe, '').split(me.spacesRe);
  4934. }
  4935. if (me.rendered) {
  4936. me.el.addCls(className);
  4937. }
  4938. else {
  4939. me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
  4940. }
  4941. return me;
  4942. },
  4943. /**
  4944. * Adds a CSS class to the top level element representing this component.
  4945. * @param {String} cls The CSS class name to add
  4946. * @return {Ext.Component} Returns the Component to allow method chaining.
  4947. */
  4948. addClass : function() {
  4949. return this.addCls.apply(this, arguments);
  4950. },
  4951. /**
  4952. * Removes a CSS class from the top level element representing this component.
  4953. * @param {Object} className
  4954. * @return {Ext.Component} Returns the Component to allow method chaining.
  4955. */
  4956. removeCls : function(className) {
  4957. var me = this;
  4958. if (!className) {
  4959. return me;
  4960. }
  4961. if (!Ext.isArray(className)){
  4962. className = className.replace(me.trimRe, '').split(me.spacesRe);
  4963. }
  4964. if (me.rendered) {
  4965. me.el.removeCls(className);
  4966. }
  4967. else if (me.additionalCls.length) {
  4968. Ext.each(className, function(cls) {
  4969. Ext.Array.remove(me.additionalCls, cls);
  4970. });
  4971. }
  4972. return me;
  4973. },
  4974. //<debug>
  4975. removeClass : function() {
  4976. if (Ext.isDefined(Ext.global.console)) {
  4977. Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
  4978. }
  4979. return this.removeCls.apply(this, arguments);
  4980. },
  4981. //</debug>
  4982. addOverCls: function() {
  4983. var me = this;
  4984. if (!me.disabled) {
  4985. me.el.addCls(me.overCls);
  4986. }
  4987. },
  4988. removeOverCls: function() {
  4989. this.el.removeCls(this.overCls);
  4990. },
  4991. addListener : function(element, listeners, scope, options) {
  4992. var me = this,
  4993. fn,
  4994. option;
  4995. if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
  4996. if (options.element) {
  4997. fn = listeners;
  4998. listeners = {};
  4999. listeners[element] = fn;
  5000. element = options.element;
  5001. if (scope) {
  5002. listeners.scope = scope;
  5003. }
  5004. for (option in options) {
  5005. if (options.hasOwnProperty(option)) {
  5006. if (me.eventOptionsRe.test(option)) {
  5007. listeners[option] = options[option];
  5008. }
  5009. }
  5010. }
  5011. }
  5012. // At this point we have a variable called element,
  5013. // and a listeners object that can be passed to on
  5014. if (me[element] && me[element].on) {
  5015. me.mon(me[element], listeners);
  5016. } else {
  5017. me.afterRenderEvents = me.afterRenderEvents || {};
  5018. if (!me.afterRenderEvents[element]) {
  5019. me.afterRenderEvents[element] = [];
  5020. }
  5021. me.afterRenderEvents[element].push(listeners);
  5022. }
  5023. }
  5024. return me.mixins.observable.addListener.apply(me, arguments);
  5025. },
  5026. // inherit docs
  5027. removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
  5028. var me = this,
  5029. element = managedListener.options ? managedListener.options.element : null;
  5030. if (element) {
  5031. element = me[element];
  5032. if (element && element.un) {
  5033. if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
  5034. element.un(managedListener.ename, managedListener.fn, managedListener.scope);
  5035. if (!isClear) {
  5036. Ext.Array.remove(me.managedListeners, managedListener);
  5037. }
  5038. }
  5039. }
  5040. } else {
  5041. return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
  5042. }
  5043. },
  5044. /**
  5045. * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
  5046. * @return {Ext.container.Container} the Container which owns this Component.
  5047. */
  5048. getBubbleTarget : function() {
  5049. return this.ownerCt;
  5050. },
  5051. /**
  5052. * Method to determine whether this Component is floating.
  5053. * @return {Boolean} the floating state of this component.
  5054. */
  5055. isFloating : function() {
  5056. return this.floating;
  5057. },
  5058. /**
  5059. * Method to determine whether this Component is draggable.
  5060. * @return {Boolean} the draggable state of this component.
  5061. */
  5062. isDraggable : function() {
  5063. return !!this.draggable;
  5064. },
  5065. /**
  5066. * Method to determine whether this Component is droppable.
  5067. * @return {Boolean} the droppable state of this component.
  5068. */
  5069. isDroppable : function() {
  5070. return !!this.droppable;
  5071. },
  5072. /**
  5073. * @private
  5074. * Method to manage awareness of when components are added to their
  5075. * respective Container, firing an added event.
  5076. * References are established at add time rather than at render time.
  5077. * @param {Ext.container.Container} container Container which holds the component
  5078. * @param {Number} pos Position at which the component was added
  5079. */
  5080. onAdded : function(container, pos) {
  5081. this.ownerCt = container;
  5082. this.fireEvent('added', this, container, pos);
  5083. },
  5084. /**
  5085. * @private
  5086. * Method to manage awareness of when components are removed from their
  5087. * respective Container, firing an removed event. References are properly
  5088. * cleaned up after removing a component from its owning container.
  5089. */
  5090. onRemoved : function() {
  5091. var me = this;
  5092. me.fireEvent('removed', me, me.ownerCt);
  5093. delete me.ownerCt;
  5094. },
  5095. // @private
  5096. beforeDestroy : Ext.emptyFn,
  5097. // @private
  5098. // @private
  5099. onResize : Ext.emptyFn,
  5100. /**
  5101. * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
  5102. * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
  5103. *
  5104. * @param {Number/String/Object} width The new width to set. This may be one of:
  5105. *
  5106. * - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  5107. * - A String used to set the CSS width style.
  5108. * - A size object in the format `{width: widthValue, height: heightValue}`.
  5109. * - `undefined` to leave the width unchanged.
  5110. *
  5111. * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
  5112. * This may be one of:
  5113. *
  5114. * - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  5115. * - A String used to set the CSS height style. Animation may **not** be used.
  5116. * - `undefined` to leave the height unchanged.
  5117. *
  5118. * @return {Ext.Component} this
  5119. */
  5120. setSize : function(width, height) {
  5121. var me = this,
  5122. layoutCollection;
  5123. // support for standard size objects
  5124. if (Ext.isObject(width)) {
  5125. height = width.height;
  5126. width = width.width;
  5127. }
  5128. // Constrain within configured maxima
  5129. if (Ext.isNumber(width)) {
  5130. width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
  5131. }
  5132. if (Ext.isNumber(height)) {
  5133. height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
  5134. }
  5135. if (!me.rendered || !me.isVisible()) {
  5136. // If an ownerCt is hidden, add my reference onto the layoutOnShow stack. Set the needsLayout flag.
  5137. if (me.hiddenAncestor) {
  5138. layoutCollection = me.hiddenAncestor.layoutOnShow;
  5139. layoutCollection.remove(me);
  5140. layoutCollection.add(me);
  5141. }
  5142. me.needsLayout = {
  5143. width: width,
  5144. height: height,
  5145. isSetSize: true
  5146. };
  5147. if (!me.rendered) {
  5148. me.width = (width !== undefined) ? width : me.width;
  5149. me.height = (height !== undefined) ? height : me.height;
  5150. }
  5151. return me;
  5152. }
  5153. me.doComponentLayout(width, height, true);
  5154. return me;
  5155. },
  5156. isFixedWidth: function() {
  5157. var me = this,
  5158. layoutManagedWidth = me.layoutManagedWidth;
  5159. if (Ext.isDefined(me.width) || layoutManagedWidth == 1) {
  5160. return true;
  5161. }
  5162. if (layoutManagedWidth == 2) {
  5163. return false;
  5164. }
  5165. return (me.ownerCt && me.ownerCt.isFixedWidth());
  5166. },
  5167. isFixedHeight: function() {
  5168. var me = this,
  5169. layoutManagedHeight = me.layoutManagedHeight;
  5170. if (Ext.isDefined(me.height) || layoutManagedHeight == 1) {
  5171. return true;
  5172. }
  5173. if (layoutManagedHeight == 2) {
  5174. return false;
  5175. }
  5176. return (me.ownerCt && me.ownerCt.isFixedHeight());
  5177. },
  5178. setCalculatedSize : function(width, height, callingContainer) {
  5179. var me = this,
  5180. layoutCollection;
  5181. // support for standard size objects
  5182. if (Ext.isObject(width)) {
  5183. callingContainer = width.ownerCt;
  5184. height = width.height;
  5185. width = width.width;
  5186. }
  5187. // Constrain within configured maxima
  5188. if (Ext.isNumber(width)) {
  5189. width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
  5190. }
  5191. if (Ext.isNumber(height)) {
  5192. height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
  5193. }
  5194. if (!me.rendered || !me.isVisible()) {
  5195. // If an ownerCt is hidden, add my reference onto the layoutOnShow stack. Set the needsLayout flag.
  5196. if (me.hiddenAncestor) {
  5197. layoutCollection = me.hiddenAncestor.layoutOnShow;
  5198. layoutCollection.remove(me);
  5199. layoutCollection.add(me);
  5200. }
  5201. me.needsLayout = {
  5202. width: width,
  5203. height: height,
  5204. isSetSize: false,
  5205. ownerCt: callingContainer
  5206. };
  5207. return me;
  5208. }
  5209. me.doComponentLayout(width, height, false, callingContainer);
  5210. return me;
  5211. },
  5212. /**
  5213. * This method needs to be called whenever you change something on this component that requires the Component's
  5214. * layout to be recalculated.
  5215. * @param {Object} width
  5216. * @param {Object} height
  5217. * @param {Object} isSetSize
  5218. * @param {Object} callingContainer
  5219. * @return {Ext.container.Container} this
  5220. */
  5221. doComponentLayout : function(width, height, isSetSize, callingContainer) {
  5222. var me = this,
  5223. componentLayout = me.getComponentLayout(),
  5224. lastComponentSize = componentLayout.lastComponentSize || {
  5225. width: undefined,
  5226. height: undefined
  5227. };
  5228. // collapsed state is not relevant here, so no testing done.
  5229. // Only Panels have a collapse method, and that just sets the width/height such that only
  5230. // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
  5231. if (me.rendered && componentLayout) {
  5232. // If no width passed, then only insert a value if the Component is NOT ALLOWED to autowidth itself.
  5233. if (!Ext.isDefined(width)) {
  5234. if (me.isFixedWidth()) {
  5235. width = Ext.isDefined(me.width) ? me.width : lastComponentSize.width;
  5236. }
  5237. }
  5238. // If no height passed, then only insert a value if the Component is NOT ALLOWED to autoheight itself.
  5239. if (!Ext.isDefined(height)) {
  5240. if (me.isFixedHeight()) {
  5241. height = Ext.isDefined(me.height) ? me.height : lastComponentSize.height;
  5242. }
  5243. }
  5244. if (isSetSize) {
  5245. me.width = width;
  5246. me.height = height;
  5247. }
  5248. componentLayout.layout(width, height, isSetSize, callingContainer);
  5249. }
  5250. return me;
  5251. },
  5252. /**
  5253. * Forces this component to redo its componentLayout.
  5254. */
  5255. forceComponentLayout: function () {
  5256. this.doComponentLayout();
  5257. },
  5258. // @private
  5259. setComponentLayout : function(layout) {
  5260. var currentLayout = this.componentLayout;
  5261. if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
  5262. currentLayout.setOwner(null);
  5263. }
  5264. this.componentLayout = layout;
  5265. layout.setOwner(this);
  5266. },
  5267. getComponentLayout : function() {
  5268. var me = this;
  5269. if (!me.componentLayout || !me.componentLayout.isLayout) {
  5270. me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
  5271. }
  5272. return me.componentLayout;
  5273. },
  5274. /**
  5275. * Occurs after componentLayout is run.
  5276. * @param {Number} adjWidth The box-adjusted width that was set
  5277. * @param {Number} adjHeight The box-adjusted height that was set
  5278. * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
  5279. * @param {Ext.Component} callingContainer Container requesting the layout. Only used when isSetSize is false.
  5280. */
  5281. afterComponentLayout: function(width, height, isSetSize, callingContainer) {
  5282. var me = this,
  5283. layout = me.componentLayout,
  5284. oldSize = me.preLayoutSize;
  5285. ++me.componentLayoutCounter;
  5286. if (!oldSize || ((width !== oldSize.width) || (height !== oldSize.height))) {
  5287. me.fireEvent('resize', me, width, height);
  5288. }
  5289. },
  5290. /**
  5291. * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
  5292. * being executed.
  5293. * @param {Number} adjWidth The box-adjusted width that was set
  5294. * @param {Number} adjHeight The box-adjusted height that was set
  5295. * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
  5296. * @param {Ext.Component} callingContainer Container requesting sent the layout. Only used when isSetSize is false.
  5297. */
  5298. beforeComponentLayout: function(width, height, isSetSize, callingContainer) {
  5299. this.preLayoutSize = this.componentLayout.lastComponentSize;
  5300. return true;
  5301. },
  5302. /**
  5303. * Sets the left and top of the component. To set the page XY position instead, use
  5304. * {@link Ext.Component#setPagePosition setPagePosition}. This method fires the {@link #move} event.
  5305. * @param {Number} left The new left
  5306. * @param {Number} top The new top
  5307. * @return {Ext.Component} this
  5308. */
  5309. setPosition : function(x, y) {
  5310. var me = this;
  5311. if (Ext.isObject(x)) {
  5312. y = x.y;
  5313. x = x.x;
  5314. }
  5315. if (!me.rendered) {
  5316. return me;
  5317. }
  5318. if (x !== undefined || y !== undefined) {
  5319. me.el.setBox(x, y);
  5320. me.onPosition(x, y);
  5321. me.fireEvent('move', me, x, y);
  5322. }
  5323. return me;
  5324. },
  5325. /**
  5326. * @private
  5327. * Called after the component is moved, this method is empty by default but can be implemented by any
  5328. * subclass that needs to perform custom logic after a move occurs.
  5329. * @param {Number} x The new x position
  5330. * @param {Number} y The new y position
  5331. */
  5332. onPosition: Ext.emptyFn,
  5333. /**
  5334. * Sets the width of the component. This method fires the {@link #resize} event.
  5335. *
  5336. * @param {Number} width The new width to setThis may be one of:
  5337. *
  5338. * - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  5339. * - A String used to set the CSS width style.
  5340. *
  5341. * @return {Ext.Component} this
  5342. */
  5343. setWidth : function(width) {
  5344. return this.setSize(width);
  5345. },
  5346. /**
  5347. * Sets the height of the component. This method fires the {@link #resize} event.
  5348. *
  5349. * @param {Number} height The new height to set. This may be one of:
  5350. *
  5351. * - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  5352. * - A String used to set the CSS height style.
  5353. * - _undefined_ to leave the height unchanged.
  5354. *
  5355. * @return {Ext.Component} this
  5356. */
  5357. setHeight : function(height) {
  5358. return this.setSize(undefined, height);
  5359. },
  5360. /**
  5361. * Gets the current size of the component's underlying element.
  5362. * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
  5363. */
  5364. getSize : function() {
  5365. return this.el.getSize();
  5366. },
  5367. /**
  5368. * Gets the current width of the component's underlying element.
  5369. * @return {Number}
  5370. */
  5371. getWidth : function() {
  5372. return this.el.getWidth();
  5373. },
  5374. /**
  5375. * Gets the current height of the component's underlying element.
  5376. * @return {Number}
  5377. */
  5378. getHeight : function() {
  5379. return this.el.getHeight();
  5380. },
  5381. /**
  5382. * Gets the {@link Ext.ComponentLoader} for this Component.
  5383. * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
  5384. */
  5385. getLoader: function(){
  5386. var me = this,
  5387. autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
  5388. loader = me.loader || autoLoad;
  5389. if (loader) {
  5390. if (!loader.isLoader) {
  5391. me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
  5392. target: me,
  5393. autoLoad: autoLoad
  5394. }, loader));
  5395. } else {
  5396. loader.setTarget(me);
  5397. }
  5398. return me.loader;
  5399. }
  5400. return null;
  5401. },
  5402. /**
  5403. * This method allows you to show or hide a LoadMask on top of this component.
  5404. *
  5405. * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
  5406. * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
  5407. * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
  5408. * setting this to true on a Panel will cause only the body to be masked.
  5409. * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
  5410. */
  5411. setLoading : function(load, targetEl) {
  5412. var me = this,
  5413. config;
  5414. if (me.rendered) {
  5415. if (load !== false && !me.collapsed) {
  5416. if (Ext.isObject(load)) {
  5417. config = load;
  5418. }
  5419. else if (Ext.isString(load)) {
  5420. config = {msg: load};
  5421. }
  5422. else {
  5423. config = {};
  5424. }
  5425. me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
  5426. me.loadMask.show();
  5427. } else if (me.loadMask) {
  5428. Ext.destroy(me.loadMask);
  5429. me.loadMask = null;
  5430. }
  5431. }
  5432. return me.loadMask;
  5433. },
  5434. /**
  5435. * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
  5436. * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
  5437. * @param {Object} dock The dock position.
  5438. * @param {Boolean} [layoutParent=false] True to re-layout parent.
  5439. * @return {Ext.Component} this
  5440. */
  5441. setDocked : function(dock, layoutParent) {
  5442. var me = this;
  5443. me.dock = dock;
  5444. if (layoutParent && me.ownerCt && me.rendered) {
  5445. me.ownerCt.doComponentLayout();
  5446. }
  5447. return me;
  5448. },
  5449. onDestroy : function() {
  5450. var me = this;
  5451. if (me.monitorResize && Ext.EventManager.resizeEvent) {
  5452. Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
  5453. }
  5454. // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
  5455. Ext.destroy(
  5456. me.componentLayout,
  5457. me.loadMask,
  5458. me.floatingItems
  5459. );
  5460. },
  5461. /**
  5462. * Remove any references to elements added via renderSelectors/childEls
  5463. * @private
  5464. */
  5465. cleanElementRefs: function(){
  5466. var me = this,
  5467. i = 0,
  5468. childEls = me.childEls,
  5469. selectors = me.renderSelectors,
  5470. selector,
  5471. name,
  5472. len;
  5473. if (me.rendered) {
  5474. if (childEls) {
  5475. for (len = childEls.length; i < len; ++i) {
  5476. name = childEls[i];
  5477. if (typeof(name) != 'string') {
  5478. name = name.name;
  5479. }
  5480. delete me[name];
  5481. }
  5482. }
  5483. if (selectors) {
  5484. for (selector in selectors) {
  5485. if (selectors.hasOwnProperty(selector)) {
  5486. delete me[selector];
  5487. }
  5488. }
  5489. }
  5490. }
  5491. delete me.rendered;
  5492. delete me.el;
  5493. delete me.frameBody;
  5494. },
  5495. /**
  5496. * Destroys the Component.
  5497. */
  5498. destroy : function() {
  5499. var me = this;
  5500. if (!me.isDestroyed) {
  5501. if (me.fireEvent('beforedestroy', me) !== false) {
  5502. me.destroying = true;
  5503. me.beforeDestroy();
  5504. if (me.floating) {
  5505. delete me.floatParent;
  5506. // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
  5507. // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
  5508. if (me.zIndexManager) {
  5509. me.zIndexManager.unregister(me);
  5510. }
  5511. } else if (me.ownerCt && me.ownerCt.remove) {
  5512. me.ownerCt.remove(me, false);
  5513. }
  5514. me.onDestroy();
  5515. // Attempt to destroy all plugins
  5516. Ext.destroy(me.plugins);
  5517. if (me.rendered) {
  5518. me.el.remove();
  5519. }
  5520. me.fireEvent('destroy', me);
  5521. Ext.ComponentManager.unregister(me);
  5522. me.mixins.state.destroy.call(me);
  5523. me.clearListeners();
  5524. // make sure we clean up the element references after removing all events
  5525. me.cleanElementRefs();
  5526. me.destroying = false;
  5527. me.isDestroyed = true;
  5528. }
  5529. }
  5530. },
  5531. /**
  5532. * Retrieves a plugin by its pluginId which has been bound to this component.
  5533. * @param {Object} pluginId
  5534. * @return {Ext.AbstractPlugin} plugin instance.
  5535. */
  5536. getPlugin: function(pluginId) {
  5537. var i = 0,
  5538. plugins = this.plugins,
  5539. ln = plugins.length;
  5540. for (; i < ln; i++) {
  5541. if (plugins[i].pluginId === pluginId) {
  5542. return plugins[i];
  5543. }
  5544. }
  5545. },
  5546. /**
  5547. * Determines whether this component is the descendant of a particular container.
  5548. * @param {Ext.Container} container
  5549. * @return {Boolean} True if it is.
  5550. */
  5551. isDescendantOf: function(container) {
  5552. return !!this.findParentBy(function(p){
  5553. return p === container;
  5554. });
  5555. }
  5556. }, function() {
  5557. this.createAlias({
  5558. on: 'addListener',
  5559. prev: 'previousSibling',
  5560. next: 'nextSibling'
  5561. });
  5562. });
  5563. /**
  5564. * The AbstractPlugin class is the base class from which user-implemented plugins should inherit.
  5565. *
  5566. * This class defines the essential API of plugins as used by Components by defining the following methods:
  5567. *
  5568. * - `init` : The plugin initialization method which the owning Component calls at Component initialization time.
  5569. *
  5570. * The Component passes itself as the sole parameter.
  5571. *
  5572. * Subclasses should set up bidirectional links between the plugin and its client Component here.
  5573. *
  5574. * - `destroy` : The plugin cleanup method which the owning Component calls at Component destruction time.
  5575. *
  5576. * Use this method to break links between the plugin and the Component and to free any allocated resources.
  5577. *
  5578. * - `enable` : The base implementation just sets the plugin's `disabled` flag to `false`
  5579. *
  5580. * - `disable` : The base implementation just sets the plugin's `disabled` flag to `true`
  5581. */
  5582. Ext.define('Ext.AbstractPlugin', {
  5583. disabled: false,
  5584. constructor: function(config) {
  5585. //<debug>
  5586. if (!config.cmp && Ext.global.console) {
  5587. Ext.global.console.warn("Attempted to attach a plugin ");
  5588. }
  5589. //</debug>
  5590. Ext.apply(this, config);
  5591. },
  5592. getCmp: function() {
  5593. return this.cmp;
  5594. },
  5595. /**
  5596. * @method
  5597. * The init method is invoked after initComponent method has been run for the client Component.
  5598. *
  5599. * The supplied implementation is empty. Subclasses should perform plugin initialization, and set up bidirectional
  5600. * links between the plugin and its client Component in their own implementation of this method.
  5601. * @param {Ext.Component} client The client Component which owns this plugin.
  5602. */
  5603. init: Ext.emptyFn,
  5604. /**
  5605. * @method
  5606. * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
  5607. *
  5608. * The supplied implementation is empty. Subclasses should perform plugin cleanup in their own implementation of
  5609. * this method.
  5610. */
  5611. destroy: Ext.emptyFn,
  5612. /**
  5613. * The base implementation just sets the plugin's `disabled` flag to `false`
  5614. *
  5615. * Plugin subclasses which need more complex processing may implement an overriding implementation.
  5616. */
  5617. enable: function() {
  5618. this.disabled = false;
  5619. },
  5620. /**
  5621. * The base implementation just sets the plugin's `disabled` flag to `true`
  5622. *
  5623. * Plugin subclasses which need more complex processing may implement an overriding implementation.
  5624. */
  5625. disable: function() {
  5626. this.disabled = true;
  5627. }
  5628. });
  5629. /**
  5630. * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
  5631. * to a configured URL, or to a URL specified at request time.
  5632. *
  5633. * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
  5634. * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
  5635. * in the request options object, or an {@link #requestcomplete event listener}.
  5636. *
  5637. * # File Uploads
  5638. *
  5639. * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
  5640. * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
  5641. * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
  5642. * after the return data has been gathered.
  5643. *
  5644. * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
  5645. * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
  5646. * insert the text unchanged into the document body.
  5647. *
  5648. * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
  5649. * `&amp;` etc.
  5650. *
  5651. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
  5652. * responseText property in order to conform to the requirements of event handlers and callbacks.
  5653. *
  5654. * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
  5655. * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
  5656. * packet content.
  5657. *
  5658. * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
  5659. */
  5660. Ext.define('Ext.data.Connection', {
  5661. mixins: {
  5662. observable: 'Ext.util.Observable'
  5663. },
  5664. statics: {
  5665. requestId: 0
  5666. },
  5667. url: null,
  5668. async: true,
  5669. method: null,
  5670. username: '',
  5671. password: '',
  5672. /**
  5673. * @cfg {Boolean} disableCaching
  5674. * True to add a unique cache-buster param to GET requests.
  5675. */
  5676. disableCaching: true,
  5677. /**
  5678. * @cfg {Boolean} withCredentials
  5679. * True to set `withCredentials = true` on the XHR object
  5680. */
  5681. withCredentials: false,
  5682. /**
  5683. * @cfg {Boolean} cors
  5684. * True to enable CORS support on the XHR object. Currently the only effect of this option
  5685. * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
  5686. */
  5687. cors: false,
  5688. /**
  5689. * @cfg {String} disableCachingParam
  5690. * Change the parameter which is sent went disabling caching through a cache buster.
  5691. */
  5692. disableCachingParam: '_dc',
  5693. /**
  5694. * @cfg {Number} timeout
  5695. * The timeout in milliseconds to be used for requests.
  5696. */
  5697. timeout : 30000,
  5698. /**
  5699. * @cfg {Object} extraParams
  5700. * Any parameters to be appended to the request.
  5701. */
  5702. useDefaultHeader : true,
  5703. defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
  5704. useDefaultXhrHeader : true,
  5705. defaultXhrHeader : 'XMLHttpRequest',
  5706. constructor : function(config) {
  5707. config = config || {};
  5708. Ext.apply(this, config);
  5709. this.addEvents(
  5710. /**
  5711. * @event beforerequest
  5712. * Fires before a network request is made to retrieve a data object.
  5713. * @param {Ext.data.Connection} conn This Connection object.
  5714. * @param {Object} options The options config object passed to the {@link #request} method.
  5715. */
  5716. 'beforerequest',
  5717. /**
  5718. * @event requestcomplete
  5719. * Fires if the request was successfully completed.
  5720. * @param {Ext.data.Connection} conn This Connection object.
  5721. * @param {Object} response The XHR object containing the response data.
  5722. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  5723. * @param {Object} options The options config object passed to the {@link #request} method.
  5724. */
  5725. 'requestcomplete',
  5726. /**
  5727. * @event requestexception
  5728. * Fires if an error HTTP status was returned from the server.
  5729. * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
  5730. * for details of HTTP status codes.
  5731. * @param {Ext.data.Connection} conn This Connection object.
  5732. * @param {Object} response The XHR object containing the response data.
  5733. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  5734. * @param {Object} options The options config object passed to the {@link #request} method.
  5735. */
  5736. 'requestexception'
  5737. );
  5738. this.requests = {};
  5739. this.mixins.observable.constructor.call(this);
  5740. },
  5741. /**
  5742. * Sends an HTTP request to a remote server.
  5743. *
  5744. * **Important:** Ajax server requests are asynchronous, and this call will
  5745. * return before the response has been received. Process any returned data
  5746. * in a callback function.
  5747. *
  5748. * Ext.Ajax.request({
  5749. * url: 'ajax_demo/sample.json',
  5750. * success: function(response, opts) {
  5751. * var obj = Ext.decode(response.responseText);
  5752. * console.dir(obj);
  5753. * },
  5754. * failure: function(response, opts) {
  5755. * console.log('server-side failure with status code ' + response.status);
  5756. * }
  5757. * });
  5758. *
  5759. * To execute a callback function in the correct scope, use the `scope` option.
  5760. *
  5761. * @param {Object} options An object which may contain the following properties:
  5762. *
  5763. * (The options object may also contain any other property which might be needed to perform
  5764. * postprocessing in a callback because it is passed to callback functions.)
  5765. *
  5766. * @param {String/Function} options.url The URL to which to send the request, or a function
  5767. * to call which returns a URL string. The scope of the function is specified by the `scope` option.
  5768. * Defaults to the configured `url`.
  5769. *
  5770. * @param {Object/String/Function} options.params An object containing properties which are
  5771. * used as parameters to the request, a url encoded string or a function to call to get either. The scope
  5772. * of the function is specified by the `scope` option.
  5773. *
  5774. * @param {String} options.method The HTTP method to use
  5775. * for the request. Defaults to the configured method, or if no method was configured,
  5776. * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
  5777. * the method name is case-sensitive and should be all caps.
  5778. *
  5779. * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
  5780. * The callback is called regardless of success or failure and is passed the following parameters:
  5781. * @param {Object} options.callback.options The parameter to the request call.
  5782. * @param {Boolean} options.callback.success True if the request succeeded.
  5783. * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
  5784. * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
  5785. * accessing elements of the response.
  5786. *
  5787. * @param {Function} options.success The function to be called upon success of the request.
  5788. * The callback is passed the following parameters:
  5789. * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
  5790. * @param {Object} options.success.options The parameter to the request call.
  5791. *
  5792. * @param {Function} options.failure The function to be called upon success of the request.
  5793. * The callback is passed the following parameters:
  5794. * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
  5795. * @param {Object} options.failure.options The parameter to the request call.
  5796. *
  5797. * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
  5798. * the callback function. If the `url`, or `params` options were specified as functions from which to
  5799. * draw values, then this also serves as the scope for those function calls. Defaults to the browser
  5800. * window.
  5801. *
  5802. * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
  5803. * Defaults to 30 seconds.
  5804. *
  5805. * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
  5806. * to pull parameters from.
  5807. *
  5808. * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
  5809. *
  5810. * True if the form object is a file upload (will be set automatically if the form was configured
  5811. * with **`enctype`** `"multipart/form-data"`).
  5812. *
  5813. * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
  5814. * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
  5815. * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
  5816. * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
  5817. * has been gathered.
  5818. *
  5819. * The server response is parsed by the browser to create the document for the IFRAME. If the
  5820. * server is using JSON to send the return object, then the [Content-Type][] header must be set to
  5821. * "text/html" in order to tell the browser to insert the text unchanged into the document body.
  5822. *
  5823. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
  5824. * containing a `responseText` property in order to conform to the requirements of event handlers
  5825. * and callbacks.
  5826. *
  5827. * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
  5828. * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
  5829. * and parameter values from the packet content.
  5830. *
  5831. * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
  5832. * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
  5833. * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
  5834. *
  5835. * @param {Object} options.headers Request headers to set for the request.
  5836. *
  5837. * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
  5838. * of params for the post data. Any params will be appended to the URL.
  5839. *
  5840. * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
  5841. * instead of params for the post data. Any params will be appended to the URL.
  5842. *
  5843. * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
  5844. *
  5845. * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
  5846. *
  5847. * @return {Object} The request object. This may be used to cancel the request.
  5848. */
  5849. request : function(options) {
  5850. options = options || {};
  5851. var me = this,
  5852. scope = options.scope || window,
  5853. username = options.username || me.username,
  5854. password = options.password || me.password || '',
  5855. async,
  5856. requestOptions,
  5857. request,
  5858. headers,
  5859. xhr;
  5860. if (me.fireEvent('beforerequest', me, options) !== false) {
  5861. requestOptions = me.setOptions(options, scope);
  5862. if (this.isFormUpload(options) === true) {
  5863. this.upload(options.form, requestOptions.url, requestOptions.data, options);
  5864. return null;
  5865. }
  5866. // if autoabort is set, cancel the current transactions
  5867. if (options.autoAbort === true || me.autoAbort) {
  5868. me.abort();
  5869. }
  5870. // create a connection object
  5871. if ((options.cors === true || me.cors === true) && Ext.isIe && Ext.ieVersion >= 8) {
  5872. xhr = new XDomainRequest();
  5873. } else {
  5874. xhr = this.getXhrInstance();
  5875. }
  5876. async = options.async !== false ? (options.async || me.async) : false;
  5877. // open the request
  5878. if (username) {
  5879. xhr.open(requestOptions.method, requestOptions.url, async, username, password);
  5880. } else {
  5881. xhr.open(requestOptions.method, requestOptions.url, async);
  5882. }
  5883. if (options.withCredentials === true || me.withCredentials === true) {
  5884. xhr.withCredentials = true;
  5885. }
  5886. headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
  5887. // create the transaction object
  5888. request = {
  5889. id: ++Ext.data.Connection.requestId,
  5890. xhr: xhr,
  5891. headers: headers,
  5892. options: options,
  5893. async: async,
  5894. timeout: setTimeout(function() {
  5895. request.timedout = true;
  5896. me.abort(request);
  5897. }, options.timeout || me.timeout)
  5898. };
  5899. me.requests[request.id] = request;
  5900. me.latestId = request.id;
  5901. // bind our statechange listener
  5902. if (async) {
  5903. xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
  5904. }
  5905. // start the request!
  5906. xhr.send(requestOptions.data);
  5907. if (!async) {
  5908. return this.onComplete(request);
  5909. }
  5910. return request;
  5911. } else {
  5912. Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
  5913. return null;
  5914. }
  5915. },
  5916. /**
  5917. * Uploads a form using a hidden iframe.
  5918. * @param {String/HTMLElement/Ext.Element} form The form to upload
  5919. * @param {String} url The url to post to
  5920. * @param {String} params Any extra parameters to pass
  5921. * @param {Object} options The initial options
  5922. */
  5923. upload: function(form, url, params, options) {
  5924. form = Ext.getDom(form);
  5925. options = options || {};
  5926. var id = Ext.id(),
  5927. frame = document.createElement('iframe'),
  5928. hiddens = [],
  5929. encoding = 'multipart/form-data',
  5930. buf = {
  5931. target: form.target,
  5932. method: form.method,
  5933. encoding: form.encoding,
  5934. enctype: form.enctype,
  5935. action: form.action
  5936. }, hiddenItem;
  5937. /*
  5938. * Originally this behaviour was modified for Opera 10 to apply the secure URL after
  5939. * the frame had been added to the document. It seems this has since been corrected in
  5940. * Opera so the behaviour has been reverted, the URL will be set before being added.
  5941. */
  5942. Ext.fly(frame).set({
  5943. id: id,
  5944. name: id,
  5945. cls: Ext.baseCSSPrefix + 'hide-display',
  5946. src: Ext.SSL_SECURE_URL
  5947. });
  5948. document.body.appendChild(frame);
  5949. // This is required so that IE doesn't pop the response up in a new window.
  5950. if (document.frames) {
  5951. document.frames[id].name = id;
  5952. }
  5953. Ext.fly(form).set({
  5954. target: id,
  5955. method: 'POST',
  5956. enctype: encoding,
  5957. encoding: encoding,
  5958. action: url || buf.action
  5959. });
  5960. // add dynamic params
  5961. if (params) {
  5962. Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
  5963. hiddenItem = document.createElement('input');
  5964. Ext.fly(hiddenItem).set({
  5965. type: 'hidden',
  5966. value: value,
  5967. name: name
  5968. });
  5969. form.appendChild(hiddenItem);
  5970. hiddens.push(hiddenItem);
  5971. });
  5972. }
  5973. Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
  5974. form.submit();
  5975. Ext.fly(form).set(buf);
  5976. Ext.each(hiddens, function(h) {
  5977. Ext.removeNode(h);
  5978. });
  5979. },
  5980. /**
  5981. * @private
  5982. * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
  5983. * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
  5984. * (or a textarea inside the body). We then clean up by removing the iframe
  5985. */
  5986. onUploadComplete: function(frame, options) {
  5987. var me = this,
  5988. // bogus response object
  5989. response = {
  5990. responseText: '',
  5991. responseXML: null
  5992. }, doc, firstChild;
  5993. try {
  5994. doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
  5995. if (doc) {
  5996. if (doc.body) {
  5997. if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
  5998. response.responseText = firstChild.value;
  5999. } else {
  6000. response.responseText = doc.body.innerHTML;
  6001. }
  6002. }
  6003. //in IE the document may still have a body even if returns XML.
  6004. response.responseXML = doc.XMLDocument || doc;
  6005. }
  6006. } catch (e) {
  6007. }
  6008. me.fireEvent('requestcomplete', me, response, options);
  6009. Ext.callback(options.success, options.scope, [response, options]);
  6010. Ext.callback(options.callback, options.scope, [options, true, response]);
  6011. setTimeout(function(){
  6012. Ext.removeNode(frame);
  6013. }, 100);
  6014. },
  6015. /**
  6016. * Detects whether the form is intended to be used for an upload.
  6017. * @private
  6018. */
  6019. isFormUpload: function(options){
  6020. var form = this.getForm(options);
  6021. if (form) {
  6022. return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
  6023. }
  6024. return false;
  6025. },
  6026. /**
  6027. * Gets the form object from options.
  6028. * @private
  6029. * @param {Object} options The request options
  6030. * @return {HTMLElement} The form, null if not passed
  6031. */
  6032. getForm: function(options){
  6033. return Ext.getDom(options.form) || null;
  6034. },
  6035. /**
  6036. * Sets various options such as the url, params for the request
  6037. * @param {Object} options The initial options
  6038. * @param {Object} scope The scope to execute in
  6039. * @return {Object} The params for the request
  6040. */
  6041. setOptions: function(options, scope){
  6042. var me = this,
  6043. params = options.params || {},
  6044. extraParams = me.extraParams,
  6045. urlParams = options.urlParams,
  6046. url = options.url || me.url,
  6047. jsonData = options.jsonData,
  6048. method,
  6049. disableCache,
  6050. data;
  6051. // allow params to be a method that returns the params object
  6052. if (Ext.isFunction(params)) {
  6053. params = params.call(scope, options);
  6054. }
  6055. // allow url to be a method that returns the actual url
  6056. if (Ext.isFunction(url)) {
  6057. url = url.call(scope, options);
  6058. }
  6059. url = this.setupUrl(options, url);
  6060. //<debug>
  6061. if (!url) {
  6062. Ext.Error.raise({
  6063. options: options,
  6064. msg: 'No URL specified'
  6065. });
  6066. }
  6067. //</debug>
  6068. // check for xml or json data, and make sure json data is encoded
  6069. data = options.rawData || options.xmlData || jsonData || null;
  6070. if (jsonData && !Ext.isPrimitive(jsonData)) {
  6071. data = Ext.encode(data);
  6072. }
  6073. // make sure params are a url encoded string and include any extraParams if specified
  6074. if (Ext.isObject(params)) {
  6075. params = Ext.Object.toQueryString(params);
  6076. }
  6077. if (Ext.isObject(extraParams)) {
  6078. extraParams = Ext.Object.toQueryString(extraParams);
  6079. }
  6080. params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
  6081. urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
  6082. params = this.setupParams(options, params);
  6083. // decide the proper method for this request
  6084. method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
  6085. this.setupMethod(options, method);
  6086. disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
  6087. // if the method is get append date to prevent caching
  6088. if (method === 'GET' && disableCache) {
  6089. url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
  6090. }
  6091. // if the method is get or there is json/xml data append the params to the url
  6092. if ((method == 'GET' || data) && params) {
  6093. url = Ext.urlAppend(url, params);
  6094. params = null;
  6095. }
  6096. // allow params to be forced into the url
  6097. if (urlParams) {
  6098. url = Ext.urlAppend(url, urlParams);
  6099. }
  6100. return {
  6101. url: url,
  6102. method: method,
  6103. data: data || params || null
  6104. };
  6105. },
  6106. /**
  6107. * Template method for overriding url
  6108. * @template
  6109. * @private
  6110. * @param {Object} options
  6111. * @param {String} url
  6112. * @return {String} The modified url
  6113. */
  6114. setupUrl: function(options, url){
  6115. var form = this.getForm(options);
  6116. if (form) {
  6117. url = url || form.action;
  6118. }
  6119. return url;
  6120. },
  6121. /**
  6122. * Template method for overriding params
  6123. * @template
  6124. * @private
  6125. * @param {Object} options
  6126. * @param {String} params
  6127. * @return {String} The modified params
  6128. */
  6129. setupParams: function(options, params) {
  6130. var form = this.getForm(options),
  6131. serializedForm;
  6132. if (form && !this.isFormUpload(options)) {
  6133. serializedForm = Ext.Element.serializeForm(form);
  6134. params = params ? (params + '&' + serializedForm) : serializedForm;
  6135. }
  6136. return params;
  6137. },
  6138. /**
  6139. * Template method for overriding method
  6140. * @template
  6141. * @private
  6142. * @param {Object} options
  6143. * @param {String} method
  6144. * @return {String} The modified method
  6145. */
  6146. setupMethod: function(options, method){
  6147. if (this.isFormUpload(options)) {
  6148. return 'POST';
  6149. }
  6150. return method;
  6151. },
  6152. /**
  6153. * Setup all the headers for the request
  6154. * @private
  6155. * @param {Object} xhr The xhr object
  6156. * @param {Object} options The options for the request
  6157. * @param {Object} data The data for the request
  6158. * @param {Object} params The params for the request
  6159. */
  6160. setupHeaders: function(xhr, options, data, params){
  6161. var me = this,
  6162. headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
  6163. contentType = me.defaultPostHeader,
  6164. jsonData = options.jsonData,
  6165. xmlData = options.xmlData,
  6166. key,
  6167. header;
  6168. if (!headers['Content-Type'] && (data || params)) {
  6169. if (data) {
  6170. if (options.rawData) {
  6171. contentType = 'text/plain';
  6172. } else {
  6173. if (xmlData && Ext.isDefined(xmlData)) {
  6174. contentType = 'text/xml';
  6175. } else if (jsonData && Ext.isDefined(jsonData)) {
  6176. contentType = 'application/json';
  6177. }
  6178. }
  6179. }
  6180. headers['Content-Type'] = contentType;
  6181. }
  6182. if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
  6183. headers['X-Requested-With'] = me.defaultXhrHeader;
  6184. }
  6185. // set up all the request headers on the xhr object
  6186. try{
  6187. for (key in headers) {
  6188. if (headers.hasOwnProperty(key)) {
  6189. header = headers[key];
  6190. xhr.setRequestHeader(key, header);
  6191. }
  6192. }
  6193. } catch(e) {
  6194. me.fireEvent('exception', key, header);
  6195. }
  6196. return headers;
  6197. },
  6198. /**
  6199. * Creates the appropriate XHR transport for the browser.
  6200. * @private
  6201. */
  6202. getXhrInstance: (function(){
  6203. var options = [function(){
  6204. return new XMLHttpRequest();
  6205. }, function(){
  6206. return new ActiveXObject('MSXML2.XMLHTTP.3.0');
  6207. }, function(){
  6208. return new ActiveXObject('MSXML2.XMLHTTP');
  6209. }, function(){
  6210. return new ActiveXObject('Microsoft.XMLHTTP');
  6211. }], i = 0,
  6212. len = options.length,
  6213. xhr;
  6214. for(; i < len; ++i) {
  6215. try{
  6216. xhr = options[i];
  6217. xhr();
  6218. break;
  6219. }catch(e){}
  6220. }
  6221. return xhr;
  6222. })(),
  6223. /**
  6224. * Determines whether this object has a request outstanding.
  6225. * @param {Object} [request] Defaults to the last transaction
  6226. * @return {Boolean} True if there is an outstanding request.
  6227. */
  6228. isLoading : function(request) {
  6229. if (!request) {
  6230. request = this.getLatest();
  6231. }
  6232. if (!(request && request.xhr)) {
  6233. return false;
  6234. }
  6235. // if there is a connection and readyState is not 0 or 4
  6236. var state = request.xhr.readyState;
  6237. return !(state === 0 || state == 4);
  6238. },
  6239. /**
  6240. * Aborts an active request.
  6241. * @param {Object} [request] Defaults to the last request
  6242. */
  6243. abort : function(request) {
  6244. var me = this;
  6245. if (!request) {
  6246. request = me.getLatest();
  6247. }
  6248. if (request && me.isLoading(request)) {
  6249. /*
  6250. * Clear out the onreadystatechange here, this allows us
  6251. * greater control, the browser may/may not fire the function
  6252. * depending on a series of conditions.
  6253. */
  6254. request.xhr.onreadystatechange = null;
  6255. request.xhr.abort();
  6256. me.clearTimeout(request);
  6257. if (!request.timedout) {
  6258. request.aborted = true;
  6259. }
  6260. me.onComplete(request);
  6261. me.cleanup(request);
  6262. }
  6263. },
  6264. /**
  6265. * Aborts all active requests
  6266. */
  6267. abortAll: function(){
  6268. var requests = this.requests,
  6269. id;
  6270. for (id in requests) {
  6271. if (requests.hasOwnProperty(id)) {
  6272. this.abort(requests[id]);
  6273. }
  6274. }
  6275. },
  6276. /**
  6277. * Gets the most recent request
  6278. * @private
  6279. * @return {Object} The request. Null if there is no recent request
  6280. */
  6281. getLatest: function(){
  6282. var id = this.latestId,
  6283. request;
  6284. if (id) {
  6285. request = this.requests[id];
  6286. }
  6287. return request || null;
  6288. },
  6289. /**
  6290. * Fires when the state of the xhr changes
  6291. * @private
  6292. * @param {Object} request The request
  6293. */
  6294. onStateChange : function(request) {
  6295. if (request.xhr.readyState == 4) {
  6296. this.clearTimeout(request);
  6297. this.onComplete(request);
  6298. this.cleanup(request);
  6299. }
  6300. },
  6301. /**
  6302. * Clears the timeout on the request
  6303. * @private
  6304. * @param {Object} The request
  6305. */
  6306. clearTimeout: function(request){
  6307. clearTimeout(request.timeout);
  6308. delete request.timeout;
  6309. },
  6310. /**
  6311. * Cleans up any left over information from the request
  6312. * @private
  6313. * @param {Object} The request
  6314. */
  6315. cleanup: function(request){
  6316. request.xhr = null;
  6317. delete request.xhr;
  6318. },
  6319. /**
  6320. * To be called when the request has come back from the server
  6321. * @private
  6322. * @param {Object} request
  6323. * @return {Object} The response
  6324. */
  6325. onComplete : function(request) {
  6326. var me = this,
  6327. options = request.options,
  6328. result,
  6329. success,
  6330. response;
  6331. try {
  6332. result = me.parseStatus(request.xhr.status);
  6333. } catch (e) {
  6334. // in some browsers we can't access the status if the readyState is not 4, so the request has failed
  6335. result = {
  6336. success : false,
  6337. isException : false
  6338. };
  6339. }
  6340. success = result.success;
  6341. if (success) {
  6342. response = me.createResponse(request);
  6343. me.fireEvent('requestcomplete', me, response, options);
  6344. Ext.callback(options.success, options.scope, [response, options]);
  6345. } else {
  6346. if (result.isException || request.aborted || request.timedout) {
  6347. response = me.createException(request);
  6348. } else {
  6349. response = me.createResponse(request);
  6350. }
  6351. me.fireEvent('requestexception', me, response, options);
  6352. Ext.callback(options.failure, options.scope, [response, options]);
  6353. }
  6354. Ext.callback(options.callback, options.scope, [options, success, response]);
  6355. delete me.requests[request.id];
  6356. return response;
  6357. },
  6358. /**
  6359. * Checks if the response status was successful
  6360. * @param {Number} status The status code
  6361. * @return {Object} An object containing success/status state
  6362. */
  6363. parseStatus: function(status) {
  6364. // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
  6365. status = status == 1223 ? 204 : status;
  6366. var success = (status >= 200 && status < 300) || status == 304,
  6367. isException = false;
  6368. if (!success) {
  6369. switch (status) {
  6370. case 12002:
  6371. case 12029:
  6372. case 12030:
  6373. case 12031:
  6374. case 12152:
  6375. case 13030:
  6376. isException = true;
  6377. break;
  6378. }
  6379. }
  6380. return {
  6381. success: success,
  6382. isException: isException
  6383. };
  6384. },
  6385. /**
  6386. * Creates the response object
  6387. * @private
  6388. * @param {Object} request
  6389. */
  6390. createResponse : function(request) {
  6391. var xhr = request.xhr,
  6392. headers = {},
  6393. lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
  6394. count = lines.length,
  6395. line, index, key, value, response;
  6396. while (count--) {
  6397. line = lines[count];
  6398. index = line.indexOf(':');
  6399. if(index >= 0) {
  6400. key = line.substr(0, index).toLowerCase();
  6401. if (line.charAt(index + 1) == ' ') {
  6402. ++index;
  6403. }
  6404. headers[key] = line.substr(index + 1);
  6405. }
  6406. }
  6407. request.xhr = null;
  6408. delete request.xhr;
  6409. response = {
  6410. request: request,
  6411. requestId : request.id,
  6412. status : xhr.status,
  6413. statusText : xhr.statusText,
  6414. getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
  6415. getAllResponseHeaders : function(){ return headers; },
  6416. responseText : xhr.responseText,
  6417. responseXML : xhr.responseXML
  6418. };
  6419. // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
  6420. // functions created with getResponseHeader/getAllResponseHeaders
  6421. xhr = null;
  6422. return response;
  6423. },
  6424. /**
  6425. * Creates the exception object
  6426. * @private
  6427. * @param {Object} request
  6428. */
  6429. createException : function(request) {
  6430. return {
  6431. request : request,
  6432. requestId : request.id,
  6433. status : request.aborted ? -1 : 0,
  6434. statusText : request.aborted ? 'transaction aborted' : 'communication failure',
  6435. aborted: request.aborted,
  6436. timedout: request.timedout
  6437. };
  6438. }
  6439. });
  6440. /**
  6441. * @class Ext.Ajax
  6442. * @singleton
  6443. * @markdown
  6444. * @extends Ext.data.Connection
  6445. A singleton instance of an {@link Ext.data.Connection}. This class
  6446. is used to communicate with your server side code. It can be used as follows:
  6447. Ext.Ajax.request({
  6448. url: 'page.php',
  6449. params: {
  6450. id: 1
  6451. },
  6452. success: function(response){
  6453. var text = response.responseText;
  6454. // process server response here
  6455. }
  6456. });
  6457. Default options for all requests can be set by changing a property on the Ext.Ajax class:
  6458. Ext.Ajax.timeout = 60000; // 60 seconds
  6459. Any options specified in the request method for the Ajax request will override any
  6460. defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
  6461. request will be 60 seconds.
  6462. Ext.Ajax.timeout = 120000; // 120 seconds
  6463. Ext.Ajax.request({
  6464. url: 'page.aspx',
  6465. timeout: 60000
  6466. });
  6467. In general, this class will be used for all Ajax requests in your application.
  6468. The main reason for creating a separate {@link Ext.data.Connection} is for a
  6469. series of requests that share common settings that are different to all other
  6470. requests in the application.
  6471. */
  6472. Ext.define('Ext.Ajax', {
  6473. extend: 'Ext.data.Connection',
  6474. singleton: true,
  6475. /**
  6476. * @cfg {String} url @hide
  6477. */
  6478. /**
  6479. * @cfg {Object} extraParams @hide
  6480. */
  6481. /**
  6482. * @cfg {Object} defaultHeaders @hide
  6483. */
  6484. /**
  6485. * @cfg {String} method (Optional) @hide
  6486. */
  6487. /**
  6488. * @cfg {Number} timeout (Optional) @hide
  6489. */
  6490. /**
  6491. * @cfg {Boolean} autoAbort (Optional) @hide
  6492. */
  6493. /**
  6494. * @cfg {Boolean} disableCaching (Optional) @hide
  6495. */
  6496. /**
  6497. * @property {Boolean} disableCaching
  6498. * True to add a unique cache-buster param to GET requests. Defaults to true.
  6499. */
  6500. /**
  6501. * @property {String} url
  6502. * The default URL to be used for requests to the server.
  6503. * If the server receives all requests through one URL, setting this once is easier than
  6504. * entering it on every request.
  6505. */
  6506. /**
  6507. * @property {Object} extraParams
  6508. * An object containing properties which are used as extra parameters to each request made
  6509. * by this object. Session information and other data that you need
  6510. * to pass with each request are commonly put here.
  6511. */
  6512. /**
  6513. * @property {Object} defaultHeaders
  6514. * An object containing request headers which are added to each request made by this object.
  6515. */
  6516. /**
  6517. * @property {String} method
  6518. * The default HTTP method to be used for requests. Note that this is case-sensitive and
  6519. * should be all caps (if not set but params are present will use
  6520. * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
  6521. */
  6522. /**
  6523. * @property {Number} timeout
  6524. * The timeout in milliseconds to be used for requests. Defaults to 30000.
  6525. */
  6526. /**
  6527. * @property {Boolean} autoAbort
  6528. * Whether a new request should abort any pending requests.
  6529. */
  6530. autoAbort : false
  6531. });
  6532. /**
  6533. * A class used to load remote content to an Element. Sample usage:
  6534. *
  6535. * Ext.get('el').load({
  6536. * url: 'myPage.php',
  6537. * scripts: true,
  6538. * params: {
  6539. * id: 1
  6540. * }
  6541. * });
  6542. *
  6543. * In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
  6544. * will be used.
  6545. */
  6546. Ext.define('Ext.ElementLoader', {
  6547. /* Begin Definitions */
  6548. mixins: {
  6549. observable: 'Ext.util.Observable'
  6550. },
  6551. uses: [
  6552. 'Ext.data.Connection',
  6553. 'Ext.Ajax'
  6554. ],
  6555. statics: {
  6556. Renderer: {
  6557. Html: function(loader, response, active){
  6558. loader.getTarget().update(response.responseText, active.scripts === true);
  6559. return true;
  6560. }
  6561. }
  6562. },
  6563. /* End Definitions */
  6564. /**
  6565. * @cfg {String} url
  6566. * The url to retrieve the content from.
  6567. */
  6568. url: null,
  6569. /**
  6570. * @cfg {Object} params
  6571. * Any params to be attached to the Ajax request. These parameters will
  6572. * be overridden by any params in the load options.
  6573. */
  6574. params: null,
  6575. /**
  6576. * @cfg {Object} baseParams Params that will be attached to every request. These parameters
  6577. * will not be overridden by any params in the load options.
  6578. */
  6579. baseParams: null,
  6580. /**
  6581. * @cfg {Boolean/Object} autoLoad
  6582. * True to have the loader make a request as soon as it is created.
  6583. * This argument can also be a set of options that will be passed to {@link #load} is called.
  6584. */
  6585. autoLoad: false,
  6586. /**
  6587. * @cfg {HTMLElement/Ext.Element/String} target
  6588. * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
  6589. */
  6590. target: null,
  6591. /**
  6592. * @cfg {Boolean/String} loadMask
  6593. * True or a string to show when the element is loading.
  6594. */
  6595. loadMask: false,
  6596. /**
  6597. * @cfg {Object} ajaxOptions
  6598. * Any additional options to be passed to the request, for example timeout or headers.
  6599. */
  6600. ajaxOptions: null,
  6601. /**
  6602. * @cfg {Boolean} scripts
  6603. * True to parse any inline script tags in the response.
  6604. */
  6605. scripts: false,
  6606. /**
  6607. * @cfg {Function} success
  6608. * A function to be called when a load request is successful.
  6609. * Will be called with the following config parameters:
  6610. *
  6611. * - this - The ElementLoader instance.
  6612. * - response - The response object.
  6613. * - options - Ajax options.
  6614. */
  6615. /**
  6616. * @cfg {Function} failure A function to be called when a load request fails.
  6617. * Will be called with the following config parameters:
  6618. *
  6619. * - this - The ElementLoader instance.
  6620. * - response - The response object.
  6621. * - options - Ajax options.
  6622. */
  6623. /**
  6624. * @cfg {Function} callback A function to be called when a load request finishes.
  6625. * Will be called with the following config parameters:
  6626. *
  6627. * - this - The ElementLoader instance.
  6628. * - success - True if successful request.
  6629. * - response - The response object.
  6630. * - options - Ajax options.
  6631. */
  6632. /**
  6633. * @cfg {Object} scope
  6634. * The scope to execute the {@link #success} and {@link #failure} functions in.
  6635. */
  6636. /**
  6637. * @cfg {Function} renderer
  6638. * A custom function to render the content to the element. The passed parameters are:
  6639. *
  6640. * - The loader
  6641. * - The response
  6642. * - The active request
  6643. */
  6644. isLoader: true,
  6645. constructor: function(config) {
  6646. var me = this,
  6647. autoLoad;
  6648. config = config || {};
  6649. Ext.apply(me, config);
  6650. me.setTarget(me.target);
  6651. me.addEvents(
  6652. /**
  6653. * @event beforeload
  6654. * Fires before a load request is made to the server.
  6655. * Returning false from an event listener can prevent the load
  6656. * from occurring.
  6657. * @param {Ext.ElementLoader} this
  6658. * @param {Object} options The options passed to the request
  6659. */
  6660. 'beforeload',
  6661. /**
  6662. * @event exception
  6663. * Fires after an unsuccessful load.
  6664. * @param {Ext.ElementLoader} this
  6665. * @param {Object} response The response from the server
  6666. * @param {Object} options The options passed to the request
  6667. */
  6668. 'exception',
  6669. /**
  6670. * @event load
  6671. * Fires after a successful load.
  6672. * @param {Ext.ElementLoader} this
  6673. * @param {Object} response The response from the server
  6674. * @param {Object} options The options passed to the request
  6675. */
  6676. 'load'
  6677. );
  6678. // don't pass config because we have already applied it.
  6679. me.mixins.observable.constructor.call(me);
  6680. if (me.autoLoad) {
  6681. autoLoad = me.autoLoad;
  6682. if (autoLoad === true) {
  6683. autoLoad = {};
  6684. }
  6685. me.load(autoLoad);
  6686. }
  6687. },
  6688. /**
  6689. * Sets an {@link Ext.Element} as the target of this loader.
  6690. * Note that if the target is changed, any active requests will be aborted.
  6691. * @param {String/HTMLElement/Ext.Element} target The element or its ID.
  6692. */
  6693. setTarget: function(target){
  6694. var me = this;
  6695. target = Ext.get(target);
  6696. if (me.target && me.target != target) {
  6697. me.abort();
  6698. }
  6699. me.target = target;
  6700. },
  6701. /**
  6702. * Returns the target of this loader.
  6703. * @return {Ext.Component} The target or null if none exists.
  6704. */
  6705. getTarget: function(){
  6706. return this.target || null;
  6707. },
  6708. /**
  6709. * Aborts the active load request
  6710. */
  6711. abort: function(){
  6712. var active = this.active;
  6713. if (active !== undefined) {
  6714. Ext.Ajax.abort(active.request);
  6715. if (active.mask) {
  6716. this.removeMask();
  6717. }
  6718. delete this.active;
  6719. }
  6720. },
  6721. /**
  6722. * Removes the mask on the target
  6723. * @private
  6724. */
  6725. removeMask: function(){
  6726. this.target.unmask();
  6727. },
  6728. /**
  6729. * Adds the mask on the target
  6730. * @private
  6731. * @param {Boolean/Object} mask The mask configuration
  6732. */
  6733. addMask: function(mask){
  6734. this.target.mask(mask === true ? null : mask);
  6735. },
  6736. /**
  6737. * Loads new data from the server.
  6738. * @param {Object} options The options for the request. They can be any configuration option that can be specified for
  6739. * the class, with the exception of the target option. Note that any options passed to the method will override any
  6740. * class defaults.
  6741. */
  6742. load: function(options) {
  6743. //<debug>
  6744. if (!this.target) {
  6745. Ext.Error.raise('A valid target is required when loading content');
  6746. }
  6747. //</debug>
  6748. options = Ext.apply({}, options);
  6749. var me = this,
  6750. target = me.target,
  6751. mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
  6752. params = Ext.apply({}, options.params),
  6753. ajaxOptions = Ext.apply({}, options.ajaxOptions),
  6754. callback = options.callback || me.callback,
  6755. scope = options.scope || me.scope || me,
  6756. request;
  6757. Ext.applyIf(ajaxOptions, me.ajaxOptions);
  6758. Ext.applyIf(options, ajaxOptions);
  6759. Ext.applyIf(params, me.params);
  6760. Ext.apply(params, me.baseParams);
  6761. Ext.applyIf(options, {
  6762. url: me.url
  6763. });
  6764. //<debug>
  6765. if (!options.url) {
  6766. Ext.Error.raise('You must specify the URL from which content should be loaded');
  6767. }
  6768. //</debug>
  6769. Ext.apply(options, {
  6770. scope: me,
  6771. params: params,
  6772. callback: me.onComplete
  6773. });
  6774. if (me.fireEvent('beforeload', me, options) === false) {
  6775. return;
  6776. }
  6777. if (mask) {
  6778. me.addMask(mask);
  6779. }
  6780. request = Ext.Ajax.request(options);
  6781. me.active = {
  6782. request: request,
  6783. options: options,
  6784. mask: mask,
  6785. scope: scope,
  6786. callback: callback,
  6787. success: options.success || me.success,
  6788. failure: options.failure || me.failure,
  6789. renderer: options.renderer || me.renderer,
  6790. scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
  6791. };
  6792. me.setOptions(me.active, options);
  6793. },
  6794. /**
  6795. * Sets any additional options on the active request
  6796. * @private
  6797. * @param {Object} active The active request
  6798. * @param {Object} options The initial options
  6799. */
  6800. setOptions: Ext.emptyFn,
  6801. /**
  6802. * Parses the response after the request completes
  6803. * @private
  6804. * @param {Object} options Ajax options
  6805. * @param {Boolean} success Success status of the request
  6806. * @param {Object} response The response object
  6807. */
  6808. onComplete: function(options, success, response) {
  6809. var me = this,
  6810. active = me.active,
  6811. scope = active.scope,
  6812. renderer = me.getRenderer(active.renderer);
  6813. if (success) {
  6814. success = renderer.call(me, me, response, active);
  6815. }
  6816. if (success) {
  6817. Ext.callback(active.success, scope, [me, response, options]);
  6818. me.fireEvent('load', me, response, options);
  6819. } else {
  6820. Ext.callback(active.failure, scope, [me, response, options]);
  6821. me.fireEvent('exception', me, response, options);
  6822. }
  6823. Ext.callback(active.callback, scope, [me, success, response, options]);
  6824. if (active.mask) {
  6825. me.removeMask();
  6826. }
  6827. delete me.active;
  6828. },
  6829. /**
  6830. * Gets the renderer to use
  6831. * @private
  6832. * @param {String/Function} renderer The renderer to use
  6833. * @return {Function} A rendering function to use.
  6834. */
  6835. getRenderer: function(renderer){
  6836. if (Ext.isFunction(renderer)) {
  6837. return renderer;
  6838. }
  6839. return this.statics().Renderer.Html;
  6840. },
  6841. /**
  6842. * Automatically refreshes the content over a specified period.
  6843. * @param {Number} interval The interval to refresh in ms.
  6844. * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
  6845. */
  6846. startAutoRefresh: function(interval, options){
  6847. var me = this;
  6848. me.stopAutoRefresh();
  6849. me.autoRefresh = setInterval(function(){
  6850. me.load(options);
  6851. }, interval);
  6852. },
  6853. /**
  6854. * Clears any auto refresh. See {@link #startAutoRefresh}.
  6855. */
  6856. stopAutoRefresh: function(){
  6857. clearInterval(this.autoRefresh);
  6858. delete this.autoRefresh;
  6859. },
  6860. /**
  6861. * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
  6862. * @return {Boolean} True if the loader is automatically refreshing
  6863. */
  6864. isAutoRefreshing: function(){
  6865. return Ext.isDefined(this.autoRefresh);
  6866. },
  6867. /**
  6868. * Destroys the loader. Any active requests will be aborted.
  6869. */
  6870. destroy: function(){
  6871. var me = this;
  6872. me.stopAutoRefresh();
  6873. delete me.target;
  6874. me.abort();
  6875. me.clearListeners();
  6876. }
  6877. });
  6878. /**
  6879. * @class Ext.ComponentLoader
  6880. * @extends Ext.ElementLoader
  6881. *
  6882. * This class is used to load content via Ajax into a {@link Ext.Component}. In general
  6883. * this class will not be instanced directly, rather a loader configuration will be passed to the
  6884. * constructor of the {@link Ext.Component}.
  6885. *
  6886. * ## HTML Renderer
  6887. * By default, the content loaded will be processed as raw html. The response text
  6888. * from the request is taken and added to the component. This can be used in
  6889. * conjunction with the {@link #scripts} option to execute any inline scripts in
  6890. * the resulting content. Using this renderer has the same effect as passing the
  6891. * {@link Ext.Component#html} configuration option.
  6892. *
  6893. * ## Data Renderer
  6894. * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
  6895. * The content received from the response is passed to the {@link Ext.Component#update} method.
  6896. * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
  6897. * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
  6898. * configuration in conjunction with a {@link Ext.Component#tpl}.
  6899. *
  6900. * ## Component Renderer
  6901. * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
  6902. * Components to be loaded remotely into a Container. The response is expected to be a single/series of
  6903. * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
  6904. * and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
  6905. * the {@link Ext.container.Container#items} configuration on a Container.
  6906. *
  6907. * ## Custom Renderer
  6908. * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
  6909. *
  6910. * ## Example Usage
  6911. * new Ext.Component({
  6912. * tpl: '{firstName} - {lastName}',
  6913. * loader: {
  6914. * url: 'myPage.php',
  6915. * renderer: 'data',
  6916. * params: {
  6917. * userId: 1
  6918. * }
  6919. * }
  6920. * });
  6921. */
  6922. Ext.define('Ext.ComponentLoader', {
  6923. /* Begin Definitions */
  6924. extend: 'Ext.ElementLoader',
  6925. statics: {
  6926. Renderer: {
  6927. Data: function(loader, response, active){
  6928. var success = true;
  6929. try {
  6930. loader.getTarget().update(Ext.decode(response.responseText));
  6931. } catch (e) {
  6932. success = false;
  6933. }
  6934. return success;
  6935. },
  6936. Component: function(loader, response, active){
  6937. var success = true,
  6938. target = loader.getTarget(),
  6939. items = [];
  6940. //<debug>
  6941. if (!target.isContainer) {
  6942. Ext.Error.raise({
  6943. target: target,
  6944. msg: 'Components can only be loaded into a container'
  6945. });
  6946. }
  6947. //</debug>
  6948. try {
  6949. items = Ext.decode(response.responseText);
  6950. } catch (e) {
  6951. success = false;
  6952. }
  6953. if (success) {
  6954. if (active.removeAll) {
  6955. target.removeAll();
  6956. }
  6957. target.add(items);
  6958. }
  6959. return success;
  6960. }
  6961. }
  6962. },
  6963. /* End Definitions */
  6964. /**
  6965. * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
  6966. * If a string is passed it will be looked up via the id.
  6967. */
  6968. target: null,
  6969. /**
  6970. * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
  6971. */
  6972. loadMask: false,
  6973. /**
  6974. * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
  6975. * {@link #renderer}.
  6976. */
  6977. /**
  6978. * @cfg {String/Function} renderer
  6979. The type of content that is to be loaded into, which can be one of 3 types:
  6980. + **html** : Loads raw html content, see {@link Ext.Component#html}
  6981. + **data** : Loads raw html content, see {@link Ext.Component#data}
  6982. + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
  6983. Alternatively, you can pass a function which is called with the following parameters.
  6984. + loader - Loader instance
  6985. + response - The server response
  6986. + active - The active request
  6987. The function must return false is loading is not successful. Below is a sample of using a custom renderer:
  6988. new Ext.Component({
  6989. loader: {
  6990. url: 'myPage.php',
  6991. renderer: function(loader, response, active) {
  6992. var text = response.responseText;
  6993. loader.getTarget().update('The response is ' + text);
  6994. return true;
  6995. }
  6996. }
  6997. });
  6998. */
  6999. renderer: 'html',
  7000. /**
  7001. * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
  7002. * any active requests will be aborted.
  7003. * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
  7004. * it will be looked up via its id.
  7005. */
  7006. setTarget: function(target){
  7007. var me = this;
  7008. if (Ext.isString(target)) {
  7009. target = Ext.getCmp(target);
  7010. }
  7011. if (me.target && me.target != target) {
  7012. me.abort();
  7013. }
  7014. me.target = target;
  7015. },
  7016. // inherit docs
  7017. removeMask: function(){
  7018. this.target.setLoading(false);
  7019. },
  7020. /**
  7021. * Add the mask on the target
  7022. * @private
  7023. * @param {Boolean/Object} mask The mask configuration
  7024. */
  7025. addMask: function(mask){
  7026. this.target.setLoading(mask);
  7027. },
  7028. /**
  7029. * Get the target of this loader.
  7030. * @return {Ext.Component} target The target, null if none exists.
  7031. */
  7032. setOptions: function(active, options){
  7033. active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
  7034. },
  7035. /**
  7036. * Gets the renderer to use
  7037. * @private
  7038. * @param {String/Function} renderer The renderer to use
  7039. * @return {Function} A rendering function to use.
  7040. */
  7041. getRenderer: function(renderer){
  7042. if (Ext.isFunction(renderer)) {
  7043. return renderer;
  7044. }
  7045. var renderers = this.statics().Renderer;
  7046. switch (renderer) {
  7047. case 'component':
  7048. return renderers.Component;
  7049. case 'data':
  7050. return renderers.Data;
  7051. default:
  7052. return Ext.ElementLoader.Renderer.Html;
  7053. }
  7054. }
  7055. });
  7056. /**
  7057. * @author Ed Spencer
  7058. *
  7059. * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
  7060. * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
  7061. * express like this:
  7062. *
  7063. * Ext.define('User', {
  7064. * extend: 'Ext.data.Model',
  7065. * fields: ['id', 'name', 'email'],
  7066. *
  7067. * hasMany: {model: 'Order', name: 'orders'}
  7068. * });
  7069. *
  7070. * Ext.define('Order', {
  7071. * extend: 'Ext.data.Model',
  7072. * fields: ['id', 'user_id', 'status', 'price'],
  7073. *
  7074. * belongsTo: 'User'
  7075. * });
  7076. *
  7077. * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
  7078. * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
  7079. * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
  7080. * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
  7081. *
  7082. * **Further Reading**
  7083. *
  7084. * - {@link Ext.data.HasManyAssociation hasMany associations}
  7085. * - {@link Ext.data.BelongsToAssociation belongsTo associations}
  7086. * - {@link Ext.data.Model using Models}
  7087. *
  7088. * # Self association models
  7089. *
  7090. * We can also have models that create parent/child associations between the same type. Below is an example, where
  7091. * groups can be nested inside other groups:
  7092. *
  7093. * // Server Data
  7094. * {
  7095. * "groups": {
  7096. * "id": 10,
  7097. * "parent_id": 100,
  7098. * "name": "Main Group",
  7099. * "parent_group": {
  7100. * "id": 100,
  7101. * "parent_id": null,
  7102. * "name": "Parent Group"
  7103. * },
  7104. * "child_groups": [{
  7105. * "id": 2,
  7106. * "parent_id": 10,
  7107. * "name": "Child Group 1"
  7108. * },{
  7109. * "id": 3,
  7110. * "parent_id": 10,
  7111. * "name": "Child Group 2"
  7112. * },{
  7113. * "id": 4,
  7114. * "parent_id": 10,
  7115. * "name": "Child Group 3"
  7116. * }]
  7117. * }
  7118. * }
  7119. *
  7120. * // Client code
  7121. * Ext.define('Group', {
  7122. * extend: 'Ext.data.Model',
  7123. * fields: ['id', 'parent_id', 'name'],
  7124. * proxy: {
  7125. * type: 'ajax',
  7126. * url: 'data.json',
  7127. * reader: {
  7128. * type: 'json',
  7129. * root: 'groups'
  7130. * }
  7131. * },
  7132. * associations: [{
  7133. * type: 'hasMany',
  7134. * model: 'Group',
  7135. * primaryKey: 'id',
  7136. * foreignKey: 'parent_id',
  7137. * autoLoad: true,
  7138. * associationKey: 'child_groups' // read child data from child_groups
  7139. * }, {
  7140. * type: 'belongsTo',
  7141. * model: 'Group',
  7142. * primaryKey: 'id',
  7143. * foreignKey: 'parent_id',
  7144. * associationKey: 'parent_group' // read parent data from parent_group
  7145. * }]
  7146. * });
  7147. *
  7148. * Ext.onReady(function(){
  7149. *
  7150. * Group.load(10, {
  7151. * success: function(group){
  7152. * console.log(group.getGroup().get('name'));
  7153. *
  7154. * group.groups().each(function(rec){
  7155. * console.log(rec.get('name'));
  7156. * });
  7157. * }
  7158. * });
  7159. *
  7160. * });
  7161. *
  7162. */
  7163. Ext.define('Ext.data.Association', {
  7164. /**
  7165. * @cfg {String} ownerModel (required)
  7166. * The string name of the model that owns the association.
  7167. */
  7168. /**
  7169. * @cfg {String} associatedModel (required)
  7170. * The string name of the model that is being associated with.
  7171. */
  7172. /**
  7173. * @cfg {String} primaryKey
  7174. * The name of the primary key on the associated model. In general this will be the
  7175. * {@link Ext.data.Model#idProperty} of the Model.
  7176. */
  7177. primaryKey: 'id',
  7178. /**
  7179. * @cfg {Ext.data.reader.Reader} reader
  7180. * A special reader to read associated data
  7181. */
  7182. /**
  7183. * @cfg {String} associationKey
  7184. * The name of the property in the data to read the association from. Defaults to the name of the associated model.
  7185. */
  7186. defaultReaderType: 'json',
  7187. statics: {
  7188. create: function(association){
  7189. if (!association.isAssociation) {
  7190. if (Ext.isString(association)) {
  7191. association = {
  7192. type: association
  7193. };
  7194. }
  7195. switch (association.type) {
  7196. case 'belongsTo':
  7197. return Ext.create('Ext.data.BelongsToAssociation', association);
  7198. case 'hasMany':
  7199. return Ext.create('Ext.data.HasManyAssociation', association);
  7200. //TODO Add this back when it's fixed
  7201. // case 'polymorphic':
  7202. // return Ext.create('Ext.data.PolymorphicAssociation', association);
  7203. default:
  7204. //<debug>
  7205. Ext.Error.raise('Unknown Association type: "' + association.type + '"');
  7206. //</debug>
  7207. }
  7208. }
  7209. return association;
  7210. }
  7211. },
  7212. /**
  7213. * Creates the Association object.
  7214. * @param {Object} [config] Config object.
  7215. */
  7216. constructor: function(config) {
  7217. Ext.apply(this, config);
  7218. var types = Ext.ModelManager.types,
  7219. ownerName = config.ownerModel,
  7220. associatedName = config.associatedModel,
  7221. ownerModel = types[ownerName],
  7222. associatedModel = types[associatedName],
  7223. ownerProto;
  7224. //<debug>
  7225. if (ownerModel === undefined) {
  7226. Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
  7227. }
  7228. if (associatedModel === undefined) {
  7229. Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
  7230. }
  7231. //</debug>
  7232. this.ownerModel = ownerModel;
  7233. this.associatedModel = associatedModel;
  7234. /**
  7235. * @property {String} ownerName
  7236. * The name of the model that 'owns' the association
  7237. */
  7238. /**
  7239. * @property {String} associatedName
  7240. * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
  7241. * 'Order')
  7242. */
  7243. Ext.applyIf(this, {
  7244. ownerName : ownerName,
  7245. associatedName: associatedName
  7246. });
  7247. },
  7248. /**
  7249. * Get a specialized reader for reading associated data
  7250. * @return {Ext.data.reader.Reader} The reader, null if not supplied
  7251. */
  7252. getReader: function(){
  7253. var me = this,
  7254. reader = me.reader,
  7255. model = me.associatedModel;
  7256. if (reader) {
  7257. if (Ext.isString(reader)) {
  7258. reader = {
  7259. type: reader
  7260. };
  7261. }
  7262. if (reader.isReader) {
  7263. reader.setModel(model);
  7264. } else {
  7265. Ext.applyIf(reader, {
  7266. model: model,
  7267. type : me.defaultReaderType
  7268. });
  7269. }
  7270. me.reader = Ext.createByAlias('reader.' + reader.type, reader);
  7271. }
  7272. return me.reader || null;
  7273. }
  7274. });
  7275. /**
  7276. * @author Ed Spencer
  7277. * @class Ext.ModelManager
  7278. * @extends Ext.AbstractManager
  7279. The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
  7280. __Creating Model Instances__
  7281. Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
  7282. the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
  7283. this by using the Model type directly. The following 3 snippets are equivalent:
  7284. Ext.define('User', {
  7285. extend: 'Ext.data.Model',
  7286. fields: ['first', 'last']
  7287. });
  7288. // method 1, create using Ext.create (recommended)
  7289. Ext.create('User', {
  7290. first: 'Ed',
  7291. last: 'Spencer'
  7292. });
  7293. // method 2, create through the manager (deprecated)
  7294. Ext.ModelManager.create({
  7295. first: 'Ed',
  7296. last: 'Spencer'
  7297. }, 'User');
  7298. // method 3, create on the type directly
  7299. new User({
  7300. first: 'Ed',
  7301. last: 'Spencer'
  7302. });
  7303. __Accessing Model Types__
  7304. A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
  7305. are normal classes, you can access the type directly. The following snippets are equivalent:
  7306. Ext.define('User', {
  7307. extend: 'Ext.data.Model',
  7308. fields: ['first', 'last']
  7309. });
  7310. // method 1, access model type through the manager
  7311. var UserType = Ext.ModelManager.getModel('User');
  7312. // method 2, reference the type directly
  7313. var UserType = User;
  7314. * @markdown
  7315. * @singleton
  7316. */
  7317. Ext.define('Ext.ModelManager', {
  7318. extend: 'Ext.AbstractManager',
  7319. alternateClassName: 'Ext.ModelMgr',
  7320. requires: ['Ext.data.Association'],
  7321. singleton: true,
  7322. typeName: 'mtype',
  7323. /**
  7324. * Private stack of associations that must be created once their associated model has been defined
  7325. * @property {Ext.data.Association[]} associationStack
  7326. */
  7327. associationStack: [],
  7328. /**
  7329. * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
  7330. * immediately, as are any addition plugins defined in the model config.
  7331. * @private
  7332. */
  7333. registerType: function(name, config) {
  7334. var proto = config.prototype,
  7335. model;
  7336. if (proto && proto.isModel) {
  7337. // registering an already defined model
  7338. model = config;
  7339. } else {
  7340. // passing in a configuration
  7341. if (!config.extend) {
  7342. config.extend = 'Ext.data.Model';
  7343. }
  7344. model = Ext.define(name, config);
  7345. }
  7346. this.types[name] = model;
  7347. return model;
  7348. },
  7349. /**
  7350. * @private
  7351. * Private callback called whenever a model has just been defined. This sets up any associations
  7352. * that were waiting for the given model to be defined
  7353. * @param {Function} model The model that was just created
  7354. */
  7355. onModelDefined: function(model) {
  7356. var stack = this.associationStack,
  7357. length = stack.length,
  7358. create = [],
  7359. association, i, created;
  7360. for (i = 0; i < length; i++) {
  7361. association = stack[i];
  7362. if (association.associatedModel == model.modelName) {
  7363. create.push(association);
  7364. }
  7365. }
  7366. for (i = 0, length = create.length; i < length; i++) {
  7367. created = create[i];
  7368. this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
  7369. Ext.Array.remove(stack, created);
  7370. }
  7371. },
  7372. /**
  7373. * Registers an association where one of the models defined doesn't exist yet.
  7374. * The ModelManager will check when new models are registered if it can link them
  7375. * together
  7376. * @private
  7377. * @param {Ext.data.Association} association The association
  7378. */
  7379. registerDeferredAssociation: function(association){
  7380. this.associationStack.push(association);
  7381. },
  7382. /**
  7383. * Returns the {@link Ext.data.Model} for a given model name
  7384. * @param {String/Object} id The id of the model or the model instance.
  7385. * @return {Ext.data.Model} a model class.
  7386. */
  7387. getModel: function(id) {
  7388. var model = id;
  7389. if (typeof model == 'string') {
  7390. model = this.types[model];
  7391. }
  7392. return model;
  7393. },
  7394. /**
  7395. * Creates a new instance of a Model using the given data.
  7396. *
  7397. * This method is deprecated. Use {@link Ext#create Ext.create} instead. For example:
  7398. *
  7399. * Ext.create('User', {
  7400. * first: 'Ed',
  7401. * last: 'Spencer'
  7402. * });
  7403. *
  7404. * @param {Object} data Data to initialize the Model's fields with
  7405. * @param {String} name The name of the model to create
  7406. * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
  7407. */
  7408. create: function(config, name, id) {
  7409. var con = typeof name == 'function' ? name : this.types[name || config.name];
  7410. return new con(config, id);
  7411. }
  7412. }, function() {
  7413. /**
  7414. * Old way for creating Model classes. Instead use:
  7415. *
  7416. * Ext.define("MyModel", {
  7417. * extend: "Ext.data.Model",
  7418. * fields: []
  7419. * });
  7420. *
  7421. * @param {String} name Name of the Model class.
  7422. * @param {Object} config A configuration object for the Model you wish to create.
  7423. * @return {Ext.data.Model} The newly registered Model
  7424. * @member Ext
  7425. * @deprecated 4.0.0 Use {@link Ext#define} instead.
  7426. */
  7427. Ext.regModel = function() {
  7428. //<debug>
  7429. if (Ext.isDefined(Ext.global.console)) {
  7430. Ext.global.console.warn('Ext.regModel has been deprecated. Models can now be created by extending Ext.data.Model: Ext.define("MyModel", {extend: "Ext.data.Model", fields: []});.');
  7431. }
  7432. //</debug>
  7433. return this.ModelManager.registerType.apply(this.ModelManager, arguments);
  7434. };
  7435. });
  7436. /**
  7437. * @singleton
  7438. *
  7439. * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
  7440. *
  7441. * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
  7442. *
  7443. * {
  7444. * ptype: 'gridviewdragdrop',
  7445. * dragText: 'Drag and drop to reorganize'
  7446. * }
  7447. *
  7448. * Or just use the ptype on its own:
  7449. *
  7450. * 'gridviewdragdrop'
  7451. *
  7452. * Alternatively you can instantiate the plugin with Ext.create:
  7453. *
  7454. * Ext.create('Ext.view.plugin.AutoComplete', {
  7455. * ptype: 'gridviewdragdrop',
  7456. * dragText: 'Drag and drop to reorganize'
  7457. * })
  7458. */
  7459. Ext.define('Ext.PluginManager', {
  7460. extend: 'Ext.AbstractManager',
  7461. alternateClassName: 'Ext.PluginMgr',
  7462. singleton: true,
  7463. typeName: 'ptype',
  7464. /**
  7465. * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
  7466. * instantiate.
  7467. * @param {Object} config A configuration object for the Plugin you wish to create.
  7468. * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
  7469. * contain a `ptype`. (Optional if the config contains a `ptype`).
  7470. * @return {Ext.Component} The newly instantiated Plugin.
  7471. */
  7472. //create: function(plugin, defaultType) {
  7473. // if (plugin instanceof this) {
  7474. // return plugin;
  7475. // } else {
  7476. // var type, config = {};
  7477. //
  7478. // if (Ext.isString(plugin)) {
  7479. // type = plugin;
  7480. // }
  7481. // else {
  7482. // type = plugin[this.typeName] || defaultType;
  7483. // config = plugin;
  7484. // }
  7485. //
  7486. // return Ext.createByAlias('plugin.' + type, config);
  7487. // }
  7488. //},
  7489. create : function(config, defaultType){
  7490. if (config.init) {
  7491. return config;
  7492. } else {
  7493. return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
  7494. }
  7495. // Prior system supported Singleton plugins.
  7496. //var PluginCls = this.types[config.ptype || defaultType];
  7497. //if (PluginCls.init) {
  7498. // return PluginCls;
  7499. //} else {
  7500. // return new PluginCls(config);
  7501. //}
  7502. },
  7503. /**
  7504. * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
  7505. * @param {String} type The type to search for
  7506. * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
  7507. * truthy
  7508. * @return {Ext.AbstractPlugin[]} All matching plugins
  7509. */
  7510. findByType: function(type, defaultsOnly) {
  7511. var matches = [],
  7512. types = this.types;
  7513. for (var name in types) {
  7514. if (!types.hasOwnProperty(name)) {
  7515. continue;
  7516. }
  7517. var item = types[name];
  7518. if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
  7519. matches.push(item);
  7520. }
  7521. }
  7522. return matches;
  7523. }
  7524. }, function() {
  7525. /**
  7526. * Shorthand for {@link Ext.PluginManager#registerType}
  7527. * @param {String} ptype The ptype mnemonic string by which the Plugin class
  7528. * may be looked up.
  7529. * @param {Function} cls The new Plugin class.
  7530. * @member Ext
  7531. * @method preg
  7532. */
  7533. Ext.preg = function() {
  7534. return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
  7535. };
  7536. });
  7537. /**
  7538. * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
  7539. *
  7540. * An instance of this class may be created by passing to the constructor either a single argument, or multiple
  7541. * arguments:
  7542. *
  7543. * # Single argument: String/Array
  7544. *
  7545. * The single argument may be either a String or an Array:
  7546. *
  7547. * - String:
  7548. *
  7549. * var t = new Ext.Template("<div>Hello {0}.</div>");
  7550. * t.{@link #append}('some-element', ['foo']);
  7551. *
  7552. * - Array:
  7553. *
  7554. * An Array will be combined with `join('')`.
  7555. *
  7556. * var t = new Ext.Template([
  7557. * '<div name="{id}">',
  7558. * '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
  7559. * '</div>',
  7560. * ]);
  7561. * t.{@link #compile}();
  7562. * t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
  7563. *
  7564. * # Multiple arguments: String, Object, Array, ...
  7565. *
  7566. * Multiple arguments will be combined with `join('')`.
  7567. *
  7568. * var t = new Ext.Template(
  7569. * '<div name="{id}">',
  7570. * '<span class="{cls}">{name} {value}</span>',
  7571. * '</div>',
  7572. * // a configuration object:
  7573. * {
  7574. * compiled: true, // {@link #compile} immediately
  7575. * }
  7576. * );
  7577. *
  7578. * # Notes
  7579. *
  7580. * - For a list of available format functions, see {@link Ext.util.Format}.
  7581. * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
  7582. */
  7583. Ext.define('Ext.Template', {
  7584. /* Begin Definitions */
  7585. requires: ['Ext.DomHelper', 'Ext.util.Format'],
  7586. inheritableStatics: {
  7587. /**
  7588. * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
  7589. * @param {String/HTMLElement} el A DOM element or its id
  7590. * @param {Object} config (optional) Config object
  7591. * @return {Ext.Template} The created template
  7592. * @static
  7593. * @inheritable
  7594. */
  7595. from: function(el, config) {
  7596. el = Ext.getDom(el);
  7597. return new this(el.value || el.innerHTML, config || '');
  7598. }
  7599. },
  7600. /* End Definitions */
  7601. /**
  7602. * Creates new template.
  7603. *
  7604. * @param {String...} html List of strings to be concatenated into template.
  7605. * Alternatively an array of strings can be given, but then no config object may be passed.
  7606. * @param {Object} config (optional) Config object
  7607. */
  7608. constructor: function(html) {
  7609. var me = this,
  7610. args = arguments,
  7611. buffer = [],
  7612. i = 0,
  7613. length = args.length,
  7614. value;
  7615. me.initialConfig = {};
  7616. if (length > 1) {
  7617. for (; i < length; i++) {
  7618. value = args[i];
  7619. if (typeof value == 'object') {
  7620. Ext.apply(me.initialConfig, value);
  7621. Ext.apply(me, value);
  7622. } else {
  7623. buffer.push(value);
  7624. }
  7625. }
  7626. html = buffer.join('');
  7627. } else {
  7628. if (Ext.isArray(html)) {
  7629. buffer.push(html.join(''));
  7630. } else {
  7631. buffer.push(html);
  7632. }
  7633. }
  7634. // @private
  7635. me.html = buffer.join('');
  7636. if (me.compiled) {
  7637. me.compile();
  7638. }
  7639. },
  7640. isTemplate: true,
  7641. /**
  7642. * @cfg {Boolean} compiled
  7643. * True to immediately compile the template. Defaults to false.
  7644. */
  7645. /**
  7646. * @cfg {Boolean} disableFormats
  7647. * True to disable format functions in the template. If the template doesn't contain
  7648. * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
  7649. */
  7650. disableFormats: false,
  7651. re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
  7652. /**
  7653. * Returns an HTML fragment of this template with the specified values applied.
  7654. *
  7655. * @param {Object/Array} values The template values. Can be an array if your params are numeric:
  7656. *
  7657. * var tpl = new Ext.Template('Name: {0}, Age: {1}');
  7658. * tpl.applyTemplate(['John', 25]);
  7659. *
  7660. * or an object:
  7661. *
  7662. * var tpl = new Ext.Template('Name: {name}, Age: {age}');
  7663. * tpl.applyTemplate({name: 'John', age: 25});
  7664. *
  7665. * @return {String} The HTML fragment
  7666. */
  7667. applyTemplate: function(values) {
  7668. var me = this,
  7669. useFormat = me.disableFormats !== true,
  7670. fm = Ext.util.Format,
  7671. tpl = me;
  7672. if (me.compiled) {
  7673. return me.compiled(values);
  7674. }
  7675. function fn(m, name, format, args) {
  7676. if (format && useFormat) {
  7677. if (args) {
  7678. args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
  7679. } else {
  7680. args = [values[name]];
  7681. }
  7682. if (format.substr(0, 5) == "this.") {
  7683. return tpl[format.substr(5)].apply(tpl, args);
  7684. }
  7685. else {
  7686. return fm[format].apply(fm, args);
  7687. }
  7688. }
  7689. else {
  7690. return values[name] !== undefined ? values[name] : "";
  7691. }
  7692. }
  7693. return me.html.replace(me.re, fn);
  7694. },
  7695. /**
  7696. * Sets the HTML used as the template and optionally compiles it.
  7697. * @param {String} html
  7698. * @param {Boolean} compile (optional) True to compile the template.
  7699. * @return {Ext.Template} this
  7700. */
  7701. set: function(html, compile) {
  7702. var me = this;
  7703. me.html = html;
  7704. me.compiled = null;
  7705. return compile ? me.compile() : me;
  7706. },
  7707. compileARe: /\\/g,
  7708. compileBRe: /(\r\n|\n)/g,
  7709. compileCRe: /'/g,
  7710. /**
  7711. * Compiles the template into an internal function, eliminating the RegEx overhead.
  7712. * @return {Ext.Template} this
  7713. */
  7714. compile: function() {
  7715. var me = this,
  7716. fm = Ext.util.Format,
  7717. useFormat = me.disableFormats !== true,
  7718. body, bodyReturn;
  7719. function fn(m, name, format, args) {
  7720. if (format && useFormat) {
  7721. args = args ? ',' + args: "";
  7722. if (format.substr(0, 5) != "this.") {
  7723. format = "fm." + format + '(';
  7724. }
  7725. else {
  7726. format = 'this.' + format.substr(5) + '(';
  7727. }
  7728. }
  7729. else {
  7730. args = '';
  7731. format = "(values['" + name + "'] == undefined ? '' : ";
  7732. }
  7733. return "'," + format + "values['" + name + "']" + args + ") ,'";
  7734. }
  7735. bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
  7736. body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
  7737. eval(body);
  7738. return me;
  7739. },
  7740. /**
  7741. * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
  7742. *
  7743. * @param {String/HTMLElement/Ext.Element} el The context element
  7744. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7745. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7746. * @return {HTMLElement/Ext.Element} The new node or Element
  7747. */
  7748. insertFirst: function(el, values, returnElement) {
  7749. return this.doInsert('afterBegin', el, values, returnElement);
  7750. },
  7751. /**
  7752. * Applies the supplied values to the template and inserts the new node(s) before el.
  7753. *
  7754. * @param {String/HTMLElement/Ext.Element} el The context element
  7755. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7756. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7757. * @return {HTMLElement/Ext.Element} The new node or Element
  7758. */
  7759. insertBefore: function(el, values, returnElement) {
  7760. return this.doInsert('beforeBegin', el, values, returnElement);
  7761. },
  7762. /**
  7763. * Applies the supplied values to the template and inserts the new node(s) after el.
  7764. *
  7765. * @param {String/HTMLElement/Ext.Element} el The context element
  7766. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7767. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7768. * @return {HTMLElement/Ext.Element} The new node or Element
  7769. */
  7770. insertAfter: function(el, values, returnElement) {
  7771. return this.doInsert('afterEnd', el, values, returnElement);
  7772. },
  7773. /**
  7774. * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
  7775. *
  7776. * For example usage see {@link Ext.Template Ext.Template class docs}.
  7777. *
  7778. * @param {String/HTMLElement/Ext.Element} el The context element
  7779. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7780. * @param {Boolean} returnElement (optional) true to return an Ext.Element.
  7781. * @return {HTMLElement/Ext.Element} The new node or Element
  7782. */
  7783. append: function(el, values, returnElement) {
  7784. return this.doInsert('beforeEnd', el, values, returnElement);
  7785. },
  7786. doInsert: function(where, el, values, returnEl) {
  7787. el = Ext.getDom(el);
  7788. var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
  7789. return returnEl ? Ext.get(newNode, true) : newNode;
  7790. },
  7791. /**
  7792. * Applies the supplied values to the template and overwrites the content of el with the new node(s).
  7793. *
  7794. * @param {String/HTMLElement/Ext.Element} el The context element
  7795. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7796. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7797. * @return {HTMLElement/Ext.Element} The new node or Element
  7798. */
  7799. overwrite: function(el, values, returnElement) {
  7800. el = Ext.getDom(el);
  7801. el.innerHTML = this.applyTemplate(values);
  7802. return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
  7803. }
  7804. }, function() {
  7805. /**
  7806. * @method apply
  7807. * @member Ext.Template
  7808. * Alias for {@link #applyTemplate}.
  7809. * @alias Ext.Template#applyTemplate
  7810. */
  7811. this.createAlias('apply', 'applyTemplate');
  7812. });
  7813. /**
  7814. * A template class that supports advanced functionality like:
  7815. *
  7816. * - Autofilling arrays using templates and sub-templates
  7817. * - Conditional processing with basic comparison operators
  7818. * - Basic math function support
  7819. * - Execute arbitrary inline code with special built-in template variables
  7820. * - Custom member functions
  7821. * - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
  7822. *
  7823. * XTemplate provides the templating mechanism built into:
  7824. *
  7825. * - {@link Ext.view.View}
  7826. *
  7827. * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
  7828. * demonstrate all of the supported features.
  7829. *
  7830. * # Sample Data
  7831. *
  7832. * This is the data object used for reference in each code example:
  7833. *
  7834. * var data = {
  7835. * name: 'Tommy Maintz',
  7836. * title: 'Lead Developer',
  7837. * company: 'Sencha Inc.',
  7838. * email: 'tommy@sencha.com',
  7839. * address: '5 Cups Drive',
  7840. * city: 'Palo Alto',
  7841. * state: 'CA',
  7842. * zip: '44102',
  7843. * drinks: ['Coffee', 'Soda', 'Water'],
  7844. * kids: [
  7845. * {
  7846. * name: 'Joshua',
  7847. * age:3
  7848. * },
  7849. * {
  7850. * name: 'Matthew',
  7851. * age:2
  7852. * },
  7853. * {
  7854. * name: 'Solomon',
  7855. * age:0
  7856. * }
  7857. * ]
  7858. * };
  7859. *
  7860. * # Auto filling of arrays
  7861. *
  7862. * The **tpl** tag and the **for** operator are used to process the provided data object:
  7863. *
  7864. * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
  7865. * tag for each item in the array.
  7866. * - If for="." is specified, the data object provided is examined.
  7867. * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
  7868. *
  7869. * Examples:
  7870. *
  7871. * <tpl for=".">...</tpl> // loop through array at root node
  7872. * <tpl for="foo">...</tpl> // loop through array at foo node
  7873. * <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
  7874. *
  7875. * Using the sample data above:
  7876. *
  7877. * var tpl = new Ext.XTemplate(
  7878. * '<p>Kids: ',
  7879. * '<tpl for=".">', // process the data.kids node
  7880. * '<p>{#}. {name}</p>', // use current array index to autonumber
  7881. * '</tpl></p>'
  7882. * );
  7883. * tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
  7884. *
  7885. * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
  7886. * object to populate the template:
  7887. *
  7888. * var tpl = new Ext.XTemplate(
  7889. * '<p>Name: {name}</p>',
  7890. * '<p>Title: {title}</p>',
  7891. * '<p>Company: {company}</p>',
  7892. * '<p>Kids: ',
  7893. * '<tpl for="kids">', // interrogate the kids property within the data
  7894. * '<p>{name}</p>',
  7895. * '</tpl></p>'
  7896. * );
  7897. * tpl.overwrite(panel.body, data); // pass the root node of the data object
  7898. *
  7899. * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
  7900. * loop. This variable will represent the value of the array at the current index:
  7901. *
  7902. * var tpl = new Ext.XTemplate(
  7903. * '<p>{name}\'s favorite beverages:</p>',
  7904. * '<tpl for="drinks">',
  7905. * '<div> - {.}</div>',
  7906. * '</tpl>'
  7907. * );
  7908. * tpl.overwrite(panel.body, data);
  7909. *
  7910. * When processing a sub-template, for example while looping through a child array, you can access the parent object's
  7911. * members via the **parent** object:
  7912. *
  7913. * var tpl = new Ext.XTemplate(
  7914. * '<p>Name: {name}</p>',
  7915. * '<p>Kids: ',
  7916. * '<tpl for="kids">',
  7917. * '<tpl if="age &gt; 1">',
  7918. * '<p>{name}</p>',
  7919. * '<p>Dad: {parent.name}</p>',
  7920. * '</tpl>',
  7921. * '</tpl></p>'
  7922. * );
  7923. * tpl.overwrite(panel.body, data);
  7924. *
  7925. * # Conditional processing with basic comparison operators
  7926. *
  7927. * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
  7928. * specific parts of the template. Notes:
  7929. *
  7930. * - Double quotes must be encoded if used within the conditional
  7931. * - There is no else operator -- if needed, two opposite if statements should be used.
  7932. *
  7933. * Examples:
  7934. *
  7935. * <tpl if="age > 1 && age < 10">Child</tpl>
  7936. * <tpl if="age >= 10 && age < 18">Teenager</tpl>
  7937. * <tpl if="this.isGirl(name)">...</tpl>
  7938. * <tpl if="id==\'download\'">...</tpl>
  7939. * <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
  7940. * // no good:
  7941. * <tpl if="name == "Tommy"">Hello</tpl>
  7942. * // encode " if it is part of the condition, e.g.
  7943. * <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
  7944. *
  7945. * Using the sample data above:
  7946. *
  7947. * var tpl = new Ext.XTemplate(
  7948. * '<p>Name: {name}</p>',
  7949. * '<p>Kids: ',
  7950. * '<tpl for="kids">',
  7951. * '<tpl if="age &gt; 1">',
  7952. * '<p>{name}</p>',
  7953. * '</tpl>',
  7954. * '</tpl></p>'
  7955. * );
  7956. * tpl.overwrite(panel.body, data);
  7957. *
  7958. * # Basic math support
  7959. *
  7960. * The following basic math operators may be applied directly on numeric data values:
  7961. *
  7962. * + - * /
  7963. *
  7964. * For example:
  7965. *
  7966. * var tpl = new Ext.XTemplate(
  7967. * '<p>Name: {name}</p>',
  7968. * '<p>Kids: ',
  7969. * '<tpl for="kids">',
  7970. * '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
  7971. * '<p>{#}: {name}</p>', // <-- Auto-number each item
  7972. * '<p>In 5 Years: {age+5}</p>', // <-- Basic math
  7973. * '<p>Dad: {parent.name}</p>',
  7974. * '</tpl>',
  7975. * '</tpl></p>'
  7976. * );
  7977. * tpl.overwrite(panel.body, data);
  7978. *
  7979. * # Execute arbitrary inline code with special built-in template variables
  7980. *
  7981. * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
  7982. * variables available in that code:
  7983. *
  7984. * - **values**: The values in the current scope. If you are using scope changing sub-templates,
  7985. * you can change what values is.
  7986. * - **parent**: The scope (values) of the ancestor template.
  7987. * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
  7988. * - **xcount**: If you are in a looping template, the total length of the array you are looping.
  7989. *
  7990. * This example demonstrates basic row striping using an inline code block and the xindex variable:
  7991. *
  7992. * var tpl = new Ext.XTemplate(
  7993. * '<p>Name: {name}</p>',
  7994. * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
  7995. * '<p>Kids: ',
  7996. * '<tpl for="kids">',
  7997. * '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
  7998. * '{name}',
  7999. * '</div>',
  8000. * '</tpl></p>'
  8001. * );
  8002. * tpl.overwrite(panel.body, data);
  8003. *
  8004. * # Template member functions
  8005. *
  8006. * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
  8007. * more complex processing:
  8008. *
  8009. * var tpl = new Ext.XTemplate(
  8010. * '<p>Name: {name}</p>',
  8011. * '<p>Kids: ',
  8012. * '<tpl for="kids">',
  8013. * '<tpl if="this.isGirl(name)">',
  8014. * '<p>Girl: {name} - {age}</p>',
  8015. * '</tpl>',
  8016. * // use opposite if statement to simulate 'else' processing:
  8017. * '<tpl if="this.isGirl(name) == false">',
  8018. * '<p>Boy: {name} - {age}</p>',
  8019. * '</tpl>',
  8020. * '<tpl if="this.isBaby(age)">',
  8021. * '<p>{name} is a baby!</p>',
  8022. * '</tpl>',
  8023. * '</tpl></p>',
  8024. * {
  8025. * // XTemplate configuration:
  8026. * disableFormats: true,
  8027. * // member functions:
  8028. * isGirl: function(name){
  8029. * return name == 'Sara Grace';
  8030. * },
  8031. * isBaby: function(age){
  8032. * return age < 1;
  8033. * }
  8034. * }
  8035. * );
  8036. * tpl.overwrite(panel.body, data);
  8037. */
  8038. Ext.define('Ext.XTemplate', {
  8039. /* Begin Definitions */
  8040. extend: 'Ext.Template',
  8041. /* End Definitions */
  8042. argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
  8043. nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
  8044. ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
  8045. execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
  8046. constructor: function() {
  8047. this.callParent(arguments);
  8048. var me = this,
  8049. html = me.html,
  8050. argsRe = me.argsRe,
  8051. nameRe = me.nameRe,
  8052. ifRe = me.ifRe,
  8053. execRe = me.execRe,
  8054. id = 0,
  8055. tpls = [],
  8056. VALUES = 'values',
  8057. PARENT = 'parent',
  8058. XINDEX = 'xindex',
  8059. XCOUNT = 'xcount',
  8060. RETURN = 'return ',
  8061. WITHVALUES = 'with(values){ ',
  8062. m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
  8063. html = ['<tpl>', html, '</tpl>'].join('');
  8064. while ((m = html.match(argsRe))) {
  8065. exp = null;
  8066. fn = null;
  8067. exec = null;
  8068. matchName = m[0].match(nameRe);
  8069. matchIf = m[0].match(ifRe);
  8070. matchExec = m[0].match(execRe);
  8071. exp = matchIf ? matchIf[1] : null;
  8072. if (exp) {
  8073. fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
  8074. }
  8075. exp = matchExec ? matchExec[1] : null;
  8076. if (exp) {
  8077. exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
  8078. }
  8079. name = matchName ? matchName[1] : null;
  8080. if (name) {
  8081. if (name === '.') {
  8082. name = VALUES;
  8083. } else if (name === '..') {
  8084. name = PARENT;
  8085. }
  8086. name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
  8087. }
  8088. tpls.push({
  8089. id: id,
  8090. target: name,
  8091. exec: exec,
  8092. test: fn,
  8093. body: m[1] || ''
  8094. });
  8095. html = html.replace(m[0], '{xtpl' + id + '}');
  8096. id = id + 1;
  8097. }
  8098. for (i = tpls.length - 1; i >= 0; --i) {
  8099. me.compileTpl(tpls[i]);
  8100. }
  8101. me.master = tpls[tpls.length - 1];
  8102. me.tpls = tpls;
  8103. },
  8104. // @private
  8105. applySubTemplate: function(id, values, parent, xindex, xcount) {
  8106. var me = this, t = me.tpls[id];
  8107. return t.compiled.call(me, values, parent, xindex, xcount);
  8108. },
  8109. /**
  8110. * @cfg {RegExp} codeRe
  8111. * The regular expression used to match code variables. Default: matches {[expression]}.
  8112. */
  8113. codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
  8114. /**
  8115. * @cfg {Boolean} compiled
  8116. * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
  8117. */
  8118. re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
  8119. // @private
  8120. compileTpl: function(tpl) {
  8121. var fm = Ext.util.Format,
  8122. me = this,
  8123. useFormat = me.disableFormats !== true,
  8124. body, bodyReturn, evaluatedFn;
  8125. function fn(m, name, format, args, math) {
  8126. var v;
  8127. // name is what is inside the {}
  8128. // Name begins with xtpl, use a Sub Template
  8129. if (name.substr(0, 4) == 'xtpl') {
  8130. return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
  8131. }
  8132. // name = "." - Just use the values object.
  8133. if (name == '.') {
  8134. // filter to not include arrays/objects/nulls
  8135. v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
  8136. }
  8137. // name = "#" - Use the xindex
  8138. else if (name == '#') {
  8139. v = 'xindex';
  8140. }
  8141. else if (name.substr(0, 7) == "parent.") {
  8142. v = name;
  8143. }
  8144. // name has a . in it - Use object literal notation, starting from values
  8145. else if (name.indexOf('.') != -1) {
  8146. v = "values." + name;
  8147. }
  8148. // name is a property of values
  8149. else {
  8150. v = "values['" + name + "']";
  8151. }
  8152. if (math) {
  8153. v = '(' + v + math + ')';
  8154. }
  8155. if (format && useFormat) {
  8156. args = args ? ',' + args : "";
  8157. if (format.substr(0, 5) != "this.") {
  8158. format = "fm." + format + '(';
  8159. }
  8160. else {
  8161. format = 'this.' + format.substr(5) + '(';
  8162. }
  8163. }
  8164. else {
  8165. args = '';
  8166. format = "(" + v + " === undefined ? '' : ";
  8167. }
  8168. return "'," + format + v + args + "),'";
  8169. }
  8170. function codeFn(m, code) {
  8171. // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
  8172. return "',(" + code.replace(me.compileARe, "'") + "),'";
  8173. }
  8174. bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
  8175. body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
  8176. eval(body);
  8177. tpl.compiled = function(values, parent, xindex, xcount) {
  8178. var vs,
  8179. length,
  8180. buffer,
  8181. i;
  8182. if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
  8183. return '';
  8184. }
  8185. vs = tpl.target ? tpl.target.call(me, values, parent) : values;
  8186. if (!vs) {
  8187. return '';
  8188. }
  8189. parent = tpl.target ? values : parent;
  8190. if (tpl.target && Ext.isArray(vs)) {
  8191. buffer = [];
  8192. length = vs.length;
  8193. if (tpl.exec) {
  8194. for (i = 0; i < length; i++) {
  8195. buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
  8196. tpl.exec.call(me, vs[i], parent, i + 1, length);
  8197. }
  8198. } else {
  8199. for (i = 0; i < length; i++) {
  8200. buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
  8201. }
  8202. }
  8203. return buffer.join('');
  8204. }
  8205. if (tpl.exec) {
  8206. tpl.exec.call(me, vs, parent, xindex, xcount);
  8207. }
  8208. return evaluatedFn.call(me, vs, parent, xindex, xcount);
  8209. };
  8210. return this;
  8211. },
  8212. // inherit docs from Ext.Template
  8213. applyTemplate: function(values) {
  8214. return this.master.compiled.call(this, values, {}, 1, 1);
  8215. },
  8216. /**
  8217. * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
  8218. * @return {Ext.XTemplate} this
  8219. */
  8220. compile: function() {
  8221. return this;
  8222. }
  8223. }, function() {
  8224. // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
  8225. this.createAlias('apply', 'applyTemplate');
  8226. });
  8227. /**
  8228. * @class Ext.app.Controller
  8229. *
  8230. * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
  8231. * views) and take some action. Here's how we might create a Controller to manage Users:
  8232. *
  8233. * Ext.define('MyApp.controller.Users', {
  8234. * extend: 'Ext.app.Controller',
  8235. *
  8236. * init: function() {
  8237. * console.log('Initialized Users! This happens before the Application launch function is called');
  8238. * }
  8239. * });
  8240. *
  8241. * The init function is a special method that is called when your application boots. It is called before the
  8242. * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
  8243. * your Viewport is created.
  8244. *
  8245. * The init function is a great place to set up how your controller interacts with the view, and is usually used in
  8246. * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
  8247. * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
  8248. * our Users controller to tell us when the panel is rendered:
  8249. *
  8250. * Ext.define('MyApp.controller.Users', {
  8251. * extend: 'Ext.app.Controller',
  8252. *
  8253. * init: function() {
  8254. * this.control({
  8255. * 'viewport > panel': {
  8256. * render: this.onPanelRendered
  8257. * }
  8258. * });
  8259. * },
  8260. *
  8261. * onPanelRendered: function() {
  8262. * console.log('The panel was rendered');
  8263. * }
  8264. * });
  8265. *
  8266. * We've updated the init function to use this.control to set up listeners on views in our application. The control
  8267. * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
  8268. * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
  8269. * it allows us to pass a CSS-like selector that will find every matching component on the page.
  8270. *
  8271. * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
  8272. * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
  8273. * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
  8274. * onPanelRendered function is called.
  8275. *
  8276. * <u>Using refs</u>
  8277. *
  8278. * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
  8279. * make it really easy to get references to Views on your page. Let's look at an example of this now:
  8280. *
  8281. * Ext.define('MyApp.controller.Users', {
  8282. * extend: 'Ext.app.Controller',
  8283. *
  8284. * refs: [
  8285. * {
  8286. * ref: 'list',
  8287. * selector: 'grid'
  8288. * }
  8289. * ],
  8290. *
  8291. * init: function() {
  8292. * this.control({
  8293. * 'button': {
  8294. * click: this.refreshGrid
  8295. * }
  8296. * });
  8297. * },
  8298. *
  8299. * refreshGrid: function() {
  8300. * this.getList().store.load();
  8301. * }
  8302. * });
  8303. *
  8304. * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
  8305. * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
  8306. * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
  8307. * assigns it to the reference 'list'.
  8308. *
  8309. * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
  8310. * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
  8311. * was capitalized and prepended with get to go from 'list' to 'getList'.
  8312. *
  8313. * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
  8314. * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
  8315. * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
  8316. * match a single View in your application (in the case above our selector will match any grid on the page).
  8317. *
  8318. * Bringing it all together, our init function is called when the application boots, at which time we call this.control
  8319. * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
  8320. * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
  8321. * simplicity). When the button is clicked we use out getList function to refresh the grid.
  8322. *
  8323. * You can create any number of refs and control any number of components this way, simply adding more functions to
  8324. * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
  8325. * examples/app/feed-viewer folder in the SDK download.
  8326. *
  8327. * <u>Generated getter methods</u>
  8328. *
  8329. * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
  8330. * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
  8331. *
  8332. * Ext.define('MyApp.controller.Users', {
  8333. * extend: 'Ext.app.Controller',
  8334. *
  8335. * models: ['User'],
  8336. * stores: ['AllUsers', 'AdminUsers'],
  8337. *
  8338. * init: function() {
  8339. * var User = this.getUserModel(),
  8340. * allUsers = this.getAllUsersStore();
  8341. *
  8342. * var ed = new User({name: 'Ed'});
  8343. * allUsers.add(ed);
  8344. * }
  8345. * });
  8346. *
  8347. * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
  8348. * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
  8349. * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
  8350. * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
  8351. * functionality.
  8352. *
  8353. * <u>Further Reading</u>
  8354. *
  8355. * For more information about writing Ext JS 4 applications, please see the
  8356. * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
  8357. *
  8358. * @docauthor Ed Spencer
  8359. */
  8360. Ext.define('Ext.app.Controller', {
  8361. mixins: {
  8362. observable: 'Ext.util.Observable'
  8363. },
  8364. /**
  8365. * @cfg {String} id The id of this controller. You can use this id when dispatching.
  8366. */
  8367. /**
  8368. * @cfg {String[]} models
  8369. * Array of models to require from AppName.model namespace. For example:
  8370. *
  8371. * Ext.define("MyApp.controller.Foo", {
  8372. * extend: "Ext.app.Controller",
  8373. * models: ['User', 'Vehicle']
  8374. * });
  8375. *
  8376. * This is equivalent of:
  8377. *
  8378. * Ext.define("MyApp.controller.Foo", {
  8379. * extend: "Ext.app.Controller",
  8380. * requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
  8381. * });
  8382. *
  8383. */
  8384. /**
  8385. * @cfg {String[]} views
  8386. * Array of views to require from AppName.view namespace. For example:
  8387. *
  8388. * Ext.define("MyApp.controller.Foo", {
  8389. * extend: "Ext.app.Controller",
  8390. * views: ['List', 'Detail']
  8391. * });
  8392. *
  8393. * This is equivalent of:
  8394. *
  8395. * Ext.define("MyApp.controller.Foo", {
  8396. * extend: "Ext.app.Controller",
  8397. * requires: ['MyApp.view.List', 'MyApp.view.Detail']
  8398. * });
  8399. *
  8400. */
  8401. /**
  8402. * @cfg {String[]} stores
  8403. * Array of stores to require from AppName.store namespace. For example:
  8404. *
  8405. * Ext.define("MyApp.controller.Foo", {
  8406. * extend: "Ext.app.Controller",
  8407. * stores: ['Users', 'Vehicles']
  8408. * });
  8409. *
  8410. * This is equivalent of:
  8411. *
  8412. * Ext.define("MyApp.controller.Foo", {
  8413. * extend: "Ext.app.Controller",
  8414. * requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
  8415. * });
  8416. *
  8417. */
  8418. onClassExtended: function(cls, data) {
  8419. var className = Ext.getClassName(cls),
  8420. match = className.match(/^(.*)\.controller\./);
  8421. if (match !== null) {
  8422. var namespace = Ext.Loader.getPrefix(className) || match[1],
  8423. onBeforeClassCreated = data.onBeforeClassCreated,
  8424. requires = [],
  8425. modules = ['model', 'view', 'store'],
  8426. prefix;
  8427. data.onBeforeClassCreated = function(cls, data) {
  8428. var i, ln, module,
  8429. items, j, subLn, item;
  8430. for (i = 0,ln = modules.length; i < ln; i++) {
  8431. module = modules[i];
  8432. items = Ext.Array.from(data[module + 's']);
  8433. for (j = 0,subLn = items.length; j < subLn; j++) {
  8434. item = items[j];
  8435. prefix = Ext.Loader.getPrefix(item);
  8436. if (prefix === '' || prefix === item) {
  8437. requires.push(namespace + '.' + module + '.' + item);
  8438. }
  8439. else {
  8440. requires.push(item);
  8441. }
  8442. }
  8443. }
  8444. Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
  8445. };
  8446. }
  8447. },
  8448. /**
  8449. * Creates new Controller.
  8450. * @param {Object} config (optional) Config object.
  8451. */
  8452. constructor: function(config) {
  8453. this.mixins.observable.constructor.call(this, config);
  8454. Ext.apply(this, config || {});
  8455. this.createGetters('model', this.models);
  8456. this.createGetters('store', this.stores);
  8457. this.createGetters('view', this.views);
  8458. if (this.refs) {
  8459. this.ref(this.refs);
  8460. }
  8461. },
  8462. /**
  8463. * A template method that is called when your application boots. It is called before the
  8464. * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
  8465. * your Viewport is created.
  8466. *
  8467. * @param {Ext.app.Application} application
  8468. * @template
  8469. */
  8470. init: function(application) {},
  8471. /**
  8472. * A template method like {@link #init}, but called after the viewport is created.
  8473. * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
  8474. *
  8475. * @param {Ext.app.Application} application
  8476. * @template
  8477. */
  8478. onLaunch: function(application) {},
  8479. createGetters: function(type, refs) {
  8480. type = Ext.String.capitalize(type);
  8481. Ext.Array.each(refs, function(ref) {
  8482. var fn = 'get',
  8483. parts = ref.split('.');
  8484. // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
  8485. Ext.Array.each(parts, function(part) {
  8486. fn += Ext.String.capitalize(part);
  8487. });
  8488. fn += type;
  8489. if (!this[fn]) {
  8490. this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
  8491. }
  8492. // Execute it right away
  8493. this[fn](ref);
  8494. },
  8495. this);
  8496. },
  8497. ref: function(refs) {
  8498. var me = this;
  8499. refs = Ext.Array.from(refs);
  8500. Ext.Array.each(refs, function(info) {
  8501. var ref = info.ref,
  8502. fn = 'get' + Ext.String.capitalize(ref);
  8503. if (!me[fn]) {
  8504. me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
  8505. }
  8506. });
  8507. },
  8508. getRef: function(ref, info, config) {
  8509. this.refCache = this.refCache || {};
  8510. info = info || {};
  8511. config = config || {};
  8512. Ext.apply(info, config);
  8513. if (info.forceCreate) {
  8514. return Ext.ComponentManager.create(info, 'component');
  8515. }
  8516. var me = this,
  8517. selector = info.selector,
  8518. cached = me.refCache[ref];
  8519. if (!cached) {
  8520. me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
  8521. if (!cached && info.autoCreate) {
  8522. me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
  8523. }
  8524. if (cached) {
  8525. cached.on('beforedestroy', function() {
  8526. me.refCache[ref] = null;
  8527. });
  8528. }
  8529. }
  8530. return cached;
  8531. },
  8532. /**
  8533. * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
  8534. * object containing component paths mapped to a hash of listener functions.
  8535. *
  8536. * In the following example the `updateUser` function is mapped to to the `click`
  8537. * event on a button component, which is a child of the `useredit` component.
  8538. *
  8539. * Ext.define('AM.controller.Users', {
  8540. * init: function() {
  8541. * this.control({
  8542. * 'useredit button[action=save]': {
  8543. * click: this.updateUser
  8544. * }
  8545. * });
  8546. * },
  8547. *
  8548. * updateUser: function(button) {
  8549. * console.log('clicked the Save button');
  8550. * }
  8551. * });
  8552. *
  8553. * See {@link Ext.ComponentQuery} for more information on component selectors.
  8554. *
  8555. * @param {String/Object} selectors If a String, the second argument is used as the
  8556. * listeners, otherwise an object of selectors -> listeners is assumed
  8557. * @param {Object} listeners
  8558. */
  8559. control: function(selectors, listeners) {
  8560. this.application.control(selectors, listeners, this);
  8561. },
  8562. /**
  8563. * Returns instance of a {@link Ext.app.Controller controller} with the given name.
  8564. * When controller doesn't exist yet, it's created.
  8565. * @param {String} name
  8566. * @return {Ext.app.Controller} a controller instance.
  8567. */
  8568. getController: function(name) {
  8569. return this.application.getController(name);
  8570. },
  8571. /**
  8572. * Returns instance of a {@link Ext.data.Store Store} with the given name.
  8573. * When store doesn't exist yet, it's created.
  8574. * @param {String} name
  8575. * @return {Ext.data.Store} a store instance.
  8576. */
  8577. getStore: function(name) {
  8578. return this.application.getStore(name);
  8579. },
  8580. /**
  8581. * Returns a {@link Ext.data.Model Model} class with the given name.
  8582. * A shorthand for using {@link Ext.ModelManager#getModel}.
  8583. * @param {String} name
  8584. * @return {Ext.data.Model} a model class.
  8585. */
  8586. getModel: function(model) {
  8587. return this.application.getModel(model);
  8588. },
  8589. /**
  8590. * Returns a View class with the given name. To create an instance of the view,
  8591. * you can use it like it's used by Application to create the Viewport:
  8592. *
  8593. * this.getView('Viewport').create();
  8594. *
  8595. * @param {String} name
  8596. * @return {Ext.Base} a view class.
  8597. */
  8598. getView: function(view) {
  8599. return this.application.getView(view);
  8600. }
  8601. });
  8602. /**
  8603. * @author Don Griffin
  8604. *
  8605. * This class is a base for all id generators. It also provides lookup of id generators by
  8606. * their id.
  8607. *
  8608. * Generally, id generators are used to generate a primary key for new model instances. There
  8609. * are different approaches to solving this problem, so this mechanism has both simple use
  8610. * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
  8611. * using the {@link Ext.data.Model#idgen} property.
  8612. *
  8613. * # Identity, Type and Shared IdGenerators
  8614. *
  8615. * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
  8616. * This is done by giving IdGenerator instances an id property by which they can be looked
  8617. * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
  8618. * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
  8619. * assign them the same id:
  8620. *
  8621. * Ext.define('MyApp.data.MyModelA', {
  8622. * extend: 'Ext.data.Model',
  8623. * idgen: {
  8624. * type: 'sequential',
  8625. * id: 'foo'
  8626. * }
  8627. * });
  8628. *
  8629. * Ext.define('MyApp.data.MyModelB', {
  8630. * extend: 'Ext.data.Model',
  8631. * idgen: {
  8632. * type: 'sequential',
  8633. * id: 'foo'
  8634. * }
  8635. * });
  8636. *
  8637. * To make this as simple as possible for generator types that are shared by many (or all)
  8638. * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
  8639. * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
  8640. * to its type ('uuid'). In other words, the following Models share the same generator:
  8641. *
  8642. * Ext.define('MyApp.data.MyModelX', {
  8643. * extend: 'Ext.data.Model',
  8644. * idgen: 'uuid'
  8645. * });
  8646. *
  8647. * Ext.define('MyApp.data.MyModelY', {
  8648. * extend: 'Ext.data.Model',
  8649. * idgen: 'uuid'
  8650. * });
  8651. *
  8652. * This can be overridden (by specifying the id explicitly), but there is no particularly
  8653. * good reason to do so for this generator type.
  8654. *
  8655. * # Creating Custom Generators
  8656. *
  8657. * An id generator should derive from this class and implement the {@link #generate} method.
  8658. * The constructor will apply config properties on new instances, so a constructor is often
  8659. * not necessary.
  8660. *
  8661. * To register an id generator type, a derived class should provide an `alias` like so:
  8662. *
  8663. * Ext.define('MyApp.data.CustomIdGenerator', {
  8664. * extend: 'Ext.data.IdGenerator',
  8665. * alias: 'idgen.custom',
  8666. *
  8667. * configProp: 42, // some config property w/default value
  8668. *
  8669. * generate: function () {
  8670. * return ... // a new id
  8671. * }
  8672. * });
  8673. *
  8674. * Using the custom id generator is then straightforward:
  8675. *
  8676. * Ext.define('MyApp.data.MyModel', {
  8677. * extend: 'Ext.data.Model',
  8678. * idgen: 'custom'
  8679. * });
  8680. * // or...
  8681. *
  8682. * Ext.define('MyApp.data.MyModel', {
  8683. * extend: 'Ext.data.Model',
  8684. * idgen: {
  8685. * type: 'custom',
  8686. * configProp: value
  8687. * }
  8688. * });
  8689. *
  8690. * It is not recommended to mix shared generators with generator configuration. This leads
  8691. * to unpredictable results unless all configurations match (which is also redundant). In
  8692. * such cases, a custom generator with a default id is the best approach.
  8693. *
  8694. * Ext.define('MyApp.data.CustomIdGenerator', {
  8695. * extend: 'Ext.data.SequentialIdGenerator',
  8696. * alias: 'idgen.custom',
  8697. *
  8698. * id: 'custom', // shared by default
  8699. *
  8700. * prefix: 'ID_',
  8701. * seed: 1000
  8702. * });
  8703. *
  8704. * Ext.define('MyApp.data.MyModelX', {
  8705. * extend: 'Ext.data.Model',
  8706. * idgen: 'custom'
  8707. * });
  8708. *
  8709. * Ext.define('MyApp.data.MyModelY', {
  8710. * extend: 'Ext.data.Model',
  8711. * idgen: 'custom'
  8712. * });
  8713. *
  8714. * // the above models share a generator that produces ID_1000, ID_1001, etc..
  8715. *
  8716. */
  8717. Ext.define('Ext.data.IdGenerator', {
  8718. isGenerator: true,
  8719. /**
  8720. * Initializes a new instance.
  8721. * @param {Object} config (optional) Configuration object to be applied to the new instance.
  8722. */
  8723. constructor: function(config) {
  8724. var me = this;
  8725. Ext.apply(me, config);
  8726. if (me.id) {
  8727. Ext.data.IdGenerator.all[me.id] = me;
  8728. }
  8729. },
  8730. /**
  8731. * @cfg {String} id
  8732. * The id by which to register a new instance. This instance can be found using the
  8733. * {@link Ext.data.IdGenerator#get} static method.
  8734. */
  8735. getRecId: function (rec) {
  8736. return rec.modelName + '-' + rec.internalId;
  8737. },
  8738. /**
  8739. * Generates and returns the next id. This method must be implemented by the derived
  8740. * class.
  8741. *
  8742. * @return {String} The next id.
  8743. * @method generate
  8744. * @abstract
  8745. */
  8746. statics: {
  8747. /**
  8748. * @property {Object} all
  8749. * This object is keyed by id to lookup instances.
  8750. * @private
  8751. * @static
  8752. */
  8753. all: {},
  8754. /**
  8755. * Returns the IdGenerator given its config description.
  8756. * @param {String/Object} config If this parameter is an IdGenerator instance, it is
  8757. * simply returned. If this is a string, it is first used as an id for lookup and
  8758. * then, if there is no match, as a type to create a new instance. This parameter
  8759. * can also be a config object that contains a `type` property (among others) that
  8760. * are used to create and configure the instance.
  8761. * @static
  8762. */
  8763. get: function (config) {
  8764. var generator,
  8765. id,
  8766. type;
  8767. if (typeof config == 'string') {
  8768. id = type = config;
  8769. config = null;
  8770. } else if (config.isGenerator) {
  8771. return config;
  8772. } else {
  8773. id = config.id || config.type;
  8774. type = config.type;
  8775. }
  8776. generator = this.all[id];
  8777. if (!generator) {
  8778. generator = Ext.create('idgen.' + type, config);
  8779. }
  8780. return generator;
  8781. }
  8782. }
  8783. });
  8784. /**
  8785. * @class Ext.data.SortTypes
  8786. * This class defines a series of static methods that are used on a
  8787. * {@link Ext.data.Field} for performing sorting. The methods cast the
  8788. * underlying values into a data type that is appropriate for sorting on
  8789. * that particular field. If a {@link Ext.data.Field#type} is specified,
  8790. * the sortType will be set to a sane default if the sortType is not
  8791. * explicitly defined on the field. The sortType will make any necessary
  8792. * modifications to the value and return it.
  8793. * <ul>
  8794. * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
  8795. * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
  8796. * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
  8797. * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
  8798. * <li><b>asFloat</b> - Converts the value to a floating point number</li>
  8799. * <li><b>asInt</b> - Converts the value to an integer number</li>
  8800. * </ul>
  8801. * <p>
  8802. * It is also possible to create a custom sortType that can be used throughout
  8803. * an application.
  8804. * <pre><code>
  8805. Ext.apply(Ext.data.SortTypes, {
  8806. asPerson: function(person){
  8807. // expects an object with a first and last name property
  8808. return person.lastName.toUpperCase() + person.firstName.toLowerCase();
  8809. }
  8810. });
  8811. Ext.define('Employee', {
  8812. extend: 'Ext.data.Model',
  8813. fields: [{
  8814. name: 'person',
  8815. sortType: 'asPerson'
  8816. }, {
  8817. name: 'salary',
  8818. type: 'float' // sortType set to asFloat
  8819. }]
  8820. });
  8821. * </code></pre>
  8822. * </p>
  8823. * @singleton
  8824. * @docauthor Evan Trimboli <evan@sencha.com>
  8825. */
  8826. Ext.define('Ext.data.SortTypes', {
  8827. singleton: true,
  8828. /**
  8829. * Default sort that does nothing
  8830. * @param {Object} s The value being converted
  8831. * @return {Object} The comparison value
  8832. */
  8833. none : function(s) {
  8834. return s;
  8835. },
  8836. /**
  8837. * The regular expression used to strip tags
  8838. * @type {RegExp}
  8839. * @property
  8840. */
  8841. stripTagsRE : /<\/?[^>]+>/gi,
  8842. /**
  8843. * Strips all HTML tags to sort on text only
  8844. * @param {Object} s The value being converted
  8845. * @return {String} The comparison value
  8846. */
  8847. asText : function(s) {
  8848. return String(s).replace(this.stripTagsRE, "");
  8849. },
  8850. /**
  8851. * Strips all HTML tags to sort on text only - Case insensitive
  8852. * @param {Object} s The value being converted
  8853. * @return {String} The comparison value
  8854. */
  8855. asUCText : function(s) {
  8856. return String(s).toUpperCase().replace(this.stripTagsRE, "");
  8857. },
  8858. /**
  8859. * Case insensitive string
  8860. * @param {Object} s The value being converted
  8861. * @return {String} The comparison value
  8862. */
  8863. asUCString : function(s) {
  8864. return String(s).toUpperCase();
  8865. },
  8866. /**
  8867. * Date sorting
  8868. * @param {Object} s The value being converted
  8869. * @return {Number} The comparison value
  8870. */
  8871. asDate : function(s) {
  8872. if(!s){
  8873. return 0;
  8874. }
  8875. if(Ext.isDate(s)){
  8876. return s.getTime();
  8877. }
  8878. return Date.parse(String(s));
  8879. },
  8880. /**
  8881. * Float sorting
  8882. * @param {Object} s The value being converted
  8883. * @return {Number} The comparison value
  8884. */
  8885. asFloat : function(s) {
  8886. var val = parseFloat(String(s).replace(/,/g, ""));
  8887. return isNaN(val) ? 0 : val;
  8888. },
  8889. /**
  8890. * Integer sorting
  8891. * @param {Object} s The value being converted
  8892. * @return {Number} The comparison value
  8893. */
  8894. asInt : function(s) {
  8895. var val = parseInt(String(s).replace(/,/g, ""), 10);
  8896. return isNaN(val) ? 0 : val;
  8897. }
  8898. });
  8899. /**
  8900. * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
  8901. * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
  8902. * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
  8903. * on their records. Example usage:
  8904. *
  8905. * //set up a fictional MixedCollection containing a few people to filter on
  8906. * var allNames = new Ext.util.MixedCollection();
  8907. * allNames.addAll([
  8908. * {id: 1, name: 'Ed', age: 25},
  8909. * {id: 2, name: 'Jamie', age: 37},
  8910. * {id: 3, name: 'Abe', age: 32},
  8911. * {id: 4, name: 'Aaron', age: 26},
  8912. * {id: 5, name: 'David', age: 32}
  8913. * ]);
  8914. *
  8915. * var ageFilter = new Ext.util.Filter({
  8916. * property: 'age',
  8917. * value : 32
  8918. * });
  8919. *
  8920. * var longNameFilter = new Ext.util.Filter({
  8921. * filterFn: function(item) {
  8922. * return item.name.length > 4;
  8923. * }
  8924. * });
  8925. *
  8926. * //a new MixedCollection with the 3 names longer than 4 characters
  8927. * var longNames = allNames.filter(longNameFilter);
  8928. *
  8929. * //a new MixedCollection with the 2 people of age 24:
  8930. * var youngFolk = allNames.filter(ageFilter);
  8931. *
  8932. */
  8933. Ext.define('Ext.util.Filter', {
  8934. /* Begin Definitions */
  8935. /* End Definitions */
  8936. /**
  8937. * @cfg {String} property
  8938. * The property to filter on. Required unless a {@link #filterFn} is passed
  8939. */
  8940. /**
  8941. * @cfg {Function} filterFn
  8942. * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
  8943. * true to accept each item or false to reject it
  8944. */
  8945. /**
  8946. * @cfg {Boolean} anyMatch
  8947. * True to allow any match - no regex start/end line anchors will be added.
  8948. */
  8949. anyMatch: false,
  8950. /**
  8951. * @cfg {Boolean} exactMatch
  8952. * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
  8953. */
  8954. exactMatch: false,
  8955. /**
  8956. * @cfg {Boolean} caseSensitive
  8957. * True to make the regex case sensitive (adds 'i' switch to regex).
  8958. */
  8959. caseSensitive: false,
  8960. /**
  8961. * @cfg {String} root
  8962. * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
  8963. * make the filter pull the {@link #property} out of the data object of each item
  8964. */
  8965. /**
  8966. * Creates new Filter.
  8967. * @param {Object} [config] Config object
  8968. */
  8969. constructor: function(config) {
  8970. var me = this;
  8971. Ext.apply(me, config);
  8972. //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
  8973. //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
  8974. me.filter = me.filter || me.filterFn;
  8975. if (me.filter === undefined) {
  8976. if (me.property === undefined || me.value === undefined) {
  8977. // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
  8978. // Model has been updated to allow string ids
  8979. // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
  8980. } else {
  8981. me.filter = me.createFilterFn();
  8982. }
  8983. me.filterFn = me.filter;
  8984. }
  8985. },
  8986. /**
  8987. * @private
  8988. * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
  8989. */
  8990. createFilterFn: function() {
  8991. var me = this,
  8992. matcher = me.createValueMatcher(),
  8993. property = me.property;
  8994. return function(item) {
  8995. var value = me.getRoot.call(me, item)[property];
  8996. return matcher === null ? value === null : matcher.test(value);
  8997. };
  8998. },
  8999. /**
  9000. * @private
  9001. * Returns the root property of the given item, based on the configured {@link #root} property
  9002. * @param {Object} item The item
  9003. * @return {Object} The root property of the object
  9004. */
  9005. getRoot: function(item) {
  9006. var root = this.root;
  9007. return root === undefined ? item : item[root];
  9008. },
  9009. /**
  9010. * @private
  9011. * Returns a regular expression based on the given value and matching options
  9012. */
  9013. createValueMatcher : function() {
  9014. var me = this,
  9015. value = me.value,
  9016. anyMatch = me.anyMatch,
  9017. exactMatch = me.exactMatch,
  9018. caseSensitive = me.caseSensitive,
  9019. escapeRe = Ext.String.escapeRegex;
  9020. if (value === null) {
  9021. return value;
  9022. }
  9023. if (!value.exec) { // not a regex
  9024. value = String(value);
  9025. if (anyMatch === true) {
  9026. value = escapeRe(value);
  9027. } else {
  9028. value = '^' + escapeRe(value);
  9029. if (exactMatch === true) {
  9030. value += '$';
  9031. }
  9032. }
  9033. value = new RegExp(value, caseSensitive ? '' : 'i');
  9034. }
  9035. return value;
  9036. }
  9037. });
  9038. /**
  9039. * Represents a single sorter that can be applied to a Store. The sorter is used
  9040. * to compare two values against each other for the purpose of ordering them. Ordering
  9041. * is achieved by specifying either:
  9042. *
  9043. * - {@link #property A sorting property}
  9044. * - {@link #sorterFn A sorting function}
  9045. *
  9046. * As a contrived example, we can specify a custom sorter that sorts by rank:
  9047. *
  9048. * Ext.define('Person', {
  9049. * extend: 'Ext.data.Model',
  9050. * fields: ['name', 'rank']
  9051. * });
  9052. *
  9053. * Ext.create('Ext.data.Store', {
  9054. * model: 'Person',
  9055. * proxy: 'memory',
  9056. * sorters: [{
  9057. * sorterFn: function(o1, o2){
  9058. * var getRank = function(o){
  9059. * var name = o.get('rank');
  9060. * if (name === 'first') {
  9061. * return 1;
  9062. * } else if (name === 'second') {
  9063. * return 2;
  9064. * } else {
  9065. * return 3;
  9066. * }
  9067. * },
  9068. * rank1 = getRank(o1),
  9069. * rank2 = getRank(o2);
  9070. *
  9071. * if (rank1 === rank2) {
  9072. * return 0;
  9073. * }
  9074. *
  9075. * return rank1 < rank2 ? -1 : 1;
  9076. * }
  9077. * }],
  9078. * data: [{
  9079. * name: 'Person1',
  9080. * rank: 'second'
  9081. * }, {
  9082. * name: 'Person2',
  9083. * rank: 'third'
  9084. * }, {
  9085. * name: 'Person3',
  9086. * rank: 'first'
  9087. * }]
  9088. * });
  9089. */
  9090. Ext.define('Ext.util.Sorter', {
  9091. /**
  9092. * @cfg {String} property
  9093. * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
  9094. * directly and compared for sorting using the built in comparison operators.
  9095. */
  9096. /**
  9097. * @cfg {Function} sorterFn
  9098. * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
  9099. * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
  9100. * function should return:
  9101. *
  9102. * - -1 if o1 is "less than" o2
  9103. * - 0 if o1 is "equal" to o2
  9104. * - 1 if o1 is "greater than" o2
  9105. */
  9106. /**
  9107. * @cfg {String} root
  9108. * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
  9109. * make the filter pull the {@link #property} out of the data object of each item
  9110. */
  9111. /**
  9112. * @cfg {Function} transform
  9113. * A function that will be run on each value before it is compared in the sorter. The function will receive a single
  9114. * argument, the value.
  9115. */
  9116. /**
  9117. * @cfg {String} direction
  9118. * The direction to sort by.
  9119. */
  9120. direction: "ASC",
  9121. constructor: function(config) {
  9122. var me = this;
  9123. Ext.apply(me, config);
  9124. //<debug>
  9125. if (me.property === undefined && me.sorterFn === undefined) {
  9126. Ext.Error.raise("A Sorter requires either a property or a sorter function");
  9127. }
  9128. //</debug>
  9129. me.updateSortFunction();
  9130. },
  9131. /**
  9132. * @private
  9133. * Creates and returns a function which sorts an array by the given property and direction
  9134. * @return {Function} A function which sorts by the property/direction combination provided
  9135. */
  9136. createSortFunction: function(sorterFn) {
  9137. var me = this,
  9138. property = me.property,
  9139. direction = me.direction || "ASC",
  9140. modifier = direction.toUpperCase() == "DESC" ? -1 : 1;
  9141. //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
  9142. //-1 if object 2 is greater or 0 if they are equal
  9143. return function(o1, o2) {
  9144. return modifier * sorterFn.call(me, o1, o2);
  9145. };
  9146. },
  9147. /**
  9148. * @private
  9149. * Basic default sorter function that just compares the defined property of each object
  9150. */
  9151. defaultSorterFn: function(o1, o2) {
  9152. var me = this,
  9153. transform = me.transform,
  9154. v1 = me.getRoot(o1)[me.property],
  9155. v2 = me.getRoot(o2)[me.property];
  9156. if (transform) {
  9157. v1 = transform(v1);
  9158. v2 = transform(v2);
  9159. }
  9160. return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  9161. },
  9162. /**
  9163. * @private
  9164. * Returns the root property of the given item, based on the configured {@link #root} property
  9165. * @param {Object} item The item
  9166. * @return {Object} The root property of the object
  9167. */
  9168. getRoot: function(item) {
  9169. return this.root === undefined ? item : item[this.root];
  9170. },
  9171. /**
  9172. * Set the sorting direction for this sorter.
  9173. * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
  9174. */
  9175. setDirection: function(direction) {
  9176. var me = this;
  9177. me.direction = direction;
  9178. me.updateSortFunction();
  9179. },
  9180. /**
  9181. * Toggles the sorting direction for this sorter.
  9182. */
  9183. toggle: function() {
  9184. var me = this;
  9185. me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
  9186. me.updateSortFunction();
  9187. },
  9188. /**
  9189. * Update the sort function for this sorter.
  9190. * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
  9191. * sorting function.
  9192. */
  9193. updateSortFunction: function(fn) {
  9194. var me = this;
  9195. fn = fn || me.sorterFn || me.defaultSorterFn;
  9196. me.sort = me.createSortFunction(fn);
  9197. }
  9198. });
  9199. /**
  9200. * @author Ed Spencer
  9201. *
  9202. * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
  9203. * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
  9204. * Operation objects directly.
  9205. *
  9206. * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
  9207. */
  9208. Ext.define('Ext.data.Operation', {
  9209. /**
  9210. * @cfg {Boolean} synchronous
  9211. * True if this Operation is to be executed synchronously. This property is inspected by a
  9212. * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
  9213. */
  9214. synchronous: true,
  9215. /**
  9216. * @cfg {String} action
  9217. * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
  9218. */
  9219. action: undefined,
  9220. /**
  9221. * @cfg {Ext.util.Filter[]} filters
  9222. * Optional array of filter objects. Only applies to 'read' actions.
  9223. */
  9224. filters: undefined,
  9225. /**
  9226. * @cfg {Ext.util.Sorter[]} sorters
  9227. * Optional array of sorter objects. Only applies to 'read' actions.
  9228. */
  9229. sorters: undefined,
  9230. /**
  9231. * @cfg {Ext.util.Grouper} group
  9232. * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
  9233. */
  9234. group: undefined,
  9235. /**
  9236. * @cfg {Number} start
  9237. * The start index (offset), used in paging when running a 'read' action.
  9238. */
  9239. start: undefined,
  9240. /**
  9241. * @cfg {Number} limit
  9242. * The number of records to load. Used on 'read' actions when paging is being used.
  9243. */
  9244. limit: undefined,
  9245. /**
  9246. * @cfg {Ext.data.Batch} batch
  9247. * The batch that this Operation is a part of.
  9248. */
  9249. batch: undefined,
  9250. /**
  9251. * @cfg {Function} callback
  9252. * Function to execute when operation completed. Will be called with the following parameters:
  9253. *
  9254. * - records : Array of Ext.data.Model objects.
  9255. * - operation : The Ext.data.Operation itself.
  9256. * - success : True when operation completed successfully.
  9257. */
  9258. callback: undefined,
  9259. /**
  9260. * @cfg {Object} scope
  9261. * Scope for the {@link #callback} function.
  9262. */
  9263. scope: undefined,
  9264. /**
  9265. * @property {Boolean} started
  9266. * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
  9267. * @private
  9268. */
  9269. started: false,
  9270. /**
  9271. * @property {Boolean} running
  9272. * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
  9273. * @private
  9274. */
  9275. running: false,
  9276. /**
  9277. * @property {Boolean} complete
  9278. * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
  9279. * @private
  9280. */
  9281. complete: false,
  9282. /**
  9283. * @property {Boolean} success
  9284. * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
  9285. * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
  9286. * {@link #wasSuccessful} to query success status.
  9287. * @private
  9288. */
  9289. success: undefined,
  9290. /**
  9291. * @property {Boolean} exception
  9292. * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
  9293. * @private
  9294. */
  9295. exception: false,
  9296. /**
  9297. * @property {String/Object} error
  9298. * The error object passed when {@link #setException} was called. This could be any object or primitive.
  9299. * @private
  9300. */
  9301. error: undefined,
  9302. /**
  9303. * @property {RegExp} actionCommitRecordsRe
  9304. * The RegExp used to categorize actions that require record commits.
  9305. */
  9306. actionCommitRecordsRe: /^(?:create|update)$/i,
  9307. /**
  9308. * @property {RegExp} actionSkipSyncRe
  9309. * The RegExp used to categorize actions that skip local record synchronization. This defaults
  9310. * to match 'destroy'.
  9311. */
  9312. actionSkipSyncRe: /^destroy$/i,
  9313. /**
  9314. * Creates new Operation object.
  9315. * @param {Object} config (optional) Config object.
  9316. */
  9317. constructor: function(config) {
  9318. Ext.apply(this, config || {});
  9319. },
  9320. /**
  9321. * This method is called to commit data to this instance's records given the records in
  9322. * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
  9323. * those records (for 'create' and 'update' actions).
  9324. *
  9325. * If this {@link #action} is 'destroy', any server records are ignored and the
  9326. * {@link Ext.data.Model#commit} method is not called.
  9327. *
  9328. * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
  9329. * the server.
  9330. * @markdown
  9331. */
  9332. commitRecords: function (serverRecords) {
  9333. var me = this,
  9334. mc, index, clientRecords, serverRec, clientRec;
  9335. if (!me.actionSkipSyncRe.test(me.action)) {
  9336. clientRecords = me.records;
  9337. if (clientRecords && clientRecords.length) {
  9338. mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
  9339. mc.addAll(clientRecords);
  9340. for (index = serverRecords ? serverRecords.length : 0; index--; ) {
  9341. serverRec = serverRecords[index];
  9342. clientRec = mc.get(serverRec.getId());
  9343. if (clientRec) {
  9344. clientRec.beginEdit();
  9345. clientRec.set(serverRec.data);
  9346. clientRec.endEdit(true);
  9347. }
  9348. }
  9349. if (me.actionCommitRecordsRe.test(me.action)) {
  9350. for (index = clientRecords.length; index--; ) {
  9351. clientRecords[index].commit();
  9352. }
  9353. }
  9354. }
  9355. }
  9356. },
  9357. /**
  9358. * Marks the Operation as started.
  9359. */
  9360. setStarted: function() {
  9361. this.started = true;
  9362. this.running = true;
  9363. },
  9364. /**
  9365. * Marks the Operation as completed.
  9366. */
  9367. setCompleted: function() {
  9368. this.complete = true;
  9369. this.running = false;
  9370. },
  9371. /**
  9372. * Marks the Operation as successful.
  9373. */
  9374. setSuccessful: function() {
  9375. this.success = true;
  9376. },
  9377. /**
  9378. * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
  9379. * @param {String/Object} error (optional) error string/object
  9380. */
  9381. setException: function(error) {
  9382. this.exception = true;
  9383. this.success = false;
  9384. this.running = false;
  9385. this.error = error;
  9386. },
  9387. /**
  9388. * Returns true if this Operation encountered an exception (see also {@link #getError})
  9389. * @return {Boolean} True if there was an exception
  9390. */
  9391. hasException: function() {
  9392. return this.exception === true;
  9393. },
  9394. /**
  9395. * Returns the error string or object that was set using {@link #setException}
  9396. * @return {String/Object} The error object
  9397. */
  9398. getError: function() {
  9399. return this.error;
  9400. },
  9401. /**
  9402. * Returns an array of Ext.data.Model instances as set by the Proxy.
  9403. * @return {Ext.data.Model[]} Any loaded Records
  9404. */
  9405. getRecords: function() {
  9406. var resultSet = this.getResultSet();
  9407. return (resultSet === undefined ? this.records : resultSet.records);
  9408. },
  9409. /**
  9410. * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
  9411. * instances as well as meta data such as number of instances fetched, number available etc
  9412. * @return {Ext.data.ResultSet} The ResultSet object
  9413. */
  9414. getResultSet: function() {
  9415. return this.resultSet;
  9416. },
  9417. /**
  9418. * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
  9419. * {@link #isRunning} to test if the Operation is currently running.
  9420. * @return {Boolean} True if the Operation has started
  9421. */
  9422. isStarted: function() {
  9423. return this.started === true;
  9424. },
  9425. /**
  9426. * Returns true if the Operation has been started but has not yet completed.
  9427. * @return {Boolean} True if the Operation is currently running
  9428. */
  9429. isRunning: function() {
  9430. return this.running === true;
  9431. },
  9432. /**
  9433. * Returns true if the Operation has been completed
  9434. * @return {Boolean} True if the Operation is complete
  9435. */
  9436. isComplete: function() {
  9437. return this.complete === true;
  9438. },
  9439. /**
  9440. * Returns true if the Operation has completed and was successful
  9441. * @return {Boolean} True if successful
  9442. */
  9443. wasSuccessful: function() {
  9444. return this.isComplete() && this.success === true;
  9445. },
  9446. /**
  9447. * @private
  9448. * Associates this Operation with a Batch
  9449. * @param {Ext.data.Batch} batch The batch
  9450. */
  9451. setBatch: function(batch) {
  9452. this.batch = batch;
  9453. },
  9454. /**
  9455. * Checks whether this operation should cause writing to occur.
  9456. * @return {Boolean} Whether the operation should cause a write to occur.
  9457. */
  9458. allowWrite: function() {
  9459. return this.action != 'read';
  9460. }
  9461. });
  9462. /**
  9463. * @author Ed Spencer
  9464. *
  9465. * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
  9466. * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
  9467. */
  9468. Ext.define('Ext.data.validations', {
  9469. singleton: true,
  9470. /**
  9471. * @property {String} presenceMessage
  9472. * The default error message used when a presence validation fails.
  9473. */
  9474. presenceMessage: 'must be present',
  9475. /**
  9476. * @property {String} lengthMessage
  9477. * The default error message used when a length validation fails.
  9478. */
  9479. lengthMessage: 'is the wrong length',
  9480. /**
  9481. * @property {Boolean} formatMessage
  9482. * The default error message used when a format validation fails.
  9483. */
  9484. formatMessage: 'is the wrong format',
  9485. /**
  9486. * @property {String} inclusionMessage
  9487. * The default error message used when an inclusion validation fails.
  9488. */
  9489. inclusionMessage: 'is not included in the list of acceptable values',
  9490. /**
  9491. * @property {String} exclusionMessage
  9492. * The default error message used when an exclusion validation fails.
  9493. */
  9494. exclusionMessage: 'is not an acceptable value',
  9495. /**
  9496. * @property {String} emailMessage
  9497. * The default error message used when an email validation fails
  9498. */
  9499. emailMessage: 'is not a valid email address',
  9500. /**
  9501. * @property {RegExp} emailRe
  9502. * The regular expression used to validate email addresses
  9503. */
  9504. emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
  9505. /**
  9506. * Validates that the given value is present.
  9507. * For example:
  9508. *
  9509. * validations: [{type: 'presence', field: 'age'}]
  9510. *
  9511. * @param {Object} config Config object
  9512. * @param {Object} value The value to validate
  9513. * @return {Boolean} True if validation passed
  9514. */
  9515. presence: function(config, value) {
  9516. if (value === undefined) {
  9517. value = config;
  9518. }
  9519. //we need an additional check for zero here because zero is an acceptable form of present data
  9520. return !!value || value === 0;
  9521. },
  9522. /**
  9523. * Returns true if the given value is between the configured min and max values.
  9524. * For example:
  9525. *
  9526. * validations: [{type: 'length', field: 'name', min: 2}]
  9527. *
  9528. * @param {Object} config Config object
  9529. * @param {String} value The value to validate
  9530. * @return {Boolean} True if the value passes validation
  9531. */
  9532. length: function(config, value) {
  9533. if (value === undefined || value === null) {
  9534. return false;
  9535. }
  9536. var length = value.length,
  9537. min = config.min,
  9538. max = config.max;
  9539. if ((min && length < min) || (max && length > max)) {
  9540. return false;
  9541. } else {
  9542. return true;
  9543. }
  9544. },
  9545. /**
  9546. * Validates that an email string is in the correct format
  9547. * @param {Object} config Config object
  9548. * @param {String} email The email address
  9549. * @return {Boolean} True if the value passes validation
  9550. */
  9551. email: function(config, email) {
  9552. return Ext.data.validations.emailRe.test(email);
  9553. },
  9554. /**
  9555. * Returns true if the given value passes validation against the configured `matcher` regex.
  9556. * For example:
  9557. *
  9558. * validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
  9559. *
  9560. * @param {Object} config Config object
  9561. * @param {String} value The value to validate
  9562. * @return {Boolean} True if the value passes the format validation
  9563. */
  9564. format: function(config, value) {
  9565. return !!(config.matcher && config.matcher.test(value));
  9566. },
  9567. /**
  9568. * Validates that the given value is present in the configured `list`.
  9569. * For example:
  9570. *
  9571. * validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
  9572. *
  9573. * @param {Object} config Config object
  9574. * @param {String} value The value to validate
  9575. * @return {Boolean} True if the value is present in the list
  9576. */
  9577. inclusion: function(config, value) {
  9578. return config.list && Ext.Array.indexOf(config.list,value) != -1;
  9579. },
  9580. /**
  9581. * Validates that the given value is present in the configured `list`.
  9582. * For example:
  9583. *
  9584. * validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
  9585. *
  9586. * @param {Object} config Config object
  9587. * @param {String} value The value to validate
  9588. * @return {Boolean} True if the value is not present in the list
  9589. */
  9590. exclusion: function(config, value) {
  9591. return config.list && Ext.Array.indexOf(config.list,value) == -1;
  9592. }
  9593. });
  9594. /**
  9595. * @author Ed Spencer
  9596. *
  9597. * Simple wrapper class that represents a set of records returned by a Proxy.
  9598. */
  9599. Ext.define('Ext.data.ResultSet', {
  9600. /**
  9601. * @cfg {Boolean} loaded
  9602. * True if the records have already been loaded. This is only meaningful when dealing with
  9603. * SQL-backed proxies.
  9604. */
  9605. loaded: true,
  9606. /**
  9607. * @cfg {Number} count
  9608. * The number of records in this ResultSet. Note that total may differ from this number.
  9609. */
  9610. count: 0,
  9611. /**
  9612. * @cfg {Number} total
  9613. * The total number of records reported by the data source. This ResultSet may form a subset of
  9614. * those records (see {@link #count}).
  9615. */
  9616. total: 0,
  9617. /**
  9618. * @cfg {Boolean} success
  9619. * True if the ResultSet loaded successfully, false if any errors were encountered.
  9620. */
  9621. success: false,
  9622. /**
  9623. * @cfg {Ext.data.Model[]} records (required)
  9624. * The array of record instances.
  9625. */
  9626. /**
  9627. * Creates the resultSet
  9628. * @param {Object} [config] Config object.
  9629. */
  9630. constructor: function(config) {
  9631. Ext.apply(this, config);
  9632. /**
  9633. * @property {Number} totalRecords
  9634. * Copy of this.total.
  9635. * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
  9636. */
  9637. this.totalRecords = this.total;
  9638. if (config.count === undefined) {
  9639. this.count = this.records.length;
  9640. }
  9641. }
  9642. });
  9643. /**
  9644. * @author Ed Spencer
  9645. *
  9646. * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
  9647. * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
  9648. * the Operations.
  9649. *
  9650. * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
  9651. * the config options passed to the JsonWriter's constructor.
  9652. *
  9653. * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
  9654. * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
  9655. * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
  9656. */
  9657. Ext.define('Ext.data.writer.Writer', {
  9658. alias: 'writer.base',
  9659. alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
  9660. /**
  9661. * @cfg {Boolean} writeAllFields
  9662. * True to write all fields from the record to the server. If set to false it will only send the fields that were
  9663. * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
  9664. */
  9665. writeAllFields: true,
  9666. /**
  9667. * @cfg {String} nameProperty
  9668. * This property is used to read the key for each value that will be sent to the server. For example:
  9669. *
  9670. * Ext.define('Person', {
  9671. * extend: 'Ext.data.Model',
  9672. * fields: [{
  9673. * name: 'first',
  9674. * mapping: 'firstName'
  9675. * }, {
  9676. * name: 'last',
  9677. * mapping: 'lastName'
  9678. * }, {
  9679. * name: 'age'
  9680. * }]
  9681. * });
  9682. * new Ext.data.writer.Writer({
  9683. * writeAllFields: true,
  9684. * nameProperty: 'mapping'
  9685. * });
  9686. *
  9687. * // This will be sent to the server
  9688. * {
  9689. * firstName: 'first name value',
  9690. * lastName: 'last name value',
  9691. * age: 1
  9692. * }
  9693. *
  9694. * If the value is not present, the field name will always be used.
  9695. */
  9696. nameProperty: 'name',
  9697. /**
  9698. * Creates new Writer.
  9699. * @param {Object} [config] Config object.
  9700. */
  9701. constructor: function(config) {
  9702. Ext.apply(this, config);
  9703. },
  9704. /**
  9705. * Prepares a Proxy's Ext.data.Request object
  9706. * @param {Ext.data.Request} request The request object
  9707. * @return {Ext.data.Request} The modified request object
  9708. */
  9709. write: function(request) {
  9710. var operation = request.operation,
  9711. records = operation.records || [],
  9712. len = records.length,
  9713. i = 0,
  9714. data = [];
  9715. for (; i < len; i++) {
  9716. data.push(this.getRecordData(records[i]));
  9717. }
  9718. return this.writeRecords(request, data);
  9719. },
  9720. /**
  9721. * Formats the data for each record before sending it to the server. This method should be overridden to format the
  9722. * data in a way that differs from the default.
  9723. * @param {Object} record The record that we are writing to the server.
  9724. * @return {Object} An object literal of name/value keys to be written to the server. By default this method returns
  9725. * the data property on the record.
  9726. */
  9727. getRecordData: function(record) {
  9728. var isPhantom = record.phantom === true,
  9729. writeAll = this.writeAllFields || isPhantom,
  9730. nameProperty = this.nameProperty,
  9731. fields = record.fields,
  9732. data = {},
  9733. changes,
  9734. name,
  9735. field,
  9736. key;
  9737. if (writeAll) {
  9738. fields.each(function(field){
  9739. if (field.persist) {
  9740. name = field[nameProperty] || field.name;
  9741. data[name] = record.get(field.name);
  9742. }
  9743. });
  9744. } else {
  9745. // Only write the changes
  9746. changes = record.getChanges();
  9747. for (key in changes) {
  9748. if (changes.hasOwnProperty(key)) {
  9749. field = fields.get(key);
  9750. name = field[nameProperty] || field.name;
  9751. data[name] = changes[key];
  9752. }
  9753. }
  9754. if (!isPhantom) {
  9755. // always include the id for non phantoms
  9756. data[record.idProperty] = record.getId();
  9757. }
  9758. }
  9759. return data;
  9760. }
  9761. });
  9762. /**
  9763. * A mixin to add floating capability to a Component.
  9764. */
  9765. Ext.define('Ext.util.Floating', {
  9766. uses: ['Ext.Layer', 'Ext.window.Window'],
  9767. /**
  9768. * @cfg {Boolean} focusOnToFront
  9769. * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
  9770. * it is {@link #toFront brought to the front}.
  9771. */
  9772. focusOnToFront: true,
  9773. /**
  9774. * @cfg {String/Boolean} shadow
  9775. * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
  9776. * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
  9777. * shadow.
  9778. */
  9779. shadow: 'sides',
  9780. constructor: function(config) {
  9781. var me = this;
  9782. me.floating = true;
  9783. me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
  9784. hideMode: me.hideMode,
  9785. hidden: me.hidden,
  9786. shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
  9787. shadowOffset: me.shadowOffset,
  9788. constrain: false,
  9789. shim: me.shim === false ? false : undefined
  9790. }), me.el);
  9791. },
  9792. onFloatRender: function() {
  9793. var me = this;
  9794. me.zIndexParent = me.getZIndexParent();
  9795. me.setFloatParent(me.ownerCt);
  9796. delete me.ownerCt;
  9797. if (me.zIndexParent) {
  9798. me.zIndexParent.registerFloatingItem(me);
  9799. } else {
  9800. Ext.WindowManager.register(me);
  9801. }
  9802. },
  9803. setFloatParent: function(floatParent) {
  9804. var me = this;
  9805. // Remove listeners from previous floatParent
  9806. if (me.floatParent) {
  9807. me.mun(me.floatParent, {
  9808. hide: me.onFloatParentHide,
  9809. show: me.onFloatParentShow,
  9810. scope: me
  9811. });
  9812. }
  9813. me.floatParent = floatParent;
  9814. // Floating Components as children of Containers must hide when their parent hides.
  9815. if (floatParent) {
  9816. me.mon(me.floatParent, {
  9817. hide: me.onFloatParentHide,
  9818. show: me.onFloatParentShow,
  9819. scope: me
  9820. });
  9821. }
  9822. // If a floating Component is configured to be constrained, but has no configured
  9823. // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
  9824. if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
  9825. me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
  9826. }
  9827. },
  9828. onFloatParentHide: function() {
  9829. var me = this;
  9830. if (me.hideOnParentHide !== false) {
  9831. me.showOnParentShow = me.isVisible();
  9832. me.hide();
  9833. }
  9834. },
  9835. onFloatParentShow: function() {
  9836. if (this.showOnParentShow) {
  9837. delete this.showOnParentShow;
  9838. this.show();
  9839. }
  9840. },
  9841. /**
  9842. * @private
  9843. * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
  9844. *
  9845. * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
  9846. *
  9847. * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
  9848. * and the global Ext.WindowManager will be used.
  9849. */
  9850. getZIndexParent: function() {
  9851. var p = this.ownerCt,
  9852. c;
  9853. if (p) {
  9854. while (p) {
  9855. c = p;
  9856. p = p.ownerCt;
  9857. }
  9858. if (c.floating) {
  9859. return c;
  9860. }
  9861. }
  9862. },
  9863. // private
  9864. // z-index is managed by the zIndexManager and may be overwritten at any time.
  9865. // Returns the next z-index to be used.
  9866. // If this is a Container, then it will have rebased any managed floating Components,
  9867. // and so the next available z-index will be approximately 10000 above that.
  9868. setZIndex: function(index) {
  9869. var me = this;
  9870. me.el.setZIndex(index);
  9871. // Next item goes 10 above;
  9872. index += 10;
  9873. // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
  9874. // The returned value is a round number approximately 10000 above the last z-index used.
  9875. if (me.floatingItems) {
  9876. index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
  9877. }
  9878. return index;
  9879. },
  9880. /**
  9881. * Moves this floating Component into a constrain region.
  9882. *
  9883. * By default, this Component is constrained to be within the container it was added to, or the element it was
  9884. * rendered to.
  9885. *
  9886. * An alternative constraint may be passed.
  9887. * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
  9888. * to be constrained. Defaults to the element into which this floating Component was rendered.
  9889. */
  9890. doConstrain: function(constrainTo) {
  9891. var me = this,
  9892. vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
  9893. xy;
  9894. if (vector) {
  9895. xy = me.getPosition();
  9896. xy[0] += vector[0];
  9897. xy[1] += vector[1];
  9898. me.setPosition(xy);
  9899. }
  9900. },
  9901. /**
  9902. * Gets the x/y offsets to constrain this float
  9903. * @private
  9904. * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
  9905. * @return {Number[]} The x/y constraints
  9906. */
  9907. getConstrainVector: function(constrainTo){
  9908. var me = this,
  9909. el;
  9910. if (me.constrain || me.constrainHeader) {
  9911. el = me.constrainHeader ? me.header.el : me.el;
  9912. constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
  9913. return el.getConstrainVector(constrainTo);
  9914. }
  9915. },
  9916. /**
  9917. * Aligns this floating Component to the specified element
  9918. *
  9919. * @param {Ext.Component/Ext.Element/HTMLElement/String} element
  9920. * The element or {@link Ext.Component} to align to. If passing a component, it must be a
  9921. * omponent instance. If a string id is passed, it will be used as an element id.
  9922. * @param {String} [position="tl-bl?"] The position to align to (see {@link
  9923. * Ext.Element#alignTo} for more details).
  9924. * @param {Number[]} [offsets] Offset the positioning by [x, y]
  9925. * @return {Ext.Component} this
  9926. */
  9927. alignTo: function(element, position, offsets) {
  9928. if (element.isComponent) {
  9929. element = element.getEl();
  9930. }
  9931. var xy = this.el.getAlignToXY(element, position, offsets);
  9932. this.setPagePosition(xy);
  9933. return this;
  9934. },
  9935. /**
  9936. * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
  9937. * Ext.ZIndexManager ZIndexManager}
  9938. *
  9939. * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
  9940. *
  9941. * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
  9942. * @return {Ext.Component} this
  9943. */
  9944. toFront: function(preventFocus) {
  9945. var me = this;
  9946. // Find the floating Component which provides the base for this Component's zIndexing.
  9947. // That must move to front to then be able to rebase its zIndex stack and move this to the front
  9948. if (me.zIndexParent) {
  9949. me.zIndexParent.toFront(true);
  9950. }
  9951. if (me.zIndexManager.bringToFront(me)) {
  9952. if (!Ext.isDefined(preventFocus)) {
  9953. preventFocus = !me.focusOnToFront;
  9954. }
  9955. if (!preventFocus) {
  9956. // Kick off a delayed focus request.
  9957. // If another floating Component is toFronted before the delay expires
  9958. // this will not receive focus.
  9959. me.focus(false, true);
  9960. }
  9961. }
  9962. return me;
  9963. },
  9964. /**
  9965. * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
  9966. * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
  9967. *
  9968. * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
  9969. *
  9970. * This method also fires the {@link Ext.Component#activate activate} or
  9971. * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
  9972. *
  9973. * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
  9974. * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
  9975. */
  9976. setActive: function(active, newActive) {
  9977. var me = this;
  9978. if (active) {
  9979. if (me.el.shadow && !me.maximized) {
  9980. me.el.enableShadow(true);
  9981. }
  9982. me.fireEvent('activate', me);
  9983. } else {
  9984. // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
  9985. // can keep their shadows all the time
  9986. if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
  9987. me.el.disableShadow();
  9988. }
  9989. me.fireEvent('deactivate', me);
  9990. }
  9991. },
  9992. /**
  9993. * Sends this Component to the back of (lower z-index than) any other visible windows
  9994. * @return {Ext.Component} this
  9995. */
  9996. toBack: function() {
  9997. this.zIndexManager.sendToBack(this);
  9998. return this;
  9999. },
  10000. /**
  10001. * Center this Component in its container.
  10002. * @return {Ext.Component} this
  10003. */
  10004. center: function() {
  10005. var me = this,
  10006. xy = me.el.getAlignToXY(me.container, 'c-c');
  10007. me.setPagePosition(xy);
  10008. return me;
  10009. },
  10010. // private
  10011. syncShadow : function(){
  10012. if (this.floating) {
  10013. this.el.sync(true);
  10014. }
  10015. },
  10016. // private
  10017. fitContainer: function() {
  10018. var parent = this.floatParent,
  10019. container = parent ? parent.getTargetEl() : this.container,
  10020. size = container.getViewSize(false);
  10021. this.setSize(size);
  10022. }
  10023. });
  10024. /**
  10025. * Base Layout class - extended by ComponentLayout and ContainerLayout
  10026. */
  10027. Ext.define('Ext.layout.Layout', {
  10028. /* Begin Definitions */
  10029. /* End Definitions */
  10030. isLayout: true,
  10031. initialized: false,
  10032. statics: {
  10033. create: function(layout, defaultType) {
  10034. var type;
  10035. if (layout instanceof Ext.layout.Layout) {
  10036. return Ext.createByAlias('layout.' + layout);
  10037. } else {
  10038. if (!layout || typeof layout === 'string') {
  10039. type = layout || defaultType;
  10040. layout = {};
  10041. }
  10042. else {
  10043. type = layout.type || defaultType;
  10044. }
  10045. return Ext.createByAlias('layout.' + type, layout || {});
  10046. }
  10047. }
  10048. },
  10049. constructor : function(config) {
  10050. this.id = Ext.id(null, this.type + '-');
  10051. Ext.apply(this, config);
  10052. },
  10053. /**
  10054. * @private
  10055. */
  10056. layout : function() {
  10057. var me = this;
  10058. me.layoutBusy = true;
  10059. me.initLayout();
  10060. if (me.beforeLayout.apply(me, arguments) !== false) {
  10061. me.layoutCancelled = false;
  10062. me.onLayout.apply(me, arguments);
  10063. me.childrenChanged = false;
  10064. me.owner.needsLayout = false;
  10065. me.layoutBusy = false;
  10066. me.afterLayout.apply(me, arguments);
  10067. }
  10068. else {
  10069. me.layoutCancelled = true;
  10070. }
  10071. me.layoutBusy = false;
  10072. me.doOwnerCtLayouts();
  10073. },
  10074. beforeLayout : function() {
  10075. this.renderChildren();
  10076. return true;
  10077. },
  10078. renderChildren: function () {
  10079. this.renderItems(this.getLayoutItems(), this.getRenderTarget());
  10080. },
  10081. /**
  10082. * @private
  10083. * Iterates over all passed items, ensuring they are rendered. If the items are already rendered,
  10084. * also determines if the items are in the proper place dom.
  10085. */
  10086. renderItems : function(items, target) {
  10087. var me = this,
  10088. ln = items.length,
  10089. i = 0,
  10090. item;
  10091. for (; i < ln; i++) {
  10092. item = items[i];
  10093. if (item && !item.rendered) {
  10094. me.renderItem(item, target, i);
  10095. } else if (!me.isValidParent(item, target, i)) {
  10096. me.moveItem(item, target, i);
  10097. } else {
  10098. // still need to configure the item, it may have moved in the container.
  10099. me.configureItem(item);
  10100. }
  10101. }
  10102. },
  10103. // @private - Validates item is in the proper place in the dom.
  10104. isValidParent : function(item, target, position) {
  10105. var dom = item.el ? item.el.dom : Ext.getDom(item);
  10106. if (dom && target && target.dom) {
  10107. if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
  10108. return false;
  10109. }
  10110. return (dom.parentNode == (target.dom || target));
  10111. }
  10112. return false;
  10113. },
  10114. /**
  10115. * @private
  10116. * Renders the given Component into the target Element.
  10117. * @param {Ext.Component} item The Component to render
  10118. * @param {Ext.Element} target The target Element
  10119. * @param {Number} position The position within the target to render the item to
  10120. */
  10121. renderItem : function(item, target, position) {
  10122. var me = this;
  10123. if (!item.rendered) {
  10124. if (me.itemCls) {
  10125. item.addCls(me.itemCls);
  10126. }
  10127. if (me.owner.itemCls) {
  10128. item.addCls(me.owner.itemCls);
  10129. }
  10130. item.render(target, position);
  10131. me.configureItem(item);
  10132. me.childrenChanged = true;
  10133. }
  10134. },
  10135. /**
  10136. * @private
  10137. * Moved Component to the provided target instead.
  10138. */
  10139. moveItem : function(item, target, position) {
  10140. // Make sure target is a dom element
  10141. target = target.dom || target;
  10142. if (typeof position == 'number') {
  10143. position = target.childNodes[position];
  10144. }
  10145. target.insertBefore(item.el.dom, position || null);
  10146. item.container = Ext.get(target);
  10147. this.configureItem(item);
  10148. this.childrenChanged = true;
  10149. },
  10150. /**
  10151. * @private
  10152. * Adds the layout's targetCls if necessary and sets
  10153. * initialized flag when complete.
  10154. */
  10155. initLayout : function() {
  10156. var me = this,
  10157. targetCls = me.targetCls;
  10158. if (!me.initialized && !Ext.isEmpty(targetCls)) {
  10159. me.getTarget().addCls(targetCls);
  10160. }
  10161. me.initialized = true;
  10162. },
  10163. // @private Sets the layout owner
  10164. setOwner : function(owner) {
  10165. this.owner = owner;
  10166. },
  10167. // @private - Returns empty array
  10168. getLayoutItems : function() {
  10169. return [];
  10170. },
  10171. /**
  10172. * @private
  10173. * Applies itemCls
  10174. * Empty template method
  10175. */
  10176. configureItem: Ext.emptyFn,
  10177. // Placeholder empty functions for subclasses to extend
  10178. onLayout : Ext.emptyFn,
  10179. afterLayout : Ext.emptyFn,
  10180. onRemove : Ext.emptyFn,
  10181. onDestroy : Ext.emptyFn,
  10182. doOwnerCtLayouts : Ext.emptyFn,
  10183. /**
  10184. * @private
  10185. * Removes itemCls
  10186. */
  10187. afterRemove : function(item) {
  10188. var el = item.el,
  10189. owner = this.owner,
  10190. itemCls = this.itemCls,
  10191. ownerCls = owner.itemCls;
  10192. // Clear managed dimensions flag when removed from the layout.
  10193. if (item.rendered && !item.isDestroyed) {
  10194. if (itemCls) {
  10195. el.removeCls(itemCls);
  10196. }
  10197. if (ownerCls) {
  10198. el.removeCls(ownerCls);
  10199. }
  10200. }
  10201. // These flags are set at the time a child item is added to a layout.
  10202. // The layout must decide if it is managing the item's width, or its height, or both.
  10203. // See AbstractComponent for docs on these properties.
  10204. delete item.layoutManagedWidth;
  10205. delete item.layoutManagedHeight;
  10206. },
  10207. /**
  10208. * Destroys this layout. This is a template method that is empty by default, but should be implemented
  10209. * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
  10210. * @template
  10211. */
  10212. destroy : function() {
  10213. var targetCls = this.targetCls,
  10214. target;
  10215. if (!Ext.isEmpty(targetCls)) {
  10216. target = this.getTarget();
  10217. if (target) {
  10218. target.removeCls(targetCls);
  10219. }
  10220. }
  10221. this.onDestroy();
  10222. }
  10223. });
  10224. /**
  10225. * @class Ext.ZIndexManager
  10226. * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
  10227. * and Component activation behavior, including masking below the active (topmost) Component.</p>
  10228. * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
  10229. * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
  10230. * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
  10231. * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
  10232. * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
  10233. * guaranteed to be correct, relative to the Window.</p>
  10234. */
  10235. Ext.define('Ext.ZIndexManager', {
  10236. alternateClassName: 'Ext.WindowGroup',
  10237. statics: {
  10238. zBase : 9000
  10239. },
  10240. constructor: function(container) {
  10241. var me = this;
  10242. me.list = {};
  10243. me.zIndexStack = [];
  10244. me.front = null;
  10245. if (container) {
  10246. // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
  10247. if (container.isContainer) {
  10248. container.on('resize', me._onContainerResize, me);
  10249. me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
  10250. // The containing element we will be dealing with (eg masking) is the content target
  10251. me.targetEl = container.getTargetEl();
  10252. me.container = container;
  10253. }
  10254. // This is the ZIndexManager for a DOM element
  10255. else {
  10256. Ext.EventManager.onWindowResize(me._onContainerResize, me);
  10257. me.zseed = me.getNextZSeed();
  10258. me.targetEl = Ext.get(container);
  10259. }
  10260. }
  10261. // No container passed means we are the global WindowManager. Our target is the doc body.
  10262. // DOM must be ready to collect that ref.
  10263. else {
  10264. Ext.EventManager.onWindowResize(me._onContainerResize, me);
  10265. me.zseed = me.getNextZSeed();
  10266. Ext.onDocumentReady(function() {
  10267. me.targetEl = Ext.getBody();
  10268. });
  10269. }
  10270. },
  10271. getNextZSeed: function() {
  10272. return (Ext.ZIndexManager.zBase += 10000);
  10273. },
  10274. setBase: function(baseZIndex) {
  10275. this.zseed = baseZIndex;
  10276. return this.assignZIndices();
  10277. },
  10278. // private
  10279. assignZIndices: function() {
  10280. var a = this.zIndexStack,
  10281. len = a.length,
  10282. i = 0,
  10283. zIndex = this.zseed,
  10284. comp;
  10285. for (; i < len; i++) {
  10286. comp = a[i];
  10287. if (comp && !comp.hidden) {
  10288. // Setting the zIndex of a Component returns the topmost zIndex consumed by
  10289. // that Component.
  10290. // If it's just a plain floating Component such as a BoundList, then the
  10291. // return value is the passed value plus 10, ready for the next item.
  10292. // If a floating *Container* has its zIndex set, it re-orders its managed
  10293. // floating children, starting from that new base, and returns a value 10000 above
  10294. // the highest zIndex which it allocates.
  10295. zIndex = comp.setZIndex(zIndex);
  10296. }
  10297. }
  10298. this._activateLast();
  10299. return zIndex;
  10300. },
  10301. // private
  10302. _setActiveChild: function(comp) {
  10303. if (comp !== this.front) {
  10304. if (this.front) {
  10305. this.front.setActive(false, comp);
  10306. }
  10307. this.front = comp;
  10308. if (comp) {
  10309. comp.setActive(true);
  10310. if (comp.modal) {
  10311. this._showModalMask(comp);
  10312. }
  10313. }
  10314. }
  10315. },
  10316. // private
  10317. _activateLast: function(justHidden) {
  10318. var comp,
  10319. lastActivated = false,
  10320. i;
  10321. // Go down through the z-index stack.
  10322. // Activate the next visible one down.
  10323. // Keep going down to find the next visible modal one to shift the modal mask down under
  10324. for (i = this.zIndexStack.length-1; i >= 0; --i) {
  10325. comp = this.zIndexStack[i];
  10326. if (!comp.hidden) {
  10327. if (!lastActivated) {
  10328. this._setActiveChild(comp);
  10329. lastActivated = true;
  10330. }
  10331. // Move any modal mask down to just under the next modal floater down the stack
  10332. if (comp.modal) {
  10333. this._showModalMask(comp);
  10334. return;
  10335. }
  10336. }
  10337. }
  10338. // none to activate, so there must be no modal mask.
  10339. // And clear the currently active property
  10340. this._hideModalMask();
  10341. if (!lastActivated) {
  10342. this._setActiveChild(null);
  10343. }
  10344. },
  10345. _showModalMask: function(comp) {
  10346. var zIndex = comp.el.getStyle('zIndex') - 4,
  10347. maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
  10348. parentBox;
  10349. if (!maskTarget) {
  10350. //<debug>
  10351. Ext.global.console && Ext.global.console.warn && Ext.global.console.warn('mask target could not be found. Mask cannot be shown');
  10352. //</debug>
  10353. return;
  10354. }
  10355. parentBox = maskTarget.getBox();
  10356. if (!this.mask) {
  10357. this.mask = Ext.getBody().createChild({
  10358. cls: Ext.baseCSSPrefix + 'mask'
  10359. });
  10360. this.mask.setVisibilityMode(Ext.Element.DISPLAY);
  10361. this.mask.on('click', this._onMaskClick, this);
  10362. }
  10363. if (maskTarget.dom === document.body) {
  10364. parentBox.height = Ext.Element.getViewHeight();
  10365. }
  10366. maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
  10367. this.mask.setBox(parentBox);
  10368. this.mask.setStyle('zIndex', zIndex);
  10369. this.mask.show();
  10370. },
  10371. _hideModalMask: function() {
  10372. if (this.mask && this.mask.dom.parentNode) {
  10373. Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
  10374. this.mask.hide();
  10375. }
  10376. },
  10377. _onMaskClick: function() {
  10378. if (this.front) {
  10379. this.front.focus();
  10380. }
  10381. },
  10382. _onContainerResize: function() {
  10383. if (this.mask && this.mask.isVisible()) {
  10384. this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
  10385. }
  10386. },
  10387. /**
  10388. * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
  10389. * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
  10390. * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
  10391. * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
  10392. * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
  10393. * ZIndexManager in the desktop sample app:</p><code><pre>
  10394. MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
  10395. </pre></code>
  10396. * @param {Ext.Component} comp The Component to register.
  10397. */
  10398. register : function(comp) {
  10399. if (comp.zIndexManager) {
  10400. comp.zIndexManager.unregister(comp);
  10401. }
  10402. comp.zIndexManager = this;
  10403. this.list[comp.id] = comp;
  10404. this.zIndexStack.push(comp);
  10405. comp.on('hide', this._activateLast, this);
  10406. },
  10407. /**
  10408. * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
  10409. * need to be called. Components are automatically unregistered upon destruction.
  10410. * See {@link #register}.</p>
  10411. * @param {Ext.Component} comp The Component to unregister.
  10412. */
  10413. unregister : function(comp) {
  10414. delete comp.zIndexManager;
  10415. if (this.list && this.list[comp.id]) {
  10416. delete this.list[comp.id];
  10417. comp.un('hide', this._activateLast);
  10418. Ext.Array.remove(this.zIndexStack, comp);
  10419. // Destruction requires that the topmost visible floater be activated. Same as hiding.
  10420. this._activateLast(comp);
  10421. }
  10422. },
  10423. /**
  10424. * Gets a registered Component by id.
  10425. * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
  10426. * @return {Ext.Component}
  10427. */
  10428. get : function(id) {
  10429. return typeof id == "object" ? id : this.list[id];
  10430. },
  10431. /**
  10432. * Brings the specified Component to the front of any other active Components in this ZIndexManager.
  10433. * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
  10434. * @return {Boolean} True if the dialog was brought to the front, else false
  10435. * if it was already in front
  10436. */
  10437. bringToFront : function(comp) {
  10438. comp = this.get(comp);
  10439. if (comp !== this.front) {
  10440. Ext.Array.remove(this.zIndexStack, comp);
  10441. this.zIndexStack.push(comp);
  10442. this.assignZIndices();
  10443. return true;
  10444. }
  10445. if (comp.modal) {
  10446. this._showModalMask(comp);
  10447. }
  10448. return false;
  10449. },
  10450. /**
  10451. * Sends the specified Component to the back of other active Components in this ZIndexManager.
  10452. * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
  10453. * @return {Ext.Component} The Component
  10454. */
  10455. sendToBack : function(comp) {
  10456. comp = this.get(comp);
  10457. Ext.Array.remove(this.zIndexStack, comp);
  10458. this.zIndexStack.unshift(comp);
  10459. this.assignZIndices();
  10460. return comp;
  10461. },
  10462. /**
  10463. * Hides all Components managed by this ZIndexManager.
  10464. */
  10465. hideAll : function() {
  10466. for (var id in this.list) {
  10467. if (this.list[id].isComponent && this.list[id].isVisible()) {
  10468. this.list[id].hide();
  10469. }
  10470. }
  10471. },
  10472. /**
  10473. * @private
  10474. * Temporarily hides all currently visible managed Components. This is for when
  10475. * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
  10476. * they should all be hidden just for the duration of the drag.
  10477. */
  10478. hide: function() {
  10479. var i = 0,
  10480. ln = this.zIndexStack.length,
  10481. comp;
  10482. this.tempHidden = [];
  10483. for (; i < ln; i++) {
  10484. comp = this.zIndexStack[i];
  10485. if (comp.isVisible()) {
  10486. this.tempHidden.push(comp);
  10487. comp.hide();
  10488. }
  10489. }
  10490. },
  10491. /**
  10492. * @private
  10493. * Restores temporarily hidden managed Components to visibility.
  10494. */
  10495. show: function() {
  10496. var i = 0,
  10497. ln = this.tempHidden.length,
  10498. comp,
  10499. x,
  10500. y;
  10501. for (; i < ln; i++) {
  10502. comp = this.tempHidden[i];
  10503. x = comp.x;
  10504. y = comp.y;
  10505. comp.show();
  10506. comp.setPosition(x, y);
  10507. }
  10508. delete this.tempHidden;
  10509. },
  10510. /**
  10511. * Gets the currently-active Component in this ZIndexManager.
  10512. * @return {Ext.Component} The active Component
  10513. */
  10514. getActive : function() {
  10515. return this.front;
  10516. },
  10517. /**
  10518. * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
  10519. * The function should accept a single {@link Ext.Component} reference as its only argument and should
  10520. * return true if the Component matches the search criteria, otherwise it should return false.
  10521. * @param {Function} fn The search function
  10522. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
  10523. * that gets passed to the function if not specified)
  10524. * @return {Array} An array of zero or more matching windows
  10525. */
  10526. getBy : function(fn, scope) {
  10527. var r = [],
  10528. i = 0,
  10529. len = this.zIndexStack.length,
  10530. comp;
  10531. for (; i < len; i++) {
  10532. comp = this.zIndexStack[i];
  10533. if (fn.call(scope||comp, comp) !== false) {
  10534. r.push(comp);
  10535. }
  10536. }
  10537. return r;
  10538. },
  10539. /**
  10540. * Executes the specified function once for every Component in this ZIndexManager, passing each
  10541. * Component as the only parameter. Returning false from the function will stop the iteration.
  10542. * @param {Function} fn The function to execute for each item
  10543. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
  10544. */
  10545. each : function(fn, scope) {
  10546. var comp;
  10547. for (var id in this.list) {
  10548. comp = this.list[id];
  10549. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  10550. return;
  10551. }
  10552. }
  10553. },
  10554. /**
  10555. * Executes the specified function once for every Component in this ZIndexManager, passing each
  10556. * Component as the only parameter. Returning false from the function will stop the iteration.
  10557. * The components are passed to the function starting at the bottom and proceeding to the top.
  10558. * @param {Function} fn The function to execute for each item
  10559. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
  10560. * is executed. Defaults to the current Component in the iteration.
  10561. */
  10562. eachBottomUp: function (fn, scope) {
  10563. var comp,
  10564. stack = this.zIndexStack,
  10565. i, n;
  10566. for (i = 0, n = stack.length ; i < n; i++) {
  10567. comp = stack[i];
  10568. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  10569. return;
  10570. }
  10571. }
  10572. },
  10573. /**
  10574. * Executes the specified function once for every Component in this ZIndexManager, passing each
  10575. * Component as the only parameter. Returning false from the function will stop the iteration.
  10576. * The components are passed to the function starting at the top and proceeding to the bottom.
  10577. * @param {Function} fn The function to execute for each item
  10578. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
  10579. * is executed. Defaults to the current Component in the iteration.
  10580. */
  10581. eachTopDown: function (fn, scope) {
  10582. var comp,
  10583. stack = this.zIndexStack,
  10584. i;
  10585. for (i = stack.length ; i-- > 0; ) {
  10586. comp = stack[i];
  10587. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  10588. return;
  10589. }
  10590. }
  10591. },
  10592. destroy: function() {
  10593. this.each(function(c) {
  10594. c.destroy();
  10595. });
  10596. delete this.zIndexStack;
  10597. delete this.list;
  10598. delete this.container;
  10599. delete this.targetEl;
  10600. }
  10601. }, function() {
  10602. /**
  10603. * @class Ext.WindowManager
  10604. * @extends Ext.ZIndexManager
  10605. * <p>The default global floating Component group that is available automatically.</p>
  10606. * <p>This manages instances of floating Components which were rendered programatically without
  10607. * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
  10608. * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
  10609. * there are managed by that ZIndexManager.</p>
  10610. * @singleton
  10611. */
  10612. Ext.WindowManager = Ext.WindowMgr = new this();
  10613. });
  10614. /**
  10615. * @private
  10616. * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
  10617. * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
  10618. * for its container.
  10619. */
  10620. Ext.define('Ext.layout.container.boxOverflow.None', {
  10621. alternateClassName: 'Ext.layout.boxOverflow.None',
  10622. constructor: function(layout, config) {
  10623. this.layout = layout;
  10624. Ext.apply(this, config || {});
  10625. },
  10626. handleOverflow: Ext.emptyFn,
  10627. clearOverflow: Ext.emptyFn,
  10628. onRemove: Ext.emptyFn,
  10629. /**
  10630. * @private
  10631. * Normalizes an item reference, string id or numerical index into a reference to the item
  10632. * @param {Ext.Component/String/Number} item The item reference, id or index
  10633. * @return {Ext.Component} The item
  10634. */
  10635. getItem: function(item) {
  10636. return this.layout.owner.getComponent(item);
  10637. },
  10638. onRemove: Ext.emptyFn
  10639. });
  10640. /**
  10641. * @class Ext.util.KeyMap
  10642. * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
  10643. * The constructor accepts the same config object as defined by {@link #addBinding}.
  10644. * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
  10645. * combination it will call the function with this signature (if the match is a multi-key
  10646. * combination the callback will still be called only once): (String key, Ext.EventObject e)
  10647. * A KeyMap can also handle a string representation of keys. By default KeyMap starts enabled.<br />
  10648. * Usage:
  10649. <pre><code>
  10650. // map one key by key code
  10651. var map = new Ext.util.KeyMap("my-element", {
  10652. key: 13, // or Ext.EventObject.ENTER
  10653. fn: myHandler,
  10654. scope: myObject
  10655. });
  10656. // map multiple keys to one action by string
  10657. var map = new Ext.util.KeyMap("my-element", {
  10658. key: "a\r\n\t",
  10659. fn: myHandler,
  10660. scope: myObject
  10661. });
  10662. // map multiple keys to multiple actions by strings and array of codes
  10663. var map = new Ext.util.KeyMap("my-element", [
  10664. {
  10665. key: [10,13],
  10666. fn: function(){ alert("Return was pressed"); }
  10667. }, {
  10668. key: "abc",
  10669. fn: function(){ alert('a, b or c was pressed'); }
  10670. }, {
  10671. key: "\t",
  10672. ctrl:true,
  10673. shift:true,
  10674. fn: function(){ alert('Control + shift + tab was pressed.'); }
  10675. }
  10676. ]);
  10677. </code></pre>
  10678. */
  10679. Ext.define('Ext.util.KeyMap', {
  10680. alternateClassName: 'Ext.KeyMap',
  10681. /**
  10682. * Creates new KeyMap.
  10683. * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
  10684. * @param {Object} binding The binding (see {@link #addBinding})
  10685. * @param {String} [eventName="keydown"] The event to bind to
  10686. */
  10687. constructor: function(el, binding, eventName){
  10688. var me = this;
  10689. Ext.apply(me, {
  10690. el: Ext.get(el),
  10691. eventName: eventName || me.eventName,
  10692. bindings: []
  10693. });
  10694. if (binding) {
  10695. me.addBinding(binding);
  10696. }
  10697. me.enable();
  10698. },
  10699. eventName: 'keydown',
  10700. /**
  10701. * Add a new binding to this KeyMap. The following config object properties are supported:
  10702. * <pre>
  10703. Property Type Description
  10704. ---------- --------------- ----------------------------------------------------------------------
  10705. key String/Array A single keycode or an array of keycodes to handle
  10706. shift Boolean True to handle key only when shift is pressed, False to handle the key only when shift is not pressed (defaults to undefined)
  10707. ctrl Boolean True to handle key only when ctrl is pressed, False to handle the key only when ctrl is not pressed (defaults to undefined)
  10708. alt Boolean True to handle key only when alt is pressed, False to handle the key only when alt is not pressed (defaults to undefined)
  10709. handler Function The function to call when KeyMap finds the expected key combination
  10710. fn Function Alias of handler (for backwards-compatibility)
  10711. scope Object The scope of the callback function
  10712. defaultEventAction String A default action to apply to the event. Possible values are: stopEvent, stopPropagation, preventDefault. If no value is set no action is performed.
  10713. </pre>
  10714. *
  10715. * Usage:
  10716. * <pre><code>
  10717. // Create a KeyMap
  10718. var map = new Ext.util.KeyMap(document, {
  10719. key: Ext.EventObject.ENTER,
  10720. fn: handleKey,
  10721. scope: this
  10722. });
  10723. //Add a new binding to the existing KeyMap later
  10724. map.addBinding({
  10725. key: 'abc',
  10726. shift: true,
  10727. fn: handleKey,
  10728. scope: this
  10729. });
  10730. </code></pre>
  10731. * @param {Object/Object[]} binding A single KeyMap config or an array of configs
  10732. */
  10733. addBinding : function(binding){
  10734. if (Ext.isArray(binding)) {
  10735. Ext.each(binding, this.addBinding, this);
  10736. return;
  10737. }
  10738. var keyCode = binding.key,
  10739. processed = false,
  10740. key,
  10741. keys,
  10742. keyString,
  10743. i,
  10744. len;
  10745. if (Ext.isString(keyCode)) {
  10746. keys = [];
  10747. keyString = keyCode.toUpperCase();
  10748. for (i = 0, len = keyString.length; i < len; ++i){
  10749. keys.push(keyString.charCodeAt(i));
  10750. }
  10751. keyCode = keys;
  10752. processed = true;
  10753. }
  10754. if (!Ext.isArray(keyCode)) {
  10755. keyCode = [keyCode];
  10756. }
  10757. if (!processed) {
  10758. for (i = 0, len = keyCode.length; i < len; ++i) {
  10759. key = keyCode[i];
  10760. if (Ext.isString(key)) {
  10761. keyCode[i] = key.toUpperCase().charCodeAt(0);
  10762. }
  10763. }
  10764. }
  10765. this.bindings.push(Ext.apply({
  10766. keyCode: keyCode
  10767. }, binding));
  10768. },
  10769. /**
  10770. * Process any keydown events on the element
  10771. * @private
  10772. * @param {Ext.EventObject} event
  10773. */
  10774. handleKeyDown: function(event) {
  10775. if (this.enabled) { //just in case
  10776. var bindings = this.bindings,
  10777. i = 0,
  10778. len = bindings.length;
  10779. event = this.processEvent(event);
  10780. for(; i < len; ++i){
  10781. this.processBinding(bindings[i], event);
  10782. }
  10783. }
  10784. },
  10785. /**
  10786. * Ugly hack to allow this class to be tested. Currently WebKit gives
  10787. * no way to raise a key event properly with both
  10788. * a) A keycode
  10789. * b) The alt/ctrl/shift modifiers
  10790. * So we have to simulate them here. Yuk!
  10791. * This is a stub method intended to be overridden by tests.
  10792. * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
  10793. * @private
  10794. */
  10795. processEvent: function(event){
  10796. return event;
  10797. },
  10798. /**
  10799. * Process a particular binding and fire the handler if necessary.
  10800. * @private
  10801. * @param {Object} binding The binding information
  10802. * @param {Ext.EventObject} event
  10803. */
  10804. processBinding: function(binding, event){
  10805. if (this.checkModifiers(binding, event)) {
  10806. var key = event.getKey(),
  10807. handler = binding.fn || binding.handler,
  10808. scope = binding.scope || this,
  10809. keyCode = binding.keyCode,
  10810. defaultEventAction = binding.defaultEventAction,
  10811. i,
  10812. len,
  10813. keydownEvent = new Ext.EventObjectImpl(event);
  10814. for (i = 0, len = keyCode.length; i < len; ++i) {
  10815. if (key === keyCode[i]) {
  10816. if (handler.call(scope, key, event) !== true && defaultEventAction) {
  10817. keydownEvent[defaultEventAction]();
  10818. }
  10819. break;
  10820. }
  10821. }
  10822. }
  10823. },
  10824. /**
  10825. * Check if the modifiers on the event match those on the binding
  10826. * @private
  10827. * @param {Object} binding
  10828. * @param {Ext.EventObject} event
  10829. * @return {Boolean} True if the event matches the binding
  10830. */
  10831. checkModifiers: function(binding, e){
  10832. var keys = ['shift', 'ctrl', 'alt'],
  10833. i = 0,
  10834. len = keys.length,
  10835. val, key;
  10836. for (; i < len; ++i){
  10837. key = keys[i];
  10838. val = binding[key];
  10839. if (!(val === undefined || (val === e[key + 'Key']))) {
  10840. return false;
  10841. }
  10842. }
  10843. return true;
  10844. },
  10845. /**
  10846. * Shorthand for adding a single key listener
  10847. * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
  10848. * following options:
  10849. * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
  10850. * @param {Function} fn The function to call
  10851. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
  10852. */
  10853. on: function(key, fn, scope) {
  10854. var keyCode, shift, ctrl, alt;
  10855. if (Ext.isObject(key) && !Ext.isArray(key)) {
  10856. keyCode = key.key;
  10857. shift = key.shift;
  10858. ctrl = key.ctrl;
  10859. alt = key.alt;
  10860. } else {
  10861. keyCode = key;
  10862. }
  10863. this.addBinding({
  10864. key: keyCode,
  10865. shift: shift,
  10866. ctrl: ctrl,
  10867. alt: alt,
  10868. fn: fn,
  10869. scope: scope
  10870. });
  10871. },
  10872. /**
  10873. * Returns true if this KeyMap is enabled
  10874. * @return {Boolean}
  10875. */
  10876. isEnabled : function(){
  10877. return this.enabled;
  10878. },
  10879. /**
  10880. * Enables this KeyMap
  10881. */
  10882. enable: function(){
  10883. var me = this;
  10884. if (!me.enabled) {
  10885. me.el.on(me.eventName, me.handleKeyDown, me);
  10886. me.enabled = true;
  10887. }
  10888. },
  10889. /**
  10890. * Disable this KeyMap
  10891. */
  10892. disable: function(){
  10893. var me = this;
  10894. if (me.enabled) {
  10895. me.el.removeListener(me.eventName, me.handleKeyDown, me);
  10896. me.enabled = false;
  10897. }
  10898. },
  10899. /**
  10900. * Convenience function for setting disabled/enabled by boolean.
  10901. * @param {Boolean} disabled
  10902. */
  10903. setDisabled : function(disabled){
  10904. if (disabled) {
  10905. this.disable();
  10906. } else {
  10907. this.enable();
  10908. }
  10909. },
  10910. /**
  10911. * Destroys the KeyMap instance and removes all handlers.
  10912. * @param {Boolean} removeEl True to also remove the attached element
  10913. */
  10914. destroy: function(removeEl){
  10915. var me = this;
  10916. me.bindings = [];
  10917. me.disable();
  10918. if (removeEl === true) {
  10919. me.el.remove();
  10920. }
  10921. delete me.el;
  10922. }
  10923. });
  10924. /**
  10925. * @class Ext.util.ClickRepeater
  10926. * @extends Ext.util.Observable
  10927. *
  10928. * A wrapper class which can be applied to any element. Fires a "click" event while the
  10929. * mouse is pressed. The interval between firings may be specified in the config but
  10930. * defaults to 20 milliseconds.
  10931. *
  10932. * Optionally, a CSS class may be applied to the element during the time it is pressed.
  10933. *
  10934. */
  10935. Ext.define('Ext.util.ClickRepeater', {
  10936. extend: 'Ext.util.Observable',
  10937. /**
  10938. * Creates new ClickRepeater.
  10939. * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
  10940. * @param {Object} config (optional) Config object.
  10941. */
  10942. constructor : function(el, config){
  10943. this.el = Ext.get(el);
  10944. this.el.unselectable();
  10945. Ext.apply(this, config);
  10946. this.addEvents(
  10947. /**
  10948. * @event mousedown
  10949. * Fires when the mouse button is depressed.
  10950. * @param {Ext.util.ClickRepeater} this
  10951. * @param {Ext.EventObject} e
  10952. */
  10953. "mousedown",
  10954. /**
  10955. * @event click
  10956. * Fires on a specified interval during the time the element is pressed.
  10957. * @param {Ext.util.ClickRepeater} this
  10958. * @param {Ext.EventObject} e
  10959. */
  10960. "click",
  10961. /**
  10962. * @event mouseup
  10963. * Fires when the mouse key is released.
  10964. * @param {Ext.util.ClickRepeater} this
  10965. * @param {Ext.EventObject} e
  10966. */
  10967. "mouseup"
  10968. );
  10969. if(!this.disabled){
  10970. this.disabled = true;
  10971. this.enable();
  10972. }
  10973. // allow inline handler
  10974. if(this.handler){
  10975. this.on("click", this.handler, this.scope || this);
  10976. }
  10977. this.callParent();
  10978. },
  10979. /**
  10980. * @cfg {String/HTMLElement/Ext.Element} el The element to act as a button.
  10981. */
  10982. /**
  10983. * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
  10984. */
  10985. /**
  10986. * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
  10987. * "interval" and "delay" are ignored.
  10988. */
  10989. /**
  10990. * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
  10991. */
  10992. interval : 20,
  10993. /**
  10994. * @cfg {Number} delay The initial delay before the repeating event begins firing.
  10995. * Similar to an autorepeat key delay.
  10996. */
  10997. delay: 250,
  10998. /**
  10999. * @cfg {Boolean} preventDefault True to prevent the default click event
  11000. */
  11001. preventDefault : true,
  11002. /**
  11003. * @cfg {Boolean} stopDefault True to stop the default click event
  11004. */
  11005. stopDefault : false,
  11006. timer : 0,
  11007. /**
  11008. * Enables the repeater and allows events to fire.
  11009. */
  11010. enable: function(){
  11011. if(this.disabled){
  11012. this.el.on('mousedown', this.handleMouseDown, this);
  11013. if (Ext.isIE){
  11014. this.el.on('dblclick', this.handleDblClick, this);
  11015. }
  11016. if(this.preventDefault || this.stopDefault){
  11017. this.el.on('click', this.eventOptions, this);
  11018. }
  11019. }
  11020. this.disabled = false;
  11021. },
  11022. /**
  11023. * Disables the repeater and stops events from firing.
  11024. */
  11025. disable: function(/* private */ force){
  11026. if(force || !this.disabled){
  11027. clearTimeout(this.timer);
  11028. if(this.pressedCls){
  11029. this.el.removeCls(this.pressedCls);
  11030. }
  11031. Ext.getDoc().un('mouseup', this.handleMouseUp, this);
  11032. this.el.removeAllListeners();
  11033. }
  11034. this.disabled = true;
  11035. },
  11036. /**
  11037. * Convenience function for setting disabled/enabled by boolean.
  11038. * @param {Boolean} disabled
  11039. */
  11040. setDisabled: function(disabled){
  11041. this[disabled ? 'disable' : 'enable']();
  11042. },
  11043. eventOptions: function(e){
  11044. if(this.preventDefault){
  11045. e.preventDefault();
  11046. }
  11047. if(this.stopDefault){
  11048. e.stopEvent();
  11049. }
  11050. },
  11051. // private
  11052. destroy : function() {
  11053. this.disable(true);
  11054. Ext.destroy(this.el);
  11055. this.clearListeners();
  11056. },
  11057. handleDblClick : function(e){
  11058. clearTimeout(this.timer);
  11059. this.el.blur();
  11060. this.fireEvent("mousedown", this, e);
  11061. this.fireEvent("click", this, e);
  11062. },
  11063. // private
  11064. handleMouseDown : function(e){
  11065. clearTimeout(this.timer);
  11066. this.el.blur();
  11067. if(this.pressedCls){
  11068. this.el.addCls(this.pressedCls);
  11069. }
  11070. this.mousedownTime = new Date();
  11071. Ext.getDoc().on("mouseup", this.handleMouseUp, this);
  11072. this.el.on("mouseout", this.handleMouseOut, this);
  11073. this.fireEvent("mousedown", this, e);
  11074. this.fireEvent("click", this, e);
  11075. // Do not honor delay or interval if acceleration wanted.
  11076. if (this.accelerate) {
  11077. this.delay = 400;
  11078. }
  11079. // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
  11080. // the global shared EventObject gets a new Event put into it before the timer fires.
  11081. e = new Ext.EventObjectImpl(e);
  11082. this.timer = Ext.defer(this.click, this.delay || this.interval, this, [e]);
  11083. },
  11084. // private
  11085. click : function(e){
  11086. this.fireEvent("click", this, e);
  11087. this.timer = Ext.defer(this.click, this.accelerate ?
  11088. this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
  11089. 400,
  11090. -390,
  11091. 12000) :
  11092. this.interval, this, [e]);
  11093. },
  11094. easeOutExpo : function (t, b, c, d) {
  11095. return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
  11096. },
  11097. // private
  11098. handleMouseOut : function(){
  11099. clearTimeout(this.timer);
  11100. if(this.pressedCls){
  11101. this.el.removeCls(this.pressedCls);
  11102. }
  11103. this.el.on("mouseover", this.handleMouseReturn, this);
  11104. },
  11105. // private
  11106. handleMouseReturn : function(){
  11107. this.el.un("mouseover", this.handleMouseReturn, this);
  11108. if(this.pressedCls){
  11109. this.el.addCls(this.pressedCls);
  11110. }
  11111. this.click();
  11112. },
  11113. // private
  11114. handleMouseUp : function(e){
  11115. clearTimeout(this.timer);
  11116. this.el.un("mouseover", this.handleMouseReturn, this);
  11117. this.el.un("mouseout", this.handleMouseOut, this);
  11118. Ext.getDoc().un("mouseup", this.handleMouseUp, this);
  11119. if(this.pressedCls){
  11120. this.el.removeCls(this.pressedCls);
  11121. }
  11122. this.fireEvent("mouseup", this, e);
  11123. }
  11124. });
  11125. /**
  11126. * @class Ext.layout.component.Component
  11127. * @extends Ext.layout.Layout
  11128. *
  11129. * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
  11130. * configuration property. See {@link Ext.Component#componentLayout} for additional details.
  11131. *
  11132. * @private
  11133. */
  11134. Ext.define('Ext.layout.component.Component', {
  11135. /* Begin Definitions */
  11136. extend: 'Ext.layout.Layout',
  11137. /* End Definitions */
  11138. type: 'component',
  11139. monitorChildren: true,
  11140. initLayout : function() {
  11141. var me = this,
  11142. owner = me.owner,
  11143. ownerEl = owner.el;
  11144. if (!me.initialized) {
  11145. if (owner.frameSize) {
  11146. me.frameSize = owner.frameSize;
  11147. }
  11148. else {
  11149. owner.frameSize = me.frameSize = {
  11150. top: 0,
  11151. left: 0,
  11152. bottom: 0,
  11153. right: 0
  11154. };
  11155. }
  11156. }
  11157. me.callParent(arguments);
  11158. },
  11159. beforeLayout : function(width, height, isSetSize, callingContainer) {
  11160. this.callParent(arguments);
  11161. var me = this,
  11162. owner = me.owner,
  11163. ownerCt = owner.ownerCt,
  11164. layout = owner.layout,
  11165. isVisible = owner.isVisible(true),
  11166. ownerElChild = owner.el.child,
  11167. layoutCollection;
  11168. // Cache the size we began with so we can see if there has been any effect.
  11169. me.previousComponentSize = me.lastComponentSize;
  11170. // Do not allow autoing of any dimensions which are fixed
  11171. if (!isSetSize
  11172. && ((!Ext.isNumber(width) && owner.isFixedWidth()) ||
  11173. (!Ext.isNumber(height) && owner.isFixedHeight()))
  11174. // unless we are being told to do so by the ownerCt's layout
  11175. && callingContainer && callingContainer !== ownerCt) {
  11176. me.doContainerLayout();
  11177. return false;
  11178. }
  11179. // If an ownerCt is hidden, add my reference onto the layoutOnShow stack. Set the needsLayout flag.
  11180. // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
  11181. if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
  11182. if (owner.hiddenAncestor) {
  11183. layoutCollection = owner.hiddenAncestor.layoutOnShow;
  11184. layoutCollection.remove(owner);
  11185. layoutCollection.add(owner);
  11186. }
  11187. owner.needsLayout = {
  11188. width: width,
  11189. height: height,
  11190. isSetSize: false
  11191. };
  11192. }
  11193. if (isVisible && this.needsLayout(width, height)) {
  11194. return owner.beforeComponentLayout(width, height, isSetSize, callingContainer);
  11195. }
  11196. else {
  11197. return false;
  11198. }
  11199. },
  11200. /**
  11201. * Check if the new size is different from the current size and only
  11202. * trigger a layout if it is necessary.
  11203. * @param {Number} width The new width to set.
  11204. * @param {Number} height The new height to set.
  11205. */
  11206. needsLayout : function(width, height) {
  11207. var me = this,
  11208. widthBeingChanged,
  11209. heightBeingChanged;
  11210. me.lastComponentSize = me.lastComponentSize || {
  11211. width: -Infinity,
  11212. height: -Infinity
  11213. };
  11214. // If autoWidthing, or an explicitly different width is passed, then the width is being changed.
  11215. widthBeingChanged = !Ext.isDefined(width) || me.lastComponentSize.width !== width;
  11216. // If autoHeighting, or an explicitly different height is passed, then the height is being changed.
  11217. heightBeingChanged = !Ext.isDefined(height) || me.lastComponentSize.height !== height;
  11218. // isSizing flag added to prevent redundant layouts when going up the layout chain
  11219. return !me.isSizing && (me.childrenChanged || widthBeingChanged || heightBeingChanged);
  11220. },
  11221. /**
  11222. * Set the size of any element supporting undefined, null, and values.
  11223. * @param {Number} width The new width to set.
  11224. * @param {Number} height The new height to set.
  11225. */
  11226. setElementSize: function(el, width, height) {
  11227. if (width !== undefined && height !== undefined) {
  11228. el.setSize(width, height);
  11229. }
  11230. else if (height !== undefined) {
  11231. el.setHeight(height);
  11232. }
  11233. else if (width !== undefined) {
  11234. el.setWidth(width);
  11235. }
  11236. },
  11237. /**
  11238. * Returns the owner component's resize element.
  11239. * @return {Ext.Element}
  11240. */
  11241. getTarget : function() {
  11242. return this.owner.el;
  11243. },
  11244. /**
  11245. * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
  11246. * May be overridden in Component layout managers which implement an inner element.
  11247. * @return {Ext.Element}
  11248. */
  11249. getRenderTarget : function() {
  11250. return this.owner.el;
  11251. },
  11252. /**
  11253. * Set the size of the target element.
  11254. * @param {Number} width The new width to set.
  11255. * @param {Number} height The new height to set.
  11256. */
  11257. setTargetSize : function(width, height) {
  11258. var me = this;
  11259. me.setElementSize(me.owner.el, width, height);
  11260. if (me.owner.frameBody) {
  11261. var targetInfo = me.getTargetInfo(),
  11262. padding = targetInfo.padding,
  11263. border = targetInfo.border,
  11264. frameSize = me.frameSize;
  11265. me.setElementSize(me.owner.frameBody,
  11266. Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
  11267. Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
  11268. );
  11269. }
  11270. me.autoSized = {
  11271. width: !Ext.isNumber(width),
  11272. height: !Ext.isNumber(height)
  11273. };
  11274. me.lastComponentSize = {
  11275. width: width,
  11276. height: height
  11277. };
  11278. },
  11279. getTargetInfo : function() {
  11280. if (!this.targetInfo) {
  11281. var target = this.getTarget(),
  11282. body = this.owner.getTargetEl();
  11283. this.targetInfo = {
  11284. padding: {
  11285. top: target.getPadding('t'),
  11286. right: target.getPadding('r'),
  11287. bottom: target.getPadding('b'),
  11288. left: target.getPadding('l')
  11289. },
  11290. border: {
  11291. top: target.getBorderWidth('t'),
  11292. right: target.getBorderWidth('r'),
  11293. bottom: target.getBorderWidth('b'),
  11294. left: target.getBorderWidth('l')
  11295. },
  11296. bodyMargin: {
  11297. top: body.getMargin('t'),
  11298. right: body.getMargin('r'),
  11299. bottom: body.getMargin('b'),
  11300. left: body.getMargin('l')
  11301. }
  11302. };
  11303. }
  11304. return this.targetInfo;
  11305. },
  11306. // Start laying out UP the ownerCt's layout when flagged to do so.
  11307. doOwnerCtLayouts: function() {
  11308. var owner = this.owner,
  11309. ownerCt = owner.ownerCt,
  11310. ownerCtComponentLayout, ownerCtContainerLayout,
  11311. curSize = this.lastComponentSize,
  11312. prevSize = this.previousComponentSize,
  11313. widthChange = (prevSize && curSize && Ext.isNumber(curSize.width )) ? curSize.width !== prevSize.width : true,
  11314. heightChange = (prevSize && curSize && Ext.isNumber(curSize.height)) ? curSize.height !== prevSize.height : true;
  11315. // If size has not changed, do not inform upstream layouts
  11316. if (!ownerCt || (!widthChange && !heightChange)) {
  11317. return;
  11318. }
  11319. ownerCtComponentLayout = ownerCt.componentLayout;
  11320. ownerCtContainerLayout = ownerCt.layout;
  11321. if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
  11322. if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
  11323. // If the owning Container may be adjusted in any of the the dimension which have changed, perform its Component layout
  11324. if (((widthChange && !ownerCt.isFixedWidth()) || (heightChange && !ownerCt.isFixedHeight()))) {
  11325. // Set the isSizing flag so that the upstream Container layout (called after a Component layout) can omit this component from sizing operations
  11326. this.isSizing = true;
  11327. ownerCt.doComponentLayout();
  11328. this.isSizing = false;
  11329. }
  11330. // Execute upstream Container layout
  11331. else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
  11332. ownerCtContainerLayout.layout();
  11333. }
  11334. }
  11335. }
  11336. },
  11337. doContainerLayout: function() {
  11338. var me = this,
  11339. owner = me.owner,
  11340. ownerCt = owner.ownerCt,
  11341. layout = owner.layout,
  11342. ownerCtComponentLayout;
  11343. // Run the container layout if it exists (layout for child items)
  11344. // **Unless automatic laying out is suspended, or the layout is currently running**
  11345. if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy && !layout.isAutoDock) {
  11346. layout.layout();
  11347. }
  11348. // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
  11349. if (ownerCt && ownerCt.componentLayout) {
  11350. ownerCtComponentLayout = ownerCt.componentLayout;
  11351. if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
  11352. ownerCtComponentLayout.childrenChanged = true;
  11353. }
  11354. }
  11355. },
  11356. afterLayout : function(width, height, isSetSize, layoutOwner) {
  11357. this.doContainerLayout();
  11358. this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
  11359. }
  11360. });
  11361. /**
  11362. * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
  11363. * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
  11364. * should not contain any HTML, otherwise it may not be measured correctly.
  11365. *
  11366. * The measurement works by copying the relevant CSS styles that can affect the font related display,
  11367. * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must
  11368. * provide a **fixed width** when doing the measurement.
  11369. *
  11370. * If multiple measurements are being done on the same element, you create a new instance to initialize
  11371. * to avoid the overhead of copying the styles to the element repeatedly.
  11372. */
  11373. Ext.define('Ext.util.TextMetrics', {
  11374. statics: {
  11375. shared: null,
  11376. /**
  11377. * Measures the size of the specified text
  11378. * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
  11379. * that can affect the size of the rendered text
  11380. * @param {String} text The text to measure
  11381. * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
  11382. * in order to accurately measure the text height
  11383. * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
  11384. */
  11385. measure: function(el, text, fixedWidth){
  11386. var me = this,
  11387. shared = me.shared;
  11388. if(!shared){
  11389. shared = me.shared = new me(el, fixedWidth);
  11390. }
  11391. shared.bind(el);
  11392. shared.setFixedWidth(fixedWidth || 'auto');
  11393. return shared.getSize(text);
  11394. },
  11395. /**
  11396. * Destroy the TextMetrics instance created by {@link #measure}.
  11397. */
  11398. destroy: function(){
  11399. var me = this;
  11400. Ext.destroy(me.shared);
  11401. me.shared = null;
  11402. }
  11403. },
  11404. /**
  11405. * Creates new TextMetrics.
  11406. * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
  11407. * @param {Number} fixedWidth (optional) A fixed width to apply to the measuring element.
  11408. */
  11409. constructor: function(bindTo, fixedWidth){
  11410. var measure = this.measure = Ext.getBody().createChild({
  11411. cls: 'x-textmetrics'
  11412. });
  11413. this.el = Ext.get(bindTo);
  11414. measure.position('absolute');
  11415. measure.setLeftTop(-1000, -1000);
  11416. measure.hide();
  11417. if (fixedWidth) {
  11418. measure.setWidth(fixedWidth);
  11419. }
  11420. },
  11421. /**
  11422. * Returns the size of the specified text based on the internal element's style and width properties
  11423. * @param {String} text The text to measure
  11424. * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
  11425. */
  11426. getSize: function(text){
  11427. var measure = this.measure,
  11428. size;
  11429. measure.update(text);
  11430. size = measure.getSize();
  11431. measure.update('');
  11432. return size;
  11433. },
  11434. /**
  11435. * Binds this TextMetrics instance to a new element
  11436. * @param {String/HTMLElement/Ext.Element} el The element or its ID.
  11437. */
  11438. bind: function(el){
  11439. var me = this;
  11440. me.el = Ext.get(el);
  11441. me.measure.setStyle(
  11442. me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
  11443. );
  11444. },
  11445. /**
  11446. * Sets a fixed width on the internal measurement element. If the text will be multiline, you have
  11447. * to set a fixed width in order to accurately measure the text height.
  11448. * @param {Number} width The width to set on the element
  11449. */
  11450. setFixedWidth : function(width){
  11451. this.measure.setWidth(width);
  11452. },
  11453. /**
  11454. * Returns the measured width of the specified text
  11455. * @param {String} text The text to measure
  11456. * @return {Number} width The width in pixels
  11457. */
  11458. getWidth : function(text){
  11459. this.measure.dom.style.width = 'auto';
  11460. return this.getSize(text).width;
  11461. },
  11462. /**
  11463. * Returns the measured height of the specified text
  11464. * @param {String} text The text to measure
  11465. * @return {Number} height The height in pixels
  11466. */
  11467. getHeight : function(text){
  11468. return this.getSize(text).height;
  11469. },
  11470. /**
  11471. * Destroy this instance
  11472. */
  11473. destroy: function(){
  11474. var me = this;
  11475. me.measure.remove();
  11476. delete me.el;
  11477. delete me.measure;
  11478. }
  11479. }, function(){
  11480. Ext.Element.addMethods({
  11481. /**
  11482. * Returns the width in pixels of the passed text, or the width of the text in this Element.
  11483. * @param {String} text The text to measure. Defaults to the innerHTML of the element.
  11484. * @param {Number} min (optional) The minumum value to return.
  11485. * @param {Number} max (optional) The maximum value to return.
  11486. * @return {Number} The text width in pixels.
  11487. * @member Ext.Element
  11488. */
  11489. getTextWidth : function(text, min, max){
  11490. return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
  11491. }
  11492. });
  11493. });
  11494. /**
  11495. * @class Ext.layout.container.boxOverflow.Scroller
  11496. * @extends Ext.layout.container.boxOverflow.None
  11497. * @private
  11498. */
  11499. Ext.define('Ext.layout.container.boxOverflow.Scroller', {
  11500. /* Begin Definitions */
  11501. extend: 'Ext.layout.container.boxOverflow.None',
  11502. requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
  11503. alternateClassName: 'Ext.layout.boxOverflow.Scroller',
  11504. mixins: {
  11505. observable: 'Ext.util.Observable'
  11506. },
  11507. /* End Definitions */
  11508. /**
  11509. * @cfg {Boolean} animateScroll
  11510. * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
  11511. */
  11512. animateScroll: false,
  11513. /**
  11514. * @cfg {Number} scrollIncrement
  11515. * The number of pixels to scroll by on scroller click
  11516. */
  11517. scrollIncrement: 20,
  11518. /**
  11519. * @cfg {Number} wheelIncrement
  11520. * The number of pixels to increment on mouse wheel scrolling.
  11521. */
  11522. wheelIncrement: 10,
  11523. /**
  11524. * @cfg {Number} scrollRepeatInterval
  11525. * Number of milliseconds between each scroll while a scroller button is held down
  11526. */
  11527. scrollRepeatInterval: 60,
  11528. /**
  11529. * @cfg {Number} scrollDuration
  11530. * Number of milliseconds that each scroll animation lasts
  11531. */
  11532. scrollDuration: 400,
  11533. /**
  11534. * @cfg {String} beforeCtCls
  11535. * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
  11536. * which must always be present at the leftmost edge of the Container
  11537. */
  11538. /**
  11539. * @cfg {String} afterCtCls
  11540. * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
  11541. * which must always be present at the rightmost edge of the Container
  11542. */
  11543. /**
  11544. * @cfg {String} [scrollerCls='x-box-scroller']
  11545. * CSS class added to both scroller elements if enableScroll is used
  11546. */
  11547. scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
  11548. /**
  11549. * @cfg {String} beforeScrollerCls
  11550. * CSS class added to the left scroller element if enableScroll is used
  11551. */
  11552. /**
  11553. * @cfg {String} afterScrollerCls
  11554. * CSS class added to the right scroller element if enableScroll is used
  11555. */
  11556. constructor: function(layout, config) {
  11557. this.layout = layout;
  11558. Ext.apply(this, config || {});
  11559. this.addEvents(
  11560. /**
  11561. * @event scroll
  11562. * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
  11563. * @param {Number} newPosition The new position of the scroller
  11564. * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
  11565. */
  11566. 'scroll'
  11567. );
  11568. },
  11569. initCSSClasses: function() {
  11570. var me = this,
  11571. layout = me.layout;
  11572. if (!me.CSSinitialized) {
  11573. me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
  11574. me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
  11575. me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
  11576. me.afterScrollerCls = me.afterScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
  11577. me.CSSinitializes = true;
  11578. }
  11579. },
  11580. handleOverflow: function(calculations, targetSize) {
  11581. var me = this,
  11582. layout = me.layout,
  11583. methodName = 'get' + layout.parallelPrefixCap,
  11584. newSize = {};
  11585. me.initCSSClasses();
  11586. me.callParent(arguments);
  11587. this.createInnerElements();
  11588. this.showScrollers();
  11589. newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
  11590. newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
  11591. return { targetSize: newSize };
  11592. },
  11593. /**
  11594. * @private
  11595. * Creates the beforeCt and afterCt elements if they have not already been created
  11596. */
  11597. createInnerElements: function() {
  11598. var me = this,
  11599. target = me.layout.getRenderTarget();
  11600. //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
  11601. //special items such as scrollers or dropdown menu triggers
  11602. if (!me.beforeCt) {
  11603. target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
  11604. me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
  11605. me.afterCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls}, 'after');
  11606. me.createWheelListener();
  11607. }
  11608. },
  11609. /**
  11610. * @private
  11611. * Sets up an listener to scroll on the layout's innerCt mousewheel event
  11612. */
  11613. createWheelListener: function() {
  11614. this.layout.innerCt.on({
  11615. scope : this,
  11616. mousewheel: function(e) {
  11617. e.stopEvent();
  11618. this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
  11619. }
  11620. });
  11621. },
  11622. /**
  11623. * @private
  11624. */
  11625. clearOverflow: function() {
  11626. this.hideScrollers();
  11627. },
  11628. /**
  11629. * @private
  11630. * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
  11631. * present.
  11632. */
  11633. showScrollers: function() {
  11634. this.createScrollers();
  11635. this.beforeScroller.show();
  11636. this.afterScroller.show();
  11637. this.updateScrollButtons();
  11638. this.layout.owner.addClsWithUI('scroller');
  11639. },
  11640. /**
  11641. * @private
  11642. * Hides the scroller elements in the beforeCt and afterCt
  11643. */
  11644. hideScrollers: function() {
  11645. if (this.beforeScroller != undefined) {
  11646. this.beforeScroller.hide();
  11647. this.afterScroller.hide();
  11648. this.layout.owner.removeClsWithUI('scroller');
  11649. }
  11650. },
  11651. /**
  11652. * @private
  11653. * Creates the clickable scroller elements and places them into the beforeCt and afterCt
  11654. */
  11655. createScrollers: function() {
  11656. if (!this.beforeScroller && !this.afterScroller) {
  11657. var before = this.beforeCt.createChild({
  11658. cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
  11659. });
  11660. var after = this.afterCt.createChild({
  11661. cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
  11662. });
  11663. before.addClsOnOver(this.beforeScrollerCls + '-hover');
  11664. after.addClsOnOver(this.afterScrollerCls + '-hover');
  11665. before.setVisibilityMode(Ext.Element.DISPLAY);
  11666. after.setVisibilityMode(Ext.Element.DISPLAY);
  11667. this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
  11668. interval: this.scrollRepeatInterval,
  11669. handler : this.scrollLeft,
  11670. scope : this
  11671. });
  11672. this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
  11673. interval: this.scrollRepeatInterval,
  11674. handler : this.scrollRight,
  11675. scope : this
  11676. });
  11677. /**
  11678. * @property beforeScroller
  11679. * @type Ext.Element
  11680. * The left scroller element. Only created when needed.
  11681. */
  11682. this.beforeScroller = before;
  11683. /**
  11684. * @property afterScroller
  11685. * @type Ext.Element
  11686. * The left scroller element. Only created when needed.
  11687. */
  11688. this.afterScroller = after;
  11689. }
  11690. },
  11691. /**
  11692. * @private
  11693. */
  11694. destroy: function() {
  11695. Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
  11696. },
  11697. /**
  11698. * @private
  11699. * Scrolls left or right by the number of pixels specified
  11700. * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
  11701. */
  11702. scrollBy: function(delta, animate) {
  11703. this.scrollTo(this.getScrollPosition() + delta, animate);
  11704. },
  11705. /**
  11706. * @private
  11707. * @return {Object} Object passed to scrollTo when scrolling
  11708. */
  11709. getScrollAnim: function() {
  11710. return {
  11711. duration: this.scrollDuration,
  11712. callback: this.updateScrollButtons,
  11713. scope : this
  11714. };
  11715. },
  11716. /**
  11717. * @private
  11718. * Enables or disables each scroller button based on the current scroll position
  11719. */
  11720. updateScrollButtons: function() {
  11721. if (this.beforeScroller == undefined || this.afterScroller == undefined) {
  11722. return;
  11723. }
  11724. var beforeMeth = this.atExtremeBefore() ? 'addCls' : 'removeCls',
  11725. afterMeth = this.atExtremeAfter() ? 'addCls' : 'removeCls',
  11726. beforeCls = this.beforeScrollerCls + '-disabled',
  11727. afterCls = this.afterScrollerCls + '-disabled';
  11728. this.beforeScroller[beforeMeth](beforeCls);
  11729. this.afterScroller[afterMeth](afterCls);
  11730. this.scrolling = false;
  11731. },
  11732. /**
  11733. * @private
  11734. * Returns true if the innerCt scroll is already at its left-most point
  11735. * @return {Boolean} True if already at furthest left point
  11736. */
  11737. atExtremeBefore: function() {
  11738. return this.getScrollPosition() === 0;
  11739. },
  11740. /**
  11741. * @private
  11742. * Scrolls to the left by the configured amount
  11743. */
  11744. scrollLeft: function() {
  11745. this.scrollBy(-this.scrollIncrement, false);
  11746. },
  11747. /**
  11748. * @private
  11749. * Scrolls to the right by the configured amount
  11750. */
  11751. scrollRight: function() {
  11752. this.scrollBy(this.scrollIncrement, false);
  11753. },
  11754. /**
  11755. * Returns the current scroll position of the innerCt element
  11756. * @return {Number} The current scroll position
  11757. */
  11758. getScrollPosition: function(){
  11759. var layout = this.layout;
  11760. return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
  11761. },
  11762. /**
  11763. * @private
  11764. * Returns the maximum value we can scrollTo
  11765. * @return {Number} The max scroll value
  11766. */
  11767. getMaxScrollPosition: function() {
  11768. var layout = this.layout;
  11769. return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
  11770. },
  11771. /**
  11772. * @private
  11773. * Returns true if the innerCt scroll is already at its right-most point
  11774. * @return {Boolean} True if already at furthest right point
  11775. */
  11776. atExtremeAfter: function() {
  11777. return this.getScrollPosition() >= this.getMaxScrollPosition();
  11778. },
  11779. /**
  11780. * @private
  11781. * Scrolls to the given position. Performs bounds checking.
  11782. * @param {Number} position The position to scroll to. This is constrained.
  11783. * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
  11784. */
  11785. scrollTo: function(position, animate) {
  11786. var me = this,
  11787. layout = me.layout,
  11788. oldPosition = me.getScrollPosition(),
  11789. newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
  11790. if (newPosition != oldPosition && !me.scrolling) {
  11791. if (animate == undefined) {
  11792. animate = me.animateScroll;
  11793. }
  11794. layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
  11795. if (animate) {
  11796. me.scrolling = true;
  11797. } else {
  11798. me.scrolling = false;
  11799. me.updateScrollButtons();
  11800. }
  11801. me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
  11802. }
  11803. },
  11804. /**
  11805. * Scrolls to the given component.
  11806. * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id
  11807. * or a reference to the component itself.
  11808. * @param {Boolean} animate True to animate the scrolling
  11809. */
  11810. scrollToItem: function(item, animate) {
  11811. var me = this,
  11812. layout = me.layout,
  11813. visibility,
  11814. box,
  11815. newPos;
  11816. item = me.getItem(item);
  11817. if (item != undefined) {
  11818. visibility = this.getItemVisibility(item);
  11819. if (!visibility.fullyVisible) {
  11820. box = item.getBox(true, true);
  11821. newPos = box[layout.parallelPosition];
  11822. if (visibility.hiddenEnd) {
  11823. newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
  11824. }
  11825. this.scrollTo(newPos, animate);
  11826. }
  11827. }
  11828. },
  11829. /**
  11830. * @private
  11831. * For a given item in the container, return an object with information on whether the item is visible
  11832. * with the current innerCt scroll value.
  11833. * @param {Ext.Component} item The item
  11834. * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
  11835. */
  11836. getItemVisibility: function(item) {
  11837. var me = this,
  11838. box = me.getItem(item).getBox(true, true),
  11839. layout = me.layout,
  11840. itemStart = box[layout.parallelPosition],
  11841. itemEnd = itemStart + box[layout.parallelPrefix],
  11842. scrollStart = me.getScrollPosition(),
  11843. scrollEnd = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
  11844. return {
  11845. hiddenStart : itemStart < scrollStart,
  11846. hiddenEnd : itemEnd > scrollEnd,
  11847. fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
  11848. };
  11849. }
  11850. });
  11851. /**
  11852. * @class Ext.util.Offset
  11853. * @ignore
  11854. */
  11855. Ext.define('Ext.util.Offset', {
  11856. /* Begin Definitions */
  11857. statics: {
  11858. fromObject: function(obj) {
  11859. return new this(obj.x, obj.y);
  11860. }
  11861. },
  11862. /* End Definitions */
  11863. constructor: function(x, y) {
  11864. this.x = (x != null && !isNaN(x)) ? x : 0;
  11865. this.y = (y != null && !isNaN(y)) ? y : 0;
  11866. return this;
  11867. },
  11868. copy: function() {
  11869. return new Ext.util.Offset(this.x, this.y);
  11870. },
  11871. copyFrom: function(p) {
  11872. this.x = p.x;
  11873. this.y = p.y;
  11874. },
  11875. toString: function() {
  11876. return "Offset[" + this.x + "," + this.y + "]";
  11877. },
  11878. equals: function(offset) {
  11879. //<debug>
  11880. if(!(offset instanceof this.statics())) {
  11881. Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
  11882. }
  11883. //</debug>
  11884. return (this.x == offset.x && this.y == offset.y);
  11885. },
  11886. round: function(to) {
  11887. if (!isNaN(to)) {
  11888. var factor = Math.pow(10, to);
  11889. this.x = Math.round(this.x * factor) / factor;
  11890. this.y = Math.round(this.y * factor) / factor;
  11891. } else {
  11892. this.x = Math.round(this.x);
  11893. this.y = Math.round(this.y);
  11894. }
  11895. },
  11896. isZero: function() {
  11897. return this.x == 0 && this.y == 0;
  11898. }
  11899. });
  11900. /**
  11901. * @class Ext.util.KeyNav
  11902. * <p>Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind
  11903. * navigation keys to function calls that will get called when the keys are pressed, providing an easy
  11904. * way to implement custom navigation schemes for any UI component.</p>
  11905. * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
  11906. * pageUp, pageDown, del, backspace, home, end. Usage:</p>
  11907. <pre><code>
  11908. var nav = new Ext.util.KeyNav("my-element", {
  11909. "left" : function(e){
  11910. this.moveLeft(e.ctrlKey);
  11911. },
  11912. "right" : function(e){
  11913. this.moveRight(e.ctrlKey);
  11914. },
  11915. "enter" : function(e){
  11916. this.save();
  11917. },
  11918. scope : this
  11919. });
  11920. </code></pre>
  11921. */
  11922. Ext.define('Ext.util.KeyNav', {
  11923. alternateClassName: 'Ext.KeyNav',
  11924. requires: ['Ext.util.KeyMap'],
  11925. statics: {
  11926. keyOptions: {
  11927. left: 37,
  11928. right: 39,
  11929. up: 38,
  11930. down: 40,
  11931. space: 32,
  11932. pageUp: 33,
  11933. pageDown: 34,
  11934. del: 46,
  11935. backspace: 8,
  11936. home: 36,
  11937. end: 35,
  11938. enter: 13,
  11939. esc: 27,
  11940. tab: 9
  11941. }
  11942. },
  11943. /**
  11944. * Creates new KeyNav.
  11945. * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
  11946. * @param {Object} config The config
  11947. */
  11948. constructor: function(el, config){
  11949. this.setConfig(el, config || {});
  11950. },
  11951. /**
  11952. * Sets up a configuration for the KeyNav.
  11953. * @private
  11954. * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
  11955. * @param {Object} config A configuration object as specified in the constructor.
  11956. */
  11957. setConfig: function(el, config) {
  11958. if (this.map) {
  11959. this.map.destroy();
  11960. }
  11961. var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
  11962. keys = Ext.util.KeyNav.keyOptions,
  11963. scope = config.scope || this,
  11964. key;
  11965. this.map = map;
  11966. for (key in keys) {
  11967. if (keys.hasOwnProperty(key)) {
  11968. if (config[key]) {
  11969. map.addBinding({
  11970. scope: scope,
  11971. key: keys[key],
  11972. handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
  11973. defaultEventAction: config.defaultEventAction || this.defaultEventAction
  11974. });
  11975. }
  11976. }
  11977. }
  11978. map.disable();
  11979. if (!config.disabled) {
  11980. map.enable();
  11981. }
  11982. },
  11983. /**
  11984. * Method for filtering out the map argument
  11985. * @private
  11986. * @param {Ext.util.KeyMap} map
  11987. * @param {Ext.EventObject} event
  11988. * @param {Object} options Contains the handler to call
  11989. */
  11990. handleEvent: function(map, event, handler){
  11991. return handler.call(this, event);
  11992. },
  11993. /**
  11994. * @cfg {Boolean} disabled
  11995. * True to disable this KeyNav instance.
  11996. */
  11997. disabled: false,
  11998. /**
  11999. * @cfg {String} defaultEventAction
  12000. * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are
  12001. * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
  12002. * {@link Ext.EventObject#stopPropagation}.
  12003. */
  12004. defaultEventAction: "stopEvent",
  12005. /**
  12006. * @cfg {Boolean} forceKeyDown
  12007. * Handle the keydown event instead of keypress. KeyNav automatically does this for IE since
  12008. * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
  12009. * handle keydown instead of keypress.
  12010. */
  12011. forceKeyDown: false,
  12012. /**
  12013. * Destroy this KeyNav (this is the same as calling disable).
  12014. * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
  12015. */
  12016. destroy: function(removeEl){
  12017. this.map.destroy(removeEl);
  12018. delete this.map;
  12019. },
  12020. /**
  12021. * Enable this KeyNav
  12022. */
  12023. enable: function() {
  12024. this.map.enable();
  12025. this.disabled = false;
  12026. },
  12027. /**
  12028. * Disable this KeyNav
  12029. */
  12030. disable: function() {
  12031. this.map.disable();
  12032. this.disabled = true;
  12033. },
  12034. /**
  12035. * Convenience function for setting disabled/enabled by boolean.
  12036. * @param {Boolean} disabled
  12037. */
  12038. setDisabled : function(disabled){
  12039. this.map.setDisabled(disabled);
  12040. this.disabled = disabled;
  12041. },
  12042. /**
  12043. * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
  12044. * as well as the useKeyDown option on the EventManager.
  12045. * @return {String} The type of event to listen for.
  12046. */
  12047. getKeyEvent: function(forceKeyDown){
  12048. return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
  12049. }
  12050. });
  12051. /**
  12052. * @class Ext.fx.Queue
  12053. * Animation Queue mixin to handle chaining and queueing by target.
  12054. * @private
  12055. */
  12056. Ext.define('Ext.fx.Queue', {
  12057. requires: ['Ext.util.HashMap'],
  12058. constructor: function() {
  12059. this.targets = Ext.create('Ext.util.HashMap');
  12060. this.fxQueue = {};
  12061. },
  12062. // @private
  12063. getFxDefaults: function(targetId) {
  12064. var target = this.targets.get(targetId);
  12065. if (target) {
  12066. return target.fxDefaults;
  12067. }
  12068. return {};
  12069. },
  12070. // @private
  12071. setFxDefaults: function(targetId, obj) {
  12072. var target = this.targets.get(targetId);
  12073. if (target) {
  12074. target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
  12075. }
  12076. },
  12077. // @private
  12078. stopAnimation: function(targetId) {
  12079. var me = this,
  12080. queue = me.getFxQueue(targetId),
  12081. ln = queue.length;
  12082. while (ln) {
  12083. queue[ln - 1].end();
  12084. ln--;
  12085. }
  12086. },
  12087. /**
  12088. * @private
  12089. * Returns current animation object if the element has any effects actively running or queued, else returns false.
  12090. */
  12091. getActiveAnimation: function(targetId) {
  12092. var queue = this.getFxQueue(targetId);
  12093. return (queue && !!queue.length) ? queue[0] : false;
  12094. },
  12095. // @private
  12096. hasFxBlock: function(targetId) {
  12097. var queue = this.getFxQueue(targetId);
  12098. return queue && queue[0] && queue[0].block;
  12099. },
  12100. // @private get fx queue for passed target, create if needed.
  12101. getFxQueue: function(targetId) {
  12102. if (!targetId) {
  12103. return false;
  12104. }
  12105. var me = this,
  12106. queue = me.fxQueue[targetId],
  12107. target = me.targets.get(targetId);
  12108. if (!target) {
  12109. return false;
  12110. }
  12111. if (!queue) {
  12112. me.fxQueue[targetId] = [];
  12113. // GarbageCollector will need to clean up Elements since they aren't currently observable
  12114. if (target.type != 'element') {
  12115. target.target.on('destroy', function() {
  12116. me.fxQueue[targetId] = [];
  12117. });
  12118. }
  12119. }
  12120. return me.fxQueue[targetId];
  12121. },
  12122. // @private
  12123. queueFx: function(anim) {
  12124. var me = this,
  12125. target = anim.target,
  12126. queue, ln;
  12127. if (!target) {
  12128. return;
  12129. }
  12130. queue = me.getFxQueue(target.getId());
  12131. ln = queue.length;
  12132. if (ln) {
  12133. if (anim.concurrent) {
  12134. anim.paused = false;
  12135. }
  12136. else {
  12137. queue[ln - 1].on('afteranimate', function() {
  12138. anim.paused = false;
  12139. });
  12140. }
  12141. }
  12142. else {
  12143. anim.paused = false;
  12144. }
  12145. anim.on('afteranimate', function() {
  12146. Ext.Array.remove(queue, anim);
  12147. if (anim.remove) {
  12148. if (target.type == 'element') {
  12149. var el = Ext.get(target.id);
  12150. if (el) {
  12151. el.remove();
  12152. }
  12153. }
  12154. }
  12155. }, this);
  12156. queue.push(anim);
  12157. }
  12158. });
  12159. /**
  12160. * @class Ext.fx.target.Target
  12161. This class specifies a generic target for an animation. It provides a wrapper around a
  12162. series of different types of objects to allow for a generic animation API.
  12163. A target can be a single object or a Composite object containing other objects that are
  12164. to be animated. This class and it's subclasses are generally not created directly, the
  12165. underlying animation will create the appropriate Ext.fx.target.Target object by passing
  12166. the instance to be animated.
  12167. The following types of objects can be animated:
  12168. - {@link Ext.fx.target.Component Components}
  12169. - {@link Ext.fx.target.Element Elements}
  12170. - {@link Ext.fx.target.Sprite Sprites}
  12171. * @markdown
  12172. * @abstract
  12173. */
  12174. Ext.define('Ext.fx.target.Target', {
  12175. isAnimTarget: true,
  12176. /**
  12177. * Creates new Target.
  12178. * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
  12179. */
  12180. constructor: function(target) {
  12181. this.target = target;
  12182. this.id = this.getId();
  12183. },
  12184. getId: function() {
  12185. return this.target.id;
  12186. }
  12187. });
  12188. /**
  12189. * @class Ext.fx.target.Sprite
  12190. * @extends Ext.fx.target.Target
  12191. This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
  12192. created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
  12193. and the appropriate target will be created.
  12194. * @markdown
  12195. */
  12196. Ext.define('Ext.fx.target.Sprite', {
  12197. /* Begin Definitions */
  12198. extend: 'Ext.fx.target.Target',
  12199. /* End Definitions */
  12200. type: 'draw',
  12201. getFromPrim: function(sprite, attr) {
  12202. var o;
  12203. if (attr == 'translate') {
  12204. o = {
  12205. x: sprite.attr.translation.x || 0,
  12206. y: sprite.attr.translation.y || 0
  12207. };
  12208. }
  12209. else if (attr == 'rotate') {
  12210. o = {
  12211. degrees: sprite.attr.rotation.degrees || 0,
  12212. x: sprite.attr.rotation.x,
  12213. y: sprite.attr.rotation.y
  12214. };
  12215. }
  12216. else {
  12217. o = sprite.attr[attr];
  12218. }
  12219. return o;
  12220. },
  12221. getAttr: function(attr, val) {
  12222. return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
  12223. },
  12224. setAttr: function(targetData) {
  12225. var ln = targetData.length,
  12226. spriteArr = [],
  12227. attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
  12228. for (i = 0; i < ln; i++) {
  12229. attrs = targetData[i].attrs;
  12230. for (attr in attrs) {
  12231. attrArr = attrs[attr];
  12232. ln2 = attrArr.length;
  12233. for (j = 0; j < ln2; j++) {
  12234. spritePtr = attrArr[j][0];
  12235. attPtr = attrArr[j][1];
  12236. if (attr === 'translate') {
  12237. value = {
  12238. x: attPtr.x,
  12239. y: attPtr.y
  12240. };
  12241. }
  12242. else if (attr === 'rotate') {
  12243. x = attPtr.x;
  12244. if (isNaN(x)) {
  12245. x = null;
  12246. }
  12247. y = attPtr.y;
  12248. if (isNaN(y)) {
  12249. y = null;
  12250. }
  12251. value = {
  12252. degrees: attPtr.degrees,
  12253. x: x,
  12254. y: y
  12255. };
  12256. }
  12257. else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
  12258. value = parseFloat(attPtr);
  12259. }
  12260. else {
  12261. value = attPtr;
  12262. }
  12263. idx = Ext.Array.indexOf(spriteArr, spritePtr);
  12264. if (idx == -1) {
  12265. spriteArr.push([spritePtr, {}]);
  12266. idx = spriteArr.length - 1;
  12267. }
  12268. spriteArr[idx][1][attr] = value;
  12269. }
  12270. }
  12271. }
  12272. ln = spriteArr.length;
  12273. for (i = 0; i < ln; i++) {
  12274. spritePtr = spriteArr[i];
  12275. spritePtr[0].setAttributes(spritePtr[1]);
  12276. }
  12277. this.target.redraw();
  12278. }
  12279. });
  12280. /**
  12281. * @class Ext.fx.target.CompositeSprite
  12282. * @extends Ext.fx.target.Sprite
  12283. This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
  12284. each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
  12285. created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
  12286. and the appropriate target will be created.
  12287. * @markdown
  12288. */
  12289. Ext.define('Ext.fx.target.CompositeSprite', {
  12290. /* Begin Definitions */
  12291. extend: 'Ext.fx.target.Sprite',
  12292. /* End Definitions */
  12293. getAttr: function(attr, val) {
  12294. var out = [],
  12295. target = this.target;
  12296. target.each(function(sprite) {
  12297. out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
  12298. }, this);
  12299. return out;
  12300. }
  12301. });
  12302. /**
  12303. * @class Ext.fx.target.Component
  12304. * @extends Ext.fx.target.Target
  12305. *
  12306. * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
  12307. * created directly, the {@link Ext.Component} will be passed to the animation and
  12308. * and the appropriate target will be created.
  12309. */
  12310. Ext.define('Ext.fx.target.Component', {
  12311. /* Begin Definitions */
  12312. extend: 'Ext.fx.target.Target',
  12313. /* End Definitions */
  12314. type: 'component',
  12315. // Methods to call to retrieve unspecified "from" values from a target Component
  12316. getPropMethod: {
  12317. top: function() {
  12318. return this.getPosition(true)[1];
  12319. },
  12320. left: function() {
  12321. return this.getPosition(true)[0];
  12322. },
  12323. x: function() {
  12324. return this.getPosition()[0];
  12325. },
  12326. y: function() {
  12327. return this.getPosition()[1];
  12328. },
  12329. height: function() {
  12330. return this.getHeight();
  12331. },
  12332. width: function() {
  12333. return this.getWidth();
  12334. },
  12335. opacity: function() {
  12336. return this.el.getStyle('opacity');
  12337. }
  12338. },
  12339. compMethod: {
  12340. top: 'setPosition',
  12341. left: 'setPosition',
  12342. x: 'setPagePosition',
  12343. y: 'setPagePosition',
  12344. height: 'setSize',
  12345. width: 'setSize',
  12346. opacity: 'setOpacity'
  12347. },
  12348. // Read the named attribute from the target Component. Use the defined getter for the attribute
  12349. getAttr: function(attr, val) {
  12350. return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
  12351. },
  12352. setAttr: function(targetData, isFirstFrame, isLastFrame) {
  12353. var me = this,
  12354. target = me.target,
  12355. ln = targetData.length,
  12356. attrs, attr, o, i, j, meth, targets, left, top, w, h;
  12357. for (i = 0; i < ln; i++) {
  12358. attrs = targetData[i].attrs;
  12359. for (attr in attrs) {
  12360. targets = attrs[attr].length;
  12361. meth = {
  12362. setPosition: {},
  12363. setPagePosition: {},
  12364. setSize: {},
  12365. setOpacity: {}
  12366. };
  12367. for (j = 0; j < targets; j++) {
  12368. o = attrs[attr][j];
  12369. // We REALLY want a single function call, so push these down to merge them: eg
  12370. // meth.setPagePosition.target = <targetComponent>
  12371. // meth.setPagePosition['x'] = 100
  12372. // meth.setPagePosition['y'] = 100
  12373. meth[me.compMethod[attr]].target = o[0];
  12374. meth[me.compMethod[attr]][attr] = o[1];
  12375. }
  12376. if (meth.setPosition.target) {
  12377. o = meth.setPosition;
  12378. left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
  12379. top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
  12380. o.target.setPosition(left, top);
  12381. }
  12382. if (meth.setPagePosition.target) {
  12383. o = meth.setPagePosition;
  12384. o.target.setPagePosition(o.x, o.y);
  12385. }
  12386. if (meth.setSize.target && meth.setSize.target.el) {
  12387. o = meth.setSize;
  12388. // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
  12389. w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
  12390. h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
  12391. // Only set the size of the Component on the last frame, or if the animation was
  12392. // configured with dynamic: true.
  12393. // In other cases, we just set the target element size.
  12394. // This will result in either clipping if animating a reduction in size, or the revealing of
  12395. // the inner elements of the Component if animating an increase in size.
  12396. // Component's animate function initially resizes to the larger size before resizing the
  12397. // outer element to clip the contents.
  12398. if (isLastFrame || me.dynamic) {
  12399. o.target.componentLayout.childrenChanged = true;
  12400. // Flag if we are being called by an animating layout: use setCalculatedSize
  12401. if (me.layoutAnimation) {
  12402. o.target.setCalculatedSize(w, h);
  12403. } else {
  12404. o.target.setSize(w, h);
  12405. }
  12406. }
  12407. else {
  12408. o.target.el.setSize(w, h);
  12409. }
  12410. }
  12411. if (meth.setOpacity.target) {
  12412. o = meth.setOpacity;
  12413. o.target.el.setStyle('opacity', o.opacity);
  12414. }
  12415. }
  12416. }
  12417. }
  12418. });
  12419. /**
  12420. * @class Ext.fx.CubicBezier
  12421. * @ignore
  12422. */
  12423. Ext.define('Ext.fx.CubicBezier', {
  12424. /* Begin Definitions */
  12425. singleton: true,
  12426. /* End Definitions */
  12427. cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
  12428. var cx = 3 * p1x,
  12429. bx = 3 * (p2x - p1x) - cx,
  12430. ax = 1 - cx - bx,
  12431. cy = 3 * p1y,
  12432. by = 3 * (p2y - p1y) - cy,
  12433. ay = 1 - cy - by;
  12434. function sampleCurveX(t) {
  12435. return ((ax * t + bx) * t + cx) * t;
  12436. }
  12437. function solve(x, epsilon) {
  12438. var t = solveCurveX(x, epsilon);
  12439. return ((ay * t + by) * t + cy) * t;
  12440. }
  12441. function solveCurveX(x, epsilon) {
  12442. var t0, t1, t2, x2, d2, i;
  12443. for (t2 = x, i = 0; i < 8; i++) {
  12444. x2 = sampleCurveX(t2) - x;
  12445. if (Math.abs(x2) < epsilon) {
  12446. return t2;
  12447. }
  12448. d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
  12449. if (Math.abs(d2) < 1e-6) {
  12450. break;
  12451. }
  12452. t2 = t2 - x2 / d2;
  12453. }
  12454. t0 = 0;
  12455. t1 = 1;
  12456. t2 = x;
  12457. if (t2 < t0) {
  12458. return t0;
  12459. }
  12460. if (t2 > t1) {
  12461. return t1;
  12462. }
  12463. while (t0 < t1) {
  12464. x2 = sampleCurveX(t2);
  12465. if (Math.abs(x2 - x) < epsilon) {
  12466. return t2;
  12467. }
  12468. if (x > x2) {
  12469. t0 = t2;
  12470. } else {
  12471. t1 = t2;
  12472. }
  12473. t2 = (t1 - t0) / 2 + t0;
  12474. }
  12475. return t2;
  12476. }
  12477. return solve(t, 1 / (200 * duration));
  12478. },
  12479. cubicBezier: function(x1, y1, x2, y2) {
  12480. var fn = function(pos) {
  12481. return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
  12482. };
  12483. fn.toCSS3 = function() {
  12484. return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
  12485. };
  12486. fn.reverse = function() {
  12487. return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
  12488. };
  12489. return fn;
  12490. }
  12491. });
  12492. /**
  12493. * Represents an RGB color and provides helper functions get
  12494. * color components in HSL color space.
  12495. */
  12496. Ext.define('Ext.draw.Color', {
  12497. /* Begin Definitions */
  12498. /* End Definitions */
  12499. colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
  12500. rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
  12501. hexRe: /\s*#([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)\s*/,
  12502. /**
  12503. * @cfg {Number} lightnessFactor
  12504. *
  12505. * The default factor to compute the lighter or darker color. Defaults to 0.2.
  12506. */
  12507. lightnessFactor: 0.2,
  12508. /**
  12509. * Creates new Color.
  12510. * @param {Number} red Red component (0..255)
  12511. * @param {Number} green Green component (0..255)
  12512. * @param {Number} blue Blue component (0..255)
  12513. */
  12514. constructor : function(red, green, blue) {
  12515. var me = this,
  12516. clamp = Ext.Number.constrain;
  12517. me.r = clamp(red, 0, 255);
  12518. me.g = clamp(green, 0, 255);
  12519. me.b = clamp(blue, 0, 255);
  12520. },
  12521. /**
  12522. * Get the red component of the color, in the range 0..255.
  12523. * @return {Number}
  12524. */
  12525. getRed: function() {
  12526. return this.r;
  12527. },
  12528. /**
  12529. * Get the green component of the color, in the range 0..255.
  12530. * @return {Number}
  12531. */
  12532. getGreen: function() {
  12533. return this.g;
  12534. },
  12535. /**
  12536. * Get the blue component of the color, in the range 0..255.
  12537. * @return {Number}
  12538. */
  12539. getBlue: function() {
  12540. return this.b;
  12541. },
  12542. /**
  12543. * Get the RGB values.
  12544. * @return {Number[]}
  12545. */
  12546. getRGB: function() {
  12547. var me = this;
  12548. return [me.r, me.g, me.b];
  12549. },
  12550. /**
  12551. * Get the equivalent HSL components of the color.
  12552. * @return {Number[]}
  12553. */
  12554. getHSL: function() {
  12555. var me = this,
  12556. r = me.r / 255,
  12557. g = me.g / 255,
  12558. b = me.b / 255,
  12559. max = Math.max(r, g, b),
  12560. min = Math.min(r, g, b),
  12561. delta = max - min,
  12562. h,
  12563. s = 0,
  12564. l = 0.5 * (max + min);
  12565. // min==max means achromatic (hue is undefined)
  12566. if (min != max) {
  12567. s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
  12568. if (r == max) {
  12569. h = 60 * (g - b) / delta;
  12570. } else if (g == max) {
  12571. h = 120 + 60 * (b - r) / delta;
  12572. } else {
  12573. h = 240 + 60 * (r - g) / delta;
  12574. }
  12575. if (h < 0) {
  12576. h += 360;
  12577. }
  12578. if (h >= 360) {
  12579. h -= 360;
  12580. }
  12581. }
  12582. return [h, s, l];
  12583. },
  12584. /**
  12585. * Return a new color that is lighter than this color.
  12586. * @param {Number} factor Lighter factor (0..1), default to 0.2
  12587. * @return Ext.draw.Color
  12588. */
  12589. getLighter: function(factor) {
  12590. var hsl = this.getHSL();
  12591. factor = factor || this.lightnessFactor;
  12592. hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
  12593. return this.fromHSL(hsl[0], hsl[1], hsl[2]);
  12594. },
  12595. /**
  12596. * Return a new color that is darker than this color.
  12597. * @param {Number} factor Darker factor (0..1), default to 0.2
  12598. * @return Ext.draw.Color
  12599. */
  12600. getDarker: function(factor) {
  12601. factor = factor || this.lightnessFactor;
  12602. return this.getLighter(-factor);
  12603. },
  12604. /**
  12605. * Return the color in the hex format, i.e. '#rrggbb'.
  12606. * @return {String}
  12607. */
  12608. toString: function() {
  12609. var me = this,
  12610. round = Math.round,
  12611. r = round(me.r).toString(16),
  12612. g = round(me.g).toString(16),
  12613. b = round(me.b).toString(16);
  12614. r = (r.length == 1) ? '0' + r : r;
  12615. g = (g.length == 1) ? '0' + g : g;
  12616. b = (b.length == 1) ? '0' + b : b;
  12617. return ['#', r, g, b].join('');
  12618. },
  12619. /**
  12620. * Convert a color to hexadecimal format.
  12621. *
  12622. * **Note:** This method is both static and instance.
  12623. *
  12624. * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
  12625. * Can also be an Array, in this case the function handles the first member.
  12626. * @returns {String} The color in hexadecimal format.
  12627. * @static
  12628. */
  12629. toHex: function(color) {
  12630. if (Ext.isArray(color)) {
  12631. color = color[0];
  12632. }
  12633. if (!Ext.isString(color)) {
  12634. return '';
  12635. }
  12636. if (color.substr(0, 1) === '#') {
  12637. return color;
  12638. }
  12639. var digits = this.colorToHexRe.exec(color);
  12640. if (Ext.isArray(digits)) {
  12641. var red = parseInt(digits[2], 10),
  12642. green = parseInt(digits[3], 10),
  12643. blue = parseInt(digits[4], 10),
  12644. rgb = blue | (green << 8) | (red << 16);
  12645. return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
  12646. }
  12647. else {
  12648. return '';
  12649. }
  12650. },
  12651. /**
  12652. * Parse the string and create a new color.
  12653. *
  12654. * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
  12655. *
  12656. * If the string is not recognized, an undefined will be returned instead.
  12657. *
  12658. * **Note:** This method is both static and instance.
  12659. *
  12660. * @param {String} str Color in string.
  12661. * @returns Ext.draw.Color
  12662. * @static
  12663. */
  12664. fromString: function(str) {
  12665. var values, r, g, b,
  12666. parse = parseInt;
  12667. if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
  12668. values = str.match(this.hexRe);
  12669. if (values) {
  12670. r = parse(values[1], 16) >> 0;
  12671. g = parse(values[2], 16) >> 0;
  12672. b = parse(values[3], 16) >> 0;
  12673. if (str.length == 4) {
  12674. r += (r * 16);
  12675. g += (g * 16);
  12676. b += (b * 16);
  12677. }
  12678. }
  12679. }
  12680. else {
  12681. values = str.match(this.rgbRe);
  12682. if (values) {
  12683. r = values[1];
  12684. g = values[2];
  12685. b = values[3];
  12686. }
  12687. }
  12688. return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
  12689. },
  12690. /**
  12691. * Returns the gray value (0 to 255) of the color.
  12692. *
  12693. * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
  12694. *
  12695. * @returns {Number}
  12696. */
  12697. getGrayscale: function() {
  12698. // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
  12699. return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
  12700. },
  12701. /**
  12702. * Create a new color based on the specified HSL values.
  12703. *
  12704. * **Note:** This method is both static and instance.
  12705. *
  12706. * @param {Number} h Hue component (0..359)
  12707. * @param {Number} s Saturation component (0..1)
  12708. * @param {Number} l Lightness component (0..1)
  12709. * @returns Ext.draw.Color
  12710. * @static
  12711. */
  12712. fromHSL: function(h, s, l) {
  12713. var C, X, m, i, rgb = [],
  12714. abs = Math.abs,
  12715. floor = Math.floor;
  12716. if (s == 0 || h == null) {
  12717. // achromatic
  12718. rgb = [l, l, l];
  12719. }
  12720. else {
  12721. // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
  12722. // C is the chroma
  12723. // X is the second largest component
  12724. // m is the lightness adjustment
  12725. h /= 60;
  12726. C = s * (1 - abs(2 * l - 1));
  12727. X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
  12728. m = l - C / 2;
  12729. switch (floor(h)) {
  12730. case 0:
  12731. rgb = [C, X, 0];
  12732. break;
  12733. case 1:
  12734. rgb = [X, C, 0];
  12735. break;
  12736. case 2:
  12737. rgb = [0, C, X];
  12738. break;
  12739. case 3:
  12740. rgb = [0, X, C];
  12741. break;
  12742. case 4:
  12743. rgb = [X, 0, C];
  12744. break;
  12745. case 5:
  12746. rgb = [C, 0, X];
  12747. break;
  12748. }
  12749. rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
  12750. }
  12751. return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
  12752. }
  12753. }, function() {
  12754. var prototype = this.prototype;
  12755. //These functions are both static and instance. TODO: find a more elegant way of copying them
  12756. this.addStatics({
  12757. fromHSL: function() {
  12758. return prototype.fromHSL.apply(prototype, arguments);
  12759. },
  12760. fromString: function() {
  12761. return prototype.fromString.apply(prototype, arguments);
  12762. },
  12763. toHex: function() {
  12764. return prototype.toHex.apply(prototype, arguments);
  12765. }
  12766. });
  12767. });
  12768. /**
  12769. * @class Ext.dd.StatusProxy
  12770. * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair. This is the
  12771. * default drag proxy used by all Ext.dd components.
  12772. */
  12773. Ext.define('Ext.dd.StatusProxy', {
  12774. animRepair: false,
  12775. /**
  12776. * Creates new StatusProxy.
  12777. * @param {Object} config (optional) Config object.
  12778. */
  12779. constructor: function(config){
  12780. Ext.apply(this, config);
  12781. this.id = this.id || Ext.id();
  12782. this.proxy = Ext.createWidget('component', {
  12783. floating: true,
  12784. stateful: false,
  12785. id: this.id,
  12786. html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
  12787. '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
  12788. cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
  12789. shadow: !config || config.shadow !== false,
  12790. renderTo: document.body
  12791. });
  12792. this.el = this.proxy.el;
  12793. this.el.show();
  12794. this.el.setVisibilityMode(Ext.Element.VISIBILITY);
  12795. this.el.hide();
  12796. this.ghost = Ext.get(this.el.dom.childNodes[1]);
  12797. this.dropStatus = this.dropNotAllowed;
  12798. },
  12799. /**
  12800. * @cfg {String} [dropAllowed="x-dd-drop-ok"]
  12801. * The CSS class to apply to the status element when drop is allowed.
  12802. */
  12803. dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
  12804. /**
  12805. * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
  12806. * The CSS class to apply to the status element when drop is not allowed.
  12807. */
  12808. dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
  12809. /**
  12810. * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
  12811. * over the current target element.
  12812. * @param {String} cssClass The css class for the new drop status indicator image
  12813. */
  12814. setStatus : function(cssClass){
  12815. cssClass = cssClass || this.dropNotAllowed;
  12816. if(this.dropStatus != cssClass){
  12817. this.el.replaceCls(this.dropStatus, cssClass);
  12818. this.dropStatus = cssClass;
  12819. }
  12820. },
  12821. /**
  12822. * Resets the status indicator to the default dropNotAllowed value
  12823. * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
  12824. */
  12825. reset : function(clearGhost){
  12826. this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
  12827. this.dropStatus = this.dropNotAllowed;
  12828. if(clearGhost){
  12829. this.ghost.update("");
  12830. }
  12831. },
  12832. /**
  12833. * Updates the contents of the ghost element
  12834. * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
  12835. * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
  12836. */
  12837. update : function(html){
  12838. if(typeof html == "string"){
  12839. this.ghost.update(html);
  12840. }else{
  12841. this.ghost.update("");
  12842. html.style.margin = "0";
  12843. this.ghost.dom.appendChild(html);
  12844. }
  12845. var el = this.ghost.dom.firstChild;
  12846. if(el){
  12847. Ext.fly(el).setStyle('float', 'none');
  12848. }
  12849. },
  12850. /**
  12851. * Returns the underlying proxy {@link Ext.Layer}
  12852. * @return {Ext.Layer} el
  12853. */
  12854. getEl : function(){
  12855. return this.el;
  12856. },
  12857. /**
  12858. * Returns the ghost element
  12859. * @return {Ext.Element} el
  12860. */
  12861. getGhost : function(){
  12862. return this.ghost;
  12863. },
  12864. /**
  12865. * Hides the proxy
  12866. * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
  12867. */
  12868. hide : function(clear) {
  12869. this.proxy.hide();
  12870. if (clear) {
  12871. this.reset(true);
  12872. }
  12873. },
  12874. /**
  12875. * Stops the repair animation if it's currently running
  12876. */
  12877. stop : function(){
  12878. if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
  12879. this.anim.stop();
  12880. }
  12881. },
  12882. /**
  12883. * Displays this proxy
  12884. */
  12885. show : function() {
  12886. this.proxy.show();
  12887. this.proxy.toFront();
  12888. },
  12889. /**
  12890. * Force the Layer to sync its shadow and shim positions to the element
  12891. */
  12892. sync : function(){
  12893. this.proxy.el.sync();
  12894. },
  12895. /**
  12896. * Causes the proxy to return to its position of origin via an animation. Should be called after an
  12897. * invalid drop operation by the item being dragged.
  12898. * @param {Number[]} xy The XY position of the element ([x, y])
  12899. * @param {Function} callback The function to call after the repair is complete.
  12900. * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
  12901. */
  12902. repair : function(xy, callback, scope){
  12903. this.callback = callback;
  12904. this.scope = scope;
  12905. if (xy && this.animRepair !== false) {
  12906. this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
  12907. this.el.hideUnders(true);
  12908. this.anim = this.el.animate({
  12909. duration: this.repairDuration || 500,
  12910. easing: 'ease-out',
  12911. to: {
  12912. x: xy[0],
  12913. y: xy[1]
  12914. },
  12915. stopAnimation: true,
  12916. callback: this.afterRepair,
  12917. scope: this
  12918. });
  12919. } else {
  12920. this.afterRepair();
  12921. }
  12922. },
  12923. // private
  12924. afterRepair : function(){
  12925. this.hide(true);
  12926. if(typeof this.callback == "function"){
  12927. this.callback.call(this.scope || this);
  12928. }
  12929. this.callback = null;
  12930. this.scope = null;
  12931. },
  12932. destroy: function(){
  12933. Ext.destroy(this.ghost, this.proxy, this.el);
  12934. }
  12935. });
  12936. /**
  12937. * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
  12938. * is primarily used internally for the Panel's drag drop implementation, and
  12939. * should never need to be created directly.
  12940. * @private
  12941. */
  12942. Ext.define('Ext.panel.Proxy', {
  12943. alternateClassName: 'Ext.dd.PanelProxy',
  12944. /**
  12945. * Creates new panel proxy.
  12946. * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
  12947. * @param {Object} [config] Config object
  12948. */
  12949. constructor: function(panel, config){
  12950. /**
  12951. * @property panel
  12952. * @type Ext.panel.Panel
  12953. */
  12954. this.panel = panel;
  12955. this.id = this.panel.id +'-ddproxy';
  12956. Ext.apply(this, config);
  12957. },
  12958. /**
  12959. * @cfg {Boolean} insertProxy
  12960. * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
  12961. * Most Panels are not absolute positioned and therefore we need to reserve this space.
  12962. */
  12963. insertProxy: true,
  12964. // private overrides
  12965. setStatus: Ext.emptyFn,
  12966. reset: Ext.emptyFn,
  12967. update: Ext.emptyFn,
  12968. stop: Ext.emptyFn,
  12969. sync: Ext.emptyFn,
  12970. /**
  12971. * Gets the proxy's element
  12972. * @return {Ext.Element} The proxy's element
  12973. */
  12974. getEl: function(){
  12975. return this.ghost.el;
  12976. },
  12977. /**
  12978. * Gets the proxy's ghost Panel
  12979. * @return {Ext.panel.Panel} The proxy's ghost Panel
  12980. */
  12981. getGhost: function(){
  12982. return this.ghost;
  12983. },
  12984. /**
  12985. * Gets the proxy element. This is the element that represents where the
  12986. * Panel was before we started the drag operation.
  12987. * @return {Ext.Element} The proxy's element
  12988. */
  12989. getProxy: function(){
  12990. return this.proxy;
  12991. },
  12992. /**
  12993. * Hides the proxy
  12994. */
  12995. hide : function(){
  12996. if (this.ghost) {
  12997. if (this.proxy) {
  12998. this.proxy.remove();
  12999. delete this.proxy;
  13000. }
  13001. // Unghost the Panel, do not move the Panel to where the ghost was
  13002. this.panel.unghost(null, false);
  13003. delete this.ghost;
  13004. }
  13005. },
  13006. /**
  13007. * Shows the proxy
  13008. */
  13009. show: function(){
  13010. if (!this.ghost) {
  13011. var panelSize = this.panel.getSize();
  13012. this.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
  13013. this.ghost = this.panel.ghost();
  13014. if (this.insertProxy) {
  13015. // bc Panels aren't absolute positioned we need to take up the space
  13016. // of where the panel previously was
  13017. this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
  13018. this.proxy.setSize(panelSize);
  13019. }
  13020. }
  13021. },
  13022. // private
  13023. repair: function(xy, callback, scope) {
  13024. this.hide();
  13025. if (typeof callback == "function") {
  13026. callback.call(scope || this);
  13027. }
  13028. },
  13029. /**
  13030. * Moves the proxy to a different position in the DOM. This is typically
  13031. * called while dragging the Panel to keep the proxy sync'd to the Panel's
  13032. * location.
  13033. * @param {HTMLElement} parentNode The proxy's parent DOM node
  13034. * @param {HTMLElement} [before] The sibling node before which the
  13035. * proxy should be inserted (defaults to the parent's last child if not
  13036. * specified)
  13037. */
  13038. moveProxy : function(parentNode, before){
  13039. if (this.proxy) {
  13040. parentNode.insertBefore(this.proxy.dom, before);
  13041. }
  13042. }
  13043. });
  13044. /**
  13045. * @class Ext.layout.component.AbstractDock
  13046. * @extends Ext.layout.component.Component
  13047. * @private
  13048. * This ComponentLayout handles docking for Panels. It takes care of panels that are
  13049. * part of a ContainerLayout that sets this Panel's size and Panels that are part of
  13050. * an AutoContainerLayout in which this panel get his height based of the CSS or
  13051. * or its content.
  13052. */
  13053. Ext.define('Ext.layout.component.AbstractDock', {
  13054. /* Begin Definitions */
  13055. extend: 'Ext.layout.component.Component',
  13056. /* End Definitions */
  13057. type: 'dock',
  13058. /**
  13059. * @private
  13060. * @property autoSizing
  13061. * @type Boolean
  13062. * This flag is set to indicate this layout may have an autoHeight/autoWidth.
  13063. */
  13064. autoSizing: true,
  13065. beforeLayout: function() {
  13066. var returnValue = this.callParent(arguments);
  13067. if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
  13068. this.handleItemBorders();
  13069. this.initializedBorders = true;
  13070. }
  13071. return returnValue;
  13072. },
  13073. handleItemBorders: function() {
  13074. var owner = this.owner,
  13075. body = owner.body,
  13076. docked = this.getLayoutItems(),
  13077. borders = {
  13078. top: [],
  13079. right: [],
  13080. bottom: [],
  13081. left: []
  13082. },
  13083. oldBorders = this.borders,
  13084. opposites = {
  13085. top: 'bottom',
  13086. right: 'left',
  13087. bottom: 'top',
  13088. left: 'right'
  13089. },
  13090. i, ln, item, dock, side;
  13091. for (i = 0, ln = docked.length; i < ln; i++) {
  13092. item = docked[i];
  13093. dock = item.dock;
  13094. if (item.ignoreBorderManagement) {
  13095. continue;
  13096. }
  13097. if (!borders[dock].satisfied) {
  13098. borders[dock].push(item);
  13099. borders[dock].satisfied = true;
  13100. }
  13101. if (!borders.top.satisfied && opposites[dock] !== 'top') {
  13102. borders.top.push(item);
  13103. }
  13104. if (!borders.right.satisfied && opposites[dock] !== 'right') {
  13105. borders.right.push(item);
  13106. }
  13107. if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
  13108. borders.bottom.push(item);
  13109. }
  13110. if (!borders.left.satisfied && opposites[dock] !== 'left') {
  13111. borders.left.push(item);
  13112. }
  13113. }
  13114. if (oldBorders) {
  13115. for (side in oldBorders) {
  13116. if (oldBorders.hasOwnProperty(side)) {
  13117. ln = oldBorders[side].length;
  13118. if (!owner.manageBodyBorders) {
  13119. for (i = 0; i < ln; i++) {
  13120. oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  13121. }
  13122. if (!oldBorders[side].satisfied && !owner.bodyBorder) {
  13123. body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  13124. }
  13125. }
  13126. else if (oldBorders[side].satisfied) {
  13127. body.setStyle('border-' + side + '-width', '');
  13128. }
  13129. }
  13130. }
  13131. }
  13132. for (side in borders) {
  13133. if (borders.hasOwnProperty(side)) {
  13134. ln = borders[side].length;
  13135. if (!owner.manageBodyBorders) {
  13136. for (i = 0; i < ln; i++) {
  13137. borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  13138. }
  13139. if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
  13140. body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  13141. }
  13142. }
  13143. else if (borders[side].satisfied) {
  13144. body.setStyle('border-' + side + '-width', '1px');
  13145. }
  13146. }
  13147. }
  13148. this.borders = borders;
  13149. },
  13150. /**
  13151. * @protected
  13152. * @param {Ext.Component} owner The Panel that owns this DockLayout
  13153. * @param {Ext.Element} target The target in which we are going to render the docked items
  13154. * @param {Array} args The arguments passed to the ComponentLayout.layout method
  13155. */
  13156. onLayout: function(width, height) {
  13157. if (this.onLayout_running) {
  13158. return;
  13159. }
  13160. this.onLayout_running = true;
  13161. var me = this,
  13162. owner = me.owner,
  13163. body = owner.body,
  13164. layout = owner.layout,
  13165. target = me.getTarget(),
  13166. autoWidth = false,
  13167. autoHeight = false,
  13168. padding, border, frameSize;
  13169. // We start of by resetting all the layouts info
  13170. var info = me.info = {
  13171. boxes: [],
  13172. size: {
  13173. width: width,
  13174. height: height
  13175. },
  13176. bodyBox: {}
  13177. };
  13178. // Clear isAutoDock flag
  13179. delete layout.isAutoDock;
  13180. Ext.applyIf(info, me.getTargetInfo());
  13181. // We need to bind to the ownerCt whenever we do not have a user set height or width.
  13182. if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
  13183. if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
  13184. owner.ownerCt.layout.bindToOwnerCtComponent = true;
  13185. }
  13186. else {
  13187. owner.ownerCt.layout.bindToOwnerCtComponent = false;
  13188. }
  13189. }
  13190. // Determine if we have an autoHeight or autoWidth.
  13191. if (height == null || width == null) {
  13192. padding = info.padding;
  13193. border = info.border;
  13194. frameSize = me.frameSize;
  13195. // Auto-everything, clear out any style height/width and read from css
  13196. if ((height == null) && (width == null)) {
  13197. autoHeight = true;
  13198. autoWidth = true;
  13199. me.setTargetSize(null);
  13200. me.setBodyBox({width: null, height: null});
  13201. }
  13202. // Auto-height
  13203. else if (height == null) {
  13204. autoHeight = true;
  13205. // Clear any sizing that we already set in a previous layout
  13206. me.setTargetSize(width);
  13207. me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
  13208. // Auto-width
  13209. }
  13210. else {
  13211. autoWidth = true;
  13212. // Clear any sizing that we already set in a previous layout
  13213. me.setTargetSize(null, height);
  13214. me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
  13215. }
  13216. // Run the container
  13217. if (layout && layout.isLayout) {
  13218. // Auto-Sized so have the container layout notify the component layout.
  13219. layout.bindToOwnerCtComponent = true;
  13220. // Set flag so we don't do a redundant container layout
  13221. layout.isAutoDock = layout.autoSize !== true;
  13222. layout.layout();
  13223. // If this is an autosized container layout, then we must compensate for a
  13224. // body that is being autosized. We do not want to adjust the body's size
  13225. // to accommodate the dock items, but rather we will want to adjust the
  13226. // target's size.
  13227. //
  13228. // This is necessary because, particularly in a Box layout, all child items
  13229. // are set with absolute dimensions that are not flexible to the size of its
  13230. // innerCt/target. So once they are laid out, they are sized for good. By
  13231. // shrinking the body box to accommodate dock items, we're merely cutting off
  13232. // parts of the body. Not good. Instead, the target's size should expand
  13233. // to fit the dock items in. This is valid because the target container is
  13234. // suppose to be autosized to fit everything accordingly.
  13235. info.autoSizedCtLayout = layout.autoSize === true;
  13236. info.autoHeight = autoHeight;
  13237. info.autoWidth = autoWidth;
  13238. }
  13239. // The dockItems method will add all the top and bottom docked items height
  13240. // to the info.panelSize height. That's why we have to call setSize after
  13241. // we dock all the items to actually set the panel's width and height.
  13242. // We have to do this because the panel body and docked items will be position
  13243. // absolute which doesn't stretch the panel.
  13244. me.dockItems();
  13245. me.setTargetSize(info.size.width, info.size.height);
  13246. }
  13247. else {
  13248. me.setTargetSize(width, height);
  13249. me.dockItems();
  13250. }
  13251. me.callParent(arguments);
  13252. this.onLayout_running = false;
  13253. },
  13254. /**
  13255. * @protected
  13256. * This method will first update all the information about the docked items,
  13257. * body dimensions and position, the panel's total size. It will then
  13258. * set all these values on the docked items and panel body.
  13259. * @param {Array} items Array containing all the docked items
  13260. * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
  13261. * AutoContainerLayout
  13262. */
  13263. dockItems : function() {
  13264. this.calculateDockBoxes();
  13265. // Both calculateAutoBoxes and calculateSizedBoxes are changing the
  13266. // information about the body, panel size, and boxes for docked items
  13267. // inside a property called info.
  13268. var info = this.info,
  13269. autoWidth = info.autoWidth,
  13270. autoHeight = info.autoHeight,
  13271. boxes = info.boxes,
  13272. ln = boxes.length,
  13273. dock, i, item;
  13274. // We are going to loop over all the boxes that were calculated
  13275. // and set the position of each item the box belongs to.
  13276. for (i = 0; i < ln; i++) {
  13277. dock = boxes[i];
  13278. item = dock.item;
  13279. item.setPosition(dock.x, dock.y);
  13280. if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
  13281. // Auto-Sized so have the container layout notify the component layout.
  13282. item.layout.bindToOwnerCtComponent = true;
  13283. }
  13284. }
  13285. // Don't adjust body width/height if the target is using an auto container layout.
  13286. // But, we do want to adjust the body size if the container layout is auto sized.
  13287. if (!info.autoSizedCtLayout) {
  13288. if (autoWidth) {
  13289. info.bodyBox.width = null;
  13290. }
  13291. if (autoHeight) {
  13292. info.bodyBox.height = null;
  13293. }
  13294. }
  13295. // If the bodyBox has been adjusted because of the docked items
  13296. // we will update the dimensions and position of the panel's body.
  13297. this.setBodyBox(info.bodyBox);
  13298. },
  13299. /**
  13300. * @protected
  13301. * This method will set up some initial information about the panel size and bodybox
  13302. * and then loop over all the items you pass it to take care of stretching, aligning,
  13303. * dock position and all calculations involved with adjusting the body box.
  13304. * @param {Array} items Array containing all the docked items we have to layout
  13305. */
  13306. calculateDockBoxes : function() {
  13307. if (this.calculateDockBoxes_running) {
  13308. // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
  13309. return;
  13310. }
  13311. this.calculateDockBoxes_running = true;
  13312. // We want to use the Panel's el width, and the Panel's body height as the initial
  13313. // size we are going to use in calculateDockBoxes. We also want to account for
  13314. // the border of the panel.
  13315. var me = this,
  13316. target = me.getTarget(),
  13317. items = me.getLayoutItems(),
  13318. owner = me.owner,
  13319. bodyEl = owner.body,
  13320. info = me.info,
  13321. autoWidth = info.autoWidth,
  13322. autoHeight = info.autoHeight,
  13323. size = info.size,
  13324. ln = items.length,
  13325. padding = info.padding,
  13326. border = info.border,
  13327. frameSize = me.frameSize,
  13328. item, i, box, rect;
  13329. // If this Panel is inside an AutoContainerLayout, we will base all the calculations
  13330. // around the height of the body and the width of the panel.
  13331. if (autoHeight) {
  13332. size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
  13333. }
  13334. else {
  13335. size.height = target.getHeight();
  13336. }
  13337. if (autoWidth) {
  13338. size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
  13339. }
  13340. else {
  13341. size.width = target.getWidth();
  13342. }
  13343. info.bodyBox = {
  13344. x: padding.left + frameSize.left,
  13345. y: padding.top + frameSize.top,
  13346. width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
  13347. height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
  13348. };
  13349. // Loop over all the docked items
  13350. for (i = 0; i < ln; i++) {
  13351. item = items[i];
  13352. // The initBox method will take care of stretching and alignment
  13353. // In some cases it will also layout the dock items to be able to
  13354. // get a width or height measurement
  13355. box = me.initBox(item);
  13356. if (autoHeight === true) {
  13357. box = me.adjustAutoBox(box, i);
  13358. }
  13359. else {
  13360. box = me.adjustSizedBox(box, i);
  13361. }
  13362. // Save our box. This allows us to loop over all docked items and do all
  13363. // calculations first. Then in one loop we will actually size and position
  13364. // all the docked items that have changed.
  13365. info.boxes.push(box);
  13366. }
  13367. this.calculateDockBoxes_running = false;
  13368. },
  13369. /**
  13370. * @protected
  13371. * This method will adjust the position of the docked item and adjust the body box
  13372. * accordingly.
  13373. * @param {Object} box The box containing information about the width and height
  13374. * of this docked item
  13375. * @param {Number} index The index position of this docked item
  13376. * @return {Object} The adjusted box
  13377. */
  13378. adjustSizedBox : function(box, index) {
  13379. var bodyBox = this.info.bodyBox,
  13380. frameSize = this.frameSize,
  13381. info = this.info,
  13382. padding = info.padding,
  13383. pos = box.type,
  13384. border = info.border;
  13385. switch (pos) {
  13386. case 'top':
  13387. box.y = bodyBox.y;
  13388. break;
  13389. case 'left':
  13390. box.x = bodyBox.x;
  13391. break;
  13392. case 'bottom':
  13393. box.y = (bodyBox.y + bodyBox.height) - box.height;
  13394. break;
  13395. case 'right':
  13396. box.x = (bodyBox.x + bodyBox.width) - box.width;
  13397. break;
  13398. }
  13399. if (box.ignoreFrame) {
  13400. if (pos == 'bottom') {
  13401. box.y += (frameSize.bottom + padding.bottom + border.bottom);
  13402. }
  13403. else {
  13404. box.y -= (frameSize.top + padding.top + border.top);
  13405. }
  13406. if (pos == 'right') {
  13407. box.x += (frameSize.right + padding.right + border.right);
  13408. }
  13409. else {
  13410. box.x -= (frameSize.left + padding.left + border.left);
  13411. }
  13412. }
  13413. // If this is not an overlaying docked item, we have to adjust the body box
  13414. if (!box.overlay) {
  13415. switch (pos) {
  13416. case 'top':
  13417. bodyBox.y += box.height;
  13418. bodyBox.height -= box.height;
  13419. break;
  13420. case 'left':
  13421. bodyBox.x += box.width;
  13422. bodyBox.width -= box.width;
  13423. break;
  13424. case 'bottom':
  13425. bodyBox.height -= box.height;
  13426. break;
  13427. case 'right':
  13428. bodyBox.width -= box.width;
  13429. break;
  13430. }
  13431. }
  13432. return box;
  13433. },
  13434. /**
  13435. * @protected
  13436. * This method will adjust the position of the docked item inside an AutoContainerLayout
  13437. * and adjust the body box accordingly.
  13438. * @param {Object} box The box containing information about the width and height
  13439. * of this docked item
  13440. * @param {Number} index The index position of this docked item
  13441. * @return {Object} The adjusted box
  13442. */
  13443. adjustAutoBox : function (box, index) {
  13444. var info = this.info,
  13445. owner = this.owner,
  13446. bodyBox = info.bodyBox,
  13447. size = info.size,
  13448. boxes = info.boxes,
  13449. boxesLn = boxes.length,
  13450. pos = box.type,
  13451. frameSize = this.frameSize,
  13452. padding = info.padding,
  13453. border = info.border,
  13454. autoSizedCtLayout = info.autoSizedCtLayout,
  13455. ln = (boxesLn < index) ? boxesLn : index,
  13456. i, adjustBox;
  13457. if (pos == 'top' || pos == 'bottom') {
  13458. // This can affect the previously set left and right and bottom docked items
  13459. for (i = 0; i < ln; i++) {
  13460. adjustBox = boxes[i];
  13461. if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
  13462. adjustBox.height += box.height;
  13463. }
  13464. else if (adjustBox.type == 'bottom') {
  13465. adjustBox.y += box.height;
  13466. }
  13467. }
  13468. }
  13469. switch (pos) {
  13470. case 'top':
  13471. box.y = bodyBox.y;
  13472. if (!box.overlay) {
  13473. bodyBox.y += box.height;
  13474. if (info.autoHeight) {
  13475. size.height += box.height;
  13476. } else {
  13477. bodyBox.height -= box.height;
  13478. }
  13479. }
  13480. break;
  13481. case 'bottom':
  13482. if (!box.overlay) {
  13483. if (info.autoHeight) {
  13484. size.height += box.height;
  13485. } else {
  13486. bodyBox.height -= box.height;
  13487. }
  13488. }
  13489. box.y = (bodyBox.y + bodyBox.height);
  13490. break;
  13491. case 'left':
  13492. box.x = bodyBox.x;
  13493. if (!box.overlay) {
  13494. bodyBox.x += box.width;
  13495. if (info.autoWidth) {
  13496. size.width += box.width;
  13497. } else {
  13498. bodyBox.width -= box.width;
  13499. }
  13500. }
  13501. break;
  13502. case 'right':
  13503. if (!box.overlay) {
  13504. if (info.autoWidth) {
  13505. size.width += box.width;
  13506. } else {
  13507. bodyBox.width -= box.width;
  13508. }
  13509. }
  13510. box.x = (bodyBox.x + bodyBox.width);
  13511. break;
  13512. }
  13513. if (box.ignoreFrame) {
  13514. if (pos == 'bottom') {
  13515. box.y += (frameSize.bottom + padding.bottom + border.bottom);
  13516. }
  13517. else {
  13518. box.y -= (frameSize.top + padding.top + border.top);
  13519. }
  13520. if (pos == 'right') {
  13521. box.x += (frameSize.right + padding.right + border.right);
  13522. }
  13523. else {
  13524. box.x -= (frameSize.left + padding.left + border.left);
  13525. }
  13526. }
  13527. return box;
  13528. },
  13529. /**
  13530. * @protected
  13531. * This method will create a box object, with a reference to the item, the type of dock
  13532. * (top, left, bottom, right). It will also take care of stretching and aligning of the
  13533. * docked items.
  13534. * @param {Ext.Component} item The docked item we want to initialize the box for
  13535. * @return {Object} The initial box containing width and height and other useful information
  13536. */
  13537. initBox : function(item) {
  13538. var me = this,
  13539. bodyBox = me.info.bodyBox,
  13540. horizontal = (item.dock == 'top' || item.dock == 'bottom'),
  13541. owner = me.owner,
  13542. frameSize = me.frameSize,
  13543. info = me.info,
  13544. padding = info.padding,
  13545. border = info.border,
  13546. box = {
  13547. item: item,
  13548. overlay: item.overlay,
  13549. type: item.dock,
  13550. offsets: Ext.Element.parseBox(item.offsets || {}),
  13551. ignoreFrame: item.ignoreParentFrame
  13552. };
  13553. // First we are going to take care of stretch and align properties for all four dock scenarios.
  13554. if (item.stretch !== false) {
  13555. box.stretched = true;
  13556. if (horizontal) {
  13557. box.x = bodyBox.x + box.offsets.left;
  13558. box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
  13559. if (box.ignoreFrame) {
  13560. box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
  13561. }
  13562. item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
  13563. }
  13564. else {
  13565. box.y = bodyBox.y + box.offsets.top;
  13566. box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
  13567. if (box.ignoreFrame) {
  13568. box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
  13569. }
  13570. item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
  13571. // At this point IE will report the left/right-docked toolbar as having a width equal to the
  13572. // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
  13573. if (!Ext.supports.ComputedStyle) {
  13574. item.el.repaint();
  13575. }
  13576. }
  13577. }
  13578. else {
  13579. item.doComponentLayout();
  13580. box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
  13581. box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
  13582. box.y += box.offsets.top;
  13583. if (horizontal) {
  13584. box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
  13585. box.x += box.offsets.left;
  13586. }
  13587. }
  13588. // If we haven't calculated the width or height of the docked item yet
  13589. // do so, since we need this for our upcoming calculations
  13590. if (box.width === undefined) {
  13591. box.width = item.getWidth() + item.el.getMargin('lr');
  13592. }
  13593. if (box.height === undefined) {
  13594. box.height = item.getHeight() + item.el.getMargin('tb');
  13595. }
  13596. return box;
  13597. },
  13598. /**
  13599. * @protected
  13600. * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
  13601. * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
  13602. */
  13603. getLayoutItems : function() {
  13604. var it = this.owner.getDockedItems(),
  13605. ln = it.length,
  13606. i = 0,
  13607. result = [];
  13608. for (; i < ln; i++) {
  13609. if (it[i].isVisible(true)) {
  13610. result.push(it[i]);
  13611. }
  13612. }
  13613. return result;
  13614. },
  13615. /**
  13616. * @protected
  13617. * Render the top and left docked items before any existing DOM nodes in our render target,
  13618. * and then render the right and bottom docked items after. This is important, for such things
  13619. * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
  13620. * Our collection of docked items will already be ordered via Panel.getDockedItems().
  13621. */
  13622. renderItems: function(items, target) {
  13623. var cns = target.dom.childNodes,
  13624. cnsLn = cns.length,
  13625. ln = items.length,
  13626. domLn = 0,
  13627. i, j, cn, item;
  13628. // Calculate the number of DOM nodes in our target that are not our docked items
  13629. for (i = 0; i < cnsLn; i++) {
  13630. cn = Ext.get(cns[i]);
  13631. for (j = 0; j < ln; j++) {
  13632. item = items[j];
  13633. if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
  13634. break;
  13635. }
  13636. }
  13637. if (j === ln) {
  13638. domLn++;
  13639. }
  13640. }
  13641. // Now we go through our docked items and render/move them
  13642. for (i = 0, j = 0; i < ln; i++, j++) {
  13643. item = items[i];
  13644. // If we're now at the right/bottom docked item, we jump ahead in our
  13645. // DOM position, just past the existing DOM nodes.
  13646. //
  13647. // TODO: This is affected if users provide custom weight values to their
  13648. // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
  13649. // sort operation here, for now, in the name of performance. getDockedItems()
  13650. // needs the sort operation not just for this layout-time rendering, but
  13651. // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
  13652. if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
  13653. j += domLn;
  13654. }
  13655. // Same logic as Layout.renderItems()
  13656. if (item && !item.rendered) {
  13657. this.renderItem(item, target, j);
  13658. }
  13659. else if (!this.isValidParent(item, target, j)) {
  13660. this.moveItem(item, target, j);
  13661. }
  13662. }
  13663. },
  13664. /**
  13665. * @protected
  13666. * This function will be called by the dockItems method. Since the body is positioned absolute,
  13667. * we need to give it dimensions and a position so that it is in the middle surrounded by
  13668. * docked items
  13669. * @param {Object} box An object containing new x, y, width and height values for the
  13670. * Panel's body
  13671. */
  13672. setBodyBox : function(box) {
  13673. var me = this,
  13674. owner = me.owner,
  13675. body = owner.body,
  13676. info = me.info,
  13677. bodyMargin = info.bodyMargin,
  13678. padding = info.padding,
  13679. border = info.border,
  13680. frameSize = me.frameSize;
  13681. // Panel collapse effectively hides the Panel's body, so this is a no-op.
  13682. if (owner.collapsed) {
  13683. return;
  13684. }
  13685. if (Ext.isNumber(box.width)) {
  13686. box.width -= bodyMargin.left + bodyMargin.right;
  13687. }
  13688. if (Ext.isNumber(box.height)) {
  13689. box.height -= bodyMargin.top + bodyMargin.bottom;
  13690. }
  13691. me.setElementSize(body, box.width, box.height);
  13692. if (Ext.isNumber(box.x)) {
  13693. body.setLeft(box.x - padding.left - frameSize.left);
  13694. }
  13695. if (Ext.isNumber(box.y)) {
  13696. body.setTop(box.y - padding.top - frameSize.top);
  13697. }
  13698. },
  13699. /**
  13700. * @protected
  13701. * We are overriding the Ext.layout.Layout configureItem method to also add a class that
  13702. * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
  13703. * An example of a class added to a dock: right item is x-docked-right
  13704. * @param {Ext.Component} item The item we are configuring
  13705. */
  13706. configureItem : function(item, pos) {
  13707. this.callParent(arguments);
  13708. if (item.dock == 'top' || item.dock == 'bottom') {
  13709. item.layoutManagedWidth = 1;
  13710. item.layoutManagedHeight = 2;
  13711. } else {
  13712. item.layoutManagedWidth = 2;
  13713. item.layoutManagedHeight = 1;
  13714. }
  13715. item.addCls(Ext.baseCSSPrefix + 'docked');
  13716. item.addClsWithUI('docked-' + item.dock);
  13717. },
  13718. afterRemove : function(item) {
  13719. this.callParent(arguments);
  13720. if (this.itemCls) {
  13721. item.el.removeCls(this.itemCls + '-' + item.dock);
  13722. }
  13723. var dom = item.el.dom;
  13724. if (!item.destroying && dom) {
  13725. dom.parentNode.removeChild(dom);
  13726. }
  13727. this.childrenChanged = true;
  13728. }
  13729. });
  13730. /**
  13731. * @class Ext.util.Memento
  13732. * This class manages a set of captured properties from an object. These captured properties
  13733. * can later be restored to an object.
  13734. */
  13735. Ext.define('Ext.util.Memento', function () {
  13736. function captureOne (src, target, prop) {
  13737. src[prop] = target[prop];
  13738. }
  13739. function removeOne (src, target, prop) {
  13740. delete src[prop];
  13741. }
  13742. function restoreOne (src, target, prop) {
  13743. var value = src[prop];
  13744. if (value || src.hasOwnProperty(prop)) {
  13745. restoreValue(target, prop, value);
  13746. }
  13747. }
  13748. function restoreValue (target, prop, value) {
  13749. if (Ext.isDefined(value)) {
  13750. target[prop] = value;
  13751. } else {
  13752. delete target[prop];
  13753. }
  13754. }
  13755. function doMany (doOne, src, target, props) {
  13756. if (src) {
  13757. if (Ext.isArray(props)) {
  13758. Ext.each(props, function (prop) {
  13759. doOne(src, target, prop);
  13760. });
  13761. } else {
  13762. doOne(src, target, props);
  13763. }
  13764. }
  13765. }
  13766. return {
  13767. /**
  13768. * @property data
  13769. * The collection of captured properties.
  13770. * @private
  13771. */
  13772. data: null,
  13773. /**
  13774. * @property target
  13775. * The default target object for capture/restore (passed to the constructor).
  13776. */
  13777. target: null,
  13778. /**
  13779. * Creates a new memento and optionally captures properties from the target object.
  13780. * @param {Object} target The target from which to capture properties. If specified in the
  13781. * constructor, this target becomes the default target for all other operations.
  13782. * @param {String/String[]} props The property or array of properties to capture.
  13783. */
  13784. constructor: function (target, props) {
  13785. if (target) {
  13786. this.target = target;
  13787. if (props) {
  13788. this.capture(props);
  13789. }
  13790. }
  13791. },
  13792. /**
  13793. * Captures the specified properties from the target object in this memento.
  13794. * @param {String/String[]} props The property or array of properties to capture.
  13795. * @param {Object} target The object from which to capture properties.
  13796. */
  13797. capture: function (props, target) {
  13798. doMany(captureOne, this.data || (this.data = {}), target || this.target, props);
  13799. },
  13800. /**
  13801. * Removes the specified properties from this memento. These properties will not be
  13802. * restored later without re-capturing their values.
  13803. * @param {String/String[]} props The property or array of properties to remove.
  13804. */
  13805. remove: function (props) {
  13806. doMany(removeOne, this.data, null, props);
  13807. },
  13808. /**
  13809. * Restores the specified properties from this memento to the target object.
  13810. * @param {String/String[]} props The property or array of properties to restore.
  13811. * @param {Boolean} clear True to remove the restored properties from this memento or
  13812. * false to keep them (default is true).
  13813. * @param {Object} target The object to which to restore properties.
  13814. */
  13815. restore: function (props, clear, target) {
  13816. doMany(restoreOne, this.data, target || this.target, props);
  13817. if (clear !== false) {
  13818. this.remove(props);
  13819. }
  13820. },
  13821. /**
  13822. * Restores all captured properties in this memento to the target object.
  13823. * @param {Boolean} clear True to remove the restored properties from this memento or
  13824. * false to keep them (default is true).
  13825. * @param {Object} target The object to which to restore properties.
  13826. */
  13827. restoreAll: function (clear, target) {
  13828. var me = this,
  13829. t = target || this.target;
  13830. Ext.Object.each(me.data, function (prop, value) {
  13831. restoreValue(t, prop, value);
  13832. });
  13833. if (clear !== false) {
  13834. delete me.data;
  13835. }
  13836. }
  13837. };
  13838. }());
  13839. /**
  13840. * @class Ext.app.EventBus
  13841. * @private
  13842. */
  13843. Ext.define('Ext.app.EventBus', {
  13844. requires: [
  13845. 'Ext.util.Event'
  13846. ],
  13847. mixins: {
  13848. observable: 'Ext.util.Observable'
  13849. },
  13850. constructor: function() {
  13851. this.mixins.observable.constructor.call(this);
  13852. this.bus = {};
  13853. var me = this;
  13854. Ext.override(Ext.Component, {
  13855. fireEvent: function(ev) {
  13856. if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
  13857. return me.dispatch.call(me, ev, this, arguments);
  13858. }
  13859. return false;
  13860. }
  13861. });
  13862. },
  13863. dispatch: function(ev, target, args) {
  13864. var bus = this.bus,
  13865. selectors = bus[ev],
  13866. selector, controllers, id, events, event, i, ln;
  13867. if (selectors) {
  13868. // Loop over all the selectors that are bound to this event
  13869. for (selector in selectors) {
  13870. // Check if the target matches the selector
  13871. if (target.is(selector)) {
  13872. // Loop over all the controllers that are bound to this selector
  13873. controllers = selectors[selector];
  13874. for (id in controllers) {
  13875. // Loop over all the events that are bound to this selector on this controller
  13876. events = controllers[id];
  13877. for (i = 0, ln = events.length; i < ln; i++) {
  13878. event = events[i];
  13879. // Fire the event!
  13880. if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
  13881. return false;
  13882. };
  13883. }
  13884. }
  13885. }
  13886. }
  13887. }
  13888. },
  13889. control: function(selectors, listeners, controller) {
  13890. var bus = this.bus,
  13891. selector, fn;
  13892. if (Ext.isString(selectors)) {
  13893. selector = selectors;
  13894. selectors = {};
  13895. selectors[selector] = listeners;
  13896. this.control(selectors, null, controller);
  13897. return;
  13898. }
  13899. Ext.Object.each(selectors, function(selector, listeners) {
  13900. Ext.Object.each(listeners, function(ev, listener) {
  13901. var options = {},
  13902. scope = controller,
  13903. event = Ext.create('Ext.util.Event', controller, ev);
  13904. // Normalize the listener
  13905. if (Ext.isObject(listener)) {
  13906. options = listener;
  13907. listener = options.fn;
  13908. scope = options.scope || controller;
  13909. delete options.fn;
  13910. delete options.scope;
  13911. }
  13912. event.addListener(listener, scope, options);
  13913. // Create the bus tree if it is not there yet
  13914. bus[ev] = bus[ev] || {};
  13915. bus[ev][selector] = bus[ev][selector] || {};
  13916. bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
  13917. // Push our listener in our bus
  13918. bus[ev][selector][controller.id].push(event);
  13919. });
  13920. });
  13921. }
  13922. });
  13923. /**
  13924. * @class Ext.data.Types
  13925. * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
  13926. * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
  13927. * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
  13928. * of this class.</p>
  13929. * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
  13930. * each type definition must contain three properties:</p>
  13931. * <div class="mdetail-params"><ul>
  13932. * <li><code>convert</code> : <i>Function</i><div class="sub-desc">A function to convert raw data values from a data block into the data
  13933. * to be stored in the Field. The function is passed the collowing parameters:
  13934. * <div class="mdetail-params"><ul>
  13935. * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
  13936. * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
  13937. * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
  13938. * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
  13939. * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
  13940. * </ul></div></div></li>
  13941. * <li><code>sortType</code> : <i>Function</i> <div class="sub-desc">A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.</div></li>
  13942. * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
  13943. * </ul></div>
  13944. * <p>For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
  13945. * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
  13946. *<pre><code>
  13947. // Add a new Field data type which stores a VELatLong object in the Record.
  13948. Ext.data.Types.VELATLONG = {
  13949. convert: function(v, data) {
  13950. return new VELatLong(data.lat, data.long);
  13951. },
  13952. sortType: function(v) {
  13953. return v.Latitude; // When sorting, order by latitude
  13954. },
  13955. type: 'VELatLong'
  13956. };
  13957. </code></pre>
  13958. * <p>Then, when declaring a Model, use: <pre><code>
  13959. var types = Ext.data.Types; // allow shorthand type access
  13960. Ext.define('Unit',
  13961. extend: 'Ext.data.Model',
  13962. fields: [
  13963. { name: 'unitName', mapping: 'UnitName' },
  13964. { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
  13965. { name: 'latitude', mapping: 'lat', type: types.FLOAT },
  13966. { name: 'longitude', mapping: 'long', type: types.FLOAT },
  13967. { name: 'position', type: types.VELATLONG }
  13968. ]
  13969. });
  13970. </code></pre>
  13971. * @singleton
  13972. */
  13973. Ext.define('Ext.data.Types', {
  13974. singleton: true,
  13975. requires: ['Ext.data.SortTypes']
  13976. }, function() {
  13977. var st = Ext.data.SortTypes;
  13978. Ext.apply(Ext.data.Types, {
  13979. /**
  13980. * @property {RegExp} stripRe
  13981. * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
  13982. * This should be overridden for localization.
  13983. */
  13984. stripRe: /[\$,%]/g,
  13985. /**
  13986. * @property {Object} AUTO
  13987. * This data type means that no conversion is applied to the raw data before it is placed into a Record.
  13988. */
  13989. AUTO: {
  13990. convert: function(v) {
  13991. return v;
  13992. },
  13993. sortType: st.none,
  13994. type: 'auto'
  13995. },
  13996. /**
  13997. * @property {Object} STRING
  13998. * This data type means that the raw data is converted into a String before it is placed into a Record.
  13999. */
  14000. STRING: {
  14001. convert: function(v) {
  14002. var defaultValue = this.useNull ? null : '';
  14003. return (v === undefined || v === null) ? defaultValue : String(v);
  14004. },
  14005. sortType: st.asUCString,
  14006. type: 'string'
  14007. },
  14008. /**
  14009. * @property {Object} INT
  14010. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  14011. * <p>The synonym <code>INTEGER</code> is equivalent.</p>
  14012. */
  14013. INT: {
  14014. convert: function(v) {
  14015. return v !== undefined && v !== null && v !== '' ?
  14016. parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
  14017. },
  14018. sortType: st.none,
  14019. type: 'int'
  14020. },
  14021. /**
  14022. * @property {Object} FLOAT
  14023. * This data type means that the raw data is converted into a number before it is placed into a Record.
  14024. * <p>The synonym <code>NUMBER</code> is equivalent.</p>
  14025. */
  14026. FLOAT: {
  14027. convert: function(v) {
  14028. return v !== undefined && v !== null && v !== '' ?
  14029. parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
  14030. },
  14031. sortType: st.none,
  14032. type: 'float'
  14033. },
  14034. /**
  14035. * @property {Object} BOOL
  14036. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  14037. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  14038. * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
  14039. */
  14040. BOOL: {
  14041. convert: function(v) {
  14042. if (this.useNull && (v === undefined || v === null || v === '')) {
  14043. return null;
  14044. }
  14045. return v === true || v === 'true' || v == 1;
  14046. },
  14047. sortType: st.none,
  14048. type: 'bool'
  14049. },
  14050. /**
  14051. * @property {Object} DATE
  14052. * This data type means that the raw data is converted into a Date before it is placed into a Record.
  14053. * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
  14054. * being applied.
  14055. */
  14056. DATE: {
  14057. convert: function(v) {
  14058. var df = this.dateFormat,
  14059. parsed;
  14060. if (!v) {
  14061. return null;
  14062. }
  14063. if (Ext.isDate(v)) {
  14064. return v;
  14065. }
  14066. if (df) {
  14067. if (df == 'timestamp') {
  14068. return new Date(v*1000);
  14069. }
  14070. if (df == 'time') {
  14071. return new Date(parseInt(v, 10));
  14072. }
  14073. return Ext.Date.parse(v, df);
  14074. }
  14075. parsed = Date.parse(v);
  14076. return parsed ? new Date(parsed) : null;
  14077. },
  14078. sortType: st.asDate,
  14079. type: 'date'
  14080. }
  14081. });
  14082. Ext.apply(Ext.data.Types, {
  14083. /**
  14084. * @property {Object} BOOLEAN
  14085. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  14086. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  14087. * <p>The synonym <code>BOOL</code> is equivalent.</p>
  14088. */
  14089. BOOLEAN: this.BOOL,
  14090. /**
  14091. * @property {Object} INTEGER
  14092. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  14093. * <p>The synonym <code>INT</code> is equivalent.</p>
  14094. */
  14095. INTEGER: this.INT,
  14096. /**
  14097. * @property {Object} NUMBER
  14098. * This data type means that the raw data is converted into a number before it is placed into a Record.
  14099. * <p>The synonym <code>FLOAT</code> is equivalent.</p>
  14100. */
  14101. NUMBER: this.FLOAT
  14102. });
  14103. });
  14104. /**
  14105. * @author Ed Spencer
  14106. *
  14107. * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
  14108. * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
  14109. * Ext.data.Model Model}. For example, we might set up a model like this:
  14110. *
  14111. * Ext.define('User', {
  14112. * extend: 'Ext.data.Model',
  14113. * fields: [
  14114. * 'name', 'email',
  14115. * {name: 'age', type: 'int'},
  14116. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  14117. * ]
  14118. * });
  14119. *
  14120. * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
  14121. * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
  14122. * up with the 'auto' type. It's as if we'd done this instead:
  14123. *
  14124. * Ext.define('User', {
  14125. * extend: 'Ext.data.Model',
  14126. * fields: [
  14127. * {name: 'name', type: 'auto'},
  14128. * {name: 'email', type: 'auto'},
  14129. * {name: 'age', type: 'int'},
  14130. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  14131. * ]
  14132. * });
  14133. *
  14134. * # Types and conversion
  14135. *
  14136. * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
  14137. * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
  14138. * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
  14139. *
  14140. * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
  14141. * this using a {@link #convert} function. Here, we're going to create a new field based on another:
  14142. *
  14143. * Ext.define('User', {
  14144. * extend: 'Ext.data.Model',
  14145. * fields: [
  14146. * 'name', 'email',
  14147. * {name: 'age', type: 'int'},
  14148. * {name: 'gender', type: 'string', defaultValue: 'Unknown'},
  14149. *
  14150. * {
  14151. * name: 'firstName',
  14152. * convert: function(value, record) {
  14153. * var fullName = record.get('name'),
  14154. * splits = fullName.split(" "),
  14155. * firstName = splits[0];
  14156. *
  14157. * return firstName;
  14158. * }
  14159. * }
  14160. * ]
  14161. * });
  14162. *
  14163. * Now when we create a new User, the firstName is populated automatically based on the name:
  14164. *
  14165. * var ed = Ext.create('User', {name: 'Ed Spencer'});
  14166. *
  14167. * console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
  14168. *
  14169. * In fact, if we log out all of the data inside ed, we'll see this:
  14170. *
  14171. * console.log(ed.data);
  14172. *
  14173. * //outputs this:
  14174. * {
  14175. * age: 0,
  14176. * email: "",
  14177. * firstName: "Ed",
  14178. * gender: "Unknown",
  14179. * name: "Ed Spencer"
  14180. * }
  14181. *
  14182. * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
  14183. * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
  14184. * that now. Let's correct that and satisfy ourselves that the types work as we expect:
  14185. *
  14186. * ed.set('gender', 'Male');
  14187. * ed.get('gender'); //returns 'Male'
  14188. *
  14189. * ed.set('age', 25.4);
  14190. * ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
  14191. */
  14192. Ext.define('Ext.data.Field', {
  14193. requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
  14194. alias: 'data.field',
  14195. constructor : function(config) {
  14196. if (Ext.isString(config)) {
  14197. config = {name: config};
  14198. }
  14199. Ext.apply(this, config);
  14200. var types = Ext.data.Types,
  14201. st = this.sortType,
  14202. t;
  14203. if (this.type) {
  14204. if (Ext.isString(this.type)) {
  14205. this.type = types[this.type.toUpperCase()] || types.AUTO;
  14206. }
  14207. } else {
  14208. this.type = types.AUTO;
  14209. }
  14210. // named sortTypes are supported, here we look them up
  14211. if (Ext.isString(st)) {
  14212. this.sortType = Ext.data.SortTypes[st];
  14213. } else if(Ext.isEmpty(st)) {
  14214. this.sortType = this.type.sortType;
  14215. }
  14216. if (!this.convert) {
  14217. this.convert = this.type.convert;
  14218. }
  14219. },
  14220. /**
  14221. * @cfg {String} name
  14222. *
  14223. * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
  14224. * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
  14225. *
  14226. * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
  14227. * just a String for the field name.
  14228. */
  14229. /**
  14230. * @cfg {String/Object} type
  14231. *
  14232. * The data type for automatic conversion from received data to the *stored* value if
  14233. * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
  14234. * Possible values are
  14235. *
  14236. * - auto (Default, implies no conversion)
  14237. * - string
  14238. * - int
  14239. * - float
  14240. * - boolean
  14241. * - date
  14242. *
  14243. * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
  14244. *
  14245. * Developers may create their own application-specific data types by defining new members of the {@link
  14246. * Ext.data.Types} class.
  14247. */
  14248. /**
  14249. * @cfg {Function} convert
  14250. *
  14251. * A function which converts the value provided by the Reader into an object that will be stored in the Model.
  14252. * It is passed the following parameters:
  14253. *
  14254. * - **v** : Mixed
  14255. *
  14256. * The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
  14257. * defaultValue}`.
  14258. *
  14259. * - **rec** : Ext.data.Model
  14260. *
  14261. * The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
  14262. * at this point as the fields are read in the order that they are defined in your
  14263. * {@link Ext.data.Model#fields fields} array.
  14264. *
  14265. * Example of convert functions:
  14266. *
  14267. * function fullName(v, record){
  14268. * return record.name.last + ', ' + record.name.first;
  14269. * }
  14270. *
  14271. * function location(v, record){
  14272. * return !record.city ? '' : (record.city + ', ' + record.state);
  14273. * }
  14274. *
  14275. * Ext.define('Dude', {
  14276. * extend: 'Ext.data.Model',
  14277. * fields: [
  14278. * {name: 'fullname', convert: fullName},
  14279. * {name: 'firstname', mapping: 'name.first'},
  14280. * {name: 'lastname', mapping: 'name.last'},
  14281. * {name: 'city', defaultValue: 'homeless'},
  14282. * 'state',
  14283. * {name: 'location', convert: location}
  14284. * ]
  14285. * });
  14286. *
  14287. * // create the data store
  14288. * var store = Ext.create('Ext.data.Store', {
  14289. * reader: {
  14290. * type: 'json',
  14291. * model: 'Dude',
  14292. * idProperty: 'key',
  14293. * root: 'daRoot',
  14294. * totalProperty: 'total'
  14295. * }
  14296. * });
  14297. *
  14298. * var myData = [
  14299. * { key: 1,
  14300. * name: { first: 'Fat', last: 'Albert' }
  14301. * // notice no city, state provided in data object
  14302. * },
  14303. * { key: 2,
  14304. * name: { first: 'Barney', last: 'Rubble' },
  14305. * city: 'Bedrock', state: 'Stoneridge'
  14306. * },
  14307. * { key: 3,
  14308. * name: { first: 'Cliff', last: 'Claven' },
  14309. * city: 'Boston', state: 'MA'
  14310. * }
  14311. * ];
  14312. */
  14313. /**
  14314. * @cfg {String} dateFormat
  14315. *
  14316. * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
  14317. *
  14318. * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
  14319. * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
  14320. * timestamp. See {@link Ext.Date}.
  14321. */
  14322. dateFormat: null,
  14323. /**
  14324. * @cfg {Boolean} useNull
  14325. *
  14326. * Use when converting received data into a Number type (either int or float). If the value cannot be
  14327. * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
  14328. */
  14329. useNull: false,
  14330. /**
  14331. * @cfg {Object} defaultValue
  14332. *
  14333. * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
  14334. * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
  14335. * (i.e. undefined). Defaults to "".
  14336. */
  14337. defaultValue: "",
  14338. /**
  14339. * @cfg {String/Number} mapping
  14340. *
  14341. * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
  14342. * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
  14343. * as the field name, the mapping may be omitted.
  14344. *
  14345. * The form of the mapping expression depends on the Reader being used.
  14346. *
  14347. * - {@link Ext.data.reader.Json}
  14348. *
  14349. * The mapping is a string containing the javascript expression to reference the data from an element of the data
  14350. * item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
  14351. *
  14352. * - {@link Ext.data.reader.Xml}
  14353. *
  14354. * The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
  14355. * {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
  14356. *
  14357. * - {@link Ext.data.reader.Array}
  14358. *
  14359. * The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
  14360. * Array position.
  14361. *
  14362. * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
  14363. * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
  14364. * return the desired data.
  14365. */
  14366. mapping: null,
  14367. /**
  14368. * @cfg {Function} sortType
  14369. *
  14370. * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
  14371. * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
  14372. *
  14373. * // current sort after sort we want
  14374. * // +-+------+ +-+------+
  14375. * // |1|First | |1|First |
  14376. * // |2|Last | |3|Second|
  14377. * // |3|Second| |2|Last |
  14378. * // +-+------+ +-+------+
  14379. *
  14380. * sortType: function(value) {
  14381. * switch (value.toLowerCase()) // native toLowerCase():
  14382. * {
  14383. * case 'first': return 1;
  14384. * case 'second': return 2;
  14385. * default: return 3;
  14386. * }
  14387. * }
  14388. */
  14389. sortType : null,
  14390. /**
  14391. * @cfg {String} sortDir
  14392. *
  14393. * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
  14394. */
  14395. sortDir : "ASC",
  14396. /**
  14397. * @cfg {Boolean} allowBlank
  14398. * @private
  14399. *
  14400. * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
  14401. * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
  14402. */
  14403. allowBlank : true,
  14404. /**
  14405. * @cfg {Boolean} persist
  14406. *
  14407. * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
  14408. * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
  14409. * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
  14410. */
  14411. persist: true
  14412. });
  14413. /**
  14414. * @class Ext.util.AbstractMixedCollection
  14415. * @private
  14416. */
  14417. Ext.define('Ext.util.AbstractMixedCollection', {
  14418. requires: ['Ext.util.Filter'],
  14419. mixins: {
  14420. observable: 'Ext.util.Observable'
  14421. },
  14422. constructor: function(allowFunctions, keyFn) {
  14423. var me = this;
  14424. me.items = [];
  14425. me.map = {};
  14426. me.keys = [];
  14427. me.length = 0;
  14428. me.addEvents(
  14429. /**
  14430. * @event clear
  14431. * Fires when the collection is cleared.
  14432. */
  14433. 'clear',
  14434. /**
  14435. * @event add
  14436. * Fires when an item is added to the collection.
  14437. * @param {Number} index The index at which the item was added.
  14438. * @param {Object} o The item added.
  14439. * @param {String} key The key associated with the added item.
  14440. */
  14441. 'add',
  14442. /**
  14443. * @event replace
  14444. * Fires when an item is replaced in the collection.
  14445. * @param {String} key he key associated with the new added.
  14446. * @param {Object} old The item being replaced.
  14447. * @param {Object} new The new item.
  14448. */
  14449. 'replace',
  14450. /**
  14451. * @event remove
  14452. * Fires when an item is removed from the collection.
  14453. * @param {Object} o The item being removed.
  14454. * @param {String} key (optional) The key associated with the removed item.
  14455. */
  14456. 'remove'
  14457. );
  14458. me.allowFunctions = allowFunctions === true;
  14459. if (keyFn) {
  14460. me.getKey = keyFn;
  14461. }
  14462. me.mixins.observable.constructor.call(me);
  14463. },
  14464. /**
  14465. * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
  14466. * function should add function references to the collection. Defaults to
  14467. * <tt>false</tt>.
  14468. */
  14469. allowFunctions : false,
  14470. /**
  14471. * Adds an item to the collection. Fires the {@link #add} event when complete.
  14472. * @param {String} key <p>The key to associate with the item, or the new item.</p>
  14473. * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
  14474. * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
  14475. * the MixedCollection will be able to <i>derive</i> the key for the new item.
  14476. * In this case just pass the new item in this parameter.</p>
  14477. * @param {Object} o The item to add.
  14478. * @return {Object} The item added.
  14479. */
  14480. add : function(key, obj){
  14481. var me = this,
  14482. myObj = obj,
  14483. myKey = key,
  14484. old;
  14485. if (arguments.length == 1) {
  14486. myObj = myKey;
  14487. myKey = me.getKey(myObj);
  14488. }
  14489. if (typeof myKey != 'undefined' && myKey !== null) {
  14490. old = me.map[myKey];
  14491. if (typeof old != 'undefined') {
  14492. return me.replace(myKey, myObj);
  14493. }
  14494. me.map[myKey] = myObj;
  14495. }
  14496. me.length++;
  14497. me.items.push(myObj);
  14498. me.keys.push(myKey);
  14499. me.fireEvent('add', me.length - 1, myObj, myKey);
  14500. return myObj;
  14501. },
  14502. /**
  14503. * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
  14504. * simply returns <b><code>item.id</code></b> but you can provide your own implementation
  14505. * to return a different value as in the following examples:<pre><code>
  14506. // normal way
  14507. var mc = new Ext.util.MixedCollection();
  14508. mc.add(someEl.dom.id, someEl);
  14509. mc.add(otherEl.dom.id, otherEl);
  14510. //and so on
  14511. // using getKey
  14512. var mc = new Ext.util.MixedCollection();
  14513. mc.getKey = function(el){
  14514. return el.dom.id;
  14515. };
  14516. mc.add(someEl);
  14517. mc.add(otherEl);
  14518. // or via the constructor
  14519. var mc = new Ext.util.MixedCollection(false, function(el){
  14520. return el.dom.id;
  14521. });
  14522. mc.add(someEl);
  14523. mc.add(otherEl);
  14524. * </code></pre>
  14525. * @param {Object} item The item for which to find the key.
  14526. * @return {Object} The key for the passed item.
  14527. */
  14528. getKey : function(o){
  14529. return o.id;
  14530. },
  14531. /**
  14532. * Replaces an item in the collection. Fires the {@link #replace} event when complete.
  14533. * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
  14534. * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
  14535. * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
  14536. * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
  14537. * with one having the same key value, then just pass the replacement item in this parameter.</p>
  14538. * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
  14539. * with that key.
  14540. * @return {Object} The new item.
  14541. */
  14542. replace : function(key, o){
  14543. var me = this,
  14544. old,
  14545. index;
  14546. if (arguments.length == 1) {
  14547. o = arguments[0];
  14548. key = me.getKey(o);
  14549. }
  14550. old = me.map[key];
  14551. if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
  14552. return me.add(key, o);
  14553. }
  14554. index = me.indexOfKey(key);
  14555. me.items[index] = o;
  14556. me.map[key] = o;
  14557. me.fireEvent('replace', key, old, o);
  14558. return o;
  14559. },
  14560. /**
  14561. * Adds all elements of an Array or an Object to the collection.
  14562. * @param {Object/Array} objs An Object containing properties which will be added
  14563. * to the collection, or an Array of values, each of which are added to the collection.
  14564. * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
  14565. * has been set to <tt>true</tt>.
  14566. */
  14567. addAll : function(objs){
  14568. var me = this,
  14569. i = 0,
  14570. args,
  14571. len,
  14572. key;
  14573. if (arguments.length > 1 || Ext.isArray(objs)) {
  14574. args = arguments.length > 1 ? arguments : objs;
  14575. for (len = args.length; i < len; i++) {
  14576. me.add(args[i]);
  14577. }
  14578. } else {
  14579. for (key in objs) {
  14580. if (objs.hasOwnProperty(key)) {
  14581. if (me.allowFunctions || typeof objs[key] != 'function') {
  14582. me.add(key, objs[key]);
  14583. }
  14584. }
  14585. }
  14586. }
  14587. },
  14588. /**
  14589. * Executes the specified function once for every item in the collection, passing the following arguments:
  14590. * <div class="mdetail-params"><ul>
  14591. * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
  14592. * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
  14593. * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
  14594. * </ul></div>
  14595. * The function should return a boolean value. Returning false from the function will stop the iteration.
  14596. * @param {Function} fn The function to execute for each item.
  14597. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
  14598. */
  14599. each : function(fn, scope){
  14600. var items = [].concat(this.items), // each safe for removal
  14601. i = 0,
  14602. len = items.length,
  14603. item;
  14604. for (; i < len; i++) {
  14605. item = items[i];
  14606. if (fn.call(scope || item, item, i, len) === false) {
  14607. break;
  14608. }
  14609. }
  14610. },
  14611. /**
  14612. * Executes the specified function once for every key in the collection, passing each
  14613. * key, and its associated item as the first two parameters.
  14614. * @param {Function} fn The function to execute for each item.
  14615. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
  14616. */
  14617. eachKey : function(fn, scope){
  14618. var keys = this.keys,
  14619. items = this.items,
  14620. i = 0,
  14621. len = keys.length;
  14622. for (; i < len; i++) {
  14623. fn.call(scope || window, keys[i], items[i], i, len);
  14624. }
  14625. },
  14626. /**
  14627. * Returns the first item in the collection which elicits a true return value from the
  14628. * passed selection function.
  14629. * @param {Function} fn The selection function to execute for each item.
  14630. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
  14631. * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
  14632. */
  14633. findBy : function(fn, scope) {
  14634. var keys = this.keys,
  14635. items = this.items,
  14636. i = 0,
  14637. len = items.length;
  14638. for (; i < len; i++) {
  14639. if (fn.call(scope || window, items[i], keys[i])) {
  14640. return items[i];
  14641. }
  14642. }
  14643. return null;
  14644. },
  14645. //<deprecated since="0.99">
  14646. find : function() {
  14647. if (Ext.isDefined(Ext.global.console)) {
  14648. Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
  14649. }
  14650. return this.findBy.apply(this, arguments);
  14651. },
  14652. //</deprecated>
  14653. /**
  14654. * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
  14655. * @param {Number} index The index to insert the item at.
  14656. * @param {String} key The key to associate with the new item, or the item itself.
  14657. * @param {Object} o (optional) If the second parameter was a key, the new item.
  14658. * @return {Object} The item inserted.
  14659. */
  14660. insert : function(index, key, obj){
  14661. var me = this,
  14662. myKey = key,
  14663. myObj = obj;
  14664. if (arguments.length == 2) {
  14665. myObj = myKey;
  14666. myKey = me.getKey(myObj);
  14667. }
  14668. if (me.containsKey(myKey)) {
  14669. me.suspendEvents();
  14670. me.removeAtKey(myKey);
  14671. me.resumeEvents();
  14672. }
  14673. if (index >= me.length) {
  14674. return me.add(myKey, myObj);
  14675. }
  14676. me.length++;
  14677. Ext.Array.splice(me.items, index, 0, myObj);
  14678. if (typeof myKey != 'undefined' && myKey !== null) {
  14679. me.map[myKey] = myObj;
  14680. }
  14681. Ext.Array.splice(me.keys, index, 0, myKey);
  14682. me.fireEvent('add', index, myObj, myKey);
  14683. return myObj;
  14684. },
  14685. /**
  14686. * Remove an item from the collection.
  14687. * @param {Object} o The item to remove.
  14688. * @return {Object} The item removed or false if no item was removed.
  14689. */
  14690. remove : function(o){
  14691. return this.removeAt(this.indexOf(o));
  14692. },
  14693. /**
  14694. * Remove all items in the passed array from the collection.
  14695. * @param {Array} items An array of items to be removed.
  14696. * @return {Ext.util.MixedCollection} this object
  14697. */
  14698. removeAll : function(items){
  14699. Ext.each(items || [], function(item) {
  14700. this.remove(item);
  14701. }, this);
  14702. return this;
  14703. },
  14704. /**
  14705. * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
  14706. * @param {Number} index The index within the collection of the item to remove.
  14707. * @return {Object} The item removed or false if no item was removed.
  14708. */
  14709. removeAt : function(index){
  14710. var me = this,
  14711. o,
  14712. key;
  14713. if (index < me.length && index >= 0) {
  14714. me.length--;
  14715. o = me.items[index];
  14716. Ext.Array.erase(me.items, index, 1);
  14717. key = me.keys[index];
  14718. if (typeof key != 'undefined') {
  14719. delete me.map[key];
  14720. }
  14721. Ext.Array.erase(me.keys, index, 1);
  14722. me.fireEvent('remove', o, key);
  14723. return o;
  14724. }
  14725. return false;
  14726. },
  14727. /**
  14728. * Removed an item associated with the passed key fom the collection.
  14729. * @param {String} key The key of the item to remove.
  14730. * @return {Object} The item removed or false if no item was removed.
  14731. */
  14732. removeAtKey : function(key){
  14733. return this.removeAt(this.indexOfKey(key));
  14734. },
  14735. /**
  14736. * Returns the number of items in the collection.
  14737. * @return {Number} the number of items in the collection.
  14738. */
  14739. getCount : function(){
  14740. return this.length;
  14741. },
  14742. /**
  14743. * Returns index within the collection of the passed Object.
  14744. * @param {Object} o The item to find the index of.
  14745. * @return {Number} index of the item. Returns -1 if not found.
  14746. */
  14747. indexOf : function(o){
  14748. return Ext.Array.indexOf(this.items, o);
  14749. },
  14750. /**
  14751. * Returns index within the collection of the passed key.
  14752. * @param {String} key The key to find the index of.
  14753. * @return {Number} index of the key.
  14754. */
  14755. indexOfKey : function(key){
  14756. return Ext.Array.indexOf(this.keys, key);
  14757. },
  14758. /**
  14759. * Returns the item associated with the passed key OR index.
  14760. * Key has priority over index. This is the equivalent
  14761. * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
  14762. * @param {String/Number} key The key or index of the item.
  14763. * @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
  14764. * If an item was found, but is a Class, returns <tt>null</tt>.
  14765. */
  14766. get : function(key) {
  14767. var me = this,
  14768. mk = me.map[key],
  14769. item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
  14770. return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
  14771. },
  14772. /**
  14773. * Returns the item at the specified index.
  14774. * @param {Number} index The index of the item.
  14775. * @return {Object} The item at the specified index.
  14776. */
  14777. getAt : function(index) {
  14778. return this.items[index];
  14779. },
  14780. /**
  14781. * Returns the item associated with the passed key.
  14782. * @param {String/Number} key The key of the item.
  14783. * @return {Object} The item associated with the passed key.
  14784. */
  14785. getByKey : function(key) {
  14786. return this.map[key];
  14787. },
  14788. /**
  14789. * Returns true if the collection contains the passed Object as an item.
  14790. * @param {Object} o The Object to look for in the collection.
  14791. * @return {Boolean} True if the collection contains the Object as an item.
  14792. */
  14793. contains : function(o){
  14794. return Ext.Array.contains(this.items, o);
  14795. },
  14796. /**
  14797. * Returns true if the collection contains the passed Object as a key.
  14798. * @param {String} key The key to look for in the collection.
  14799. * @return {Boolean} True if the collection contains the Object as a key.
  14800. */
  14801. containsKey : function(key){
  14802. return typeof this.map[key] != 'undefined';
  14803. },
  14804. /**
  14805. * Removes all items from the collection. Fires the {@link #clear} event when complete.
  14806. */
  14807. clear : function(){
  14808. var me = this;
  14809. me.length = 0;
  14810. me.items = [];
  14811. me.keys = [];
  14812. me.map = {};
  14813. me.fireEvent('clear');
  14814. },
  14815. /**
  14816. * Returns the first item in the collection.
  14817. * @return {Object} the first item in the collection..
  14818. */
  14819. first : function() {
  14820. return this.items[0];
  14821. },
  14822. /**
  14823. * Returns the last item in the collection.
  14824. * @return {Object} the last item in the collection..
  14825. */
  14826. last : function() {
  14827. return this.items[this.length - 1];
  14828. },
  14829. /**
  14830. * Collects all of the values of the given property and returns their sum
  14831. * @param {String} property The property to sum by
  14832. * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
  14833. * summing fields in records, where the fields are all stored inside the 'data' object
  14834. * @param {Number} [start=0] The record index to start at
  14835. * @param {Number} [end=-1] The record index to end at
  14836. * @return {Number} The total
  14837. */
  14838. sum: function(property, root, start, end) {
  14839. var values = this.extractValues(property, root),
  14840. length = values.length,
  14841. sum = 0,
  14842. i;
  14843. start = start || 0;
  14844. end = (end || end === 0) ? end : length - 1;
  14845. for (i = start; i <= end; i++) {
  14846. sum += values[i];
  14847. }
  14848. return sum;
  14849. },
  14850. /**
  14851. * Collects unique values of a particular property in this MixedCollection
  14852. * @param {String} property The property to collect on
  14853. * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
  14854. * summing fields in records, where the fields are all stored inside the 'data' object
  14855. * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
  14856. * @return {Array} The unique values
  14857. */
  14858. collect: function(property, root, allowNull) {
  14859. var values = this.extractValues(property, root),
  14860. length = values.length,
  14861. hits = {},
  14862. unique = [],
  14863. value, strValue, i;
  14864. for (i = 0; i < length; i++) {
  14865. value = values[i];
  14866. strValue = String(value);
  14867. if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
  14868. hits[strValue] = true;
  14869. unique.push(value);
  14870. }
  14871. }
  14872. return unique;
  14873. },
  14874. /**
  14875. * @private
  14876. * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
  14877. * functions like sum and collect.
  14878. * @param {String} property The property to extract
  14879. * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
  14880. * extracting field data from Model instances, where the fields are stored inside the 'data' object
  14881. * @return {Array} The extracted values
  14882. */
  14883. extractValues: function(property, root) {
  14884. var values = this.items;
  14885. if (root) {
  14886. values = Ext.Array.pluck(values, root);
  14887. }
  14888. return Ext.Array.pluck(values, property);
  14889. },
  14890. /**
  14891. * Returns a range of items in this collection
  14892. * @param {Number} startIndex (optional) The starting index. Defaults to 0.
  14893. * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
  14894. * @return {Array} An array of items
  14895. */
  14896. getRange : function(start, end){
  14897. var me = this,
  14898. items = me.items,
  14899. range = [],
  14900. i;
  14901. if (items.length < 1) {
  14902. return range;
  14903. }
  14904. start = start || 0;
  14905. end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
  14906. if (start <= end) {
  14907. for (i = start; i <= end; i++) {
  14908. range[range.length] = items[i];
  14909. }
  14910. } else {
  14911. for (i = start; i >= end; i--) {
  14912. range[range.length] = items[i];
  14913. }
  14914. }
  14915. return range;
  14916. },
  14917. /**
  14918. * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
  14919. * property/value pair with optional parameters for substring matching and case sensitivity. See
  14920. * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
  14921. * MixedCollection can be easily filtered by property like this:</p>
  14922. <pre><code>
  14923. //create a simple store with a few people defined
  14924. var people = new Ext.util.MixedCollection();
  14925. people.addAll([
  14926. {id: 1, age: 25, name: 'Ed'},
  14927. {id: 2, age: 24, name: 'Tommy'},
  14928. {id: 3, age: 24, name: 'Arne'},
  14929. {id: 4, age: 26, name: 'Aaron'}
  14930. ]);
  14931. //a new MixedCollection containing only the items where age == 24
  14932. var middleAged = people.filter('age', 24);
  14933. </code></pre>
  14934. *
  14935. *
  14936. * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
  14937. * @param {String/RegExp} value Either string that the property values
  14938. * should start with or a RegExp to test against the property
  14939. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
  14940. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
  14941. * @return {Ext.util.MixedCollection} The new filtered collection
  14942. */
  14943. filter : function(property, value, anyMatch, caseSensitive) {
  14944. var filters = [],
  14945. filterFn;
  14946. //support for the simple case of filtering by property/value
  14947. if (Ext.isString(property)) {
  14948. filters.push(Ext.create('Ext.util.Filter', {
  14949. property : property,
  14950. value : value,
  14951. anyMatch : anyMatch,
  14952. caseSensitive: caseSensitive
  14953. }));
  14954. } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
  14955. filters = filters.concat(property);
  14956. }
  14957. //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
  14958. //so here we construct a function that combines these filters by ANDing them together
  14959. filterFn = function(record) {
  14960. var isMatch = true,
  14961. length = filters.length,
  14962. i;
  14963. for (i = 0; i < length; i++) {
  14964. var filter = filters[i],
  14965. fn = filter.filterFn,
  14966. scope = filter.scope;
  14967. isMatch = isMatch && fn.call(scope, record);
  14968. }
  14969. return isMatch;
  14970. };
  14971. return this.filterBy(filterFn);
  14972. },
  14973. /**
  14974. * Filter by a function. Returns a <i>new</i> collection that has been filtered.
  14975. * The passed function will be called with each object in the collection.
  14976. * If the function returns true, the value is included otherwise it is filtered.
  14977. * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
  14978. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
  14979. * @return {Ext.util.MixedCollection} The new filtered collection
  14980. */
  14981. filterBy : function(fn, scope) {
  14982. var me = this,
  14983. newMC = new this.self(),
  14984. keys = me.keys,
  14985. items = me.items,
  14986. length = items.length,
  14987. i;
  14988. newMC.getKey = me.getKey;
  14989. for (i = 0; i < length; i++) {
  14990. if (fn.call(scope || me, items[i], keys[i])) {
  14991. newMC.add(keys[i], items[i]);
  14992. }
  14993. }
  14994. return newMC;
  14995. },
  14996. /**
  14997. * Finds the index of the first matching object in this collection by a specific property/value.
  14998. * @param {String} property The name of a property on your objects.
  14999. * @param {String/RegExp} value A string that the property values
  15000. * should start with or a RegExp to test against the property.
  15001. * @param {Number} [start=0] The index to start searching at.
  15002. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
  15003. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
  15004. * @return {Number} The matched index or -1
  15005. */
  15006. findIndex : function(property, value, start, anyMatch, caseSensitive){
  15007. if(Ext.isEmpty(value, false)){
  15008. return -1;
  15009. }
  15010. value = this.createValueMatcher(value, anyMatch, caseSensitive);
  15011. return this.findIndexBy(function(o){
  15012. return o && value.test(o[property]);
  15013. }, null, start);
  15014. },
  15015. /**
  15016. * Find the index of the first matching object in this collection by a function.
  15017. * If the function returns <i>true</i> it is considered a match.
  15018. * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
  15019. * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
  15020. * @param {Number} [start=0] The index to start searching at.
  15021. * @return {Number} The matched index or -1
  15022. */
  15023. findIndexBy : function(fn, scope, start){
  15024. var me = this,
  15025. keys = me.keys,
  15026. items = me.items,
  15027. i = start || 0,
  15028. len = items.length;
  15029. for (; i < len; i++) {
  15030. if (fn.call(scope || me, items[i], keys[i])) {
  15031. return i;
  15032. }
  15033. }
  15034. return -1;
  15035. },
  15036. /**
  15037. * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
  15038. * and by Ext.data.Store#filter
  15039. * @private
  15040. * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
  15041. * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
  15042. * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
  15043. * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
  15044. */
  15045. createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
  15046. if (!value.exec) { // not a regex
  15047. var er = Ext.String.escapeRegex;
  15048. value = String(value);
  15049. if (anyMatch === true) {
  15050. value = er(value);
  15051. } else {
  15052. value = '^' + er(value);
  15053. if (exactMatch === true) {
  15054. value += '$';
  15055. }
  15056. }
  15057. value = new RegExp(value, caseSensitive ? '' : 'i');
  15058. }
  15059. return value;
  15060. },
  15061. /**
  15062. * Creates a shallow copy of this collection
  15063. * @return {Ext.util.MixedCollection}
  15064. */
  15065. clone : function() {
  15066. var me = this,
  15067. copy = new this.self(),
  15068. keys = me.keys,
  15069. items = me.items,
  15070. i = 0,
  15071. len = items.length;
  15072. for(; i < len; i++){
  15073. copy.add(keys[i], items[i]);
  15074. }
  15075. copy.getKey = me.getKey;
  15076. return copy;
  15077. }
  15078. });
  15079. /**
  15080. * @docauthor Tommy Maintz <tommy@sencha.com>
  15081. *
  15082. * A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
  15083. *
  15084. * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
  15085. * is more likely you will want to use one of the component classes that import this mixin, such as
  15086. * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
  15087. */
  15088. Ext.define("Ext.util.Sortable", {
  15089. /**
  15090. * @property {Boolean} isSortable
  15091. * Flag denoting that this object is sortable. Always true.
  15092. */
  15093. isSortable: true,
  15094. /**
  15095. * @property {String} defaultSortDirection
  15096. * The default sort direction to use if one is not specified.
  15097. */
  15098. defaultSortDirection: "ASC",
  15099. requires: [
  15100. 'Ext.util.Sorter'
  15101. ],
  15102. /**
  15103. * @property {String} sortRoot
  15104. * The property in each item that contains the data to sort.
  15105. */
  15106. /**
  15107. * Performs initialization of this mixin. Component classes using this mixin should call this method during their
  15108. * own initialization.
  15109. */
  15110. initSortable: function() {
  15111. var me = this,
  15112. sorters = me.sorters;
  15113. /**
  15114. * @property {Ext.util.MixedCollection} sorters
  15115. * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
  15116. */
  15117. me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
  15118. return item.id || item.property;
  15119. });
  15120. if (sorters) {
  15121. me.sorters.addAll(me.decodeSorters(sorters));
  15122. }
  15123. },
  15124. /**
  15125. * Sorts the data in the Store by one or more of its properties. Example usage:
  15126. *
  15127. * //sort by a single field
  15128. * myStore.sort('myField', 'DESC');
  15129. *
  15130. * //sorting by multiple fields
  15131. * myStore.sort([
  15132. * {
  15133. * property : 'age',
  15134. * direction: 'ASC'
  15135. * },
  15136. * {
  15137. * property : 'name',
  15138. * direction: 'DESC'
  15139. * }
  15140. * ]);
  15141. *
  15142. * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
  15143. * the actual sorting to its internal {@link Ext.util.MixedCollection}.
  15144. *
  15145. * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
  15146. *
  15147. * store.sort('myField');
  15148. * store.sort('myField');
  15149. *
  15150. * Is equivalent to this code, because Store handles the toggling automatically:
  15151. *
  15152. * store.sort('myField', 'ASC');
  15153. * store.sort('myField', 'DESC');
  15154. *
  15155. * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
  15156. * {@link Ext.data.Model Model}, or an array of sorter configurations.
  15157. * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
  15158. * @return {Ext.util.Sorter[]}
  15159. */
  15160. sort: function(sorters, direction, where, doSort) {
  15161. var me = this,
  15162. sorter, sorterFn,
  15163. newSorters;
  15164. if (Ext.isArray(sorters)) {
  15165. doSort = where;
  15166. where = direction;
  15167. newSorters = sorters;
  15168. }
  15169. else if (Ext.isObject(sorters)) {
  15170. doSort = where;
  15171. where = direction;
  15172. newSorters = [sorters];
  15173. }
  15174. else if (Ext.isString(sorters)) {
  15175. sorter = me.sorters.get(sorters);
  15176. if (!sorter) {
  15177. sorter = {
  15178. property : sorters,
  15179. direction: direction
  15180. };
  15181. newSorters = [sorter];
  15182. }
  15183. else if (direction === undefined) {
  15184. sorter.toggle();
  15185. }
  15186. else {
  15187. sorter.setDirection(direction);
  15188. }
  15189. }
  15190. if (newSorters && newSorters.length) {
  15191. newSorters = me.decodeSorters(newSorters);
  15192. if (Ext.isString(where)) {
  15193. if (where === 'prepend') {
  15194. sorters = me.sorters.clone().items;
  15195. me.sorters.clear();
  15196. me.sorters.addAll(newSorters);
  15197. me.sorters.addAll(sorters);
  15198. }
  15199. else {
  15200. me.sorters.addAll(newSorters);
  15201. }
  15202. }
  15203. else {
  15204. me.sorters.clear();
  15205. me.sorters.addAll(newSorters);
  15206. }
  15207. }
  15208. if (doSort !== false) {
  15209. me.onBeforeSort(newSorters);
  15210. sorters = me.sorters.items;
  15211. if (sorters.length) {
  15212. //construct an amalgamated sorter function which combines all of the Sorters passed
  15213. sorterFn = function(r1, r2) {
  15214. var result = sorters[0].sort(r1, r2),
  15215. length = sorters.length,
  15216. i;
  15217. //if we have more than one sorter, OR any additional sorter functions together
  15218. for (i = 1; i < length; i++) {
  15219. result = result || sorters[i].sort.call(this, r1, r2);
  15220. }
  15221. return result;
  15222. };
  15223. me.doSort(sorterFn);
  15224. }
  15225. }
  15226. return sorters;
  15227. },
  15228. onBeforeSort: Ext.emptyFn,
  15229. /**
  15230. * @private
  15231. * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
  15232. * @param {Object[]} sorters The sorters array
  15233. * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
  15234. */
  15235. decodeSorters: function(sorters) {
  15236. if (!Ext.isArray(sorters)) {
  15237. if (sorters === undefined) {
  15238. sorters = [];
  15239. } else {
  15240. sorters = [sorters];
  15241. }
  15242. }
  15243. var length = sorters.length,
  15244. Sorter = Ext.util.Sorter,
  15245. fields = this.model ? this.model.prototype.fields : null,
  15246. field,
  15247. config, i;
  15248. for (i = 0; i < length; i++) {
  15249. config = sorters[i];
  15250. if (!(config instanceof Sorter)) {
  15251. if (Ext.isString(config)) {
  15252. config = {
  15253. property: config
  15254. };
  15255. }
  15256. Ext.applyIf(config, {
  15257. root : this.sortRoot,
  15258. direction: "ASC"
  15259. });
  15260. //support for 3.x style sorters where a function can be defined as 'fn'
  15261. if (config.fn) {
  15262. config.sorterFn = config.fn;
  15263. }
  15264. //support a function to be passed as a sorter definition
  15265. if (typeof config == 'function') {
  15266. config = {
  15267. sorterFn: config
  15268. };
  15269. }
  15270. // ensure sortType gets pushed on if necessary
  15271. if (fields && !config.transform) {
  15272. field = fields.get(config.property);
  15273. config.transform = field ? field.sortType : undefined;
  15274. }
  15275. sorters[i] = Ext.create('Ext.util.Sorter', config);
  15276. }
  15277. }
  15278. return sorters;
  15279. },
  15280. getSorters: function() {
  15281. return this.sorters.items;
  15282. }
  15283. });
  15284. /**
  15285. * @class Ext.util.MixedCollection
  15286. * <p>
  15287. * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
  15288. * must be unique, the same key cannot exist twice. This collection is ordered, items in the
  15289. * collection can be accessed by index or via the key. Newly added items are added to
  15290. * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
  15291. * is heavier and provides more functionality. Sample usage:
  15292. * <pre><code>
  15293. var coll = new Ext.util.MixedCollection();
  15294. coll.add('key1', 'val1');
  15295. coll.add('key2', 'val2');
  15296. coll.add('key3', 'val3');
  15297. console.log(coll.get('key1')); // prints 'val1'
  15298. console.log(coll.indexOfKey('key3')); // prints 2
  15299. * </code></pre>
  15300. *
  15301. * <p>
  15302. * The MixedCollection also has support for sorting and filtering of the values in the collection.
  15303. * <pre><code>
  15304. var coll = new Ext.util.MixedCollection();
  15305. coll.add('key1', 100);
  15306. coll.add('key2', -100);
  15307. coll.add('key3', 17);
  15308. coll.add('key4', 0);
  15309. var biggerThanZero = coll.filterBy(function(value){
  15310. return value > 0;
  15311. });
  15312. console.log(biggerThanZero.getCount()); // prints 2
  15313. * </code></pre>
  15314. * </p>
  15315. */
  15316. Ext.define('Ext.util.MixedCollection', {
  15317. extend: 'Ext.util.AbstractMixedCollection',
  15318. mixins: {
  15319. sortable: 'Ext.util.Sortable'
  15320. },
  15321. /**
  15322. * Creates new MixedCollection.
  15323. * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
  15324. * function should add function references to the collection. Defaults to
  15325. * <tt>false</tt>.
  15326. * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
  15327. * and return the key value for that item. This is used when available to look up the key on items that
  15328. * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
  15329. * equivalent to providing an implementation for the {@link #getKey} method.
  15330. */
  15331. constructor: function() {
  15332. var me = this;
  15333. me.callParent(arguments);
  15334. me.addEvents('sort');
  15335. me.mixins.sortable.initSortable.call(me);
  15336. },
  15337. doSort: function(sorterFn) {
  15338. this.sortBy(sorterFn);
  15339. },
  15340. /**
  15341. * @private
  15342. * Performs the actual sorting based on a direction and a sorting function. Internally,
  15343. * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
  15344. * the sorted array data back into this.items and this.keys
  15345. * @param {String} property Property to sort by ('key', 'value', or 'index')
  15346. * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
  15347. * @param {Function} fn (optional) Comparison function that defines the sort order.
  15348. * Defaults to sorting by numeric value.
  15349. */
  15350. _sort : function(property, dir, fn){
  15351. var me = this,
  15352. i, len,
  15353. dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
  15354. //this is a temporary array used to apply the sorting function
  15355. c = [],
  15356. keys = me.keys,
  15357. items = me.items;
  15358. //default to a simple sorter function if one is not provided
  15359. fn = fn || function(a, b) {
  15360. return a - b;
  15361. };
  15362. //copy all the items into a temporary array, which we will sort
  15363. for(i = 0, len = items.length; i < len; i++){
  15364. c[c.length] = {
  15365. key : keys[i],
  15366. value: items[i],
  15367. index: i
  15368. };
  15369. }
  15370. //sort the temporary array
  15371. Ext.Array.sort(c, function(a, b){
  15372. var v = fn(a[property], b[property]) * dsc;
  15373. if(v === 0){
  15374. v = (a.index < b.index ? -1 : 1);
  15375. }
  15376. return v;
  15377. });
  15378. //copy the temporary array back into the main this.items and this.keys objects
  15379. for(i = 0, len = c.length; i < len; i++){
  15380. items[i] = c[i].value;
  15381. keys[i] = c[i].key;
  15382. }
  15383. me.fireEvent('sort', me);
  15384. },
  15385. /**
  15386. * Sorts the collection by a single sorter function
  15387. * @param {Function} sorterFn The function to sort by
  15388. */
  15389. sortBy: function(sorterFn) {
  15390. var me = this,
  15391. items = me.items,
  15392. keys = me.keys,
  15393. length = items.length,
  15394. temp = [],
  15395. i;
  15396. //first we create a copy of the items array so that we can sort it
  15397. for (i = 0; i < length; i++) {
  15398. temp[i] = {
  15399. key : keys[i],
  15400. value: items[i],
  15401. index: i
  15402. };
  15403. }
  15404. Ext.Array.sort(temp, function(a, b) {
  15405. var v = sorterFn(a.value, b.value);
  15406. if (v === 0) {
  15407. v = (a.index < b.index ? -1 : 1);
  15408. }
  15409. return v;
  15410. });
  15411. //copy the temporary array back into the main this.items and this.keys objects
  15412. for (i = 0; i < length; i++) {
  15413. items[i] = temp[i].value;
  15414. keys[i] = temp[i].key;
  15415. }
  15416. me.fireEvent('sort', me, items, keys);
  15417. },
  15418. /**
  15419. * Reorders each of the items based on a mapping from old index to new index. Internally this
  15420. * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
  15421. * @param {Object} mapping Mapping from old item index to new item index
  15422. */
  15423. reorder: function(mapping) {
  15424. var me = this,
  15425. items = me.items,
  15426. index = 0,
  15427. length = items.length,
  15428. order = [],
  15429. remaining = [],
  15430. oldIndex;
  15431. me.suspendEvents();
  15432. //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
  15433. for (oldIndex in mapping) {
  15434. order[mapping[oldIndex]] = items[oldIndex];
  15435. }
  15436. for (index = 0; index < length; index++) {
  15437. if (mapping[index] == undefined) {
  15438. remaining.push(items[index]);
  15439. }
  15440. }
  15441. for (index = 0; index < length; index++) {
  15442. if (order[index] == undefined) {
  15443. order[index] = remaining.shift();
  15444. }
  15445. }
  15446. me.clear();
  15447. me.addAll(order);
  15448. me.resumeEvents();
  15449. me.fireEvent('sort', me);
  15450. },
  15451. /**
  15452. * Sorts this collection by <b>key</b>s.
  15453. * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
  15454. * @param {Function} fn (optional) Comparison function that defines the sort order.
  15455. * Defaults to sorting by case insensitive string.
  15456. */
  15457. sortByKey : function(dir, fn){
  15458. this._sort('key', dir, fn || function(a, b){
  15459. var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
  15460. return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  15461. });
  15462. }
  15463. });
  15464. /**
  15465. * @author Ed Spencer
  15466. * @class Ext.data.Errors
  15467. * @extends Ext.util.MixedCollection
  15468. *
  15469. * <p>Wraps a collection of validation error responses and provides convenient functions for
  15470. * accessing and errors for specific fields.</p>
  15471. *
  15472. * <p>Usually this class does not need to be instantiated directly - instances are instead created
  15473. * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
  15474. *
  15475. <pre><code>
  15476. //validate some existing model instance - in this case it returned 2 failures messages
  15477. var errors = myModel.validate();
  15478. errors.isValid(); //false
  15479. errors.length; //2
  15480. errors.getByField('name'); // [{field: 'name', message: 'must be present'}]
  15481. errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
  15482. </code></pre>
  15483. */
  15484. Ext.define('Ext.data.Errors', {
  15485. extend: 'Ext.util.MixedCollection',
  15486. /**
  15487. * Returns true if there are no errors in the collection
  15488. * @return {Boolean}
  15489. */
  15490. isValid: function() {
  15491. return this.length === 0;
  15492. },
  15493. /**
  15494. * Returns all of the errors for the given field
  15495. * @param {String} fieldName The field to get errors for
  15496. * @return {Object[]} All errors for the given field
  15497. */
  15498. getByField: function(fieldName) {
  15499. var errors = [],
  15500. error, field, i;
  15501. for (i = 0; i < this.length; i++) {
  15502. error = this.items[i];
  15503. if (error.field == fieldName) {
  15504. errors.push(error);
  15505. }
  15506. }
  15507. return errors;
  15508. }
  15509. });
  15510. /**
  15511. * @author Ed Spencer
  15512. *
  15513. * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
  15514. * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
  15515. * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
  15516. * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
  15517. *
  15518. * Ext.create('Ext.data.Store', {
  15519. * model: 'User',
  15520. * proxy: {
  15521. * type: 'ajax',
  15522. * url : 'users.json',
  15523. * reader: {
  15524. * type: 'json',
  15525. * root: 'users'
  15526. * }
  15527. * },
  15528. * });
  15529. *
  15530. * The above reader is configured to consume a JSON string that looks something like this:
  15531. *
  15532. * {
  15533. * "success": true,
  15534. * "users": [
  15535. * { "name": "User 1" },
  15536. * { "name": "User 2" }
  15537. * ]
  15538. * }
  15539. *
  15540. *
  15541. * # Loading Nested Data
  15542. *
  15543. * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
  15544. * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
  15545. * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
  15546. *
  15547. * Ext.define("User", {
  15548. * extend: 'Ext.data.Model',
  15549. * fields: [
  15550. * 'id', 'name'
  15551. * ],
  15552. *
  15553. * hasMany: {model: 'Order', name: 'orders'},
  15554. *
  15555. * proxy: {
  15556. * type: 'rest',
  15557. * url : 'users.json',
  15558. * reader: {
  15559. * type: 'json',
  15560. * root: 'users'
  15561. * }
  15562. * }
  15563. * });
  15564. *
  15565. * Ext.define("Order", {
  15566. * extend: 'Ext.data.Model',
  15567. * fields: [
  15568. * 'id', 'total'
  15569. * ],
  15570. *
  15571. * hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
  15572. * belongsTo: 'User'
  15573. * });
  15574. *
  15575. * Ext.define("OrderItem", {
  15576. * extend: 'Ext.data.Model',
  15577. * fields: [
  15578. * 'id', 'price', 'quantity', 'order_id', 'product_id'
  15579. * ],
  15580. *
  15581. * belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
  15582. * });
  15583. *
  15584. * Ext.define("Product", {
  15585. * extend: 'Ext.data.Model',
  15586. * fields: [
  15587. * 'id', 'name'
  15588. * ],
  15589. *
  15590. * hasMany: 'OrderItem'
  15591. * });
  15592. *
  15593. * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
  15594. * Finally, each OrderItem has a single Product. This allows us to consume data like this:
  15595. *
  15596. * {
  15597. * "users": [
  15598. * {
  15599. * "id": 123,
  15600. * "name": "Ed",
  15601. * "orders": [
  15602. * {
  15603. * "id": 50,
  15604. * "total": 100,
  15605. * "order_items": [
  15606. * {
  15607. * "id" : 20,
  15608. * "price" : 40,
  15609. * "quantity": 2,
  15610. * "product" : {
  15611. * "id": 1000,
  15612. * "name": "MacBook Pro"
  15613. * }
  15614. * },
  15615. * {
  15616. * "id" : 21,
  15617. * "price" : 20,
  15618. * "quantity": 3,
  15619. * "product" : {
  15620. * "id": 1001,
  15621. * "name": "iPhone"
  15622. * }
  15623. * }
  15624. * ]
  15625. * }
  15626. * ]
  15627. * }
  15628. * ]
  15629. * }
  15630. *
  15631. * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
  15632. * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
  15633. * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
  15634. *
  15635. * var store = Ext.create('Ext.data.Store', {
  15636. * model: "User"
  15637. * });
  15638. *
  15639. * store.load({
  15640. * callback: function() {
  15641. * //the user that was loaded
  15642. * var user = store.first();
  15643. *
  15644. * console.log("Orders for " + user.get('name') + ":")
  15645. *
  15646. * //iterate over the Orders for each User
  15647. * user.orders().each(function(order) {
  15648. * console.log("Order ID: " + order.getId() + ", which contains items:");
  15649. *
  15650. * //iterate over the OrderItems for each Order
  15651. * order.orderItems().each(function(orderItem) {
  15652. * //we know that the Product data is already loaded, so we can use the synchronous getProduct
  15653. * //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
  15654. * var product = orderItem.getProduct();
  15655. *
  15656. * console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
  15657. * });
  15658. * });
  15659. * }
  15660. * });
  15661. *
  15662. * Running the code above results in the following:
  15663. *
  15664. * Orders for Ed:
  15665. * Order ID: 50, which contains items:
  15666. * 2 orders of MacBook Pro
  15667. * 3 orders of iPhone
  15668. */
  15669. Ext.define('Ext.data.reader.Reader', {
  15670. requires: ['Ext.data.ResultSet'],
  15671. alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
  15672. /**
  15673. * @cfg {String} idProperty
  15674. * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
  15675. * model. If an idProperty is explicitly specified it will override that of the one specified on the model
  15676. */
  15677. /**
  15678. * @cfg {String} totalProperty
  15679. * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
  15680. * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
  15681. */
  15682. totalProperty: 'total',
  15683. /**
  15684. * @cfg {String} successProperty
  15685. * Name of the property from which to retrieve the success attribute. Defaults to success. See
  15686. * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
  15687. */
  15688. successProperty: 'success',
  15689. /**
  15690. * @cfg {String} root
  15691. * The name of the property which contains the Array of row objects. For JSON reader it's dot-separated list
  15692. * of property names. For XML reader it's a CSS selector. For array reader it's not applicable.
  15693. *
  15694. * By default the natural root of the data will be used. The root Json array, the root XML element, or the array.
  15695. *
  15696. * The data packet value for this property should be an empty array to clear the data or show no data.
  15697. */
  15698. root: '',
  15699. /**
  15700. * @cfg {String} messageProperty
  15701. * The name of the property which contains a response message. This property is optional.
  15702. */
  15703. /**
  15704. * @cfg {Boolean} implicitIncludes
  15705. * True to automatically parse models nested within other models in a response object. See the
  15706. * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
  15707. */
  15708. implicitIncludes: true,
  15709. isReader: true,
  15710. /**
  15711. * Creates new Reader.
  15712. * @param {Object} config (optional) Config object.
  15713. */
  15714. constructor: function(config) {
  15715. var me = this;
  15716. Ext.apply(me, config || {});
  15717. me.fieldCount = 0;
  15718. me.model = Ext.ModelManager.getModel(config.model);
  15719. if (me.model) {
  15720. me.buildExtractors();
  15721. }
  15722. },
  15723. /**
  15724. * Sets a new model for the reader.
  15725. * @private
  15726. * @param {Object} model The model to set.
  15727. * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
  15728. */
  15729. setModel: function(model, setOnProxy) {
  15730. var me = this;
  15731. me.model = Ext.ModelManager.getModel(model);
  15732. me.buildExtractors(true);
  15733. if (setOnProxy && me.proxy) {
  15734. me.proxy.setModel(me.model, true);
  15735. }
  15736. },
  15737. /**
  15738. * Reads the given response object. This method normalizes the different types of response object that may be passed
  15739. * to it, before handing off the reading of records to the {@link #readRecords} function.
  15740. * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
  15741. * @return {Ext.data.ResultSet} The parsed ResultSet object
  15742. */
  15743. read: function(response) {
  15744. var data = response;
  15745. if (response && response.responseText) {
  15746. data = this.getResponseData(response);
  15747. }
  15748. if (data) {
  15749. return this.readRecords(data);
  15750. } else {
  15751. return this.nullResultSet;
  15752. }
  15753. },
  15754. /**
  15755. * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
  15756. * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
  15757. * processing should not be needed.
  15758. * @param {Object} data The raw data object
  15759. * @return {Ext.data.ResultSet} A ResultSet object
  15760. */
  15761. readRecords: function(data) {
  15762. var me = this;
  15763. /*
  15764. * We check here whether the number of fields has changed since the last read.
  15765. * This works around an issue when a Model is used for both a Tree and another
  15766. * source, because the tree decorates the model with extra fields and it causes
  15767. * issues because the readers aren't notified.
  15768. */
  15769. if (me.fieldCount !== me.getFields().length) {
  15770. me.buildExtractors(true);
  15771. }
  15772. /**
  15773. * @property {Object} rawData
  15774. * The raw data object that was last passed to readRecords. Stored for further processing if needed
  15775. */
  15776. me.rawData = data;
  15777. data = me.getData(data);
  15778. // If we pass an array as the data, we dont use getRoot on the data.
  15779. // Instead the root equals to the data.
  15780. var root = Ext.isArray(data) ? data : me.getRoot(data),
  15781. success = true,
  15782. recordCount = 0,
  15783. total, value, records, message;
  15784. if (root) {
  15785. total = root.length;
  15786. }
  15787. if (me.totalProperty) {
  15788. value = parseInt(me.getTotal(data), 10);
  15789. if (!isNaN(value)) {
  15790. total = value;
  15791. }
  15792. }
  15793. if (me.successProperty) {
  15794. value = me.getSuccess(data);
  15795. if (value === false || value === 'false') {
  15796. success = false;
  15797. }
  15798. }
  15799. if (me.messageProperty) {
  15800. message = me.getMessage(data);
  15801. }
  15802. if (root) {
  15803. records = me.extractData(root);
  15804. recordCount = records.length;
  15805. } else {
  15806. recordCount = 0;
  15807. records = [];
  15808. }
  15809. return Ext.create('Ext.data.ResultSet', {
  15810. total : total || recordCount,
  15811. count : recordCount,
  15812. records: records,
  15813. success: success,
  15814. message: message
  15815. });
  15816. },
  15817. /**
  15818. * Returns extracted, type-cast rows of data. Iterates to call #extractValues for each row
  15819. * @param {Object[]/Object} root from server response
  15820. * @private
  15821. */
  15822. extractData : function(root) {
  15823. var me = this,
  15824. values = [],
  15825. records = [],
  15826. Model = me.model,
  15827. i = 0,
  15828. length = root.length,
  15829. idProp = me.getIdProperty(),
  15830. node, id, record;
  15831. if (!root.length && Ext.isObject(root)) {
  15832. root = [root];
  15833. length = 1;
  15834. }
  15835. for (; i < length; i++) {
  15836. node = root[i];
  15837. values = me.extractValues(node);
  15838. id = me.getId(node);
  15839. record = new Model(values, id, node);
  15840. records.push(record);
  15841. if (me.implicitIncludes) {
  15842. me.readAssociated(record, node);
  15843. }
  15844. }
  15845. return records;
  15846. },
  15847. /**
  15848. * @private
  15849. * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
  15850. * on the record provided.
  15851. * @param {Ext.data.Model} record The record to load associations for
  15852. * @param {Object} data The data object
  15853. * @return {String} Return value description
  15854. */
  15855. readAssociated: function(record, data) {
  15856. var associations = record.associations.items,
  15857. i = 0,
  15858. length = associations.length,
  15859. association, associationData, proxy, reader;
  15860. for (; i < length; i++) {
  15861. association = associations[i];
  15862. associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
  15863. if (associationData) {
  15864. reader = association.getReader();
  15865. if (!reader) {
  15866. proxy = association.associatedModel.proxy;
  15867. // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
  15868. if (proxy) {
  15869. reader = proxy.getReader();
  15870. } else {
  15871. reader = new this.constructor({
  15872. model: association.associatedName
  15873. });
  15874. }
  15875. }
  15876. association.read(record, reader, associationData);
  15877. }
  15878. }
  15879. },
  15880. /**
  15881. * @private
  15882. * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
  15883. * record, this should return the relevant part of that data for the given association name. This is only really
  15884. * needed to support the XML Reader, which has to do a query to get the associated data object
  15885. * @param {Object} data The raw data object
  15886. * @param {String} associationName The name of the association to get data for (uses associationKey if present)
  15887. * @return {Object} The root
  15888. */
  15889. getAssociatedDataRoot: function(data, associationName) {
  15890. return data[associationName];
  15891. },
  15892. getFields: function() {
  15893. return this.model.prototype.fields.items;
  15894. },
  15895. /**
  15896. * @private
  15897. * Given an object representing a single model instance's data, iterates over the model's fields and
  15898. * builds an object with the value for each field.
  15899. * @param {Object} data The data object to convert
  15900. * @return {Object} Data object suitable for use with a model constructor
  15901. */
  15902. extractValues: function(data) {
  15903. var fields = this.getFields(),
  15904. i = 0,
  15905. length = fields.length,
  15906. output = {},
  15907. field, value;
  15908. for (; i < length; i++) {
  15909. field = fields[i];
  15910. value = this.extractorFunctions[i](data);
  15911. output[field.name] = value;
  15912. }
  15913. return output;
  15914. },
  15915. /**
  15916. * @private
  15917. * By default this function just returns what is passed to it. It can be overridden in a subclass
  15918. * to return something else. See XmlReader for an example.
  15919. * @param {Object} data The data object
  15920. * @return {Object} The normalized data object
  15921. */
  15922. getData: function(data) {
  15923. return data;
  15924. },
  15925. /**
  15926. * @private
  15927. * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
  15928. * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
  15929. * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
  15930. * @param {Object} data The data object
  15931. * @return {Object} The same data object
  15932. */
  15933. getRoot: function(data) {
  15934. return data;
  15935. },
  15936. /**
  15937. * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
  15938. * implemented by each subclass
  15939. * @param {Object} response The responce object
  15940. * @return {Object} The useful data from the response
  15941. */
  15942. getResponseData: function(response) {
  15943. //<debug>
  15944. Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
  15945. //</debug>
  15946. },
  15947. /**
  15948. * @private
  15949. * Reconfigures the meta data tied to this Reader
  15950. */
  15951. onMetaChange : function(meta) {
  15952. var fields = meta.fields,
  15953. newModel;
  15954. Ext.apply(this, meta);
  15955. if (fields) {
  15956. newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
  15957. extend: 'Ext.data.Model',
  15958. fields: fields
  15959. });
  15960. this.setModel(newModel, true);
  15961. } else {
  15962. this.buildExtractors(true);
  15963. }
  15964. },
  15965. /**
  15966. * Get the idProperty to use for extracting data
  15967. * @private
  15968. * @return {String} The id property
  15969. */
  15970. getIdProperty: function(){
  15971. var prop = this.idProperty;
  15972. if (Ext.isEmpty(prop)) {
  15973. prop = this.model.prototype.idProperty;
  15974. }
  15975. return prop;
  15976. },
  15977. /**
  15978. * @private
  15979. * This builds optimized functions for retrieving record data and meta data from an object.
  15980. * Subclasses may need to implement their own getRoot function.
  15981. * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
  15982. */
  15983. buildExtractors: function(force) {
  15984. var me = this,
  15985. idProp = me.getIdProperty(),
  15986. totalProp = me.totalProperty,
  15987. successProp = me.successProperty,
  15988. messageProp = me.messageProperty,
  15989. accessor;
  15990. if (force === true) {
  15991. delete me.extractorFunctions;
  15992. }
  15993. if (me.extractorFunctions) {
  15994. return;
  15995. }
  15996. //build the extractors for all the meta data
  15997. if (totalProp) {
  15998. me.getTotal = me.createAccessor(totalProp);
  15999. }
  16000. if (successProp) {
  16001. me.getSuccess = me.createAccessor(successProp);
  16002. }
  16003. if (messageProp) {
  16004. me.getMessage = me.createAccessor(messageProp);
  16005. }
  16006. if (idProp) {
  16007. accessor = me.createAccessor(idProp);
  16008. me.getId = function(record) {
  16009. var id = accessor.call(me, record);
  16010. return (id === undefined || id === '') ? null : id;
  16011. };
  16012. } else {
  16013. me.getId = function() {
  16014. return null;
  16015. };
  16016. }
  16017. me.buildFieldExtractors();
  16018. },
  16019. /**
  16020. * @private
  16021. */
  16022. buildFieldExtractors: function() {
  16023. //now build the extractors for all the fields
  16024. var me = this,
  16025. fields = me.getFields(),
  16026. ln = fields.length,
  16027. i = 0,
  16028. extractorFunctions = [],
  16029. field, map;
  16030. for (; i < ln; i++) {
  16031. field = fields[i];
  16032. map = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
  16033. extractorFunctions.push(me.createAccessor(map));
  16034. }
  16035. me.fieldCount = ln;
  16036. me.extractorFunctions = extractorFunctions;
  16037. }
  16038. }, function() {
  16039. Ext.apply(this, {
  16040. // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
  16041. nullResultSet: Ext.create('Ext.data.ResultSet', {
  16042. total : 0,
  16043. count : 0,
  16044. records: [],
  16045. success: true
  16046. })
  16047. });
  16048. });
  16049. /**
  16050. * @author Ed Spencer
  16051. * @class Ext.data.reader.Json
  16052. * @extends Ext.data.reader.Reader
  16053. *
  16054. * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
  16055. * happens as a result of loading a Store - for example we might create something like this:</p>
  16056. *
  16057. <pre><code>
  16058. Ext.define('User', {
  16059. extend: 'Ext.data.Model',
  16060. fields: ['id', 'name', 'email']
  16061. });
  16062. var store = Ext.create('Ext.data.Store', {
  16063. model: 'User',
  16064. proxy: {
  16065. type: 'ajax',
  16066. url : 'users.json',
  16067. reader: {
  16068. type: 'json'
  16069. }
  16070. }
  16071. });
  16072. </code></pre>
  16073. *
  16074. * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
  16075. * not already familiar with them.</p>
  16076. *
  16077. * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
  16078. * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
  16079. * Store, so it is as if we passed this instead:
  16080. *
  16081. <pre><code>
  16082. reader: {
  16083. type : 'json',
  16084. model: 'User'
  16085. }
  16086. </code></pre>
  16087. *
  16088. * <p>The reader we set up is ready to read data from our server - at the moment it will accept a response like this:</p>
  16089. *
  16090. <pre><code>
  16091. [
  16092. {
  16093. "id": 1,
  16094. "name": "Ed Spencer",
  16095. "email": "ed@sencha.com"
  16096. },
  16097. {
  16098. "id": 2,
  16099. "name": "Abe Elias",
  16100. "email": "abe@sencha.com"
  16101. }
  16102. ]
  16103. </code></pre>
  16104. *
  16105. * <p><u>Reading other JSON formats</u></p>
  16106. *
  16107. * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
  16108. * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
  16109. * {@link #root} configuration to parse data that comes back like this:</p>
  16110. *
  16111. <pre><code>
  16112. {
  16113. "users": [
  16114. {
  16115. "id": 1,
  16116. "name": "Ed Spencer",
  16117. "email": "ed@sencha.com"
  16118. },
  16119. {
  16120. "id": 2,
  16121. "name": "Abe Elias",
  16122. "email": "abe@sencha.com"
  16123. }
  16124. ]
  16125. }
  16126. </code></pre>
  16127. *
  16128. * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
  16129. *
  16130. <pre><code>
  16131. reader: {
  16132. type: 'json',
  16133. root: 'users'
  16134. }
  16135. </code></pre>
  16136. *
  16137. * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
  16138. * around each record inside a nested structure like this:</p>
  16139. *
  16140. <pre><code>
  16141. {
  16142. "total": 122,
  16143. "offset": 0,
  16144. "users": [
  16145. {
  16146. "id": "ed-spencer-1",
  16147. "value": 1,
  16148. "user": {
  16149. "id": 1,
  16150. "name": "Ed Spencer",
  16151. "email": "ed@sencha.com"
  16152. }
  16153. }
  16154. ]
  16155. }
  16156. </code></pre>
  16157. *
  16158. * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
  16159. * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
  16160. * JSON above we need to specify the {@link #record} configuration like this:</p>
  16161. *
  16162. <pre><code>
  16163. reader: {
  16164. type : 'json',
  16165. root : 'users',
  16166. record: 'user'
  16167. }
  16168. </code></pre>
  16169. *
  16170. * <p><u>Response metadata</u></p>
  16171. *
  16172. * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
  16173. * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
  16174. * like this:</p>
  16175. *
  16176. <pre><code>
  16177. {
  16178. "total": 100,
  16179. "success": true,
  16180. "users": [
  16181. {
  16182. "id": 1,
  16183. "name": "Ed Spencer",
  16184. "email": "ed@sencha.com"
  16185. }
  16186. ]
  16187. }
  16188. </code></pre>
  16189. *
  16190. * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
  16191. * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
  16192. * options:</p>
  16193. *
  16194. <pre><code>
  16195. reader: {
  16196. type : 'json',
  16197. root : 'users',
  16198. totalProperty : 'total',
  16199. successProperty: 'success'
  16200. }
  16201. </code></pre>
  16202. *
  16203. * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
  16204. * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
  16205. * returned.</p>
  16206. */
  16207. Ext.define('Ext.data.reader.Json', {
  16208. extend: 'Ext.data.reader.Reader',
  16209. alternateClassName: 'Ext.data.JsonReader',
  16210. alias : 'reader.json',
  16211. root: '',
  16212. /**
  16213. * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
  16214. * See the JsonReader intro docs for more details. This is not often needed.
  16215. */
  16216. /**
  16217. * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
  16218. * reading values. Defalts to <tt>false</tt>.
  16219. * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
  16220. * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
  16221. * "foo.bar.baz" direct from the root object.
  16222. */
  16223. useSimpleAccessors: false,
  16224. /**
  16225. * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
  16226. * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
  16227. * @param {Object} data The raw JSON data
  16228. * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
  16229. */
  16230. readRecords: function(data) {
  16231. //this has to be before the call to super because we use the meta data in the superclass readRecords
  16232. if (data.metaData) {
  16233. this.onMetaChange(data.metaData);
  16234. }
  16235. /**
  16236. * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
  16237. * @property {Object} jsonData
  16238. */
  16239. this.jsonData = data;
  16240. return this.callParent([data]);
  16241. },
  16242. //inherit docs
  16243. getResponseData: function(response) {
  16244. var data;
  16245. try {
  16246. data = Ext.decode(response.responseText);
  16247. }
  16248. catch (ex) {
  16249. Ext.Error.raise({
  16250. response: response,
  16251. json: response.responseText,
  16252. parseError: ex,
  16253. msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
  16254. });
  16255. }
  16256. //<debug>
  16257. if (!data) {
  16258. Ext.Error.raise('JSON object not found');
  16259. }
  16260. //</debug>
  16261. return data;
  16262. },
  16263. //inherit docs
  16264. buildExtractors : function() {
  16265. var me = this;
  16266. me.callParent(arguments);
  16267. if (me.root) {
  16268. me.getRoot = me.createAccessor(me.root);
  16269. } else {
  16270. me.getRoot = function(root) {
  16271. return root;
  16272. };
  16273. }
  16274. },
  16275. /**
  16276. * @private
  16277. * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
  16278. * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
  16279. * @param {Object} root The JSON root node
  16280. * @return {Ext.data.Model[]} The records
  16281. */
  16282. extractData: function(root) {
  16283. var recordName = this.record,
  16284. data = [],
  16285. length, i;
  16286. if (recordName) {
  16287. length = root.length;
  16288. if (!length && Ext.isObject(root)) {
  16289. length = 1;
  16290. root = [root];
  16291. }
  16292. for (i = 0; i < length; i++) {
  16293. data[i] = root[i][recordName];
  16294. }
  16295. } else {
  16296. data = root;
  16297. }
  16298. return this.callParent([data]);
  16299. },
  16300. /**
  16301. * @private
  16302. * Returns an accessor function for the given property string. Gives support for properties such as the following:
  16303. * 'someProperty'
  16304. * 'some.property'
  16305. * 'some["property"]'
  16306. * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
  16307. */
  16308. createAccessor: function() {
  16309. var re = /[\[\.]/;
  16310. return function(expr) {
  16311. if (Ext.isEmpty(expr)) {
  16312. return Ext.emptyFn;
  16313. }
  16314. if (Ext.isFunction(expr)) {
  16315. return expr;
  16316. }
  16317. if (this.useSimpleAccessors !== true) {
  16318. var i = String(expr).search(re);
  16319. if (i >= 0) {
  16320. return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
  16321. }
  16322. }
  16323. return function(obj) {
  16324. return obj[expr];
  16325. };
  16326. };
  16327. }()
  16328. });
  16329. /**
  16330. * @class Ext.data.writer.Json
  16331. * @extends Ext.data.writer.Writer
  16332. This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
  16333. The {@link #allowSingle} configuration can be set to false to force the records to always be
  16334. encoded in an array, even if there is only a single record being sent.
  16335. * @markdown
  16336. */
  16337. Ext.define('Ext.data.writer.Json', {
  16338. extend: 'Ext.data.writer.Writer',
  16339. alternateClassName: 'Ext.data.JsonWriter',
  16340. alias: 'writer.json',
  16341. /**
  16342. * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
  16343. * Example generated request, using root: 'records':
  16344. <pre><code>
  16345. {'records': [{name: 'my record'}, {name: 'another record'}]}
  16346. </code></pre>
  16347. */
  16348. root: undefined,
  16349. /**
  16350. * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
  16351. * The encode option should only be set to true when a {@link #root} is defined, because the values will be
  16352. * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
  16353. * sent to the server.
  16354. */
  16355. encode: false,
  16356. /**
  16357. * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
  16358. * one record being sent. When there is more than one record, they will always be encoded into an array.
  16359. * Defaults to <tt>true</tt>. Example:
  16360. * <pre><code>
  16361. // with allowSingle: true
  16362. "root": {
  16363. "first": "Mark",
  16364. "last": "Corrigan"
  16365. }
  16366. // with allowSingle: false
  16367. "root": [{
  16368. "first": "Mark",
  16369. "last": "Corrigan"
  16370. }]
  16371. * </code></pre>
  16372. */
  16373. allowSingle: true,
  16374. //inherit docs
  16375. writeRecords: function(request, data) {
  16376. var root = this.root;
  16377. if (this.allowSingle && data.length == 1) {
  16378. // convert to single object format
  16379. data = data[0];
  16380. }
  16381. if (this.encode) {
  16382. if (root) {
  16383. // sending as a param, need to encode
  16384. request.params[root] = Ext.encode(data);
  16385. } else {
  16386. //<debug>
  16387. Ext.Error.raise('Must specify a root when using encode');
  16388. //</debug>
  16389. }
  16390. } else {
  16391. // send as jsonData
  16392. request.jsonData = request.jsonData || {};
  16393. if (root) {
  16394. request.jsonData[root] = data;
  16395. } else {
  16396. request.jsonData = data;
  16397. }
  16398. }
  16399. return request;
  16400. }
  16401. });
  16402. /**
  16403. * @author Ed Spencer
  16404. *
  16405. * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
  16406. * data. Usually developers will not need to create or interact with proxies directly.
  16407. *
  16408. * # Types of Proxy
  16409. *
  16410. * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
  16411. * The Client proxies save their data locally and include the following subclasses:
  16412. *
  16413. * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
  16414. * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
  16415. * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
  16416. *
  16417. * The Server proxies save their data by sending requests to some remote server. These proxies include:
  16418. *
  16419. * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
  16420. * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
  16421. * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
  16422. *
  16423. * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
  16424. * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
  16425. * respectively. Each Proxy subclass implements these functions.
  16426. *
  16427. * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
  16428. * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
  16429. * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
  16430. * method also accepts a callback function to be called asynchronously on completion.
  16431. *
  16432. * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
  16433. * method.
  16434. */
  16435. Ext.define('Ext.data.proxy.Proxy', {
  16436. alias: 'proxy.proxy',
  16437. alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
  16438. requires: [
  16439. 'Ext.data.reader.Json',
  16440. 'Ext.data.writer.Json'
  16441. ],
  16442. uses: [
  16443. 'Ext.data.Batch',
  16444. 'Ext.data.Operation',
  16445. 'Ext.data.Model'
  16446. ],
  16447. mixins: {
  16448. observable: 'Ext.util.Observable'
  16449. },
  16450. /**
  16451. * @cfg {String} batchOrder
  16452. * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
  16453. * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
  16454. */
  16455. batchOrder: 'create,update,destroy',
  16456. /**
  16457. * @cfg {Boolean} batchActions
  16458. * True to batch actions of a particular type when synchronizing the store. Defaults to true.
  16459. */
  16460. batchActions: true,
  16461. /**
  16462. * @cfg {String} defaultReaderType
  16463. * The default registered reader type. Defaults to 'json'.
  16464. * @private
  16465. */
  16466. defaultReaderType: 'json',
  16467. /**
  16468. * @cfg {String} defaultWriterType
  16469. * The default registered writer type. Defaults to 'json'.
  16470. * @private
  16471. */
  16472. defaultWriterType: 'json',
  16473. /**
  16474. * @cfg {String/Ext.data.Model} model
  16475. * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
  16476. * Model constructor. Required.
  16477. */
  16478. /**
  16479. * @cfg {Object/String/Ext.data.reader.Reader} reader
  16480. * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
  16481. * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
  16482. */
  16483. /**
  16484. * @cfg {Object/String/Ext.data.writer.Writer} writer
  16485. * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
  16486. * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
  16487. */
  16488. isProxy: true,
  16489. /**
  16490. * Creates the Proxy
  16491. * @param {Object} config (optional) Config object.
  16492. */
  16493. constructor: function(config) {
  16494. config = config || {};
  16495. if (config.model === undefined) {
  16496. delete config.model;
  16497. }
  16498. this.mixins.observable.constructor.call(this, config);
  16499. if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
  16500. this.setModel(this.model);
  16501. }
  16502. },
  16503. /**
  16504. * Sets the model associated with this proxy. This will only usually be called by a Store
  16505. *
  16506. * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
  16507. * or a reference to the model's constructor
  16508. * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
  16509. */
  16510. setModel: function(model, setOnStore) {
  16511. this.model = Ext.ModelManager.getModel(model);
  16512. var reader = this.reader,
  16513. writer = this.writer;
  16514. this.setReader(reader);
  16515. this.setWriter(writer);
  16516. if (setOnStore && this.store) {
  16517. this.store.setModel(this.model);
  16518. }
  16519. },
  16520. /**
  16521. * Returns the model attached to this Proxy
  16522. * @return {Ext.data.Model} The model
  16523. */
  16524. getModel: function() {
  16525. return this.model;
  16526. },
  16527. /**
  16528. * Sets the Proxy's Reader by string, config object or Reader instance
  16529. *
  16530. * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
  16531. * a configuration object or an Ext.data.reader.Reader instance
  16532. * @return {Ext.data.reader.Reader} The attached Reader object
  16533. */
  16534. setReader: function(reader) {
  16535. var me = this;
  16536. if (reader === undefined || typeof reader == 'string') {
  16537. reader = {
  16538. type: reader
  16539. };
  16540. }
  16541. if (reader.isReader) {
  16542. reader.setModel(me.model);
  16543. } else {
  16544. Ext.applyIf(reader, {
  16545. proxy: me,
  16546. model: me.model,
  16547. type : me.defaultReaderType
  16548. });
  16549. reader = Ext.createByAlias('reader.' + reader.type, reader);
  16550. }
  16551. me.reader = reader;
  16552. return me.reader;
  16553. },
  16554. /**
  16555. * Returns the reader currently attached to this proxy instance
  16556. * @return {Ext.data.reader.Reader} The Reader instance
  16557. */
  16558. getReader: function() {
  16559. return this.reader;
  16560. },
  16561. /**
  16562. * Sets the Proxy's Writer by string, config object or Writer instance
  16563. *
  16564. * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
  16565. * a configuration object or an Ext.data.writer.Writer instance
  16566. * @return {Ext.data.writer.Writer} The attached Writer object
  16567. */
  16568. setWriter: function(writer) {
  16569. if (writer === undefined || typeof writer == 'string') {
  16570. writer = {
  16571. type: writer
  16572. };
  16573. }
  16574. if (!(writer instanceof Ext.data.writer.Writer)) {
  16575. Ext.applyIf(writer, {
  16576. model: this.model,
  16577. type : this.defaultWriterType
  16578. });
  16579. writer = Ext.createByAlias('writer.' + writer.type, writer);
  16580. }
  16581. this.writer = writer;
  16582. return this.writer;
  16583. },
  16584. /**
  16585. * Returns the writer currently attached to this proxy instance
  16586. * @return {Ext.data.writer.Writer} The Writer instance
  16587. */
  16588. getWriter: function() {
  16589. return this.writer;
  16590. },
  16591. /**
  16592. * Performs the given create operation.
  16593. * @param {Ext.data.Operation} operation The Operation to perform
  16594. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  16595. * @param {Object} scope Scope to execute the callback function in
  16596. * @method
  16597. */
  16598. create: Ext.emptyFn,
  16599. /**
  16600. * Performs the given read operation.
  16601. * @param {Ext.data.Operation} operation The Operation to perform
  16602. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  16603. * @param {Object} scope Scope to execute the callback function in
  16604. * @method
  16605. */
  16606. read: Ext.emptyFn,
  16607. /**
  16608. * Performs the given update operation.
  16609. * @param {Ext.data.Operation} operation The Operation to perform
  16610. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  16611. * @param {Object} scope Scope to execute the callback function in
  16612. * @method
  16613. */
  16614. update: Ext.emptyFn,
  16615. /**
  16616. * Performs the given destroy operation.
  16617. * @param {Ext.data.Operation} operation The Operation to perform
  16618. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  16619. * @param {Object} scope Scope to execute the callback function in
  16620. * @method
  16621. */
  16622. destroy: Ext.emptyFn,
  16623. /**
  16624. * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
  16625. * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
  16626. *
  16627. * myProxy.batch({
  16628. * create : [myModel1, myModel2],
  16629. * update : [myModel3],
  16630. * destroy: [myModel4, myModel5]
  16631. * });
  16632. *
  16633. * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
  16634. * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
  16635. * saved but should now be destroyed.
  16636. *
  16637. * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
  16638. * @param {Object} listeners (optional) listeners object passed straight through to the Batch -
  16639. * see {@link Ext.data.Batch}
  16640. * @return {Ext.data.Batch} The newly created Ext.data.Batch object
  16641. */
  16642. batch: function(operations, listeners) {
  16643. var me = this,
  16644. batch = Ext.create('Ext.data.Batch', {
  16645. proxy: me,
  16646. listeners: listeners || {}
  16647. }),
  16648. useBatch = me.batchActions,
  16649. records;
  16650. Ext.each(me.batchOrder.split(','), function(action) {
  16651. records = operations[action];
  16652. if (records) {
  16653. if (useBatch) {
  16654. batch.add(Ext.create('Ext.data.Operation', {
  16655. action: action,
  16656. records: records
  16657. }));
  16658. } else {
  16659. Ext.each(records, function(record){
  16660. batch.add(Ext.create('Ext.data.Operation', {
  16661. action : action,
  16662. records: [record]
  16663. }));
  16664. });
  16665. }
  16666. }
  16667. }, me);
  16668. batch.start();
  16669. return batch;
  16670. }
  16671. }, function() {
  16672. // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
  16673. //backwards compatibility
  16674. Ext.data.DataProxy = this;
  16675. // Ext.deprecate('platform', '2.0', function() {
  16676. // Ext.data.DataProxy = this;
  16677. // }, this);
  16678. });
  16679. /**
  16680. * @author Ed Spencer
  16681. *
  16682. * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
  16683. * would not usually be used directly.
  16684. *
  16685. * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
  16686. * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
  16687. * an alias of AjaxProxy).
  16688. * @private
  16689. */
  16690. Ext.define('Ext.data.proxy.Server', {
  16691. extend: 'Ext.data.proxy.Proxy',
  16692. alias : 'proxy.server',
  16693. alternateClassName: 'Ext.data.ServerProxy',
  16694. uses : ['Ext.data.Request'],
  16695. /**
  16696. * @cfg {String} url
  16697. * The URL from which to request the data object.
  16698. */
  16699. /**
  16700. * @cfg {String} pageParam
  16701. * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
  16702. * want to send a page parameter.
  16703. */
  16704. pageParam: 'page',
  16705. /**
  16706. * @cfg {String} startParam
  16707. * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
  16708. * want to send a start parameter.
  16709. */
  16710. startParam: 'start',
  16711. /**
  16712. * @cfg {String} limitParam
  16713. * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
  16714. * want to send a limit parameter.
  16715. */
  16716. limitParam: 'limit',
  16717. /**
  16718. * @cfg {String} groupParam
  16719. * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
  16720. * want to send a group parameter.
  16721. */
  16722. groupParam: 'group',
  16723. /**
  16724. * @cfg {String} sortParam
  16725. * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
  16726. * want to send a sort parameter.
  16727. */
  16728. sortParam: 'sort',
  16729. /**
  16730. * @cfg {String} filterParam
  16731. * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
  16732. * want to send a filter parameter.
  16733. */
  16734. filterParam: 'filter',
  16735. /**
  16736. * @cfg {String} directionParam
  16737. * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
  16738. * true.** Defaults to 'dir'.
  16739. */
  16740. directionParam: 'dir',
  16741. /**
  16742. * @cfg {Boolean} simpleSortMode
  16743. * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
  16744. * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
  16745. * or 'DESC'.
  16746. */
  16747. simpleSortMode: false,
  16748. /**
  16749. * @cfg {Boolean} noCache
  16750. * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
  16751. */
  16752. noCache : true,
  16753. /**
  16754. * @cfg {String} cacheString
  16755. * The name of the cache param added to the url when using noCache. Defaults to "_dc".
  16756. */
  16757. cacheString: "_dc",
  16758. /**
  16759. * @cfg {Number} timeout
  16760. * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
  16761. */
  16762. timeout : 30000,
  16763. /**
  16764. * @cfg {Object} api
  16765. * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
  16766. *
  16767. * api: {
  16768. * create : undefined,
  16769. * read : undefined,
  16770. * update : undefined,
  16771. * destroy : undefined
  16772. * }
  16773. *
  16774. * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
  16775. * {@link #api} property, or if undefined default to the configured
  16776. * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
  16777. *
  16778. * For example:
  16779. *
  16780. * api: {
  16781. * create : '/controller/new',
  16782. * read : '/controller/load',
  16783. * update : '/controller/update',
  16784. * destroy : '/controller/destroy_action'
  16785. * }
  16786. *
  16787. * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
  16788. * configured {@link Ext.data.proxy.Server#url url}.
  16789. */
  16790. constructor: function(config) {
  16791. var me = this;
  16792. config = config || {};
  16793. this.addEvents(
  16794. /**
  16795. * @event exception
  16796. * Fires when the server returns an exception
  16797. * @param {Ext.data.proxy.Proxy} this
  16798. * @param {Object} response The response from the AJAX request
  16799. * @param {Ext.data.Operation} operation The operation that triggered request
  16800. */
  16801. 'exception'
  16802. );
  16803. me.callParent([config]);
  16804. /**
  16805. * @cfg {Object} extraParams
  16806. * Extra parameters that will be included on every request. Individual requests with params of the same name
  16807. * will override these params when they are in conflict.
  16808. */
  16809. me.extraParams = config.extraParams || {};
  16810. me.api = config.api || {};
  16811. //backwards compatibility, will be deprecated in 5.0
  16812. me.nocache = me.noCache;
  16813. },
  16814. //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
  16815. create: function() {
  16816. return this.doRequest.apply(this, arguments);
  16817. },
  16818. read: function() {
  16819. return this.doRequest.apply(this, arguments);
  16820. },
  16821. update: function() {
  16822. return this.doRequest.apply(this, arguments);
  16823. },
  16824. destroy: function() {
  16825. return this.doRequest.apply(this, arguments);
  16826. },
  16827. /**
  16828. * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
  16829. * that this Proxy is attached to.
  16830. * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
  16831. * @return {Ext.data.Request} The request object
  16832. */
  16833. buildRequest: function(operation) {
  16834. var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
  16835. request;
  16836. //copy any sorters, filters etc into the params so they can be sent over the wire
  16837. params = Ext.applyIf(params, this.getParams(operation));
  16838. if (operation.id && !params.id) {
  16839. params.id = operation.id;
  16840. }
  16841. request = Ext.create('Ext.data.Request', {
  16842. params : params,
  16843. action : operation.action,
  16844. records : operation.records,
  16845. operation: operation,
  16846. url : operation.url
  16847. });
  16848. request.url = this.buildUrl(request);
  16849. /*
  16850. * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
  16851. * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
  16852. */
  16853. operation.request = request;
  16854. return request;
  16855. },
  16856. // Should this be documented as protected method?
  16857. processResponse: function(success, operation, request, response, callback, scope){
  16858. var me = this,
  16859. reader,
  16860. result;
  16861. if (success === true) {
  16862. reader = me.getReader();
  16863. result = reader.read(me.extractResponseData(response));
  16864. if (result.success !== false) {
  16865. //see comment in buildRequest for why we include the response object here
  16866. Ext.apply(operation, {
  16867. response: response,
  16868. resultSet: result
  16869. });
  16870. operation.commitRecords(result.records);
  16871. operation.setCompleted();
  16872. operation.setSuccessful();
  16873. } else {
  16874. operation.setException(result.message);
  16875. me.fireEvent('exception', this, response, operation);
  16876. }
  16877. } else {
  16878. me.setException(operation, response);
  16879. me.fireEvent('exception', this, response, operation);
  16880. }
  16881. //this callback is the one that was passed to the 'read' or 'write' function above
  16882. if (typeof callback == 'function') {
  16883. callback.call(scope || me, operation);
  16884. }
  16885. me.afterRequest(request, success);
  16886. },
  16887. /**
  16888. * Sets up an exception on the operation
  16889. * @private
  16890. * @param {Ext.data.Operation} operation The operation
  16891. * @param {Object} response The response
  16892. */
  16893. setException: function(operation, response){
  16894. operation.setException({
  16895. status: response.status,
  16896. statusText: response.statusText
  16897. });
  16898. },
  16899. /**
  16900. * Template method to allow subclasses to specify how to get the response for the reader.
  16901. * @template
  16902. * @private
  16903. * @param {Object} response The server response
  16904. * @return {Object} The response data to be used by the reader
  16905. */
  16906. extractResponseData: function(response){
  16907. return response;
  16908. },
  16909. /**
  16910. * Encode any values being sent to the server. Can be overridden in subclasses.
  16911. * @private
  16912. * @param {Array} An array of sorters/filters.
  16913. * @return {Object} The encoded value
  16914. */
  16915. applyEncoding: function(value){
  16916. return Ext.encode(value);
  16917. },
  16918. /**
  16919. * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
  16920. * this simply JSON-encodes the sorter data
  16921. * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
  16922. * @return {String} The encoded sorters
  16923. */
  16924. encodeSorters: function(sorters) {
  16925. var min = [],
  16926. length = sorters.length,
  16927. i = 0;
  16928. for (; i < length; i++) {
  16929. min[i] = {
  16930. property : sorters[i].property,
  16931. direction: sorters[i].direction
  16932. };
  16933. }
  16934. return this.applyEncoding(min);
  16935. },
  16936. /**
  16937. * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
  16938. * this simply JSON-encodes the filter data
  16939. * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
  16940. * @return {String} The encoded filters
  16941. */
  16942. encodeFilters: function(filters) {
  16943. var min = [],
  16944. length = filters.length,
  16945. i = 0;
  16946. for (; i < length; i++) {
  16947. min[i] = {
  16948. property: filters[i].property,
  16949. value : filters[i].value
  16950. };
  16951. }
  16952. return this.applyEncoding(min);
  16953. },
  16954. /**
  16955. * @private
  16956. * Copy any sorters, filters etc into the params so they can be sent over the wire
  16957. */
  16958. getParams: function(operation) {
  16959. var me = this,
  16960. params = {},
  16961. isDef = Ext.isDefined,
  16962. groupers = operation.groupers,
  16963. sorters = operation.sorters,
  16964. filters = operation.filters,
  16965. page = operation.page,
  16966. start = operation.start,
  16967. limit = operation.limit,
  16968. simpleSortMode = me.simpleSortMode,
  16969. pageParam = me.pageParam,
  16970. startParam = me.startParam,
  16971. limitParam = me.limitParam,
  16972. groupParam = me.groupParam,
  16973. sortParam = me.sortParam,
  16974. filterParam = me.filterParam,
  16975. directionParam = me.directionParam;
  16976. if (pageParam && isDef(page)) {
  16977. params[pageParam] = page;
  16978. }
  16979. if (startParam && isDef(start)) {
  16980. params[startParam] = start;
  16981. }
  16982. if (limitParam && isDef(limit)) {
  16983. params[limitParam] = limit;
  16984. }
  16985. if (groupParam && groupers && groupers.length > 0) {
  16986. // Grouper is a subclass of sorter, so we can just use the sorter method
  16987. params[groupParam] = me.encodeSorters(groupers);
  16988. }
  16989. if (sortParam && sorters && sorters.length > 0) {
  16990. if (simpleSortMode) {
  16991. params[sortParam] = sorters[0].property;
  16992. params[directionParam] = sorters[0].direction;
  16993. } else {
  16994. params[sortParam] = me.encodeSorters(sorters);
  16995. }
  16996. }
  16997. if (filterParam && filters && filters.length > 0) {
  16998. params[filterParam] = me.encodeFilters(filters);
  16999. }
  17000. return params;
  17001. },
  17002. /**
  17003. * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
  17004. * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
  17005. * @param {Ext.data.Request} request The request object
  17006. * @return {String} The url
  17007. */
  17008. buildUrl: function(request) {
  17009. var me = this,
  17010. url = me.getUrl(request);
  17011. //<debug>
  17012. if (!url) {
  17013. Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
  17014. }
  17015. //</debug>
  17016. if (me.noCache) {
  17017. url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
  17018. }
  17019. return url;
  17020. },
  17021. /**
  17022. * Get the url for the request taking into account the order of priority,
  17023. * - The request
  17024. * - The api
  17025. * - The url
  17026. * @private
  17027. * @param {Ext.data.Request} request The request
  17028. * @return {String} The url
  17029. */
  17030. getUrl: function(request){
  17031. return request.url || this.api[request.action] || this.url;
  17032. },
  17033. /**
  17034. * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
  17035. * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
  17036. * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
  17037. * each of the methods that delegate to it.
  17038. *
  17039. * @param {Ext.data.Operation} operation The Ext.data.Operation object
  17040. * @param {Function} callback The callback function to call when the Operation has completed
  17041. * @param {Object} scope The scope in which to execute the callback
  17042. */
  17043. doRequest: function(operation, callback, scope) {
  17044. //<debug>
  17045. Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
  17046. //</debug>
  17047. },
  17048. /**
  17049. * Optional callback function which can be used to clean up after a request has been completed.
  17050. * @param {Ext.data.Request} request The Request object
  17051. * @param {Boolean} success True if the request was successful
  17052. * @method
  17053. */
  17054. afterRequest: Ext.emptyFn,
  17055. onDestroy: function() {
  17056. Ext.destroy(this.reader, this.writer);
  17057. }
  17058. });
  17059. /**
  17060. * @author Ed Spencer
  17061. *
  17062. * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
  17063. * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
  17064. * Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a {@link Ext.data.Model
  17065. * Model}:
  17066. *
  17067. * Ext.define('User', {
  17068. * extend: 'Ext.data.Model',
  17069. * fields: ['id', 'name', 'email']
  17070. * });
  17071. *
  17072. * //The Store contains the AjaxProxy as an inline configuration
  17073. * var store = Ext.create('Ext.data.Store', {
  17074. * model: 'User',
  17075. * proxy: {
  17076. * type: 'ajax',
  17077. * url : 'users.json'
  17078. * }
  17079. * });
  17080. *
  17081. * store.load();
  17082. *
  17083. * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
  17084. * the fields that we expect the server to return. Next we set up the Store itself, along with a
  17085. * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
  17086. * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
  17087. * It's as if we'd done this:
  17088. *
  17089. * new Ext.data.proxy.Ajax({
  17090. * url: 'users.json',
  17091. * model: 'User',
  17092. * reader: 'json'
  17093. * });
  17094. *
  17095. * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
  17096. * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
  17097. * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
  17098. *
  17099. * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
  17100. * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
  17101. * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
  17102. * will be sent as a POST request).
  17103. *
  17104. * # Limitations
  17105. *
  17106. * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
  17107. * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
  17108. * talking to each other via AJAX.
  17109. *
  17110. * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
  17111. * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
  17112. * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
  17113. * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
  17114. * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
  17115. *
  17116. * # Readers and Writers
  17117. *
  17118. * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
  17119. * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
  17120. * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
  17121. * Ext.data.reader.Reader Reader} instance:
  17122. *
  17123. * var proxy = new Ext.data.proxy.Ajax({
  17124. * model: 'User',
  17125. * reader: {
  17126. * type: 'xml',
  17127. * root: 'users'
  17128. * }
  17129. * });
  17130. *
  17131. * proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
  17132. *
  17133. * # Url generation
  17134. *
  17135. * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
  17136. * each request. These are controlled with the following configuration options:
  17137. *
  17138. * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
  17139. * - {@link #sortParam} - controls how sort information is sent to the server
  17140. * - {@link #groupParam} - controls how grouping information is sent to the server
  17141. * - {@link #filterParam} - controls how filter information is sent to the server
  17142. *
  17143. * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
  17144. * the generated urls, let's say we're loading the Proxy with the following Operation:
  17145. *
  17146. * var operation = new Ext.data.Operation({
  17147. * action: 'read',
  17148. * page : 2
  17149. * });
  17150. *
  17151. * Now we'll issue the request for this Operation by calling {@link #read}:
  17152. *
  17153. * var proxy = new Ext.data.proxy.Ajax({
  17154. * url: '/users'
  17155. * });
  17156. *
  17157. * proxy.read(operation); //GET /users?page=2
  17158. *
  17159. * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
  17160. * to the server:
  17161. *
  17162. * var proxy = new Ext.data.proxy.Ajax({
  17163. * url: '/users',
  17164. * pagePage: 'pageNumber'
  17165. * });
  17166. *
  17167. * proxy.read(operation); //GET /users?pageNumber=2
  17168. *
  17169. * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
  17170. *
  17171. * var operation = new Ext.data.Operation({
  17172. * action: 'read',
  17173. * start : 50,
  17174. * limit : 25
  17175. * });
  17176. *
  17177. * var proxy = new Ext.data.proxy.Ajax({
  17178. * url: '/users'
  17179. * });
  17180. *
  17181. * proxy.read(operation); //GET /users?start=50&limit;=25
  17182. *
  17183. * Again we can customize this url:
  17184. *
  17185. * var proxy = new Ext.data.proxy.Ajax({
  17186. * url: '/users',
  17187. * startParam: 'startIndex',
  17188. * limitParam: 'limitIndex'
  17189. * });
  17190. *
  17191. * proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
  17192. *
  17193. * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
  17194. * expressive Operation object:
  17195. *
  17196. * var operation = new Ext.data.Operation({
  17197. * action: 'read',
  17198. * sorters: [
  17199. * new Ext.util.Sorter({
  17200. * property : 'name',
  17201. * direction: 'ASC'
  17202. * }),
  17203. * new Ext.util.Sorter({
  17204. * property : 'age',
  17205. * direction: 'DESC'
  17206. * })
  17207. * ],
  17208. * filters: [
  17209. * new Ext.util.Filter({
  17210. * property: 'eyeColor',
  17211. * value : 'brown'
  17212. * })
  17213. * ]
  17214. * });
  17215. *
  17216. * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
  17217. * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
  17218. * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
  17219. *
  17220. * var proxy = new Ext.data.proxy.Ajax({
  17221. * url: '/users'
  17222. * });
  17223. *
  17224. * proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
  17225. *
  17226. * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
  17227. * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
  17228. * that format like this:
  17229. *
  17230. * var proxy = new Ext.data.proxy.Ajax({
  17231. * url: '/users',
  17232. * sortParam: 'sortBy',
  17233. * filterParam: 'filterBy',
  17234. *
  17235. * //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
  17236. * encodeSorters: function(sorters) {
  17237. * var length = sorters.length,
  17238. * sortStrs = [],
  17239. * sorter, i;
  17240. *
  17241. * for (i = 0; i < length; i++) {
  17242. * sorter = sorters[i];
  17243. *
  17244. * sortStrs[i] = sorter.property + '#' + sorter.direction
  17245. * }
  17246. *
  17247. * return sortStrs.join(",");
  17248. * }
  17249. * });
  17250. *
  17251. * proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
  17252. *
  17253. * We can also provide a custom {@link #encodeFilters} function to encode our filters.
  17254. *
  17255. * @constructor
  17256. * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
  17257. * {@link Ext.data.Store#load load} will override any specified callback and params options. In this case, use the
  17258. * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
  17259. *
  17260. * @param {Object} config (optional) Config object.
  17261. * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
  17262. */
  17263. Ext.define('Ext.data.proxy.Ajax', {
  17264. requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
  17265. extend: 'Ext.data.proxy.Server',
  17266. alias: 'proxy.ajax',
  17267. alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
  17268. /**
  17269. * @property {Object} actionMethods
  17270. * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
  17271. * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
  17272. * correct RESTful methods.
  17273. */
  17274. actionMethods: {
  17275. create : 'POST',
  17276. read : 'GET',
  17277. update : 'POST',
  17278. destroy: 'POST'
  17279. },
  17280. /**
  17281. * @cfg {Object} headers
  17282. * Any headers to add to the Ajax request. Defaults to undefined.
  17283. */
  17284. /**
  17285. * @ignore
  17286. */
  17287. doRequest: function(operation, callback, scope) {
  17288. var writer = this.getWriter(),
  17289. request = this.buildRequest(operation, callback, scope);
  17290. if (operation.allowWrite()) {
  17291. request = writer.write(request);
  17292. }
  17293. Ext.apply(request, {
  17294. headers : this.headers,
  17295. timeout : this.timeout,
  17296. scope : this,
  17297. callback : this.createRequestCallback(request, operation, callback, scope),
  17298. method : this.getMethod(request),
  17299. disableCaching: false // explicitly set it to false, ServerProxy handles caching
  17300. });
  17301. Ext.Ajax.request(request);
  17302. return request;
  17303. },
  17304. /**
  17305. * Returns the HTTP method name for a given request. By default this returns based on a lookup on
  17306. * {@link #actionMethods}.
  17307. * @param {Ext.data.Request} request The request object
  17308. * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
  17309. */
  17310. getMethod: function(request) {
  17311. return this.actionMethods[request.action];
  17312. },
  17313. /**
  17314. * @private
  17315. * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
  17316. * of code duplication inside the returned function so we need to find a way to DRY this up.
  17317. * @param {Ext.data.Request} request The Request object
  17318. * @param {Ext.data.Operation} operation The Operation being executed
  17319. * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
  17320. * passed to doRequest
  17321. * @param {Object} scope The scope in which to execute the callback function
  17322. * @return {Function} The callback function
  17323. */
  17324. createRequestCallback: function(request, operation, callback, scope) {
  17325. var me = this;
  17326. return function(options, success, response) {
  17327. me.processResponse(success, operation, request, response, callback, scope);
  17328. };
  17329. }
  17330. }, function() {
  17331. //backwards compatibility, remove in Ext JS 5.0
  17332. Ext.data.HttpProxy = this;
  17333. });
  17334. /**
  17335. * @author Ed Spencer
  17336. *
  17337. * A Model represents some object that your application manages. For example, one might define a Model for Users,
  17338. * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
  17339. * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
  17340. * of the data-bound components in Ext.
  17341. *
  17342. * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
  17343. *
  17344. * Ext.define('User', {
  17345. * extend: 'Ext.data.Model',
  17346. * fields: [
  17347. * {name: 'name', type: 'string'},
  17348. * {name: 'age', type: 'int'},
  17349. * {name: 'phone', type: 'string'},
  17350. * {name: 'alive', type: 'boolean', defaultValue: true}
  17351. * ],
  17352. *
  17353. * changeName: function() {
  17354. * var oldName = this.get('name'),
  17355. * newName = oldName + " The Barbarian";
  17356. *
  17357. * this.set('name', newName);
  17358. * }
  17359. * });
  17360. *
  17361. * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
  17362. * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
  17363. *
  17364. * Now we can create instances of our User model and call any model logic we defined:
  17365. *
  17366. * var user = Ext.create('User', {
  17367. * name : 'Conan',
  17368. * age : 24,
  17369. * phone: '555-555-5555'
  17370. * });
  17371. *
  17372. * user.changeName();
  17373. * user.get('name'); //returns "Conan The Barbarian"
  17374. *
  17375. * # Validations
  17376. *
  17377. * Models have built-in support for validations, which are executed against the validator functions in {@link
  17378. * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
  17379. * models:
  17380. *
  17381. * Ext.define('User', {
  17382. * extend: 'Ext.data.Model',
  17383. * fields: [
  17384. * {name: 'name', type: 'string'},
  17385. * {name: 'age', type: 'int'},
  17386. * {name: 'phone', type: 'string'},
  17387. * {name: 'gender', type: 'string'},
  17388. * {name: 'username', type: 'string'},
  17389. * {name: 'alive', type: 'boolean', defaultValue: true}
  17390. * ],
  17391. *
  17392. * validations: [
  17393. * {type: 'presence', field: 'age'},
  17394. * {type: 'length', field: 'name', min: 2},
  17395. * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
  17396. * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
  17397. * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
  17398. * ]
  17399. * });
  17400. *
  17401. * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
  17402. * object:
  17403. *
  17404. * var instance = Ext.create('User', {
  17405. * name: 'Ed',
  17406. * gender: 'Male',
  17407. * username: 'edspencer'
  17408. * });
  17409. *
  17410. * var errors = instance.validate();
  17411. *
  17412. * # Associations
  17413. *
  17414. * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
  17415. * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
  17416. * application which deals with Users, Posts and Comments. We can express the relationships between these models like
  17417. * this:
  17418. *
  17419. * Ext.define('Post', {
  17420. * extend: 'Ext.data.Model',
  17421. * fields: ['id', 'user_id'],
  17422. *
  17423. * belongsTo: 'User',
  17424. * hasMany : {model: 'Comment', name: 'comments'}
  17425. * });
  17426. *
  17427. * Ext.define('Comment', {
  17428. * extend: 'Ext.data.Model',
  17429. * fields: ['id', 'user_id', 'post_id'],
  17430. *
  17431. * belongsTo: 'Post'
  17432. * });
  17433. *
  17434. * Ext.define('User', {
  17435. * extend: 'Ext.data.Model',
  17436. * fields: ['id'],
  17437. *
  17438. * hasMany: [
  17439. * 'Post',
  17440. * {model: 'Comment', name: 'comments'}
  17441. * ]
  17442. * });
  17443. *
  17444. * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
  17445. * usage and configuration of associations. Note that associations can also be specified like this:
  17446. *
  17447. * Ext.define('User', {
  17448. * extend: 'Ext.data.Model',
  17449. * fields: ['id'],
  17450. *
  17451. * associations: [
  17452. * {type: 'hasMany', model: 'Post', name: 'posts'},
  17453. * {type: 'hasMany', model: 'Comment', name: 'comments'}
  17454. * ]
  17455. * });
  17456. *
  17457. * # Using a Proxy
  17458. *
  17459. * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
  17460. * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
  17461. * can be set directly on the Model:
  17462. *
  17463. * Ext.define('User', {
  17464. * extend: 'Ext.data.Model',
  17465. * fields: ['id', 'name', 'email'],
  17466. *
  17467. * proxy: {
  17468. * type: 'rest',
  17469. * url : '/users'
  17470. * }
  17471. * });
  17472. *
  17473. * Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
  17474. * RESTful backend. Let's see how this works:
  17475. *
  17476. * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
  17477. *
  17478. * user.save(); //POST /users
  17479. *
  17480. * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
  17481. * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
  17482. * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
  17483. * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
  17484. *
  17485. * Loading data via the Proxy is equally easy:
  17486. *
  17487. * //get a reference to the User model class
  17488. * var User = Ext.ModelManager.getModel('User');
  17489. *
  17490. * //Uses the configured RestProxy to make a GET request to /users/123
  17491. * User.load(123, {
  17492. * success: function(user) {
  17493. * console.log(user.getId()); //logs 123
  17494. * }
  17495. * });
  17496. *
  17497. * Models can also be updated and destroyed easily:
  17498. *
  17499. * //the user Model we loaded in the last snippet:
  17500. * user.set('name', 'Edward Spencer');
  17501. *
  17502. * //tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
  17503. * user.save({
  17504. * success: function() {
  17505. * console.log('The User was updated');
  17506. * }
  17507. * });
  17508. *
  17509. * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
  17510. * user.destroy({
  17511. * success: function() {
  17512. * console.log('The User was destroyed!');
  17513. * }
  17514. * });
  17515. *
  17516. * # Usage in Stores
  17517. *
  17518. * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
  17519. * creating a {@link Ext.data.Store Store}:
  17520. *
  17521. * var store = Ext.create('Ext.data.Store', {
  17522. * model: 'User'
  17523. * });
  17524. *
  17525. * //uses the Proxy we set up on Model to load the Store data
  17526. * store.load();
  17527. *
  17528. * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
  17529. * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
  17530. * Ext.data.Store Store docs} for more information on Stores.
  17531. *
  17532. * @constructor
  17533. * Creates new Model instance.
  17534. * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
  17535. * @param {Number} id (optional) Unique ID to assign to this model instance
  17536. */
  17537. Ext.define('Ext.data.Model', {
  17538. alternateClassName: 'Ext.data.Record',
  17539. mixins: {
  17540. observable: 'Ext.util.Observable'
  17541. },
  17542. requires: [
  17543. 'Ext.ModelManager',
  17544. 'Ext.data.IdGenerator',
  17545. 'Ext.data.Field',
  17546. 'Ext.data.Errors',
  17547. 'Ext.data.Operation',
  17548. 'Ext.data.validations',
  17549. 'Ext.data.proxy.Ajax',
  17550. 'Ext.util.MixedCollection'
  17551. ],
  17552. onClassExtended: function(cls, data) {
  17553. var onBeforeClassCreated = data.onBeforeClassCreated;
  17554. data.onBeforeClassCreated = function(cls, data) {
  17555. var me = this,
  17556. name = Ext.getClassName(cls),
  17557. prototype = cls.prototype,
  17558. superCls = cls.prototype.superclass,
  17559. validations = data.validations || [],
  17560. fields = data.fields || [],
  17561. associations = data.associations || [],
  17562. belongsTo = data.belongsTo,
  17563. hasMany = data.hasMany,
  17564. idgen = data.idgen,
  17565. fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
  17566. return field.name;
  17567. }),
  17568. associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
  17569. return association.name;
  17570. }),
  17571. superValidations = superCls.validations,
  17572. superFields = superCls.fields,
  17573. superAssociations = superCls.associations,
  17574. association, i, ln,
  17575. dependencies = [];
  17576. // Save modelName on class and its prototype
  17577. cls.modelName = name;
  17578. prototype.modelName = name;
  17579. // Merge the validations of the superclass and the new subclass
  17580. if (superValidations) {
  17581. validations = superValidations.concat(validations);
  17582. }
  17583. data.validations = validations;
  17584. // Merge the fields of the superclass and the new subclass
  17585. if (superFields) {
  17586. fields = superFields.items.concat(fields);
  17587. }
  17588. for (i = 0, ln = fields.length; i < ln; ++i) {
  17589. fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
  17590. }
  17591. data.fields = fieldsMixedCollection;
  17592. if (idgen) {
  17593. data.idgen = Ext.data.IdGenerator.get(idgen);
  17594. }
  17595. //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
  17596. //we support that here
  17597. if (belongsTo) {
  17598. belongsTo = Ext.Array.from(belongsTo);
  17599. for (i = 0, ln = belongsTo.length; i < ln; ++i) {
  17600. association = belongsTo[i];
  17601. if (!Ext.isObject(association)) {
  17602. association = {model: association};
  17603. }
  17604. association.type = 'belongsTo';
  17605. associations.push(association);
  17606. }
  17607. delete data.belongsTo;
  17608. }
  17609. if (hasMany) {
  17610. hasMany = Ext.Array.from(hasMany);
  17611. for (i = 0, ln = hasMany.length; i < ln; ++i) {
  17612. association = hasMany[i];
  17613. if (!Ext.isObject(association)) {
  17614. association = {model: association};
  17615. }
  17616. association.type = 'hasMany';
  17617. associations.push(association);
  17618. }
  17619. delete data.hasMany;
  17620. }
  17621. if (superAssociations) {
  17622. associations = superAssociations.items.concat(associations);
  17623. }
  17624. for (i = 0, ln = associations.length; i < ln; ++i) {
  17625. dependencies.push('association.' + associations[i].type.toLowerCase());
  17626. }
  17627. if (data.proxy) {
  17628. if (typeof data.proxy === 'string') {
  17629. dependencies.push('proxy.' + data.proxy);
  17630. }
  17631. else if (typeof data.proxy.type === 'string') {
  17632. dependencies.push('proxy.' + data.proxy.type);
  17633. }
  17634. }
  17635. Ext.require(dependencies, function() {
  17636. Ext.ModelManager.registerType(name, cls);
  17637. for (i = 0, ln = associations.length; i < ln; ++i) {
  17638. association = associations[i];
  17639. Ext.apply(association, {
  17640. ownerModel: name,
  17641. associatedModel: association.model
  17642. });
  17643. if (Ext.ModelManager.getModel(association.model) === undefined) {
  17644. Ext.ModelManager.registerDeferredAssociation(association);
  17645. } else {
  17646. associationsMixedCollection.add(Ext.data.Association.create(association));
  17647. }
  17648. }
  17649. data.associations = associationsMixedCollection;
  17650. onBeforeClassCreated.call(me, cls, data);
  17651. cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
  17652. // Fire the onModelDefined template method on ModelManager
  17653. Ext.ModelManager.onModelDefined(cls);
  17654. });
  17655. };
  17656. },
  17657. inheritableStatics: {
  17658. /**
  17659. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  17660. * {@link Ext#createByAlias Ext.createByAlias}.
  17661. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  17662. * @return {Ext.data.proxy.Proxy}
  17663. * @static
  17664. * @inheritable
  17665. */
  17666. setProxy: function(proxy) {
  17667. //make sure we have an Ext.data.proxy.Proxy object
  17668. if (!proxy.isProxy) {
  17669. if (typeof proxy == "string") {
  17670. proxy = {
  17671. type: proxy
  17672. };
  17673. }
  17674. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  17675. }
  17676. proxy.setModel(this);
  17677. this.proxy = this.prototype.proxy = proxy;
  17678. return proxy;
  17679. },
  17680. /**
  17681. * Returns the configured Proxy for this Model
  17682. * @return {Ext.data.proxy.Proxy} The proxy
  17683. * @static
  17684. * @inheritable
  17685. */
  17686. getProxy: function() {
  17687. return this.proxy;
  17688. },
  17689. /**
  17690. * Asynchronously loads a model instance by id. Sample usage:
  17691. *
  17692. * MyApp.User = Ext.define('User', {
  17693. * extend: 'Ext.data.Model',
  17694. * fields: [
  17695. * {name: 'id', type: 'int'},
  17696. * {name: 'name', type: 'string'}
  17697. * ]
  17698. * });
  17699. *
  17700. * MyApp.User.load(10, {
  17701. * scope: this,
  17702. * failure: function(record, operation) {
  17703. * //do something if the load failed
  17704. * },
  17705. * success: function(record, operation) {
  17706. * //do something if the load succeeded
  17707. * },
  17708. * callback: function(record, operation) {
  17709. * //do something whether the load succeeded or failed
  17710. * }
  17711. * });
  17712. *
  17713. * @param {Number} id The id of the model to load
  17714. * @param {Object} config (optional) config object containing success, failure and callback functions, plus
  17715. * optional scope
  17716. * @static
  17717. * @inheritable
  17718. */
  17719. load: function(id, config) {
  17720. config = Ext.apply({}, config);
  17721. config = Ext.applyIf(config, {
  17722. action: 'read',
  17723. id : id
  17724. });
  17725. var operation = Ext.create('Ext.data.Operation', config),
  17726. scope = config.scope || this,
  17727. record = null,
  17728. callback;
  17729. callback = function(operation) {
  17730. if (operation.wasSuccessful()) {
  17731. record = operation.getRecords()[0];
  17732. Ext.callback(config.success, scope, [record, operation]);
  17733. } else {
  17734. Ext.callback(config.failure, scope, [record, operation]);
  17735. }
  17736. Ext.callback(config.callback, scope, [record, operation]);
  17737. };
  17738. this.proxy.read(operation, callback, this);
  17739. }
  17740. },
  17741. statics: {
  17742. PREFIX : 'ext-record',
  17743. AUTO_ID: 1,
  17744. EDIT : 'edit',
  17745. REJECT : 'reject',
  17746. COMMIT : 'commit',
  17747. /**
  17748. * Generates a sequential id. This method is typically called when a record is {@link Ext#create
  17749. * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
  17750. * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
  17751. *
  17752. * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
  17753. * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
  17754. *
  17755. * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
  17756. * @return {String} auto-generated string id, `"ext-record-i++"`;
  17757. * @static
  17758. */
  17759. id: function(rec) {
  17760. var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
  17761. rec.phantom = true;
  17762. rec.internalId = id;
  17763. return id;
  17764. }
  17765. },
  17766. /**
  17767. * @cfg {String/Object} idgen
  17768. * The id generator to use for this model. The default id generator does not generate
  17769. * values for the {@link #idProperty}.
  17770. *
  17771. * This can be overridden at the model level to provide a custom generator for a model.
  17772. * The simplest form of this would be:
  17773. *
  17774. * Ext.define('MyApp.data.MyModel', {
  17775. * extend: 'Ext.data.Model',
  17776. * requires: ['Ext.data.SequentialIdGenerator'],
  17777. * idgen: 'sequential',
  17778. * ...
  17779. * });
  17780. *
  17781. * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
  17782. * as 1, 2, 3 etc..
  17783. *
  17784. * Another useful id generator is {@link Ext.data.UuidGenerator}:
  17785. *
  17786. * Ext.define('MyApp.data.MyModel', {
  17787. * extend: 'Ext.data.Model',
  17788. * requires: ['Ext.data.UuidGenerator'],
  17789. * idgen: 'uuid',
  17790. * ...
  17791. * });
  17792. *
  17793. * An id generation can also be further configured:
  17794. *
  17795. * Ext.define('MyApp.data.MyModel', {
  17796. * extend: 'Ext.data.Model',
  17797. * idgen: {
  17798. * type: 'sequential',
  17799. * seed: 1000,
  17800. * prefix: 'ID_'
  17801. * }
  17802. * });
  17803. *
  17804. * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
  17805. *
  17806. * If multiple models share an id space, a single generator can be shared:
  17807. *
  17808. * Ext.define('MyApp.data.MyModelX', {
  17809. * extend: 'Ext.data.Model',
  17810. * idgen: {
  17811. * type: 'sequential',
  17812. * id: 'xy'
  17813. * }
  17814. * });
  17815. *
  17816. * Ext.define('MyApp.data.MyModelY', {
  17817. * extend: 'Ext.data.Model',
  17818. * idgen: {
  17819. * type: 'sequential',
  17820. * id: 'xy'
  17821. * }
  17822. * });
  17823. *
  17824. * For more complex, shared id generators, a custom generator is the best approach.
  17825. * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
  17826. *
  17827. * @markdown
  17828. */
  17829. idgen: {
  17830. isGenerator: true,
  17831. type: 'default',
  17832. generate: function () {
  17833. return null;
  17834. },
  17835. getRecId: function (rec) {
  17836. return rec.modelName + '-' + rec.internalId;
  17837. }
  17838. },
  17839. /**
  17840. * @property {Boolean} editing
  17841. * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
  17842. */
  17843. editing : false,
  17844. /**
  17845. * @property {Boolean} dirty
  17846. * True if this Record has been modified. Read-only.
  17847. */
  17848. dirty : false,
  17849. /**
  17850. * @cfg {String} persistenceProperty
  17851. * The property on this Persistable object that its data is saved to. Defaults to 'data'
  17852. * (e.g. all persistable data resides in this.data.)
  17853. */
  17854. persistenceProperty: 'data',
  17855. evented: false,
  17856. isModel: true,
  17857. /**
  17858. * @property {Boolean} phantom
  17859. * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
  17860. * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
  17861. */
  17862. phantom : false,
  17863. /**
  17864. * @cfg {String} idProperty
  17865. * The name of the field treated as this Model's unique id. Defaults to 'id'.
  17866. */
  17867. idProperty: 'id',
  17868. /**
  17869. * @cfg {String} defaultProxyType
  17870. * The string type of the default Model Proxy. Defaults to 'ajax'.
  17871. */
  17872. defaultProxyType: 'ajax',
  17873. // Fields config and property
  17874. /**
  17875. * @cfg {Object[]/String[]} fields
  17876. * The fields for this model.
  17877. */
  17878. /**
  17879. * @property {Ext.util.MixedCollection} fields
  17880. * The fields defined on this model.
  17881. */
  17882. /**
  17883. * @cfg {Object[]} validations
  17884. * An array of {@link Ext.data.validations validations} for this model.
  17885. */
  17886. // Associations configs and properties
  17887. /**
  17888. * @cfg {Object[]} associations
  17889. * An array of {@link Ext.data.Association associations} for this model.
  17890. */
  17891. /**
  17892. * @cfg {String/Object/String[]/Object[]} hasMany
  17893. * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
  17894. */
  17895. /**
  17896. * @cfg {String/Object/String[]/Object[]} belongsTo
  17897. * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
  17898. */
  17899. /**
  17900. * @property {Ext.util.MixedCollection} associations
  17901. * {@link Ext.data.Association Associations} defined on this model.
  17902. */
  17903. /**
  17904. * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
  17905. * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
  17906. */
  17907. // raw not documented intentionally, meant to be used internally.
  17908. constructor: function(data, id, raw) {
  17909. data = data || {};
  17910. var me = this,
  17911. fields,
  17912. length,
  17913. field,
  17914. name,
  17915. i,
  17916. newId,
  17917. isArray = Ext.isArray(data),
  17918. newData = isArray ? {} : null; // to hold mapped array data if needed
  17919. /**
  17920. * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
  17921. * @property internalId
  17922. * @type String
  17923. * @private
  17924. */
  17925. me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
  17926. /**
  17927. * @property {Object} raw The raw data used to create this model if created via a reader.
  17928. */
  17929. me.raw = raw;
  17930. Ext.applyIf(me, {
  17931. data: {}
  17932. });
  17933. /**
  17934. * @property {Object} modified Key: value pairs of all fields whose values have changed
  17935. */
  17936. me.modified = {};
  17937. // Deal with spelling error in previous releases
  17938. if (me.persistanceProperty) {
  17939. //<debug>
  17940. if (Ext.isDefined(Ext.global.console)) {
  17941. Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
  17942. }
  17943. //</debug>
  17944. me.persistenceProperty = me.persistanceProperty;
  17945. }
  17946. me[me.persistenceProperty] = {};
  17947. me.mixins.observable.constructor.call(me);
  17948. //add default field values if present
  17949. fields = me.fields.items;
  17950. length = fields.length;
  17951. for (i = 0; i < length; i++) {
  17952. field = fields[i];
  17953. name = field.name;
  17954. if (isArray){
  17955. // Have to map array data so the values get assigned to the named fields
  17956. // rather than getting set as the field names with undefined values.
  17957. newData[name] = data[i];
  17958. }
  17959. else if (data[name] === undefined) {
  17960. data[name] = field.defaultValue;
  17961. }
  17962. }
  17963. me.set(newData || data);
  17964. if (me.getId()) {
  17965. me.phantom = false;
  17966. } else if (me.phantom) {
  17967. newId = me.idgen.generate();
  17968. if (newId !== null) {
  17969. me.setId(newId);
  17970. }
  17971. }
  17972. // clear any dirty/modified since we're initializing
  17973. me.dirty = false;
  17974. me.modified = {};
  17975. if (typeof me.init == 'function') {
  17976. me.init();
  17977. }
  17978. me.id = me.idgen.getRecId(me);
  17979. },
  17980. /**
  17981. * Returns the value of the given field
  17982. * @param {String} fieldName The field to fetch the value for
  17983. * @return {Object} The value
  17984. */
  17985. get: function(field) {
  17986. return this[this.persistenceProperty][field];
  17987. },
  17988. /**
  17989. * Sets the given field to the given value, marks the instance as dirty
  17990. * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
  17991. * @param {Object} value The value to set
  17992. */
  17993. set: function(fieldName, value) {
  17994. var me = this,
  17995. fields = me.fields,
  17996. modified = me.modified,
  17997. convertFields = [],
  17998. field, key, i, currentValue, notEditing, count, length;
  17999. /*
  18000. * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
  18001. * set those last so that all other possible data is set before the convert function is called
  18002. */
  18003. if (arguments.length == 1 && Ext.isObject(fieldName)) {
  18004. notEditing = !me.editing;
  18005. count = 0;
  18006. for (key in fieldName) {
  18007. if (fieldName.hasOwnProperty(key)) {
  18008. //here we check for the custom convert function. Note that if a field doesn't have a convert function,
  18009. //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
  18010. field = fields.get(key);
  18011. if (field && field.convert !== field.type.convert) {
  18012. convertFields.push(key);
  18013. continue;
  18014. }
  18015. if (!count && notEditing) {
  18016. me.beginEdit();
  18017. }
  18018. ++count;
  18019. me.set(key, fieldName[key]);
  18020. }
  18021. }
  18022. length = convertFields.length;
  18023. if (length) {
  18024. if (!count && notEditing) {
  18025. me.beginEdit();
  18026. }
  18027. count += length;
  18028. for (i = 0; i < length; i++) {
  18029. field = convertFields[i];
  18030. me.set(field, fieldName[field]);
  18031. }
  18032. }
  18033. if (notEditing && count) {
  18034. me.endEdit();
  18035. }
  18036. } else {
  18037. if (fields) {
  18038. field = fields.get(fieldName);
  18039. if (field && field.convert) {
  18040. value = field.convert(value, me);
  18041. }
  18042. }
  18043. currentValue = me.get(fieldName);
  18044. me[me.persistenceProperty][fieldName] = value;
  18045. if (field && field.persist && !me.isEqual(currentValue, value)) {
  18046. if (me.isModified(fieldName)) {
  18047. if (me.isEqual(modified[fieldName], value)) {
  18048. // the original value in me.modified equals the new value, so the
  18049. // field is no longer modified
  18050. delete modified[fieldName];
  18051. // we might have removed the last modified field, so check to see if
  18052. // there are any modified fields remaining and correct me.dirty:
  18053. me.dirty = false;
  18054. for (key in modified) {
  18055. if (modified.hasOwnProperty(key)){
  18056. me.dirty = true;
  18057. break;
  18058. }
  18059. }
  18060. }
  18061. } else {
  18062. me.dirty = true;
  18063. modified[fieldName] = currentValue;
  18064. }
  18065. }
  18066. if (!me.editing) {
  18067. me.afterEdit();
  18068. }
  18069. }
  18070. },
  18071. /**
  18072. * Checks if two values are equal, taking into account certain
  18073. * special factors, for example dates.
  18074. * @private
  18075. * @param {Object} a The first value
  18076. * @param {Object} b The second value
  18077. * @return {Boolean} True if the values are equal
  18078. */
  18079. isEqual: function(a, b){
  18080. if (Ext.isDate(a) && Ext.isDate(b)) {
  18081. return a.getTime() === b.getTime();
  18082. }
  18083. return a === b;
  18084. },
  18085. /**
  18086. * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
  18087. * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
  18088. */
  18089. beginEdit : function(){
  18090. var me = this;
  18091. if (!me.editing) {
  18092. me.editing = true;
  18093. me.dirtySave = me.dirty;
  18094. me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
  18095. me.modifiedSave = Ext.apply({}, me.modified);
  18096. }
  18097. },
  18098. /**
  18099. * Cancels all changes made in the current edit operation.
  18100. */
  18101. cancelEdit : function(){
  18102. var me = this;
  18103. if (me.editing) {
  18104. me.editing = false;
  18105. // reset the modified state, nothing changed since the edit began
  18106. me.modified = me.modifiedSave;
  18107. me[me.persistenceProperty] = me.dataSave;
  18108. me.dirty = me.dirtySave;
  18109. delete me.modifiedSave;
  18110. delete me.dataSave;
  18111. delete me.dirtySave;
  18112. }
  18113. },
  18114. /**
  18115. * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
  18116. * fire).
  18117. * @param {Boolean} silent True to not notify the store of the change
  18118. */
  18119. endEdit : function(silent){
  18120. var me = this,
  18121. didChange;
  18122. if (me.editing) {
  18123. me.editing = false;
  18124. didChange = me.dirty || me.changedWhileEditing();
  18125. delete me.modifiedSave;
  18126. delete me.dataSave;
  18127. delete me.dirtySave;
  18128. if (silent !== true && didChange) {
  18129. me.afterEdit();
  18130. }
  18131. }
  18132. },
  18133. /**
  18134. * Checks if the underlying data has changed during an edit. This doesn't necessarily
  18135. * mean the record is dirty, however we still need to notify the store since it may need
  18136. * to update any views.
  18137. * @private
  18138. * @return {Boolean} True if the underlying data has changed during an edit.
  18139. */
  18140. changedWhileEditing: function(){
  18141. var me = this,
  18142. saved = me.dataSave,
  18143. data = me[me.persistenceProperty],
  18144. key;
  18145. for (key in data) {
  18146. if (data.hasOwnProperty(key)) {
  18147. if (!me.isEqual(data[key], saved[key])) {
  18148. return true;
  18149. }
  18150. }
  18151. }
  18152. return false;
  18153. },
  18154. /**
  18155. * Gets a hash of only the fields that have been modified since this Model was created or commited.
  18156. * @return {Object}
  18157. */
  18158. getChanges : function(){
  18159. var modified = this.modified,
  18160. changes = {},
  18161. field;
  18162. for (field in modified) {
  18163. if (modified.hasOwnProperty(field)){
  18164. changes[field] = this.get(field);
  18165. }
  18166. }
  18167. return changes;
  18168. },
  18169. /**
  18170. * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
  18171. * @param {String} fieldName {@link Ext.data.Field#name}
  18172. * @return {Boolean}
  18173. */
  18174. isModified : function(fieldName) {
  18175. return this.modified.hasOwnProperty(fieldName);
  18176. },
  18177. /**
  18178. * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
  18179. * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
  18180. *
  18181. * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
  18182. * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
  18183. */
  18184. setDirty : function() {
  18185. var me = this,
  18186. name;
  18187. me.dirty = true;
  18188. me.fields.each(function(field) {
  18189. if (field.persist) {
  18190. name = field.name;
  18191. me.modified[name] = me.get(name);
  18192. }
  18193. }, me);
  18194. },
  18195. //<debug>
  18196. markDirty : function() {
  18197. if (Ext.isDefined(Ext.global.console)) {
  18198. Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
  18199. }
  18200. return this.setDirty.apply(this, arguments);
  18201. },
  18202. //</debug>
  18203. /**
  18204. * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
  18205. * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
  18206. * reverted to their original values.
  18207. *
  18208. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
  18209. * operations.
  18210. *
  18211. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  18212. * Defaults to false.
  18213. */
  18214. reject : function(silent) {
  18215. var me = this,
  18216. modified = me.modified,
  18217. field;
  18218. for (field in modified) {
  18219. if (modified.hasOwnProperty(field)) {
  18220. if (typeof modified[field] != "function") {
  18221. me[me.persistenceProperty][field] = modified[field];
  18222. }
  18223. }
  18224. }
  18225. me.dirty = false;
  18226. me.editing = false;
  18227. me.modified = {};
  18228. if (silent !== true) {
  18229. me.afterReject();
  18230. }
  18231. },
  18232. /**
  18233. * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
  18234. * instance since either creation or the last commit operation.
  18235. *
  18236. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
  18237. * operations.
  18238. *
  18239. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  18240. * Defaults to false.
  18241. */
  18242. commit : function(silent) {
  18243. var me = this;
  18244. me.phantom = me.dirty = me.editing = false;
  18245. me.modified = {};
  18246. if (silent !== true) {
  18247. me.afterCommit();
  18248. }
  18249. },
  18250. /**
  18251. * Creates a copy (clone) of this Model instance.
  18252. *
  18253. * @param {String} [id] A new id, defaults to the id of the instance being copied.
  18254. * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
  18255. *
  18256. * var rec = record.copy(); // clone the record
  18257. * Ext.data.Model.id(rec); // automatically generate a unique sequential id
  18258. *
  18259. * @return {Ext.data.Model}
  18260. */
  18261. copy : function(newId) {
  18262. var me = this;
  18263. return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
  18264. },
  18265. /**
  18266. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  18267. * {@link Ext#createByAlias Ext.createByAlias}.
  18268. *
  18269. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  18270. * @return {Ext.data.proxy.Proxy}
  18271. */
  18272. setProxy: function(proxy) {
  18273. //make sure we have an Ext.data.proxy.Proxy object
  18274. if (!proxy.isProxy) {
  18275. if (typeof proxy === "string") {
  18276. proxy = {
  18277. type: proxy
  18278. };
  18279. }
  18280. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  18281. }
  18282. proxy.setModel(this.self);
  18283. this.proxy = proxy;
  18284. return proxy;
  18285. },
  18286. /**
  18287. * Returns the configured Proxy for this Model.
  18288. * @return {Ext.data.proxy.Proxy} The proxy
  18289. */
  18290. getProxy: function() {
  18291. return this.proxy;
  18292. },
  18293. /**
  18294. * Validates the current data against all of its configured {@link #validations}.
  18295. * @return {Ext.data.Errors} The errors object
  18296. */
  18297. validate: function() {
  18298. var errors = Ext.create('Ext.data.Errors'),
  18299. validations = this.validations,
  18300. validators = Ext.data.validations,
  18301. length, validation, field, valid, type, i;
  18302. if (validations) {
  18303. length = validations.length;
  18304. for (i = 0; i < length; i++) {
  18305. validation = validations[i];
  18306. field = validation.field || validation.name;
  18307. type = validation.type;
  18308. valid = validators[type](validation, this.get(field));
  18309. if (!valid) {
  18310. errors.add({
  18311. field : field,
  18312. message: validation.message || validators[type + 'Message']
  18313. });
  18314. }
  18315. }
  18316. }
  18317. return errors;
  18318. },
  18319. /**
  18320. * Checks if the model is valid. See {@link #validate}.
  18321. * @return {Boolean} True if the model is valid.
  18322. */
  18323. isValid: function(){
  18324. return this.validate().isValid();
  18325. },
  18326. /**
  18327. * Saves the model instance using the configured proxy.
  18328. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  18329. * @return {Ext.data.Model} The Model instance
  18330. */
  18331. save: function(options) {
  18332. options = Ext.apply({}, options);
  18333. var me = this,
  18334. action = me.phantom ? 'create' : 'update',
  18335. record = null,
  18336. scope = options.scope || me,
  18337. operation,
  18338. callback;
  18339. Ext.apply(options, {
  18340. records: [me],
  18341. action : action
  18342. });
  18343. operation = Ext.create('Ext.data.Operation', options);
  18344. callback = function(operation) {
  18345. if (operation.wasSuccessful()) {
  18346. record = operation.getRecords()[0];
  18347. //we need to make sure we've set the updated data here. Ideally this will be redundant once the
  18348. //ModelCache is in place
  18349. me.set(record.data);
  18350. record.dirty = false;
  18351. Ext.callback(options.success, scope, [record, operation]);
  18352. } else {
  18353. Ext.callback(options.failure, scope, [record, operation]);
  18354. }
  18355. Ext.callback(options.callback, scope, [record, operation]);
  18356. };
  18357. me.getProxy()[action](operation, callback, me);
  18358. return me;
  18359. },
  18360. /**
  18361. * Destroys the model using the configured proxy.
  18362. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  18363. * @return {Ext.data.Model} The Model instance
  18364. */
  18365. destroy: function(options){
  18366. options = Ext.apply({}, options);
  18367. var me = this,
  18368. record = null,
  18369. scope = options.scope || me,
  18370. operation,
  18371. callback;
  18372. Ext.apply(options, {
  18373. records: [me],
  18374. action : 'destroy'
  18375. });
  18376. operation = Ext.create('Ext.data.Operation', options);
  18377. callback = function(operation) {
  18378. if (operation.wasSuccessful()) {
  18379. Ext.callback(options.success, scope, [record, operation]);
  18380. } else {
  18381. Ext.callback(options.failure, scope, [record, operation]);
  18382. }
  18383. Ext.callback(options.callback, scope, [record, operation]);
  18384. };
  18385. me.getProxy().destroy(operation, callback, me);
  18386. return me;
  18387. },
  18388. /**
  18389. * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
  18390. * @return {Number} The id
  18391. */
  18392. getId: function() {
  18393. return this.get(this.idProperty);
  18394. },
  18395. /**
  18396. * Sets the model instance's id field to the given id.
  18397. * @param {Number} id The new id
  18398. */
  18399. setId: function(id) {
  18400. this.set(this.idProperty, id);
  18401. },
  18402. /**
  18403. * Tells this model instance that it has been added to a store.
  18404. * @param {Ext.data.Store} store The store to which this model has been added.
  18405. */
  18406. join : function(store) {
  18407. /**
  18408. * @property {Ext.data.Store} store
  18409. * The {@link Ext.data.Store Store} to which this Record belongs.
  18410. */
  18411. this.store = store;
  18412. },
  18413. /**
  18414. * Tells this model instance that it has been removed from the store.
  18415. * @param {Ext.data.Store} store The store from which this model has been removed.
  18416. */
  18417. unjoin: function(store) {
  18418. delete this.store;
  18419. },
  18420. /**
  18421. * @private
  18422. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  18423. * afterEdit method is called
  18424. */
  18425. afterEdit : function() {
  18426. this.callStore('afterEdit');
  18427. },
  18428. /**
  18429. * @private
  18430. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  18431. * afterReject method is called
  18432. */
  18433. afterReject : function() {
  18434. this.callStore("afterReject");
  18435. },
  18436. /**
  18437. * @private
  18438. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  18439. * afterCommit method is called
  18440. */
  18441. afterCommit: function() {
  18442. this.callStore('afterCommit');
  18443. },
  18444. /**
  18445. * @private
  18446. * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
  18447. * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
  18448. * will always be called with the model instance as its single argument.
  18449. * @param {String} fn The function to call on the store
  18450. */
  18451. callStore: function(fn) {
  18452. var store = this.store;
  18453. if (store !== undefined && typeof store[fn] == "function") {
  18454. store[fn](this);
  18455. }
  18456. },
  18457. /**
  18458. * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
  18459. * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
  18460. *
  18461. * {
  18462. * orders: [
  18463. * {
  18464. * id: 123,
  18465. * status: 'shipped',
  18466. * orderItems: [
  18467. * ...
  18468. * ]
  18469. * }
  18470. * ]
  18471. * }
  18472. *
  18473. * @return {Object} The nested data set for the Model's loaded associations
  18474. */
  18475. getAssociatedData: function(){
  18476. return this.prepareAssociatedData(this, [], null);
  18477. },
  18478. /**
  18479. * @private
  18480. * This complex-looking method takes a given Model instance and returns an object containing all data from
  18481. * all of that Model's *loaded* associations. See (@link #getAssociatedData}
  18482. * @param {Ext.data.Model} record The Model instance
  18483. * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
  18484. * @param {String} associationType (optional) The name of the type of association to limit to.
  18485. * @return {Object} The nested data set for the Model's loaded associations
  18486. */
  18487. prepareAssociatedData: function(record, ids, associationType) {
  18488. //we keep track of all of the internalIds of the models that we have loaded so far in here
  18489. var associations = record.associations.items,
  18490. associationCount = associations.length,
  18491. associationData = {},
  18492. associatedStore, associatedName, associatedRecords, associatedRecord,
  18493. associatedRecordCount, association, id, i, j, type, allow;
  18494. for (i = 0; i < associationCount; i++) {
  18495. association = associations[i];
  18496. type = association.type;
  18497. allow = true;
  18498. if (associationType) {
  18499. allow = type == associationType;
  18500. }
  18501. if (allow && type == 'hasMany') {
  18502. //this is the hasMany store filled with the associated data
  18503. associatedStore = record[association.storeName];
  18504. //we will use this to contain each associated record's data
  18505. associationData[association.name] = [];
  18506. //if it's loaded, put it into the association data
  18507. if (associatedStore && associatedStore.data.length > 0) {
  18508. associatedRecords = associatedStore.data.items;
  18509. associatedRecordCount = associatedRecords.length;
  18510. //now we're finally iterating over the records in the association. We do this recursively
  18511. for (j = 0; j < associatedRecordCount; j++) {
  18512. associatedRecord = associatedRecords[j];
  18513. // Use the id, since it is prefixed with the model name, guaranteed to be unique
  18514. id = associatedRecord.id;
  18515. //when we load the associations for a specific model instance we add it to the set of loaded ids so that
  18516. //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
  18517. if (Ext.Array.indexOf(ids, id) == -1) {
  18518. ids.push(id);
  18519. associationData[association.name][j] = associatedRecord.data;
  18520. Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
  18521. }
  18522. }
  18523. }
  18524. } else if (allow && type == 'belongsTo') {
  18525. associatedRecord = record[association.instanceName];
  18526. if (associatedRecord !== undefined) {
  18527. id = associatedRecord.id;
  18528. if (Ext.Array.indexOf(ids, id) == -1) {
  18529. ids.push(id);
  18530. associationData[association.name] = associatedRecord.data;
  18531. Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
  18532. }
  18533. }
  18534. }
  18535. }
  18536. return associationData;
  18537. }
  18538. });
  18539. /**
  18540. * @docauthor Evan Trimboli <evan@sencha.com>
  18541. *
  18542. * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
  18543. * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
  18544. * referred to via it's identifier:
  18545. *
  18546. * Ext.create('Ext.data.Store', {
  18547. * model: 'SomeModel',
  18548. * storeId: 'myStore'
  18549. * });
  18550. *
  18551. * var store = Ext.data.StoreManager.lookup('myStore');
  18552. *
  18553. * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
  18554. *
  18555. * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
  18556. * it with any Component that consumes data from a store:
  18557. *
  18558. * Ext.create('Ext.data.Store', {
  18559. * model: 'SomeModel',
  18560. * storeId: 'myStore'
  18561. * });
  18562. *
  18563. * Ext.create('Ext.view.View', {
  18564. * store: 'myStore',
  18565. * // other configuration here
  18566. * });
  18567. *
  18568. */
  18569. Ext.define('Ext.data.StoreManager', {
  18570. extend: 'Ext.util.MixedCollection',
  18571. alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
  18572. singleton: true,
  18573. uses: ['Ext.data.ArrayStore'],
  18574. /**
  18575. * @cfg {Object} listeners @hide
  18576. */
  18577. /**
  18578. * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
  18579. * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
  18580. * @param {Ext.data.Store...} stores Any number of Store instances
  18581. */
  18582. register : function() {
  18583. for (var i = 0, s; (s = arguments[i]); i++) {
  18584. this.add(s);
  18585. }
  18586. },
  18587. /**
  18588. * Unregisters one or more Stores with the StoreManager
  18589. * @param {String/Object...} stores Any number of Store instances or ID-s
  18590. */
  18591. unregister : function() {
  18592. for (var i = 0, s; (s = arguments[i]); i++) {
  18593. this.remove(this.lookup(s));
  18594. }
  18595. },
  18596. /**
  18597. * Gets a registered Store by id
  18598. * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
  18599. * @return {Ext.data.Store}
  18600. */
  18601. lookup : function(store) {
  18602. // handle the case when we are given an array or an array of arrays.
  18603. if (Ext.isArray(store)) {
  18604. var fields = ['field1'],
  18605. expand = !Ext.isArray(store[0]),
  18606. data = store,
  18607. i,
  18608. len;
  18609. if(expand){
  18610. data = [];
  18611. for (i = 0, len = store.length; i < len; ++i) {
  18612. data.push([store[i]]);
  18613. }
  18614. } else {
  18615. for(i = 2, len = store[0].length; i <= len; ++i){
  18616. fields.push('field' + i);
  18617. }
  18618. }
  18619. return Ext.create('Ext.data.ArrayStore', {
  18620. data : data,
  18621. fields: fields,
  18622. autoDestroy: true,
  18623. autoCreated: true,
  18624. expanded: expand
  18625. });
  18626. }
  18627. if (Ext.isString(store)) {
  18628. // store id
  18629. return this.get(store);
  18630. } else {
  18631. // store instance or store config
  18632. return Ext.data.AbstractStore.create(store);
  18633. }
  18634. },
  18635. // getKey implementation for MixedCollection
  18636. getKey : function(o) {
  18637. return o.storeId;
  18638. }
  18639. }, function() {
  18640. /**
  18641. * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}.
  18642. * Sample usage:
  18643. *
  18644. * Ext.regStore('AllUsers', {
  18645. * model: 'User'
  18646. * });
  18647. *
  18648. * // the store can now easily be used throughout the application
  18649. * new Ext.List({
  18650. * store: 'AllUsers',
  18651. * ... other config
  18652. * });
  18653. *
  18654. * @param {String} id The id to set on the new store
  18655. * @param {Object} config The store config
  18656. * @member Ext
  18657. * @method regStore
  18658. */
  18659. Ext.regStore = function(name, config) {
  18660. var store;
  18661. if (Ext.isObject(name)) {
  18662. config = name;
  18663. } else {
  18664. config.storeId = name;
  18665. }
  18666. if (config instanceof Ext.data.Store) {
  18667. store = config;
  18668. } else {
  18669. store = Ext.create('Ext.data.Store', config);
  18670. }
  18671. return Ext.data.StoreManager.register(store);
  18672. };
  18673. /**
  18674. * Shortcut to {@link Ext.data.StoreManager#lookup}.
  18675. * @member Ext
  18676. * @method getStore
  18677. * @alias Ext.data.StoreManager#lookup
  18678. */
  18679. Ext.getStore = function(name) {
  18680. return Ext.data.StoreManager.lookup(name);
  18681. };
  18682. });
  18683. /**
  18684. * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
  18685. * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
  18686. * class. Components may be added to a Container through the {@link Ext.container.Container#items items} config option
  18687. * at the time the Container is created, or they may be added dynamically via the
  18688. * {@link Ext.container.Container#add add} method.
  18689. *
  18690. * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
  18691. *
  18692. * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
  18693. * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
  18694. *
  18695. * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
  18696. * subclass Component.
  18697. *
  18698. * See the [Creating new UI controls][1] tutorial for details on how and to either extend or augment ExtJs base classes
  18699. * to create custom Components.
  18700. *
  18701. * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
  18702. * like {@link #getXType} and {@link #isXType}. See the [Component Guide][2] for more information on xtypes and the
  18703. * Component hierarchy.
  18704. *
  18705. * This is the list of all valid xtypes:
  18706. *
  18707. * xtype Class
  18708. * ------------- ------------------
  18709. * button {@link Ext.button.Button}
  18710. * buttongroup {@link Ext.container.ButtonGroup}
  18711. * colorpalette {@link Ext.picker.Color}
  18712. * component {@link Ext.Component}
  18713. * container {@link Ext.container.Container}
  18714. * cycle {@link Ext.button.Cycle}
  18715. * dataview {@link Ext.view.View}
  18716. * datepicker {@link Ext.picker.Date}
  18717. * editor {@link Ext.Editor}
  18718. * editorgrid {@link Ext.grid.plugin.Editing}
  18719. * grid {@link Ext.grid.Panel}
  18720. * multislider {@link Ext.slider.Multi}
  18721. * panel {@link Ext.panel.Panel}
  18722. * progressbar {@link Ext.ProgressBar}
  18723. * slider {@link Ext.slider.Single}
  18724. * splitbutton {@link Ext.button.Split}
  18725. * tabpanel {@link Ext.tab.Panel}
  18726. * treepanel {@link Ext.tree.Panel}
  18727. * viewport {@link Ext.container.Viewport}
  18728. * window {@link Ext.window.Window}
  18729. *
  18730. * Toolbar components
  18731. * ---------------------------------------
  18732. * pagingtoolbar {@link Ext.toolbar.Paging}
  18733. * toolbar {@link Ext.toolbar.Toolbar}
  18734. * tbfill {@link Ext.toolbar.Fill}
  18735. * tbitem {@link Ext.toolbar.Item}
  18736. * tbseparator {@link Ext.toolbar.Separator}
  18737. * tbspacer {@link Ext.toolbar.Spacer}
  18738. * tbtext {@link Ext.toolbar.TextItem}
  18739. *
  18740. * Menu components
  18741. * ---------------------------------------
  18742. * menu {@link Ext.menu.Menu}
  18743. * menucheckitem {@link Ext.menu.CheckItem}
  18744. * menuitem {@link Ext.menu.Item}
  18745. * menuseparator {@link Ext.menu.Separator}
  18746. * menutextitem {@link Ext.menu.Item}
  18747. *
  18748. * Form components
  18749. * ---------------------------------------
  18750. * form {@link Ext.form.Panel}
  18751. * checkbox {@link Ext.form.field.Checkbox}
  18752. * combo {@link Ext.form.field.ComboBox}
  18753. * datefield {@link Ext.form.field.Date}
  18754. * displayfield {@link Ext.form.field.Display}
  18755. * field {@link Ext.form.field.Base}
  18756. * fieldset {@link Ext.form.FieldSet}
  18757. * hidden {@link Ext.form.field.Hidden}
  18758. * htmleditor {@link Ext.form.field.HtmlEditor}
  18759. * label {@link Ext.form.Label}
  18760. * numberfield {@link Ext.form.field.Number}
  18761. * radio {@link Ext.form.field.Radio}
  18762. * radiogroup {@link Ext.form.RadioGroup}
  18763. * textarea {@link Ext.form.field.TextArea}
  18764. * textfield {@link Ext.form.field.Text}
  18765. * timefield {@link Ext.form.field.Time}
  18766. * trigger {@link Ext.form.field.Trigger}
  18767. *
  18768. * Chart components
  18769. * ---------------------------------------
  18770. * chart {@link Ext.chart.Chart}
  18771. * barchart {@link Ext.chart.series.Bar}
  18772. * columnchart {@link Ext.chart.series.Column}
  18773. * linechart {@link Ext.chart.series.Line}
  18774. * piechart {@link Ext.chart.series.Pie}
  18775. *
  18776. * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
  18777. * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
  18778. * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
  18779. *
  18780. * @example
  18781. * Ext.create('Ext.Component', {
  18782. * html: 'Hello world!',
  18783. * width: 300,
  18784. * height: 200,
  18785. * padding: 20,
  18786. * style: {
  18787. * color: '#FFFFFF',
  18788. * backgroundColor:'#000000'
  18789. * },
  18790. * renderTo: Ext.getBody()
  18791. * });
  18792. *
  18793. * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
  18794. * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
  18795. * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
  18796. * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
  18797. *
  18798. * [1]: http://sencha.com/learn/Tutorial:Creating_new_UI_controls
  18799. */
  18800. Ext.define('Ext.Component', {
  18801. /* Begin Definitions */
  18802. alias: ['widget.component', 'widget.box'],
  18803. extend: 'Ext.AbstractComponent',
  18804. requires: [
  18805. 'Ext.util.DelayedTask'
  18806. ],
  18807. uses: [
  18808. 'Ext.Layer',
  18809. 'Ext.resizer.Resizer',
  18810. 'Ext.util.ComponentDragger'
  18811. ],
  18812. mixins: {
  18813. floating: 'Ext.util.Floating'
  18814. },
  18815. statics: {
  18816. // Collapse/expand directions
  18817. DIRECTION_TOP: 'top',
  18818. DIRECTION_RIGHT: 'right',
  18819. DIRECTION_BOTTOM: 'bottom',
  18820. DIRECTION_LEFT: 'left',
  18821. VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
  18822. // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
  18823. // This includes dot, comma and whitespace
  18824. INVALID_ID_CHARS_Re: /[\.,\s]/g
  18825. },
  18826. /* End Definitions */
  18827. /**
  18828. * @cfg {Boolean/Object} resizable
  18829. * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
  18830. *
  18831. * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
  18832. * to override any defaults. By default the Component passes its minimum and maximum size, and uses
  18833. * `{@link Ext.resizer.Resizer#dynamic}: false`
  18834. */
  18835. /**
  18836. * @cfg {String} resizeHandles
  18837. * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
  18838. */
  18839. resizeHandles: 'all',
  18840. /**
  18841. * @cfg {Boolean} [autoScroll=false]
  18842. * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
  18843. * `false` to clip any overflowing content.
  18844. */
  18845. /**
  18846. * @cfg {Boolean} floating
  18847. * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
  18848. *
  18849. * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
  18850. *
  18851. * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
  18852. * the global {@link Ext.WindowManager ZIndexManager}
  18853. *
  18854. * ### Floating Components as child items of a Container
  18855. *
  18856. * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
  18857. * a ZIndexManager by examining the ownerCt chain.
  18858. *
  18859. * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
  18860. * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
  18861. * of its stack when the Component's {@link #toFront} method is called.
  18862. *
  18863. * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
  18864. * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
  18865. * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
  18866. * ancestor Container.
  18867. *
  18868. * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
  18869. * ZIndexManager}.
  18870. *
  18871. * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
  18872. * you explicitly {@link #show} them.
  18873. *
  18874. * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
  18875. * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
  18876. * not be set.
  18877. */
  18878. floating: false,
  18879. /**
  18880. * @cfg {Boolean} toFrontOnShow
  18881. * True to automatically call {@link #toFront} when the {@link #show} method is called on an already visible,
  18882. * floating component.
  18883. */
  18884. toFrontOnShow: true,
  18885. /**
  18886. * @property {Ext.ZIndexManager} zIndexManager
  18887. * Only present for {@link #floating} Components after they have been rendered.
  18888. *
  18889. * A reference to the ZIndexManager which is managing this Component's z-index.
  18890. *
  18891. * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
  18892. * a single modal mask which is insert just beneath the topmost visible modal floating Component.
  18893. *
  18894. * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
  18895. * z-index stack.
  18896. *
  18897. * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
  18898. * programatically {@link Ext.Component#render rendered}.
  18899. *
  18900. * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
  18901. * ancestor Container found which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is
  18902. * used.
  18903. *
  18904. * See {@link #floating} and {@link #floatParent}
  18905. */
  18906. /**
  18907. * @property {Ext.Container} floatParent
  18908. * Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.
  18909. *
  18910. * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
  18911. * property.
  18912. *
  18913. * For {@link #floating} Components which are child items of a Container, the floatParent will be the floating
  18914. * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
  18915. * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
  18916. * Components.
  18917. *
  18918. * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
  18919. * Window as its `floatParent`
  18920. *
  18921. * See {@link #floating} and {@link #zIndexManager}
  18922. */
  18923. /**
  18924. * @cfg {Boolean/Object} [draggable=false]
  18925. * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
  18926. * the drag handle.
  18927. *
  18928. * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
  18929. * instantiated to perform dragging.
  18930. *
  18931. * For example to create a Component which may only be dragged around using a certain internal element as the drag
  18932. * handle, use the delegate option:
  18933. *
  18934. * new Ext.Component({
  18935. * constrain: true,
  18936. * floating: true,
  18937. * style: {
  18938. * backgroundColor: '#fff',
  18939. * border: '1px solid black'
  18940. * },
  18941. * html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
  18942. * draggable: {
  18943. * delegate: 'h1'
  18944. * }
  18945. * }).show();
  18946. */
  18947. /**
  18948. * @cfg {Boolean} [maintainFlex=false]
  18949. * **Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a
  18950. * {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox} layout.**
  18951. *
  18952. * Specifies that if an immediate sibling Splitter is moved, the Component on the *other* side is resized, and this
  18953. * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.
  18954. */
  18955. hideMode: 'display',
  18956. // Deprecate 5.0
  18957. hideParent: false,
  18958. ariaRole: 'presentation',
  18959. bubbleEvents: [],
  18960. actionMode: 'el',
  18961. monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
  18962. //renderTpl: new Ext.XTemplate(
  18963. // '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
  18964. // compiled: true,
  18965. // disableFormats: true
  18966. // }
  18967. //),
  18968. /**
  18969. * Creates new Component.
  18970. * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
  18971. *
  18972. * - **an element** : it is set as the internal element and its id used as the component id
  18973. * - **a string** : it is assumed to be the id of an existing element and is used as the component id
  18974. * - **anything else** : it is assumed to be a standard config object and is applied to the component
  18975. */
  18976. constructor: function(config) {
  18977. var me = this;
  18978. config = config || {};
  18979. if (config.initialConfig) {
  18980. // Being initialized from an Ext.Action instance...
  18981. if (config.isAction) {
  18982. me.baseAction = config;
  18983. }
  18984. config = config.initialConfig;
  18985. // component cloning / action set up
  18986. }
  18987. else if (config.tagName || config.dom || Ext.isString(config)) {
  18988. // element object
  18989. config = {
  18990. applyTo: config,
  18991. id: config.id || config
  18992. };
  18993. }
  18994. me.callParent([config]);
  18995. // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
  18996. // register this Component as one of its items
  18997. if (me.baseAction){
  18998. me.baseAction.addComponent(me);
  18999. }
  19000. },
  19001. /**
  19002. * The initComponent template method is an important initialization step for a Component. It is intended to be
  19003. * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
  19004. * initComponent method of the class being created is called first, with each initComponent method
  19005. * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
  19006. * if needed, override the constructor logic of the Component at any step in the hierarchy.
  19007. *
  19008. * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
  19009. * to ensure that the parent class' initComponent method is also called.
  19010. *
  19011. * The following example demonstrates using a dynamic string for the text of a button at the time of
  19012. * instantiation of the class.
  19013. *
  19014. * Ext.define('DynamicButtonText', {
  19015. * extend: 'Ext.button.Button',
  19016. *
  19017. * initComponent: function() {
  19018. * this.text = new Date();
  19019. * this.renderTo = Ext.getBody();
  19020. * this.callParent();
  19021. * }
  19022. * });
  19023. *
  19024. * Ext.onReady(function() {
  19025. * Ext.create('DynamicButtonText');
  19026. * });
  19027. *
  19028. * @template
  19029. */
  19030. initComponent: function() {
  19031. var me = this;
  19032. me.callParent();
  19033. if (me.listeners) {
  19034. me.on(me.listeners);
  19035. delete me.listeners;
  19036. }
  19037. me.enableBubble(me.bubbleEvents);
  19038. me.mons = [];
  19039. },
  19040. // private
  19041. afterRender: function() {
  19042. var me = this,
  19043. resizable = me.resizable;
  19044. if (me.floating) {
  19045. me.makeFloating(me.floating);
  19046. } else {
  19047. me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
  19048. }
  19049. if (Ext.isDefined(me.autoScroll)) {
  19050. me.setAutoScroll(me.autoScroll);
  19051. }
  19052. me.callParent();
  19053. if (!(me.x && me.y) && (me.pageX || me.pageY)) {
  19054. me.setPagePosition(me.pageX, me.pageY);
  19055. }
  19056. if (resizable) {
  19057. me.initResizable(resizable);
  19058. }
  19059. if (me.draggable) {
  19060. me.initDraggable();
  19061. }
  19062. me.initAria();
  19063. },
  19064. initAria: function() {
  19065. var actionEl = this.getActionEl(),
  19066. role = this.ariaRole;
  19067. if (role) {
  19068. actionEl.dom.setAttribute('role', role);
  19069. }
  19070. },
  19071. /**
  19072. * Sets the overflow on the content element of the component.
  19073. * @param {Boolean} scroll True to allow the Component to auto scroll.
  19074. * @return {Ext.Component} this
  19075. */
  19076. setAutoScroll : function(scroll){
  19077. var me = this,
  19078. targetEl;
  19079. scroll = !!scroll;
  19080. if (me.rendered) {
  19081. targetEl = me.getTargetEl();
  19082. targetEl.setStyle('overflow', scroll ? 'auto' : '');
  19083. if (scroll && (Ext.isIE6 || Ext.isIE7)) {
  19084. // The scrollable container element must be non-statically positioned or IE6/7 will make
  19085. // positioned children stay in place rather than scrolling with the rest of the content
  19086. targetEl.position();
  19087. }
  19088. }
  19089. me.autoScroll = scroll;
  19090. return me;
  19091. },
  19092. // private
  19093. makeFloating : function(cfg){
  19094. this.mixins.floating.constructor.call(this, cfg);
  19095. },
  19096. initResizable: function(resizable) {
  19097. var me = this;
  19098. resizable = Ext.apply({
  19099. target: me,
  19100. dynamic: false,
  19101. constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent()),
  19102. handles: me.resizeHandles
  19103. }, resizable);
  19104. resizable.target = me;
  19105. me.resizer = Ext.create('Ext.resizer.Resizer', resizable);
  19106. },
  19107. getDragEl: function() {
  19108. return this.el;
  19109. },
  19110. initDraggable: function() {
  19111. var me = this,
  19112. ddConfig = Ext.applyIf({
  19113. el: me.getDragEl(),
  19114. constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
  19115. }, me.draggable);
  19116. // Add extra configs if Component is specified to be constrained
  19117. if (me.constrain || me.constrainDelegate) {
  19118. ddConfig.constrain = me.constrain;
  19119. ddConfig.constrainDelegate = me.constrainDelegate;
  19120. }
  19121. me.dd = Ext.create('Ext.util.ComponentDragger', me, ddConfig);
  19122. },
  19123. /**
  19124. * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
  19125. * method fires the {@link #move} event.
  19126. * @param {Number} left The new left
  19127. * @param {Number} top The new top
  19128. * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
  19129. * animation configuration.
  19130. * @return {Ext.Component} this
  19131. */
  19132. setPosition: function(x, y, animate) {
  19133. var me = this,
  19134. el = me.el,
  19135. to = {},
  19136. adj, adjX, adjY, xIsNumber, yIsNumber;
  19137. if (Ext.isArray(x)) {
  19138. animate = y;
  19139. y = x[1];
  19140. x = x[0];
  19141. }
  19142. me.x = x;
  19143. me.y = y;
  19144. if (!me.rendered) {
  19145. return me;
  19146. }
  19147. adj = me.adjustPosition(x, y);
  19148. adjX = adj.x;
  19149. adjY = adj.y;
  19150. xIsNumber = Ext.isNumber(adjX);
  19151. yIsNumber = Ext.isNumber(adjY);
  19152. if (xIsNumber || yIsNumber) {
  19153. if (animate) {
  19154. if (xIsNumber) {
  19155. to.left = adjX;
  19156. }
  19157. if (yIsNumber) {
  19158. to.top = adjY;
  19159. }
  19160. me.stopAnimation();
  19161. me.animate(Ext.apply({
  19162. duration: 1000,
  19163. listeners: {
  19164. afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
  19165. },
  19166. to: to
  19167. }, animate));
  19168. }
  19169. else {
  19170. if (!xIsNumber) {
  19171. el.setTop(adjY);
  19172. }
  19173. else if (!yIsNumber) {
  19174. el.setLeft(adjX);
  19175. }
  19176. else {
  19177. el.setLeftTop(adjX, adjY);
  19178. }
  19179. me.afterSetPosition(adjX, adjY);
  19180. }
  19181. }
  19182. return me;
  19183. },
  19184. /**
  19185. * @private
  19186. * @template
  19187. * Template method called after a Component has been positioned.
  19188. */
  19189. afterSetPosition: function(ax, ay) {
  19190. this.onPosition(ax, ay);
  19191. this.fireEvent('move', this, ax, ay);
  19192. },
  19193. /**
  19194. * Displays component at specific xy position.
  19195. * A floating component (like a menu) is positioned relative to its ownerCt if any.
  19196. * Useful for popping up a context menu:
  19197. *
  19198. * listeners: {
  19199. * itemcontextmenu: function(view, record, item, index, event, options) {
  19200. * Ext.create('Ext.menu.Menu', {
  19201. * width: 100,
  19202. * height: 100,
  19203. * margin: '0 0 10 0',
  19204. * items: [{
  19205. * text: 'regular item 1'
  19206. * },{
  19207. * text: 'regular item 2'
  19208. * },{
  19209. * text: 'regular item 3'
  19210. * }]
  19211. * }).showAt(event.getXY());
  19212. * }
  19213. * }
  19214. *
  19215. * @param {Number} x The new x position
  19216. * @param {Number} y The new y position
  19217. * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
  19218. * animation configuration.
  19219. */
  19220. showAt: function(x, y, animate) {
  19221. var me = this;
  19222. if (me.floating) {
  19223. me.setPosition(x, y, animate);
  19224. } else {
  19225. me.setPagePosition(x, y, animate);
  19226. }
  19227. me.show();
  19228. },
  19229. /**
  19230. * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
  19231. * This method fires the {@link #move} event.
  19232. * @param {Number} x The new x position
  19233. * @param {Number} y The new y position
  19234. * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
  19235. * animation configuration.
  19236. * @return {Ext.Component} this
  19237. */
  19238. setPagePosition: function(x, y, animate) {
  19239. var me = this,
  19240. p;
  19241. if (Ext.isArray(x)) {
  19242. y = x[1];
  19243. x = x[0];
  19244. }
  19245. me.pageX = x;
  19246. me.pageY = y;
  19247. if (me.floating && me.floatParent) {
  19248. // Floating Components being positioned in their ownerCt have to be made absolute
  19249. p = me.floatParent.getTargetEl().getViewRegion();
  19250. if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
  19251. x -= p.left;
  19252. }
  19253. if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
  19254. y -= p.top;
  19255. }
  19256. me.setPosition(x, y, animate);
  19257. }
  19258. else {
  19259. p = me.el.translatePoints(x, y);
  19260. me.setPosition(p.left, p.top, animate);
  19261. }
  19262. return me;
  19263. },
  19264. /**
  19265. * Gets the current box measurements of the component's underlying element.
  19266. * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
  19267. * @return {Object} box An object in the format {x, y, width, height}
  19268. */
  19269. getBox : function(local){
  19270. var pos = this.getPosition(local),
  19271. size = this.getSize();
  19272. size.x = pos[0];
  19273. size.y = pos[1];
  19274. return size;
  19275. },
  19276. /**
  19277. * Sets the current box measurements of the component's underlying element.
  19278. * @param {Object} box An object in the format {x, y, width, height}
  19279. * @return {Ext.Component} this
  19280. */
  19281. updateBox : function(box){
  19282. this.setSize(box.width, box.height);
  19283. this.setPagePosition(box.x, box.y);
  19284. return this;
  19285. },
  19286. // Include margins
  19287. getOuterSize: function() {
  19288. var el = this.el;
  19289. return {
  19290. width: el.getWidth() + el.getMargin('lr'),
  19291. height: el.getHeight() + el.getMargin('tb')
  19292. };
  19293. },
  19294. // private
  19295. adjustPosition: function(x, y) {
  19296. // Floating Components being positioned in their ownerCt have to be made absolute
  19297. if (this.floating && this.floatParent) {
  19298. var o = this.floatParent.getTargetEl().getViewRegion();
  19299. x += o.left;
  19300. y += o.top;
  19301. }
  19302. return {
  19303. x: x,
  19304. y: y
  19305. };
  19306. },
  19307. /**
  19308. * Gets the current XY position of the component's underlying element.
  19309. * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
  19310. * @return {Number[]} The XY position of the element (e.g., [100, 200])
  19311. */
  19312. getPosition: function(local) {
  19313. var me = this,
  19314. el = me.el,
  19315. xy,
  19316. o;
  19317. // Floating Components which were just rendered with no ownerCt return local position.
  19318. if ((local === true) || (me.floating && !me.floatParent)) {
  19319. return [el.getLeft(true), el.getTop(true)];
  19320. }
  19321. xy = me.xy || el.getXY();
  19322. // Floating Components in an ownerCt have to have their positions made relative
  19323. if (me.floating) {
  19324. o = me.floatParent.getTargetEl().getViewRegion();
  19325. xy[0] -= o.left;
  19326. xy[1] -= o.top;
  19327. }
  19328. return xy;
  19329. },
  19330. getId: function() {
  19331. var me = this,
  19332. xtype;
  19333. if (!me.id) {
  19334. xtype = me.getXType();
  19335. xtype = xtype ? xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-') : 'ext-comp';
  19336. me.id = xtype + '-' + me.getAutoId();
  19337. }
  19338. return me.id;
  19339. },
  19340. onEnable: function() {
  19341. var actionEl = this.getActionEl();
  19342. actionEl.dom.removeAttribute('aria-disabled');
  19343. actionEl.dom.disabled = false;
  19344. this.callParent();
  19345. },
  19346. onDisable: function() {
  19347. var actionEl = this.getActionEl();
  19348. actionEl.dom.setAttribute('aria-disabled', true);
  19349. actionEl.dom.disabled = true;
  19350. this.callParent();
  19351. },
  19352. /**
  19353. * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
  19354. *
  19355. * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
  19356. * brought to the front of its {@link #zIndexManager z-index stack}.
  19357. *
  19358. * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
  19359. * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
  19360. * with `floating: true`.** The target from which the Component should animate from while opening.
  19361. * @param {Function} [callback] A callback function to call after the Component is displayed.
  19362. * Only necessary if animation was specified.
  19363. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  19364. * Defaults to this Component.
  19365. * @return {Ext.Component} this
  19366. */
  19367. show: function(animateTarget, cb, scope) {
  19368. var me = this;
  19369. if (me.rendered && me.isVisible()) {
  19370. if (me.toFrontOnShow && me.floating) {
  19371. me.toFront();
  19372. }
  19373. } else if (me.fireEvent('beforeshow', me) !== false) {
  19374. me.hidden = false;
  19375. // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
  19376. if (!me.rendered && (me.autoRender || me.floating)) {
  19377. me.doAutoRender();
  19378. }
  19379. if (me.rendered) {
  19380. me.beforeShow();
  19381. me.onShow.apply(me, arguments);
  19382. // Notify any owning Container unless it's suspended.
  19383. // Floating Components do not participate in layouts.
  19384. if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
  19385. me.ownerCt.doLayout();
  19386. }
  19387. me.afterShow.apply(me, arguments);
  19388. }
  19389. }
  19390. return me;
  19391. },
  19392. beforeShow: Ext.emptyFn,
  19393. // Private. Override in subclasses where more complex behaviour is needed.
  19394. onShow: function() {
  19395. var me = this;
  19396. me.el.show();
  19397. me.callParent(arguments);
  19398. if (me.floating && me.constrain) {
  19399. me.doConstrain();
  19400. }
  19401. },
  19402. afterShow: function(animateTarget, cb, scope) {
  19403. var me = this,
  19404. fromBox,
  19405. toBox,
  19406. ghostPanel;
  19407. // Default to configured animate target if none passed
  19408. animateTarget = animateTarget || me.animateTarget;
  19409. // Need to be able to ghost the Component
  19410. if (!me.ghost) {
  19411. animateTarget = null;
  19412. }
  19413. // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
  19414. if (animateTarget) {
  19415. animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
  19416. toBox = me.el.getBox();
  19417. fromBox = animateTarget.getBox();
  19418. me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
  19419. ghostPanel = me.ghost();
  19420. ghostPanel.el.stopAnimation();
  19421. // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
  19422. ghostPanel.el.setX(-10000);
  19423. ghostPanel.el.animate({
  19424. from: fromBox,
  19425. to: toBox,
  19426. listeners: {
  19427. afteranimate: function() {
  19428. delete ghostPanel.componentLayout.lastComponentSize;
  19429. me.unghost();
  19430. me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
  19431. me.onShowComplete(cb, scope);
  19432. }
  19433. }
  19434. });
  19435. }
  19436. else {
  19437. me.onShowComplete(cb, scope);
  19438. }
  19439. },
  19440. onShowComplete: function(cb, scope) {
  19441. var me = this;
  19442. if (me.floating) {
  19443. me.toFront();
  19444. }
  19445. Ext.callback(cb, scope || me);
  19446. me.fireEvent('show', me);
  19447. },
  19448. /**
  19449. * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
  19450. * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
  19451. * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
  19452. * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
  19453. * @param {Function} [callback] A callback function to call after the Component is hidden.
  19454. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  19455. * Defaults to this Component.
  19456. * @return {Ext.Component} this
  19457. */
  19458. hide: function() {
  19459. var me = this;
  19460. // Clear the flag which is set if a floatParent was hidden while this is visible.
  19461. // If a hide operation was subsequently called, that pending show must be hidden.
  19462. me.showOnParentShow = false;
  19463. if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
  19464. me.hidden = true;
  19465. if (me.rendered) {
  19466. me.onHide.apply(me, arguments);
  19467. // Notify any owning Container unless it's suspended.
  19468. // Floating Components do not participate in layouts.
  19469. if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
  19470. me.ownerCt.doLayout();
  19471. }
  19472. }
  19473. }
  19474. return me;
  19475. },
  19476. // Possibly animate down to a target element.
  19477. onHide: function(animateTarget, cb, scope) {
  19478. var me = this,
  19479. ghostPanel,
  19480. toBox;
  19481. // Default to configured animate target if none passed
  19482. animateTarget = animateTarget || me.animateTarget;
  19483. // Need to be able to ghost the Component
  19484. if (!me.ghost) {
  19485. animateTarget = null;
  19486. }
  19487. // If we're animating, kick off an animation of the ghost down to the target
  19488. if (animateTarget) {
  19489. animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
  19490. ghostPanel = me.ghost();
  19491. ghostPanel.el.stopAnimation();
  19492. toBox = animateTarget.getBox();
  19493. toBox.width += 'px';
  19494. toBox.height += 'px';
  19495. ghostPanel.el.animate({
  19496. to: toBox,
  19497. listeners: {
  19498. afteranimate: function() {
  19499. delete ghostPanel.componentLayout.lastComponentSize;
  19500. ghostPanel.el.hide();
  19501. me.afterHide(cb, scope);
  19502. }
  19503. }
  19504. });
  19505. }
  19506. me.el.hide();
  19507. if (!animateTarget) {
  19508. me.afterHide(cb, scope);
  19509. }
  19510. },
  19511. afterHide: function(cb, scope) {
  19512. Ext.callback(cb, scope || this);
  19513. this.fireEvent('hide', this);
  19514. },
  19515. /**
  19516. * @private
  19517. * @template
  19518. * Template method to contribute functionality at destroy time.
  19519. */
  19520. onDestroy: function() {
  19521. var me = this;
  19522. // Ensure that any ancillary components are destroyed.
  19523. if (me.rendered) {
  19524. Ext.destroy(
  19525. me.proxy,
  19526. me.proxyWrap,
  19527. me.resizer
  19528. );
  19529. // Different from AbstractComponent
  19530. if (me.actionMode == 'container' || me.removeMode == 'container') {
  19531. me.container.remove();
  19532. }
  19533. }
  19534. delete me.focusTask;
  19535. me.callParent();
  19536. },
  19537. deleteMembers: function() {
  19538. var args = arguments,
  19539. len = args.length,
  19540. i = 0;
  19541. for (; i < len; ++i) {
  19542. delete this[args[i]];
  19543. }
  19544. },
  19545. /**
  19546. * Try to focus this component.
  19547. * @param {Boolean} [selectText] If applicable, true to also select the text in this component
  19548. * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
  19549. * @return {Ext.Component} this
  19550. */
  19551. focus: function(selectText, delay) {
  19552. var me = this,
  19553. focusEl;
  19554. if (delay) {
  19555. if (!me.focusTask) {
  19556. me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
  19557. }
  19558. me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
  19559. return me;
  19560. }
  19561. if (me.rendered && !me.isDestroyed) {
  19562. // getFocusEl could return a Component.
  19563. focusEl = me.getFocusEl();
  19564. focusEl.focus();
  19565. if (focusEl.dom && selectText === true) {
  19566. focusEl.dom.select();
  19567. }
  19568. // Focusing a floating Component brings it to the front of its stack.
  19569. // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
  19570. if (me.floating) {
  19571. me.toFront(true);
  19572. }
  19573. }
  19574. return me;
  19575. },
  19576. /**
  19577. * @private
  19578. * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
  19579. * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
  19580. * by the {@link #focus} method.
  19581. * @returns {Ext.Element} the focus holing element.
  19582. */
  19583. getFocusEl: function() {
  19584. return this.el;
  19585. },
  19586. // private
  19587. blur: function() {
  19588. if (this.rendered) {
  19589. this.getFocusEl().blur();
  19590. }
  19591. return this;
  19592. },
  19593. getEl: function() {
  19594. return this.el;
  19595. },
  19596. // Deprecate 5.0
  19597. getResizeEl: function() {
  19598. return this.el;
  19599. },
  19600. // Deprecate 5.0
  19601. getPositionEl: function() {
  19602. return this.el;
  19603. },
  19604. // Deprecate 5.0
  19605. getActionEl: function() {
  19606. return this.el;
  19607. },
  19608. // Deprecate 5.0
  19609. getVisibilityEl: function() {
  19610. return this.el;
  19611. },
  19612. // Deprecate 5.0
  19613. onResize: Ext.emptyFn,
  19614. // private
  19615. getBubbleTarget: function() {
  19616. return this.ownerCt;
  19617. },
  19618. // private
  19619. getContentTarget: function() {
  19620. return this.el;
  19621. },
  19622. /**
  19623. * Clone the current component using the original config values passed into this instance by default.
  19624. * @param {Object} overrides A new config containing any properties to override in the cloned version.
  19625. * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
  19626. * @return {Ext.Component} clone The cloned copy of this component
  19627. */
  19628. cloneConfig: function(overrides) {
  19629. overrides = overrides || {};
  19630. var id = overrides.id || Ext.id(),
  19631. cfg = Ext.applyIf(overrides, this.initialConfig),
  19632. self;
  19633. cfg.id = id;
  19634. self = Ext.getClass(this);
  19635. // prevent dup id
  19636. return new self(cfg);
  19637. },
  19638. /**
  19639. * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
  19640. * xtypes, see the {@link Ext.Component} header. Example usage:
  19641. *
  19642. * var t = new Ext.form.field.Text();
  19643. * alert(t.getXType()); // alerts 'textfield'
  19644. *
  19645. * @return {String} The xtype
  19646. */
  19647. getXType: function() {
  19648. return this.self.xtype;
  19649. },
  19650. /**
  19651. * Find a container above this component at any level by a custom function. If the passed function returns true, the
  19652. * container will be returned.
  19653. * @param {Function} fn The custom function to call with the arguments (container, this component).
  19654. * @return {Ext.container.Container} The first Container for which the custom function returns true
  19655. */
  19656. findParentBy: function(fn) {
  19657. var p;
  19658. // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
  19659. for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
  19660. return p || null;
  19661. },
  19662. /**
  19663. * Find a container above this component at any level by xtype or class
  19664. *
  19665. * See also the {@link Ext.Component#up up} method.
  19666. *
  19667. * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
  19668. * @return {Ext.container.Container} The first Container which matches the given xtype or class
  19669. */
  19670. findParentByType: function(xtype) {
  19671. return Ext.isFunction(xtype) ?
  19672. this.findParentBy(function(p) {
  19673. return p.constructor === xtype;
  19674. })
  19675. :
  19676. this.up(xtype);
  19677. },
  19678. /**
  19679. * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
  19680. * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
  19681. * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
  19682. *
  19683. * @param {Function} fn The function to call
  19684. * @param {Object} [scope] The scope of the function. Defaults to current node.
  19685. * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
  19686. * @return {Ext.Component} this
  19687. */
  19688. bubble: function(fn, scope, args) {
  19689. var p = this;
  19690. while (p) {
  19691. if (fn.apply(scope || p, args || [p]) === false) {
  19692. break;
  19693. }
  19694. p = p.ownerCt;
  19695. }
  19696. return this;
  19697. },
  19698. getProxy: function() {
  19699. var me = this,
  19700. target;
  19701. if (!me.proxy) {
  19702. target = Ext.getBody();
  19703. if (Ext.scopeResetCSS) {
  19704. me.proxyWrap = target = Ext.getBody().createChild({
  19705. cls: Ext.baseCSSPrefix + 'reset'
  19706. });
  19707. }
  19708. me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
  19709. }
  19710. return me.proxy;
  19711. }
  19712. });
  19713. /**
  19714. * @class Ext.layout.container.AbstractContainer
  19715. * @extends Ext.layout.Layout
  19716. * Please refer to sub classes documentation
  19717. * @private
  19718. */
  19719. Ext.define('Ext.layout.container.AbstractContainer', {
  19720. /* Begin Definitions */
  19721. extend: 'Ext.layout.Layout',
  19722. /* End Definitions */
  19723. type: 'container',
  19724. /**
  19725. * @cfg {Boolean} bindToOwnerCtComponent
  19726. * Flag to notify the ownerCt Component on afterLayout of a change
  19727. */
  19728. bindToOwnerCtComponent: false,
  19729. /**
  19730. * @cfg {Boolean} bindToOwnerCtContainer
  19731. * Flag to notify the ownerCt Container on afterLayout of a change
  19732. */
  19733. bindToOwnerCtContainer: false,
  19734. /**
  19735. * @cfg {String} itemCls
  19736. * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
  19737. * customized styles to the container or any of its children using standard CSS rules. See
  19738. * {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.</p>
  19739. * </p>
  19740. */
  19741. /**
  19742. * Set the size of an item within the Container. We should always use setCalculatedSize.
  19743. * @private
  19744. */
  19745. setItemSize: function(item, width, height) {
  19746. if (Ext.isObject(width)) {
  19747. height = width.height;
  19748. width = width.width;
  19749. }
  19750. item.setCalculatedSize(width, height, this.owner);
  19751. },
  19752. /**
  19753. * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
  19754. * base class), or the layout phase (onLayout).</p>
  19755. * @return {Ext.Component[]} of child components
  19756. */
  19757. getLayoutItems: function() {
  19758. return this.owner && this.owner.items && this.owner.items.items || [];
  19759. },
  19760. /**
  19761. * Containers should not lay out child components when collapsed.
  19762. */
  19763. beforeLayout: function() {
  19764. return !this.owner.collapsed && this.callParent(arguments);
  19765. },
  19766. afterLayout: function() {
  19767. this.owner.afterLayout(this);
  19768. },
  19769. /**
  19770. * Returns the owner component's resize element.
  19771. * @return {Ext.Element}
  19772. */
  19773. getTarget: function() {
  19774. return this.owner.getTargetEl();
  19775. },
  19776. /**
  19777. * <p>Returns the element into which rendering must take place. Defaults to the owner Container's target element.</p>
  19778. * May be overridden in layout managers which implement an inner element.
  19779. * @return {Ext.Element}
  19780. */
  19781. getRenderTarget: function() {
  19782. return this.owner.getTargetEl();
  19783. }
  19784. });
  19785. /**
  19786. * @class Ext.layout.container.Container
  19787. * @extends Ext.layout.container.AbstractContainer
  19788. * <p>This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
  19789. * configuration property. See {@link Ext.container.Container#layout} for additional details.</p>
  19790. */
  19791. Ext.define('Ext.layout.container.Container', {
  19792. /* Begin Definitions */
  19793. extend: 'Ext.layout.container.AbstractContainer',
  19794. alternateClassName: 'Ext.layout.ContainerLayout',
  19795. /* End Definitions */
  19796. layoutItem: function(item, box) {
  19797. if (box) {
  19798. item.doComponentLayout(box.width, box.height);
  19799. } else {
  19800. item.doComponentLayout();
  19801. }
  19802. },
  19803. getLayoutTargetSize : function() {
  19804. var target = this.getTarget(),
  19805. ret;
  19806. if (target) {
  19807. ret = target.getViewSize();
  19808. // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
  19809. // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
  19810. // with getViewSize
  19811. if (Ext.isIE && ret.width == 0){
  19812. ret = target.getStyleSize();
  19813. }
  19814. ret.width -= target.getPadding('lr');
  19815. ret.height -= target.getPadding('tb');
  19816. }
  19817. return ret;
  19818. },
  19819. beforeLayout: function() {
  19820. if (this.owner.beforeLayout(arguments) !== false) {
  19821. return this.callParent(arguments);
  19822. }
  19823. else {
  19824. return false;
  19825. }
  19826. },
  19827. /**
  19828. * @protected
  19829. * Returns all items that are rendered
  19830. * @return {Array} All matching items
  19831. */
  19832. getRenderedItems: function() {
  19833. var me = this,
  19834. target = me.getTarget(),
  19835. items = me.getLayoutItems(),
  19836. ln = items.length,
  19837. renderedItems = [],
  19838. i, item;
  19839. for (i = 0; i < ln; i++) {
  19840. item = items[i];
  19841. if (item.rendered && me.isValidParent(item, target, i)) {
  19842. renderedItems.push(item);
  19843. }
  19844. }
  19845. return renderedItems;
  19846. },
  19847. /**
  19848. * @protected
  19849. * Returns all items that are both rendered and visible
  19850. * @return {Array} All matching items
  19851. */
  19852. getVisibleItems: function() {
  19853. var target = this.getTarget(),
  19854. items = this.getLayoutItems(),
  19855. ln = items.length,
  19856. visibleItems = [],
  19857. i, item;
  19858. for (i = 0; i < ln; i++) {
  19859. item = items[i];
  19860. if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
  19861. visibleItems.push(item);
  19862. }
  19863. }
  19864. return visibleItems;
  19865. }
  19866. });
  19867. /**
  19868. * @class Ext.layout.container.Auto
  19869. * @extends Ext.layout.container.Container
  19870. *
  19871. * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
  19872. * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
  19873. * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
  19874. * to any child containers.
  19875. *
  19876. * @example
  19877. * Ext.create('Ext.Panel', {
  19878. * width: 500,
  19879. * height: 280,
  19880. * title: "AutoLayout Panel",
  19881. * layout: 'auto',
  19882. * renderTo: document.body,
  19883. * items: [{
  19884. * xtype: 'panel',
  19885. * title: 'Top Inner Panel',
  19886. * width: '75%',
  19887. * height: 90
  19888. * },
  19889. * {
  19890. * xtype: 'panel',
  19891. * title: 'Bottom Inner Panel',
  19892. * width: '75%',
  19893. * height: 90
  19894. * }]
  19895. * });
  19896. */
  19897. Ext.define('Ext.layout.container.Auto', {
  19898. /* Begin Definitions */
  19899. alias: ['layout.auto', 'layout.autocontainer'],
  19900. extend: 'Ext.layout.container.Container',
  19901. /* End Definitions */
  19902. type: 'autocontainer',
  19903. bindToOwnerCtComponent: true,
  19904. // @private
  19905. onLayout : function(owner, target) {
  19906. var me = this,
  19907. items = me.getLayoutItems(),
  19908. ln = items.length,
  19909. i;
  19910. // Ensure the Container is only primed with the clear element if there are child items.
  19911. if (ln) {
  19912. // Auto layout uses natural HTML flow to arrange the child items.
  19913. // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
  19914. // containing element height, we create a zero-sized element with style clear:both to force a "new line"
  19915. if (!me.clearEl) {
  19916. me.clearEl = me.getRenderTarget().createChild({
  19917. cls: Ext.baseCSSPrefix + 'clear',
  19918. role: 'presentation'
  19919. });
  19920. }
  19921. // Auto layout allows CSS to size its child items.
  19922. for (i = 0; i < ln; i++) {
  19923. me.setItemSize(items[i]);
  19924. }
  19925. }
  19926. },
  19927. configureItem: function(item) {
  19928. this.callParent(arguments);
  19929. // Auto layout does not manage any dimensions.
  19930. item.layoutManagedHeight = 2;
  19931. item.layoutManagedWidth = 2;
  19932. }
  19933. });
  19934. /**
  19935. * @class Ext.container.AbstractContainer
  19936. * @extends Ext.Component
  19937. * An abstract base class which provides shared methods for Containers across the Sencha product line.
  19938. * @private
  19939. */
  19940. Ext.define('Ext.container.AbstractContainer', {
  19941. /* Begin Definitions */
  19942. extend: 'Ext.Component',
  19943. requires: [
  19944. 'Ext.util.MixedCollection',
  19945. 'Ext.layout.container.Auto',
  19946. 'Ext.ZIndexManager'
  19947. ],
  19948. /* End Definitions */
  19949. /**
  19950. * @cfg {String/Object} layout
  19951. * <p><b>Important</b>: In order for child items to be correctly sized and
  19952. * positioned, typically a layout manager <b>must</b> be specified through
  19953. * the <code>layout</code> configuration option.</p>
  19954. * <p>The sizing and positioning of child {@link #items} is the responsibility of
  19955. * the Container's layout manager which creates and manages the type of layout
  19956. * you have in mind. For example:</p>
  19957. * <p>If the {@link #layout} configuration is not explicitly specified for
  19958. * a general purpose container (e.g. Container or Panel) the
  19959. * {@link Ext.layout.container.Auto default layout manager} will be used
  19960. * which does nothing but render child components sequentially into the
  19961. * Container (no sizing or positioning will be performed in this situation).</p>
  19962. * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
  19963. * <div><ul class="mdetail-params">
  19964. * <li><u>Specify as an Object</u></li>
  19965. * <div><ul class="mdetail-params">
  19966. * <li>Example usage:</li>
  19967. * <pre><code>
  19968. layout: {
  19969. type: 'vbox',
  19970. align: 'left'
  19971. }
  19972. </code></pre>
  19973. *
  19974. * <li><code><b>type</b></code></li>
  19975. * <br/><p>The layout type to be used for this container. If not specified,
  19976. * a default {@link Ext.layout.container.Auto} will be created and used.</p>
  19977. * <p>Valid layout <code>type</code> values are:</p>
  19978. * <div class="sub-desc"><ul class="mdetail-params">
  19979. * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
  19980. * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
  19981. * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
  19982. * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
  19983. * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
  19984. * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
  19985. * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
  19986. * </ul></div>
  19987. *
  19988. * <li>Layout specific configuration properties</li>
  19989. * <p>Additional layout specific configuration properties may also be
  19990. * specified. For complete details regarding the valid config options for
  19991. * each layout type, see the layout class corresponding to the <code>type</code>
  19992. * specified.</p>
  19993. *
  19994. * </ul></div>
  19995. *
  19996. * <li><u>Specify as a String</u></li>
  19997. * <div><ul class="mdetail-params">
  19998. * <li>Example usage:</li>
  19999. * <pre><code>
  20000. layout: 'vbox'
  20001. </code></pre>
  20002. * <li><code><b>layout</b></code></li>
  20003. * <p>The layout <code>type</code> to be used for this container (see list
  20004. * of valid layout type values above).</p>
  20005. * <p>Additional layout specific configuration properties. For complete
  20006. * details regarding the valid config options for each layout type, see the
  20007. * layout class corresponding to the <code>layout</code> specified.</p>
  20008. * </ul></div></ul></div>
  20009. */
  20010. /**
  20011. * @cfg {String/Number} activeItem
  20012. * A string component id or the numeric index of the component that should be initially activated within the
  20013. * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
  20014. * item in the container's collection). activeItem only applies to layout styles that can display
  20015. * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
  20016. */
  20017. /**
  20018. * @cfg {Object/Object[]} items
  20019. * <p>A single item, or an array of child Components to be added to this container</p>
  20020. * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
  20021. * its encapsulating element and performs no sizing or positioning upon them.</b><p>
  20022. * <p>Example:</p>
  20023. * <pre><code>
  20024. // specifying a single item
  20025. items: {...},
  20026. layout: 'fit', // The single items is sized to fit
  20027. // specifying multiple items
  20028. items: [{...}, {...}],
  20029. layout: 'hbox', // The items are arranged horizontally
  20030. </code></pre>
  20031. * <p>Each item may be:</p>
  20032. * <ul>
  20033. * <li>A {@link Ext.Component Component}</li>
  20034. * <li>A Component configuration object</li>
  20035. * </ul>
  20036. * <p>If a configuration object is specified, the actual type of Component to be
  20037. * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
  20038. * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
  20039. * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
  20040. * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
  20041. * <p><b>Notes</b>:</p>
  20042. * <p>Ext uses lazy rendering. Child Components will only be rendered
  20043. * should it become necessary. Items are automatically laid out when they are first
  20044. * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
  20045. * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
  20046. * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
  20047. */
  20048. /**
  20049. * @cfg {Object/Function} defaults
  20050. * This option is a means of applying default settings to all added items whether added through the {@link #items}
  20051. * config or via the {@link #add} or {@link #insert} methods.
  20052. *
  20053. * Defaults are applied to both config objects and instantiated components conditionally so as not to override
  20054. * existing properties in the item (see {@link Ext#applyIf}).
  20055. *
  20056. * If the defaults option is specified as a function, then the function will be called using this Container as the
  20057. * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
  20058. * from that call is then applied to the item as default properties.
  20059. *
  20060. * For example, to automatically apply padding to the body of each of a set of
  20061. * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
  20062. *
  20063. * Usage:
  20064. *
  20065. * defaults: { // defaults are applied to items, not the container
  20066. * autoScroll: true
  20067. * },
  20068. * items: [
  20069. * // default will not be applied here, panel1 will be autoScroll: false
  20070. * {
  20071. * xtype: 'panel',
  20072. * id: 'panel1',
  20073. * autoScroll: false
  20074. * },
  20075. * // this component will have autoScroll: true
  20076. * new Ext.panel.Panel({
  20077. * id: 'panel2'
  20078. * })
  20079. * ]
  20080. */
  20081. /** @cfg {Boolean} suspendLayout
  20082. * If true, suspend calls to doLayout. Useful when batching multiple adds to a container and not passing them
  20083. * as multiple arguments or an array.
  20084. */
  20085. suspendLayout : false,
  20086. /** @cfg {Boolean} autoDestroy
  20087. * If true the container will automatically destroy any contained component that is removed from it, else
  20088. * destruction must be handled manually.
  20089. * Defaults to true.
  20090. */
  20091. autoDestroy : true,
  20092. /** @cfg {String} defaultType
  20093. * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
  20094. * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
  20095. * <p>Defaults to <code>'panel'</code>.</p>
  20096. */
  20097. defaultType: 'panel',
  20098. isContainer : true,
  20099. /**
  20100. * The number of container layout calls made on this object.
  20101. * @property layoutCounter
  20102. * @type {Number}
  20103. * @private
  20104. */
  20105. layoutCounter : 0,
  20106. baseCls: Ext.baseCSSPrefix + 'container',
  20107. /**
  20108. * @cfg {String[]} bubbleEvents
  20109. * <p>An array of events that, when fired, should be bubbled to any parent container.
  20110. * See {@link Ext.util.Observable#enableBubble}.
  20111. * Defaults to <code>['add', 'remove']</code>.
  20112. */
  20113. bubbleEvents: ['add', 'remove'],
  20114. // @private
  20115. initComponent : function(){
  20116. var me = this;
  20117. me.addEvents(
  20118. /**
  20119. * @event afterlayout
  20120. * Fires when the components in this container are arranged by the associated layout manager.
  20121. * @param {Ext.container.Container} this
  20122. * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
  20123. */
  20124. 'afterlayout',
  20125. /**
  20126. * @event beforeadd
  20127. * Fires before any {@link Ext.Component} is added or inserted into the container.
  20128. * A handler can return false to cancel the add.
  20129. * @param {Ext.container.Container} this
  20130. * @param {Ext.Component} component The component being added
  20131. * @param {Number} index The index at which the component will be added to the container's items collection
  20132. */
  20133. 'beforeadd',
  20134. /**
  20135. * @event beforeremove
  20136. * Fires before any {@link Ext.Component} is removed from the container. A handler can return
  20137. * false to cancel the remove.
  20138. * @param {Ext.container.Container} this
  20139. * @param {Ext.Component} component The component being removed
  20140. */
  20141. 'beforeremove',
  20142. /**
  20143. * @event add
  20144. * @bubbles
  20145. * Fires after any {@link Ext.Component} is added or inserted into the container.
  20146. * @param {Ext.container.Container} this
  20147. * @param {Ext.Component} component The component that was added
  20148. * @param {Number} index The index at which the component was added to the container's items collection
  20149. */
  20150. 'add',
  20151. /**
  20152. * @event remove
  20153. * @bubbles
  20154. * Fires after any {@link Ext.Component} is removed from the container.
  20155. * @param {Ext.container.Container} this
  20156. * @param {Ext.Component} component The component that was removed
  20157. */
  20158. 'remove'
  20159. );
  20160. // layoutOnShow stack
  20161. me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
  20162. me.callParent();
  20163. me.initItems();
  20164. },
  20165. // @private
  20166. initItems : function() {
  20167. var me = this,
  20168. items = me.items;
  20169. /**
  20170. * The MixedCollection containing all the child items of this container.
  20171. * @property items
  20172. * @type Ext.util.MixedCollection
  20173. */
  20174. me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
  20175. if (items) {
  20176. if (!Ext.isArray(items)) {
  20177. items = [items];
  20178. }
  20179. me.add(items);
  20180. }
  20181. },
  20182. // @private
  20183. afterRender : function() {
  20184. this.getLayout();
  20185. this.callParent();
  20186. },
  20187. renderChildren: function () {
  20188. var me = this,
  20189. layout = me.getLayout();
  20190. me.callParent();
  20191. // this component's elements exist, so now create the child components' elements
  20192. if (layout) {
  20193. me.suspendLayout = true;
  20194. layout.renderChildren();
  20195. delete me.suspendLayout;
  20196. }
  20197. },
  20198. // @private
  20199. setLayout : function(layout) {
  20200. var currentLayout = this.layout;
  20201. if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
  20202. currentLayout.setOwner(null);
  20203. }
  20204. this.layout = layout;
  20205. layout.setOwner(this);
  20206. },
  20207. /**
  20208. * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
  20209. * If a layout has not been instantiated yet, that is done first
  20210. * @return {Ext.layout.container.AbstractContainer} The layout
  20211. */
  20212. getLayout : function() {
  20213. var me = this;
  20214. if (!me.layout || !me.layout.isLayout) {
  20215. me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
  20216. }
  20217. return me.layout;
  20218. },
  20219. /**
  20220. * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
  20221. * form most cases.
  20222. * @return {Ext.container.Container} this
  20223. */
  20224. doLayout : function() {
  20225. var me = this,
  20226. layout = me.getLayout();
  20227. if (me.rendered && layout && !me.suspendLayout) {
  20228. // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
  20229. if (!me.isFixedWidth() || !me.isFixedHeight()) {
  20230. // Only run the ComponentLayout if it is not already in progress
  20231. if (me.componentLayout.layoutBusy !== true) {
  20232. me.doComponentLayout();
  20233. if (me.componentLayout.layoutCancelled === true) {
  20234. layout.layout();
  20235. }
  20236. }
  20237. }
  20238. // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
  20239. else {
  20240. // Only run the ContainerLayout if it is not already in progress
  20241. if (layout.layoutBusy !== true) {
  20242. layout.layout();
  20243. }
  20244. }
  20245. }
  20246. return me;
  20247. },
  20248. // @private
  20249. afterLayout : function(layout) {
  20250. ++this.layoutCounter;
  20251. this.fireEvent('afterlayout', this, layout);
  20252. },
  20253. // @private
  20254. prepareItems : function(items, applyDefaults) {
  20255. if (!Ext.isArray(items)) {
  20256. items = [items];
  20257. }
  20258. // Make sure defaults are applied and item is initialized
  20259. var i = 0,
  20260. len = items.length,
  20261. item;
  20262. for (; i < len; i++) {
  20263. item = items[i];
  20264. if (applyDefaults) {
  20265. item = this.applyDefaults(item);
  20266. }
  20267. items[i] = this.lookupComponent(item);
  20268. }
  20269. return items;
  20270. },
  20271. // @private
  20272. applyDefaults : function(config) {
  20273. var defaults = this.defaults;
  20274. if (defaults) {
  20275. if (Ext.isFunction(defaults)) {
  20276. defaults = defaults.call(this, config);
  20277. }
  20278. if (Ext.isString(config)) {
  20279. config = Ext.ComponentManager.get(config);
  20280. }
  20281. Ext.applyIf(config, defaults);
  20282. }
  20283. return config;
  20284. },
  20285. // @private
  20286. lookupComponent : function(comp) {
  20287. return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
  20288. },
  20289. // @private
  20290. createComponent : function(config, defaultType) {
  20291. // // add in ownerCt at creation time but then immediately
  20292. // // remove so that onBeforeAdd can handle it
  20293. // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
  20294. //
  20295. // delete component.initialConfig.ownerCt;
  20296. // delete component.ownerCt;
  20297. return Ext.ComponentManager.create(config, defaultType || this.defaultType);
  20298. },
  20299. // @private - used as the key lookup function for the items collection
  20300. getComponentId : function(comp) {
  20301. return comp.getItemId();
  20302. },
  20303. /**
  20304. Adds {@link Ext.Component Component}(s) to this Container.
  20305. ##Description:##
  20306. - Fires the {@link #beforeadd} event before adding.
  20307. - The Container's {@link #defaults default config values} will be applied
  20308. accordingly (see `{@link #defaults}` for details).
  20309. - Fires the `{@link #add}` event after the component has been added.
  20310. ##Notes:##
  20311. If the Container is __already rendered__ when `add`
  20312. is called, it will render the newly added Component into its content area.
  20313. __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
  20314. will recalculate its internal layout at this time too.
  20315. Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
  20316. If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
  20317. tb = new {@link Ext.toolbar.Toolbar}({
  20318. renderTo: document.body
  20319. }); // toolbar is rendered
  20320. tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
  20321. ##Warning:##
  20322. Components directly managed by the BorderLayout layout manager
  20323. may not be removed or added. See the Notes for {@link Ext.layout.container.Border BorderLayout}
  20324. for more details.
  20325. * @param {Ext.Component[]/Ext.Component...} component
  20326. * Either one or more Components to add or an Array of Components to add.
  20327. * See `{@link #items}` for additional information.
  20328. *
  20329. * @return {Ext.Component[]/Ext.Component} The Components that were added.
  20330. * @markdown
  20331. */
  20332. add : function() {
  20333. var me = this,
  20334. args = Array.prototype.slice.call(arguments),
  20335. hasMultipleArgs,
  20336. items,
  20337. results = [],
  20338. i,
  20339. ln,
  20340. item,
  20341. index = -1,
  20342. cmp;
  20343. if (typeof args[0] == 'number') {
  20344. index = args.shift();
  20345. }
  20346. hasMultipleArgs = args.length > 1;
  20347. if (hasMultipleArgs || Ext.isArray(args[0])) {
  20348. items = hasMultipleArgs ? args : args[0];
  20349. // Suspend Layouts while we add multiple items to the container
  20350. me.suspendLayout = true;
  20351. for (i = 0, ln = items.length; i < ln; i++) {
  20352. item = items[i];
  20353. //<debug>
  20354. if (!item) {
  20355. Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
  20356. }
  20357. //</debug>
  20358. if (index != -1) {
  20359. item = me.add(index + i, item);
  20360. } else {
  20361. item = me.add(item);
  20362. }
  20363. results.push(item);
  20364. }
  20365. // Resume Layouts now that all items have been added and do a single layout for all the items just added
  20366. me.suspendLayout = false;
  20367. me.doLayout();
  20368. return results;
  20369. }
  20370. cmp = me.prepareItems(args[0], true)[0];
  20371. // Floating Components are not added into the items collection
  20372. // But they do get an upward ownerCt link so that they can traverse
  20373. // up to their z-index parent.
  20374. if (cmp.floating) {
  20375. cmp.onAdded(me, index);
  20376. } else {
  20377. index = (index !== -1) ? index : me.items.length;
  20378. if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
  20379. me.items.insert(index, cmp);
  20380. cmp.onAdded(me, index);
  20381. me.onAdd(cmp, index);
  20382. me.fireEvent('add', me, cmp, index);
  20383. }
  20384. me.doLayout();
  20385. }
  20386. return cmp;
  20387. },
  20388. onAdd : Ext.emptyFn,
  20389. onRemove : Ext.emptyFn,
  20390. /**
  20391. * Inserts a Component into this Container at a specified index. Fires the
  20392. * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
  20393. * Component has been inserted.
  20394. * @param {Number} index The index at which the Component will be inserted
  20395. * into the Container's items collection
  20396. * @param {Ext.Component} component The child Component to insert.<br><br>
  20397. * Ext uses lazy rendering, and will only render the inserted Component should
  20398. * it become necessary.<br><br>
  20399. * A Component config object may be passed in order to avoid the overhead of
  20400. * constructing a real Component object if lazy rendering might mean that the
  20401. * inserted Component will not be rendered immediately. To take advantage of
  20402. * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
  20403. * property to the registered type of the Component wanted.<br><br>
  20404. * For a list of all available xtypes, see {@link Ext.Component}.
  20405. * @return {Ext.Component} component The Component (or config object) that was
  20406. * inserted with the Container's default config values applied.
  20407. */
  20408. insert : function(index, comp) {
  20409. return this.add(index, comp);
  20410. },
  20411. /**
  20412. * Moves a Component within the Container
  20413. * @param {Number} fromIdx The index the Component you wish to move is currently at.
  20414. * @param {Number} toIdx The new index for the Component.
  20415. * @return {Ext.Component} component The Component (or config object) that was moved.
  20416. */
  20417. move : function(fromIdx, toIdx) {
  20418. var items = this.items,
  20419. item;
  20420. item = items.removeAt(fromIdx);
  20421. if (item === false) {
  20422. return false;
  20423. }
  20424. items.insert(toIdx, item);
  20425. this.doLayout();
  20426. return item;
  20427. },
  20428. // @private
  20429. onBeforeAdd : function(item) {
  20430. var me = this;
  20431. if (item.ownerCt) {
  20432. item.ownerCt.remove(item, false);
  20433. }
  20434. if (me.border === false || me.border === 0) {
  20435. item.border = (item.border === true);
  20436. }
  20437. },
  20438. /**
  20439. * Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires
  20440. * the {@link #remove} event after the component has been removed.
  20441. * @param {Ext.Component/String} component The component reference or id to remove.
  20442. * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
  20443. * Defaults to the value of this Container's {@link #autoDestroy} config.
  20444. * @return {Ext.Component} component The Component that was removed.
  20445. */
  20446. remove : function(comp, autoDestroy) {
  20447. var me = this,
  20448. c = me.getComponent(comp);
  20449. //<debug>
  20450. if (Ext.isDefined(Ext.global.console) && !c) {
  20451. console.warn("Attempted to remove a component that does not exist. Ext.container.Container: remove takes an argument of the component to remove. cmp.remove() is incorrect usage.");
  20452. }
  20453. //</debug>
  20454. if (c && me.fireEvent('beforeremove', me, c) !== false) {
  20455. me.doRemove(c, autoDestroy);
  20456. me.fireEvent('remove', me, c);
  20457. }
  20458. return c;
  20459. },
  20460. // @private
  20461. doRemove : function(component, autoDestroy) {
  20462. var me = this,
  20463. layout = me.layout,
  20464. hasLayout = layout && me.rendered;
  20465. me.items.remove(component);
  20466. component.onRemoved();
  20467. if (hasLayout) {
  20468. layout.onRemove(component);
  20469. }
  20470. me.onRemove(component, autoDestroy);
  20471. if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
  20472. component.destroy();
  20473. }
  20474. if (hasLayout && !autoDestroy) {
  20475. layout.afterRemove(component);
  20476. }
  20477. if (!me.destroying) {
  20478. me.doLayout();
  20479. }
  20480. },
  20481. /**
  20482. * Removes all components from this container.
  20483. * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
  20484. * Defaults to the value of this Container's {@link #autoDestroy} config.
  20485. * @return {Ext.Component[]} Array of the destroyed components
  20486. */
  20487. removeAll : function(autoDestroy) {
  20488. var me = this,
  20489. removeItems = me.items.items.slice(),
  20490. items = [],
  20491. i = 0,
  20492. len = removeItems.length,
  20493. item;
  20494. // Suspend Layouts while we remove multiple items from the container
  20495. me.suspendLayout = true;
  20496. for (; i < len; i++) {
  20497. item = removeItems[i];
  20498. me.remove(item, autoDestroy);
  20499. if (item.ownerCt !== me) {
  20500. items.push(item);
  20501. }
  20502. }
  20503. // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
  20504. me.suspendLayout = false;
  20505. if (len) {
  20506. me.doLayout();
  20507. }
  20508. return items;
  20509. },
  20510. // Used by ComponentQuery to retrieve all of the items
  20511. // which can potentially be considered a child of this Container.
  20512. // This should be overriden by components which have child items
  20513. // that are not contained in items. For example dockedItems, menu, etc
  20514. // IMPORTANT note for maintainers:
  20515. // Items are returned in tree traversal order. Each item is appended to the result array
  20516. // followed by the results of that child's getRefItems call.
  20517. // Floating child items are appended after internal child items.
  20518. getRefItems : function(deep) {
  20519. var me = this,
  20520. items = me.items.items,
  20521. len = items.length,
  20522. i = 0,
  20523. item,
  20524. result = [];
  20525. for (; i < len; i++) {
  20526. item = items[i];
  20527. result.push(item);
  20528. if (deep && item.getRefItems) {
  20529. result.push.apply(result, item.getRefItems(true));
  20530. }
  20531. }
  20532. // Append floating items to the list.
  20533. // These will only be present after they are rendered.
  20534. if (me.floatingItems && me.floatingItems.accessList) {
  20535. result.push.apply(result, me.floatingItems.accessList);
  20536. }
  20537. return result;
  20538. },
  20539. /**
  20540. * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
  20541. * each component. The scope (<code>this</code> reference) of the
  20542. * function call will be the scope provided or the current component. The arguments to the function
  20543. * will be the args provided or the current component. If the function returns false at any point,
  20544. * the cascade is stopped on that branch.
  20545. * @param {Function} fn The function to call
  20546. * @param {Object} [scope] The scope of the function (defaults to current component)
  20547. * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
  20548. * @return {Ext.Container} this
  20549. */
  20550. cascade : function(fn, scope, origArgs){
  20551. var me = this,
  20552. cs = me.items ? me.items.items : [],
  20553. len = cs.length,
  20554. i = 0,
  20555. c,
  20556. args = origArgs ? origArgs.concat(me) : [me],
  20557. componentIndex = args.length - 1;
  20558. if (fn.apply(scope || me, args) !== false) {
  20559. for(; i < len; i++){
  20560. c = cs[i];
  20561. if (c.cascade) {
  20562. c.cascade(fn, scope, origArgs);
  20563. } else {
  20564. args[componentIndex] = c;
  20565. fn.apply(scope || cs, args);
  20566. }
  20567. }
  20568. }
  20569. return this;
  20570. },
  20571. /**
  20572. * Examines this container's <code>{@link #items}</code> <b>property</b>
  20573. * and gets a direct child component of this container.
  20574. * @param {String/Number} comp This parameter may be any of the following:
  20575. * <div><ul class="mdetail-params">
  20576. * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
  20577. * or <code>{@link Ext.Component#id id}</code> of the child component </li>
  20578. * <li>a <b><code>Number</code></b> : representing the position of the child component
  20579. * within the <code>{@link #items}</code> <b>property</b></li>
  20580. * </ul></div>
  20581. * <p>For additional information see {@link Ext.util.MixedCollection#get}.
  20582. * @return Ext.Component The component (if found).
  20583. */
  20584. getComponent : function(comp) {
  20585. if (Ext.isObject(comp)) {
  20586. comp = comp.getItemId();
  20587. }
  20588. return this.items.get(comp);
  20589. },
  20590. /**
  20591. * Retrieves all descendant components which match the passed selector.
  20592. * Executes an Ext.ComponentQuery.query using this container as its root.
  20593. * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
  20594. * If no selector is specified all items will be returned.
  20595. * @return {Ext.Component[]} Components which matched the selector
  20596. */
  20597. query : function(selector) {
  20598. selector = selector || '*';
  20599. return Ext.ComponentQuery.query(selector, this);
  20600. },
  20601. /**
  20602. * Retrieves the first direct child of this container which matches the passed selector.
  20603. * The passed in selector must comply with an Ext.ComponentQuery selector.
  20604. * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
  20605. * specified, the first child will be returned.
  20606. * @return Ext.Component
  20607. */
  20608. child : function(selector) {
  20609. selector = selector || '';
  20610. return this.query('> ' + selector)[0] || null;
  20611. },
  20612. /**
  20613. * Retrieves the first descendant of this container which matches the passed selector.
  20614. * The passed in selector must comply with an Ext.ComponentQuery selector.
  20615. * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
  20616. * specified, the first child will be returned.
  20617. * @return Ext.Component
  20618. */
  20619. down : function(selector) {
  20620. return this.query(selector)[0] || null;
  20621. },
  20622. // inherit docs
  20623. show : function() {
  20624. this.callParent(arguments);
  20625. this.performDeferredLayouts();
  20626. return this;
  20627. },
  20628. // Lay out any descendant containers who queued a layout operation during the time this was hidden
  20629. // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
  20630. performDeferredLayouts: function() {
  20631. var layoutCollection = this.layoutOnShow,
  20632. ln = layoutCollection.getCount(),
  20633. i = 0,
  20634. needsLayout,
  20635. item;
  20636. for (; i < ln; i++) {
  20637. item = layoutCollection.get(i);
  20638. needsLayout = item.needsLayout;
  20639. if (Ext.isObject(needsLayout)) {
  20640. item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
  20641. }
  20642. }
  20643. layoutCollection.clear();
  20644. },
  20645. //@private
  20646. // Enable all immediate children that was previously disabled
  20647. onEnable: function() {
  20648. Ext.Array.each(this.query('[isFormField]'), function(item) {
  20649. if (item.resetDisable) {
  20650. item.enable();
  20651. delete item.resetDisable;
  20652. }
  20653. });
  20654. this.callParent();
  20655. },
  20656. // @private
  20657. // Disable all immediate children that was previously disabled
  20658. onDisable: function() {
  20659. Ext.Array.each(this.query('[isFormField]'), function(item) {
  20660. if (item.resetDisable !== false && !item.disabled) {
  20661. item.disable();
  20662. item.resetDisable = true;
  20663. }
  20664. });
  20665. this.callParent();
  20666. },
  20667. /**
  20668. * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
  20669. * from being executed.
  20670. */
  20671. beforeLayout: function() {
  20672. return true;
  20673. },
  20674. // @private
  20675. beforeDestroy : function() {
  20676. var me = this,
  20677. items = me.items,
  20678. c;
  20679. if (items) {
  20680. while ((c = items.first())) {
  20681. me.doRemove(c, true);
  20682. }
  20683. }
  20684. Ext.destroy(
  20685. me.layout
  20686. );
  20687. me.callParent();
  20688. }
  20689. });
  20690. /**
  20691. * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
  20692. * containing items, namely adding, inserting and removing items.
  20693. *
  20694. * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
  20695. * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
  20696. * lightweight Container to be encapsulated by an HTML element to your specifications by using the
  20697. * {@link Ext.Component#autoEl autoEl} config option.
  20698. *
  20699. * The code below illustrates how to explicitly create a Container:
  20700. *
  20701. * @example
  20702. * // Explicitly create a Container
  20703. * Ext.create('Ext.container.Container', {
  20704. * layout: {
  20705. * type: 'hbox'
  20706. * },
  20707. * width: 400,
  20708. * renderTo: Ext.getBody(),
  20709. * border: 1,
  20710. * style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
  20711. * defaults: {
  20712. * labelWidth: 80,
  20713. * // implicitly create Container by specifying xtype
  20714. * xtype: 'datefield',
  20715. * flex: 1,
  20716. * style: {
  20717. * padding: '10px'
  20718. * }
  20719. * },
  20720. * items: [{
  20721. * xtype: 'datefield',
  20722. * name: 'startDate',
  20723. * fieldLabel: 'Start date'
  20724. * },{
  20725. * xtype: 'datefield',
  20726. * name: 'endDate',
  20727. * fieldLabel: 'End date'
  20728. * }]
  20729. * });
  20730. *
  20731. * ## Layout
  20732. *
  20733. * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
  20734. * the Container using the `{@link #layout}` configuration property.
  20735. *
  20736. * When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
  20737. * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
  20738. * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
  20739. * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
  20740. * inside the Container, and **does not apply any sizing** at all.
  20741. *
  20742. * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
  20743. * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
  20744. * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
  20745. * any way when the Container is resized.
  20746. *
  20747. * Certain layout managers allow dynamic addition of child components. Those that do include
  20748. * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
  20749. * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
  20750. *
  20751. * // Create the GridPanel.
  20752. * var myNewGrid = new Ext.grid.Panel({
  20753. * store: myStore,
  20754. * headers: myHeaders,
  20755. * title: 'Results', // the title becomes the title of the tab
  20756. * });
  20757. *
  20758. * myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
  20759. * myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
  20760. *
  20761. * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
  20762. * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
  20763. * Ext.layout.container.Fit fit} exactly into its client area.
  20764. *
  20765. * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
  20766. * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
  20767. * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
  20768. * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
  20769. * GridPanel will not be sized as expected.
  20770. *
  20771. * ## Adding via remote configuration
  20772. *
  20773. * A server side script can be used to add Components which are generated dynamically on the server. An example of
  20774. * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
  20775. *
  20776. * // execute an Ajax request to invoke server side script:
  20777. * Ext.Ajax.request({
  20778. * url: 'gen-invoice-grid.php',
  20779. * // send additional parameters to instruct server script
  20780. * params: {
  20781. * startDate: Ext.getCmp('start-date').getValue(),
  20782. * endDate: Ext.getCmp('end-date').getValue()
  20783. * },
  20784. * // process the response object to add it to the TabPanel:
  20785. * success: function(xhr) {
  20786. * var newComponent = eval(xhr.responseText); // see discussion below
  20787. * myTabPanel.add(newComponent); // add the component to the TabPanel
  20788. * myTabPanel.setActiveTab(newComponent);
  20789. * },
  20790. * failure: function() {
  20791. * Ext.Msg.alert("Grid create failed", "Server communication failure");
  20792. * }
  20793. * });
  20794. *
  20795. * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
  20796. * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
  20797. *
  20798. * {
  20799. * "xtype": 'grid',
  20800. * "title": 'Invoice Report',
  20801. * "store": {
  20802. * "model": 'Invoice',
  20803. * "proxy": {
  20804. * "type": 'ajax',
  20805. * "url": 'get-invoice-data.php',
  20806. * "reader": {
  20807. * "type": 'json'
  20808. * "record": 'transaction',
  20809. * "idProperty": 'id',
  20810. * "totalRecords": 'total'
  20811. * })
  20812. * },
  20813. * "autoLoad": {
  20814. * "params": {
  20815. * "startDate": '01/01/2008',
  20816. * "endDate": '01/31/2008'
  20817. * }
  20818. * }
  20819. * },
  20820. * "headers": [
  20821. * {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
  20822. * {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
  20823. * {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
  20824. * {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
  20825. * ]
  20826. * }
  20827. *
  20828. * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
  20829. * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
  20830. * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
  20831. *
  20832. * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
  20833. * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
  20834. * into the code since these are all known on the server.
  20835. */
  20836. Ext.define('Ext.container.Container', {
  20837. extend: 'Ext.container.AbstractContainer',
  20838. alias: 'widget.container',
  20839. alternateClassName: 'Ext.Container',
  20840. /**
  20841. * Return the immediate child Component in which the passed element is located.
  20842. * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
  20843. * @return {Ext.Component} The child item which contains the passed element.
  20844. */
  20845. getChildByElement: function(el) {
  20846. var item,
  20847. itemEl,
  20848. i = 0,
  20849. it = this.items.items,
  20850. ln = it.length;
  20851. el = Ext.getDom(el);
  20852. for (; i < ln; i++) {
  20853. item = it[i];
  20854. itemEl = item.getEl();
  20855. if ((itemEl.dom === el) || itemEl.contains(el)) {
  20856. return item;
  20857. }
  20858. }
  20859. return null;
  20860. }
  20861. });
  20862. /**
  20863. * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
  20864. * the right-justified button container.
  20865. *
  20866. * @example
  20867. * Ext.create('Ext.panel.Panel', {
  20868. * title: 'Toolbar Fill Example',
  20869. * width: 300,
  20870. * height: 200,
  20871. * tbar : [
  20872. * 'Item 1',
  20873. * { xtype: 'tbfill' },
  20874. * 'Item 2'
  20875. * ],
  20876. * renderTo: Ext.getBody()
  20877. * });
  20878. */
  20879. Ext.define('Ext.toolbar.Fill', {
  20880. extend: 'Ext.Component',
  20881. alias: 'widget.tbfill',
  20882. alternateClassName: 'Ext.Toolbar.Fill',
  20883. isFill : true,
  20884. flex: 1
  20885. });
  20886. /**
  20887. * @class Ext.toolbar.Item
  20888. * @extends Ext.Component
  20889. * The base class that other non-interacting Toolbar Item classes should extend in order to
  20890. * get some basic common toolbar item functionality.
  20891. */
  20892. Ext.define('Ext.toolbar.Item', {
  20893. extend: 'Ext.Component',
  20894. alias: 'widget.tbitem',
  20895. alternateClassName: 'Ext.Toolbar.Item',
  20896. enable:Ext.emptyFn,
  20897. disable:Ext.emptyFn,
  20898. focus:Ext.emptyFn
  20899. /**
  20900. * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
  20901. */
  20902. });
  20903. /**
  20904. * @class Ext.toolbar.Separator
  20905. * @extends Ext.toolbar.Item
  20906. * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
  20907. *
  20908. * @example
  20909. * Ext.create('Ext.panel.Panel', {
  20910. * title: 'Toolbar Seperator Example',
  20911. * width: 300,
  20912. * height: 200,
  20913. * tbar : [
  20914. * 'Item 1',
  20915. * { xtype: 'tbseparator' },
  20916. * 'Item 2'
  20917. * ],
  20918. * renderTo: Ext.getBody()
  20919. * });
  20920. */
  20921. Ext.define('Ext.toolbar.Separator', {
  20922. extend: 'Ext.toolbar.Item',
  20923. alias: 'widget.tbseparator',
  20924. alternateClassName: 'Ext.Toolbar.Separator',
  20925. baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
  20926. focusable: false
  20927. });
  20928. /**
  20929. * @class Ext.menu.Manager
  20930. * Provides a common registry of all menus on a page.
  20931. * @singleton
  20932. */
  20933. Ext.define('Ext.menu.Manager', {
  20934. singleton: true,
  20935. requires: [
  20936. 'Ext.util.MixedCollection',
  20937. 'Ext.util.KeyMap'
  20938. ],
  20939. alternateClassName: 'Ext.menu.MenuMgr',
  20940. uses: ['Ext.menu.Menu'],
  20941. menus: {},
  20942. groups: {},
  20943. attached: false,
  20944. lastShow: new Date(),
  20945. init: function() {
  20946. var me = this;
  20947. me.active = Ext.create('Ext.util.MixedCollection');
  20948. Ext.getDoc().addKeyListener(27, function() {
  20949. if (me.active.length > 0) {
  20950. me.hideAll();
  20951. }
  20952. }, me);
  20953. },
  20954. /**
  20955. * Hides all menus that are currently visible
  20956. * @return {Boolean} success True if any active menus were hidden.
  20957. */
  20958. hideAll: function() {
  20959. var active = this.active,
  20960. c;
  20961. if (active && active.length > 0) {
  20962. c = active.clone();
  20963. c.each(function(m) {
  20964. m.hide();
  20965. });
  20966. return true;
  20967. }
  20968. return false;
  20969. },
  20970. onHide: function(m) {
  20971. var me = this,
  20972. active = me.active;
  20973. active.remove(m);
  20974. if (active.length < 1) {
  20975. Ext.getDoc().un('mousedown', me.onMouseDown, me);
  20976. me.attached = false;
  20977. }
  20978. },
  20979. onShow: function(m) {
  20980. var me = this,
  20981. active = me.active,
  20982. last = active.last(),
  20983. attached = me.attached,
  20984. menuEl = m.getEl(),
  20985. zIndex;
  20986. me.lastShow = new Date();
  20987. active.add(m);
  20988. if (!attached) {
  20989. Ext.getDoc().on('mousedown', me.onMouseDown, me);
  20990. me.attached = true;
  20991. }
  20992. m.toFront();
  20993. },
  20994. onBeforeHide: function(m) {
  20995. if (m.activeChild) {
  20996. m.activeChild.hide();
  20997. }
  20998. if (m.autoHideTimer) {
  20999. clearTimeout(m.autoHideTimer);
  21000. delete m.autoHideTimer;
  21001. }
  21002. },
  21003. onBeforeShow: function(m) {
  21004. var active = this.active,
  21005. parentMenu = m.parentMenu;
  21006. active.remove(m);
  21007. if (!parentMenu && !m.allowOtherMenus) {
  21008. this.hideAll();
  21009. }
  21010. else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
  21011. parentMenu.activeChild.hide();
  21012. }
  21013. },
  21014. // private
  21015. onMouseDown: function(e) {
  21016. var me = this,
  21017. active = me.active,
  21018. lastShow = me.lastShow,
  21019. target = e.target;
  21020. if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
  21021. me.hideAll();
  21022. // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
  21023. // fired on the element, so we'll focus it here
  21024. if (Ext.isIE && Ext.fly(target).focusable()) {
  21025. target.focus();
  21026. }
  21027. }
  21028. },
  21029. // private
  21030. register: function(menu) {
  21031. var me = this;
  21032. if (!me.active) {
  21033. me.init();
  21034. }
  21035. if (menu.floating) {
  21036. me.menus[menu.id] = menu;
  21037. menu.on({
  21038. beforehide: me.onBeforeHide,
  21039. hide: me.onHide,
  21040. beforeshow: me.onBeforeShow,
  21041. show: me.onShow,
  21042. scope: me
  21043. });
  21044. }
  21045. },
  21046. /**
  21047. * Returns a {@link Ext.menu.Menu} object
  21048. * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
  21049. * be used to generate and return a new Menu this.
  21050. * @return {Ext.menu.Menu} The specified menu, or null if none are found
  21051. */
  21052. get: function(menu) {
  21053. var menus = this.menus;
  21054. if (typeof menu == 'string') { // menu id
  21055. if (!menus) { // not initialized, no menus to return
  21056. return null;
  21057. }
  21058. return menus[menu];
  21059. } else if (menu.isMenu) { // menu instance
  21060. return menu;
  21061. } else if (Ext.isArray(menu)) { // array of menu items
  21062. return Ext.create('Ext.menu.Menu', {items:menu});
  21063. } else { // otherwise, must be a config
  21064. return Ext.ComponentManager.create(menu, 'menu');
  21065. }
  21066. },
  21067. // private
  21068. unregister: function(menu) {
  21069. var me = this,
  21070. menus = me.menus,
  21071. active = me.active;
  21072. delete menus[menu.id];
  21073. active.remove(menu);
  21074. menu.un({
  21075. beforehide: me.onBeforeHide,
  21076. hide: me.onHide,
  21077. beforeshow: me.onBeforeShow,
  21078. show: me.onShow,
  21079. scope: me
  21080. });
  21081. },
  21082. // private
  21083. registerCheckable: function(menuItem) {
  21084. var groups = this.groups,
  21085. groupId = menuItem.group;
  21086. if (groupId) {
  21087. if (!groups[groupId]) {
  21088. groups[groupId] = [];
  21089. }
  21090. groups[groupId].push(menuItem);
  21091. }
  21092. },
  21093. // private
  21094. unregisterCheckable: function(menuItem) {
  21095. var groups = this.groups,
  21096. groupId = menuItem.group;
  21097. if (groupId) {
  21098. Ext.Array.remove(groups[groupId], menuItem);
  21099. }
  21100. },
  21101. onCheckChange: function(menuItem, state) {
  21102. var groups = this.groups,
  21103. groupId = menuItem.group,
  21104. i = 0,
  21105. group, ln, curr;
  21106. if (groupId && state) {
  21107. group = groups[groupId];
  21108. ln = group.length;
  21109. for (; i < ln; i++) {
  21110. curr = group[i];
  21111. if (curr != menuItem) {
  21112. curr.setChecked(false);
  21113. }
  21114. }
  21115. }
  21116. }
  21117. });
  21118. /**
  21119. * Component layout for buttons
  21120. * @class Ext.layout.component.Button
  21121. * @extends Ext.layout.component.Component
  21122. * @private
  21123. */
  21124. Ext.define('Ext.layout.component.Button', {
  21125. /* Begin Definitions */
  21126. alias: ['layout.button'],
  21127. extend: 'Ext.layout.component.Component',
  21128. /* End Definitions */
  21129. type: 'button',
  21130. cellClsRE: /-btn-(tl|br)\b/,
  21131. htmlRE: /<.*>/,
  21132. beforeLayout: function() {
  21133. return this.callParent(arguments) || this.lastText !== this.owner.text;
  21134. },
  21135. /**
  21136. * Set the dimensions of the inner &lt;button&gt; element to match the
  21137. * component dimensions.
  21138. */
  21139. onLayout: function(width, height) {
  21140. var me = this,
  21141. isNum = Ext.isNumber,
  21142. owner = me.owner,
  21143. ownerEl = owner.el,
  21144. btnEl = owner.btnEl,
  21145. btnInnerEl = owner.btnInnerEl,
  21146. btnIconEl = owner.btnIconEl,
  21147. sizeIconEl = (owner.icon || owner.iconCls) && (owner.iconAlign == "top" || owner.iconAlign == "bottom"),
  21148. minWidth = owner.minWidth,
  21149. maxWidth = owner.maxWidth,
  21150. ownerWidth, btnFrameWidth, metrics;
  21151. me.getTargetInfo();
  21152. me.callParent(arguments);
  21153. btnInnerEl.unclip();
  21154. me.setTargetSize(width, height);
  21155. if (!isNum(width)) {
  21156. // In IE7 strict mode button elements with width:auto get strange extra side margins within
  21157. // the wrapping table cell, but they go away if the width is explicitly set. So we measure
  21158. // the size of the text and set the width to match.
  21159. if (owner.text && (Ext.isIE6 || Ext.isIE7) && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
  21160. btnFrameWidth = me.btnFrameWidth;
  21161. metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
  21162. ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
  21163. btnEl.setWidth(metrics.width + btnFrameWidth);
  21164. btnInnerEl.setWidth(metrics.width + btnFrameWidth);
  21165. if (sizeIconEl) {
  21166. btnIconEl.setWidth(metrics.width + btnFrameWidth);
  21167. }
  21168. } else {
  21169. // Remove any previous fixed widths
  21170. ownerEl.setWidth(null);
  21171. btnEl.setWidth(null);
  21172. btnInnerEl.setWidth(null);
  21173. btnIconEl.setWidth(null);
  21174. }
  21175. // Handle maxWidth/minWidth config
  21176. if (minWidth || maxWidth) {
  21177. ownerWidth = ownerEl.getWidth();
  21178. if (minWidth && (ownerWidth < minWidth)) {
  21179. me.setTargetSize(minWidth, height);
  21180. }
  21181. else if (maxWidth && (ownerWidth > maxWidth)) {
  21182. btnInnerEl.clip();
  21183. me.setTargetSize(maxWidth, height);
  21184. }
  21185. }
  21186. }
  21187. this.lastText = owner.text;
  21188. },
  21189. setTargetSize: function(width, height) {
  21190. var me = this,
  21191. owner = me.owner,
  21192. isNum = Ext.isNumber,
  21193. btnInnerEl = owner.btnInnerEl,
  21194. btnWidth = (isNum(width) ? width - me.adjWidth : width),
  21195. btnHeight = (isNum(height) ? height - me.adjHeight : height),
  21196. btnFrameHeight = me.btnFrameHeight,
  21197. text = owner.getText(),
  21198. textHeight;
  21199. me.callParent(arguments);
  21200. me.setElementSize(owner.btnEl, btnWidth, btnHeight);
  21201. me.setElementSize(btnInnerEl, btnWidth, btnHeight);
  21202. if (btnHeight >= 0) {
  21203. btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
  21204. }
  21205. // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
  21206. // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
  21207. // line-height to normal, measure the rendered text height, and add padding-top to center the text block
  21208. // vertically within the button's height. This is more expensive than the basic line-height approach so
  21209. // we only do it if the text contains markup.
  21210. if (text && this.htmlRE.test(text)) {
  21211. btnInnerEl.setStyle('line-height', 'normal');
  21212. textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
  21213. btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
  21214. me.setElementSize(btnInnerEl, btnWidth, btnHeight);
  21215. }
  21216. },
  21217. getTargetInfo: function() {
  21218. var me = this,
  21219. owner = me.owner,
  21220. ownerEl = owner.el,
  21221. frameSize = me.frameSize,
  21222. frameBody = owner.frameBody,
  21223. btnWrap = owner.btnWrap,
  21224. innerEl = owner.btnInnerEl;
  21225. if (!('adjWidth' in me)) {
  21226. Ext.apply(me, {
  21227. // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
  21228. adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
  21229. btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
  21230. adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
  21231. btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
  21232. btnFrameWidth: innerEl.getFrameWidth('lr'),
  21233. btnFrameHeight: innerEl.getFrameWidth('tb'),
  21234. btnFrameTop: innerEl.getFrameWidth('t')
  21235. });
  21236. }
  21237. return me.callParent();
  21238. }
  21239. });
  21240. /**
  21241. * @docauthor Robert Dougan <rob@sencha.com>
  21242. *
  21243. * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
  21244. * {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
  21245. * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
  21246. * a user clicks the button, or use {@link #listeners listeners} for other events such as
  21247. * {@link #mouseover mouseover}. Example usage:
  21248. *
  21249. * @example
  21250. * Ext.create('Ext.Button', {
  21251. * text: 'Click me',
  21252. * renderTo: Ext.getBody(),
  21253. * handler: function() {
  21254. * alert('You clicked the button!')
  21255. * }
  21256. * });
  21257. *
  21258. * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
  21259. * method. Example usage:
  21260. *
  21261. * @example
  21262. * Ext.create('Ext.Button', {
  21263. * text : 'Dynamic Handler Button',
  21264. * renderTo: Ext.getBody(),
  21265. * handler : function() {
  21266. * // this button will spit out a different number every time you click it.
  21267. * // so firstly we must check if that number is already set:
  21268. * if (this.clickCount) {
  21269. * // looks like the property is already set, so lets just add 1 to that number and alert the user
  21270. * this.clickCount++;
  21271. * alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
  21272. * } else {
  21273. * // if the clickCount property is not set, we will set it and alert the user
  21274. * this.clickCount = 1;
  21275. * alert('You just clicked the button for the first time!\n\nTry pressing it again..');
  21276. * }
  21277. * }
  21278. * });
  21279. *
  21280. * A button within a container:
  21281. *
  21282. * @example
  21283. * Ext.create('Ext.Container', {
  21284. * renderTo: Ext.getBody(),
  21285. * items : [
  21286. * {
  21287. * xtype: 'button',
  21288. * text : 'My Button'
  21289. * }
  21290. * ]
  21291. * });
  21292. *
  21293. * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
  21294. *
  21295. * - `'small'`
  21296. * - `'medium'`
  21297. * - `'large'`
  21298. *
  21299. * Example usage:
  21300. *
  21301. * @example
  21302. * Ext.create('Ext.Button', {
  21303. * renderTo: document.body,
  21304. * text : 'Click me',
  21305. * scale : 'large'
  21306. * });
  21307. *
  21308. * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
  21309. * Example usage:
  21310. *
  21311. * @example
  21312. * Ext.create('Ext.Button', {
  21313. * renderTo: Ext.getBody(),
  21314. * text: 'Click Me',
  21315. * enableToggle: true
  21316. * });
  21317. *
  21318. * You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration
  21319. * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
  21320. * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
  21321. * added to the button. You can change the alignment of the arrow using the {@link #arrowAlign} configuration
  21322. * on button. Example usage:
  21323. *
  21324. * @example
  21325. * Ext.create('Ext.Button', {
  21326. * text : 'Menu button',
  21327. * renderTo : Ext.getBody(),
  21328. * arrowAlign: 'bottom',
  21329. * menu : [
  21330. * {text: 'Item 1'},
  21331. * {text: 'Item 2'},
  21332. * {text: 'Item 3'},
  21333. * {text: 'Item 4'}
  21334. * ]
  21335. * });
  21336. *
  21337. * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
  21338. * configuration or using the {@link #addListener} method. Button has a variety of different listeners:
  21339. *
  21340. * - `click`
  21341. * - `toggle`
  21342. * - `mouseover`
  21343. * - `mouseout`
  21344. * - `mouseshow`
  21345. * - `menuhide`
  21346. * - `menutriggerover`
  21347. * - `menutriggerout`
  21348. *
  21349. * Example usage:
  21350. *
  21351. * @example
  21352. * Ext.create('Ext.Button', {
  21353. * text : 'Button',
  21354. * renderTo : Ext.getBody(),
  21355. * listeners: {
  21356. * click: function() {
  21357. * // this == the button, as we are in the local scope
  21358. * this.setText('I was clicked!');
  21359. * },
  21360. * mouseover: function() {
  21361. * // set a new config which says we moused over, if not already set
  21362. * if (!this.mousedOver) {
  21363. * this.mousedOver = true;
  21364. * alert('You moused over a button!\n\nI wont do this again.');
  21365. * }
  21366. * }
  21367. * }
  21368. * });
  21369. */
  21370. Ext.define('Ext.button.Button', {
  21371. /* Begin Definitions */
  21372. alias: 'widget.button',
  21373. extend: 'Ext.Component',
  21374. requires: [
  21375. 'Ext.menu.Manager',
  21376. 'Ext.util.ClickRepeater',
  21377. 'Ext.layout.component.Button',
  21378. 'Ext.util.TextMetrics',
  21379. 'Ext.util.KeyMap'
  21380. ],
  21381. alternateClassName: 'Ext.Button',
  21382. /* End Definitions */
  21383. isButton: true,
  21384. componentLayout: 'button',
  21385. /**
  21386. * @property {Boolean} hidden
  21387. * True if this button is hidden. Read-only.
  21388. */
  21389. hidden: false,
  21390. /**
  21391. * @property {Boolean} disabled
  21392. * True if this button is disabled. Read-only.
  21393. */
  21394. disabled: false,
  21395. /**
  21396. * @property {Boolean} pressed
  21397. * True if this button is pressed (only if enableToggle = true). Read-only.
  21398. */
  21399. pressed: false,
  21400. /**
  21401. * @cfg {String} text
  21402. * The button text to be used as innerHTML (html tags are accepted).
  21403. */
  21404. /**
  21405. * @cfg {String} icon
  21406. * The path to an image to display in the button (the image will be set as the background-image CSS property of the
  21407. * button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
  21408. */
  21409. /**
  21410. * @cfg {Function} handler
  21411. * A function called when the button is clicked (can be used instead of click event).
  21412. * @cfg {Ext.button.Button} handler.button This button.
  21413. * @cfg {Ext.EventObject} handler.e The click event.
  21414. */
  21415. /**
  21416. * @cfg {Number} minWidth
  21417. * The minimum width for this button (used to give a set of buttons a common width).
  21418. * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
  21419. */
  21420. /**
  21421. * @cfg {String/Object} tooltip
  21422. * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
  21423. * QuickTips config object.
  21424. */
  21425. /**
  21426. * @cfg {Boolean} [hidden=false]
  21427. * True to start hidden.
  21428. */
  21429. /**
  21430. * @cfg {Boolean} [disabled=true]
  21431. * True to start disabled.
  21432. */
  21433. /**
  21434. * @cfg {Boolean} [pressed=false]
  21435. * True to start pressed (only if enableToggle = true)
  21436. */
  21437. /**
  21438. * @cfg {String} toggleGroup
  21439. * The group this toggle button is a member of (only 1 per group can be pressed)
  21440. */
  21441. /**
  21442. * @cfg {Boolean/Object} [repeat=false]
  21443. * True to repeat fire the click event while the mouse is down. This can also be a
  21444. * {@link Ext.util.ClickRepeater ClickRepeater} config object.
  21445. */
  21446. /**
  21447. * @cfg {Number} tabIndex
  21448. * Set a DOM tabIndex for this button.
  21449. */
  21450. /**
  21451. * @cfg {Boolean} [allowDepress=true]
  21452. * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
  21453. */
  21454. /**
  21455. * @cfg {Boolean} [enableToggle=false]
  21456. * True to enable pressed/not pressed toggling.
  21457. */
  21458. enableToggle: false,
  21459. /**
  21460. * @cfg {Function} toggleHandler
  21461. * Function called when a Button with {@link #enableToggle} set to true is clicked.
  21462. * @cfg {Ext.button.Button} toggleHandler.button This button.
  21463. * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
  21464. */
  21465. /**
  21466. * @cfg {Ext.menu.Menu/String/Object} menu
  21467. * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
  21468. */
  21469. /**
  21470. * @cfg {String} menuAlign
  21471. * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
  21472. */
  21473. menuAlign: 'tl-bl?',
  21474. /**
  21475. * @cfg {String} textAlign
  21476. * The text alignment for this button (center, left, right).
  21477. */
  21478. textAlign: 'center',
  21479. /**
  21480. * @cfg {String} overflowText
  21481. * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
  21482. * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
  21483. */
  21484. /**
  21485. * @cfg {String} iconCls
  21486. * A css class which sets a background image to be used as the icon for this button.
  21487. */
  21488. /**
  21489. * @cfg {String} type
  21490. * The type of `<input>` to create: submit, reset or button.
  21491. */
  21492. type: 'button',
  21493. /**
  21494. * @cfg {String} clickEvent
  21495. * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
  21496. */
  21497. clickEvent: 'click',
  21498. /**
  21499. * @cfg {Boolean} preventDefault
  21500. * True to prevent the default action when the {@link #clickEvent} is processed.
  21501. */
  21502. preventDefault: true,
  21503. /**
  21504. * @cfg {Boolean} handleMouseEvents
  21505. * False to disable visual cues on mouseover, mouseout and mousedown.
  21506. */
  21507. handleMouseEvents: true,
  21508. /**
  21509. * @cfg {String} tooltipType
  21510. * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
  21511. */
  21512. tooltipType: 'qtip',
  21513. /**
  21514. * @cfg {String} [baseCls='x-btn']
  21515. * The base CSS class to add to all buttons.
  21516. */
  21517. baseCls: Ext.baseCSSPrefix + 'btn',
  21518. /**
  21519. * @cfg {String} pressedCls
  21520. * The CSS class to add to a button when it is in the pressed state.
  21521. */
  21522. pressedCls: 'pressed',
  21523. /**
  21524. * @cfg {String} overCls
  21525. * The CSS class to add to a button when it is in the over (hovered) state.
  21526. */
  21527. overCls: 'over',
  21528. /**
  21529. * @cfg {String} focusCls
  21530. * The CSS class to add to a button when it is in the focussed state.
  21531. */
  21532. focusCls: 'focus',
  21533. /**
  21534. * @cfg {String} menuActiveCls
  21535. * The CSS class to add to a button when it's menu is active.
  21536. */
  21537. menuActiveCls: 'menu-active',
  21538. /**
  21539. * @cfg {String} href
  21540. * The URL to visit when the button is clicked. Specifying this config is equivalent to specifying:
  21541. *
  21542. * handler: function() { window.location = "http://www.sencha.com" }
  21543. */
  21544. /**
  21545. * @cfg {Object} baseParams
  21546. * An object literal of parameters to pass to the url when the {@link #href} property is specified.
  21547. */
  21548. /**
  21549. * @cfg {Object} params
  21550. * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
  21551. * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
  21552. */
  21553. ariaRole: 'button',
  21554. // inherited
  21555. renderTpl:
  21556. '<em id="{id}-btnWrap" class="{splitCls}">' +
  21557. '<tpl if="href">' +
  21558. '<a id="{id}-btnEl" href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
  21559. '<span id="{id}-btnInnerEl" class="{baseCls}-inner">' +
  21560. '{text}' +
  21561. '</span>' +
  21562. '<span id="{id}-btnIconEl" class="{baseCls}-icon"></span>' +
  21563. '</a>' +
  21564. '</tpl>' +
  21565. '<tpl if="!href">' +
  21566. '<button id="{id}-btnEl" type="{type}" hidefocus="true"' +
  21567. // the autocomplete="off" is required to prevent Firefox from remembering
  21568. // the button's disabled state between page reloads.
  21569. '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
  21570. '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">' +
  21571. '{text}' +
  21572. '</span>' +
  21573. '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}">&#160;</span>' +
  21574. '</button>' +
  21575. '</tpl>' +
  21576. '</em>' ,
  21577. /**
  21578. * @cfg {String} scale
  21579. * The size of the Button. Three values are allowed:
  21580. *
  21581. * - 'small' - Results in the button element being 16px high.
  21582. * - 'medium' - Results in the button element being 24px high.
  21583. * - 'large' - Results in the button element being 32px high.
  21584. */
  21585. scale: 'small',
  21586. /**
  21587. * @private
  21588. * An array of allowed scales.
  21589. */
  21590. allowedScales: ['small', 'medium', 'large'],
  21591. /**
  21592. * @cfg {Object} scope
  21593. * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
  21594. * Defaults to this Button.
  21595. */
  21596. /**
  21597. * @cfg {String} iconAlign
  21598. * The side of the Button box to render the icon. Four values are allowed:
  21599. *
  21600. * - 'top'
  21601. * - 'right'
  21602. * - 'bottom'
  21603. * - 'left'
  21604. */
  21605. iconAlign: 'left',
  21606. /**
  21607. * @cfg {String} arrowAlign
  21608. * The side of the Button box to render the arrow if the button has an associated {@link #menu}. Two
  21609. * values are allowed:
  21610. *
  21611. * - 'right'
  21612. * - 'bottom'
  21613. */
  21614. arrowAlign: 'right',
  21615. /**
  21616. * @cfg {String} arrowCls
  21617. * The className used for the inner arrow element if the button has a menu.
  21618. */
  21619. arrowCls: 'arrow',
  21620. /**
  21621. * @property {Ext.Template} template
  21622. * A {@link Ext.Template Template} used to create the Button's DOM structure.
  21623. *
  21624. * Instances, or subclasses which need a different DOM structure may provide a different template layout in
  21625. * conjunction with an implementation of {@link #getTemplateArgs}.
  21626. */
  21627. /**
  21628. * @cfg {String} cls
  21629. * A CSS class string to apply to the button's main element.
  21630. */
  21631. /**
  21632. * @property {Ext.menu.Menu} menu
  21633. * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config
  21634. * option.
  21635. */
  21636. /**
  21637. * @cfg {Boolean} autoWidth
  21638. * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content. If
  21639. * the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent the button
  21640. * from doing this automatic sizing.
  21641. */
  21642. maskOnDisable: false,
  21643. // inherit docs
  21644. initComponent: function() {
  21645. var me = this;
  21646. me.callParent(arguments);
  21647. me.addEvents(
  21648. /**
  21649. * @event click
  21650. * Fires when this button is clicked
  21651. * @param {Ext.button.Button} this
  21652. * @param {Event} e The click event
  21653. */
  21654. 'click',
  21655. /**
  21656. * @event toggle
  21657. * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
  21658. * @param {Ext.button.Button} this
  21659. * @param {Boolean} pressed
  21660. */
  21661. 'toggle',
  21662. /**
  21663. * @event mouseover
  21664. * Fires when the mouse hovers over the button
  21665. * @param {Ext.button.Button} this
  21666. * @param {Event} e The event object
  21667. */
  21668. 'mouseover',
  21669. /**
  21670. * @event mouseout
  21671. * Fires when the mouse exits the button
  21672. * @param {Ext.button.Button} this
  21673. * @param {Event} e The event object
  21674. */
  21675. 'mouseout',
  21676. /**
  21677. * @event menushow
  21678. * If this button has a menu, this event fires when it is shown
  21679. * @param {Ext.button.Button} this
  21680. * @param {Ext.menu.Menu} menu
  21681. */
  21682. 'menushow',
  21683. /**
  21684. * @event menuhide
  21685. * If this button has a menu, this event fires when it is hidden
  21686. * @param {Ext.button.Button} this
  21687. * @param {Ext.menu.Menu} menu
  21688. */
  21689. 'menuhide',
  21690. /**
  21691. * @event menutriggerover
  21692. * If this button has a menu, this event fires when the mouse enters the menu triggering element
  21693. * @param {Ext.button.Button} this
  21694. * @param {Ext.menu.Menu} menu
  21695. * @param {Event} e
  21696. */
  21697. 'menutriggerover',
  21698. /**
  21699. * @event menutriggerout
  21700. * If this button has a menu, this event fires when the mouse leaves the menu triggering element
  21701. * @param {Ext.button.Button} this
  21702. * @param {Ext.menu.Menu} menu
  21703. * @param {Event} e
  21704. */
  21705. 'menutriggerout'
  21706. );
  21707. if (me.menu) {
  21708. // Flag that we'll have a splitCls
  21709. me.split = true;
  21710. // retrieve menu by id or instantiate instance if needed
  21711. me.menu = Ext.menu.Manager.get(me.menu);
  21712. me.menu.ownerCt = me;
  21713. }
  21714. // Accept url as a synonym for href
  21715. if (me.url) {
  21716. me.href = me.url;
  21717. }
  21718. // preventDefault defaults to false for links
  21719. if (me.href && !me.hasOwnProperty('preventDefault')) {
  21720. me.preventDefault = false;
  21721. }
  21722. if (Ext.isString(me.toggleGroup)) {
  21723. me.enableToggle = true;
  21724. }
  21725. },
  21726. // private
  21727. initAria: function() {
  21728. this.callParent();
  21729. var actionEl = this.getActionEl();
  21730. if (this.menu) {
  21731. actionEl.dom.setAttribute('aria-haspopup', true);
  21732. }
  21733. },
  21734. // inherit docs
  21735. getActionEl: function() {
  21736. return this.btnEl;
  21737. },
  21738. // inherit docs
  21739. getFocusEl: function() {
  21740. return this.btnEl;
  21741. },
  21742. // private
  21743. setButtonCls: function() {
  21744. var me = this,
  21745. cls = [],
  21746. btnIconEl = me.btnIconEl,
  21747. hide = 'x-hide-display';
  21748. if (me.useSetClass) {
  21749. if (!Ext.isEmpty(me.oldCls)) {
  21750. me.removeClsWithUI(me.oldCls);
  21751. me.removeClsWithUI(me.pressedCls);
  21752. }
  21753. // Check whether the button has an icon or not, and if it has an icon, what is th alignment
  21754. if (me.iconCls || me.icon) {
  21755. if (me.text) {
  21756. cls.push('icon-text-' + me.iconAlign);
  21757. } else {
  21758. cls.push('icon');
  21759. }
  21760. if (btnIconEl) {
  21761. btnIconEl.removeCls(hide);
  21762. }
  21763. } else {
  21764. if (me.text) {
  21765. cls.push('noicon');
  21766. }
  21767. if (btnIconEl) {
  21768. btnIconEl.addCls(hide);
  21769. }
  21770. }
  21771. me.oldCls = cls;
  21772. me.addClsWithUI(cls);
  21773. me.addClsWithUI(me.pressed ? me.pressedCls : null);
  21774. }
  21775. },
  21776. // private
  21777. onRender: function(ct, position) {
  21778. // classNames for the button
  21779. var me = this,
  21780. repeater, btn;
  21781. // Apply the renderData to the template args
  21782. Ext.applyIf(me.renderData, me.getTemplateArgs());
  21783. me.addChildEls('btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl');
  21784. if (me.scale) {
  21785. me.ui = me.ui + '-' + me.scale;
  21786. }
  21787. // Render internal structure
  21788. me.callParent(arguments);
  21789. // If it is a split button + has a toolip for the arrow
  21790. if (me.split && me.arrowTooltip) {
  21791. me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
  21792. }
  21793. // Add listeners to the focus and blur events on the element
  21794. me.mon(me.btnEl, {
  21795. scope: me,
  21796. focus: me.onFocus,
  21797. blur : me.onBlur
  21798. });
  21799. // Set btn as a local variable for easy access
  21800. btn = me.el;
  21801. if (me.icon) {
  21802. me.setIcon(me.icon);
  21803. }
  21804. if (me.iconCls) {
  21805. me.setIconCls(me.iconCls);
  21806. }
  21807. if (me.tooltip) {
  21808. me.setTooltip(me.tooltip, true);
  21809. }
  21810. if (me.textAlign) {
  21811. me.setTextAlign(me.textAlign);
  21812. }
  21813. // Add the mouse events to the button
  21814. if (me.handleMouseEvents) {
  21815. me.mon(btn, {
  21816. scope: me,
  21817. mouseover: me.onMouseOver,
  21818. mouseout: me.onMouseOut,
  21819. mousedown: me.onMouseDown
  21820. });
  21821. if (me.split) {
  21822. me.mon(btn, {
  21823. mousemove: me.onMouseMove,
  21824. scope: me
  21825. });
  21826. }
  21827. }
  21828. // Check if the button has a menu
  21829. if (me.menu) {
  21830. me.mon(me.menu, {
  21831. scope: me,
  21832. show: me.onMenuShow,
  21833. hide: me.onMenuHide
  21834. });
  21835. me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
  21836. key: Ext.EventObject.DOWN,
  21837. handler: me.onDownKey,
  21838. scope: me
  21839. });
  21840. }
  21841. // Check if it is a repeat button
  21842. if (me.repeat) {
  21843. repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
  21844. me.mon(repeater, 'click', me.onRepeatClick, me);
  21845. } else {
  21846. me.mon(btn, me.clickEvent, me.onClick, me);
  21847. }
  21848. // Register the button in the toggle manager
  21849. Ext.ButtonToggleManager.register(me);
  21850. },
  21851. /**
  21852. * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
  21853. * create this Button's DOM structure.
  21854. *
  21855. * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
  21856. * their own implementation of this method.
  21857. *
  21858. * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
  21859. * {@link #template} returns an Object containing the following properties:
  21860. * @return {String} return.type The `<button>`'s {@link #type}
  21861. * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
  21862. * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
  21863. * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
  21864. * button's scale and icon alignment.
  21865. * @return {String} return.text The {@link #text} to display ion the Button.
  21866. * @return {Number} return.tabIndex The tab index within the input flow.
  21867. */
  21868. getTemplateArgs: function() {
  21869. var me = this,
  21870. persistentPadding = me.getPersistentBtnPadding(),
  21871. innerSpanStyle = '';
  21872. // Create negative margin offsets to counteract persistent button padding if needed
  21873. if (Math.max.apply(Math, persistentPadding) > 0) {
  21874. innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
  21875. return -pad + 'px';
  21876. }).join(' ');
  21877. }
  21878. return {
  21879. href : me.getHref(),
  21880. target : me.target || '_blank',
  21881. type : me.type,
  21882. splitCls : me.getSplitCls(),
  21883. cls : me.cls,
  21884. iconCls : me.iconCls || '',
  21885. text : me.text || '&#160;',
  21886. tabIndex : me.tabIndex,
  21887. innerSpanStyle: innerSpanStyle
  21888. };
  21889. },
  21890. /**
  21891. * @private
  21892. * If there is a configured href for this Button, returns the href with parameters appended.
  21893. * @returns The href string with parameters appended.
  21894. */
  21895. getHref: function() {
  21896. var me = this,
  21897. params = Ext.apply({}, me.baseParams);
  21898. // write baseParams first, then write any params
  21899. params = Ext.apply(params, me.params);
  21900. return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
  21901. },
  21902. /**
  21903. * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
  21904. *
  21905. * **Only valid if the Button was originally configured with a {@link #href}**
  21906. *
  21907. * @param {Object} params Parameters to use in the href URL.
  21908. */
  21909. setParams: function(params) {
  21910. this.params = params;
  21911. this.btnEl.dom.href = this.getHref();
  21912. },
  21913. getSplitCls: function() {
  21914. var me = this;
  21915. return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
  21916. },
  21917. // private
  21918. afterRender: function() {
  21919. var me = this;
  21920. me.useSetClass = true;
  21921. me.setButtonCls();
  21922. me.doc = Ext.getDoc();
  21923. this.callParent(arguments);
  21924. },
  21925. /**
  21926. * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
  21927. * value of the {@link #iconCls} config internally.
  21928. * @param {String} cls The CSS class providing the icon image
  21929. * @return {Ext.button.Button} this
  21930. */
  21931. setIconCls: function(cls) {
  21932. var me = this,
  21933. btnIconEl = me.btnIconEl,
  21934. oldCls = me.iconCls;
  21935. me.iconCls = cls;
  21936. if (btnIconEl) {
  21937. // Remove the previous iconCls from the button
  21938. btnIconEl.removeCls(oldCls);
  21939. btnIconEl.addCls(cls || '');
  21940. me.setButtonCls();
  21941. }
  21942. return me;
  21943. },
  21944. /**
  21945. * Sets the tooltip for this Button.
  21946. *
  21947. * @param {String/Object} tooltip This may be:
  21948. *
  21949. * - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
  21950. * - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
  21951. *
  21952. * @return {Ext.button.Button} this
  21953. */
  21954. setTooltip: function(tooltip, initial) {
  21955. var me = this;
  21956. if (me.rendered) {
  21957. if (!initial) {
  21958. me.clearTip();
  21959. }
  21960. if (Ext.isObject(tooltip)) {
  21961. Ext.tip.QuickTipManager.register(Ext.apply({
  21962. target: me.btnEl.id
  21963. },
  21964. tooltip));
  21965. me.tooltip = tooltip;
  21966. } else {
  21967. me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
  21968. }
  21969. } else {
  21970. me.tooltip = tooltip;
  21971. }
  21972. return me;
  21973. },
  21974. /**
  21975. * Sets the text alignment for this button.
  21976. * @param {String} align The new alignment of the button text. See {@link #textAlign}.
  21977. */
  21978. setTextAlign: function(align) {
  21979. var me = this,
  21980. btnEl = me.btnEl;
  21981. if (btnEl) {
  21982. btnEl.removeCls(me.baseCls + '-' + me.textAlign);
  21983. btnEl.addCls(me.baseCls + '-' + align);
  21984. }
  21985. me.textAlign = align;
  21986. return me;
  21987. },
  21988. getTipAttr: function(){
  21989. return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
  21990. },
  21991. // private
  21992. getRefItems: function(deep){
  21993. var menu = this.menu,
  21994. items;
  21995. if (menu) {
  21996. items = menu.getRefItems(deep);
  21997. items.unshift(menu);
  21998. }
  21999. return items || [];
  22000. },
  22001. // private
  22002. clearTip: function() {
  22003. if (Ext.isObject(this.tooltip)) {
  22004. Ext.tip.QuickTipManager.unregister(this.btnEl);
  22005. }
  22006. },
  22007. // private
  22008. beforeDestroy: function() {
  22009. var me = this;
  22010. if (me.rendered) {
  22011. me.clearTip();
  22012. }
  22013. if (me.menu && me.destroyMenu !== false) {
  22014. Ext.destroy(me.menu);
  22015. }
  22016. Ext.destroy(me.btnInnerEl, me.repeater);
  22017. me.callParent();
  22018. },
  22019. // private
  22020. onDestroy: function() {
  22021. var me = this;
  22022. if (me.rendered) {
  22023. me.doc.un('mouseover', me.monitorMouseOver, me);
  22024. me.doc.un('mouseup', me.onMouseUp, me);
  22025. delete me.doc;
  22026. Ext.ButtonToggleManager.unregister(me);
  22027. Ext.destroy(me.keyMap);
  22028. delete me.keyMap;
  22029. }
  22030. me.callParent();
  22031. },
  22032. /**
  22033. * Assigns this Button's click handler
  22034. * @param {Function} handler The function to call when the button is clicked
  22035. * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
  22036. * Defaults to this Button.
  22037. * @return {Ext.button.Button} this
  22038. */
  22039. setHandler: function(handler, scope) {
  22040. this.handler = handler;
  22041. this.scope = scope;
  22042. return this;
  22043. },
  22044. /**
  22045. * Sets this Button's text
  22046. * @param {String} text The button text
  22047. * @return {Ext.button.Button} this
  22048. */
  22049. setText: function(text) {
  22050. var me = this;
  22051. me.text = text;
  22052. if (me.el) {
  22053. me.btnInnerEl.update(text || '&#160;');
  22054. me.setButtonCls();
  22055. }
  22056. me.doComponentLayout();
  22057. return me;
  22058. },
  22059. /**
  22060. * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
  22061. * config internally.
  22062. * @param {String} icon The path to an image to display in the button
  22063. * @return {Ext.button.Button} this
  22064. */
  22065. setIcon: function(icon) {
  22066. var me = this,
  22067. iconEl = me.btnIconEl;
  22068. me.icon = icon;
  22069. if (iconEl) {
  22070. iconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
  22071. me.setButtonCls();
  22072. }
  22073. return me;
  22074. },
  22075. /**
  22076. * Gets the text for this Button
  22077. * @return {String} The button text
  22078. */
  22079. getText: function() {
  22080. return this.text;
  22081. },
  22082. /**
  22083. * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
  22084. * @param {Boolean} [state] Force a particular state
  22085. * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
  22086. * @return {Ext.button.Button} this
  22087. */
  22088. toggle: function(state, suppressEvent) {
  22089. var me = this;
  22090. state = state === undefined ? !me.pressed : !!state;
  22091. if (state !== me.pressed) {
  22092. if (me.rendered) {
  22093. me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
  22094. }
  22095. me.btnEl.dom.setAttribute('aria-pressed', state);
  22096. me.pressed = state;
  22097. if (!suppressEvent) {
  22098. me.fireEvent('toggle', me, state);
  22099. Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
  22100. }
  22101. }
  22102. return me;
  22103. },
  22104. maybeShowMenu: function(){
  22105. var me = this;
  22106. if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
  22107. me.showMenu();
  22108. }
  22109. },
  22110. /**
  22111. * Shows this button's menu (if it has one)
  22112. */
  22113. showMenu: function() {
  22114. var me = this;
  22115. if (me.rendered && me.menu) {
  22116. if (me.tooltip && me.getTipAttr() != 'title') {
  22117. Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
  22118. }
  22119. if (me.menu.isVisible()) {
  22120. me.menu.hide();
  22121. }
  22122. me.menu.showBy(me.el, me.menuAlign);
  22123. }
  22124. return me;
  22125. },
  22126. /**
  22127. * Hides this button's menu (if it has one)
  22128. */
  22129. hideMenu: function() {
  22130. if (this.hasVisibleMenu()) {
  22131. this.menu.hide();
  22132. }
  22133. return this;
  22134. },
  22135. /**
  22136. * Returns true if the button has a menu and it is visible
  22137. * @return {Boolean}
  22138. */
  22139. hasVisibleMenu: function() {
  22140. var menu = this.menu;
  22141. return menu && menu.rendered && menu.isVisible();
  22142. },
  22143. // private
  22144. onRepeatClick: function(repeat, e) {
  22145. this.onClick(e);
  22146. },
  22147. // private
  22148. onClick: function(e) {
  22149. var me = this;
  22150. if (me.preventDefault || (me.disabled && me.getHref()) && e) {
  22151. e.preventDefault();
  22152. }
  22153. if (e.button !== 0) {
  22154. return;
  22155. }
  22156. if (!me.disabled) {
  22157. me.doToggle();
  22158. me.maybeShowMenu();
  22159. me.fireHandler(e);
  22160. }
  22161. },
  22162. fireHandler: function(e){
  22163. var me = this,
  22164. handler = me.handler;
  22165. me.fireEvent('click', me, e);
  22166. if (handler) {
  22167. handler.call(me.scope || me, me, e);
  22168. }
  22169. me.onBlur();
  22170. },
  22171. doToggle: function(){
  22172. var me = this;
  22173. if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
  22174. me.toggle();
  22175. }
  22176. },
  22177. /**
  22178. * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
  22179. * The targets are interrogated to see what is being entered from where.
  22180. * @param e
  22181. */
  22182. onMouseOver: function(e) {
  22183. var me = this;
  22184. if (!me.disabled && !e.within(me.el, true, true)) {
  22185. me.onMouseEnter(e);
  22186. }
  22187. },
  22188. /**
  22189. * @private
  22190. * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
  22191. * or the mouse leaves the encapsulating element.
  22192. * The targets are interrogated to see what is being exited to where.
  22193. * @param e
  22194. */
  22195. onMouseOut: function(e) {
  22196. var me = this;
  22197. if (!e.within(me.el, true, true)) {
  22198. if (me.overMenuTrigger) {
  22199. me.onMenuTriggerOut(e);
  22200. }
  22201. me.onMouseLeave(e);
  22202. }
  22203. },
  22204. /**
  22205. * @private
  22206. * mousemove handler called when the mouse moves anywhere within the encapsulating element.
  22207. * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
  22208. * mousemove to check this is more resource intensive than we'd like, but it is necessary because
  22209. * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
  22210. * events when needed. In the future we should consider making the trigger a separate element that
  22211. * is absolutely positioned and sized over the trigger area.
  22212. */
  22213. onMouseMove: function(e) {
  22214. var me = this,
  22215. el = me.el,
  22216. over = me.overMenuTrigger,
  22217. overlap, btnSize;
  22218. if (me.split) {
  22219. if (me.arrowAlign === 'right') {
  22220. overlap = e.getX() - el.getX();
  22221. btnSize = el.getWidth();
  22222. } else {
  22223. overlap = e.getY() - el.getY();
  22224. btnSize = el.getHeight();
  22225. }
  22226. if (overlap > (btnSize - me.getTriggerSize())) {
  22227. if (!over) {
  22228. me.onMenuTriggerOver(e);
  22229. }
  22230. } else {
  22231. if (over) {
  22232. me.onMenuTriggerOut(e);
  22233. }
  22234. }
  22235. }
  22236. },
  22237. /**
  22238. * @private
  22239. * Measures the size of the trigger area for menu and split buttons. Will be a width for
  22240. * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
  22241. */
  22242. getTriggerSize: function() {
  22243. var me = this,
  22244. size = me.triggerSize,
  22245. side, sideFirstLetter, undef;
  22246. if (size === undef) {
  22247. side = me.arrowAlign;
  22248. sideFirstLetter = side.charAt(0);
  22249. size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
  22250. }
  22251. return size;
  22252. },
  22253. /**
  22254. * @private
  22255. * virtual mouseenter handler called when it is detected that the mouseout event
  22256. * signified the mouse entering the encapsulating element.
  22257. * @param e
  22258. */
  22259. onMouseEnter: function(e) {
  22260. var me = this;
  22261. me.addClsWithUI(me.overCls);
  22262. me.fireEvent('mouseover', me, e);
  22263. },
  22264. /**
  22265. * @private
  22266. * virtual mouseleave handler called when it is detected that the mouseover event
  22267. * signified the mouse entering the encapsulating element.
  22268. * @param e
  22269. */
  22270. onMouseLeave: function(e) {
  22271. var me = this;
  22272. me.removeClsWithUI(me.overCls);
  22273. me.fireEvent('mouseout', me, e);
  22274. },
  22275. /**
  22276. * @private
  22277. * virtual mouseenter handler called when it is detected that the mouseover event
  22278. * signified the mouse entering the arrow area of the button - the <em>.
  22279. * @param e
  22280. */
  22281. onMenuTriggerOver: function(e) {
  22282. var me = this;
  22283. me.overMenuTrigger = true;
  22284. me.fireEvent('menutriggerover', me, me.menu, e);
  22285. },
  22286. /**
  22287. * @private
  22288. * virtual mouseleave handler called when it is detected that the mouseout event
  22289. * signified the mouse leaving the arrow area of the button - the <em>.
  22290. * @param e
  22291. */
  22292. onMenuTriggerOut: function(e) {
  22293. var me = this;
  22294. delete me.overMenuTrigger;
  22295. me.fireEvent('menutriggerout', me, me.menu, e);
  22296. },
  22297. // inherit docs
  22298. enable : function(silent) {
  22299. var me = this;
  22300. me.callParent(arguments);
  22301. me.removeClsWithUI('disabled');
  22302. return me;
  22303. },
  22304. // inherit docs
  22305. disable : function(silent) {
  22306. var me = this;
  22307. me.callParent(arguments);
  22308. me.addClsWithUI('disabled');
  22309. me.removeClsWithUI(me.overCls);
  22310. return me;
  22311. },
  22312. /**
  22313. * Method to change the scale of the button. See {@link #scale} for allowed configurations.
  22314. * @param {String} scale The scale to change to.
  22315. */
  22316. setScale: function(scale) {
  22317. var me = this,
  22318. ui = me.ui.replace('-' + me.scale, '');
  22319. //check if it is an allowed scale
  22320. if (!Ext.Array.contains(me.allowedScales, scale)) {
  22321. throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
  22322. }
  22323. me.scale = scale;
  22324. me.setUI(ui);
  22325. },
  22326. // inherit docs
  22327. setUI: function(ui) {
  22328. var me = this;
  22329. //we need to append the scale to the UI, if not already done
  22330. if (me.scale && !ui.match(me.scale)) {
  22331. ui = ui + '-' + me.scale;
  22332. }
  22333. me.callParent([ui]);
  22334. // Set all the state classNames, as they need to include the UI
  22335. // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
  22336. },
  22337. // private
  22338. onFocus: function(e) {
  22339. var me = this;
  22340. if (!me.disabled) {
  22341. me.addClsWithUI(me.focusCls);
  22342. }
  22343. },
  22344. // private
  22345. onBlur: function(e) {
  22346. var me = this;
  22347. me.removeClsWithUI(me.focusCls);
  22348. },
  22349. // private
  22350. onMouseDown: function(e) {
  22351. var me = this;
  22352. if (!me.disabled && e.button === 0) {
  22353. me.addClsWithUI(me.pressedCls);
  22354. me.doc.on('mouseup', me.onMouseUp, me);
  22355. }
  22356. },
  22357. // private
  22358. onMouseUp: function(e) {
  22359. var me = this;
  22360. if (e.button === 0) {
  22361. if (!me.pressed) {
  22362. me.removeClsWithUI(me.pressedCls);
  22363. }
  22364. me.doc.un('mouseup', me.onMouseUp, me);
  22365. }
  22366. },
  22367. // private
  22368. onMenuShow: function(e) {
  22369. var me = this;
  22370. me.ignoreNextClick = 0;
  22371. me.addClsWithUI(me.menuActiveCls);
  22372. me.fireEvent('menushow', me, me.menu);
  22373. },
  22374. // private
  22375. onMenuHide: function(e) {
  22376. var me = this;
  22377. me.removeClsWithUI(me.menuActiveCls);
  22378. me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
  22379. me.fireEvent('menuhide', me, me.menu);
  22380. },
  22381. // private
  22382. restoreClick: function() {
  22383. this.ignoreNextClick = 0;
  22384. },
  22385. // private
  22386. onDownKey: function() {
  22387. var me = this;
  22388. if (!me.disabled) {
  22389. if (me.menu) {
  22390. me.showMenu();
  22391. }
  22392. }
  22393. },
  22394. /**
  22395. * @private
  22396. * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
  22397. * element that cannot be removed. This method returns the size of that padding with a one-time detection.
  22398. * @return {Number[]} [top, right, bottom, left]
  22399. */
  22400. getPersistentBtnPadding: function() {
  22401. var cls = Ext.button.Button,
  22402. padding = cls.persistentPadding,
  22403. btn, leftTop, btnEl, btnInnerEl;
  22404. if (!padding) {
  22405. padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
  22406. if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
  22407. // Create auto-size button offscreen and measure its insides
  22408. btn = Ext.create('Ext.button.Button', {
  22409. renderTo: Ext.getBody(),
  22410. text: 'test',
  22411. style: 'position:absolute;top:-999px;'
  22412. });
  22413. btnEl = btn.btnEl;
  22414. btnInnerEl = btn.btnInnerEl;
  22415. btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
  22416. leftTop = btnInnerEl.getOffsetsTo(btnEl);
  22417. padding[0] = leftTop[1];
  22418. padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
  22419. padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
  22420. padding[3] = leftTop[0];
  22421. btn.destroy();
  22422. }
  22423. }
  22424. return padding;
  22425. }
  22426. }, function() {
  22427. var groups = {};
  22428. function toggleGroup(btn, state) {
  22429. var g, i, l;
  22430. if (state) {
  22431. g = groups[btn.toggleGroup];
  22432. for (i = 0, l = g.length; i < l; i++) {
  22433. if (g[i] !== btn) {
  22434. g[i].toggle(false);
  22435. }
  22436. }
  22437. }
  22438. }
  22439. /**
  22440. * Private utility class used by Button
  22441. * @hide
  22442. */
  22443. Ext.ButtonToggleManager = {
  22444. register: function(btn) {
  22445. if (!btn.toggleGroup) {
  22446. return;
  22447. }
  22448. var group = groups[btn.toggleGroup];
  22449. if (!group) {
  22450. group = groups[btn.toggleGroup] = [];
  22451. }
  22452. group.push(btn);
  22453. btn.on('toggle', toggleGroup);
  22454. },
  22455. unregister: function(btn) {
  22456. if (!btn.toggleGroup) {
  22457. return;
  22458. }
  22459. var group = groups[btn.toggleGroup];
  22460. if (group) {
  22461. Ext.Array.remove(group, btn);
  22462. btn.un('toggle', toggleGroup);
  22463. }
  22464. },
  22465. /**
  22466. * Gets the pressed button in the passed group or null
  22467. * @param {String} group
  22468. * @return {Ext.button.Button}
  22469. */
  22470. getPressed: function(group) {
  22471. var g = groups[group],
  22472. i = 0,
  22473. len;
  22474. if (g) {
  22475. for (len = g.length; i < len; i++) {
  22476. if (g[i].pressed === true) {
  22477. return g[i];
  22478. }
  22479. }
  22480. }
  22481. return null;
  22482. }
  22483. };
  22484. });
  22485. /**
  22486. * @class Ext.layout.container.boxOverflow.Menu
  22487. * @extends Ext.layout.container.boxOverflow.None
  22488. * @private
  22489. */
  22490. Ext.define('Ext.layout.container.boxOverflow.Menu', {
  22491. /* Begin Definitions */
  22492. extend: 'Ext.layout.container.boxOverflow.None',
  22493. requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
  22494. alternateClassName: 'Ext.layout.boxOverflow.Menu',
  22495. /* End Definitions */
  22496. /**
  22497. * @cfg {String} afterCtCls
  22498. * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
  22499. * which must always be present at the rightmost edge of the Container
  22500. */
  22501. /**
  22502. * @property noItemsMenuText
  22503. * @type String
  22504. * HTML fragment to render into the toolbar overflow menu if there are no items to display
  22505. */
  22506. noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
  22507. constructor: function(layout) {
  22508. var me = this;
  22509. me.callParent(arguments);
  22510. // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
  22511. layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
  22512. me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
  22513. /**
  22514. * @property menuItems
  22515. * @type Array
  22516. * Array of all items that are currently hidden and should go into the dropdown menu
  22517. */
  22518. me.menuItems = [];
  22519. },
  22520. onRemove: function(comp){
  22521. Ext.Array.remove(this.menuItems, comp);
  22522. },
  22523. handleOverflow: function(calculations, targetSize) {
  22524. var me = this,
  22525. layout = me.layout,
  22526. methodName = 'get' + layout.parallelPrefixCap,
  22527. newSize = {},
  22528. posArgs = [null, null];
  22529. me.callParent(arguments);
  22530. this.createMenu(calculations, targetSize);
  22531. newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
  22532. newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
  22533. // Center the menuTrigger button.
  22534. // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
  22535. posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
  22536. me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
  22537. return { targetSize: newSize };
  22538. },
  22539. /**
  22540. * @private
  22541. * Called by the layout, when it determines that there is no overflow.
  22542. * Also called as an interceptor to the layout's onLayout method to reshow
  22543. * previously hidden overflowing items.
  22544. */
  22545. clearOverflow: function(calculations, targetSize) {
  22546. var me = this,
  22547. newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
  22548. items = me.menuItems,
  22549. i = 0,
  22550. length = items.length,
  22551. item;
  22552. me.hideTrigger();
  22553. for (; i < length; i++) {
  22554. items[i].show();
  22555. }
  22556. items.length = 0;
  22557. return targetSize ? {
  22558. targetSize: {
  22559. height: targetSize.height,
  22560. width : newWidth
  22561. }
  22562. } : null;
  22563. },
  22564. /**
  22565. * @private
  22566. */
  22567. showTrigger: function() {
  22568. this.menuTrigger.show();
  22569. },
  22570. /**
  22571. * @private
  22572. */
  22573. hideTrigger: function() {
  22574. if (this.menuTrigger !== undefined) {
  22575. this.menuTrigger.hide();
  22576. }
  22577. },
  22578. /**
  22579. * @private
  22580. * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
  22581. */
  22582. beforeMenuShow: function(menu) {
  22583. var me = this,
  22584. items = me.menuItems,
  22585. i = 0,
  22586. len = items.length,
  22587. item,
  22588. prev;
  22589. var needsSep = function(group, prev){
  22590. return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
  22591. };
  22592. me.clearMenu();
  22593. menu.removeAll();
  22594. for (; i < len; i++) {
  22595. item = items[i];
  22596. // Do not show a separator as a first item
  22597. if (!i && (item instanceof Ext.toolbar.Separator)) {
  22598. continue;
  22599. }
  22600. if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
  22601. menu.add('-');
  22602. }
  22603. me.addComponentToMenu(menu, item);
  22604. prev = item;
  22605. }
  22606. // put something so the menu isn't empty if no compatible items found
  22607. if (menu.items.length < 1) {
  22608. menu.add(me.noItemsMenuText);
  22609. }
  22610. },
  22611. /**
  22612. * @private
  22613. * Returns a menu config for a given component. This config is used to create a menu item
  22614. * to be added to the expander menu
  22615. * @param {Ext.Component} component The component to create the config for
  22616. * @param {Boolean} hideOnClick Passed through to the menu item
  22617. */
  22618. createMenuConfig : function(component, hideOnClick) {
  22619. var config = Ext.apply({}, component.initialConfig),
  22620. group = component.toggleGroup;
  22621. Ext.copyTo(config, component, [
  22622. 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
  22623. ]);
  22624. Ext.apply(config, {
  22625. text : component.overflowText || component.text,
  22626. hideOnClick: hideOnClick,
  22627. destroyMenu: false
  22628. });
  22629. if (group || component.enableToggle) {
  22630. Ext.apply(config, {
  22631. group : group,
  22632. checked: component.pressed,
  22633. listeners: {
  22634. checkchange: function(item, checked){
  22635. component.toggle(checked);
  22636. }
  22637. }
  22638. });
  22639. }
  22640. delete config.ownerCt;
  22641. delete config.xtype;
  22642. delete config.id;
  22643. return config;
  22644. },
  22645. /**
  22646. * @private
  22647. * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
  22648. * @param {Ext.menu.Menu} menu The menu to add to
  22649. * @param {Ext.Component} component The component to add
  22650. */
  22651. addComponentToMenu : function(menu, component) {
  22652. var me = this;
  22653. if (component instanceof Ext.toolbar.Separator) {
  22654. menu.add('-');
  22655. } else if (component.isComponent) {
  22656. if (component.isXType('splitbutton')) {
  22657. menu.add(me.createMenuConfig(component, true));
  22658. } else if (component.isXType('button')) {
  22659. menu.add(me.createMenuConfig(component, !component.menu));
  22660. } else if (component.isXType('buttongroup')) {
  22661. component.items.each(function(item){
  22662. me.addComponentToMenu(menu, item);
  22663. });
  22664. } else {
  22665. menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
  22666. }
  22667. }
  22668. },
  22669. /**
  22670. * @private
  22671. * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
  22672. * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
  22673. */
  22674. clearMenu : function() {
  22675. var menu = this.moreMenu;
  22676. if (menu && menu.items) {
  22677. menu.items.each(function(item) {
  22678. if (item.menu) {
  22679. delete item.menu;
  22680. }
  22681. });
  22682. }
  22683. },
  22684. /**
  22685. * @private
  22686. * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
  22687. * in the layout are too wide to fit in the space available
  22688. */
  22689. createMenu: function(calculations, targetSize) {
  22690. var me = this,
  22691. layout = me.layout,
  22692. startProp = layout.parallelBefore,
  22693. sizeProp = layout.parallelPrefix,
  22694. available = targetSize[sizeProp],
  22695. boxes = calculations.boxes,
  22696. i = 0,
  22697. len = boxes.length,
  22698. box;
  22699. if (!me.menuTrigger) {
  22700. me.createInnerElements();
  22701. /**
  22702. * @private
  22703. * @property menu
  22704. * @type Ext.menu.Menu
  22705. * The expand menu - holds items for every item that cannot be shown
  22706. * because the container is currently not large enough.
  22707. */
  22708. me.menu = Ext.create('Ext.menu.Menu', {
  22709. listeners: {
  22710. scope: me,
  22711. beforeshow: me.beforeMenuShow
  22712. }
  22713. });
  22714. /**
  22715. * @private
  22716. * @property menuTrigger
  22717. * @type Ext.button.Button
  22718. * The expand button which triggers the overflow menu to be shown
  22719. */
  22720. me.menuTrigger = Ext.create('Ext.button.Button', {
  22721. ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
  22722. iconCls : me.layout.owner.menuTriggerCls,
  22723. ui : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
  22724. menu : me.menu,
  22725. getSplitCls: function() { return '';},
  22726. renderTo: me.afterCt
  22727. });
  22728. }
  22729. me.showTrigger();
  22730. available -= me.afterCt.getWidth();
  22731. // Hide all items which are off the end, and store them to allow them to be restored
  22732. // before each layout operation.
  22733. me.menuItems.length = 0;
  22734. for (; i < len; i++) {
  22735. box = boxes[i];
  22736. if (box[startProp] + box[sizeProp] > available) {
  22737. me.menuItems.push(box.component);
  22738. box.component.hide();
  22739. }
  22740. }
  22741. },
  22742. /**
  22743. * @private
  22744. * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
  22745. * @param {Ext.container.Container} container The Container attached to this Layout instance
  22746. * @param {Ext.Element} target The target Element
  22747. */
  22748. createInnerElements: function() {
  22749. var me = this,
  22750. target = me.layout.getRenderTarget();
  22751. if (!this.afterCt) {
  22752. target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
  22753. this.afterCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
  22754. }
  22755. },
  22756. /**
  22757. * @private
  22758. */
  22759. destroy: function() {
  22760. Ext.destroy(this.menu, this.menuTrigger);
  22761. }
  22762. });
  22763. /**
  22764. * This class represents a rectangular region in X,Y space, and performs geometric
  22765. * transformations or tests upon the region.
  22766. *
  22767. * This class may be used to compare the document regions occupied by elements.
  22768. */
  22769. Ext.define('Ext.util.Region', {
  22770. /* Begin Definitions */
  22771. requires: ['Ext.util.Offset'],
  22772. statics: {
  22773. /**
  22774. * @static
  22775. * Retrieves an Ext.util.Region for a particular element.
  22776. * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
  22777. * @returns {Ext.util.Region} region
  22778. */
  22779. getRegion: function(el) {
  22780. return Ext.fly(el).getPageBox(true);
  22781. },
  22782. /**
  22783. * @static
  22784. * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
  22785. * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
  22786. * @return {Ext.util.Region} region The Region constructed based on the passed object
  22787. */
  22788. from: function(o) {
  22789. return new this(o.top, o.right, o.bottom, o.left);
  22790. }
  22791. },
  22792. /* End Definitions */
  22793. /**
  22794. * Creates a region from the bounding sides.
  22795. * @param {Number} top Top The topmost pixel of the Region.
  22796. * @param {Number} right Right The rightmost pixel of the Region.
  22797. * @param {Number} bottom Bottom The bottom pixel of the Region.
  22798. * @param {Number} left Left The leftmost pixel of the Region.
  22799. */
  22800. constructor : function(t, r, b, l) {
  22801. var me = this;
  22802. me.y = me.top = me[1] = t;
  22803. me.right = r;
  22804. me.bottom = b;
  22805. me.x = me.left = me[0] = l;
  22806. },
  22807. /**
  22808. * Checks if this region completely contains the region that is passed in.
  22809. * @param {Ext.util.Region} region
  22810. * @return {Boolean}
  22811. */
  22812. contains : function(region) {
  22813. var me = this;
  22814. return (region.x >= me.x &&
  22815. region.right <= me.right &&
  22816. region.y >= me.y &&
  22817. region.bottom <= me.bottom);
  22818. },
  22819. /**
  22820. * Checks if this region intersects the region passed in.
  22821. * @param {Ext.util.Region} region
  22822. * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
  22823. */
  22824. intersect : function(region) {
  22825. var me = this,
  22826. t = Math.max(me.y, region.y),
  22827. r = Math.min(me.right, region.right),
  22828. b = Math.min(me.bottom, region.bottom),
  22829. l = Math.max(me.x, region.x);
  22830. if (b > t && r > l) {
  22831. return new this.self(t, r, b, l);
  22832. }
  22833. else {
  22834. return false;
  22835. }
  22836. },
  22837. /**
  22838. * Returns the smallest region that contains the current AND targetRegion.
  22839. * @param {Ext.util.Region} region
  22840. * @return {Ext.util.Region} a new region
  22841. */
  22842. union : function(region) {
  22843. var me = this,
  22844. t = Math.min(me.y, region.y),
  22845. r = Math.max(me.right, region.right),
  22846. b = Math.max(me.bottom, region.bottom),
  22847. l = Math.min(me.x, region.x);
  22848. return new this.self(t, r, b, l);
  22849. },
  22850. /**
  22851. * Modifies the current region to be constrained to the targetRegion.
  22852. * @param {Ext.util.Region} targetRegion
  22853. * @return {Ext.util.Region} this
  22854. */
  22855. constrainTo : function(r) {
  22856. var me = this,
  22857. constrain = Ext.Number.constrain;
  22858. me.top = me.y = constrain(me.top, r.y, r.bottom);
  22859. me.bottom = constrain(me.bottom, r.y, r.bottom);
  22860. me.left = me.x = constrain(me.left, r.x, r.right);
  22861. me.right = constrain(me.right, r.x, r.right);
  22862. return me;
  22863. },
  22864. /**
  22865. * Modifies the current region to be adjusted by offsets.
  22866. * @param {Number} top top offset
  22867. * @param {Number} right right offset
  22868. * @param {Number} bottom bottom offset
  22869. * @param {Number} left left offset
  22870. * @return {Ext.util.Region} this
  22871. */
  22872. adjust : function(t, r, b, l) {
  22873. var me = this;
  22874. me.top = me.y += t;
  22875. me.left = me.x += l;
  22876. me.right += r;
  22877. me.bottom += b;
  22878. return me;
  22879. },
  22880. /**
  22881. * Get the offset amount of a point outside the region
  22882. * @param {String} [axis]
  22883. * @param {Ext.util.Point} [p] the point
  22884. * @return {Ext.util.Offset}
  22885. */
  22886. getOutOfBoundOffset: function(axis, p) {
  22887. if (!Ext.isObject(axis)) {
  22888. if (axis == 'x') {
  22889. return this.getOutOfBoundOffsetX(p);
  22890. } else {
  22891. return this.getOutOfBoundOffsetY(p);
  22892. }
  22893. } else {
  22894. p = axis;
  22895. var d = Ext.create('Ext.util.Offset');
  22896. d.x = this.getOutOfBoundOffsetX(p.x);
  22897. d.y = this.getOutOfBoundOffsetY(p.y);
  22898. return d;
  22899. }
  22900. },
  22901. /**
  22902. * Get the offset amount on the x-axis
  22903. * @param {Number} p the offset
  22904. * @return {Number}
  22905. */
  22906. getOutOfBoundOffsetX: function(p) {
  22907. if (p <= this.x) {
  22908. return this.x - p;
  22909. } else if (p >= this.right) {
  22910. return this.right - p;
  22911. }
  22912. return 0;
  22913. },
  22914. /**
  22915. * Get the offset amount on the y-axis
  22916. * @param {Number} p the offset
  22917. * @return {Number}
  22918. */
  22919. getOutOfBoundOffsetY: function(p) {
  22920. if (p <= this.y) {
  22921. return this.y - p;
  22922. } else if (p >= this.bottom) {
  22923. return this.bottom - p;
  22924. }
  22925. return 0;
  22926. },
  22927. /**
  22928. * Check whether the point / offset is out of bound
  22929. * @param {String} [axis]
  22930. * @param {Ext.util.Point/Number} [p] the point / offset
  22931. * @return {Boolean}
  22932. */
  22933. isOutOfBound: function(axis, p) {
  22934. if (!Ext.isObject(axis)) {
  22935. if (axis == 'x') {
  22936. return this.isOutOfBoundX(p);
  22937. } else {
  22938. return this.isOutOfBoundY(p);
  22939. }
  22940. } else {
  22941. p = axis;
  22942. return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
  22943. }
  22944. },
  22945. /**
  22946. * Check whether the offset is out of bound in the x-axis
  22947. * @param {Number} p the offset
  22948. * @return {Boolean}
  22949. */
  22950. isOutOfBoundX: function(p) {
  22951. return (p < this.x || p > this.right);
  22952. },
  22953. /**
  22954. * Check whether the offset is out of bound in the y-axis
  22955. * @param {Number} p the offset
  22956. * @return {Boolean}
  22957. */
  22958. isOutOfBoundY: function(p) {
  22959. return (p < this.y || p > this.bottom);
  22960. },
  22961. /**
  22962. * Restrict a point within the region by a certain factor.
  22963. * @param {String} [axis]
  22964. * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
  22965. * @param {Number} [factor]
  22966. * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
  22967. * @private
  22968. */
  22969. restrict: function(axis, p, factor) {
  22970. if (Ext.isObject(axis)) {
  22971. var newP;
  22972. factor = p;
  22973. p = axis;
  22974. if (p.copy) {
  22975. newP = p.copy();
  22976. }
  22977. else {
  22978. newP = {
  22979. x: p.x,
  22980. y: p.y
  22981. };
  22982. }
  22983. newP.x = this.restrictX(p.x, factor);
  22984. newP.y = this.restrictY(p.y, factor);
  22985. return newP;
  22986. } else {
  22987. if (axis == 'x') {
  22988. return this.restrictX(p, factor);
  22989. } else {
  22990. return this.restrictY(p, factor);
  22991. }
  22992. }
  22993. },
  22994. /**
  22995. * Restrict an offset within the region by a certain factor, on the x-axis
  22996. * @param {Number} p
  22997. * @param {Number} [factor=1] The factor.
  22998. * @return {Number}
  22999. * @private
  23000. */
  23001. restrictX : function(p, factor) {
  23002. if (!factor) {
  23003. factor = 1;
  23004. }
  23005. if (p <= this.x) {
  23006. p -= (p - this.x) * factor;
  23007. }
  23008. else if (p >= this.right) {
  23009. p -= (p - this.right) * factor;
  23010. }
  23011. return p;
  23012. },
  23013. /**
  23014. * Restrict an offset within the region by a certain factor, on the y-axis
  23015. * @param {Number} p
  23016. * @param {Number} [factor] The factor, defaults to 1
  23017. * @return {Number}
  23018. * @private
  23019. */
  23020. restrictY : function(p, factor) {
  23021. if (!factor) {
  23022. factor = 1;
  23023. }
  23024. if (p <= this.y) {
  23025. p -= (p - this.y) * factor;
  23026. }
  23027. else if (p >= this.bottom) {
  23028. p -= (p - this.bottom) * factor;
  23029. }
  23030. return p;
  23031. },
  23032. /**
  23033. * Get the width / height of this region
  23034. * @return {Object} an object with width and height properties
  23035. * @private
  23036. */
  23037. getSize: function() {
  23038. return {
  23039. width: this.right - this.x,
  23040. height: this.bottom - this.y
  23041. };
  23042. },
  23043. /**
  23044. * Create a copy of this Region.
  23045. * @return {Ext.util.Region}
  23046. */
  23047. copy: function() {
  23048. return new this.self(this.y, this.right, this.bottom, this.x);
  23049. },
  23050. /**
  23051. * Copy the values of another Region to this Region
  23052. * @param {Ext.util.Region} p The region to copy from.
  23053. * @return {Ext.util.Region} This Region
  23054. */
  23055. copyFrom: function(p) {
  23056. var me = this;
  23057. me.top = me.y = me[1] = p.y;
  23058. me.right = p.right;
  23059. me.bottom = p.bottom;
  23060. me.left = me.x = me[0] = p.x;
  23061. return this;
  23062. },
  23063. /*
  23064. * Dump this to an eye-friendly string, great for debugging
  23065. * @return {String}
  23066. */
  23067. toString: function() {
  23068. return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
  23069. },
  23070. /**
  23071. * Translate this region by the given offset amount
  23072. * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
  23073. * Or the x value is using the two argument form.
  23074. * @param {Number} y The y value unless using an Offset object.
  23075. * @return {Ext.util.Region} this This Region
  23076. */
  23077. translateBy: function(x, y) {
  23078. if (arguments.length == 1) {
  23079. y = x.y;
  23080. x = x.x;
  23081. }
  23082. var me = this;
  23083. me.top = me.y += y;
  23084. me.right += x;
  23085. me.bottom += y;
  23086. me.left = me.x += x;
  23087. return me;
  23088. },
  23089. /**
  23090. * Round all the properties of this region
  23091. * @return {Ext.util.Region} this This Region
  23092. */
  23093. round: function() {
  23094. var me = this;
  23095. me.top = me.y = Math.round(me.y);
  23096. me.right = Math.round(me.right);
  23097. me.bottom = Math.round(me.bottom);
  23098. me.left = me.x = Math.round(me.x);
  23099. return me;
  23100. },
  23101. /**
  23102. * Check whether this region is equivalent to the given region
  23103. * @param {Ext.util.Region} region The region to compare with
  23104. * @return {Boolean}
  23105. */
  23106. equals: function(region) {
  23107. return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
  23108. }
  23109. });
  23110. /*
  23111. * This is a derivative of the similarly named class in the YUI Library.
  23112. * The original license:
  23113. * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  23114. * Code licensed under the BSD License:
  23115. * http://developer.yahoo.net/yui/license.txt
  23116. */
  23117. /**
  23118. * @class Ext.dd.DragDropManager
  23119. * DragDropManager is a singleton that tracks the element interaction for
  23120. * all DragDrop items in the window. Generally, you will not call
  23121. * this class directly, but it does have helper methods that could
  23122. * be useful in your DragDrop implementations.
  23123. * @singleton
  23124. */
  23125. Ext.define('Ext.dd.DragDropManager', {
  23126. singleton: true,
  23127. requires: ['Ext.util.Region'],
  23128. uses: ['Ext.tip.QuickTipManager'],
  23129. // shorter ClassName, to save bytes and use internally
  23130. alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
  23131. /**
  23132. * Two dimensional Array of registered DragDrop objects. The first
  23133. * dimension is the DragDrop item group, the second the DragDrop
  23134. * object.
  23135. * @property ids
  23136. * @type String[]
  23137. * @private
  23138. */
  23139. ids: {},
  23140. /**
  23141. * Array of element ids defined as drag handles. Used to determine
  23142. * if the element that generated the mousedown event is actually the
  23143. * handle and not the html element itself.
  23144. * @property handleIds
  23145. * @type String[]
  23146. * @private
  23147. */
  23148. handleIds: {},
  23149. /**
  23150. * the DragDrop object that is currently being dragged
  23151. * @property {Ext.dd.DragDrop} dragCurrent
  23152. * @private
  23153. **/
  23154. dragCurrent: null,
  23155. /**
  23156. * the DragDrop object(s) that are being hovered over
  23157. * @property {Ext.dd.DragDrop[]} dragOvers
  23158. * @private
  23159. */
  23160. dragOvers: {},
  23161. /**
  23162. * the X distance between the cursor and the object being dragged
  23163. * @property deltaX
  23164. * @type Number
  23165. * @private
  23166. */
  23167. deltaX: 0,
  23168. /**
  23169. * the Y distance between the cursor and the object being dragged
  23170. * @property deltaY
  23171. * @type Number
  23172. * @private
  23173. */
  23174. deltaY: 0,
  23175. /**
  23176. * Flag to determine if we should prevent the default behavior of the
  23177. * events we define. By default this is true, but this can be set to
  23178. * false if you need the default behavior (not recommended)
  23179. * @property preventDefault
  23180. * @type Boolean
  23181. */
  23182. preventDefault: true,
  23183. /**
  23184. * Flag to determine if we should stop the propagation of the events
  23185. * we generate. This is true by default but you may want to set it to
  23186. * false if the html element contains other features that require the
  23187. * mouse click.
  23188. * @property stopPropagation
  23189. * @type Boolean
  23190. */
  23191. stopPropagation: true,
  23192. /**
  23193. * Internal flag that is set to true when drag and drop has been
  23194. * intialized
  23195. * @property initialized
  23196. * @private
  23197. */
  23198. initialized: false,
  23199. /**
  23200. * All drag and drop can be disabled.
  23201. * @property locked
  23202. * @private
  23203. */
  23204. locked: false,
  23205. /**
  23206. * Called the first time an element is registered.
  23207. * @method init
  23208. * @private
  23209. */
  23210. init: function() {
  23211. this.initialized = true;
  23212. },
  23213. /**
  23214. * In point mode, drag and drop interaction is defined by the
  23215. * location of the cursor during the drag/drop
  23216. * @property POINT
  23217. * @type Number
  23218. */
  23219. POINT: 0,
  23220. /**
  23221. * In intersect mode, drag and drop interaction is defined by the
  23222. * overlap of two or more drag and drop objects.
  23223. * @property INTERSECT
  23224. * @type Number
  23225. */
  23226. INTERSECT: 1,
  23227. /**
  23228. * The current drag and drop mode. Default: POINT
  23229. * @property mode
  23230. * @type Number
  23231. */
  23232. mode: 0,
  23233. /**
  23234. * Runs method on all drag and drop objects
  23235. * @method _execOnAll
  23236. * @private
  23237. */
  23238. _execOnAll: function(sMethod, args) {
  23239. for (var i in this.ids) {
  23240. for (var j in this.ids[i]) {
  23241. var oDD = this.ids[i][j];
  23242. if (! this.isTypeOfDD(oDD)) {
  23243. continue;
  23244. }
  23245. oDD[sMethod].apply(oDD, args);
  23246. }
  23247. }
  23248. },
  23249. /**
  23250. * Drag and drop initialization. Sets up the global event handlers
  23251. * @method _onLoad
  23252. * @private
  23253. */
  23254. _onLoad: function() {
  23255. this.init();
  23256. var Event = Ext.EventManager;
  23257. Event.on(document, "mouseup", this.handleMouseUp, this, true);
  23258. Event.on(document, "mousemove", this.handleMouseMove, this, true);
  23259. Event.on(window, "unload", this._onUnload, this, true);
  23260. Event.on(window, "resize", this._onResize, this, true);
  23261. // Event.on(window, "mouseout", this._test);
  23262. },
  23263. /**
  23264. * Reset constraints on all drag and drop objs
  23265. * @method _onResize
  23266. * @private
  23267. */
  23268. _onResize: function(e) {
  23269. this._execOnAll("resetConstraints", []);
  23270. },
  23271. /**
  23272. * Lock all drag and drop functionality
  23273. * @method lock
  23274. */
  23275. lock: function() { this.locked = true; },
  23276. /**
  23277. * Unlock all drag and drop functionality
  23278. * @method unlock
  23279. */
  23280. unlock: function() { this.locked = false; },
  23281. /**
  23282. * Is drag and drop locked?
  23283. * @method isLocked
  23284. * @return {Boolean} True if drag and drop is locked, false otherwise.
  23285. */
  23286. isLocked: function() { return this.locked; },
  23287. /**
  23288. * Location cache that is set for all drag drop objects when a drag is
  23289. * initiated, cleared when the drag is finished.
  23290. * @property locationCache
  23291. * @private
  23292. */
  23293. locationCache: {},
  23294. /**
  23295. * Set useCache to false if you want to force object the lookup of each
  23296. * drag and drop linked element constantly during a drag.
  23297. * @property useCache
  23298. * @type Boolean
  23299. */
  23300. useCache: true,
  23301. /**
  23302. * The number of pixels that the mouse needs to move after the
  23303. * mousedown before the drag is initiated. Default=3;
  23304. * @property clickPixelThresh
  23305. * @type Number
  23306. */
  23307. clickPixelThresh: 3,
  23308. /**
  23309. * The number of milliseconds after the mousedown event to initiate the
  23310. * drag if we don't get a mouseup event. Default=350
  23311. * @property clickTimeThresh
  23312. * @type Number
  23313. */
  23314. clickTimeThresh: 350,
  23315. /**
  23316. * Flag that indicates that either the drag pixel threshold or the
  23317. * mousdown time threshold has been met
  23318. * @property dragThreshMet
  23319. * @type Boolean
  23320. * @private
  23321. */
  23322. dragThreshMet: false,
  23323. /**
  23324. * Timeout used for the click time threshold
  23325. * @property clickTimeout
  23326. * @type Object
  23327. * @private
  23328. */
  23329. clickTimeout: null,
  23330. /**
  23331. * The X position of the mousedown event stored for later use when a
  23332. * drag threshold is met.
  23333. * @property startX
  23334. * @type Number
  23335. * @private
  23336. */
  23337. startX: 0,
  23338. /**
  23339. * The Y position of the mousedown event stored for later use when a
  23340. * drag threshold is met.
  23341. * @property startY
  23342. * @type Number
  23343. * @private
  23344. */
  23345. startY: 0,
  23346. /**
  23347. * Each DragDrop instance must be registered with the DragDropManager.
  23348. * This is executed in DragDrop.init()
  23349. * @method regDragDrop
  23350. * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
  23351. * @param {String} sGroup the name of the group this element belongs to
  23352. */
  23353. regDragDrop: function(oDD, sGroup) {
  23354. if (!this.initialized) { this.init(); }
  23355. if (!this.ids[sGroup]) {
  23356. this.ids[sGroup] = {};
  23357. }
  23358. this.ids[sGroup][oDD.id] = oDD;
  23359. },
  23360. /**
  23361. * Removes the supplied dd instance from the supplied group. Executed
  23362. * by DragDrop.removeFromGroup, so don't call this function directly.
  23363. * @method removeDDFromGroup
  23364. * @private
  23365. */
  23366. removeDDFromGroup: function(oDD, sGroup) {
  23367. if (!this.ids[sGroup]) {
  23368. this.ids[sGroup] = {};
  23369. }
  23370. var obj = this.ids[sGroup];
  23371. if (obj && obj[oDD.id]) {
  23372. delete obj[oDD.id];
  23373. }
  23374. },
  23375. /**
  23376. * Unregisters a drag and drop item. This is executed in
  23377. * DragDrop.unreg, use that method instead of calling this directly.
  23378. * @method _remove
  23379. * @private
  23380. */
  23381. _remove: function(oDD) {
  23382. for (var g in oDD.groups) {
  23383. if (g && this.ids[g] && this.ids[g][oDD.id]) {
  23384. delete this.ids[g][oDD.id];
  23385. }
  23386. }
  23387. delete this.handleIds[oDD.id];
  23388. },
  23389. /**
  23390. * Each DragDrop handle element must be registered. This is done
  23391. * automatically when executing DragDrop.setHandleElId()
  23392. * @method regHandle
  23393. * @param {String} sDDId the DragDrop id this element is a handle for
  23394. * @param {String} sHandleId the id of the element that is the drag
  23395. * handle
  23396. */
  23397. regHandle: function(sDDId, sHandleId) {
  23398. if (!this.handleIds[sDDId]) {
  23399. this.handleIds[sDDId] = {};
  23400. }
  23401. this.handleIds[sDDId][sHandleId] = sHandleId;
  23402. },
  23403. /**
  23404. * Utility function to determine if a given element has been
  23405. * registered as a drag drop item.
  23406. * @method isDragDrop
  23407. * @param {String} id the element id to check
  23408. * @return {Boolean} true if this element is a DragDrop item,
  23409. * false otherwise
  23410. */
  23411. isDragDrop: function(id) {
  23412. return ( this.getDDById(id) ) ? true : false;
  23413. },
  23414. /**
  23415. * Returns the drag and drop instances that are in all groups the
  23416. * passed in instance belongs to.
  23417. * @method getRelated
  23418. * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
  23419. * @param {Boolean} bTargetsOnly if true, only return targetable objs
  23420. * @return {Ext.dd.DragDrop[]} the related instances
  23421. */
  23422. getRelated: function(p_oDD, bTargetsOnly) {
  23423. var oDDs = [];
  23424. for (var i in p_oDD.groups) {
  23425. for (var j in this.ids[i]) {
  23426. var dd = this.ids[i][j];
  23427. if (! this.isTypeOfDD(dd)) {
  23428. continue;
  23429. }
  23430. if (!bTargetsOnly || dd.isTarget) {
  23431. oDDs[oDDs.length] = dd;
  23432. }
  23433. }
  23434. }
  23435. return oDDs;
  23436. },
  23437. /**
  23438. * Returns true if the specified dd target is a legal target for
  23439. * the specifice drag obj
  23440. * @method isLegalTarget
  23441. * @param {Ext.dd.DragDrop} oDD the drag obj
  23442. * @param {Ext.dd.DragDrop} oTargetDD the target
  23443. * @return {Boolean} true if the target is a legal target for the
  23444. * dd obj
  23445. */
  23446. isLegalTarget: function (oDD, oTargetDD) {
  23447. var targets = this.getRelated(oDD, true);
  23448. for (var i=0, len=targets.length;i<len;++i) {
  23449. if (targets[i].id == oTargetDD.id) {
  23450. return true;
  23451. }
  23452. }
  23453. return false;
  23454. },
  23455. /**
  23456. * My goal is to be able to transparently determine if an object is
  23457. * typeof DragDrop, and the exact subclass of DragDrop. typeof
  23458. * returns "object", oDD.constructor.toString() always returns
  23459. * "DragDrop" and not the name of the subclass. So for now it just
  23460. * evaluates a well-known variable in DragDrop.
  23461. * @method isTypeOfDD
  23462. * @param {Object} the object to evaluate
  23463. * @return {Boolean} true if typeof oDD = DragDrop
  23464. */
  23465. isTypeOfDD: function (oDD) {
  23466. return (oDD && oDD.__ygDragDrop);
  23467. },
  23468. /**
  23469. * Utility function to determine if a given element has been
  23470. * registered as a drag drop handle for the given Drag Drop object.
  23471. * @method isHandle
  23472. * @param {String} id the element id to check
  23473. * @return {Boolean} true if this element is a DragDrop handle, false
  23474. * otherwise
  23475. */
  23476. isHandle: function(sDDId, sHandleId) {
  23477. return ( this.handleIds[sDDId] &&
  23478. this.handleIds[sDDId][sHandleId] );
  23479. },
  23480. /**
  23481. * Returns the DragDrop instance for a given id
  23482. * @method getDDById
  23483. * @param {String} id the id of the DragDrop object
  23484. * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
  23485. */
  23486. getDDById: function(id) {
  23487. for (var i in this.ids) {
  23488. if (this.ids[i][id]) {
  23489. return this.ids[i][id];
  23490. }
  23491. }
  23492. return null;
  23493. },
  23494. /**
  23495. * Fired after a registered DragDrop object gets the mousedown event.
  23496. * Sets up the events required to track the object being dragged
  23497. * @method handleMouseDown
  23498. * @param {Event} e the event
  23499. * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
  23500. * @private
  23501. */
  23502. handleMouseDown: function(e, oDD) {
  23503. if(Ext.tip.QuickTipManager){
  23504. Ext.tip.QuickTipManager.ddDisable();
  23505. }
  23506. if(this.dragCurrent){
  23507. // the original browser mouseup wasn't handled (e.g. outside FF browser window)
  23508. // so clean up first to avoid breaking the next drag
  23509. this.handleMouseUp(e);
  23510. }
  23511. this.currentTarget = e.getTarget();
  23512. this.dragCurrent = oDD;
  23513. var el = oDD.getEl();
  23514. // track start position
  23515. this.startX = e.getPageX();
  23516. this.startY = e.getPageY();
  23517. this.deltaX = this.startX - el.offsetLeft;
  23518. this.deltaY = this.startY - el.offsetTop;
  23519. this.dragThreshMet = false;
  23520. this.clickTimeout = setTimeout(
  23521. function() {
  23522. var DDM = Ext.dd.DragDropManager;
  23523. DDM.startDrag(DDM.startX, DDM.startY);
  23524. },
  23525. this.clickTimeThresh );
  23526. },
  23527. /**
  23528. * Fired when either the drag pixel threshol or the mousedown hold
  23529. * time threshold has been met.
  23530. * @method startDrag
  23531. * @param {Number} x the X position of the original mousedown
  23532. * @param {Number} y the Y position of the original mousedown
  23533. */
  23534. startDrag: function(x, y) {
  23535. clearTimeout(this.clickTimeout);
  23536. if (this.dragCurrent) {
  23537. this.dragCurrent.b4StartDrag(x, y);
  23538. this.dragCurrent.startDrag(x, y);
  23539. }
  23540. this.dragThreshMet = true;
  23541. },
  23542. /**
  23543. * Internal function to handle the mouseup event. Will be invoked
  23544. * from the context of the document.
  23545. * @method handleMouseUp
  23546. * @param {Event} e the event
  23547. * @private
  23548. */
  23549. handleMouseUp: function(e) {
  23550. if(Ext.tip && Ext.tip.QuickTipManager){
  23551. Ext.tip.QuickTipManager.ddEnable();
  23552. }
  23553. if (! this.dragCurrent) {
  23554. return;
  23555. }
  23556. clearTimeout(this.clickTimeout);
  23557. if (this.dragThreshMet) {
  23558. this.fireEvents(e, true);
  23559. } else {
  23560. }
  23561. this.stopDrag(e);
  23562. this.stopEvent(e);
  23563. },
  23564. /**
  23565. * Utility to stop event propagation and event default, if these
  23566. * features are turned on.
  23567. * @method stopEvent
  23568. * @param {Event} e the event as returned by this.getEvent()
  23569. */
  23570. stopEvent: function(e){
  23571. if(this.stopPropagation) {
  23572. e.stopPropagation();
  23573. }
  23574. if (this.preventDefault) {
  23575. e.preventDefault();
  23576. }
  23577. },
  23578. /**
  23579. * Internal function to clean up event handlers after the drag
  23580. * operation is complete
  23581. * @method stopDrag
  23582. * @param {Event} e the event
  23583. * @private
  23584. */
  23585. stopDrag: function(e) {
  23586. // Fire the drag end event for the item that was dragged
  23587. if (this.dragCurrent) {
  23588. if (this.dragThreshMet) {
  23589. this.dragCurrent.b4EndDrag(e);
  23590. this.dragCurrent.endDrag(e);
  23591. }
  23592. this.dragCurrent.onMouseUp(e);
  23593. }
  23594. this.dragCurrent = null;
  23595. this.dragOvers = {};
  23596. },
  23597. /**
  23598. * Internal function to handle the mousemove event. Will be invoked
  23599. * from the context of the html element.
  23600. *
  23601. * @TODO figure out what we can do about mouse events lost when the
  23602. * user drags objects beyond the window boundary. Currently we can
  23603. * detect this in internet explorer by verifying that the mouse is
  23604. * down during the mousemove event. Firefox doesn't give us the
  23605. * button state on the mousemove event.
  23606. * @method handleMouseMove
  23607. * @param {Event} e the event
  23608. * @private
  23609. */
  23610. handleMouseMove: function(e) {
  23611. if (! this.dragCurrent) {
  23612. return true;
  23613. }
  23614. // var button = e.which || e.button;
  23615. // check for IE mouseup outside of page boundary
  23616. if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
  23617. this.stopEvent(e);
  23618. return this.handleMouseUp(e);
  23619. }
  23620. if (!this.dragThreshMet) {
  23621. var diffX = Math.abs(this.startX - e.getPageX());
  23622. var diffY = Math.abs(this.startY - e.getPageY());
  23623. if (diffX > this.clickPixelThresh ||
  23624. diffY > this.clickPixelThresh) {
  23625. this.startDrag(this.startX, this.startY);
  23626. }
  23627. }
  23628. if (this.dragThreshMet) {
  23629. this.dragCurrent.b4Drag(e);
  23630. this.dragCurrent.onDrag(e);
  23631. if(!this.dragCurrent.moveOnly){
  23632. this.fireEvents(e, false);
  23633. }
  23634. }
  23635. this.stopEvent(e);
  23636. return true;
  23637. },
  23638. /**
  23639. * Iterates over all of the DragDrop elements to find ones we are
  23640. * hovering over or dropping on
  23641. * @method fireEvents
  23642. * @param {Event} e the event
  23643. * @param {Boolean} isDrop is this a drop op or a mouseover op?
  23644. * @private
  23645. */
  23646. fireEvents: function(e, isDrop) {
  23647. var dc = this.dragCurrent;
  23648. // If the user did the mouse up outside of the window, we could
  23649. // get here even though we have ended the drag.
  23650. if (!dc || dc.isLocked()) {
  23651. return;
  23652. }
  23653. var pt = e.getPoint();
  23654. // cache the previous dragOver array
  23655. var oldOvers = [];
  23656. var outEvts = [];
  23657. var overEvts = [];
  23658. var dropEvts = [];
  23659. var enterEvts = [];
  23660. // Check to see if the object(s) we were hovering over is no longer
  23661. // being hovered over so we can fire the onDragOut event
  23662. for (var i in this.dragOvers) {
  23663. var ddo = this.dragOvers[i];
  23664. if (! this.isTypeOfDD(ddo)) {
  23665. continue;
  23666. }
  23667. if (! this.isOverTarget(pt, ddo, this.mode)) {
  23668. outEvts.push( ddo );
  23669. }
  23670. oldOvers[i] = true;
  23671. delete this.dragOvers[i];
  23672. }
  23673. for (var sGroup in dc.groups) {
  23674. if ("string" != typeof sGroup) {
  23675. continue;
  23676. }
  23677. for (i in this.ids[sGroup]) {
  23678. var oDD = this.ids[sGroup][i];
  23679. if (! this.isTypeOfDD(oDD)) {
  23680. continue;
  23681. }
  23682. if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
  23683. if (this.isOverTarget(pt, oDD, this.mode)) {
  23684. // look for drop interactions
  23685. if (isDrop) {
  23686. dropEvts.push( oDD );
  23687. // look for drag enter and drag over interactions
  23688. } else {
  23689. // initial drag over: dragEnter fires
  23690. if (!oldOvers[oDD.id]) {
  23691. enterEvts.push( oDD );
  23692. // subsequent drag overs: dragOver fires
  23693. } else {
  23694. overEvts.push( oDD );
  23695. }
  23696. this.dragOvers[oDD.id] = oDD;
  23697. }
  23698. }
  23699. }
  23700. }
  23701. }
  23702. if (this.mode) {
  23703. if (outEvts.length) {
  23704. dc.b4DragOut(e, outEvts);
  23705. dc.onDragOut(e, outEvts);
  23706. }
  23707. if (enterEvts.length) {
  23708. dc.onDragEnter(e, enterEvts);
  23709. }
  23710. if (overEvts.length) {
  23711. dc.b4DragOver(e, overEvts);
  23712. dc.onDragOver(e, overEvts);
  23713. }
  23714. if (dropEvts.length) {
  23715. dc.b4DragDrop(e, dropEvts);
  23716. dc.onDragDrop(e, dropEvts);
  23717. }
  23718. } else {
  23719. // fire dragout events
  23720. var len = 0;
  23721. for (i=0, len=outEvts.length; i<len; ++i) {
  23722. dc.b4DragOut(e, outEvts[i].id);
  23723. dc.onDragOut(e, outEvts[i].id);
  23724. }
  23725. // fire enter events
  23726. for (i=0,len=enterEvts.length; i<len; ++i) {
  23727. // dc.b4DragEnter(e, oDD.id);
  23728. dc.onDragEnter(e, enterEvts[i].id);
  23729. }
  23730. // fire over events
  23731. for (i=0,len=overEvts.length; i<len; ++i) {
  23732. dc.b4DragOver(e, overEvts[i].id);
  23733. dc.onDragOver(e, overEvts[i].id);
  23734. }
  23735. // fire drop events
  23736. for (i=0, len=dropEvts.length; i<len; ++i) {
  23737. dc.b4DragDrop(e, dropEvts[i].id);
  23738. dc.onDragDrop(e, dropEvts[i].id);
  23739. }
  23740. }
  23741. // notify about a drop that did not find a target
  23742. if (isDrop && !dropEvts.length) {
  23743. dc.onInvalidDrop(e);
  23744. }
  23745. },
  23746. /**
  23747. * Helper function for getting the best match from the list of drag
  23748. * and drop objects returned by the drag and drop events when we are
  23749. * in INTERSECT mode. It returns either the first object that the
  23750. * cursor is over, or the object that has the greatest overlap with
  23751. * the dragged element.
  23752. * @method getBestMatch
  23753. * @param {Ext.dd.DragDrop[]} dds The array of drag and drop objects
  23754. * targeted
  23755. * @return {Ext.dd.DragDrop} The best single match
  23756. */
  23757. getBestMatch: function(dds) {
  23758. var winner = null;
  23759. // Return null if the input is not what we expect
  23760. //if (!dds || !dds.length || dds.length == 0) {
  23761. // winner = null;
  23762. // If there is only one item, it wins
  23763. //} else if (dds.length == 1) {
  23764. var len = dds.length;
  23765. if (len == 1) {
  23766. winner = dds[0];
  23767. } else {
  23768. // Loop through the targeted items
  23769. for (var i=0; i<len; ++i) {
  23770. var dd = dds[i];
  23771. // If the cursor is over the object, it wins. If the
  23772. // cursor is over multiple matches, the first one we come
  23773. // to wins.
  23774. if (dd.cursorIsOver) {
  23775. winner = dd;
  23776. break;
  23777. // Otherwise the object with the most overlap wins
  23778. } else {
  23779. if (!winner ||
  23780. winner.overlap.getArea() < dd.overlap.getArea()) {
  23781. winner = dd;
  23782. }
  23783. }
  23784. }
  23785. }
  23786. return winner;
  23787. },
  23788. /**
  23789. * Refreshes the cache of the top-left and bottom-right points of the
  23790. * drag and drop objects in the specified group(s). This is in the
  23791. * format that is stored in the drag and drop instance, so typical
  23792. * usage is:
  23793. * <code>
  23794. * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
  23795. * </code>
  23796. * Alternatively:
  23797. * <code>
  23798. * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
  23799. * </code>
  23800. * @TODO this really should be an indexed array. Alternatively this
  23801. * method could accept both.
  23802. * @method refreshCache
  23803. * @param {Object} groups an associative array of groups to refresh
  23804. */
  23805. refreshCache: function(groups) {
  23806. for (var sGroup in groups) {
  23807. if ("string" != typeof sGroup) {
  23808. continue;
  23809. }
  23810. for (var i in this.ids[sGroup]) {
  23811. var oDD = this.ids[sGroup][i];
  23812. if (this.isTypeOfDD(oDD)) {
  23813. // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
  23814. var loc = this.getLocation(oDD);
  23815. if (loc) {
  23816. this.locationCache[oDD.id] = loc;
  23817. } else {
  23818. delete this.locationCache[oDD.id];
  23819. // this will unregister the drag and drop object if
  23820. // the element is not in a usable state
  23821. // oDD.unreg();
  23822. }
  23823. }
  23824. }
  23825. }
  23826. },
  23827. /**
  23828. * This checks to make sure an element exists and is in the DOM. The
  23829. * main purpose is to handle cases where innerHTML is used to remove
  23830. * drag and drop objects from the DOM. IE provides an 'unspecified
  23831. * error' when trying to access the offsetParent of such an element
  23832. * @method verifyEl
  23833. * @param {HTMLElement} el the element to check
  23834. * @return {Boolean} true if the element looks usable
  23835. */
  23836. verifyEl: function(el) {
  23837. if (el) {
  23838. var parent;
  23839. if(Ext.isIE){
  23840. try{
  23841. parent = el.offsetParent;
  23842. }catch(e){}
  23843. }else{
  23844. parent = el.offsetParent;
  23845. }
  23846. if (parent) {
  23847. return true;
  23848. }
  23849. }
  23850. return false;
  23851. },
  23852. /**
  23853. * Returns a Region object containing the drag and drop element's position
  23854. * and size, including the padding configured for it
  23855. * @method getLocation
  23856. * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
  23857. * @return {Ext.util.Region} a Region object representing the total area
  23858. * the element occupies, including any padding
  23859. * the instance is configured for.
  23860. */
  23861. getLocation: function(oDD) {
  23862. if (! this.isTypeOfDD(oDD)) {
  23863. return null;
  23864. }
  23865. //delegate getLocation method to the
  23866. //drag and drop target.
  23867. if (oDD.getRegion) {
  23868. return oDD.getRegion();
  23869. }
  23870. var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
  23871. try {
  23872. pos= Ext.Element.getXY(el);
  23873. } catch (e) { }
  23874. if (!pos) {
  23875. return null;
  23876. }
  23877. x1 = pos[0];
  23878. x2 = x1 + el.offsetWidth;
  23879. y1 = pos[1];
  23880. y2 = y1 + el.offsetHeight;
  23881. t = y1 - oDD.padding[0];
  23882. r = x2 + oDD.padding[1];
  23883. b = y2 + oDD.padding[2];
  23884. l = x1 - oDD.padding[3];
  23885. return Ext.create('Ext.util.Region', t, r, b, l);
  23886. },
  23887. /**
  23888. * Checks the cursor location to see if it over the target
  23889. * @method isOverTarget
  23890. * @param {Ext.util.Point} pt The point to evaluate
  23891. * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
  23892. * @return {Boolean} true if the mouse is over the target
  23893. * @private
  23894. */
  23895. isOverTarget: function(pt, oTarget, intersect) {
  23896. // use cache if available
  23897. var loc = this.locationCache[oTarget.id];
  23898. if (!loc || !this.useCache) {
  23899. loc = this.getLocation(oTarget);
  23900. this.locationCache[oTarget.id] = loc;
  23901. }
  23902. if (!loc) {
  23903. return false;
  23904. }
  23905. oTarget.cursorIsOver = loc.contains( pt );
  23906. // DragDrop is using this as a sanity check for the initial mousedown
  23907. // in this case we are done. In POINT mode, if the drag obj has no
  23908. // contraints, we are also done. Otherwise we need to evaluate the
  23909. // location of the target as related to the actual location of the
  23910. // dragged element.
  23911. var dc = this.dragCurrent;
  23912. if (!dc || !dc.getTargetCoord ||
  23913. (!intersect && !dc.constrainX && !dc.constrainY)) {
  23914. return oTarget.cursorIsOver;
  23915. }
  23916. oTarget.overlap = null;
  23917. // Get the current location of the drag element, this is the
  23918. // location of the mouse event less the delta that represents
  23919. // where the original mousedown happened on the element. We
  23920. // need to consider constraints and ticks as well.
  23921. var pos = dc.getTargetCoord(pt.x, pt.y);
  23922. var el = dc.getDragEl();
  23923. var curRegion = Ext.create('Ext.util.Region', pos.y,
  23924. pos.x + el.offsetWidth,
  23925. pos.y + el.offsetHeight,
  23926. pos.x );
  23927. var overlap = curRegion.intersect(loc);
  23928. if (overlap) {
  23929. oTarget.overlap = overlap;
  23930. return (intersect) ? true : oTarget.cursorIsOver;
  23931. } else {
  23932. return false;
  23933. }
  23934. },
  23935. /**
  23936. * unload event handler
  23937. * @method _onUnload
  23938. * @private
  23939. */
  23940. _onUnload: function(e, me) {
  23941. Ext.dd.DragDropManager.unregAll();
  23942. },
  23943. /**
  23944. * Cleans up the drag and drop events and objects.
  23945. * @method unregAll
  23946. * @private
  23947. */
  23948. unregAll: function() {
  23949. if (this.dragCurrent) {
  23950. this.stopDrag();
  23951. this.dragCurrent = null;
  23952. }
  23953. this._execOnAll("unreg", []);
  23954. for (var i in this.elementCache) {
  23955. delete this.elementCache[i];
  23956. }
  23957. this.elementCache = {};
  23958. this.ids = {};
  23959. },
  23960. /**
  23961. * A cache of DOM elements
  23962. * @property elementCache
  23963. * @private
  23964. */
  23965. elementCache: {},
  23966. /**
  23967. * Get the wrapper for the DOM element specified
  23968. * @method getElWrapper
  23969. * @param {String} id the id of the element to get
  23970. * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
  23971. * @private
  23972. * @deprecated This wrapper isn't that useful
  23973. */
  23974. getElWrapper: function(id) {
  23975. var oWrapper = this.elementCache[id];
  23976. if (!oWrapper || !oWrapper.el) {
  23977. oWrapper = this.elementCache[id] =
  23978. new this.ElementWrapper(Ext.getDom(id));
  23979. }
  23980. return oWrapper;
  23981. },
  23982. /**
  23983. * Returns the actual DOM element
  23984. * @method getElement
  23985. * @param {String} id the id of the elment to get
  23986. * @return {Object} The element
  23987. * @deprecated use Ext.lib.Ext.getDom instead
  23988. */
  23989. getElement: function(id) {
  23990. return Ext.getDom(id);
  23991. },
  23992. /**
  23993. * Returns the style property for the DOM element (i.e.,
  23994. * document.getElById(id).style)
  23995. * @method getCss
  23996. * @param {String} id the id of the elment to get
  23997. * @return {Object} The style property of the element
  23998. */
  23999. getCss: function(id) {
  24000. var el = Ext.getDom(id);
  24001. return (el) ? el.style : null;
  24002. },
  24003. /**
  24004. * @class Ext.dd.DragDropManager.ElementWrapper
  24005. * Inner class for cached elements
  24006. * @private
  24007. * @deprecated
  24008. */
  24009. ElementWrapper: function(el) {
  24010. /**
  24011. * The element
  24012. * @property el
  24013. */
  24014. this.el = el || null;
  24015. /**
  24016. * The element id
  24017. * @property id
  24018. */
  24019. this.id = this.el && el.id;
  24020. /**
  24021. * A reference to the style property
  24022. * @property css
  24023. */
  24024. this.css = this.el && el.style;
  24025. },
  24026. // The DragDropManager class continues
  24027. /** @class Ext.dd.DragDropManager */
  24028. /**
  24029. * Returns the X position of an html element
  24030. * @param {HTMLElement} el the element for which to get the position
  24031. * @return {Number} the X coordinate
  24032. */
  24033. getPosX: function(el) {
  24034. return Ext.Element.getX(el);
  24035. },
  24036. /**
  24037. * Returns the Y position of an html element
  24038. * @param {HTMLElement} el the element for which to get the position
  24039. * @return {Number} the Y coordinate
  24040. */
  24041. getPosY: function(el) {
  24042. return Ext.Element.getY(el);
  24043. },
  24044. /**
  24045. * Swap two nodes. In IE, we use the native method, for others we
  24046. * emulate the IE behavior
  24047. * @param {HTMLElement} n1 the first node to swap
  24048. * @param {HTMLElement} n2 the other node to swap
  24049. */
  24050. swapNode: function(n1, n2) {
  24051. if (n1.swapNode) {
  24052. n1.swapNode(n2);
  24053. } else {
  24054. var p = n2.parentNode;
  24055. var s = n2.nextSibling;
  24056. if (s == n1) {
  24057. p.insertBefore(n1, n2);
  24058. } else if (n2 == n1.nextSibling) {
  24059. p.insertBefore(n2, n1);
  24060. } else {
  24061. n1.parentNode.replaceChild(n2, n1);
  24062. p.insertBefore(n1, s);
  24063. }
  24064. }
  24065. },
  24066. /**
  24067. * Returns the current scroll position
  24068. * @private
  24069. */
  24070. getScroll: function () {
  24071. var doc = window.document,
  24072. docEl = doc.documentElement,
  24073. body = doc.body,
  24074. top = 0,
  24075. left = 0;
  24076. if (Ext.isGecko4) {
  24077. top = window.scrollYOffset;
  24078. left = window.scrollXOffset;
  24079. } else {
  24080. if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
  24081. top = docEl.scrollTop;
  24082. left = docEl.scrollLeft;
  24083. } else if (body) {
  24084. top = body.scrollTop;
  24085. left = body.scrollLeft;
  24086. }
  24087. }
  24088. return {
  24089. top: top,
  24090. left: left
  24091. };
  24092. },
  24093. /**
  24094. * Returns the specified element style property
  24095. * @param {HTMLElement} el the element
  24096. * @param {String} styleProp the style property
  24097. * @return {String} The value of the style property
  24098. */
  24099. getStyle: function(el, styleProp) {
  24100. return Ext.fly(el).getStyle(styleProp);
  24101. },
  24102. /**
  24103. * Gets the scrollTop
  24104. * @return {Number} the document's scrollTop
  24105. */
  24106. getScrollTop: function () {
  24107. return this.getScroll().top;
  24108. },
  24109. /**
  24110. * Gets the scrollLeft
  24111. * @return {Number} the document's scrollTop
  24112. */
  24113. getScrollLeft: function () {
  24114. return this.getScroll().left;
  24115. },
  24116. /**
  24117. * Sets the x/y position of an element to the location of the
  24118. * target element.
  24119. * @param {HTMLElement} moveEl The element to move
  24120. * @param {HTMLElement} targetEl The position reference element
  24121. */
  24122. moveToEl: function (moveEl, targetEl) {
  24123. var aCoord = Ext.Element.getXY(targetEl);
  24124. Ext.Element.setXY(moveEl, aCoord);
  24125. },
  24126. /**
  24127. * Numeric array sort function
  24128. * @param {Number} a
  24129. * @param {Number} b
  24130. * @returns {Number} positive, negative or 0
  24131. */
  24132. numericSort: function(a, b) {
  24133. return (a - b);
  24134. },
  24135. /**
  24136. * Internal counter
  24137. * @property {Number} _timeoutCount
  24138. * @private
  24139. */
  24140. _timeoutCount: 0,
  24141. /**
  24142. * Trying to make the load order less important. Without this we get
  24143. * an error if this file is loaded before the Event Utility.
  24144. * @private
  24145. */
  24146. _addListeners: function() {
  24147. if ( document ) {
  24148. this._onLoad();
  24149. } else {
  24150. if (this._timeoutCount > 2000) {
  24151. } else {
  24152. setTimeout(this._addListeners, 10);
  24153. if (document && document.body) {
  24154. this._timeoutCount += 1;
  24155. }
  24156. }
  24157. }
  24158. },
  24159. /**
  24160. * Recursively searches the immediate parent and all child nodes for
  24161. * the handle element in order to determine wheter or not it was
  24162. * clicked.
  24163. * @param {HTMLElement} node the html element to inspect
  24164. */
  24165. handleWasClicked: function(node, id) {
  24166. if (this.isHandle(id, node.id)) {
  24167. return true;
  24168. } else {
  24169. // check to see if this is a text node child of the one we want
  24170. var p = node.parentNode;
  24171. while (p) {
  24172. if (this.isHandle(id, p.id)) {
  24173. return true;
  24174. } else {
  24175. p = p.parentNode;
  24176. }
  24177. }
  24178. }
  24179. return false;
  24180. }
  24181. }, function() {
  24182. this._addListeners();
  24183. });
  24184. /**
  24185. * @class Ext.layout.container.Box
  24186. * @extends Ext.layout.container.Container
  24187. * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
  24188. */
  24189. Ext.define('Ext.layout.container.Box', {
  24190. /* Begin Definitions */
  24191. alias: ['layout.box'],
  24192. extend: 'Ext.layout.container.Container',
  24193. alternateClassName: 'Ext.layout.BoxLayout',
  24194. requires: [
  24195. 'Ext.layout.container.boxOverflow.None',
  24196. 'Ext.layout.container.boxOverflow.Menu',
  24197. 'Ext.layout.container.boxOverflow.Scroller',
  24198. 'Ext.util.Format',
  24199. 'Ext.dd.DragDropManager'
  24200. ],
  24201. /* End Definitions */
  24202. /**
  24203. * @cfg {Boolean/Number/Object} animate
  24204. * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
  24205. * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
  24206. * <p>May be set as a property at any time.</p>
  24207. */
  24208. /**
  24209. * @cfg {Object} defaultMargins
  24210. * <p>If the individual contained items do not have a <tt>margins</tt>
  24211. * property specified or margin specified via CSS, the default margins from this property will be
  24212. * applied to each item.</p>
  24213. * <br><p>This property may be specified as an object containing margins
  24214. * to apply in the format:</p><pre><code>
  24215. {
  24216. top: (top margin),
  24217. right: (right margin),
  24218. bottom: (bottom margin),
  24219. left: (left margin)
  24220. }</code></pre>
  24221. * <p>This property may also be specified as a string containing
  24222. * space-separated, numeric margin values. The order of the sides associated
  24223. * with each value matches the way CSS processes margin values:</p>
  24224. * <div class="mdetail-params"><ul>
  24225. * <li>If there is only one value, it applies to all sides.</li>
  24226. * <li>If there are two values, the top and bottom borders are set to the
  24227. * first value and the right and left are set to the second.</li>
  24228. * <li>If there are three values, the top is set to the first value, the left
  24229. * and right are set to the second, and the bottom is set to the third.</li>
  24230. * <li>If there are four values, they apply to the top, right, bottom, and
  24231. * left, respectively.</li>
  24232. * </ul></div>
  24233. */
  24234. defaultMargins: {
  24235. top: 0,
  24236. right: 0,
  24237. bottom: 0,
  24238. left: 0
  24239. },
  24240. /**
  24241. * @cfg {String} padding
  24242. * <p>Sets the padding to be applied to all child items managed by this layout.</p>
  24243. * <p>This property must be specified as a string containing
  24244. * space-separated, numeric padding values. The order of the sides associated
  24245. * with each value matches the way CSS processes padding values:</p>
  24246. * <div class="mdetail-params"><ul>
  24247. * <li>If there is only one value, it applies to all sides.</li>
  24248. * <li>If there are two values, the top and bottom borders are set to the
  24249. * first value and the right and left are set to the second.</li>
  24250. * <li>If there are three values, the top is set to the first value, the left
  24251. * and right are set to the second, and the bottom is set to the third.</li>
  24252. * <li>If there are four values, they apply to the top, right, bottom, and
  24253. * left, respectively.</li>
  24254. * </ul></div>
  24255. */
  24256. padding: '0',
  24257. // documented in subclasses
  24258. pack: 'start',
  24259. /**
  24260. * @cfg {String} pack
  24261. * Controls how the child items of the container are packed together. Acceptable configuration values
  24262. * for this property are:
  24263. * <div class="mdetail-params"><ul>
  24264. * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
  24265. * <b>left</b> side of container</div></li>
  24266. * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
  24267. * <b>mid-width</b> of container</div></li>
  24268. * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
  24269. * side of container</div></li>
  24270. * </ul></div>
  24271. */
  24272. /**
  24273. * @cfg {Number} flex
  24274. * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
  24275. * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
  24276. * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
  24277. * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
  24278. * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
  24279. */
  24280. type: 'box',
  24281. scrollOffset: 0,
  24282. itemCls: Ext.baseCSSPrefix + 'box-item',
  24283. targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
  24284. innerCls: Ext.baseCSSPrefix + 'box-inner',
  24285. bindToOwnerCtContainer: true,
  24286. // availableSpaceOffset is used to adjust the availableWidth, typically used
  24287. // to reserve space for a scrollbar
  24288. availableSpaceOffset: 0,
  24289. // whether or not to reserve the availableSpaceOffset in layout calculations
  24290. reserveOffset: true,
  24291. /**
  24292. * @cfg {Boolean} shrinkToFit
  24293. * True (the default) to allow fixed size components to shrink (limited to their
  24294. * minimum size) to avoid overflow. False to preserve fixed sizes even if they cause
  24295. * overflow.
  24296. */
  24297. shrinkToFit: true,
  24298. /**
  24299. * @cfg {Boolean} clearInnerCtOnLayout
  24300. */
  24301. clearInnerCtOnLayout: false,
  24302. flexSortFn: function (a, b) {
  24303. var maxParallelPrefix = 'max' + this.parallelPrefixCap,
  24304. infiniteValue = Infinity;
  24305. a = a.component[maxParallelPrefix] || infiniteValue;
  24306. b = b.component[maxParallelPrefix] || infiniteValue;
  24307. // IE 6/7 Don't like Infinity - Infinity...
  24308. if (!isFinite(a) && !isFinite(b)) {
  24309. return false;
  24310. }
  24311. return a - b;
  24312. },
  24313. // Sort into *descending* order.
  24314. minSizeSortFn: function(a, b) {
  24315. return b.available - a.available;
  24316. },
  24317. constructor: function(config) {
  24318. var me = this;
  24319. me.callParent(arguments);
  24320. // The sort function needs access to properties in this, so must be bound.
  24321. me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
  24322. me.initOverflowHandler();
  24323. },
  24324. /**
  24325. * @private
  24326. * Returns the current size and positioning of the passed child item.
  24327. * @param {Ext.Component} child The child Component to calculate the box for
  24328. * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
  24329. */
  24330. getChildBox: function(child) {
  24331. child = child.el || this.owner.getComponent(child).el;
  24332. var size = child.getBox(false, true);
  24333. return {
  24334. left: size.left,
  24335. top: size.top,
  24336. width: size.width,
  24337. height: size.height
  24338. };
  24339. },
  24340. /**
  24341. * @private
  24342. * Calculates the size and positioning of the passed child item.
  24343. * @param {Ext.Component} child The child Component to calculate the box for
  24344. * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
  24345. */
  24346. calculateChildBox: function(child) {
  24347. var me = this,
  24348. boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
  24349. ln = boxes.length,
  24350. i = 0;
  24351. child = me.owner.getComponent(child);
  24352. for (; i < ln; i++) {
  24353. if (boxes[i].component === child) {
  24354. return boxes[i];
  24355. }
  24356. }
  24357. },
  24358. /**
  24359. * @private
  24360. * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
  24361. * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
  24362. * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
  24363. * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
  24364. * @param {Object} targetSize Object containing target size and height
  24365. * @return {Object} Object containing box measurements for each child, plus meta data
  24366. */
  24367. calculateChildBoxes: function(visibleItems, targetSize) {
  24368. var me = this,
  24369. math = Math,
  24370. mmax = math.max,
  24371. infiniteValue = Infinity,
  24372. undefinedValue,
  24373. parallelPrefix = me.parallelPrefix,
  24374. parallelPrefixCap = me.parallelPrefixCap,
  24375. perpendicularPrefix = me.perpendicularPrefix,
  24376. perpendicularPrefixCap = me.perpendicularPrefixCap,
  24377. parallelMinString = 'min' + parallelPrefixCap,
  24378. perpendicularMinString = 'min' + perpendicularPrefixCap,
  24379. perpendicularMaxString = 'max' + perpendicularPrefixCap,
  24380. parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
  24381. perpendicularSize = targetSize[perpendicularPrefix],
  24382. padding = me.padding,
  24383. parallelOffset = padding[me.parallelBefore],
  24384. paddingParallel = parallelOffset + padding[me.parallelAfter],
  24385. perpendicularOffset = padding[me.perpendicularLeftTop],
  24386. paddingPerpendicular = perpendicularOffset + padding[me.perpendicularRightBottom],
  24387. availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
  24388. innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
  24389. isStart = me.pack == 'start',
  24390. isCenter = me.pack == 'center',
  24391. isEnd = me.pack == 'end',
  24392. constrain = Ext.Number.constrain,
  24393. visibleCount = visibleItems.length,
  24394. nonFlexSize = 0,
  24395. totalFlex = 0,
  24396. desiredSize = 0,
  24397. minimumSize = 0,
  24398. maxSize = 0,
  24399. boxes = [],
  24400. minSizes = [],
  24401. calculatedWidth,
  24402. i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
  24403. tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
  24404. flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
  24405. perpendicularMargins, stretchSize;
  24406. //gather the total flex of all flexed items and the width taken up by fixed width items
  24407. for (i = 0; i < visibleCount; i++) {
  24408. child = visibleItems[i];
  24409. childPerpendicular = child[perpendicularPrefix];
  24410. if (!child.flex || !(me.align == 'stretch' || me.align == 'stretchmax')) {
  24411. if (child.componentLayout.initialized !== true) {
  24412. me.layoutItem(child);
  24413. }
  24414. }
  24415. childMargins = child.margins;
  24416. parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
  24417. // Create the box description object for this child item.
  24418. tmpObj = {
  24419. component: child,
  24420. margins: childMargins
  24421. };
  24422. // flex and not 'auto' width
  24423. if (child.flex) {
  24424. totalFlex += child.flex;
  24425. childParallel = undefinedValue;
  24426. }
  24427. // Not flexed or 'auto' width or undefined width
  24428. else {
  24429. if (!(child[parallelPrefix] && childPerpendicular)) {
  24430. childSize = child.getSize();
  24431. }
  24432. childParallel = child[parallelPrefix] || childSize[parallelPrefix];
  24433. childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
  24434. }
  24435. nonFlexSize += parallelMargins + (childParallel || 0);
  24436. desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
  24437. minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
  24438. // Max height for align - force layout of non-laid out subcontainers without a numeric height
  24439. if (typeof childPerpendicular != 'number') {
  24440. // Clear any static sizing and revert to flow so we can get a proper measurement
  24441. // child['set' + perpendicularPrefixCap](null);
  24442. childPerpendicular = child['get' + perpendicularPrefixCap]();
  24443. }
  24444. // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
  24445. // Ensure that the tracked maximum perpendicular size takes into account child min[Width|Height] settings!
  24446. maxSize = mmax(maxSize, mmax(childPerpendicular, child[perpendicularMinString]||0) + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
  24447. tmpObj[parallelPrefix] = childParallel || undefinedValue;
  24448. tmpObj.dirtySize = child.componentLayout.lastComponentSize ? (tmpObj[parallelPrefix] !== child.componentLayout.lastComponentSize[parallelPrefix]) : false;
  24449. tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
  24450. boxes.push(tmpObj);
  24451. }
  24452. // Only calculate parallel overflow indicators if we are not auto sizing
  24453. if (!me.autoSize) {
  24454. shortfall = desiredSize - parallelSize;
  24455. tooNarrow = minimumSize > parallelSize;
  24456. }
  24457. //the space available to the flexed items
  24458. availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
  24459. if (tooNarrow) {
  24460. for (i = 0; i < visibleCount; i++) {
  24461. box = boxes[i];
  24462. minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
  24463. box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
  24464. box[parallelPrefix] = minSize;
  24465. }
  24466. }
  24467. else {
  24468. //all flexed items should be sized to their minimum size, other items should be shrunk down until
  24469. //the shortfall has been accounted for
  24470. if (shortfall > 0) {
  24471. /*
  24472. * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
  24473. * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
  24474. * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
  24475. */
  24476. for (i = 0; i < visibleCount; i++) {
  24477. item = visibleItems[i];
  24478. minSize = item[parallelMinString] || 0;
  24479. //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
  24480. //shrunk to their minSize because they're flexible and should be the first to lose size
  24481. if (item.flex) {
  24482. box = boxes[i];
  24483. box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
  24484. box[parallelPrefix] = minSize;
  24485. } else if (me.shrinkToFit) {
  24486. minSizes.push({
  24487. minSize: minSize,
  24488. available: boxes[i][parallelPrefix] - minSize,
  24489. index: i
  24490. });
  24491. }
  24492. }
  24493. //sort by descending amount of width remaining before minWidth is reached
  24494. Ext.Array.sort(minSizes, me.minSizeSortFn);
  24495. /*
  24496. * Distribute the shortfall (difference between total desired size of all items and actual size available)
  24497. * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
  24498. * smallest difference between their size and minSize first, so that if reducing the size by the average
  24499. * amount would make that item less than its minSize, we carry the remainder over to the next item.
  24500. */
  24501. for (i = 0, length = minSizes.length; i < length; i++) {
  24502. itemIndex = minSizes[i].index;
  24503. if (itemIndex == undefinedValue) {
  24504. continue;
  24505. }
  24506. item = visibleItems[itemIndex];
  24507. minSize = minSizes[i].minSize;
  24508. box = boxes[itemIndex];
  24509. oldSize = box[parallelPrefix];
  24510. newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
  24511. reduction = oldSize - newSize;
  24512. box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
  24513. box[parallelPrefix] = newSize;
  24514. shortfall -= reduction;
  24515. }
  24516. tooNarrow = (shortfall > 0);
  24517. }
  24518. else {
  24519. remainingSpace = availableSpace;
  24520. remainingFlex = totalFlex;
  24521. flexedBoxes = [];
  24522. // Create an array containing *just the flexed boxes* for allocation of remainingSpace
  24523. for (i = 0; i < visibleCount; i++) {
  24524. child = visibleItems[i];
  24525. if (isStart && child.flex) {
  24526. flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
  24527. }
  24528. }
  24529. // The flexed boxes need to be sorted in ascending order of maxSize to work properly
  24530. // so that unallocated space caused by maxWidth being less than flexed width
  24531. // can be reallocated to subsequent flexed boxes.
  24532. Ext.Array.sort(flexedBoxes, me.flexSortFn);
  24533. // Calculate the size of each flexed item, and attempt to set it.
  24534. for (i = 0; i < flexedBoxes.length; i++) {
  24535. calcs = flexedBoxes[i];
  24536. child = calcs.component;
  24537. childMargins = calcs.margins;
  24538. flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
  24539. // Implement maxSize and minSize check
  24540. flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
  24541. // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
  24542. remainingSpace -= flexedSize;
  24543. remainingFlex -= child.flex;
  24544. calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
  24545. calcs[parallelPrefix] = flexedSize;
  24546. }
  24547. }
  24548. }
  24549. if (isCenter) {
  24550. parallelOffset += availableSpace / 2;
  24551. }
  24552. else if (isEnd) {
  24553. parallelOffset += availableSpace;
  24554. }
  24555. // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
  24556. // Older Microsoft browsers do not size a position:absolute element's width to match its content.
  24557. // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
  24558. // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
  24559. if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
  24560. calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
  24561. if (me.owner.frameSize) {
  24562. calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
  24563. }
  24564. // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
  24565. availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
  24566. }
  24567. //finally, calculate the left and top position of each item
  24568. for (i = 0; i < visibleCount; i++) {
  24569. child = visibleItems[i];
  24570. calcs = boxes[i];
  24571. childMargins = calcs.margins;
  24572. perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
  24573. // Advance past the "before" margin
  24574. parallelOffset += childMargins[me.parallelBefore];
  24575. calcs[me.parallelBefore] = parallelOffset;
  24576. calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
  24577. if (me.align == 'stretch') {
  24578. stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
  24579. calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
  24580. calcs[perpendicularPrefix] = stretchSize;
  24581. }
  24582. else if (me.align == 'stretchmax') {
  24583. stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
  24584. calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
  24585. calcs[perpendicularPrefix] = stretchSize;
  24586. }
  24587. else if (me.align == me.alignCenteringString) {
  24588. // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
  24589. // the size to yield the space available to center within.
  24590. // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
  24591. diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
  24592. if (diff > 0) {
  24593. calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
  24594. }
  24595. }
  24596. // Advance past the box size and the "after" margin
  24597. parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
  24598. }
  24599. return {
  24600. boxes: boxes,
  24601. meta : {
  24602. calculatedWidth: calculatedWidth,
  24603. maxSize: maxSize,
  24604. nonFlexSize: nonFlexSize,
  24605. desiredSize: desiredSize,
  24606. minimumSize: minimumSize,
  24607. shortfall: shortfall,
  24608. tooNarrow: tooNarrow
  24609. }
  24610. };
  24611. },
  24612. onRemove: function(comp){
  24613. this.callParent(arguments);
  24614. if (this.overflowHandler) {
  24615. this.overflowHandler.onRemove(comp);
  24616. }
  24617. },
  24618. /**
  24619. * @private
  24620. */
  24621. initOverflowHandler: function() {
  24622. var handler = this.overflowHandler;
  24623. if (typeof handler == 'string') {
  24624. handler = {
  24625. type: handler
  24626. };
  24627. }
  24628. var handlerType = 'None';
  24629. if (handler && handler.type !== undefined) {
  24630. handlerType = handler.type;
  24631. }
  24632. var constructor = Ext.layout.container.boxOverflow[handlerType];
  24633. if (constructor[this.type]) {
  24634. constructor = constructor[this.type];
  24635. }
  24636. this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
  24637. },
  24638. /**
  24639. * @private
  24640. * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
  24641. * when laying out
  24642. */
  24643. onLayout: function() {
  24644. this.callParent();
  24645. // Clear the innerCt size so it doesn't influence the child items.
  24646. if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
  24647. this.innerCt.setSize(null, null);
  24648. }
  24649. var me = this,
  24650. targetSize = me.getLayoutTargetSize(),
  24651. items = me.getVisibleItems(),
  24652. calcs = me.calculateChildBoxes(items, targetSize),
  24653. boxes = calcs.boxes,
  24654. meta = calcs.meta,
  24655. handler, method, results;
  24656. if (me.autoSize && calcs.meta.desiredSize) {
  24657. targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
  24658. }
  24659. //invoke the overflow handler, if one is configured
  24660. if (meta.shortfall > 0) {
  24661. handler = me.overflowHandler;
  24662. method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
  24663. results = handler[method](calcs, targetSize);
  24664. if (results) {
  24665. if (results.targetSize) {
  24666. targetSize = results.targetSize;
  24667. }
  24668. if (results.recalculate) {
  24669. items = me.getVisibleItems();
  24670. calcs = me.calculateChildBoxes(items, targetSize);
  24671. boxes = calcs.boxes;
  24672. }
  24673. }
  24674. } else {
  24675. me.overflowHandler.clearOverflow();
  24676. }
  24677. /**
  24678. * @private
  24679. * @property layoutTargetLastSize
  24680. * @type Object
  24681. * Private cache of the last measured size of the layout target. This should never be used except by
  24682. * BoxLayout subclasses during their onLayout run.
  24683. */
  24684. me.layoutTargetLastSize = targetSize;
  24685. /**
  24686. * @private
  24687. * @property childBoxCache
  24688. * @type Array
  24689. * Array of the last calculated height, width, top and left positions of each visible rendered component
  24690. * within the Box layout.
  24691. */
  24692. me.childBoxCache = calcs;
  24693. me.updateInnerCtSize(targetSize, calcs);
  24694. me.updateChildBoxes(boxes);
  24695. me.handleTargetOverflow(targetSize);
  24696. },
  24697. animCallback: Ext.emptyFn,
  24698. /**
  24699. * Resizes and repositions each child component
  24700. * @param {Object[]} boxes The box measurements
  24701. */
  24702. updateChildBoxes: function(boxes) {
  24703. var me = this,
  24704. i = 0,
  24705. length = boxes.length,
  24706. animQueue = [],
  24707. dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
  24708. oldBox, newBox, changed, comp, boxAnim, animCallback;
  24709. for (; i < length; i++) {
  24710. newBox = boxes[i];
  24711. comp = newBox.component;
  24712. // If a Component is being drag/dropped, skip positioning it.
  24713. // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
  24714. if (dd && (dd.getDragEl() === comp.el.dom)) {
  24715. continue;
  24716. }
  24717. changed = false;
  24718. oldBox = me.getChildBox(comp);
  24719. // If we are animating, we build up an array of Anim config objects, one for each
  24720. // child Component which has any changed box properties. Those with unchanged
  24721. // properties are not animated.
  24722. if (me.animate) {
  24723. // Animate may be a config object containing callback.
  24724. animCallback = me.animate.callback || me.animate;
  24725. boxAnim = {
  24726. layoutAnimation: true, // Component Target handler must use set*Calculated*Size
  24727. target: comp,
  24728. from: {},
  24729. to: {},
  24730. listeners: {}
  24731. };
  24732. // Only set from and to properties when there's a change.
  24733. // Perform as few Component setter methods as possible.
  24734. // Temporarily set the property values that we are not animating
  24735. // so that doComponentLayout does not auto-size them.
  24736. if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
  24737. changed = true;
  24738. // boxAnim.from.width = oldBox.width;
  24739. boxAnim.to.width = newBox.width;
  24740. }
  24741. if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
  24742. changed = true;
  24743. // boxAnim.from.height = oldBox.height;
  24744. boxAnim.to.height = newBox.height;
  24745. }
  24746. if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
  24747. changed = true;
  24748. // boxAnim.from.left = oldBox.left;
  24749. boxAnim.to.left = newBox.left;
  24750. }
  24751. if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
  24752. changed = true;
  24753. // boxAnim.from.top = oldBox.top;
  24754. boxAnim.to.top = newBox.top;
  24755. }
  24756. if (changed) {
  24757. animQueue.push(boxAnim);
  24758. }
  24759. } else {
  24760. if (newBox.dirtySize) {
  24761. if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
  24762. me.setItemSize(comp, newBox.width, newBox.height);
  24763. }
  24764. }
  24765. // Don't set positions to NaN
  24766. if (isNaN(newBox.left) || isNaN(newBox.top)) {
  24767. continue;
  24768. }
  24769. comp.setPosition(newBox.left, newBox.top);
  24770. }
  24771. }
  24772. // Kick off any queued animations
  24773. length = animQueue.length;
  24774. if (length) {
  24775. // A function which cleans up when a Component's animation is done.
  24776. // The last one to finish calls the callback.
  24777. var afterAnimate = function(anim) {
  24778. // When we've animated all changed boxes into position, clear our busy flag and call the callback.
  24779. length -= 1;
  24780. if (!length) {
  24781. me.animCallback(anim);
  24782. me.layoutBusy = false;
  24783. if (Ext.isFunction(animCallback)) {
  24784. animCallback();
  24785. }
  24786. }
  24787. };
  24788. var beforeAnimate = function() {
  24789. me.layoutBusy = true;
  24790. };
  24791. // Start each box animation off
  24792. for (i = 0, length = animQueue.length; i < length; i++) {
  24793. boxAnim = animQueue[i];
  24794. // Clean up the Component after. Clean up the *layout* after the last animation finishes
  24795. boxAnim.listeners.afteranimate = afterAnimate;
  24796. // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
  24797. if (!i) {
  24798. boxAnim.listeners.beforeanimate = beforeAnimate;
  24799. }
  24800. if (me.animate.duration) {
  24801. boxAnim.duration = me.animate.duration;
  24802. }
  24803. comp = boxAnim.target;
  24804. delete boxAnim.target;
  24805. // Stop any currently running animation
  24806. comp.stopAnimation();
  24807. comp.animate(boxAnim);
  24808. }
  24809. }
  24810. },
  24811. /**
  24812. * @private
  24813. * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
  24814. * to make sure all child items fit within it. We call this before sizing the children because if our child
  24815. * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
  24816. * again immediately afterwards, giving a performance hit.
  24817. * Subclasses should provide an implementation.
  24818. * @param {Object} currentSize The current height and width of the innerCt
  24819. * @param {Object} calculations The new box calculations of all items to be laid out
  24820. */
  24821. updateInnerCtSize: function(tSize, calcs) {
  24822. var me = this,
  24823. mmax = Math.max,
  24824. align = me.align,
  24825. padding = me.padding,
  24826. width = tSize.width,
  24827. height = tSize.height,
  24828. meta = calcs.meta,
  24829. innerCtWidth,
  24830. innerCtHeight;
  24831. if (me.direction == 'horizontal') {
  24832. innerCtWidth = width;
  24833. innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
  24834. if (align == 'stretch') {
  24835. innerCtHeight = height;
  24836. }
  24837. else if (align == 'middle') {
  24838. innerCtHeight = mmax(height, innerCtHeight);
  24839. }
  24840. } else {
  24841. innerCtHeight = height;
  24842. innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
  24843. if (align == 'stretch') {
  24844. innerCtWidth = width;
  24845. }
  24846. else if (align == 'center') {
  24847. innerCtWidth = mmax(width, innerCtWidth);
  24848. }
  24849. }
  24850. me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
  24851. // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
  24852. // then, if the Component has not assumed the size of its content, set it to do so.
  24853. if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
  24854. me.owner.el.setWidth(meta.calculatedWidth);
  24855. }
  24856. if (me.innerCt.dom.scrollTop) {
  24857. me.innerCt.dom.scrollTop = 0;
  24858. }
  24859. },
  24860. /**
  24861. * @private
  24862. * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
  24863. * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
  24864. * target. Having a Box layout inside such a target is therefore not recommended.
  24865. * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
  24866. * @param {Ext.container.Container} container The container
  24867. * @param {Ext.Element} target The target element
  24868. * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
  24869. */
  24870. handleTargetOverflow: function(previousTargetSize) {
  24871. var target = this.getTarget(),
  24872. overflow = target.getStyle('overflow'),
  24873. newTargetSize;
  24874. if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
  24875. newTargetSize = this.getLayoutTargetSize();
  24876. if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
  24877. this.adjustmentPass = true;
  24878. this.onLayout();
  24879. return true;
  24880. }
  24881. }
  24882. delete this.adjustmentPass;
  24883. },
  24884. // private
  24885. isValidParent : function(item, target, position) {
  24886. // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
  24887. // We only care whether the item is a direct child of the innerCt element.
  24888. var itemEl = item.el ? item.el.dom : Ext.getDom(item);
  24889. return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
  24890. },
  24891. // Overridden method from AbstractContainer.
  24892. // Used in the base AbstractLayout.beforeLayout method to render all items into.
  24893. getRenderTarget: function() {
  24894. if (!this.innerCt) {
  24895. // the innerCt prevents wrapping and shuffling while the container is resizing
  24896. this.innerCt = this.getTarget().createChild({
  24897. cls: this.innerCls,
  24898. role: 'presentation'
  24899. });
  24900. this.padding = Ext.util.Format.parseBox(this.padding);
  24901. }
  24902. return this.innerCt;
  24903. },
  24904. // private
  24905. renderItem: function(item, target) {
  24906. this.callParent(arguments);
  24907. var me = this,
  24908. itemEl = item.getEl(),
  24909. style = itemEl.dom.style,
  24910. margins = item.margins || item.margin;
  24911. // Parse the item's margin/margins specification
  24912. if (margins) {
  24913. if (Ext.isString(margins) || Ext.isNumber(margins)) {
  24914. margins = Ext.util.Format.parseBox(margins);
  24915. } else {
  24916. Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
  24917. }
  24918. } else {
  24919. margins = Ext.apply({}, me.defaultMargins);
  24920. }
  24921. // Add any before/after CSS margins to the configured margins, and zero the CSS margins
  24922. margins.top += itemEl.getMargin('t');
  24923. margins.right += itemEl.getMargin('r');
  24924. margins.bottom += itemEl.getMargin('b');
  24925. margins.left += itemEl.getMargin('l');
  24926. margins.height = margins.top + margins.bottom;
  24927. margins.width = margins.left + margins.right;
  24928. style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
  24929. // Item must reference calculated margins.
  24930. item.margins = margins;
  24931. },
  24932. /**
  24933. * @private
  24934. */
  24935. destroy: function() {
  24936. Ext.destroy(this.innerCt, this.overflowHandler);
  24937. this.callParent(arguments);
  24938. }
  24939. });
  24940. /**
  24941. * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
  24942. * space between child items containing a numeric `flex` configuration.
  24943. *
  24944. * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
  24945. *
  24946. * @example
  24947. * Ext.create('Ext.Panel', {
  24948. * width: 500,
  24949. * height: 300,
  24950. * title: "HBoxLayout Panel",
  24951. * layout: {
  24952. * type: 'hbox',
  24953. * align: 'stretch'
  24954. * },
  24955. * renderTo: document.body,
  24956. * items: [{
  24957. * xtype: 'panel',
  24958. * title: 'Inner Panel One',
  24959. * flex: 2
  24960. * },{
  24961. * xtype: 'panel',
  24962. * title: 'Inner Panel Two',
  24963. * flex: 1
  24964. * },{
  24965. * xtype: 'panel',
  24966. * title: 'Inner Panel Three',
  24967. * flex: 1
  24968. * }]
  24969. * });
  24970. */
  24971. Ext.define('Ext.layout.container.HBox', {
  24972. /* Begin Definitions */
  24973. alias: ['layout.hbox'],
  24974. extend: 'Ext.layout.container.Box',
  24975. alternateClassName: 'Ext.layout.HBoxLayout',
  24976. /* End Definitions */
  24977. /**
  24978. * @cfg {String} align
  24979. * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
  24980. *
  24981. * - **top** : **Default** child items are aligned vertically at the **top** of the container
  24982. * - **middle** : child items are aligned vertically in the **middle** of the container
  24983. * - **stretch** : child items are stretched vertically to fill the height of the container
  24984. * - **stretchmax** : child items are stretched vertically to the height of the largest item.
  24985. */
  24986. align: 'top', // top, middle, stretch, strechmax
  24987. //@private
  24988. alignCenteringString: 'middle',
  24989. type : 'hbox',
  24990. direction: 'horizontal',
  24991. // When creating an argument list to setSize, use this order
  24992. parallelSizeIndex: 0,
  24993. perpendicularSizeIndex: 1,
  24994. parallelPrefix: 'width',
  24995. parallelPrefixCap: 'Width',
  24996. parallelLT: 'l',
  24997. parallelRB: 'r',
  24998. parallelBefore: 'left',
  24999. parallelBeforeCap: 'Left',
  25000. parallelAfter: 'right',
  25001. parallelPosition: 'x',
  25002. perpendicularPrefix: 'height',
  25003. perpendicularPrefixCap: 'Height',
  25004. perpendicularLT: 't',
  25005. perpendicularRB: 'b',
  25006. perpendicularLeftTop: 'top',
  25007. perpendicularRightBottom: 'bottom',
  25008. perpendicularPosition: 'y',
  25009. configureItem: function(item) {
  25010. if (item.flex) {
  25011. item.layoutManagedWidth = 1;
  25012. } else {
  25013. item.layoutManagedWidth = 2;
  25014. }
  25015. if (this.align === 'stretch' || this.align === 'stretchmax') {
  25016. item.layoutManagedHeight = 1;
  25017. } else {
  25018. item.layoutManagedHeight = 2;
  25019. }
  25020. this.callParent(arguments);
  25021. }
  25022. });
  25023. /**
  25024. * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
  25025. * between child items containing a numeric `flex` configuration.
  25026. *
  25027. * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
  25028. *
  25029. * @example
  25030. * Ext.create('Ext.Panel', {
  25031. * width: 500,
  25032. * height: 400,
  25033. * title: "VBoxLayout Panel",
  25034. * layout: {
  25035. * type: 'vbox',
  25036. * align: 'center'
  25037. * },
  25038. * renderTo: document.body,
  25039. * items: [{
  25040. * xtype: 'panel',
  25041. * title: 'Inner Panel One',
  25042. * width: 250,
  25043. * flex: 2
  25044. * },
  25045. * {
  25046. * xtype: 'panel',
  25047. * title: 'Inner Panel Two',
  25048. * width: 250,
  25049. * flex: 4
  25050. * },
  25051. * {
  25052. * xtype: 'panel',
  25053. * title: 'Inner Panel Three',
  25054. * width: '50%',
  25055. * flex: 4
  25056. * }]
  25057. * });
  25058. */
  25059. Ext.define('Ext.layout.container.VBox', {
  25060. /* Begin Definitions */
  25061. alias: ['layout.vbox'],
  25062. extend: 'Ext.layout.container.Box',
  25063. alternateClassName: 'Ext.layout.VBoxLayout',
  25064. /* End Definitions */
  25065. /**
  25066. * @cfg {String} align
  25067. * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
  25068. *
  25069. * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
  25070. * - **center** : child items are aligned horizontally at the **mid-width** of the container
  25071. * - **stretch** : child items are stretched horizontally to fill the width of the container
  25072. * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
  25073. */
  25074. align : 'left', // left, center, stretch, strechmax
  25075. //@private
  25076. alignCenteringString: 'center',
  25077. type: 'vbox',
  25078. direction: 'vertical',
  25079. // When creating an argument list to setSize, use this order
  25080. parallelSizeIndex: 1,
  25081. perpendicularSizeIndex: 0,
  25082. parallelPrefix: 'height',
  25083. parallelPrefixCap: 'Height',
  25084. parallelLT: 't',
  25085. parallelRB: 'b',
  25086. parallelBefore: 'top',
  25087. parallelBeforeCap: 'Top',
  25088. parallelAfter: 'bottom',
  25089. parallelPosition: 'y',
  25090. perpendicularPrefix: 'width',
  25091. perpendicularPrefixCap: 'Width',
  25092. perpendicularLT: 'l',
  25093. perpendicularRB: 'r',
  25094. perpendicularLeftTop: 'left',
  25095. perpendicularRightBottom: 'right',
  25096. perpendicularPosition: 'x',
  25097. configureItem: function(item) {
  25098. if (item.flex) {
  25099. item.layoutManagedHeight = 1;
  25100. } else {
  25101. item.layoutManagedHeight = 2;
  25102. }
  25103. if (this.align === 'stretch' || this.align === 'stretchmax') {
  25104. item.layoutManagedWidth = 1;
  25105. } else {
  25106. item.layoutManagedWidth = 2;
  25107. }
  25108. this.callParent(arguments);
  25109. }
  25110. });
  25111. /**
  25112. * @class Ext.FocusManager
  25113. The FocusManager is responsible for globally:
  25114. 1. Managing component focus
  25115. 2. Providing basic keyboard navigation
  25116. 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
  25117. To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
  25118. deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();. The
  25119. FocusManager is disabled by default.
  25120. To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
  25121. Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
  25122. that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
  25123. call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
  25124. {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
  25125. * @singleton
  25126. * @author Jarred Nicholls <jarred@sencha.com>
  25127. * @docauthor Jarred Nicholls <jarred@sencha.com>
  25128. */
  25129. Ext.define('Ext.FocusManager', {
  25130. singleton: true,
  25131. alternateClassName: 'Ext.FocusMgr',
  25132. mixins: {
  25133. observable: 'Ext.util.Observable'
  25134. },
  25135. requires: [
  25136. 'Ext.ComponentManager',
  25137. 'Ext.ComponentQuery',
  25138. 'Ext.util.HashMap',
  25139. 'Ext.util.KeyNav'
  25140. ],
  25141. /**
  25142. * @property {Boolean} enabled
  25143. * Whether or not the FocusManager is currently enabled
  25144. */
  25145. enabled: false,
  25146. /**
  25147. * @property {Ext.Component} focusedCmp
  25148. * The currently focused component. Defaults to `undefined`.
  25149. */
  25150. focusElementCls: Ext.baseCSSPrefix + 'focus-element',
  25151. focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
  25152. /**
  25153. * @property {String[]} whitelist
  25154. * A list of xtypes that should ignore certain navigation input keys and
  25155. * allow for the default browser event/behavior. These input keys include:
  25156. *
  25157. * 1. Backspace
  25158. * 2. Delete
  25159. * 3. Left
  25160. * 4. Right
  25161. * 5. Up
  25162. * 6. Down
  25163. *
  25164. * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
  25165. * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
  25166. * the user to move the input cursor left and right, and to delete characters, etc.
  25167. */
  25168. whitelist: [
  25169. 'textfield'
  25170. ],
  25171. tabIndexWhitelist: [
  25172. 'a',
  25173. 'button',
  25174. 'embed',
  25175. 'frame',
  25176. 'iframe',
  25177. 'img',
  25178. 'input',
  25179. 'object',
  25180. 'select',
  25181. 'textarea'
  25182. ],
  25183. constructor: function() {
  25184. var me = this,
  25185. CQ = Ext.ComponentQuery;
  25186. me.addEvents(
  25187. /**
  25188. * @event beforecomponentfocus
  25189. * Fires before a component becomes focused. Return `false` to prevent
  25190. * the component from gaining focus.
  25191. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  25192. * @param {Ext.Component} cmp The component that is being focused
  25193. * @param {Ext.Component} previousCmp The component that was previously focused,
  25194. * or `undefined` if there was no previously focused component.
  25195. */
  25196. 'beforecomponentfocus',
  25197. /**
  25198. * @event componentfocus
  25199. * Fires after a component becomes focused.
  25200. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  25201. * @param {Ext.Component} cmp The component that has been focused
  25202. * @param {Ext.Component} previousCmp The component that was previously focused,
  25203. * or `undefined` if there was no previously focused component.
  25204. */
  25205. 'componentfocus',
  25206. /**
  25207. * @event disable
  25208. * Fires when the FocusManager is disabled
  25209. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  25210. */
  25211. 'disable',
  25212. /**
  25213. * @event enable
  25214. * Fires when the FocusManager is enabled
  25215. * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
  25216. */
  25217. 'enable'
  25218. );
  25219. // Setup KeyNav that's bound to document to catch all
  25220. // unhandled/bubbled key events for navigation
  25221. me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
  25222. disabled: true,
  25223. scope: me,
  25224. backspace: me.focusLast,
  25225. enter: me.navigateIn,
  25226. esc: me.navigateOut,
  25227. tab: me.navigateSiblings
  25228. //space: me.navigateIn,
  25229. //del: me.focusLast,
  25230. //left: me.navigateSiblings,
  25231. //right: me.navigateSiblings,
  25232. //down: me.navigateSiblings,
  25233. //up: me.navigateSiblings
  25234. });
  25235. me.focusData = {};
  25236. me.subscribers = Ext.create('Ext.util.HashMap');
  25237. me.focusChain = {};
  25238. // Setup some ComponentQuery pseudos
  25239. Ext.apply(CQ.pseudos, {
  25240. focusable: function(cmps) {
  25241. var len = cmps.length,
  25242. results = [],
  25243. i = 0,
  25244. c,
  25245. isFocusable = function(x) {
  25246. return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
  25247. };
  25248. for (; i < len; i++) {
  25249. c = cmps[i];
  25250. if (isFocusable(c)) {
  25251. results.push(c);
  25252. }
  25253. }
  25254. return results;
  25255. },
  25256. nextFocus: function(cmps, idx, step) {
  25257. step = step || 1;
  25258. idx = parseInt(idx, 10);
  25259. var len = cmps.length,
  25260. i = idx + step,
  25261. c;
  25262. for (; i != idx; i += step) {
  25263. if (i >= len) {
  25264. i = 0;
  25265. } else if (i < 0) {
  25266. i = len - 1;
  25267. }
  25268. c = cmps[i];
  25269. if (CQ.is(c, ':focusable')) {
  25270. return [c];
  25271. } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
  25272. return [c.placeholder];
  25273. }
  25274. }
  25275. return [];
  25276. },
  25277. prevFocus: function(cmps, idx) {
  25278. return this.nextFocus(cmps, idx, -1);
  25279. },
  25280. root: function(cmps) {
  25281. var len = cmps.length,
  25282. results = [],
  25283. i = 0,
  25284. c;
  25285. for (; i < len; i++) {
  25286. c = cmps[i];
  25287. if (!c.ownerCt) {
  25288. results.push(c);
  25289. }
  25290. }
  25291. return results;
  25292. }
  25293. });
  25294. },
  25295. /**
  25296. * Adds the specified xtype to the {@link #whitelist}.
  25297. * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
  25298. */
  25299. addXTypeToWhitelist: function(xtype) {
  25300. var me = this;
  25301. if (Ext.isArray(xtype)) {
  25302. Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
  25303. return;
  25304. }
  25305. if (!Ext.Array.contains(me.whitelist, xtype)) {
  25306. me.whitelist.push(xtype);
  25307. }
  25308. },
  25309. clearComponent: function(cmp) {
  25310. clearTimeout(this.cmpFocusDelay);
  25311. if (!cmp.isDestroyed) {
  25312. cmp.blur();
  25313. }
  25314. },
  25315. /**
  25316. * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
  25317. */
  25318. disable: function() {
  25319. var me = this;
  25320. if (!me.enabled) {
  25321. return;
  25322. }
  25323. delete me.options;
  25324. me.enabled = false;
  25325. Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
  25326. me.removeDOM();
  25327. // Stop handling key navigation
  25328. me.keyNav.disable();
  25329. // disable focus for all components
  25330. me.setFocusAll(false);
  25331. me.fireEvent('disable', me);
  25332. },
  25333. /**
  25334. * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
  25335. * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
  25336. - focusFrame : Boolean
  25337. `true` to show the focus frame around a component when it is focused. Defaults to `false`.
  25338. * @markdown
  25339. */
  25340. enable: function(options) {
  25341. var me = this;
  25342. if (options === true) {
  25343. options = { focusFrame: true };
  25344. }
  25345. me.options = options = options || {};
  25346. if (me.enabled) {
  25347. return;
  25348. }
  25349. // Handle components that are newly added after we are enabled
  25350. Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
  25351. me.initDOM(options);
  25352. // Start handling key navigation
  25353. me.keyNav.enable();
  25354. // enable focus for all components
  25355. me.setFocusAll(true, options);
  25356. // Finally, let's focus our global focus el so we start fresh
  25357. me.focusEl.focus();
  25358. delete me.focusedCmp;
  25359. me.enabled = true;
  25360. me.fireEvent('enable', me);
  25361. },
  25362. focusLast: function(e) {
  25363. var me = this;
  25364. if (me.isWhitelisted(me.focusedCmp)) {
  25365. return true;
  25366. }
  25367. // Go back to last focused item
  25368. if (me.previousFocusedCmp) {
  25369. me.previousFocusedCmp.focus();
  25370. }
  25371. },
  25372. getRootComponents: function() {
  25373. var me = this,
  25374. CQ = Ext.ComponentQuery,
  25375. inline = CQ.query(':focusable:root:not([floating])'),
  25376. floating = CQ.query(':focusable:root[floating]');
  25377. // Floating items should go to the top of our root stack, and be ordered
  25378. // by their z-index (highest first)
  25379. floating.sort(function(a, b) {
  25380. return a.el.getZIndex() > b.el.getZIndex();
  25381. });
  25382. return floating.concat(inline);
  25383. },
  25384. initDOM: function(options) {
  25385. var me = this,
  25386. sp = '&#160',
  25387. cls = me.focusFrameCls;
  25388. if (!Ext.isReady) {
  25389. Ext.onReady(me.initDOM, me);
  25390. return;
  25391. }
  25392. // Create global focus element
  25393. if (!me.focusEl) {
  25394. me.focusEl = Ext.getBody().createChild({
  25395. tabIndex: '-1',
  25396. cls: me.focusElementCls,
  25397. html: sp
  25398. });
  25399. }
  25400. // Create global focus frame
  25401. if (!me.focusFrame && options.focusFrame) {
  25402. me.focusFrame = Ext.getBody().createChild({
  25403. cls: cls,
  25404. children: [
  25405. { cls: cls + '-top' },
  25406. { cls: cls + '-bottom' },
  25407. { cls: cls + '-left' },
  25408. { cls: cls + '-right' }
  25409. ],
  25410. style: 'top: -100px; left: -100px;'
  25411. });
  25412. me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
  25413. me.focusFrameWidth = 2;
  25414. me.focusFrame.hide().setLeftTop(0, 0);
  25415. }
  25416. },
  25417. isWhitelisted: function(cmp) {
  25418. return cmp && Ext.Array.some(this.whitelist, function(x) {
  25419. return cmp.isXType(x);
  25420. });
  25421. },
  25422. navigateIn: function(e) {
  25423. var me = this,
  25424. focusedCmp = me.focusedCmp,
  25425. rootCmps,
  25426. firstChild;
  25427. if (!focusedCmp) {
  25428. // No focus yet, so focus the first root cmp on the page
  25429. rootCmps = me.getRootComponents();
  25430. if (rootCmps.length) {
  25431. rootCmps[0].focus();
  25432. }
  25433. } else {
  25434. // Drill into child ref items of the focused cmp, if applicable.
  25435. // This works for any Component with a getRefItems implementation.
  25436. firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
  25437. if (firstChild) {
  25438. firstChild.focus();
  25439. } else {
  25440. // Let's try to fire a click event, as if it came from the mouse
  25441. if (Ext.isFunction(focusedCmp.onClick)) {
  25442. e.button = 0;
  25443. focusedCmp.onClick(e);
  25444. focusedCmp.focus();
  25445. }
  25446. }
  25447. }
  25448. },
  25449. navigateOut: function(e) {
  25450. var me = this,
  25451. parent;
  25452. if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
  25453. me.focusEl.focus();
  25454. } else {
  25455. parent.focus();
  25456. }
  25457. // In some browsers (Chrome) FocusManager can handle this before other
  25458. // handlers. Ext Windows have their own Esc key handling, so we need to
  25459. // return true here to allow the event to bubble.
  25460. return true;
  25461. },
  25462. navigateSiblings: function(e, source, parent) {
  25463. var me = this,
  25464. src = source || me,
  25465. key = e.getKey(),
  25466. EO = Ext.EventObject,
  25467. goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
  25468. checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
  25469. nextSelector = goBack ? 'prev' : 'next',
  25470. idx, next, focusedCmp;
  25471. focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
  25472. if (!focusedCmp && !parent) {
  25473. return;
  25474. }
  25475. if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
  25476. return true;
  25477. }
  25478. parent = parent || focusedCmp.up();
  25479. if (parent) {
  25480. idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
  25481. next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
  25482. if (next && focusedCmp !== next) {
  25483. next.focus();
  25484. return next;
  25485. }
  25486. }
  25487. },
  25488. onComponentBlur: function(cmp, e) {
  25489. var me = this;
  25490. if (me.focusedCmp === cmp) {
  25491. me.previousFocusedCmp = cmp;
  25492. delete me.focusedCmp;
  25493. }
  25494. if (me.focusFrame) {
  25495. me.focusFrame.hide();
  25496. }
  25497. },
  25498. onComponentCreated: function(hash, id, cmp) {
  25499. this.setFocus(cmp, true, this.options);
  25500. },
  25501. onComponentDestroy: function(cmp) {
  25502. this.setFocus(cmp, false);
  25503. },
  25504. onComponentFocus: function(cmp, e) {
  25505. var me = this,
  25506. chain = me.focusChain;
  25507. if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
  25508. me.clearComponent(cmp);
  25509. // Check our focus chain, so we don't run into a never ending recursion
  25510. // If we've attempted (unsuccessfully) to focus this component before,
  25511. // then we're caught in a loop of child->parent->...->child and we
  25512. // need to cut the loop off rather than feed into it.
  25513. if (chain[cmp.id]) {
  25514. return;
  25515. }
  25516. // Try to focus the parent instead
  25517. var parent = cmp.up();
  25518. if (parent) {
  25519. // Add component to our focus chain to detect infinite focus loop
  25520. // before we fire off an attempt to focus our parent.
  25521. // See the comments above.
  25522. chain[cmp.id] = true;
  25523. parent.focus();
  25524. }
  25525. return;
  25526. }
  25527. // Clear our focus chain when we have a focusable component
  25528. me.focusChain = {};
  25529. // Defer focusing for 90ms so components can do a layout/positioning
  25530. // and give us an ability to buffer focuses
  25531. clearTimeout(me.cmpFocusDelay);
  25532. if (arguments.length !== 2) {
  25533. me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
  25534. return;
  25535. }
  25536. if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
  25537. me.clearComponent(cmp);
  25538. return;
  25539. }
  25540. me.focusedCmp = cmp;
  25541. // If we have a focus frame, show it around the focused component
  25542. if (me.shouldShowFocusFrame(cmp)) {
  25543. var cls = '.' + me.focusFrameCls + '-',
  25544. ff = me.focusFrame,
  25545. fw = me.focusFrameWidth,
  25546. box = cmp.el.getPageBox(),
  25547. // Size the focus frame's t/b/l/r according to the box
  25548. // This leaves a hole in the middle of the frame so user
  25549. // interaction w/ the mouse can continue
  25550. bt = box.top,
  25551. bl = box.left,
  25552. bw = box.width,
  25553. bh = box.height,
  25554. ft = ff.child(cls + 'top'),
  25555. fb = ff.child(cls + 'bottom'),
  25556. fl = ff.child(cls + 'left'),
  25557. fr = ff.child(cls + 'right');
  25558. ft.setWidth(bw).setLeftTop(bl, bt);
  25559. fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
  25560. fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
  25561. fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
  25562. ff.show();
  25563. }
  25564. me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
  25565. },
  25566. onComponentHide: function(cmp) {
  25567. var me = this,
  25568. CQ = Ext.ComponentQuery,
  25569. cmpHadFocus = false,
  25570. focusedCmp,
  25571. parent;
  25572. if (me.focusedCmp) {
  25573. focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
  25574. cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
  25575. if (focusedCmp) {
  25576. me.clearComponent(focusedCmp);
  25577. }
  25578. }
  25579. me.clearComponent(cmp);
  25580. if (cmpHadFocus) {
  25581. parent = CQ.query('^:focusable', cmp)[0];
  25582. if (parent) {
  25583. parent.focus();
  25584. }
  25585. }
  25586. },
  25587. removeDOM: function() {
  25588. var me = this;
  25589. // If we are still enabled globally, or there are still subscribers
  25590. // then we will halt here, since our DOM stuff is still being used
  25591. if (me.enabled || me.subscribers.length) {
  25592. return;
  25593. }
  25594. Ext.destroy(
  25595. me.focusEl,
  25596. me.focusFrame
  25597. );
  25598. delete me.focusEl;
  25599. delete me.focusFrame;
  25600. delete me.focusFrameWidth;
  25601. },
  25602. /**
  25603. * Removes the specified xtype from the {@link #whitelist}.
  25604. * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
  25605. */
  25606. removeXTypeFromWhitelist: function(xtype) {
  25607. var me = this;
  25608. if (Ext.isArray(xtype)) {
  25609. Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
  25610. return;
  25611. }
  25612. Ext.Array.remove(me.whitelist, xtype);
  25613. },
  25614. setFocus: function(cmp, focusable, options) {
  25615. var me = this,
  25616. el, dom, data,
  25617. needsTabIndex = function(n) {
  25618. return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
  25619. && n.tabIndex <= 0;
  25620. };
  25621. options = options || {};
  25622. // Come back and do this after the component is rendered
  25623. if (!cmp.rendered) {
  25624. cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
  25625. return;
  25626. }
  25627. el = cmp.getFocusEl();
  25628. dom = el.dom;
  25629. // Decorate the component's focus el for focus-ability
  25630. if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
  25631. if (focusable) {
  25632. data = {
  25633. focusFrame: options.focusFrame
  25634. };
  25635. // Only set -1 tabIndex if we need it
  25636. // inputs, buttons, and anchor tags do not need it,
  25637. // and neither does any DOM that has it set already
  25638. // programmatically or in markup.
  25639. if (needsTabIndex(dom)) {
  25640. data.tabIndex = dom.tabIndex;
  25641. dom.tabIndex = -1;
  25642. }
  25643. el.on({
  25644. focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
  25645. blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
  25646. scope: me
  25647. });
  25648. cmp.on({
  25649. hide: me.onComponentHide,
  25650. close: me.onComponentHide,
  25651. beforedestroy: me.onComponentDestroy,
  25652. scope: me
  25653. });
  25654. me.focusData[cmp.id] = data;
  25655. } else {
  25656. data = me.focusData[cmp.id];
  25657. if ('tabIndex' in data) {
  25658. dom.tabIndex = data.tabIndex;
  25659. }
  25660. el.un('focus', data.focusFn, me);
  25661. el.un('blur', data.blurFn, me);
  25662. cmp.un('hide', me.onComponentHide, me);
  25663. cmp.un('close', me.onComponentHide, me);
  25664. cmp.un('beforedestroy', me.onComponentDestroy, me);
  25665. delete me.focusData[cmp.id];
  25666. }
  25667. }
  25668. },
  25669. setFocusAll: function(focusable, options) {
  25670. var me = this,
  25671. cmps = Ext.ComponentManager.all.getArray(),
  25672. len = cmps.length,
  25673. cmp,
  25674. i = 0;
  25675. for (; i < len; i++) {
  25676. me.setFocus(cmps[i], focusable, options);
  25677. }
  25678. },
  25679. setupSubscriberKeys: function(container, keys) {
  25680. var me = this,
  25681. el = container.getFocusEl(),
  25682. scope = keys.scope,
  25683. handlers = {
  25684. backspace: me.focusLast,
  25685. enter: me.navigateIn,
  25686. esc: me.navigateOut,
  25687. scope: me
  25688. },
  25689. navSiblings = function(e) {
  25690. if (me.focusedCmp === container) {
  25691. // Root the sibling navigation to this container, so that we
  25692. // can automatically dive into the container, rather than forcing
  25693. // the user to hit the enter key to dive in.
  25694. return me.navigateSiblings(e, me, container);
  25695. } else {
  25696. return me.navigateSiblings(e);
  25697. }
  25698. };
  25699. Ext.iterate(keys, function(key, cb) {
  25700. handlers[key] = function(e) {
  25701. var ret = navSiblings(e);
  25702. if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
  25703. return true;
  25704. }
  25705. return ret;
  25706. };
  25707. }, me);
  25708. return Ext.create('Ext.util.KeyNav', el, handlers);
  25709. },
  25710. shouldShowFocusFrame: function(cmp) {
  25711. var me = this,
  25712. opts = me.options || {};
  25713. if (!me.focusFrame || !cmp) {
  25714. return false;
  25715. }
  25716. // Global trumps
  25717. if (opts.focusFrame) {
  25718. return true;
  25719. }
  25720. if (me.focusData[cmp.id].focusFrame) {
  25721. return true;
  25722. }
  25723. return false;
  25724. },
  25725. /**
  25726. * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
  25727. * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
  25728. * @param {Boolean/Object} options An object of the following options
  25729. * @param {Array/Object} options.keys
  25730. * An array containing the string names of navigation keys to be supported. The allowed values are:
  25731. *
  25732. * - 'left'
  25733. * - 'right'
  25734. * - 'up'
  25735. * - 'down'
  25736. *
  25737. * Or, an object containing those key names as keys with `true` or a callback function as their value. A scope may also be passed. E.g.:
  25738. *
  25739. * {
  25740. * left: this.onLeftKey,
  25741. * right: this.onRightKey,
  25742. * scope: this
  25743. * }
  25744. *
  25745. * @param {Boolean} options.focusFrame
  25746. * `true` to show the focus frame around a component when it is focused. Defaults to `false`.
  25747. */
  25748. subscribe: function(container, options) {
  25749. var me = this,
  25750. EA = Ext.Array,
  25751. data = {},
  25752. subs = me.subscribers,
  25753. // Recursively add focus ability as long as a descendent container isn't
  25754. // itself subscribed to the FocusManager, or else we'd have unwanted side
  25755. // effects for subscribing a descendent container twice.
  25756. safeSetFocus = function(cmp) {
  25757. if (cmp.isContainer && !subs.containsKey(cmp.id)) {
  25758. EA.forEach(cmp.query('>'), safeSetFocus);
  25759. me.setFocus(cmp, true, options);
  25760. cmp.on('add', data.onAdd, me);
  25761. } else if (!cmp.isContainer) {
  25762. me.setFocus(cmp, true, options);
  25763. }
  25764. };
  25765. // We only accept containers
  25766. if (!container || !container.isContainer) {
  25767. return;
  25768. }
  25769. if (!container.rendered) {
  25770. container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
  25771. return;
  25772. }
  25773. // Init the DOM, incase this is the first time it will be used
  25774. me.initDOM(options);
  25775. // Create key navigation for subscriber based on keys option
  25776. data.keyNav = me.setupSubscriberKeys(container, options.keys);
  25777. // We need to keep track of components being added to our subscriber
  25778. // and any containers nested deeply within it (omg), so let's do that.
  25779. // Components that are removed are globally handled.
  25780. // Also keep track of destruction of our container for auto-unsubscribe.
  25781. data.onAdd = function(ct, cmp, idx) {
  25782. safeSetFocus(cmp);
  25783. };
  25784. container.on('beforedestroy', me.unsubscribe, me);
  25785. // Now we setup focusing abilities for the container and all its components
  25786. safeSetFocus(container);
  25787. // Add to our subscribers list
  25788. subs.add(container.id, data);
  25789. },
  25790. /**
  25791. * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
  25792. * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
  25793. */
  25794. unsubscribe: function(container) {
  25795. var me = this,
  25796. EA = Ext.Array,
  25797. subs = me.subscribers,
  25798. data,
  25799. // Recursively remove focus ability as long as a descendent container isn't
  25800. // itself subscribed to the FocusManager, or else we'd have unwanted side
  25801. // effects for unsubscribing an ancestor container.
  25802. safeSetFocus = function(cmp) {
  25803. if (cmp.isContainer && !subs.containsKey(cmp.id)) {
  25804. EA.forEach(cmp.query('>'), safeSetFocus);
  25805. me.setFocus(cmp, false);
  25806. cmp.un('add', data.onAdd, me);
  25807. } else if (!cmp.isContainer) {
  25808. me.setFocus(cmp, false);
  25809. }
  25810. };
  25811. if (!container || !subs.containsKey(container.id)) {
  25812. return;
  25813. }
  25814. data = subs.get(container.id);
  25815. data.keyNav.destroy();
  25816. container.un('beforedestroy', me.unsubscribe, me);
  25817. subs.removeAtKey(container.id);
  25818. safeSetFocus(container);
  25819. me.removeDOM();
  25820. }
  25821. });
  25822. /**
  25823. * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
  25824. * elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
  25825. * constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
  25826. *
  25827. * ## Some items have shortcut strings for creation:
  25828. *
  25829. * | Shortcut | xtype | Class | Description
  25830. * |:---------|:--------------|:------------------------------|:---------------------------------------------------
  25831. * | `->` | `tbfill` | {@link Ext.toolbar.Fill} | begin using the right-justified button container
  25832. * | `-` | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
  25833. * | ` ` | `tbspacer` | {@link Ext.toolbar.Spacer} | add horiztonal space between elements
  25834. *
  25835. * @example
  25836. * Ext.create('Ext.toolbar.Toolbar', {
  25837. * renderTo: document.body,
  25838. * width : 500,
  25839. * items: [
  25840. * {
  25841. * // xtype: 'button', // default for Toolbars
  25842. * text: 'Button'
  25843. * },
  25844. * {
  25845. * xtype: 'splitbutton',
  25846. * text : 'Split Button'
  25847. * },
  25848. * // begin using the right-justified button container
  25849. * '->', // same as { xtype: 'tbfill' }
  25850. * {
  25851. * xtype : 'textfield',
  25852. * name : 'field1',
  25853. * emptyText: 'enter search term'
  25854. * },
  25855. * // add a vertical separator bar between toolbar items
  25856. * '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
  25857. * 'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
  25858. * { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
  25859. * 'text 2',
  25860. * { xtype: 'tbspacer', width: 50 }, // add a 50px space
  25861. * 'text 3'
  25862. * ]
  25863. * });
  25864. *
  25865. * Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
  25866. *
  25867. * @example
  25868. * Ext.create('Ext.toolbar.Toolbar', {
  25869. * renderTo: document.body,
  25870. * width : 400,
  25871. * items: [
  25872. * {
  25873. * text: 'Button'
  25874. * },
  25875. * {
  25876. * xtype: 'splitbutton',
  25877. * text : 'Split Button'
  25878. * },
  25879. * '->',
  25880. * {
  25881. * xtype : 'textfield',
  25882. * name : 'field1',
  25883. * emptyText: 'enter search term'
  25884. * }
  25885. * ]
  25886. * });
  25887. *
  25888. * Example
  25889. *
  25890. * @example
  25891. * var enableBtn = Ext.create('Ext.button.Button', {
  25892. * text : 'Enable All Items',
  25893. * disabled: true,
  25894. * scope : this,
  25895. * handler : function() {
  25896. * //disable the enable button and enable the disable button
  25897. * enableBtn.disable();
  25898. * disableBtn.enable();
  25899. *
  25900. * //enable the toolbar
  25901. * toolbar.enable();
  25902. * }
  25903. * });
  25904. *
  25905. * var disableBtn = Ext.create('Ext.button.Button', {
  25906. * text : 'Disable All Items',
  25907. * scope : this,
  25908. * handler : function() {
  25909. * //enable the enable button and disable button
  25910. * disableBtn.disable();
  25911. * enableBtn.enable();
  25912. *
  25913. * //disable the toolbar
  25914. * toolbar.disable();
  25915. * }
  25916. * });
  25917. *
  25918. * var toolbar = Ext.create('Ext.toolbar.Toolbar', {
  25919. * renderTo: document.body,
  25920. * width : 400,
  25921. * margin : '5 0 0 0',
  25922. * items : [enableBtn, disableBtn]
  25923. * });
  25924. *
  25925. * Adding items to and removing items from a toolbar is as simple as calling the {@link #add} and {@link #remove} methods. There is also a {@link #removeAll} method
  25926. * which remove all items within the toolbar.
  25927. *
  25928. * @example
  25929. * var toolbar = Ext.create('Ext.toolbar.Toolbar', {
  25930. * renderTo: document.body,
  25931. * width : 700,
  25932. * items: [
  25933. * {
  25934. * text: 'Example Button'
  25935. * }
  25936. * ]
  25937. * });
  25938. *
  25939. * var addedItems = [];
  25940. *
  25941. * Ext.create('Ext.toolbar.Toolbar', {
  25942. * renderTo: document.body,
  25943. * width : 700,
  25944. * margin : '5 0 0 0',
  25945. * items : [
  25946. * {
  25947. * text : 'Add a button',
  25948. * scope : this,
  25949. * handler: function() {
  25950. * var text = prompt('Please enter the text for your button:');
  25951. * addedItems.push(toolbar.add({
  25952. * text: text
  25953. * }));
  25954. * }
  25955. * },
  25956. * {
  25957. * text : 'Add a text item',
  25958. * scope : this,
  25959. * handler: function() {
  25960. * var text = prompt('Please enter the text for your item:');
  25961. * addedItems.push(toolbar.add(text));
  25962. * }
  25963. * },
  25964. * {
  25965. * text : 'Add a toolbar seperator',
  25966. * scope : this,
  25967. * handler: function() {
  25968. * addedItems.push(toolbar.add('-'));
  25969. * }
  25970. * },
  25971. * {
  25972. * text : 'Add a toolbar spacer',
  25973. * scope : this,
  25974. * handler: function() {
  25975. * addedItems.push(toolbar.add('->'));
  25976. * }
  25977. * },
  25978. * '->',
  25979. * {
  25980. * text : 'Remove last inserted item',
  25981. * scope : this,
  25982. * handler: function() {
  25983. * if (addedItems.length) {
  25984. * toolbar.remove(addedItems.pop());
  25985. * } else if (toolbar.items.length) {
  25986. * toolbar.remove(toolbar.items.last());
  25987. * } else {
  25988. * alert('No items in the toolbar');
  25989. * }
  25990. * }
  25991. * },
  25992. * {
  25993. * text : 'Remove all items',
  25994. * scope : this,
  25995. * handler: function() {
  25996. * toolbar.removeAll();
  25997. * }
  25998. * }
  25999. * ]
  26000. * });
  26001. *
  26002. * @constructor
  26003. * Creates a new Toolbar
  26004. * @param {Object/Object[]} config A config object or an array of buttons to <code>{@link #add}</code>
  26005. * @docauthor Robert Dougan <rob@sencha.com>
  26006. */
  26007. Ext.define('Ext.toolbar.Toolbar', {
  26008. extend: 'Ext.container.Container',
  26009. requires: [
  26010. 'Ext.toolbar.Fill',
  26011. 'Ext.layout.container.HBox',
  26012. 'Ext.layout.container.VBox',
  26013. 'Ext.FocusManager'
  26014. ],
  26015. uses: [
  26016. 'Ext.toolbar.Separator'
  26017. ],
  26018. alias: 'widget.toolbar',
  26019. alternateClassName: 'Ext.Toolbar',
  26020. isToolbar: true,
  26021. baseCls : Ext.baseCSSPrefix + 'toolbar',
  26022. ariaRole : 'toolbar',
  26023. defaultType: 'button',
  26024. /**
  26025. * @cfg {Boolean} vertical
  26026. * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
  26027. */
  26028. vertical: false,
  26029. /**
  26030. * @cfg {String/Object} layout
  26031. * This class assigns a default layout (`layout: 'hbox'`).
  26032. * Developers _may_ override this configuration option if another layout
  26033. * is required (the constructor must be passed a configuration object in this
  26034. * case instead of an array).
  26035. * See {@link Ext.container.Container#layout} for additional information.
  26036. */
  26037. /**
  26038. * @cfg {Boolean} enableOverflow
  26039. * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
  26040. * items which overflow the Toolbar's width.
  26041. */
  26042. enableOverflow: false,
  26043. /**
  26044. * @cfg {String} menuTriggerCls
  26045. * Configure the icon class of the overflow button.
  26046. */
  26047. menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
  26048. // private
  26049. trackMenus: true,
  26050. itemCls: Ext.baseCSSPrefix + 'toolbar-item',
  26051. initComponent: function() {
  26052. var me = this,
  26053. keys;
  26054. // check for simplified (old-style) overflow config:
  26055. if (!me.layout && me.enableOverflow) {
  26056. me.layout = { overflowHandler: 'Menu' };
  26057. }
  26058. if (me.dock === 'right' || me.dock === 'left') {
  26059. me.vertical = true;
  26060. }
  26061. me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
  26062. type: me.layout
  26063. } : me.layout || {}, {
  26064. type: me.vertical ? 'vbox' : 'hbox',
  26065. align: me.vertical ? 'stretchmax' : 'middle',
  26066. clearInnerCtOnLayout: true
  26067. });
  26068. if (me.vertical) {
  26069. me.addCls