/ext-4.1.0_b3/docs/extjs/examples/kitchensink/all-classes.js

https://bitbucket.org/srogerf/javascript · JavaScript · 28581 lines · 24127 code · 835 blank · 3619 comment · 863 complexity · 1a8790657b1626f7a21dd493d975bf3f MD5 · raw file

  1. /*
  2. Copyright(c) 2011 Sencha
  3. */
  4. /**
  5. * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
  6. * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
  7. *
  8. * For example:
  9. *
  10. * Ext.define('Employee', {
  11. * mixins: {
  12. * observable: 'Ext.util.Observable'
  13. * },
  14. *
  15. * constructor: function (config) {
  16. * // The Observable constructor copies all of the properties of `config` on
  17. * // to `this` using {@link Ext#apply}. Further, the `listeners` property is
  18. * // processed to add listeners.
  19. * //
  20. * this.mixins.observable.constructor.call(this, config);
  21. *
  22. * this.addEvents(
  23. * 'fired',
  24. * 'quit'
  25. * );
  26. * }
  27. * });
  28. *
  29. * This could then be used like this:
  30. *
  31. * var newEmployee = new Employee({
  32. * name: employeeName,
  33. * listeners: {
  34. * quit: function() {
  35. * // By default, "this" will be the object that fired the event.
  36. * alert(this.name + " has quit!");
  37. * }
  38. * }
  39. * });
  40. */
  41. Ext.define('Ext.util.Observable', {
  42. /* Begin Definitions */
  43. requires: ['Ext.util.Event'],
  44. statics: {
  45. /**
  46. * Removes **all** added captures from the Observable.
  47. *
  48. * @param {Ext.util.Observable} o The Observable to release
  49. * @static
  50. */
  51. releaseCapture: function(o) {
  52. o.fireEvent = this.prototype.fireEvent;
  53. },
  54. /**
  55. * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
  56. * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
  57. * the event will not fire.
  58. *
  59. * @param {Ext.util.Observable} o The Observable to capture events from.
  60. * @param {Function} fn The function to call when an event is fired.
  61. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
  62. * the Observable firing the event.
  63. * @static
  64. */
  65. capture: function(o, fn, scope) {
  66. o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
  67. },
  68. /**
  69. * Sets observability on the passed class constructor.
  70. *
  71. * This makes any event fired on any instance of the passed class also fire a single event through
  72. * the **class** allowing for central handling of events on many instances at once.
  73. *
  74. * Usage:
  75. *
  76. * Ext.util.Observable.observe(Ext.data.Connection);
  77. * Ext.data.Connection.on('beforerequest', function(con, options) {
  78. * console.log('Ajax request made to ' + options.url);
  79. * });
  80. *
  81. * @param {Function} c The class constructor to make observable.
  82. * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
  83. * @static
  84. */
  85. observe: function(cls, listeners) {
  86. if (cls) {
  87. if (!cls.isObservable) {
  88. Ext.applyIf(cls, new this());
  89. this.capture(cls.prototype, cls.fireEvent, cls);
  90. }
  91. if (Ext.isObject(listeners)) {
  92. cls.on(listeners);
  93. }
  94. return cls;
  95. }
  96. }
  97. },
  98. /* End Definitions */
  99. /**
  100. * @cfg {Object} listeners
  101. *
  102. * A config object containing one or more event handlers to be added to this object during initialization. This
  103. * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
  104. * handlers at once.
  105. *
  106. * **DOM events from Ext JS {@link Ext.Component Components}**
  107. *
  108. * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
  109. * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
  110. * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
  111. * child element of a Component, we need to specify the `element` option to identify the Component property to add a
  112. * DOM listener to:
  113. *
  114. * new Ext.panel.Panel({
  115. * width: 400,
  116. * height: 200,
  117. * dockedItems: [{
  118. * xtype: 'toolbar'
  119. * }],
  120. * listeners: {
  121. * click: {
  122. * element: 'el', //bind to the underlying el property on the panel
  123. * fn: function(){ console.log('click el'); }
  124. * },
  125. * dblclick: {
  126. * element: 'body', //bind to the underlying body property on the panel
  127. * fn: function(){ console.log('dblclick body'); }
  128. * }
  129. * }
  130. * });
  131. */
  132. /**
  133. * @property {Boolean} isObservable
  134. * `true` in this class to identify an objact as an instantiated Observable, or subclass thereof.
  135. */
  136. isObservable: true,
  137. constructor: function(config) {
  138. var me = this;
  139. Ext.apply(me, config);
  140. // Hash of event "hasListeners" flags.
  141. // For repeated events in time-critical code, the firing code should use
  142. // if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //code... }
  143. // Bubbling the events counts as one listener.
  144. // The subclass may have already initialized it.
  145. me.hasListeners = me.hasListeners || {};
  146. me.events = me.events || {};
  147. if (me.listeners) {
  148. me.on(me.listeners);
  149. me.listeners = null; //Set as an instance property to pre-empt the prototype in case any are set there.
  150. }
  151. if (me.bubbleEvents) {
  152. me.enableBubble(me.bubbleEvents);
  153. }
  154. },
  155. // @private
  156. eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
  157. /**
  158. * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
  159. * destroyed.
  160. *
  161. * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
  162. * @param {Object/String} ename The event name, or an object containing event name properties.
  163. * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
  164. * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
  165. * in which the handler function is executed.
  166. * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
  167. * {@link Ext.util.Observable#addListener addListener} options.
  168. */
  169. addManagedListener : function(item, ename, fn, scope, options) {
  170. var me = this,
  171. managedListeners = me.managedListeners = me.managedListeners || [],
  172. config;
  173. if (typeof ename !== 'string') {
  174. options = ename;
  175. for (ename in options) {
  176. if (options.hasOwnProperty(ename)) {
  177. config = options[ename];
  178. if (!me.eventOptionsRe.test(ename)) {
  179. me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
  180. }
  181. }
  182. }
  183. }
  184. else {
  185. managedListeners.push({
  186. item: item,
  187. ename: ename,
  188. fn: fn,
  189. scope: scope,
  190. options: options
  191. });
  192. item.on(ename, fn, scope, options);
  193. }
  194. },
  195. /**
  196. * Removes listeners that were added by the {@link #mon} method.
  197. *
  198. * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
  199. * @param {Object/String} ename The event name, or an object containing event name properties.
  200. * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
  201. * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
  202. * in which the handler function is executed.
  203. */
  204. removeManagedListener : function(item, ename, fn, scope) {
  205. var me = this,
  206. options,
  207. config,
  208. managedListeners,
  209. length,
  210. i;
  211. if (typeof ename !== 'string') {
  212. options = ename;
  213. for (ename in options) {
  214. if (options.hasOwnProperty(ename)) {
  215. config = options[ename];
  216. if (!me.eventOptionsRe.test(ename)) {
  217. me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
  218. }
  219. }
  220. }
  221. }
  222. managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
  223. for (i = 0, length = managedListeners.length; i < length; i++) {
  224. me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
  225. }
  226. },
  227. /**
  228. * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
  229. * to {@link #addListener}).
  230. *
  231. * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
  232. * calling {@link #enableBubble}.
  233. *
  234. * @param {String} eventName The name of the event to fire.
  235. * @param {Object...} args Variable number of parameters are passed to handlers.
  236. * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
  237. */
  238. fireEvent: function(eventName) {
  239. eventName = eventName.toLowerCase();
  240. var me = this,
  241. events = me.events,
  242. event = events && events[eventName];
  243. // Only continue firing the event if there are listeners to be informed.
  244. // Bubbled events will always have a listener count, so will be fired.
  245. if (event && me.hasListeners[eventName]) {
  246. return me.continueFireEvent(eventName, Ext.Array.slice(arguments, 1), event.bubble);
  247. }
  248. },
  249. /**
  250. * Continue to fire event.
  251. * @private
  252. *
  253. * @param {String} eventName
  254. * @param {Array} args
  255. * @param {Boolean} bubbles
  256. */
  257. continueFireEvent: function(eventName, args, bubbles) {
  258. var target = this,
  259. queue, event,
  260. ret = true;
  261. do {
  262. if (target.eventsSuspended === true) {
  263. if ((queue = target.eventQueue)) {
  264. queue.push([eventName, args, bubbles]);
  265. }
  266. return ret;
  267. } else {
  268. event = target.events[eventName];
  269. // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
  270. // configure to bubble.
  271. if (event && event != true) {
  272. if ((ret = event.fire.apply(event, args)) === false) {
  273. break;
  274. }
  275. }
  276. }
  277. } while (bubbles && (target = target.getBubbleParent()));
  278. return ret;
  279. },
  280. /**
  281. * Gets the bubbling parent for an Observable
  282. * @private
  283. * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
  284. */
  285. getBubbleParent: function(){
  286. var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
  287. if (parent && parent.isObservable) {
  288. return parent;
  289. }
  290. return null;
  291. },
  292. /**
  293. * Appends an event handler to this object. For example:
  294. *
  295. * myGridPanel.on("mouseover", this.onMouseOver, this);
  296. *
  297. * The method also allows for a single argument to be passed which is a config object
  298. * containing properties which specify multiple events. For example:
  299. *
  300. * myGridPanel.on({
  301. * cellClick: this.onCellClick,
  302. * mouseover: this.onMouseOver,
  303. * mouseout: this.onMouseOut,
  304. * scope: this // Important. Ensure "this" is correct during handler execution
  305. * });
  306. *
  307. * One can also specify options for each event handler separately:
  308. *
  309. * myGridPanel.on({
  310. * cellClick: {fn: this.onCellClick, scope: this, single: true},
  311. * mouseover: {fn: panel.onMouseOver, scope: panel}
  312. * });
  313. *
  314. * *Names* of methods in a specified scope may also be used. Note that
  315. * `scope` MUST be specified to use this option:
  316. *
  317. * myGridPanel.on({
  318. * cellClick: {fn: 'onCellClick', scope: this, single: true},
  319. * mouseover: {fn: 'onMouseOver', scope: panel}
  320. * });
  321. *
  322. * @param {String/Object} eventName The name of the event to listen for.
  323. * May also be an object who's property names are event names.
  324. *
  325. * @param {Function} [fn] The method the event invokes, or *if `scope` is specified, the *name* of the method within
  326. * the specified `scope`. Will be called with arguments
  327. * given to {@link #fireEvent} plus the `options` parameter described below.
  328. *
  329. * @param {Object} [scope] The scope (`this` reference) in which the handler function is
  330. * executed. **If omitted, defaults to the object which fired the event.**
  331. *
  332. * @param {Object} [options] An object containing handler configuration.
  333. *
  334. * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last
  335. * argument to every event handler.
  336. *
  337. * This object may contain any of the following properties:
  338. *
  339. * @param {Object} options.scope
  340. * The scope (`this` reference) in which the handler function is executed. **If omitted,
  341. * defaults to the object which fired the event.**
  342. *
  343. * @param {Number} options.delay
  344. * The number of milliseconds to delay the invocation of the handler after the event fires.
  345. *
  346. * @param {Boolean} options.single
  347. * True to add a handler to handle just the next firing of the event, and then remove itself.
  348. *
  349. * @param {Number} options.buffer
  350. * Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
  351. * by the specified number of milliseconds. If the event fires again within that time,
  352. * the original handler is _not_ invoked, but the new handler is scheduled in its place.
  353. *
  354. * @param {Ext.util.Observable} options.target
  355. * Only call the handler if the event was fired on the target Observable, _not_ if the event
  356. * was bubbled up from a child Observable.
  357. *
  358. * @param {String} options.element
  359. * **This option is only valid for listeners bound to {@link Ext.Component Components}.**
  360. * The name of a Component property which references an element to add a listener to.
  361. *
  362. * This option is useful during Component construction to add DOM event listeners to elements of
  363. * {@link Ext.Component Components} which will exist only after the Component is rendered.
  364. * For example, to add a click listener to a Panel's body:
  365. *
  366. * new Ext.panel.Panel({
  367. * title: 'The title',
  368. * listeners: {
  369. * click: this.handlePanelClick,
  370. * element: 'body'
  371. * }
  372. * });
  373. *
  374. * **Combining Options**
  375. *
  376. * Using the options argument, it is possible to combine different types of listeners:
  377. *
  378. * A delayed, one-time listener.
  379. *
  380. * myPanel.on('hide', this.handleClick, this, {
  381. * single: true,
  382. * delay: 100
  383. * });
  384. *
  385. */
  386. addListener: function(ename, fn, scope, options) {
  387. var me = this,
  388. config,
  389. event;
  390. if (typeof ename !== 'string') {
  391. options = ename;
  392. for (ename in options) {
  393. if (options.hasOwnProperty(ename)) {
  394. config = options[ename];
  395. if (!me.eventOptionsRe.test(ename)) {
  396. me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
  397. }
  398. }
  399. }
  400. }
  401. else {
  402. ename = ename.toLowerCase();
  403. me.events[ename] = me.events[ename] || true;
  404. event = me.events[ename] || true;
  405. if (Ext.isBoolean(event)) {
  406. me.events[ename] = event = new Ext.util.Event(me, ename);
  407. }
  408. // Allow listeners: { click: 'onClick', scope: myObject }
  409. if (typeof fn === 'string') {
  410. fn = scope[fn] || me.fn;
  411. }
  412. event.addListener(fn, scope, Ext.isObject(options) ? options : {});
  413. // Maintain count of listeners for each event name.
  414. // For repeated events in time-critical code, the firing code should use
  415. // if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //code... }
  416. me.hasListeners[ename] = (me.hasListeners[ename]||0) + 1;
  417. }
  418. },
  419. /**
  420. * Removes an event handler.
  421. *
  422. * @param {String} eventName The type of event the handler was associated with.
  423. * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
  424. * {@link #addListener} call.**
  425. * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
  426. * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
  427. */
  428. removeListener: function(ename, fn, scope) {
  429. var me = this,
  430. config,
  431. event,
  432. options;
  433. if (typeof ename !== 'string') {
  434. options = ename;
  435. for (ename in options) {
  436. if (options.hasOwnProperty(ename)) {
  437. config = options[ename];
  438. if (!me.eventOptionsRe.test(ename)) {
  439. me.removeListener(ename, config.fn || config, config.scope || options.scope);
  440. }
  441. }
  442. }
  443. } else {
  444. ename = ename.toLowerCase();
  445. event = me.events[ename];
  446. if (event && event.isEvent) {
  447. event.removeListener(fn, scope);
  448. // Maintain count of listeners for each event name.
  449. // For repeated events in time-critical code, the firing code should use
  450. // if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //code... }
  451. me.hasListeners[ename]--;
  452. }
  453. }
  454. },
  455. /**
  456. * Removes all listeners for this object including the managed listeners
  457. */
  458. clearListeners: function() {
  459. var events = this.events,
  460. event,
  461. key;
  462. for (key in events) {
  463. if (events.hasOwnProperty(key)) {
  464. event = events[key];
  465. if (event.isEvent) {
  466. event.clearListeners();
  467. }
  468. }
  469. }
  470. this.clearManagedListeners();
  471. },
  472. purgeListeners : function() {
  473. if (Ext.global.console) {
  474. Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
  475. }
  476. return this.clearListeners.apply(this, arguments);
  477. },
  478. /**
  479. * Removes all managed listeners for this object.
  480. */
  481. clearManagedListeners : function() {
  482. var managedListeners = this.managedListeners || [],
  483. i = 0,
  484. len = managedListeners.length;
  485. for (; i < len; i++) {
  486. this.removeManagedListenerItem(true, managedListeners[i]);
  487. }
  488. this.managedListeners = [];
  489. },
  490. /**
  491. * Remove a single managed listener item
  492. * @private
  493. * @param {Boolean} isClear True if this is being called during a clear
  494. * @param {Object} managedListener The managed listener item
  495. * See removeManagedListener for other args
  496. */
  497. removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
  498. if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
  499. managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
  500. if (!isClear) {
  501. Ext.Array.remove(this.managedListeners, managedListener);
  502. }
  503. }
  504. },
  505. purgeManagedListeners : function() {
  506. if (Ext.global.console) {
  507. Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
  508. }
  509. return this.clearManagedListeners.apply(this, arguments);
  510. },
  511. /**
  512. * Adds the specified events to the list of events which this Observable may fire.
  513. *
  514. * @param {Object/String...} eventNames Either an object with event names as properties with
  515. * a value of `true`. For example:
  516. *
  517. * this.addEvents({
  518. * storeloaded: true,
  519. * storecleared: true
  520. * });
  521. *
  522. * Or any number of event names as separate parameters. For example:
  523. *
  524. * this.addEvents('storeloaded', 'storecleared');
  525. *
  526. */
  527. addEvents: function(o) {
  528. var me = this,
  529. events = me.events || (me.events = {}),
  530. arg, args, i;
  531. if (typeof o == 'string') {
  532. for (args = arguments, i = args.length; i--; ) {
  533. arg = args[i];
  534. if (!events[arg]) {
  535. events[arg] = true;
  536. }
  537. }
  538. } else {
  539. Ext.applyIf(me.events, o);
  540. }
  541. },
  542. /**
  543. * Checks to see if this object has any listeners for a specified event, or whether the event bubbles. The answer
  544. * indicates whether the event needs firing or not.
  545. *
  546. * @param {String} eventName The name of the event to check for
  547. * @return {Boolean} `true` if the event is being listened for or bubbles, else `false`
  548. */
  549. hasListener: function(ename) {
  550. return !!this.hasListeners[ename.toLowerCase()];
  551. },
  552. /**
  553. * Suspends the firing of all events. (see {@link #resumeEvents})
  554. *
  555. * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
  556. * after the {@link #resumeEvents} call instead of discarding all suspended events.
  557. */
  558. suspendEvents: function(queueSuspended) {
  559. this.eventsSuspended = true;
  560. if (queueSuspended && !this.eventQueue) {
  561. this.eventQueue = [];
  562. }
  563. },
  564. /**
  565. * Resumes firing events (see {@link #suspendEvents}).
  566. *
  567. * If events were suspended using the `queueSuspended` parameter, then all events fired
  568. * during event suspension will be sent to any listeners now.
  569. */
  570. resumeEvents: function() {
  571. var me = this,
  572. queued = me.eventQueue,
  573. qLen, q;
  574. me.eventsSuspended = false;
  575. delete me.eventQueue;
  576. if (queued) {
  577. qLen = queued.length;
  578. for (q = 0; q < qLen; q++) {
  579. me.continueFireEvent.apply(me, queued[q]);
  580. }
  581. }
  582. },
  583. /**
  584. * Relays selected events from the specified Observable as if the events were fired by `this`.
  585. *
  586. * For example if you are extending Grid, you might decide to forward some events from store.
  587. * So you can do this inside your initComponent:
  588. *
  589. * this.relayEvents(this.getStore(), ['load']);
  590. *
  591. * The grid instance will then have an observable 'load' event which will be passed the
  592. * parameters of the store's load event and any function fired with the grid's load event
  593. * would have access to the grid using the `this` keyword.
  594. *
  595. * @param {Object} origin The Observable whose events this object is to relay.
  596. * @param {String[]} events Array of event names to relay.
  597. * @param {String} [prefix] A common prefix to attach to the event names. For example:
  598. *
  599. * this.relayEvents(this.getStore(), ['load', 'clear'], 'store');
  600. *
  601. * Now the grid will forward 'load' and 'clear' events of store as 'storeload' and 'storeclear'.
  602. */
  603. relayEvents : function(origin, events, prefix) {
  604. prefix = prefix || '';
  605. var me = this,
  606. len = events.length,
  607. i = 0,
  608. oldName,
  609. newName;
  610. for (; i < len; i++) {
  611. oldName = events[i];
  612. newName = prefix + oldName;
  613. me.events[newName] = me.events[newName] || true;
  614. origin.on(oldName, me.createRelayer(newName));
  615. }
  616. },
  617. /**
  618. * @private
  619. * Creates an event handling function which refires the event from this object as the passed event name.
  620. * @param newName
  621. * @param {Array} beginEnd (optional) The caller can specify on which indices to slice
  622. * @returns {Function}
  623. */
  624. createRelayer: function(newName, beginEnd){
  625. var me = this;
  626. return function(){
  627. return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.apply(arguments, beginEnd || [0, -1])));
  628. };
  629. },
  630. /**
  631. * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
  632. * present. There is no implementation in the Observable base class.
  633. *
  634. * This is commonly used by Ext.Components to bubble events to owner Containers.
  635. * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
  636. * Component's immediate owner. But if a known target is required, this can be overridden to access the
  637. * required target more quickly.
  638. *
  639. * Example:
  640. *
  641. * Ext.override(Ext.form.field.Base, {
  642. * // Add functionality to Field's initComponent to enable the change event to bubble
  643. * initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
  644. * this.enableBubble('change');
  645. * }),
  646. *
  647. * // We know that we want Field's events to bubble directly to the FormPanel.
  648. * getBubbleTarget : function() {
  649. * if (!this.formPanel) {
  650. * this.formPanel = this.findParentByType('form');
  651. * }
  652. * return this.formPanel;
  653. * }
  654. * });
  655. *
  656. * var myForm = new Ext.formPanel({
  657. * title: 'User Details',
  658. * items: [{
  659. * ...
  660. * }],
  661. * listeners: {
  662. * change: function() {
  663. * // Title goes red if form has been modified.
  664. * myForm.header.setStyle('color', 'red');
  665. * }
  666. * }
  667. * });
  668. *
  669. * @param {String/String[]} eventNames The event name to bubble, or an Array of event names.
  670. */
  671. enableBubble: function(eventNames) {
  672. if (eventNames) {
  673. var me = this,
  674. names = (typeof eventNames == 'string') ? arguments : eventNames,
  675. length = names.length,
  676. events = me.events,
  677. ename, event, i;
  678. for (i = 0; i < length; ++i) {
  679. ename = names[i].toLowerCase();
  680. event = events[ename];
  681. if (!event || typeof event == 'boolean') {
  682. events[ename] = event = new Ext.util.Event(me, ename);
  683. }
  684. // Event must fire if it bubbles (We don't know if anyone up the bubble hierarchy has listeners added)
  685. me.hasListeners[ename] = (me.hasListeners[ename]||0) + 1;
  686. event.bubble = true;
  687. }
  688. }
  689. }
  690. }, function() {
  691. this.createAlias({
  692. /**
  693. * @method
  694. * Shorthand for {@link #addListener}.
  695. * @inheritdoc Ext.util.Observable#addListener
  696. */
  697. on: 'addListener',
  698. /**
  699. * @method
  700. * Shorthand for {@link #removeListener}.
  701. * @inheritdoc Ext.util.Observable#removeListener
  702. */
  703. un: 'removeListener',
  704. /**
  705. * @method
  706. * Shorthand for {@link #addManagedListener}.
  707. * @inheritdoc Ext.util.Observable#addManagedListener
  708. */
  709. mon: 'addManagedListener',
  710. /**
  711. * @method
  712. * Shorthand for {@link #removeManagedListener}.
  713. * @inheritdoc Ext.util.Observable#removeManagedListener
  714. */
  715. mun: 'removeManagedListener'
  716. });
  717. //deprecated, will be removed in 5.0
  718. this.observeClass = this.observe;
  719. Ext.apply(Ext.util.Observable.prototype, function(){
  720. // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
  721. // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
  722. // private
  723. function getMethodEvent(method){
  724. var e = (this.methodEvents = this.methodEvents || {})[method],
  725. returnValue,
  726. v,
  727. cancel,
  728. obj = this;
  729. if (!e) {
  730. this.methodEvents[method] = e = {};
  731. e.originalFn = this[method];
  732. e.methodName = method;
  733. e.before = [];
  734. e.after = [];
  735. var makeCall = function(fn, scope, args){
  736. if((v = fn.apply(scope || obj, args)) !== undefined){
  737. if (typeof v == 'object') {
  738. if(v.returnValue !== undefined){
  739. returnValue = v.returnValue;
  740. }else{
  741. returnValue = v;
  742. }
  743. cancel = !!v.cancel;
  744. }
  745. else
  746. if (v === false) {
  747. cancel = true;
  748. }
  749. else {
  750. returnValue = v;
  751. }
  752. }
  753. };
  754. this[method] = function(){
  755. var args = Array.prototype.slice.call(arguments, 0),
  756. b, i, len;
  757. returnValue = v = undefined;
  758. cancel = false;
  759. for(i = 0, len = e.before.length; i < len; i++){
  760. b = e.before[i];
  761. makeCall(b.fn, b.scope, args);
  762. if (cancel) {
  763. return returnValue;
  764. }
  765. }
  766. if((v = e.originalFn.apply(obj, args)) !== undefined){
  767. returnValue = v;
  768. }
  769. for(i = 0, len = e.after.length; i < len; i++){
  770. b = e.after[i];
  771. makeCall(b.fn, b.scope, args);
  772. if (cancel) {
  773. return returnValue;
  774. }
  775. }
  776. return returnValue;
  777. };
  778. }
  779. return e;
  780. }
  781. return {
  782. // these are considered experimental
  783. // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
  784. // adds an 'interceptor' called before the original method
  785. beforeMethod : function(method, fn, scope){
  786. getMethodEvent.call(this, method).before.push({
  787. fn: fn,
  788. scope: scope
  789. });
  790. },
  791. // adds a 'sequence' called after the original method
  792. afterMethod : function(method, fn, scope){
  793. getMethodEvent.call(this, method).after.push({
  794. fn: fn,
  795. scope: scope
  796. });
  797. },
  798. removeMethodListener: function(method, fn, scope){
  799. var e = this.getMethodEvent(method),
  800. i, len;
  801. for(i = 0, len = e.before.length; i < len; i++){
  802. if(e.before[i].fn == fn && e.before[i].scope == scope){
  803. Ext.Array.erase(e.before, i, 1);
  804. return;
  805. }
  806. }
  807. for(i = 0, len = e.after.length; i < len; i++){
  808. if(e.after[i].fn == fn && e.after[i].scope == scope){
  809. Ext.Array.erase(e.after, i, 1);
  810. return;
  811. }
  812. }
  813. },
  814. toggleEventLogging: function(toggle) {
  815. Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
  816. if (Ext.isDefined(Ext.global.console)) {
  817. Ext.global.console.log(en, arguments);
  818. }
  819. });
  820. }
  821. };
  822. }());
  823. });
  824. /**
  825. * @author Ed Spencer
  826. *
  827. * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
  828. * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
  829. * express like this:
  830. *
  831. * Ext.define('User', {
  832. * extend: 'Ext.data.Model',
  833. * fields: ['id', 'name', 'email'],
  834. *
  835. * hasMany: {model: 'Order', name: 'orders'}
  836. * });
  837. *
  838. * Ext.define('Order', {
  839. * extend: 'Ext.data.Model',
  840. * fields: ['id', 'user_id', 'status', 'price'],
  841. *
  842. * belongsTo: 'User'
  843. * });
  844. *
  845. * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
  846. * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
  847. * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
  848. * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
  849. *
  850. * **Further Reading**
  851. *
  852. * - {@link Ext.data.association.HasMany hasMany associations}
  853. * - {@link Ext.data.association.BelongsTo belongsTo associations}
  854. * - {@link Ext.data.association.HasOne hasOne associations}
  855. * - {@link Ext.data.Model using Models}
  856. *
  857. * # Self association models
  858. *
  859. * We can also have models that create parent/child associations between the same type. Below is an example, where
  860. * groups can be nested inside other groups:
  861. *
  862. * // Server Data
  863. * {
  864. * "groups": {
  865. * "id": 10,
  866. * "parent_id": 100,
  867. * "name": "Main Group",
  868. * "parent_group": {
  869. * "id": 100,
  870. * "parent_id": null,
  871. * "name": "Parent Group"
  872. * },
  873. * "child_groups": [{
  874. * "id": 2,
  875. * "parent_id": 10,
  876. * "name": "Child Group 1"
  877. * },{
  878. * "id": 3,
  879. * "parent_id": 10,
  880. * "name": "Child Group 2"
  881. * },{
  882. * "id": 4,
  883. * "parent_id": 10,
  884. * "name": "Child Group 3"
  885. * }]
  886. * }
  887. * }
  888. *
  889. * // Client code
  890. * Ext.define('Group', {
  891. * extend: 'Ext.data.Model',
  892. * fields: ['id', 'parent_id', 'name'],
  893. * proxy: {
  894. * type: 'ajax',
  895. * url: 'data.json',
  896. * reader: {
  897. * type: 'json',
  898. * root: 'groups'
  899. * }
  900. * },
  901. * associations: [{
  902. * type: 'hasMany',
  903. * model: 'Group',
  904. * primaryKey: 'id',
  905. * foreignKey: 'parent_id',
  906. * autoLoad: true,
  907. * associationKey: 'child_groups' // read child data from child_groups
  908. * }, {
  909. * type: 'belongsTo',
  910. * model: 'Group',
  911. * primaryKey: 'id',
  912. * foreignKey: 'parent_id',
  913. * associationKey: 'parent_group' // read parent data from parent_group
  914. * }]
  915. * });
  916. *
  917. * Ext.onReady(function(){
  918. *
  919. * Group.load(10, {
  920. * success: function(group){
  921. * console.log(group.getGroup().get('name'));
  922. *
  923. * group.groups().each(function(rec){
  924. * console.log(rec.get('name'));
  925. * });
  926. * }
  927. * });
  928. *
  929. * });
  930. *
  931. */
  932. Ext.define('Ext.data.association.Association', {
  933. alternateClassName: 'Ext.data.Association',
  934. /**
  935. * @cfg {String} ownerModel (required)
  936. * The string name of the model that owns the association.
  937. */
  938. /**
  939. * @cfg {String} associatedModel (required)
  940. * The string name of the model that is being associated with.
  941. */
  942. /**
  943. * @cfg {String} primaryKey
  944. * The name of the primary key on the associated model. In general this will be the
  945. * {@link Ext.data.Model#idProperty} of the Model.
  946. */
  947. primaryKey: 'id',
  948. /**
  949. * @cfg {Ext.data.reader.Reader} reader
  950. * A special reader to read associated data
  951. */
  952. /**
  953. * @cfg {String} associationKey
  954. * The name of the property in the data to read the association from. Defaults to the name of the associated model.
  955. */
  956. defaultReaderType: 'json',
  957. statics: {
  958. create: function(association){
  959. if (!association.isAssociation) {
  960. if (Ext.isString(association)) {
  961. association = {
  962. type: association
  963. };
  964. }
  965. switch (association.type) {
  966. case 'belongsTo':
  967. return new Ext.data.association.BelongsTo(association);
  968. case 'hasMany':
  969. return new Ext.data.association.HasMany(association);
  970. case 'hasOne':
  971. return new Ext.data.association.HasOne(association);
  972. //TODO Add this back when it's fixed
  973. // case 'polymorphic':
  974. // return Ext.create('Ext.data.PolymorphicAssociation', association);
  975. default:
  976. Ext.Error.raise('Unknown Association type: "' + association.type + '"');
  977. }
  978. }
  979. return association;
  980. }
  981. },
  982. /**
  983. * Creates the Association object.
  984. * @param {Object} [config] Config object.
  985. */
  986. constructor: function(config) {
  987. Ext.apply(this, config);
  988. var types = Ext.ModelManager.types,
  989. ownerName = config.ownerModel,
  990. associatedName = config.associatedModel,
  991. ownerModel = types[ownerName],
  992. associatedModel = types[associatedName],
  993. ownerProto;
  994. if (ownerModel === undefined) {
  995. Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
  996. }
  997. if (associatedModel === undefined) {
  998. Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
  999. }
  1000. this.ownerModel = ownerModel;
  1001. this.associatedModel = associatedModel;
  1002. /**
  1003. * @property {String} ownerName
  1004. * The name of the model that 'owns' the association
  1005. */
  1006. /**
  1007. * @property {String} associatedName
  1008. * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
  1009. * 'Order')
  1010. */
  1011. Ext.applyIf(this, {
  1012. ownerName : ownerName,
  1013. associatedName: associatedName
  1014. });
  1015. },
  1016. /**
  1017. * Get a specialized reader for reading associated data
  1018. * @return {Ext.data.reader.Reader} The reader, null if not supplied
  1019. */
  1020. getReader: function(){
  1021. var me = this,
  1022. reader = me.reader,
  1023. model = me.associatedModel;
  1024. if (reader) {
  1025. if (Ext.isString(reader)) {
  1026. reader = {
  1027. type: reader
  1028. };
  1029. }
  1030. if (reader.isReader) {
  1031. reader.setModel(model);
  1032. } else {
  1033. Ext.applyIf(reader, {
  1034. model: model,
  1035. type : me.defaultReaderType
  1036. });
  1037. }
  1038. me.reader = Ext.createByAlias('reader.' + reader.type, reader);
  1039. }
  1040. return me.reader || null;
  1041. }
  1042. });
  1043. /**
  1044. * @author Don Griffin
  1045. *
  1046. * This class is a base for all id generators. It also provides lookup of id generators by
  1047. * their id.
  1048. *
  1049. * Generally, id generators are used to generate a primary key for new model instances. There
  1050. * are different approaches to solving this problem, so this mechanism has both simple use
  1051. * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
  1052. * using the {@link Ext.data.Model#idgen} property.
  1053. *
  1054. * # Identity, Type and Shared IdGenerators
  1055. *
  1056. * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
  1057. * This is done by giving IdGenerator instances an id property by which they can be looked
  1058. * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
  1059. * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
  1060. * assign them the same id:
  1061. *
  1062. * Ext.define('MyApp.data.MyModelA', {
  1063. * extend: 'Ext.data.Model',
  1064. * idgen: {
  1065. * type: 'sequential',
  1066. * id: 'foo'
  1067. * }
  1068. * });
  1069. *
  1070. * Ext.define('MyApp.data.MyModelB', {
  1071. * extend: 'Ext.data.Model',
  1072. * idgen: {
  1073. * type: 'sequential',
  1074. * id: 'foo'
  1075. * }
  1076. * });
  1077. *
  1078. * To make this as simple as possible for generator types that are shared by many (or all)
  1079. * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
  1080. * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
  1081. * to its type ('uuid'). In other words, the following Models share the same generator:
  1082. *
  1083. * Ext.define('MyApp.data.MyModelX', {
  1084. * extend: 'Ext.data.Model',
  1085. * idgen: 'uuid'
  1086. * });
  1087. *
  1088. * Ext.define('MyApp.data.MyModelY', {
  1089. * extend: 'Ext.data.Model',
  1090. * idgen: 'uuid'
  1091. * });
  1092. *
  1093. * This can be overridden (by specifying the id explicitly), but there is no particularly
  1094. * good reason to do so for this generator type.
  1095. *
  1096. * # Creating Custom Generators
  1097. *
  1098. * An id generator should derive from this class and implement the {@link #generate} method.
  1099. * The constructor will apply config properties on new instances, so a constructor is often
  1100. * not necessary.
  1101. *
  1102. * To register an id generator type, a derived class should provide an `alias` like so:
  1103. *
  1104. * Ext.define('MyApp.data.CustomIdGenerator', {
  1105. * extend: 'Ext.data.IdGenerator',
  1106. * alias: 'idgen.custom',
  1107. *
  1108. * configProp: 42, // some config property w/default value
  1109. *
  1110. * generate: function () {
  1111. * return ... // a new id
  1112. * }
  1113. * });
  1114. *
  1115. * Using the custom id generator is then straightforward:
  1116. *
  1117. * Ext.define('MyApp.data.MyModel', {
  1118. * extend: 'Ext.data.Model',
  1119. * idgen: 'custom'
  1120. * });
  1121. * // or...
  1122. *
  1123. * Ext.define('MyApp.data.MyModel', {
  1124. * extend: 'Ext.data.Model',
  1125. * idgen: {
  1126. * type: 'custom',
  1127. * configProp: value
  1128. * }
  1129. * });
  1130. *
  1131. * It is not recommended to mix shared generators with generator configuration. This leads
  1132. * to unpredictable results unless all configurations match (which is also redundant). In
  1133. * such cases, a custom generator with a default id is the best approach.
  1134. *
  1135. * Ext.define('MyApp.data.CustomIdGenerator', {
  1136. * extend: 'Ext.data.SequentialIdGenerator',
  1137. * alias: 'idgen.custom',
  1138. *
  1139. * id: 'custom', // shared by default
  1140. *
  1141. * prefix: 'ID_',
  1142. * seed: 1000
  1143. * });
  1144. *
  1145. * Ext.define('MyApp.data.MyModelX', {
  1146. * extend: 'Ext.data.Model',
  1147. * idgen: 'custom'
  1148. * });
  1149. *
  1150. * Ext.define('MyApp.data.MyModelY', {
  1151. * extend: 'Ext.data.Model',
  1152. * idgen: 'custom'
  1153. * });
  1154. *
  1155. * // the above models share a generator that produces ID_1000, ID_1001, etc..
  1156. *
  1157. */
  1158. Ext.define('Ext.data.IdGenerator', {
  1159. /**
  1160. * @property {Boolean} isGenerator
  1161. * `true` in this class to identify an objact as an instantiated IdGenerator, or subclass thereof.
  1162. */
  1163. isGenerator: true,
  1164. /**
  1165. * Initializes a new instance.
  1166. * @param {Object} config (optional) Configuration object to be applied to the new instance.
  1167. */
  1168. constructor: function(config) {
  1169. var me = this;
  1170. Ext.apply(me, config);
  1171. if (me.id) {
  1172. Ext.data.IdGenerator.all[me.id] = me;
  1173. }
  1174. },
  1175. /**
  1176. * @cfg {String} id
  1177. * The id by which to register a new instance. This instance can be found using the
  1178. * {@link Ext.data.IdGenerator#get} static method.
  1179. */
  1180. getRecId: function (rec) {
  1181. return rec.modelName + '-' + rec.internalId;
  1182. },
  1183. /**
  1184. * Generates and returns the next id. This method must be implemented by the derived
  1185. * class.
  1186. *
  1187. * @return {String} The next id.
  1188. * @method generate
  1189. * @abstract
  1190. */
  1191. statics: {
  1192. /**
  1193. * @property {Object} all
  1194. * This object is keyed by id to lookup instances.
  1195. * @private
  1196. * @static
  1197. */
  1198. all: {},
  1199. /**
  1200. * Returns the IdGenerator given its config description.
  1201. * @param {String/Object} config If this parameter is an IdGenerator instance, it is
  1202. * simply returned. If this is a string, it is first used as an id for lookup and
  1203. * then, if there is no match, as a type to create a new instance. This parameter
  1204. * can also be a config object that contains a `type` property (among others) that
  1205. * are used to create and configure the instance.
  1206. * @static
  1207. */
  1208. get: function (config) {
  1209. var generator,
  1210. id,
  1211. type;
  1212. if (typeof config == 'string') {
  1213. id = type = config;
  1214. config = null;
  1215. } else if (config.isGenerator) {
  1216. return config;
  1217. } else {
  1218. id = config.id || config.type;
  1219. type = config.type;
  1220. }
  1221. generator = this.all[id];
  1222. if (!generator) {
  1223. generator = Ext.create('idgen.' + type, config);
  1224. }
  1225. return generator;
  1226. }
  1227. }
  1228. });
  1229. /**
  1230. * @author Ed Spencer
  1231. *
  1232. * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
  1233. * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
  1234. * Operation objects directly.
  1235. *
  1236. * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
  1237. */
  1238. Ext.define('Ext.data.Operation', {
  1239. /**
  1240. * @cfg {Boolean} synchronous
  1241. * True if this Operation is to be executed synchronously. This property is inspected by a
  1242. * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
  1243. */
  1244. synchronous: true,
  1245. /**
  1246. * @cfg {String} action
  1247. * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
  1248. */
  1249. action: undefined,
  1250. /**
  1251. * @cfg {Ext.util.Filter[]} filters
  1252. * Optional array of filter objects. Only applies to 'read' actions.
  1253. */
  1254. filters: undefined,
  1255. /**
  1256. * @cfg {Ext.util.Sorter[]} sorters
  1257. * Optional array of sorter objects. Only applies to 'read' actions.
  1258. */
  1259. sorters: undefined,
  1260. /**
  1261. * @cfg {Ext.util.Grouper[]} groupers
  1262. * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
  1263. */
  1264. groupers: undefined,
  1265. /**
  1266. * @cfg {Number} start
  1267. * The start index (offset), used in paging when running a 'read' action.
  1268. */
  1269. start: undefined,
  1270. /**
  1271. * @cfg {Number} limit
  1272. * The number of records to load. Used on 'read' actions when paging is being used.
  1273. */
  1274. limit: undefined,
  1275. /**
  1276. * @cfg {Ext.data.Batch} batch
  1277. * The batch that this Operation is a part of.
  1278. */
  1279. batch: undefined,
  1280. /**
  1281. * @cfg {Function} callback
  1282. * Function to execute when operation completed.
  1283. * @cfg {Ext.data.Model[]} callback.records Array of records.
  1284. * @cfg {Ext.data.Operation} callback.operation The Operation itself.
  1285. * @cfg {Boolean} callback.success True when operation completed successfully.
  1286. */
  1287. callback: undefined,
  1288. /**
  1289. * @cfg {Object} scope
  1290. * Scope for the {@link #callback} function.
  1291. */
  1292. scope: undefined,
  1293. /**
  1294. * @property {Boolean} started
  1295. * The start status of this Operation. Use {@link #isStarted}.
  1296. * @readonly
  1297. * @private
  1298. */
  1299. started: false,
  1300. /**
  1301. * @property {Boolean} running
  1302. * The run status of this Operation. Use {@link #isRunning}.
  1303. * @readonly
  1304. * @private
  1305. */
  1306. running: false,
  1307. /**
  1308. * @property {Boolean} complete
  1309. * The completion status of this Operation. Use {@link #isComplete}.
  1310. * @readonly
  1311. * @private
  1312. */
  1313. complete: false,
  1314. /**
  1315. * @property {Boolean} success
  1316. * Whether the Operation was successful or not. This starts as undefined and is set to true
  1317. * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
  1318. * {@link #wasSuccessful} to query success status.
  1319. * @readonly
  1320. * @private
  1321. */
  1322. success: undefined,
  1323. /**
  1324. * @property {Boolean} exception
  1325. * The exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
  1326. * @readonly
  1327. * @private
  1328. */
  1329. exception: false,
  1330. /**
  1331. * @property {String/Object} error
  1332. * The error object passed when {@link #setException} was called. This could be any object or primitive.
  1333. * @private
  1334. */
  1335. error: undefined,
  1336. /**
  1337. * @property {RegExp} actionCommitRecordsRe
  1338. * The RegExp used to categorize actions that require record commits.
  1339. */
  1340. actionCommitRecordsRe: /^(?:create|update)$/i,
  1341. /**
  1342. * @property {RegExp} actionSkipSyncRe
  1343. * The RegExp used to categorize actions that skip local record synchronization. This defaults
  1344. * to match 'destroy'.
  1345. */
  1346. actionSkipSyncRe: /^destroy$/i,
  1347. /**
  1348. * Creates new Operation object.
  1349. * @param {Object} config (optional) Config object.
  1350. */
  1351. constructor: function(config) {
  1352. Ext.apply(this, config || {});
  1353. },
  1354. /**
  1355. * This method is called to commit data to this instance's records given the records in
  1356. * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
  1357. * those records (for 'create' and 'update' actions).
  1358. *
  1359. * If this {@link #action} is 'destroy', any server records are ignored and the
  1360. * {@link Ext.data.Model#commit} method is not called.
  1361. *
  1362. * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
  1363. * the server.
  1364. * @markdown
  1365. */
  1366. commitRecords: function (serverRecords) {
  1367. var me = this,
  1368. mc, index, clientRecords, serverRec, clientRec;
  1369. if (!me.actionSkipSyncRe.test(me.action)) {
  1370. clientRecords = me.records;
  1371. if (clientRecords && clientRecords.length) {
  1372. if(clientRecords.length > 1) {
  1373. // if this operation has multiple records, client records need to be matched up with server records
  1374. // so that any data returned from the server can be updated in the client records.
  1375. mc = new Ext.util.MixedCollection();
  1376. mc.addAll(serverRecords);
  1377. for (index = clientRecords.length; index--; ) {
  1378. clientRec = clientRecords[index];
  1379. serverRec = mc.findBy(function(record) {
  1380. var clientRecordId = clientRec.getId();
  1381. if(clientRecordId && record.getId() === clientRecordId) {
  1382. return true;
  1383. }
  1384. // if the server record cannot be found by id, find by internalId.
  1385. // this allows client records that did not previously exist on the server
  1386. // to be updated with the correct server id and data.
  1387. return record.internalId === clientRec.internalId;
  1388. });
  1389. // replace client record data with server record data
  1390. me.updateClientRecord(clientRec, serverRec);
  1391. }
  1392. } else {
  1393. // operation only has one record, so just match the first client record up with the first server record
  1394. clientRec = clientRecords[0];
  1395. serverRec = serverRecords[0];
  1396. // if the client record is not a phantom, make sure the ids match before replacing the client data with server data.
  1397. if(serverRec && (clientRec.phantom || clientRec.getId() === serverRec.getId())) {
  1398. me.updateClientRecord(clientRec, serverRec);
  1399. }
  1400. }
  1401. if (me.actionCommitRecordsRe.test(me.action)) {
  1402. for (index = clientRecords.length; index--; ) {
  1403. clientRecords[index].commit();
  1404. }
  1405. }
  1406. }
  1407. }
  1408. },
  1409. /**
  1410. * Replaces the data in a client record with the data from a server record. If either record is undefined, does nothing.
  1411. * Since non-persistent fields will have default values in the server record, this method only replaces data for persistent
  1412. * fields to avoid overwriting the client record's data with default values from the server record.
  1413. * @private
  1414. * @param {Ext.data.Model} [clientRecord]
  1415. * @param {Ext.data.Model} [serverRecord]
  1416. */
  1417. updateClientRecord: function(clientRecord, serverRecord) {
  1418. if (clientRecord && serverRecord) {
  1419. clientRecord.beginEdit();
  1420. var fields = clientRecord.fields.items,
  1421. fLen = fields.length,
  1422. field, f;
  1423. for (f = 0; f < fLen; f++) {
  1424. field = fields[f];
  1425. if (field.persist) {
  1426. clientRecord.set(field.name, serverRecord.get(field.name));
  1427. }
  1428. }
  1429. if(clientRecord.phantom) {
  1430. clientRecord.setId(serverRecord.getId());
  1431. }
  1432. clientRecord.endEdit(true);
  1433. }
  1434. },
  1435. /**
  1436. * Marks the Operation as started.
  1437. */
  1438. setStarted: function() {
  1439. this.started = true;
  1440. this.running = true;
  1441. },
  1442. /**
  1443. * Marks the Operation as completed.
  1444. */
  1445. setCompleted: function() {
  1446. this.complete = true;
  1447. this.running = false;
  1448. },
  1449. /**
  1450. * Marks the Operation as successful.
  1451. */
  1452. setSuccessful: function() {
  1453. this.success = true;
  1454. },
  1455. /**
  1456. * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
  1457. * @param {String/Object} error (optional) error string/object
  1458. */
  1459. setException: function(error) {
  1460. this.exception = true;
  1461. this.success = false;
  1462. this.running = false;
  1463. this.error = error;
  1464. },
  1465. /**
  1466. * Returns true if this Operation encountered an exception (see also {@link #getError})
  1467. * @return {Boolean} True if there was an exception
  1468. */
  1469. hasException: function() {
  1470. return this.exception === true;
  1471. },
  1472. /**
  1473. * Returns the error string or object that was set using {@link #setException}
  1474. * @return {String/Object} The error object
  1475. */
  1476. getError: function() {
  1477. return this.error;
  1478. },
  1479. /**
  1480. * Returns the {@link Ext.data.Model record}s associated with this operation. For read operations the records as set by the {@link Ext.data.proxy.Proxy Proxy} will be returned (returns `null` if the proxy has not yet set the records).
  1481. * For create, update, and destroy operations the operation's initially configured records will be returned, although the proxy may modify these records' data at some point after the operation is initialized.
  1482. * @return {Ext.data.Model[]}
  1483. */
  1484. getRecords: function() {
  1485. var resultSet = this.getResultSet();
  1486. return this.records || (resultSet ? resultSet.records : null);
  1487. },
  1488. /**
  1489. * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
  1490. * instances as well as meta data such as number of instances fetched, number available etc
  1491. * @return {Ext.data.ResultSet} The ResultSet object
  1492. */
  1493. getResultSet: function() {
  1494. return this.resultSet;
  1495. },
  1496. /**
  1497. * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
  1498. * {@link #isRunning} to test if the Operation is currently running.
  1499. * @return {Boolean} True if the Operation has started
  1500. */
  1501. isStarted: function() {
  1502. return this.started === true;
  1503. },
  1504. /**
  1505. * Returns true if the Operation has been started but has not yet completed.
  1506. * @return {Boolean} True if the Operation is currently running
  1507. */
  1508. isRunning: function() {
  1509. return this.running === true;
  1510. },
  1511. /**
  1512. * Returns true if the Operation has been completed
  1513. * @return {Boolean} True if the Operation is complete
  1514. */
  1515. isComplete: function() {
  1516. return this.complete === true;
  1517. },
  1518. /**
  1519. * Returns true if the Operation has completed and was successful
  1520. * @return {Boolean} True if successful
  1521. */
  1522. wasSuccessful: function() {
  1523. return this.isComplete() && this.success === true;
  1524. },
  1525. /**
  1526. * @private
  1527. * Associates this Operation with a Batch
  1528. * @param {Ext.data.Batch} batch The batch
  1529. */
  1530. setBatch: function(batch) {
  1531. this.batch = batch;
  1532. },
  1533. /**
  1534. * Checks whether this operation should cause writing to occur.
  1535. * @return {Boolean} Whether the operation should cause a write to occur.
  1536. */
  1537. allowWrite: function() {
  1538. return this.action != 'read';
  1539. }
  1540. });
  1541. /**
  1542. * @author Ed Spencer
  1543. *
  1544. * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
  1545. * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
  1546. */
  1547. Ext.define('Ext.data.validations', {
  1548. singleton: true,
  1549. /**
  1550. * @property {String} presenceMessage
  1551. * The default error message used when a presence validation fails.
  1552. */
  1553. presenceMessage: 'must be present',
  1554. /**
  1555. * @property {String} lengthMessage
  1556. * The default error message used when a length validation fails.
  1557. */
  1558. lengthMessage: 'is the wrong length',
  1559. /**
  1560. * @property {Boolean} formatMessage
  1561. * The default error message used when a format validation fails.
  1562. */
  1563. formatMessage: 'is the wrong format',
  1564. /**
  1565. * @property {String} inclusionMessage
  1566. * The default error message used when an inclusion validation fails.
  1567. */
  1568. inclusionMessage: 'is not included in the list of acceptable values',
  1569. /**
  1570. * @property {String} exclusionMessage
  1571. * The default error message used when an exclusion validation fails.
  1572. */
  1573. exclusionMessage: 'is not an acceptable value',
  1574. /**
  1575. * @property {String} emailMessage
  1576. * The default error message used when an email validation fails
  1577. */
  1578. emailMessage: 'is not a valid email address',
  1579. /**
  1580. * @property {RegExp} emailRe
  1581. * The regular expression used to validate email addresses
  1582. */
  1583. emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
  1584. /**
  1585. * Validates that the given value is present.
  1586. * For example:
  1587. *
  1588. * validations: [{type: 'presence', field: 'age'}]
  1589. *
  1590. * @param {Object} config Config object
  1591. * @param {Object} value The value to validate
  1592. * @return {Boolean} True if validation passed
  1593. */
  1594. presence: function(config, value) {
  1595. if (value === undefined) {
  1596. value = config;
  1597. }
  1598. //we need an additional check for zero here because zero is an acceptable form of present data
  1599. return !!value || value === 0;
  1600. },
  1601. /**
  1602. * Returns true if the given value is between the configured min and max values.
  1603. * For example:
  1604. *
  1605. * validations: [{type: 'length', field: 'name', min: 2}]
  1606. *
  1607. * @param {Object} config Config object
  1608. * @param {String} value The value to validate
  1609. * @return {Boolean} True if the value passes validation
  1610. */
  1611. length: function(config, value) {
  1612. if (value === undefined || value === null) {
  1613. return false;
  1614. }
  1615. var length = value.length,
  1616. min = config.min,
  1617. max = config.max;
  1618. if ((min && length < min) || (max && length > max)) {
  1619. return false;
  1620. } else {
  1621. return true;
  1622. }
  1623. },
  1624. /**
  1625. * Validates that an email string is in the correct format
  1626. * @param {Object} config Config object
  1627. * @param {String} email The email address
  1628. * @return {Boolean} True if the value passes validation
  1629. */
  1630. email: function(config, email) {
  1631. return Ext.data.validations.emailRe.test(email);
  1632. },
  1633. /**
  1634. * Returns true if the given value passes validation against the configured `matcher` regex.
  1635. * For example:
  1636. *
  1637. * validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
  1638. *
  1639. * @param {Object} config Config object
  1640. * @param {String} value The value to validate
  1641. * @return {Boolean} True if the value passes the format validation
  1642. */
  1643. format: function(config, value) {
  1644. return !!(config.matcher && config.matcher.test(value));
  1645. },
  1646. /**
  1647. * Validates that the given value is present in the configured `list`.
  1648. * For example:
  1649. *
  1650. * validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
  1651. *
  1652. * @param {Object} config Config object
  1653. * @param {String} value The value to validate
  1654. * @return {Boolean} True if the value is present in the list
  1655. */
  1656. inclusion: function(config, value) {
  1657. return config.list && Ext.Array.indexOf(config.list,value) != -1;
  1658. },
  1659. /**
  1660. * Validates that the given value is present in the configured `list`.
  1661. * For example:
  1662. *
  1663. * validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
  1664. *
  1665. * @param {Object} config Config object
  1666. * @param {String} value The value to validate
  1667. * @return {Boolean} True if the value is not present in the list
  1668. */
  1669. exclusion: function(config, value) {
  1670. return config.list && Ext.Array.indexOf(config.list,value) == -1;
  1671. }
  1672. });
  1673. /**
  1674. * @class Ext.util.HashMap
  1675. * <p>
  1676. * Represents a collection of a set of key and value pairs. Each key in the HashMap
  1677. * must be unique, the same key cannot exist twice. Access to items is provided via
  1678. * the key only. Sample usage:
  1679. * <pre><code>
  1680. var map = new Ext.util.HashMap();
  1681. map.add('key1', 1);
  1682. map.add('key2', 2);
  1683. map.add('key3', 3);
  1684. map.each(function(key, value, length){
  1685. console.log(key, value, length);
  1686. });
  1687. * </code></pre>
  1688. * </p>
  1689. *
  1690. * <p>The HashMap is an unordered class,
  1691. * there is no guarantee when iterating over the items that they will be in any particular
  1692. * order. If this is required, then use a {@link Ext.util.MixedCollection}.
  1693. * </p>
  1694. */
  1695. Ext.define('Ext.util.HashMap', {
  1696. mixins: {
  1697. observable: 'Ext.util.Observable'
  1698. },
  1699. /**
  1700. * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
  1701. * A default is provided that returns the <b>id</b> property on the object. This function is only used
  1702. * if the add method is called with a single argument.
  1703. */
  1704. /**
  1705. * Creates new HashMap.
  1706. * @param {Object} config (optional) Config object.
  1707. */
  1708. constructor: function(config) {
  1709. config = config || {};
  1710. var me = this,
  1711. keyFn = config.keyFn;
  1712. me.addEvents(
  1713. /**
  1714. * @event add
  1715. * Fires when a new item is added to the hash
  1716. * @param {Ext.util.HashMap} this.
  1717. * @param {String} key The key of the added item.
  1718. * @param {Object} value The value of the added item.
  1719. */
  1720. 'add',
  1721. /**
  1722. * @event clear
  1723. * Fires when the hash is cleared.
  1724. * @param {Ext.util.HashMap} this.
  1725. */
  1726. 'clear',
  1727. /**
  1728. * @event remove
  1729. * Fires when an item is removed from the hash.
  1730. * @param {Ext.util.HashMap} this.
  1731. * @param {String} key The key of the removed item.
  1732. * @param {Object} value The value of the removed item.
  1733. */
  1734. 'remove',
  1735. /**
  1736. * @event replace
  1737. * Fires when an item is replaced in the hash.
  1738. * @param {Ext.util.HashMap} this.
  1739. * @param {String} key The key of the replaced item.
  1740. * @param {Object} value The new value for the item.
  1741. * @param {Object} old The old value for the item.
  1742. */
  1743. 'replace'
  1744. );
  1745. me.mixins.observable.constructor.call(me, config);
  1746. me.clear(true);
  1747. if (keyFn) {
  1748. me.getKey = keyFn;
  1749. }
  1750. },
  1751. /**
  1752. * Gets the number of items in the hash.
  1753. * @return {Number} The number of items in the hash.
  1754. */
  1755. getCount: function() {
  1756. return this.length;
  1757. },
  1758. /**
  1759. * Implementation for being able to extract the key from an object if only
  1760. * a single argument is passed.
  1761. * @private
  1762. * @param {String} key The key
  1763. * @param {Object} value The value
  1764. * @return {Array} [key, value]
  1765. */
  1766. getData: function(key, value) {
  1767. // if we have no value, it means we need to get the key from the object
  1768. if (value === undefined) {
  1769. value = key;
  1770. key = this.getKey(value);
  1771. }
  1772. return [key, value];
  1773. },
  1774. /**
  1775. * Extracts the key from an object. This is a default implementation, it may be overridden
  1776. * @param {Object} o The object to get the key from
  1777. * @return {String} The key to use.
  1778. */
  1779. getKey: function(o) {
  1780. return o.id;
  1781. },
  1782. /**
  1783. * Adds an item to the collection. Fires the {@link #event-add} event when complete.
  1784. * @param {String} key <p>The key to associate with the item, or the new item.</p>
  1785. * <p>If a {@link #getKey} implementation was specified for this HashMap,
  1786. * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
  1787. * the HashMap will be able to <i>derive</i> the key for the new item.
  1788. * In this case just pass the new item in this parameter.</p>
  1789. * @param {Object} o The item to add.
  1790. * @return {Object} The item added.
  1791. */
  1792. add: function(key, value) {
  1793. var me = this,
  1794. data;
  1795. if (arguments.length === 1) {
  1796. value = key;
  1797. key = me.getKey(value);
  1798. }
  1799. if (me.containsKey(key)) {
  1800. return me.replace(key, value);
  1801. }
  1802. data = me.getData(key, value);
  1803. key = data[0];
  1804. value = data[1];
  1805. me.map[key] = value;
  1806. ++me.length;
  1807. if (me.hasListeners.add) {
  1808. me.fireEvent('add', me, key, value);
  1809. }
  1810. return value;
  1811. },
  1812. /**
  1813. * Replaces an item in the hash. If the key doesn't exist, the
  1814. * {@link #method-add} method will be used.
  1815. * @param {String} key The key of the item.
  1816. * @param {Object} value The new value for the item.
  1817. * @return {Object} The new value of the item.
  1818. */
  1819. replace: function(key, value) {
  1820. var me = this,
  1821. map = me.map,
  1822. old;
  1823. if (!me.containsKey(key)) {
  1824. me.add(key, value);
  1825. }
  1826. old = map[key];
  1827. map[key] = value;
  1828. if (me.hasListeners.replace) {
  1829. me.fireEvent('replace', me, key, value, old);
  1830. }
  1831. return value;
  1832. },
  1833. /**
  1834. * Remove an item from the hash.
  1835. * @param {Object} o The value of the item to remove.
  1836. * @return {Boolean} True if the item was successfully removed.
  1837. */
  1838. remove: function(o) {
  1839. var key = this.findKey(o);
  1840. if (key !== undefined) {
  1841. return this.removeAtKey(key);
  1842. }
  1843. return false;
  1844. },
  1845. /**
  1846. * Remove an item from the hash.
  1847. * @param {String} key The key to remove.
  1848. * @return {Boolean} True if the item was successfully removed.
  1849. */
  1850. removeAtKey: function(key) {
  1851. var me = this,
  1852. value;
  1853. if (me.containsKey(key)) {
  1854. value = me.map[key];
  1855. delete me.map[key];
  1856. --me.length;
  1857. if (me.hasListeners.remove) {
  1858. me.fireEvent('remove', me, key, value);
  1859. }
  1860. return true;
  1861. }
  1862. return false;
  1863. },
  1864. /**
  1865. * Retrieves an item with a particular key.
  1866. * @param {String} key The key to lookup.
  1867. * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
  1868. */
  1869. get: function(key) {
  1870. return this.map[key];
  1871. },
  1872. /**
  1873. * Removes all items from the hash.
  1874. * @return {Ext.util.HashMap} this
  1875. */
  1876. clear: function(/* private */ initial) {
  1877. var me = this;
  1878. me.map = {};
  1879. me.length = 0;
  1880. if (initial !== true && me.hasListeners.clear) {
  1881. me.fireEvent('clear', me);
  1882. }
  1883. return me;
  1884. },
  1885. /**
  1886. * Checks whether a key exists in the hash.
  1887. * @param {String} key The key to check for.
  1888. * @return {Boolean} True if they key exists in the hash.
  1889. */
  1890. containsKey: function(key) {
  1891. return this.map[key] !== undefined;
  1892. },
  1893. /**
  1894. * Checks whether a value exists in the hash.
  1895. * @param {Object} value The value to check for.
  1896. * @return {Boolean} True if the value exists in the dictionary.
  1897. */
  1898. contains: function(value) {
  1899. return this.containsKey(this.findKey(value));
  1900. },
  1901. /**
  1902. * Return all of the keys in the hash.
  1903. * @return {Array} An array of keys.
  1904. */
  1905. getKeys: function() {
  1906. return this.getArray(true);
  1907. },
  1908. /**
  1909. * Return all of the values in the hash.
  1910. * @return {Array} An array of values.
  1911. */
  1912. getValues: function() {
  1913. return this.getArray(false);
  1914. },
  1915. /**
  1916. * Gets either the keys/values in an array from the hash.
  1917. * @private
  1918. * @param {Boolean} isKey True to extract the keys, otherwise, the value
  1919. * @return {Array} An array of either keys/values from the hash.
  1920. */
  1921. getArray: function(isKey) {
  1922. var arr = [],
  1923. key,
  1924. map = this.map;
  1925. for (key in map) {
  1926. if (map.hasOwnProperty(key)) {
  1927. arr.push(isKey ? key: map[key]);
  1928. }
  1929. }
  1930. return arr;
  1931. },
  1932. /**
  1933. * Executes the specified function once for each item in the hash.
  1934. * Returning false from the function will cease iteration.
  1935. *
  1936. * The paramaters passed to the function are:
  1937. * <div class="mdetail-params"><ul>
  1938. * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
  1939. * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
  1940. * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
  1941. * </ul></div>
  1942. * @param {Function} fn The function to execute.
  1943. * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
  1944. * @return {Ext.util.HashMap} this
  1945. */
  1946. each: function(fn, scope) {
  1947. // copy items so they may be removed during iteration.
  1948. var items = Ext.apply({}, this.map),
  1949. key,
  1950. length = this.length;
  1951. scope = scope || this;
  1952. for (key in items) {
  1953. if (items.hasOwnProperty(key)) {
  1954. if (fn.call(scope, key, items[key], length) === false) {
  1955. break;
  1956. }
  1957. }
  1958. }
  1959. return this;
  1960. },
  1961. /**
  1962. * Performs a shallow copy on this hash.
  1963. * @return {Ext.util.HashMap} The new hash object.
  1964. */
  1965. clone: function() {
  1966. var hash = new this.self(),
  1967. map = this.map,
  1968. key;
  1969. hash.suspendEvents();
  1970. for (key in map) {
  1971. if (map.hasOwnProperty(key)) {
  1972. hash.add(key, map[key]);
  1973. }
  1974. }
  1975. hash.resumeEvents();
  1976. return hash;
  1977. },
  1978. /**
  1979. * @private
  1980. * Find the key for a value.
  1981. * @param {Object} value The value to find.
  1982. * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
  1983. */
  1984. findKey: function(value) {
  1985. var key,
  1986. map = this.map;
  1987. for (key in map) {
  1988. if (map.hasOwnProperty(key) && map[key] === value) {
  1989. return key;
  1990. }
  1991. }
  1992. return undefined;
  1993. }
  1994. });
  1995. /**
  1996. * @class Ext.data.SortTypes
  1997. * This class defines a series of static methods that are used on a
  1998. * {@link Ext.data.Field} for performing sorting. The methods cast the
  1999. * underlying values into a data type that is appropriate for sorting on
  2000. * that particular field. If a {@link Ext.data.Field#type} is specified,
  2001. * the sortType will be set to a sane default if the sortType is not
  2002. * explicitly defined on the field. The sortType will make any necessary
  2003. * modifications to the value and return it.
  2004. * <ul>
  2005. * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
  2006. * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
  2007. * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
  2008. * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
  2009. * <li><b>asFloat</b> - Converts the value to a floating point number</li>
  2010. * <li><b>asInt</b> - Converts the value to an integer number</li>
  2011. * </ul>
  2012. * <p>
  2013. * It is also possible to create a custom sortType that can be used throughout
  2014. * an application.
  2015. * <pre><code>
  2016. Ext.apply(Ext.data.SortTypes, {
  2017. asPerson: function(person){
  2018. // expects an object with a first and last name property
  2019. return person.lastName.toUpperCase() + person.firstName.toLowerCase();
  2020. }
  2021. });
  2022. Ext.define('Employee', {
  2023. extend: 'Ext.data.Model',
  2024. fields: [{
  2025. name: 'person',
  2026. sortType: 'asPerson'
  2027. }, {
  2028. name: 'salary',
  2029. type: 'float' // sortType set to asFloat
  2030. }]
  2031. });
  2032. * </code></pre>
  2033. * </p>
  2034. * @singleton
  2035. * @docauthor Evan Trimboli <evan@sencha.com>
  2036. */
  2037. Ext.define('Ext.data.SortTypes', {
  2038. singleton: true,
  2039. /**
  2040. * Default sort that does nothing
  2041. * @param {Object} s The value being converted
  2042. * @return {Object} The comparison value
  2043. */
  2044. none : function(s) {
  2045. return s;
  2046. },
  2047. /**
  2048. * The regular expression used to strip tags
  2049. * @type {RegExp}
  2050. * @property
  2051. */
  2052. stripTagsRE : /<\/?[^>]+>/gi,
  2053. /**
  2054. * Strips all HTML tags to sort on text only
  2055. * @param {Object} s The value being converted
  2056. * @return {String} The comparison value
  2057. */
  2058. asText : function(s) {
  2059. return String(s).replace(this.stripTagsRE, "");
  2060. },
  2061. /**
  2062. * Strips all HTML tags to sort on text only - Case insensitive
  2063. * @param {Object} s The value being converted
  2064. * @return {String} The comparison value
  2065. */
  2066. asUCText : function(s) {
  2067. return String(s).toUpperCase().replace(this.stripTagsRE, "");
  2068. },
  2069. /**
  2070. * Case insensitive string
  2071. * @param {Object} s The value being converted
  2072. * @return {String} The comparison value
  2073. */
  2074. asUCString : function(s) {
  2075. return String(s).toUpperCase();
  2076. },
  2077. /**
  2078. * Date sorting
  2079. * @param {Object} s The value being converted
  2080. * @return {Number} The comparison value
  2081. */
  2082. asDate : function(s) {
  2083. if(!s){
  2084. return 0;
  2085. }
  2086. if(Ext.isDate(s)){
  2087. return s.getTime();
  2088. }
  2089. return Date.parse(String(s));
  2090. },
  2091. /**
  2092. * Float sorting
  2093. * @param {Object} s The value being converted
  2094. * @return {Number} The comparison value
  2095. */
  2096. asFloat : function(s) {
  2097. var val = parseFloat(String(s).replace(/,/g, ""));
  2098. return isNaN(val) ? 0 : val;
  2099. },
  2100. /**
  2101. * Integer sorting
  2102. * @param {Object} s The value being converted
  2103. * @return {Number} The comparison value
  2104. */
  2105. asInt : function(s) {
  2106. var val = parseInt(String(s).replace(/,/g, ""), 10);
  2107. return isNaN(val) ? 0 : val;
  2108. }
  2109. });
  2110. /**
  2111. * A mixin to add floating capability to a Component.
  2112. */
  2113. Ext.define('Ext.util.Floating', {
  2114. uses: ['Ext.Layer', 'Ext.window.Window'],
  2115. /**
  2116. * @cfg {Boolean} focusOnToFront
  2117. * Specifies whether the floated component should be automatically {@link Ext.Component#method-focus focused} when
  2118. * it is {@link #toFront brought to the front}.
  2119. */
  2120. focusOnToFront: true,
  2121. /**
  2122. * @cfg {String/Boolean} shadow
  2123. * Specifies whether the floating component should be given a shadow. Set to true to automatically create an
  2124. * {@link Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to
  2125. * disable the shadow.
  2126. */
  2127. shadow: 'sides',
  2128. /**
  2129. * @cfg {String/Boolean} shadowOffset
  2130. * Number of pixels to offset the shadow.
  2131. */
  2132. constructor: function (dom) {
  2133. var me = this;
  2134. me.el = new Ext.Layer(Ext.apply({
  2135. hideMode : me.hideMode,
  2136. hidden : me.hidden,
  2137. shadow : (typeof me.shadow != 'undefined') ? me.shadow : 'sides',
  2138. shadowOffset : me.shadowOffset,
  2139. constrain : false,
  2140. shim : (me.shim === false) ? false : undefined
  2141. }, me.floating), dom);
  2142. // release config object (if it was one)
  2143. me.floating = true;
  2144. // Register with the configured ownerCt.
  2145. // With this we acquire a floatParent for relative positioning, and a zIndexParent which is an
  2146. // ancestor floater which provides zIndex management.
  2147. me.registerWithOwnerCt();
  2148. },
  2149. registerWithOwnerCt: function() {
  2150. var me = this;
  2151. if (me.zIndexParent) {
  2152. me.zIndexParent.unregisterFloatingItem(me);
  2153. }
  2154. // Acquire a zIndexParent by traversing the ownerCt axis for the nearest floating ancestor
  2155. me.zIndexParent = me.up('[floating]');
  2156. me.setFloatParent(me.ownerCt);
  2157. delete me.ownerCt;
  2158. if (me.zIndexParent) {
  2159. me.zIndexParent.registerFloatingItem(me);
  2160. } else {
  2161. Ext.WindowManager.register(me);
  2162. }
  2163. },
  2164. setFloatParent: function(floatParent) {
  2165. var me = this;
  2166. // Remove listeners from previous floatParent
  2167. if (me.floatParent) {
  2168. me.mun(me.floatParent, {
  2169. hide: me.onFloatParentHide,
  2170. show: me.onFloatParentShow,
  2171. scope: me
  2172. });
  2173. }
  2174. me.floatParent = floatParent;
  2175. // Floating Components as children of Containers must hide when their parent hides.
  2176. if (floatParent) {
  2177. me.mon(me.floatParent, {
  2178. hide: me.onFloatParentHide,
  2179. show: me.onFloatParentShow,
  2180. scope: me
  2181. });
  2182. }
  2183. // If a floating Component is configured to be constrained, but has no configured
  2184. // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
  2185. if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
  2186. me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
  2187. }
  2188. },
  2189. onFloatParentHide: function() {
  2190. var me = this;
  2191. if (me.hideOnParentHide !== false && me.isVisible()) {
  2192. me.hide();
  2193. me.showOnParentShow = true;
  2194. }
  2195. },
  2196. onFloatParentShow: function() {
  2197. if (this.showOnParentShow) {
  2198. delete this.showOnParentShow;
  2199. this.show();
  2200. }
  2201. },
  2202. // private
  2203. // z-index is managed by the zIndexManager and may be overwritten at any time.
  2204. // Returns the next z-index to be used.
  2205. // If this is a Container, then it will have rebased any managed floating Components,
  2206. // and so the next available z-index will be approximately 10000 above that.
  2207. setZIndex: function(index) {
  2208. var me = this;
  2209. me.el.setZIndex(index);
  2210. // Next item goes 10 above;
  2211. index += 10;
  2212. // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
  2213. // The returned value is a round number approximately 10000 above the last z-index used.
  2214. if (me.floatingItems) {
  2215. index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
  2216. }
  2217. return index;
  2218. },
  2219. /**
  2220. * Moves this floating Component into a constrain region.
  2221. *
  2222. * By default, this Component is constrained to be within the container it was added to, or the element it was
  2223. * rendered to.
  2224. *
  2225. * An alternative constraint may be passed.
  2226. * @param {String/HTMLElement/Ext.Element/Ext.util.Region} [constrainTo] The Element or {@link Ext.util.Region Region}
  2227. * into which this Component is to be constrained. Defaults to the element into which this floating Component
  2228. * was rendered.
  2229. */
  2230. doConstrain: function(constrainTo) {
  2231. var me = this,
  2232. // Calculate the constrain vector to coerce our position to within our
  2233. // constrainTo setting. getConstrainVector will provide a default constraint
  2234. // region if there is no explicit constrainTo, *and* there is no floatParent owner Component.
  2235. vector = me.getConstrainVector(constrainTo),
  2236. xy;
  2237. if (vector) {
  2238. xy = me.getPosition();
  2239. xy[0] += vector[0];
  2240. xy[1] += vector[1];
  2241. me.setPosition(xy);
  2242. }
  2243. },
  2244. /**
  2245. * Gets the x/y offsets to constrain this float
  2246. * @private
  2247. * @param {String/HTMLElement/Ext.Element/Ext.util.Region} [constrainTo] The Element or {@link Ext.util.Region Region}
  2248. * into which this Component is to be constrained.
  2249. * @return {Number[]} The x/y constraints
  2250. */
  2251. getConstrainVector: function(constrainTo){
  2252. var me = this;
  2253. if (me.constrain || me.constrainHeader) {
  2254. constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container || me.el.getScopeParent();
  2255. return (me.constrainHeader ? me.header.el : me.el).getConstrainVector(constrainTo);
  2256. }
  2257. },
  2258. /**
  2259. * Aligns this floating Component to the specified element
  2260. *
  2261. * @param {Ext.Component/Ext.Element/HTMLElement/String} element
  2262. * The element or {@link Ext.Component} to align to. If passing a component, it must be a
  2263. * component instance. If a string id is passed, it will be used as an element id.
  2264. * @param {String} [position="tl-bl?"] The position to align to
  2265. * (see {@link Ext.Element#alignTo} for more details).
  2266. * @param {Number[]} [offsets] Offset the positioning by [x, y]
  2267. * @return {Ext.Component} this
  2268. */
  2269. alignTo: function(element, position, offsets) {
  2270. // element may be a Component, so first attempt to use its el to align to.
  2271. // When aligning to an Element's X,Y position, we must use setPagePosition which disregards any floatParent
  2272. this.setPagePosition(this.el.getAlignToXY(element.el || element, position, offsets));
  2273. return this;
  2274. },
  2275. /**
  2276. * Brings this floating Component to the front of any other visible, floating Components managed by the same
  2277. * {@link Ext.ZIndexManager ZIndexManager}
  2278. *
  2279. * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
  2280. *
  2281. * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
  2282. * @return {Ext.Component} this
  2283. */
  2284. toFront: function(preventFocus) {
  2285. var me = this;
  2286. // Find the floating Component which provides the base for this Component's zIndexing.
  2287. // That must move to front to then be able to rebase its zIndex stack and move this to the front
  2288. if (me.zIndexParent && me.bringParentToFront !== false) {
  2289. me.zIndexParent.toFront(true);
  2290. }
  2291. if (!Ext.isDefined(preventFocus)) {
  2292. preventFocus = !me.focusOnToFront;
  2293. }
  2294. if (preventFocus) {
  2295. me.preventFocusOnActivate = true;
  2296. }
  2297. if (me.zIndexManager.bringToFront(me)) {
  2298. if (!preventFocus) {
  2299. // Kick off a delayed focus request.
  2300. // If another floating Component is toFronted before the delay expires
  2301. // this will not receive focus.
  2302. me.focus(false, true);
  2303. }
  2304. }
  2305. delete me.preventFocusOnActivate;
  2306. return me;
  2307. },
  2308. /**
  2309. * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
  2310. * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
  2311. *
  2312. * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
  2313. *
  2314. * This method also fires the {@link Ext.Component#activate activate} or
  2315. * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
  2316. *
  2317. * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
  2318. * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
  2319. */
  2320. setActive: function(active, newActive) {
  2321. var me = this;
  2322. if (active) {
  2323. if (me.el.shadow && !me.maximized) {
  2324. me.el.enableShadow(true);
  2325. }
  2326. if (me.modal && !me.preventFocusOnActivate) {
  2327. me.focus(false, true);
  2328. }
  2329. me.fireEvent('activate', me);
  2330. } else {
  2331. // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
  2332. // can keep their shadows all the time
  2333. if (me.isWindow && (newActive && newActive.isWindow)) {
  2334. me.el.disableShadow();
  2335. }
  2336. me.fireEvent('deactivate', me);
  2337. }
  2338. },
  2339. /**
  2340. * Sends this Component to the back of (lower z-index than) any other visible windows
  2341. * @return {Ext.Component} this
  2342. */
  2343. toBack: function() {
  2344. this.zIndexManager.sendToBack(this);
  2345. return this;
  2346. },
  2347. /**
  2348. * Center this Component in its container.
  2349. * @return {Ext.Component} this
  2350. */
  2351. center: function() {
  2352. var me = this,
  2353. xy;
  2354. if (me.isVisible()) {
  2355. xy = me.el.getAlignToXY(me.container, 'c-c');
  2356. me.setPagePosition(xy);
  2357. } else {
  2358. me.needsCenter = true;
  2359. }
  2360. return me;
  2361. },
  2362. onFloatShow: function(){
  2363. if (this.needsCenter) {
  2364. this.center();
  2365. }
  2366. delete this.needsCenter;
  2367. },
  2368. // private
  2369. syncShadow : function(){
  2370. if (this.floating) {
  2371. this.el.sync(true);
  2372. }
  2373. },
  2374. // private
  2375. fitContainer: function() {
  2376. var parent = this.floatParent,
  2377. container = parent ? parent.getTargetEl() : this.container,
  2378. size = container.getViewSize(false);
  2379. this.setSize(size);
  2380. }
  2381. });
  2382. /**
  2383. * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
  2384. * to a configured URL, or to a URL specified at request time.
  2385. *
  2386. * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
  2387. * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
  2388. * in the request options object, or an {@link #requestcomplete event listener}.
  2389. *
  2390. * # File Uploads
  2391. *
  2392. * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
  2393. * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
  2394. * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
  2395. * after the return data has been gathered.
  2396. *
  2397. * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
  2398. * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
  2399. * insert the text unchanged into the document body.
  2400. *
  2401. * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
  2402. * `&amp;` etc.
  2403. *
  2404. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
  2405. * responseText property in order to conform to the requirements of event handlers and callbacks.
  2406. *
  2407. * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
  2408. * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
  2409. * packet content.
  2410. *
  2411. * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
  2412. */
  2413. Ext.define('Ext.data.Connection', {
  2414. mixins: {
  2415. observable: 'Ext.util.Observable'
  2416. },
  2417. statics: {
  2418. requestId: 0
  2419. },
  2420. url: null,
  2421. async: true,
  2422. method: null,
  2423. username: '',
  2424. password: '',
  2425. /**
  2426. * @cfg {Boolean} disableCaching
  2427. * True to add a unique cache-buster param to GET requests.
  2428. */
  2429. disableCaching: true,
  2430. /**
  2431. * @cfg {Boolean} withCredentials
  2432. * True to set `withCredentials = true` on the XHR object
  2433. */
  2434. withCredentials: false,
  2435. /**
  2436. * @cfg {Boolean} cors
  2437. * True to enable CORS support on the XHR object. Currently the only effect of this option
  2438. * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 or above.
  2439. */
  2440. cors: false,
  2441. /**
  2442. * @cfg {String} disableCachingParam
  2443. * Change the parameter which is sent went disabling caching through a cache buster.
  2444. */
  2445. disableCachingParam: '_dc',
  2446. /**
  2447. * @cfg {Number} timeout
  2448. * The timeout in milliseconds to be used for requests.
  2449. */
  2450. timeout : 30000,
  2451. /**
  2452. * @cfg {Object} extraParams
  2453. * Any parameters to be appended to the request.
  2454. */
  2455. useDefaultHeader : true,
  2456. defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
  2457. useDefaultXhrHeader : true,
  2458. defaultXhrHeader : 'XMLHttpRequest',
  2459. constructor : function(config) {
  2460. config = config || {};
  2461. Ext.apply(this, config);
  2462. /**
  2463. * @event beforerequest
  2464. * Fires before a network request is made to retrieve a data object.
  2465. * @param {Ext.data.Connection} conn This Connection object.
  2466. * @param {Object} options The options config object passed to the {@link #request} method.
  2467. */
  2468. /**
  2469. * @event requestcomplete
  2470. * Fires if the request was successfully completed.
  2471. * @param {Ext.data.Connection} conn This Connection object.
  2472. * @param {Object} response The XHR object containing the response data.
  2473. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  2474. * @param {Object} options The options config object passed to the {@link #request} method.
  2475. */
  2476. /**
  2477. * @event requestexception
  2478. * Fires if an error HTTP status was returned from the server.
  2479. * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
  2480. * for details of HTTP status codes.
  2481. * @param {Ext.data.Connection} conn This Connection object.
  2482. * @param {Object} response The XHR object containing the response data.
  2483. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  2484. * @param {Object} options The options config object passed to the {@link #request} method.
  2485. */
  2486. this.requests = {};
  2487. this.mixins.observable.constructor.call(this);
  2488. },
  2489. /**
  2490. * Sends an HTTP request to a remote server.
  2491. *
  2492. * **Important:** Ajax server requests are asynchronous, and this call will
  2493. * return before the response has been received. Process any returned data
  2494. * in a callback function.
  2495. *
  2496. * Ext.Ajax.request({
  2497. * url: 'ajax_demo/sample.json',
  2498. * success: function(response, opts) {
  2499. * var obj = Ext.decode(response.responseText);
  2500. * console.dir(obj);
  2501. * },
  2502. * failure: function(response, opts) {
  2503. * console.log('server-side failure with status code ' + response.status);
  2504. * }
  2505. * });
  2506. *
  2507. * To execute a callback function in the correct scope, use the `scope` option.
  2508. *
  2509. * @param {Object} options An object which may contain the following properties:
  2510. *
  2511. * (The options object may also contain any other property which might be needed to perform
  2512. * postprocessing in a callback because it is passed to callback functions.)
  2513. *
  2514. * @param {String/Function} options.url The URL to which to send the request, or a function
  2515. * to call which returns a URL string. The scope of the function is specified by the `scope` option.
  2516. * Defaults to the configured `url`.
  2517. *
  2518. * @param {Object/String/Function} options.params An object containing properties which are
  2519. * used as parameters to the request, a url encoded string or a function to call to get either. The scope
  2520. * of the function is specified by the `scope` option.
  2521. *
  2522. * @param {String} options.method The HTTP method to use
  2523. * for the request. Defaults to the configured method, or if no method was configured,
  2524. * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
  2525. * the method name is case-sensitive and should be all caps.
  2526. *
  2527. * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
  2528. * The callback is called regardless of success or failure and is passed the following parameters:
  2529. * @param {Object} options.callback.options The parameter to the request call.
  2530. * @param {Boolean} options.callback.success True if the request succeeded.
  2531. * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
  2532. * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
  2533. * accessing elements of the response.
  2534. *
  2535. * @param {Function} options.success The function to be called upon success of the request.
  2536. * The callback is passed the following parameters:
  2537. * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
  2538. * @param {Object} options.success.options The parameter to the request call.
  2539. *
  2540. * @param {Function} options.failure The function to be called upon success of the request.
  2541. * The callback is passed the following parameters:
  2542. * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
  2543. * @param {Object} options.failure.options The parameter to the request call.
  2544. *
  2545. * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
  2546. * the callback function. If the `url`, or `params` options were specified as functions from which to
  2547. * draw values, then this also serves as the scope for those function calls. Defaults to the browser
  2548. * window.
  2549. *
  2550. * @param {Number} options.timeout The timeout in milliseconds to be used for this request.
  2551. * Defaults to 30 seconds.
  2552. *
  2553. * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
  2554. * to pull parameters from.
  2555. *
  2556. * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
  2557. *
  2558. * True if the form object is a file upload (will be set automatically if the form was configured
  2559. * with **`enctype`** `"multipart/form-data"`).
  2560. *
  2561. * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
  2562. * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
  2563. * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
  2564. * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
  2565. * has been gathered.
  2566. *
  2567. * The server response is parsed by the browser to create the document for the IFRAME. If the
  2568. * server is using JSON to send the return object, then the [Content-Type][] header must be set to
  2569. * "text/html" in order to tell the browser to insert the text unchanged into the document body.
  2570. *
  2571. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
  2572. * containing a `responseText` property in order to conform to the requirements of event handlers
  2573. * and callbacks.
  2574. *
  2575. * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
  2576. * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
  2577. * and parameter values from the packet content.
  2578. *
  2579. * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
  2580. * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
  2581. * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
  2582. *
  2583. * @param {Object} options.headers Request headers to set for the request.
  2584. *
  2585. * @param {Object} options.xmlData XML document to use for the post. Note: This will be used instead
  2586. * of params for the post data. Any params will be appended to the URL.
  2587. *
  2588. * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used
  2589. * instead of params for the post data. Any params will be appended to the URL.
  2590. *
  2591. * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
  2592. *
  2593. * @param {Boolean} options.withCredentials True to add the withCredentials property to the XHR object
  2594. *
  2595. * @return {Object} The request object. This may be used to cancel the request.
  2596. */
  2597. request : function(options) {
  2598. options = options || {};
  2599. var me = this,
  2600. scope = options.scope || window,
  2601. username = options.username || me.username,
  2602. password = options.password || me.password || '',
  2603. async,
  2604. requestOptions,
  2605. request,
  2606. headers,
  2607. xhr;
  2608. if (me.fireEvent('beforerequest', me, options) !== false) {
  2609. requestOptions = me.setOptions(options, scope);
  2610. if (me.isFormUpload(options)) {
  2611. me.upload(options.form, requestOptions.url, requestOptions.data, options);
  2612. return null;
  2613. }
  2614. // if autoabort is set, cancel the current transactions
  2615. if (options.autoAbort || me.autoAbort) {
  2616. me.abort();
  2617. }
  2618. // create a connection object
  2619. async = options.async !== false ? (options.async || me.async) : false;
  2620. xhr = me.openRequest(options, requestOptions, async, username, password);
  2621. headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
  2622. // create the transaction object
  2623. request = {
  2624. id: ++Ext.data.Connection.requestId,
  2625. xhr: xhr,
  2626. headers: headers,
  2627. options: options,
  2628. async: async,
  2629. timeout: setTimeout(function() {
  2630. request.timedout = true;
  2631. me.abort(request);
  2632. }, options.timeout || me.timeout)
  2633. };
  2634. me.requests[request.id] = request;
  2635. me.latestId = request.id;
  2636. // bind our statechange listener
  2637. if (async) {
  2638. xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
  2639. }
  2640. // start the request!
  2641. xhr.send(requestOptions.data);
  2642. if (!async) {
  2643. return me.onComplete(request);
  2644. }
  2645. return request;
  2646. } else {
  2647. Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
  2648. return null;
  2649. }
  2650. },
  2651. /**
  2652. * Uploads a form using a hidden iframe.
  2653. * @param {String/HTMLElement/Ext.Element} form The form to upload
  2654. * @param {String} url The url to post to
  2655. * @param {String} params Any extra parameters to pass
  2656. * @param {Object} options The initial options
  2657. */
  2658. upload: function(form, url, params, options) {
  2659. form = Ext.getDom(form);
  2660. options = options || {};
  2661. var id = Ext.id(),
  2662. frame = document.createElement('iframe'),
  2663. hiddens = [],
  2664. encoding = 'multipart/form-data',
  2665. buf = {
  2666. target: form.target,
  2667. method: form.method,
  2668. encoding: form.encoding,
  2669. enctype: form.enctype,
  2670. action: form.action
  2671. },
  2672. addField = function(name, value) {
  2673. hiddenItem = document.createElement('input');
  2674. Ext.fly(hiddenItem).set({
  2675. type: 'hidden',
  2676. value: value,
  2677. name: name
  2678. });
  2679. form.appendChild(hiddenItem);
  2680. hiddens.push(hiddenItem);
  2681. },
  2682. hiddenItem, obj, value, name, vLen, v, hLen, h;
  2683. /*
  2684. * Originally this behaviour was modified for Opera 10 to apply the secure URL after
  2685. * the frame had been added to the document. It seems this has since been corrected in
  2686. * Opera so the behaviour has been reverted, the URL will be set before being added.
  2687. */
  2688. Ext.fly(frame).set({
  2689. id: id,
  2690. name: id,
  2691. cls: Ext.baseCSSPrefix + 'hide-display',
  2692. src: Ext.SSL_SECURE_URL
  2693. });
  2694. document.body.appendChild(frame);
  2695. // This is required so that IE doesn't pop the response up in a new window.
  2696. if (document.frames) {
  2697. document.frames[id].name = id;
  2698. }
  2699. Ext.fly(form).set({
  2700. target: id,
  2701. method: 'POST',
  2702. enctype: encoding,
  2703. encoding: encoding,
  2704. action: url || buf.action
  2705. });
  2706. // add dynamic params
  2707. if (params) {
  2708. obj = Ext.Object.fromQueryString(params) || {};
  2709. for (name in obj) {
  2710. value = obj[name];
  2711. if (obj.hasOwnProperty(value)) {
  2712. if (Ext.isArray(value)) {
  2713. vLen = value.length;
  2714. for (v = 0; v < vLen; v++) {
  2715. addField(name, value[v]);
  2716. }
  2717. } else {
  2718. addField(name, value);
  2719. }
  2720. }
  2721. }
  2722. }
  2723. Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
  2724. form.submit();
  2725. Ext.fly(form).set(buf);
  2726. hLen = hiddens.length;
  2727. for (h = 0; h < hLen; h++) {
  2728. Ext.removeNode(hiddens[h]);
  2729. }
  2730. },
  2731. /**
  2732. * @private
  2733. * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
  2734. * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
  2735. * (or a textarea inside the body). We then clean up by removing the iframe
  2736. */
  2737. onUploadComplete: function(frame, options) {
  2738. var me = this,
  2739. // bogus response object
  2740. response = {
  2741. responseText: '',
  2742. responseXML: null
  2743. }, doc, firstChild;
  2744. try {
  2745. doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
  2746. if (doc) {
  2747. if (doc.body) {
  2748. if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
  2749. response.responseText = firstChild.value;
  2750. } else {
  2751. response.responseText = doc.body.innerHTML;
  2752. }
  2753. }
  2754. //in IE the document may still have a body even if returns XML.
  2755. response.responseXML = doc.XMLDocument || doc;
  2756. }
  2757. } catch (e) {
  2758. }
  2759. me.fireEvent('requestcomplete', me, response, options);
  2760. Ext.callback(options.success, options.scope, [response, options]);
  2761. Ext.callback(options.callback, options.scope, [options, true, response]);
  2762. setTimeout(function() {
  2763. Ext.removeNode(frame);
  2764. }, 100);
  2765. },
  2766. /**
  2767. * Detects whether the form is intended to be used for an upload.
  2768. * @private
  2769. */
  2770. isFormUpload: function(options) {
  2771. var form = this.getForm(options);
  2772. if (form) {
  2773. return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
  2774. }
  2775. return false;
  2776. },
  2777. /**
  2778. * Gets the form object from options.
  2779. * @private
  2780. * @param {Object} options The request options
  2781. * @return {HTMLElement} The form, null if not passed
  2782. */
  2783. getForm: function(options) {
  2784. return Ext.getDom(options.form) || null;
  2785. },
  2786. /**
  2787. * Sets various options such as the url, params for the request
  2788. * @param {Object} options The initial options
  2789. * @param {Object} scope The scope to execute in
  2790. * @return {Object} The params for the request
  2791. */
  2792. setOptions: function(options, scope) {
  2793. var me = this,
  2794. params = options.params || {},
  2795. extraParams = me.extraParams,
  2796. urlParams = options.urlParams,
  2797. url = options.url || me.url,
  2798. jsonData = options.jsonData,
  2799. method,
  2800. disableCache,
  2801. data;
  2802. // allow params to be a method that returns the params object
  2803. if (Ext.isFunction(params)) {
  2804. params = params.call(scope, options);
  2805. }
  2806. // allow url to be a method that returns the actual url
  2807. if (Ext.isFunction(url)) {
  2808. url = url.call(scope, options);
  2809. }
  2810. url = this.setupUrl(options, url);
  2811. if (!url) {
  2812. Ext.Error.raise({
  2813. options: options,
  2814. msg: 'No URL specified'
  2815. });
  2816. }
  2817. // check for xml or json data, and make sure json data is encoded
  2818. data = options.rawData || options.xmlData || jsonData || null;
  2819. if (jsonData && !Ext.isPrimitive(jsonData)) {
  2820. data = Ext.encode(data);
  2821. }
  2822. // make sure params are a url encoded string and include any extraParams if specified
  2823. if (Ext.isObject(params)) {
  2824. params = Ext.Object.toQueryString(params);
  2825. }
  2826. if (Ext.isObject(extraParams)) {
  2827. extraParams = Ext.Object.toQueryString(extraParams);
  2828. }
  2829. params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
  2830. urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
  2831. params = this.setupParams(options, params);
  2832. // decide the proper method for this request
  2833. method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
  2834. this.setupMethod(options, method);
  2835. disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
  2836. // if the method is get append date to prevent caching
  2837. if (method === 'GET' && disableCache) {
  2838. url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
  2839. }
  2840. // if the method is get or there is json/xml data append the params to the url
  2841. if ((method == 'GET' || data) && params) {
  2842. url = Ext.urlAppend(url, params);
  2843. params = null;
  2844. }
  2845. // allow params to be forced into the url
  2846. if (urlParams) {
  2847. url = Ext.urlAppend(url, urlParams);
  2848. }
  2849. return {
  2850. url: url,
  2851. method: method,
  2852. data: data || params || null
  2853. };
  2854. },
  2855. /**
  2856. * Template method for overriding url
  2857. * @template
  2858. * @private
  2859. * @param {Object} options
  2860. * @param {String} url
  2861. * @return {String} The modified url
  2862. */
  2863. setupUrl: function(options, url) {
  2864. var form = this.getForm(options);
  2865. if (form) {
  2866. url = url || form.action;
  2867. }
  2868. return url;
  2869. },
  2870. /**
  2871. * Template method for overriding params
  2872. * @template
  2873. * @private
  2874. * @param {Object} options
  2875. * @param {String} params
  2876. * @return {String} The modified params
  2877. */
  2878. setupParams: function(options, params) {
  2879. var form = this.getForm(options),
  2880. serializedForm;
  2881. if (form && !this.isFormUpload(options)) {
  2882. serializedForm = Ext.Element.serializeForm(form);
  2883. params = params ? (params + '&' + serializedForm) : serializedForm;
  2884. }
  2885. return params;
  2886. },
  2887. /**
  2888. * Template method for overriding method
  2889. * @template
  2890. * @private
  2891. * @param {Object} options
  2892. * @param {String} method
  2893. * @return {String} The modified method
  2894. */
  2895. setupMethod: function(options, method) {
  2896. if (this.isFormUpload(options)) {
  2897. return 'POST';
  2898. }
  2899. return method;
  2900. },
  2901. /**
  2902. * Setup all the headers for the request
  2903. * @private
  2904. * @param {Object} xhr The xhr object
  2905. * @param {Object} options The options for the request
  2906. * @param {Object} data The data for the request
  2907. * @param {Object} params The params for the request
  2908. */
  2909. setupHeaders: function(xhr, options, data, params) {
  2910. var me = this,
  2911. headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
  2912. contentType = me.defaultPostHeader,
  2913. jsonData = options.jsonData,
  2914. xmlData = options.xmlData,
  2915. key,
  2916. header;
  2917. if (!headers['Content-Type'] && (data || params)) {
  2918. if (data) {
  2919. if (options.rawData) {
  2920. contentType = 'text/plain';
  2921. } else {
  2922. if (xmlData && Ext.isDefined(xmlData)) {
  2923. contentType = 'text/xml';
  2924. } else if (jsonData && Ext.isDefined(jsonData)) {
  2925. contentType = 'application/json';
  2926. }
  2927. }
  2928. }
  2929. headers['Content-Type'] = contentType;
  2930. }
  2931. if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
  2932. headers['X-Requested-With'] = me.defaultXhrHeader;
  2933. }
  2934. // set up all the request headers on the xhr object
  2935. try {
  2936. for (key in headers) {
  2937. if (headers.hasOwnProperty(key)) {
  2938. header = headers[key];
  2939. xhr.setRequestHeader(key, header);
  2940. }
  2941. }
  2942. } catch(e) {
  2943. me.fireEvent('exception', key, header);
  2944. }
  2945. return headers;
  2946. },
  2947. /**
  2948. * Creates the appropriate XHR transport for a given request on this browser. On IE
  2949. * this may be an `XDomainRequest` rather than an `XMLHttpRequest`.
  2950. * @private
  2951. */
  2952. newRequest: function (options) {
  2953. var xhr;
  2954. if ((options.cors || this.cors) && Ext.isIE && Ext.ieVersion >= 8) {
  2955. xhr = new XDomainRequest();
  2956. } else {
  2957. xhr = this.getXhrInstance();
  2958. }
  2959. return xhr;
  2960. },
  2961. /**
  2962. * Creates and opens an appropriate XHR transport for a given request on this browser.
  2963. * This logic is contained in an individual method to allow for overrides to process all
  2964. * of the parameters and options and return a suitable, open connection.
  2965. * @private
  2966. */
  2967. openRequest: function (options, requestOptions, async, username, password) {
  2968. var xhr = this.newRequest(options);
  2969. if (username) {
  2970. xhr.open(requestOptions.method, requestOptions.url, async, username, password);
  2971. } else {
  2972. xhr.open(requestOptions.method, requestOptions.url, async);
  2973. }
  2974. if (options.withCredentials || this.withCredentials) {
  2975. xhr.withCredentials = true;
  2976. }
  2977. return xhr;
  2978. },
  2979. /**
  2980. * Creates the appropriate XHR transport for this browser.
  2981. * @private
  2982. */
  2983. getXhrInstance: (function() {
  2984. var options = [function() {
  2985. return new XMLHttpRequest();
  2986. }, function() {
  2987. return new ActiveXObject('MSXML2.XMLHTTP.3.0');
  2988. }, function() {
  2989. return new ActiveXObject('MSXML2.XMLHTTP');
  2990. }, function() {
  2991. return new ActiveXObject('Microsoft.XMLHTTP');
  2992. }], i = 0,
  2993. len = options.length,
  2994. xhr;
  2995. for (; i < len; ++i) {
  2996. try {
  2997. xhr = options[i];
  2998. xhr();
  2999. break;
  3000. } catch(e) {
  3001. }
  3002. }
  3003. return xhr;
  3004. })(),
  3005. /**
  3006. * Determines whether this object has a request outstanding.
  3007. * @param {Object} [request] Defaults to the last transaction
  3008. * @return {Boolean} True if there is an outstanding request.
  3009. */
  3010. isLoading : function(request) {
  3011. if (!request) {
  3012. request = this.getLatest();
  3013. }
  3014. if (!(request && request.xhr)) {
  3015. return false;
  3016. }
  3017. // if there is a connection and readyState is not 0 or 4
  3018. var state = request.xhr.readyState;
  3019. return !(state === 0 || state == 4);
  3020. },
  3021. /**
  3022. * Aborts an active request.
  3023. * @param {Object} [request] Defaults to the last request
  3024. */
  3025. abort : function(request) {
  3026. var me = this,
  3027. xhr;
  3028. if (!request) {
  3029. request = me.getLatest();
  3030. }
  3031. if (request && me.isLoading(request)) {
  3032. /*
  3033. * Clear out the onreadystatechange here, this allows us
  3034. * greater control, the browser may/may not fire the function
  3035. * depending on a series of conditions.
  3036. */
  3037. xhr = request.xhr;
  3038. try {
  3039. xhr.onreadystatechange = null;
  3040. } catch (e) {
  3041. // Setting onreadystatechange to null can cause problems in IE, see
  3042. // http://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_a_1.html
  3043. xhr = Ext.emptyFn;
  3044. }
  3045. xhr.abort();
  3046. me.clearTimeout(request);
  3047. if (!request.timedout) {
  3048. request.aborted = true;
  3049. }
  3050. me.onComplete(request);
  3051. me.cleanup(request);
  3052. }
  3053. },
  3054. /**
  3055. * Aborts all active requests
  3056. */
  3057. abortAll: function(){
  3058. var requests = this.requests,
  3059. id;
  3060. for (id in requests) {
  3061. if (requests.hasOwnProperty(id)) {
  3062. this.abort(requests[id]);
  3063. }
  3064. }
  3065. },
  3066. /**
  3067. * Gets the most recent request
  3068. * @private
  3069. * @return {Object} The request. Null if there is no recent request
  3070. */
  3071. getLatest: function(){
  3072. var id = this.latestId,
  3073. request;
  3074. if (id) {
  3075. request = this.requests[id];
  3076. }
  3077. return request || null;
  3078. },
  3079. /**
  3080. * Fires when the state of the xhr changes
  3081. * @private
  3082. * @param {Object} request The request
  3083. */
  3084. onStateChange : function(request) {
  3085. if (request.xhr.readyState == 4) {
  3086. this.clearTimeout(request);
  3087. this.onComplete(request);
  3088. this.cleanup(request);
  3089. }
  3090. },
  3091. /**
  3092. * Clears the timeout on the request
  3093. * @private
  3094. * @param {Object} The request
  3095. */
  3096. clearTimeout: function(request) {
  3097. clearTimeout(request.timeout);
  3098. delete request.timeout;
  3099. },
  3100. /**
  3101. * Cleans up any left over information from the request
  3102. * @private
  3103. * @param {Object} The request
  3104. */
  3105. cleanup: function(request) {
  3106. request.xhr = null;
  3107. delete request.xhr;
  3108. },
  3109. /**
  3110. * To be called when the request has come back from the server
  3111. * @private
  3112. * @param {Object} request
  3113. * @return {Object} The response
  3114. */
  3115. onComplete : function(request) {
  3116. var me = this,
  3117. options = request.options,
  3118. result,
  3119. success,
  3120. response;
  3121. try {
  3122. result = me.parseStatus(request.xhr.status);
  3123. } catch (e) {
  3124. // in some browsers we can't access the status if the readyState is not 4, so the request has failed
  3125. result = {
  3126. success : false,
  3127. isException : false
  3128. };
  3129. }
  3130. success = result.success;
  3131. if (success) {
  3132. response = me.createResponse(request);
  3133. me.fireEvent('requestcomplete', me, response, options);
  3134. Ext.callback(options.success, options.scope, [response, options]);
  3135. } else {
  3136. if (result.isException || request.aborted || request.timedout) {
  3137. response = me.createException(request);
  3138. } else {
  3139. response = me.createResponse(request);
  3140. }
  3141. me.fireEvent('requestexception', me, response, options);
  3142. Ext.callback(options.failure, options.scope, [response, options]);
  3143. }
  3144. Ext.callback(options.callback, options.scope, [options, success, response]);
  3145. delete me.requests[request.id];
  3146. return response;
  3147. },
  3148. /**
  3149. * Checks if the response status was successful
  3150. * @param {Number} status The status code
  3151. * @return {Object} An object containing success/status state
  3152. */
  3153. parseStatus: function(status) {
  3154. // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
  3155. status = status == 1223 ? 204 : status;
  3156. var success = (status >= 200 && status < 300) || status == 304,
  3157. isException = false;
  3158. if (!success) {
  3159. switch (status) {
  3160. case 12002:
  3161. case 12029:
  3162. case 12030:
  3163. case 12031:
  3164. case 12152:
  3165. case 13030:
  3166. isException = true;
  3167. break;
  3168. }
  3169. }
  3170. return {
  3171. success: success,
  3172. isException: isException
  3173. };
  3174. },
  3175. /**
  3176. * Creates the response object
  3177. * @private
  3178. * @param {Object} request
  3179. */
  3180. createResponse : function(request) {
  3181. var xhr = request.xhr,
  3182. headers = {},
  3183. lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
  3184. count = lines.length,
  3185. line, index, key, value, response;
  3186. while (count--) {
  3187. line = lines[count];
  3188. index = line.indexOf(':');
  3189. if (index >= 0) {
  3190. key = line.substr(0, index).toLowerCase();
  3191. if (line.charAt(index + 1) == ' ') {
  3192. ++index;
  3193. }
  3194. headers[key] = line.substr(index + 1);
  3195. }
  3196. }
  3197. request.xhr = null;
  3198. delete request.xhr;
  3199. response = {
  3200. request: request,
  3201. requestId : request.id,
  3202. status : xhr.status,
  3203. statusText : xhr.statusText,
  3204. getResponseHeader : function(header) {
  3205. return headers[header.toLowerCase()];
  3206. },
  3207. getAllResponseHeaders : function() {
  3208. return headers;
  3209. },
  3210. responseText : xhr.responseText,
  3211. responseXML : xhr.responseXML
  3212. };
  3213. // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
  3214. // functions created with getResponseHeader/getAllResponseHeaders
  3215. xhr = null;
  3216. return response;
  3217. },
  3218. /**
  3219. * Creates the exception object
  3220. * @private
  3221. * @param {Object} request
  3222. */
  3223. createException : function(request) {
  3224. return {
  3225. request : request,
  3226. requestId : request.id,
  3227. status : request.aborted ? -1 : 0,
  3228. statusText : request.aborted ? 'transaction aborted' : 'communication failure',
  3229. aborted: request.aborted,
  3230. timedout: request.timedout
  3231. };
  3232. }
  3233. });
  3234. /**
  3235. * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
  3236. * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
  3237. * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
  3238. * on their records. Example usage:
  3239. *
  3240. * //set up a fictional MixedCollection containing a few people to filter on
  3241. * var allNames = new Ext.util.MixedCollection();
  3242. * allNames.addAll([
  3243. * {id: 1, name: 'Ed', age: 25},
  3244. * {id: 2, name: 'Jamie', age: 37},
  3245. * {id: 3, name: 'Abe', age: 32},
  3246. * {id: 4, name: 'Aaron', age: 26},
  3247. * {id: 5, name: 'David', age: 32}
  3248. * ]);
  3249. *
  3250. * var ageFilter = new Ext.util.Filter({
  3251. * property: 'age',
  3252. * value : 32
  3253. * });
  3254. *
  3255. * var longNameFilter = new Ext.util.Filter({
  3256. * filterFn: function(item) {
  3257. * return item.name.length > 4;
  3258. * }
  3259. * });
  3260. *
  3261. * //a new MixedCollection with the 3 names longer than 4 characters
  3262. * var longNames = allNames.filter(longNameFilter);
  3263. *
  3264. * //a new MixedCollection with the 2 people of age 24:
  3265. * var youngFolk = allNames.filter(ageFilter);
  3266. *
  3267. */
  3268. Ext.define('Ext.util.Filter', {
  3269. /* Begin Definitions */
  3270. /* End Definitions */
  3271. /**
  3272. * @cfg {String} property
  3273. * The property to filter on. Required unless a {@link #filterFn} is passed
  3274. */
  3275. /**
  3276. * @cfg {Function} filterFn
  3277. * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
  3278. * true to accept each item or false to reject it
  3279. */
  3280. /**
  3281. * @cfg {Boolean} anyMatch
  3282. * True to allow any match - no regex start/end line anchors will be added.
  3283. */
  3284. anyMatch: false,
  3285. /**
  3286. * @cfg {Boolean} exactMatch
  3287. * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
  3288. */
  3289. exactMatch: false,
  3290. /**
  3291. * @cfg {Boolean} caseSensitive
  3292. * True to make the regex case sensitive (adds 'i' switch to regex).
  3293. */
  3294. caseSensitive: false,
  3295. /**
  3296. * @cfg {String} root
  3297. * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
  3298. * make the filter pull the {@link #property} out of the data object of each item
  3299. */
  3300. /**
  3301. * Creates new Filter.
  3302. * @param {Object} [config] Config object
  3303. */
  3304. constructor: function(config) {
  3305. var me = this;
  3306. Ext.apply(me, config);
  3307. //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
  3308. //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
  3309. me.filter = me.filter || me.filterFn;
  3310. if (me.filter === undefined) {
  3311. if (me.property === undefined || me.value === undefined) {
  3312. // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
  3313. // Model has been updated to allow string ids
  3314. // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
  3315. } else {
  3316. me.filter = me.createFilterFn();
  3317. }
  3318. me.filterFn = me.filter;
  3319. }
  3320. },
  3321. /**
  3322. * @private
  3323. * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
  3324. */
  3325. createFilterFn: function() {
  3326. var me = this,
  3327. matcher = me.createValueMatcher(),
  3328. property = me.property;
  3329. return function(item) {
  3330. var value = me.getRoot.call(me, item)[property];
  3331. return matcher === null ? value === null : matcher.test(value);
  3332. };
  3333. },
  3334. /**
  3335. * @private
  3336. * Returns the root property of the given item, based on the configured {@link #root} property
  3337. * @param {Object} item The item
  3338. * @return {Object} The root property of the object
  3339. */
  3340. getRoot: function(item) {
  3341. var root = this.root;
  3342. return root === undefined ? item : item[root];
  3343. },
  3344. /**
  3345. * @private
  3346. * Returns a regular expression based on the given value and matching options
  3347. */
  3348. createValueMatcher : function() {
  3349. var me = this,
  3350. value = me.value,
  3351. anyMatch = me.anyMatch,
  3352. exactMatch = me.exactMatch,
  3353. caseSensitive = me.caseSensitive,
  3354. escapeRe = Ext.String.escapeRegex;
  3355. if (value === null) {
  3356. return value;
  3357. }
  3358. if (!value.exec) { // not a regex
  3359. value = String(value);
  3360. if (anyMatch === true) {
  3361. value = escapeRe(value);
  3362. } else {
  3363. value = '^' + escapeRe(value);
  3364. if (exactMatch === true) {
  3365. value += '$';
  3366. }
  3367. }
  3368. value = new RegExp(value, caseSensitive ? '' : 'i');
  3369. }
  3370. return value;
  3371. }
  3372. });
  3373. /**
  3374. * Represents a single sorter that can be applied to a Store. The sorter is used
  3375. * to compare two values against each other for the purpose of ordering them. Ordering
  3376. * is achieved by specifying either:
  3377. *
  3378. * - {@link #property A sorting property}
  3379. * - {@link #sorterFn A sorting function}
  3380. *
  3381. * As a contrived example, we can specify a custom sorter that sorts by rank:
  3382. *
  3383. * Ext.define('Person', {
  3384. * extend: 'Ext.data.Model',
  3385. * fields: ['name', 'rank']
  3386. * });
  3387. *
  3388. * Ext.create('Ext.data.Store', {
  3389. * model: 'Person',
  3390. * proxy: 'memory',
  3391. * sorters: [{
  3392. * sorterFn: function(o1, o2){
  3393. * var getRank = function(o){
  3394. * var name = o.get('rank');
  3395. * if (name === 'first') {
  3396. * return 1;
  3397. * } else if (name === 'second') {
  3398. * return 2;
  3399. * } else {
  3400. * return 3;
  3401. * }
  3402. * },
  3403. * rank1 = getRank(o1),
  3404. * rank2 = getRank(o2);
  3405. *
  3406. * if (rank1 === rank2) {
  3407. * return 0;
  3408. * }
  3409. *
  3410. * return rank1 < rank2 ? -1 : 1;
  3411. * }
  3412. * }],
  3413. * data: [{
  3414. * name: 'Person1',
  3415. * rank: 'second'
  3416. * }, {
  3417. * name: 'Person2',
  3418. * rank: 'third'
  3419. * }, {
  3420. * name: 'Person3',
  3421. * rank: 'first'
  3422. * }]
  3423. * });
  3424. */
  3425. Ext.define('Ext.util.Sorter', {
  3426. /**
  3427. * @cfg {String} property
  3428. * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
  3429. * directly and compared for sorting using the built in comparison operators.
  3430. */
  3431. /**
  3432. * @cfg {Function} sorterFn
  3433. * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
  3434. * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
  3435. * function should return:
  3436. *
  3437. * - -1 if o1 is "less than" o2
  3438. * - 0 if o1 is "equal" to o2
  3439. * - 1 if o1 is "greater than" o2
  3440. */
  3441. /**
  3442. * @cfg {String} root
  3443. * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
  3444. * make the filter pull the {@link #property} out of the data object of each item
  3445. */
  3446. /**
  3447. * @cfg {Function} transform
  3448. * A function that will be run on each value before it is compared in the sorter. The function will receive a single
  3449. * argument, the value.
  3450. */
  3451. /**
  3452. * @cfg {String} direction
  3453. * The direction to sort by.
  3454. */
  3455. direction: "ASC",
  3456. constructor: function(config) {
  3457. var me = this;
  3458. Ext.apply(me, config);
  3459. if (me.property === undefined && me.sorterFn === undefined) {
  3460. Ext.Error.raise("A Sorter requires either a property or a sorter function");
  3461. }
  3462. me.updateSortFunction();
  3463. },
  3464. /**
  3465. * @private
  3466. * Creates and returns a function which sorts an array by the given property and direction
  3467. * @return {Function} A function which sorts by the property/direction combination provided
  3468. */
  3469. createSortFunction: function(sorterFn) {
  3470. var me = this,
  3471. property = me.property,
  3472. direction = me.direction || "ASC",
  3473. modifier = direction.toUpperCase() == "DESC" ? -1 : 1;
  3474. //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
  3475. //-1 if object 2 is greater or 0 if they are equal
  3476. return function(o1, o2) {
  3477. return modifier * sorterFn.call(me, o1, o2);
  3478. };
  3479. },
  3480. /**
  3481. * @private
  3482. * Basic default sorter function that just compares the defined property of each object
  3483. */
  3484. defaultSorterFn: function(o1, o2) {
  3485. var me = this,
  3486. transform = me.transform,
  3487. v1 = me.getRoot(o1)[me.property],
  3488. v2 = me.getRoot(o2)[me.property];
  3489. if (transform) {
  3490. v1 = transform(v1);
  3491. v2 = transform(v2);
  3492. }
  3493. return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  3494. },
  3495. /**
  3496. * @private
  3497. * Returns the root property of the given item, based on the configured {@link #root} property
  3498. * @param {Object} item The item
  3499. * @return {Object} The root property of the object
  3500. */
  3501. getRoot: function(item) {
  3502. return this.root === undefined ? item : item[this.root];
  3503. },
  3504. /**
  3505. * Set the sorting direction for this sorter.
  3506. * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
  3507. */
  3508. setDirection: function(direction) {
  3509. var me = this;
  3510. me.direction = direction;
  3511. me.updateSortFunction();
  3512. },
  3513. /**
  3514. * Toggles the sorting direction for this sorter.
  3515. */
  3516. toggle: function() {
  3517. var me = this;
  3518. me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
  3519. me.updateSortFunction();
  3520. },
  3521. /**
  3522. * Update the sort function for this sorter.
  3523. * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
  3524. * sorting function.
  3525. */
  3526. updateSortFunction: function(fn) {
  3527. var me = this;
  3528. fn = fn || me.sorterFn || me.defaultSorterFn;
  3529. me.sort = me.createSortFunction(fn);
  3530. }
  3531. });
  3532. /**
  3533. * This animation class is a mixin.
  3534. *
  3535. * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.
  3536. * This class is used as a mixin and currently applied to {@link Ext.Element}, {@link Ext.CompositeElement},
  3537. * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}. Note that Components
  3538. * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and
  3539. * opacity (color, paddings, and margins can not be animated).
  3540. *
  3541. * ## Animation Basics
  3542. *
  3543. * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property)
  3544. * you wish to animate. Easing and duration are defaulted values specified below.
  3545. * Easing describes how the intermediate values used during a transition will be calculated.
  3546. * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
  3547. * You may use the defaults for easing and duration, but you must always set a
  3548. * {@link Ext.fx.Anim#to to} property which is the end value for all animations.
  3549. *
  3550. * Popular element 'to' configurations are:
  3551. *
  3552. * - opacity
  3553. * - x
  3554. * - y
  3555. * - color
  3556. * - height
  3557. * - width
  3558. *
  3559. * Popular sprite 'to' configurations are:
  3560. *
  3561. * - translation
  3562. * - path
  3563. * - scale
  3564. * - stroke
  3565. * - rotation
  3566. *
  3567. * The default duration for animations is 250 (which is a 1/4 of a second). Duration is denoted in
  3568. * milliseconds. Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve
  3569. * used for all animations is 'ease'. Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
  3570. *
  3571. * For example, a simple animation to fade out an element with a default easing and duration:
  3572. *
  3573. * var p1 = Ext.get('myElementId');
  3574. *
  3575. * p1.animate({
  3576. * to: {
  3577. * opacity: 0
  3578. * }
  3579. * });
  3580. *
  3581. * To make this animation fade out in a tenth of a second:
  3582. *
  3583. * var p1 = Ext.get('myElementId');
  3584. *
  3585. * p1.animate({
  3586. * duration: 100,
  3587. * to: {
  3588. * opacity: 0
  3589. * }
  3590. * });
  3591. *
  3592. * ## Animation Queues
  3593. *
  3594. * By default all animations are added to a queue which allows for animation via a chain-style API.
  3595. * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
  3596. *
  3597. * p1.animate({
  3598. * to: {
  3599. * x: 500
  3600. * }
  3601. * }).animate({
  3602. * to: {
  3603. * y: 150
  3604. * }
  3605. * }).animate({
  3606. * to: {
  3607. * backgroundColor: '#f00' //red
  3608. * }
  3609. * }).animate({
  3610. * to: {
  3611. * opacity: 0
  3612. * }
  3613. * });
  3614. *
  3615. * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all
  3616. * subsequent animations for the specified target will be run concurrently (at the same time).
  3617. *
  3618. * p1.syncFx(); //this will make all animations run at the same time
  3619. *
  3620. * p1.animate({
  3621. * to: {
  3622. * x: 500
  3623. * }
  3624. * }).animate({
  3625. * to: {
  3626. * y: 150
  3627. * }
  3628. * }).animate({
  3629. * to: {
  3630. * backgroundColor: '#f00' //red
  3631. * }
  3632. * }).animate({
  3633. * to: {
  3634. * opacity: 0
  3635. * }
  3636. * });
  3637. *
  3638. * This works the same as:
  3639. *
  3640. * p1.animate({
  3641. * to: {
  3642. * x: 500,
  3643. * y: 150,
  3644. * backgroundColor: '#f00' //red
  3645. * opacity: 0
  3646. * }
  3647. * });
  3648. *
  3649. * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any
  3650. * currently running animations and clear any queued animations.
  3651. *
  3652. * ## Animation Keyframes
  3653. *
  3654. * You can also set up complex animations with {@link Ext.fx.Anim#keyframes keyframes} which follow the
  3655. * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites.
  3656. * The previous example can be written with the following syntax:
  3657. *
  3658. * p1.animate({
  3659. * duration: 1000, //one second total
  3660. * keyframes: {
  3661. * 25: { //from 0 to 250ms (25%)
  3662. * x: 0
  3663. * },
  3664. * 50: { //from 250ms to 500ms (50%)
  3665. * y: 0
  3666. * },
  3667. * 75: { //from 500ms to 750ms (75%)
  3668. * backgroundColor: '#f00' //red
  3669. * },
  3670. * 100: { //from 750ms to 1sec
  3671. * opacity: 0
  3672. * }
  3673. * }
  3674. * });
  3675. *
  3676. * ## Animation Events
  3677. *
  3678. * Each animation you create has events for {@link Ext.fx.Anim#beforeanimate beforeanimate},
  3679. * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.
  3680. * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which
  3681. * fires for each keyframe in your animation.
  3682. *
  3683. * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
  3684. *
  3685. * startAnimate: function() {
  3686. * var p1 = Ext.get('myElementId');
  3687. * p1.animate({
  3688. * duration: 100,
  3689. * to: {
  3690. * opacity: 0
  3691. * },
  3692. * listeners: {
  3693. * beforeanimate: function() {
  3694. * // Execute my custom method before the animation
  3695. * this.myBeforeAnimateFn();
  3696. * },
  3697. * afteranimate: function() {
  3698. * // Execute my custom method after the animation
  3699. * this.myAfterAnimateFn();
  3700. * },
  3701. * scope: this
  3702. * });
  3703. * },
  3704. * myBeforeAnimateFn: function() {
  3705. * // My custom logic
  3706. * },
  3707. * myAfterAnimateFn: function() {
  3708. * // My custom logic
  3709. * }
  3710. *
  3711. * Due to the fact that animations run asynchronously, you can determine if an animation is currently
  3712. * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation}
  3713. * method. This method will return false if there are no active animations or return the currently
  3714. * running {@link Ext.fx.Anim} instance.
  3715. *
  3716. * In this example, we're going to wait for the current animation to finish, then stop any other
  3717. * queued animations before we fade our element's opacity to 0:
  3718. *
  3719. * var curAnim = p1.getActiveAnimation();
  3720. * if (curAnim) {
  3721. * curAnim.on('afteranimate', function() {
  3722. * p1.stopAnimation();
  3723. * p1.animate({
  3724. * to: {
  3725. * opacity: 0
  3726. * }
  3727. * });
  3728. * });
  3729. * }
  3730. */
  3731. Ext.define('Ext.util.Animate', {
  3732. uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
  3733. /**
  3734. * Perform custom animation on this object.
  3735. *
  3736. * This method is applicable to both the {@link Ext.Component Component} class and the {@link Ext.Element Element}
  3737. * class. It performs animated transitions of certain properties of this object over a specified timeline.
  3738. *
  3739. * The sole parameter is an object which specifies start property values, end property values, and properties which
  3740. * describe the timeline.
  3741. *
  3742. * ### Animating an {@link Ext.Element Element}
  3743. *
  3744. * When animating an Element, the following properties may be specified in `from`, `to`, and `keyframe` objects:
  3745. *
  3746. * - `x` - The page X position in pixels.
  3747. *
  3748. * - `y` - The page Y position in pixels
  3749. *
  3750. * - `left` - The element's CSS `left` value. Units must be supplied.
  3751. *
  3752. * - `top` - The element's CSS `top` value. Units must be supplied.
  3753. *
  3754. * - `width` - The element's CSS `width` value. Units must be supplied.
  3755. *
  3756. * - `height` - The element's CSS `height` value. Units must be supplied.
  3757. *
  3758. * - `scrollLeft` - The element's `scrollLeft` value.
  3759. *
  3760. * - `scrollTop` - The element's `scrollLeft` value.
  3761. *
  3762. * - `opacity` - The element's `opacity` value. This must be a value between `0` and `1`.
  3763. *
  3764. * **Be aware than animating an Element which is being used by an Ext Component without in some way informing the
  3765. * Component about the changed element state will result in incorrect Component behaviour. This is because the
  3766. * Component will be using the old state of the element. To avoid this problem, it is now possible to directly
  3767. * animate certain properties of Components.**
  3768. *
  3769. * ### Animating a {@link Ext.Component Component}
  3770. *
  3771. * When animating a Component, the following properties may be specified in `from`, `to`, and `keyframe` objects:
  3772. *
  3773. * - `x` - The Component's page X position in pixels.
  3774. *
  3775. * - `y` - The Component's page Y position in pixels
  3776. *
  3777. * - `left` - The Component's `left` value in pixels.
  3778. *
  3779. * - `top` - The Component's `top` value in pixels.
  3780. *
  3781. * - `width` - The Component's `width` value in pixels.
  3782. *
  3783. * - `width` - The Component's `width` value in pixels.
  3784. *
  3785. * - `dynamic` - Specify as true to update the Component's layout (if it is a Container) at every frame of the animation.
  3786. * *Use sparingly as laying out on every intermediate size change is an expensive operation.*
  3787. *
  3788. * For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:
  3789. *
  3790. * myWindow = Ext.create('Ext.window.Window', {
  3791. * title: 'Test Component animation',
  3792. * width: 500,
  3793. * height: 300,
  3794. * layout: {
  3795. * type: 'hbox',
  3796. * align: 'stretch'
  3797. * },
  3798. * items: [{
  3799. * title: 'Left: 33%',
  3800. * margins: '5 0 5 5',
  3801. * flex: 1
  3802. * }, {
  3803. * title: 'Left: 66%',
  3804. * margins: '5 5 5 5',
  3805. * flex: 2
  3806. * }]
  3807. * });
  3808. * myWindow.show();
  3809. * myWindow.header.el.on('click', function() {
  3810. * myWindow.animate({
  3811. * to: {
  3812. * width: (myWindow.getWidth() == 500) ? 700 : 500,
  3813. * height: (myWindow.getHeight() == 300) ? 400 : 300,
  3814. * }
  3815. * });
  3816. * });
  3817. *
  3818. * For performance reasons, by default, the internal layout is only updated when the Window reaches its final `"to"`
  3819. * size. If dynamic updating of the Window's child Components is required, then configure the animation with
  3820. * `dynamic: true` and the two child items will maintain their proportions during the animation.
  3821. *
  3822. * @param {Object} config An object containing properties which describe the animation's start and end states, and
  3823. * the timeline of the animation. Of the properties listed below, only **`to`** is mandatory.
  3824. *
  3825. * Properties include:
  3826. *
  3827. * @param {Object} config.from
  3828. * An object which specifies start values for the properties being animated. If not supplied, properties are
  3829. * animated from current settings. The actual properties which may be animated depend upon ths object being
  3830. * animated. See the sections below on Element and Component animation.
  3831. *
  3832. * @param {Object} config.to
  3833. * An object which specifies end values for the properties being animated.
  3834. *
  3835. * @param {Number} config.duration
  3836. * The duration **in milliseconds** for which the animation will run.
  3837. *
  3838. * @param {String} config.easing
  3839. * A string value describing an easing type to modify the rate of change from the default linear to non-linear.
  3840. * Values may be one of:
  3841. *
  3842. * - ease
  3843. * - easeIn
  3844. * - easeOut
  3845. * - easeInOut
  3846. * - backIn
  3847. * - backOut
  3848. * - elasticIn
  3849. * - elasticOut
  3850. * - bounceIn
  3851. * - bounceOut
  3852. *
  3853. * @param {Object} config.keyframes
  3854. * This is an object which describes the state of animated properties at certain points along the timeline. it is an
  3855. * object containing properties who's names are the percentage along the timeline being described and who's values
  3856. * specify the animation state at that point.
  3857. *
  3858. * @param {Object} config.listeners
  3859. * This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used to
  3860. * inject behaviour at either the `beforeanimate` event or the `afteranimate` event.
  3861. *
  3862. * @return {Object} this
  3863. */
  3864. animate: function(animObj) {
  3865. var me = this;
  3866. if (Ext.fx.Manager.hasFxBlock(me.id)) {
  3867. return me;
  3868. }
  3869. Ext.fx.Manager.queueFx(new Ext.fx.Anim(me.anim(animObj)));
  3870. return this;
  3871. },
  3872. // @private - process the passed fx configuration.
  3873. anim: function(config) {
  3874. if (!Ext.isObject(config)) {
  3875. return (config) ? {} : false;
  3876. }
  3877. var me = this;
  3878. if (config.stopAnimation) {
  3879. me.stopAnimation();
  3880. }
  3881. Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
  3882. return Ext.apply({
  3883. target: me,
  3884. paused: true
  3885. }, config);
  3886. },
  3887. /**
  3888. * Stops any running effects and clears this object's internal effects queue if it contains any additional effects
  3889. * that haven't started yet.
  3890. * @deprecated 4.0 Replaced by {@link #stopAnimation}
  3891. * @return {Ext.Element} The Element
  3892. * @method
  3893. */
  3894. stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
  3895. /**
  3896. * Stops any running effects and clears this object's internal effects queue if it contains any additional effects
  3897. * that haven't started yet.
  3898. * @return {Ext.Element} The Element
  3899. */
  3900. stopAnimation: function() {
  3901. Ext.fx.Manager.stopAnimation(this.id);
  3902. return this;
  3903. },
  3904. /**
  3905. * Ensures that all effects queued after syncFx is called on this object are run concurrently. This is the opposite
  3906. * of {@link #sequenceFx}.
  3907. * @return {Object} this
  3908. */
  3909. syncFx: function() {
  3910. Ext.fx.Manager.setFxDefaults(this.id, {
  3911. concurrent: true
  3912. });
  3913. return this;
  3914. },
  3915. /**
  3916. * Ensures that all effects queued after sequenceFx is called on this object are run in sequence. This is the
  3917. * opposite of {@link #syncFx}.
  3918. * @return {Object} this
  3919. */
  3920. sequenceFx: function() {
  3921. Ext.fx.Manager.setFxDefaults(this.id, {
  3922. concurrent: false
  3923. });
  3924. return this;
  3925. },
  3926. /**
  3927. * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
  3928. * @inheritdoc Ext.util.Animate#getActiveAnimation
  3929. * @method
  3930. */
  3931. hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
  3932. /**
  3933. * Returns the current animation if this object has any effects actively running or queued, else returns false.
  3934. * @return {Ext.fx.Anim/Boolean} Anim if element has active effects, else false
  3935. */
  3936. getActiveAnimation: function() {
  3937. return Ext.fx.Manager.getActiveAnimation(this.id);
  3938. }
  3939. }, function(){
  3940. // Apply Animate mixin manually until Element is defined in the proper 4.x way
  3941. Ext.applyIf(Ext.Element.prototype, this.prototype);
  3942. // We need to call this again so the animation methods get copied over to CE
  3943. Ext.CompositeElementLite.importElementMethods();
  3944. });
  3945. /**
  3946. * This mixin enables classes to declare relationships to child elements and provides the
  3947. * mechanics for acquiring the {@link Ext.Element elements} and storing them on an object
  3948. * instance as properties.
  3949. *
  3950. * This class is used by {@link Ext.Component components} and {@link Ext.layout.container.Container container layouts} to
  3951. * manage their child elements.
  3952. *
  3953. * A typical component that uses these features might look something like this:
  3954. *
  3955. * Ext.define('Ext.ux.SomeComponent', {
  3956. * extend: 'Ext.Component',
  3957. *
  3958. * childEls: [
  3959. * 'bodyEl'
  3960. * ],
  3961. *
  3962. * renderTpl: [
  3963. * '&lt;div id="{id}-bodyEl"&gt;&lt;/div&gt;'
  3964. * ],
  3965. *
  3966. * // ...
  3967. * });
  3968. *
  3969. * The `childEls` array lists one or more relationships to child elements managed by the
  3970. * component. The items in this array can be either of the following types:
  3971. *
  3972. * - String: the id suffix and property name in one. For example, "bodyEl" in the above
  3973. * example means a "bodyEl" property will be added to the instance with the result of
  3974. * {@link Ext#get} given "componentId-bodyEl" where "componentId" is the component instance's
  3975. * id.
  3976. * - Object: with a `name` property that names the instance property for the element, and
  3977. * one of the following additional properties:
  3978. * - `id`: The full id of the child element.
  3979. * - `itemId`: The suffix part of the id to which "componentId-" is prepended.
  3980. * - `select`: A selector that will be passed to {@link Ext#select}.
  3981. * - `selectNode`: A selector that will be passed to {@link Ext.DomQuery#selectNode}.
  3982. *
  3983. * The example above could have used this instead to achieve the same result:
  3984. *
  3985. * childEls: [
  3986. * { name: 'bodyEl', itemId: 'bodyEl' }
  3987. * ]
  3988. *
  3989. * When using `select`, the property will be an instance of {@link Ext.CompositeElement}. In
  3990. * all other cases, the property will be an {@link Ext.Element} or `null` if not found.
  3991. *
  3992. * Care should be taken when using `select` or `selectNode` to find child elements. The
  3993. * following issues should be considered:
  3994. *
  3995. * - Performance: using selectors can be slower than id lookup by a factor 10x or more.
  3996. * - Over-selecting: selectors are applied after the DOM elements for all children have
  3997. * been rendered, so selectors can match elements from child components (including nested
  3998. * versions of the same component) accidentally.
  3999. *
  4000. * This above issues are most important when using `select` since it returns multiple
  4001. * elements.
  4002. *
  4003. * **IMPORTANT**
  4004. * Unlike a `renderTpl` where there is a single value for an instance, `childEls` are aggregated
  4005. * up the class hierarchy so that they are effectively inherited. In other words, if a
  4006. * class where to derive from `Ext.ux.SomeComponent` in the example above, it could also
  4007. * have a `childEls` property in the same way as `Ext.ux.SomeComponent`.
  4008. *
  4009. * Ext.define('Ext.ux.AnotherComponent', {
  4010. * extend: 'Ext.ux.SomeComponent',
  4011. *
  4012. * childEls: [
  4013. * // 'bodyEl' is inherited
  4014. * 'innerEl'
  4015. * ],
  4016. *
  4017. * renderTpl: [
  4018. * '&lt;div id="{id}-bodyEl"&gt;'
  4019. * '&lt;div id="{id}-innerEl"&gt;&lt;/div&gt;'
  4020. * '&lt;/div&gt;'
  4021. * ],
  4022. *
  4023. * // ...
  4024. * });
  4025. *
  4026. * The `renderTpl` contains both child elements and unites them in the desired markup, but
  4027. * the `childEls` only contains the new child element. The {@link #applyChildEls} method
  4028. * takes care of looking up all `childEls` for an instance and considers `childEls`
  4029. * properties on all the super classes and mixins.
  4030. *
  4031. * @private
  4032. */
  4033. Ext.define('Ext.util.ElementContainer', {
  4034. childEls: [
  4035. // empty - this solves a couple problems:
  4036. // 1. It ensures that all classes have a childEls (avoid null ptr)
  4037. // 2. It prevents mixins from smashing on their own childEls (these are gathered
  4038. // specifically)
  4039. ],
  4040. constructor: function () {
  4041. var me = this,
  4042. childEls;
  4043. // if we have configured childEls, we need to merge them with those from this
  4044. // class, its bases and the set of mixins...
  4045. if (me.hasOwnProperty('childEls')) {
  4046. childEls = me.childEls;
  4047. delete me.childEls;
  4048. me.addChildEls.apply(me, childEls);
  4049. }
  4050. },
  4051. destroy: function () {
  4052. var me = this,
  4053. childEls = me.getChildEls(),
  4054. child, childName, i, k;
  4055. for (i = childEls.length; i--; ) {
  4056. childName = childEls[i];
  4057. if (typeof childName != 'string') {
  4058. childName = childName.name;
  4059. }
  4060. child = me[childName];
  4061. if (child) {
  4062. me[childName] = null; // better than delete since that changes the "shape"
  4063. child.remove();
  4064. }
  4065. }
  4066. },
  4067. /**
  4068. * Adds each argument passed to this method to the {@link #childEls} array.
  4069. */
  4070. addChildEls: function () {
  4071. var me = this,
  4072. args = arguments;;
  4073. if (me.hasOwnProperty('childEls')) {
  4074. me.childEls.push.apply(me.childEls, args);
  4075. } else {
  4076. me.childEls = me.getChildEls().concat(Array.prototype.slice.call(args));
  4077. }
  4078. me.prune(me.childEls, false);
  4079. },
  4080. /**
  4081. * Sets references to elements inside the component.
  4082. * @private
  4083. */
  4084. applyChildEls: function(el, id) {
  4085. var me = this,
  4086. childEls = me.getChildEls(),
  4087. baseId, childName, i, selector, value;
  4088. baseId = (id || me.id) + '-';
  4089. for (i = childEls.length; i--; ) {
  4090. childName = childEls[i];
  4091. if (typeof childName == 'string') {
  4092. // We don't use Ext.get because that is 3x (or more) slower on IE6-8. Since
  4093. // we know the el's are children of our el we use getById instead:
  4094. value = el.getById(baseId + childName);
  4095. } else {
  4096. if ((selector = childName.select)) {
  4097. value = Ext.select(selector, true, el.dom); // a CompositeElement
  4098. } else if ((selector = childName.selectNode)) {
  4099. value = Ext.get(Ext.DomQuery.selectNode(selector, el.dom));
  4100. } else {
  4101. // see above re:getById...
  4102. value = el.getById(childName.id || (baseId + childName.itemId));
  4103. }
  4104. childName = childName.name;
  4105. }
  4106. me[childName] = value;
  4107. }
  4108. },
  4109. getChildEls: function () {
  4110. var me = this,
  4111. self;
  4112. // If an instance has its own childEls, that is the complete set:
  4113. if (me.hasOwnProperty('childEls')) {
  4114. return me.childEls;
  4115. }
  4116. // Typically, however, the childEls is a class-level concept, so check to see if
  4117. // we have cached the complete set on the class:
  4118. self = me.self;
  4119. return self.$childEls || me.getClassChildEls(self);
  4120. },
  4121. getClassChildEls: function (cls) {
  4122. var me = this,
  4123. result = cls.$childEls,
  4124. childEls, i, length, forked, mixin, mixins, name, parts, proto, supr, superMixins;
  4125. if (!result) {
  4126. // We put the various childEls arrays into parts in the order of superclass,
  4127. // new mixins and finally from cls. These parts can be null or undefined and
  4128. // we will skip them later.
  4129. supr = cls.superclass;
  4130. if (supr) {
  4131. supr = supr.self;
  4132. parts = [supr.$childEls || me.getClassChildEls(supr)]; // super+mixins
  4133. superMixins = supr.prototype.mixins || {};
  4134. } else {
  4135. parts = [];
  4136. superMixins = {};
  4137. }
  4138. proto = cls.prototype;
  4139. mixins = proto.mixins; // since we are a mixin, there will be at least us
  4140. for (name in mixins) {
  4141. if (mixins.hasOwnProperty(name) && !superMixins.hasOwnProperty(name)) {
  4142. mixin = mixins[name].self;
  4143. parts.push(mixin.$childEls || me.getClassChildEls(mixin));
  4144. }
  4145. }
  4146. parts.push(proto.hasOwnProperty('childEls') && proto.childEls);
  4147. for (i = 0, length = parts.length; i < length; ++i) {
  4148. childEls = parts[i];
  4149. if (childEls && childEls.length) {
  4150. if (!result) {
  4151. result = childEls;
  4152. } else {
  4153. if (!forked) {
  4154. forked = true;
  4155. result = result.slice(0);
  4156. }
  4157. result.push.apply(result, childEls);
  4158. }
  4159. }
  4160. }
  4161. cls.$childEls = result = (result ? me.prune(result, !forked) : []);
  4162. }
  4163. return result;
  4164. },
  4165. prune: function (childEls, shared) {
  4166. var index = childEls.length,
  4167. map = {},
  4168. name;
  4169. while (index--) {
  4170. name = childEls[index];
  4171. if (typeof name != 'string') {
  4172. name = name.name;
  4173. }
  4174. if (!map[name]) {
  4175. map[name] = 1;
  4176. } else {
  4177. if (shared) {
  4178. shared = false;
  4179. childEls = childEls.slice(0);
  4180. }
  4181. Ext.Array.erase(childEls, index, 1);
  4182. }
  4183. }
  4184. return childEls;
  4185. },
  4186. /**
  4187. * Removes items in the childEls array based on the return value of a supplied test
  4188. * function. The function is called with a entry in childEls and if the test function
  4189. * return true, that entry is removed. If false, that entry is kept.
  4190. *
  4191. * @param {Function} testFn The test function.
  4192. */
  4193. removeChildEls: function (testFn) {
  4194. var me = this,
  4195. old = me.getChildEls(),
  4196. keepers = (me.childEls = []),
  4197. n, i, cel;
  4198. for (i = 0, n = old.length; i < n; ++i) {
  4199. cel = old[i];
  4200. if (!testFn(cel)) {
  4201. keepers.push(cel);
  4202. }
  4203. }
  4204. }
  4205. });
  4206. /**
  4207. * Given a component hierarchy of this:
  4208. *
  4209. * {
  4210. * xtype: 'panel',
  4211. * id: 'ContainerA',
  4212. * layout: 'hbox',
  4213. * renderTo: Ext.getBody(),
  4214. * items: [
  4215. * {
  4216. * id: 'ContainerB',
  4217. * xtype: 'container',
  4218. * items: [
  4219. * { id: 'ComponentA' }
  4220. * ]
  4221. * }
  4222. * ]
  4223. * }
  4224. *
  4225. * The rendering of the above proceeds roughly like this:
  4226. *
  4227. * - ContainerA's initComponent calls #render passing the `renderTo` property as the
  4228. * container argument.
  4229. * - `render` calls the `getRenderTree` method to get a complete {@link Ext.DomHelper} spec.
  4230. * - `getRenderTree` fires the "beforerender" event and calls the #beforeRender
  4231. * method. Its result is obtained by calling #getElConfig.
  4232. * - The #getElConfig method uses the `renderTpl` and its render data as the content
  4233. * of the `autoEl` described element.
  4234. * - The result of `getRenderTree` is passed to {@link Ext.DomHelper#append}.
  4235. * - The `renderTpl` contains calls to render things like docked items, container items
  4236. * and raw markup (such as the `html` or `tpl` config properties). These calls are to
  4237. * methods added to the {@link Ext.XTemplate} instance by #setupRenderTpl.
  4238. * - The #setupRenderTpl method adds methods such as `renderItems`, `renderContent`, etc.
  4239. * to the template. These are directed to "doRenderItems", "doRenderContent" etc..
  4240. * - The #setupRenderTpl calls traverse from components to their {@link Ext.layout.Layout}
  4241. * object.
  4242. * - When a container is rendered, it also has a `renderTpl`. This is processed when the
  4243. * `renderContainer` method is called in the component's `renderTpl`. This call goes to
  4244. * Ext.layout.container.Container#doRenderContainer. This method repeats this
  4245. * process for all components in the container.
  4246. * - After the top-most component's markup is generated and placed in to the DOM, the next
  4247. * step is to link elements to their components and finish calling the component methods
  4248. * `onRender` and `afterRender` as well as fire the corresponding events.
  4249. * - The first step in this is to call #finishRender. This method descends the
  4250. * component hierarchy and calls `onRender` and fires the `render` event. These calls
  4251. * are delivered top-down to approximate the timing of these calls/events from previous
  4252. * versions.
  4253. * - During the pass, the component's `el` is set. Likewise, the `renderSelectors` and
  4254. * `childEls` are applied to capture references to the component's elements.
  4255. * - These calls are also made on the {@link Ext.layout.container.Container} layout to
  4256. * capture its elements. Both of these classes use {@link Ext.util.ElementContainer} to
  4257. * handle `childEls` processing.
  4258. * - Once this is complete, a similar pass is made by calling #finishAfterRender.
  4259. * This call also descends the component hierarchy, but this time the calls are made in
  4260. * a bottom-up order to `afterRender`.
  4261. *
  4262. * @private
  4263. */
  4264. Ext.define('Ext.util.Renderable', {
  4265. requires: [
  4266. 'Ext.dom.Element'
  4267. ],
  4268. frameCls: Ext.baseCSSPrefix + 'frame',
  4269. frameIdRegex: /[\-]frame\d+[TMB][LCR]$/,
  4270. frameElementCls: {
  4271. tl: [],
  4272. tc: [],
  4273. tr: [],
  4274. ml: [],
  4275. mc: [],
  4276. mr: [],
  4277. bl: [],
  4278. bc: [],
  4279. br: []
  4280. },
  4281. frameElNames: ['TL','TC','TR','ML','MC','MR','BL','BC','BR'],
  4282. frameTpl: [
  4283. '{%this.renderDockedItems(out,values,0);%}',
  4284. '<tpl if="top">',
  4285. '<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>',
  4286. '<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>',
  4287. '<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>',
  4288. '<tpl if="right"></div></tpl>',
  4289. '<tpl if="left"></div></tpl>',
  4290. '</tpl>',
  4291. '<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>',
  4292. '<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>',
  4293. '<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">',
  4294. '{%this.applyRenderTpl(out, values)%}',
  4295. '</div>',
  4296. '<tpl if="right"></div></tpl>',
  4297. '<tpl if="left"></div></tpl>',
  4298. '<tpl if="bottom">',
  4299. '<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>',
  4300. '<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>',
  4301. '<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>',
  4302. '<tpl if="right"></div></tpl>',
  4303. '<tpl if="left"></div></tpl>',
  4304. '</tpl>',
  4305. '{%this.renderDockedItems(out,values,1);%}'
  4306. ],
  4307. frameTableTpl: [
  4308. '{%this.renderDockedItems(out,values,0);%}',
  4309. '<table><tbody>',
  4310. '<tpl if="top">',
  4311. '<tr>',
  4312. '<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>',
  4313. '<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>',
  4314. '<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>',
  4315. '</tr>',
  4316. '</tpl>',
  4317. '<tr>',
  4318. '<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>',
  4319. '<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">',
  4320. '{%this.applyRenderTpl(out, values)%}',
  4321. '</td>',
  4322. '<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>',
  4323. '</tr>',
  4324. '<tpl if="bottom">',
  4325. '<tr>',
  4326. '<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>',
  4327. '<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>',
  4328. '<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>',
  4329. '</tr>',
  4330. '</tpl>',
  4331. '</tbody></table>',
  4332. '{%this.renderDockedItems(out,values,1);%}'
  4333. ],
  4334. /**
  4335. * Allows addition of behavior after rendering is complete. At this stage the Component’s Element
  4336. * will have been styled according to the configuration, will have had any configured CSS class
  4337. * names added, and will be in the configured visibility and the configured enable state.
  4338. *
  4339. * @template
  4340. * @protected
  4341. */
  4342. afterRender : function() {
  4343. var me = this,
  4344. data = {},
  4345. protoEl = me.protoEl,
  4346. target = me.getTargetEl(),
  4347. item;
  4348. me.finishRenderChildren();
  4349. if (me.styleHtmlContent) {
  4350. target.addCls(me.styleHtmlCls);
  4351. }
  4352. protoEl.writeTo(data);
  4353. // Here we apply any styles that were set on the protoEl during the rendering phase
  4354. // A majority of times this will not happen, but we still need to handle it
  4355. item = data.removed;
  4356. if (item) {
  4357. target.removeCls(item);
  4358. }
  4359. item = data.cls;
  4360. if (item.length) {
  4361. target.addCls(item);
  4362. }
  4363. item = data.style;
  4364. if (data.style) {
  4365. target.setStyle(item);
  4366. }
  4367. me.protoEl = null;
  4368. // If this is the outermost Container, lay it out as soon as it is rendered.
  4369. if (!me.ownerCt) {
  4370. me.updateLayout();
  4371. }
  4372. },
  4373. afterFirstLayout : function() {
  4374. var me = this,
  4375. hasX = Ext.isDefined(me.x),
  4376. hasY = Ext.isDefined(me.y),
  4377. pos, xy;
  4378. // For floaters, calculate x and y if they aren't defined by aligning
  4379. // the sized element to the center of either the container or the ownerCt
  4380. if (me.floating && (!hasX || !hasY)) {
  4381. if (me.floatParent) {
  4382. xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
  4383. pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
  4384. } else {
  4385. xy = me.el.getAlignToXY(me.container, 'c-c');
  4386. pos = me.container.translatePoints(xy[0], xy[1]);
  4387. }
  4388. me.x = hasX ? me.x : pos.left;
  4389. me.y = hasY ? me.y : pos.top;
  4390. hasX = hasY = true;
  4391. }
  4392. if (hasX || hasY) {
  4393. me.setPosition(me.x, me.y);
  4394. }
  4395. me.onBoxReady();
  4396. if (me.hasListeners.boxready) {
  4397. me.fireEvent('boxready', me);
  4398. }
  4399. },
  4400. onBoxReady: Ext.emptyFn,
  4401. /**
  4402. * Sets references to elements inside the component. This applies {@link #renderSelectors}
  4403. * as well as {@link #childEls}.
  4404. * @private
  4405. */
  4406. applyRenderSelectors: function() {
  4407. var me = this,
  4408. selectors = me.renderSelectors,
  4409. el = me.el,
  4410. dom = el.dom,
  4411. selector;
  4412. me.applyChildEls(el);
  4413. // We still support renderSelectors. There are a few places in the framework that
  4414. // need them and they are a documented part of the API. In fact, we support mixing
  4415. // childEls and renderSelectors (no reason not to).
  4416. if (selectors) {
  4417. for (selector in selectors) {
  4418. if (selectors.hasOwnProperty(selector) && selectors[selector]) {
  4419. me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
  4420. }
  4421. }
  4422. }
  4423. },
  4424. beforeRender: function () {
  4425. var me = this,
  4426. layout = me.getComponentLayout();
  4427. if (!layout.initialized) {
  4428. layout.initLayout();
  4429. }
  4430. me.setUI(me.ui);
  4431. if (me.disabled) {
  4432. // pass silent so the event doesn't fire the first time.
  4433. me.disable(true);
  4434. }
  4435. },
  4436. /**
  4437. * @private
  4438. * Called from the selected frame generation template to insert this Component's inner structure inside the framing structure.
  4439. *
  4440. * When framing is used, a selected frame generation template is used as the primary template of the #getElConfig instead
  4441. * of the configured {@link #renderTpl}. The {@link #renderTpl} is invoked by this method which is injected into the framing template.
  4442. */
  4443. doApplyRenderTpl: function(out, values) {
  4444. // Careful! This method is bolted on to the frameTpl so all we get for context is
  4445. // the renderData! The "this" pointer is the frameTpl instance!
  4446. var me = values.$comp,
  4447. tpl;
  4448. // Don't do this if the component is already rendered:
  4449. if (!me.rendered) {
  4450. tpl = me.initRenderTpl();
  4451. tpl.applyOut(values.renderData, out);
  4452. }
  4453. },
  4454. /**
  4455. * Handles autoRender.
  4456. * Floating Components may have an ownerCt. If they are asking to be constrained, constrain them within that
  4457. * ownerCt, and have their z-index managed locally. Floating Components are always rendered to document.body
  4458. */
  4459. doAutoRender: function() {
  4460. var me = this;
  4461. if (!me.rendered) {
  4462. if (me.floating) {
  4463. me.render(document.body);
  4464. } else {
  4465. me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
  4466. }
  4467. }
  4468. },
  4469. doRenderContent: function (out, renderData) {
  4470. // Careful! This method is bolted on to the renderTpl so all we get for context is
  4471. // the renderData! The "this" pointer is the renderTpl instance!
  4472. var me = renderData.$comp;
  4473. if (me.html) {
  4474. Ext.DomHelper.generateMarkup(me.html, out);
  4475. delete me.html;
  4476. }
  4477. if (me.tpl) {
  4478. // Make sure this.tpl is an instantiated XTemplate
  4479. if (!me.tpl.isTemplate) {
  4480. me.tpl = new Ext.XTemplate(me.tpl);
  4481. }
  4482. if (me.data) {
  4483. //me.tpl[me.tplWriteMode](target, me.data);
  4484. me.tpl.applyOut(me.data, out);
  4485. delete me.data;
  4486. }
  4487. }
  4488. },
  4489. doRenderFramingDockedItems: function (out, renderData, after) {
  4490. // Careful! This method is bolted on to the frameTpl so all we get for context is
  4491. // the renderData! The "this" pointer is the frameTpl instance!
  4492. var me = renderData.$comp;
  4493. // Most components don't have dockedItems, so check for doRenderDockedItems on the
  4494. // component (also, don't do this if the component is already rendered):
  4495. if (!me.rendered && me.doRenderDockedItems) {
  4496. // The "renderData" property is placed in scope for the renderTpl, but we don't
  4497. // want to render docked items at that level in addition to the framing level:
  4498. renderData.renderData.$skipDockedItems = true;
  4499. // doRenderDockedItems requires the $comp property on renderData, but this is
  4500. // set on the frameTpl's renderData as well:
  4501. me.doRenderDockedItems.call(this, out, renderData, after);
  4502. }
  4503. },
  4504. /**
  4505. * This method visits the rendered component tree in a "top-down" order. That is, this
  4506. * code runs on a parent component before running on a child. This method calls the
  4507. * {@link #onRender} method of each component.
  4508. * @param {Number} containerIdx The index into the Container items of this Component.
  4509. *
  4510. * @private
  4511. */
  4512. finishRender: function(containerIdx) {
  4513. var me = this,
  4514. tpl, data, contentEl, el, pre, hide, target;
  4515. // We are typically called w/me.el==null as a child of some ownerCt that is being
  4516. // rendered. We are also called by render for a normal component (w/o a configured
  4517. // me.el). In this case, render sets me.el and me.rendering (indirectly). Lastly
  4518. // we are also called on a component (like a Viewport) that has a configured me.el
  4519. // (body for a Viewport) when render is called. In this case, it is not flagged as
  4520. // "me.rendering" yet becasue it does not produce a renderTree. We use this to know
  4521. // not to regen the renderTpl.
  4522. if (!me.el || me.$pid) {
  4523. if (me.container) {
  4524. el = me.container.getById(me.id, true);
  4525. } else {
  4526. el = Ext.getDom(me.id);
  4527. }
  4528. if (!me.el) {
  4529. // Typical case: we produced the el during render
  4530. me.wrapPrimaryEl(el);
  4531. } else {
  4532. // We were configured with an el and created a proxy, so now we can swap
  4533. // the proxy for me.el:
  4534. delete me.$pid;
  4535. if (!me.el.dom) {
  4536. // make sure me.el is an Element
  4537. me.wrapPrimaryEl(me.el);
  4538. }
  4539. el.parentNode.insertBefore(me.el.dom, el);
  4540. Ext.removeNode(el); // remove placeholder el
  4541. // TODO - what about class/style?
  4542. }
  4543. } else if (!me.rendering) {
  4544. // We were configured with an el and then told to render (e.g., Viewport). We
  4545. // need to generate the proper DOM. Insert first because the layout system
  4546. // insists that child Component elements indices match the Component indices.
  4547. tpl = me.initRenderTpl();
  4548. if (tpl) {
  4549. data = me.initRenderData();
  4550. tpl.insertFirst(me.getTargetEl(), data);
  4551. }
  4552. }
  4553. // else we are rendering
  4554. if (!me.container) {
  4555. // top-level rendered components will already have me.container set up
  4556. me.container = Ext.get(me.el.dom.parentNode);
  4557. }
  4558. if (me.ctCls) {
  4559. me.container.addCls(me.ctCls);
  4560. }
  4561. // Sets the rendered flag and clears the redering flag
  4562. me.onRender(me.container, containerIdx);
  4563. // Initialize with correct overflow attributes
  4564. target = me.getTargetEl();
  4565. target.setStyle(me.getOverflowStyle());
  4566. // Tell the encapsulating element to hide itself in the way the Component is configured to hide
  4567. // This means DISPLAY, VISIBILITY or OFFSETS.
  4568. me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
  4569. if (me.overCls) {
  4570. me.el.hover(me.addOverCls, me.removeOverCls, me);
  4571. }
  4572. if (me.hasListeners.render) {
  4573. me.fireEvent('render', me);
  4574. }
  4575. if (me.contentEl) {
  4576. pre = Ext.baseCSSPrefix;
  4577. hide = pre + 'hide-';
  4578. contentEl = Ext.get(me.contentEl);
  4579. contentEl.removeCls([pre+'hidden', hide+'display', hide+'offsets', hide+'nosize']);
  4580. target.appendChild(contentEl.dom);
  4581. }
  4582. me.afterRender(); // this can cause a layout
  4583. if (me.hasListeners.afterrender) {
  4584. me.fireEvent('afterrender', me);
  4585. }
  4586. me.initEvents();
  4587. if (me.hidden) {
  4588. // Hiding during the render process should not perform any ancillary
  4589. // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
  4590. // So just make the element hidden according to the configured hideMode
  4591. me.el.hide();
  4592. }
  4593. },
  4594. finishRenderChildren: function () {
  4595. var layout = this.getComponentLayout();
  4596. layout.finishRender();
  4597. },
  4598. getElConfig : function() {
  4599. var me = this,
  4600. autoEl = me.autoEl,
  4601. frameInfo = me.getFrameInfo(),
  4602. config = {
  4603. tag: 'div',
  4604. id: me.id,
  4605. tpl: frameInfo ? me.initFramingTpl(frameInfo.table) : me.initRenderTpl()
  4606. };
  4607. me.initStyles(me.protoEl);
  4608. me.protoEl.writeTo(config);
  4609. me.protoEl.flush();
  4610. if (Ext.isString(autoEl)) {
  4611. config.tag = autoEl;
  4612. } else {
  4613. Ext.apply(config, autoEl); // harmless if !autoEl
  4614. }
  4615. if (config.tpl) {
  4616. // Use the framingTpl as the main content creating template. It will call out to this.applyRenderTpl(out, values)
  4617. if (frameInfo) {
  4618. var i,
  4619. frameElNames = me.frameElNames,
  4620. len = frameElNames.length,
  4621. suffix,
  4622. frameGenId = me.id + '-frame1';
  4623. me.frameGenId = 1;
  4624. config.tplData = Ext.apply({}, {
  4625. $comp: me,
  4626. fgid: frameGenId,
  4627. ui: me.ui,
  4628. uiCls: me.uiCls,
  4629. frameCls: me.frameCls,
  4630. baseCls: me.baseCls,
  4631. frameWidth: frameInfo.maxWidth,
  4632. top: !!frameInfo.top,
  4633. left: !!frameInfo.left,
  4634. right: !!frameInfo.right,
  4635. bottom: !!frameInfo.bottom,
  4636. renderData: me.initRenderData()
  4637. }, me.getFramePositions(frameInfo));
  4638. // Add the childEls for each of the frame elements
  4639. for (i = 0; i < len; i++) {
  4640. suffix = frameElNames[i];
  4641. me.addChildEls({ name: 'frame' + suffix, id: frameGenId + suffix });
  4642. }
  4643. // Panel must have a frameBody
  4644. me.addChildEls({
  4645. name: 'frameBody',
  4646. id: frameGenId + 'MC'
  4647. });
  4648. } else {
  4649. config.tplData = me.initRenderData();
  4650. }
  4651. }
  4652. return config;
  4653. },
  4654. // Create the framingTpl from the string.
  4655. // Poke in a reference to applyRenderTpl(frameInfo, out)
  4656. initFramingTpl: function(table) {
  4657. var tpl = table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
  4658. if (tpl && !tpl.applyRenderTpl) {
  4659. this.setupFramingTpl(tpl);
  4660. }
  4661. return tpl;
  4662. },
  4663. /**
  4664. * @private
  4665. * Inject a reference to the function which applies the render template into the framing template. The framing template
  4666. * wraps the content.
  4667. */
  4668. setupFramingTpl: function(frameTpl) {
  4669. frameTpl.applyRenderTpl = this.doApplyRenderTpl;
  4670. frameTpl.renderDockedItems = this.doRenderFramingDockedItems;
  4671. },
  4672. /**
  4673. * This function takes the position argument passed to onRender and returns a
  4674. * DOM element that you can use in the insertBefore.
  4675. * @param {String/Number/Ext.dom.Element/HTMLElement} position Index, element id or element you want
  4676. * to put this component before.
  4677. * @return {HTMLElement} DOM element that you can use in the insertBefore
  4678. */
  4679. getInsertPosition: function(position) {
  4680. // Convert the position to an element to insert before
  4681. if (position !== undefined) {
  4682. if (Ext.isNumber(position)) {
  4683. position = this.container.dom.childNodes[position];
  4684. }
  4685. else {
  4686. position = Ext.getDom(position);
  4687. }
  4688. }
  4689. return position;
  4690. },
  4691. getRenderTree: function() {
  4692. var me = this;
  4693. me.beforeRender();
  4694. if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) {
  4695. // Flag to let the layout's finishRenderItems and afterFinishRenderItems
  4696. // know which items to process
  4697. me.rendering = true;
  4698. if (me.el) {
  4699. // Since we are producing a render tree, we produce a "proxy el" that will
  4700. // sit in the rendered DOM precisely where me.el belongs. We replace the
  4701. // proxy el in the finishRender phase.
  4702. return {
  4703. tag: 'div',
  4704. id: (me.$pid = Ext.id())
  4705. };
  4706. }
  4707. return me.getElConfig();
  4708. }
  4709. return null;
  4710. },
  4711. initContainer: function(container) {
  4712. var me = this;
  4713. // If you render a component specifying the el, we get the container
  4714. // of the el, and make sure we dont move the el around in the dom
  4715. // during the render
  4716. if (!container && me.el) {
  4717. container = me.el.dom.parentNode;
  4718. me.allowDomMove = false;
  4719. }
  4720. me.container = container.dom ? container : Ext.get(container);
  4721. return me.container;
  4722. },
  4723. /**
  4724. * Initialized the renderData to be used when rendering the renderTpl.
  4725. * @return {Object} Object with keys and values that are going to be applied to the renderTpl
  4726. * @private
  4727. */
  4728. initRenderData: function() {
  4729. var me = this;
  4730. return Ext.apply({
  4731. $comp: me,
  4732. id: me.id,
  4733. ui: me.ui,
  4734. uiCls: me.uiCls,
  4735. baseCls: me.baseCls,
  4736. componentCls: me.componentCls,
  4737. frame: me.frame
  4738. }, me.renderData);
  4739. },
  4740. /**
  4741. * Initializes the renderTpl.
  4742. * @return {Ext.XTemplate} The renderTpl XTemplate instance.
  4743. * @private
  4744. */
  4745. initRenderTpl: function() {
  4746. var tpl = this.getTpl('renderTpl');
  4747. if (tpl && !tpl.renderContent) {
  4748. this.setupRenderTpl(tpl);
  4749. }
  4750. return tpl;
  4751. },
  4752. /**
  4753. * Template method called when this Component's DOM structure is created.
  4754. *
  4755. * At this point, this Component's (and all descendants') DOM structure *exists* but it has not
  4756. * been layed out (positioned and sized).
  4757. *
  4758. * Subclasses which override this to gain access to the structure at render time should
  4759. * call the parent class's method before attempting to access any child elements of the Component.
  4760. *
  4761. * @param {Ext.core.Element} parentNode The parent Element in which this Component's encapsulating element is contained.
  4762. * @param {Number} containerIdx The index within the parent Container's child collection of this Component.
  4763. *
  4764. * @template
  4765. * @protected
  4766. */
  4767. onRender: function(parentNode, containerIdx) {
  4768. var me = this,
  4769. x = me.x,
  4770. y = me.y,
  4771. lastBox, width, height,
  4772. el = me.el;
  4773. // After the container property has been collected, we can wrap the Component in a reset wraper if necessary
  4774. if (Ext.scopeResetCSS && !me.ownerCt) {
  4775. // If this component's el is the body element, we add the reset class to the html tag
  4776. if (el.dom == Ext.getBody().dom) {
  4777. el.parent().addCls(Ext.resetCls);
  4778. }
  4779. else {
  4780. // Else we wrap this element in an element that adds the reset class.
  4781. me.resetEl = el.wrap({
  4782. cls: Ext.resetCls
  4783. });
  4784. }
  4785. }
  4786. me.applyRenderSelectors();
  4787. // Flag set on getRenderTree to flag to the layout's postprocessing routine that
  4788. // the Component is in the process of being rendered and needs postprocessing.
  4789. delete me.rendering;
  4790. me.rendered = true;
  4791. // We need to remember these to avoid writing them during the initial layout:
  4792. lastBox = null;
  4793. if (x !== undefined) {
  4794. lastBox = lastBox || {};
  4795. lastBox.x = x;
  4796. }
  4797. if (y !== undefined) {
  4798. lastBox = lastBox || {};
  4799. lastBox.y = y;
  4800. }
  4801. // Framed components need their width/height to apply to the frame, which is
  4802. // best handled in layout at present.
  4803. // If we're using the content box model, we also cannot assign initial sizes since we do not know the border widths to subtract
  4804. if (!me.getFrameInfo() && Ext.isBorderBox) {
  4805. width = me.width;
  4806. height = me.height;
  4807. if (typeof width == 'number') {
  4808. lastBox = lastBox || {};
  4809. lastBox.width = width;
  4810. }
  4811. if (typeof height == 'number') {
  4812. lastBox = lastBox || {};
  4813. lastBox.height = height;
  4814. }
  4815. }
  4816. me.lastBox = me.el.lastBox = lastBox;
  4817. },
  4818. render: function(container, position) {
  4819. var me = this,
  4820. el = me.el && (me.el = Ext.get(me.el)), // ensure me.el is wrapped
  4821. tree,
  4822. nextSibling;
  4823. Ext.suspendLayouts();
  4824. container = me.initContainer(container);
  4825. nextSibling = me.getInsertPosition(position);
  4826. if (!el) {
  4827. tree = me.getRenderTree();
  4828. if (nextSibling) {
  4829. el = Ext.DomHelper.insertBefore(nextSibling, tree);
  4830. } else {
  4831. el = Ext.DomHelper.append(container, tree);
  4832. }
  4833. me.wrapPrimaryEl(el);
  4834. } else {
  4835. // Set configured styles on pre-rendered Component's element
  4836. me.initStyles(el);
  4837. if (me.allowDomMove !== false) {
  4838. //debugger; // TODO
  4839. if (nextSibling) {
  4840. container.dom.insertBefore(el.dom, nextSibling);
  4841. } else {
  4842. container.dom.appendChild(el.dom);
  4843. }
  4844. }
  4845. }
  4846. me.finishRender(position);
  4847. Ext.resumeLayouts(!container.isDetachedBody);
  4848. },
  4849. /**
  4850. * Ensures that this component is attached to `document.body`. If the component was
  4851. * rendered to {@link Ext#getDetachedBody}, then it will be appended to `document.body`.
  4852. * Any configured position is also restored.
  4853. * @param {Boolean} [runLayout=false] True to run the component's layout.
  4854. */
  4855. ensureAttachedToBody: function (runLayout) {
  4856. var comp = this,
  4857. body;
  4858. while (comp.ownerCt) {
  4859. comp = comp.ownerCt;
  4860. }
  4861. if (comp.container.isDetachedBody) {
  4862. comp.container = body = Ext.getBody();
  4863. body.appendChild(comp.el.dom);
  4864. if (runLayout) {
  4865. comp.updateLayout();
  4866. }
  4867. if (typeof comp.x == 'number' || typeof comp.y == 'number') {
  4868. comp.setPosition(comp.x, comp.y);
  4869. }
  4870. }
  4871. },
  4872. setupRenderTpl: function (renderTpl) {
  4873. renderTpl.renderBody = renderTpl.renderContent = this.doRenderContent;
  4874. },
  4875. wrapPrimaryEl: function (dom) {
  4876. this.el = Ext.get(dom, true);
  4877. },
  4878. /**
  4879. * @private
  4880. */
  4881. initFrame : function() {
  4882. if (Ext.supports.CSS3BorderRadius) {
  4883. return;
  4884. }
  4885. var me = this,
  4886. frameInfo = me.getFrameInfo(),
  4887. frameWidth, frameTpl, frameGenId,
  4888. i,
  4889. frameElNames = me.frameElNames,
  4890. len = frameElNames.length,
  4891. suffix;
  4892. if (frameInfo) {
  4893. frameWidth = frameInfo.maxWidth;
  4894. frameTpl = me.getFrameTpl(frameInfo.table);
  4895. // since we render id's into the markup and id's NEED to be unique, we have a
  4896. // simple strategy for numbering their generations.
  4897. me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
  4898. frameGenId = me.id + '-frame' + frameGenId;
  4899. // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
  4900. frameTpl.insertFirst(me.el, Ext.apply({
  4901. $comp: me,
  4902. fgid: frameGenId,
  4903. ui: me.ui,
  4904. uiCls: me.uiCls,
  4905. frameCls: me.frameCls,
  4906. baseCls: me.baseCls,
  4907. frameWidth: frameWidth,
  4908. top: !!frameInfo.top,
  4909. left: !!frameInfo.left,
  4910. right: !!frameInfo.right,
  4911. bottom: !!frameInfo.bottom
  4912. }, me.getFramePositions(frameInfo)));
  4913. // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.
  4914. me.frameBody = me.el.down('.' + me.frameCls + '-mc');
  4915. // Clean out the childEls for the old frame elements (the majority of the els)
  4916. me.removeChildEls(function (c) {
  4917. return c.id && me.frameIdRegex.test(c.id);
  4918. });
  4919. // Grab references to the childEls for each of the new frame elements
  4920. for (i = 0; i < len; i++) {
  4921. suffix = frameElNames[i];
  4922. me['frame' + suffix] = me.el.getById(frameGenId + suffix);
  4923. }
  4924. }
  4925. },
  4926. updateFrame: function() {
  4927. if (Ext.supports.CSS3BorderRadius) {
  4928. return;
  4929. }
  4930. var me = this,
  4931. wasTable = this.frameSize && this.frameSize.table,
  4932. oldFrameTL = this.frameTL,
  4933. oldFrameBL = this.frameBL,
  4934. oldFrameML = this.frameML,
  4935. oldFrameMC = this.frameMC,
  4936. newMCClassName;
  4937. this.initFrame();
  4938. if (oldFrameMC) {
  4939. if (me.frame) {
  4940. // Store the class names set on the new MC
  4941. newMCClassName = this.frameMC.dom.className;
  4942. // Framing elements have been selected in initFrame, no need to run applyRenderSelectors
  4943. // Replace the new mc with the old mc
  4944. oldFrameMC.insertAfter(this.frameMC);
  4945. this.frameMC.remove();
  4946. // Restore the reference to the old frame mc as the framebody
  4947. this.frameBody = this.frameMC = oldFrameMC;
  4948. // Apply the new mc classes to the old mc element
  4949. oldFrameMC.dom.className = newMCClassName;
  4950. // Remove the old framing
  4951. if (wasTable) {
  4952. me.el.query('> table')[1].remove();
  4953. }
  4954. else {
  4955. if (oldFrameTL) {
  4956. oldFrameTL.remove();
  4957. }
  4958. if (oldFrameBL) {
  4959. oldFrameBL.remove();
  4960. }
  4961. if (oldFrameML) {
  4962. oldFrameML.remove();
  4963. }
  4964. }
  4965. }
  4966. else {
  4967. // We were framed but not anymore. Move all content from the old frame to the body
  4968. }
  4969. }
  4970. else if (me.frame) {
  4971. this.applyRenderSelectors();
  4972. }
  4973. },
  4974. /**
  4975. * @private
  4976. * On render, reads an encoded style attribute, "background-position" from the style of this Component's element.
  4977. * This information is memoized based upon the CSS class name of this Component's element.
  4978. * Because child Components are rendered as textual HTML as part of the topmost Container, a dummy div is inserted
  4979. * into the document to receive the document element's CSS class name, and therefore style attributes.
  4980. */
  4981. getFrameInfo: function() {
  4982. // If native framing can be used, or this Component is not configured (or written) to be framed,
  4983. // then do not attempt to read CSS framing info.
  4984. if (Ext.supports.CSS3BorderRadius) {
  4985. return false;
  4986. }
  4987. var me = this,
  4988. frameInfoCache = me.frameInfoCache,
  4989. el = me.el || me.protoEl,
  4990. cls = el.dom ? el.dom.className : el.classList.join(' '),
  4991. frameInfo = frameInfoCache[cls],
  4992. styleEl, left, top, info;
  4993. if (frameInfo == null) {
  4994. // Get the singleton frame style proxy with our el class name stamped into it.
  4995. styleEl = Ext.fly(me.getStyleProxy(cls), 'frame-style-el');
  4996. left = styleEl.getStyle('background-position-x');
  4997. top = styleEl.getStyle('background-position-y');
  4998. // Some browsers don't support background-position-x and y, so for those
  4999. // browsers let's split background-position into two parts.
  5000. if (!left && !top) {
  5001. info = styleEl.getStyle('background-position').split(' ');
  5002. left = info[0];
  5003. top = info[1];
  5004. }
  5005. frameInfo = me.calculateFrame(left, top);
  5006. if (frameInfo) {
  5007. // Just to be sure we set the background image of the el to none.
  5008. el.setStyle('background-image', 'none');
  5009. }
  5010. // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
  5011. // This way IE can't figure out what sizes to use and thus framing can't work.
  5012. if (me.frame === true && !frameInfo) {
  5013. Ext.log.error('You have set frame: true explicity on this component (' + me.getXType() + ') and it ' +
  5014. 'does not have any framing defined in the CSS template. In this case IE cannot figure out ' +
  5015. 'what sizes to use and thus framing on this component will be disabled.');
  5016. }
  5017. frameInfoCache[cls] = frameInfo;
  5018. }
  5019. me.frame = !!frameInfo;
  5020. me.frameSize = frameInfo;
  5021. return frameInfo;
  5022. },
  5023. calculateFrame: function(left, top){
  5024. // We actually pass a string in the form of '[type][tl][tr]px [direction][br][bl]px' as
  5025. // the background position of this.el from the CSS to indicate to IE that this component needs
  5026. // framing. We parse it here.
  5027. if (!(parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000)) {
  5028. return false;
  5029. }
  5030. var max = Math.max,
  5031. tl = parseInt(left.substr(3, 2), 10),
  5032. tr = parseInt(left.substr(5, 2), 10),
  5033. br = parseInt(top.substr(3, 2), 10),
  5034. bl = parseInt(top.substr(5, 2), 10),
  5035. frameInfo = {
  5036. // Table markup starts with 110, div markup with 100.
  5037. table: left.substr(0, 3) == '110',
  5038. // Determine if we are dealing with a horizontal or vertical component
  5039. vertical: top.substr(0, 3) == '110',
  5040. // Get and parse the different border radius sizes
  5041. top: max(tl, tr),
  5042. right: max(tr, br),
  5043. bottom: max(bl, br),
  5044. left: max(tl, bl)
  5045. };
  5046. frameInfo.maxWidth = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
  5047. frameInfo.width = frameInfo.left + frameInfo.right;
  5048. frameInfo.height = frameInfo.top + frameInfo.bottom;
  5049. return frameInfo;
  5050. },
  5051. /**
  5052. * @private
  5053. * Returns an offscreen div with the same class name as the element this is being rendered.
  5054. * This is because child item rendering takes place in a detached div which, being ot part of the document, has no styling.
  5055. */
  5056. getStyleProxy: function(cls) {
  5057. var result = this.styleProxyEl || (Ext.AbstractComponent.prototype.styleProxyEl = Ext.getBody().createChild({
  5058. style: {
  5059. position: 'absolute',
  5060. top: '-10000px'
  5061. }
  5062. }, null, true));
  5063. result.className = cls;
  5064. return result;
  5065. },
  5066. getFramePositions: function(frameInfo) {
  5067. var me = this,
  5068. frameWidth = frameInfo.maxWidth,
  5069. dock = me.dock,
  5070. positions, tc, bc, ml, mr;
  5071. if (frameInfo.vertical) {
  5072. tc = '0 -' + (frameWidth * 0) + 'px';
  5073. bc = '0 -' + (frameWidth * 1) + 'px';
  5074. if (dock && dock == "right") {
  5075. tc = 'right -' + (frameWidth * 0) + 'px';
  5076. bc = 'right -' + (frameWidth * 1) + 'px';
  5077. }
  5078. positions = {
  5079. tl: '0 -' + (frameWidth * 0) + 'px',
  5080. tr: '0 -' + (frameWidth * 1) + 'px',
  5081. bl: '0 -' + (frameWidth * 2) + 'px',
  5082. br: '0 -' + (frameWidth * 3) + 'px',
  5083. ml: '-' + (frameWidth * 1) + 'px 0',
  5084. mr: 'right 0',
  5085. tc: tc,
  5086. bc: bc
  5087. };
  5088. } else {
  5089. ml = '-' + (frameWidth * 0) + 'px 0';
  5090. mr = 'right 0';
  5091. if (dock && dock == "bottom") {
  5092. ml = 'left bottom';
  5093. mr = 'right bottom';
  5094. }
  5095. positions = {
  5096. tl: '0 -' + (frameWidth * 2) + 'px',
  5097. tr: 'right -' + (frameWidth * 3) + 'px',
  5098. bl: '0 -' + (frameWidth * 4) + 'px',
  5099. br: 'right -' + (frameWidth * 5) + 'px',
  5100. ml: ml,
  5101. mr: mr,
  5102. tc: '0 -' + (frameWidth * 0) + 'px',
  5103. bc: '0 -' + (frameWidth * 1) + 'px'
  5104. };
  5105. }
  5106. return positions;
  5107. },
  5108. /**
  5109. * @private
  5110. */
  5111. getFrameTpl : function(table) {
  5112. return this.getTpl(table ? 'frameTableTpl' : 'frameTpl');
  5113. },
  5114. // Cache the frame information object so as not to cause style recalculations
  5115. frameInfoCache: {}
  5116. });
  5117. /**
  5118. * Provides searching of Components within Ext.ComponentManager (globally) or a specific
  5119. * Ext.container.Container on the document with a similar syntax to a CSS selector.
  5120. *
  5121. * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
  5122. *
  5123. * - `component` or `.component`
  5124. * - `gridpanel` or `.gridpanel`
  5125. *
  5126. * An itemId or id must be prefixed with a #
  5127. *
  5128. * - `#myContainer`
  5129. *
  5130. * Attributes must be wrapped in brackets
  5131. *
  5132. * - `component[autoScroll]`
  5133. * - `panel[title="Test"]`
  5134. *
  5135. * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
  5136. * the candidate Component will be included in the query:
  5137. *
  5138. * var disabledFields = myFormPanel.query("{isDisabled()}");
  5139. *
  5140. * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
  5141. *
  5142. * // Function receives array and returns a filtered array.
  5143. * Ext.ComponentQuery.pseudos.invalid = function(items) {
  5144. * var i = 0, l = items.length, c, result = [];
  5145. * for (; i < l; i++) {
  5146. * if (!(c = items[i]).isValid()) {
  5147. * result.push(c);
  5148. * }
  5149. * }
  5150. * return result;
  5151. * };
  5152. *
  5153. * var invalidFields = myFormPanel.query('field:invalid');
  5154. * if (invalidFields.length) {
  5155. * invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
  5156. * for (var i = 0, l = invalidFields.length; i < l; i++) {
  5157. * invalidFields[i].getEl().frame("red");
  5158. * }
  5159. * }
  5160. *
  5161. * Default pseudos include:
  5162. *
  5163. * - not
  5164. * - last
  5165. *
  5166. * Queries return an array of components.
  5167. * Here are some example queries.
  5168. *
  5169. * // retrieve all Ext.Panels in the document by xtype
  5170. * var panelsArray = Ext.ComponentQuery.query('panel');
  5171. *
  5172. * // retrieve all Ext.Panels within the container with an id myCt
  5173. * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
  5174. *
  5175. * // retrieve all direct children which are Ext.Panels within myCt
  5176. * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
  5177. *
  5178. * // retrieve all grids and trees
  5179. * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
  5180. *
  5181. * For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
  5182. * {@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
  5183. * {@link Ext.Component#up}.
  5184. */
  5185. Ext.define('Ext.ComponentQuery', {
  5186. singleton: true,
  5187. uses: ['Ext.ComponentManager']
  5188. }, function() {
  5189. var cq = this,
  5190. // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
  5191. // as a member on each item in the passed array.
  5192. filterFnPattern = [
  5193. 'var r = [],',
  5194. 'i = 0,',
  5195. 'it = items,',
  5196. 'l = it.length,',
  5197. 'c;',
  5198. 'for (; i < l; i++) {',
  5199. 'c = it[i];',
  5200. 'if (c.{0}) {',
  5201. 'r.push(c);',
  5202. '}',
  5203. '}',
  5204. 'return r;'
  5205. ].join(''),
  5206. filterItems = function(items, operation) {
  5207. // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
  5208. // The operation's method loops over each item in the candidate array and
  5209. // returns an array of items which match its criteria
  5210. return operation.method.apply(this, [ items ].concat(operation.args));
  5211. },
  5212. getItems = function(items, mode) {
  5213. var result = [],
  5214. i = 0,
  5215. length = items.length,
  5216. candidate,
  5217. deep = mode !== '>';
  5218. for (; i < length; i++) {
  5219. candidate = items[i];
  5220. if (candidate.getRefItems) {
  5221. result = result.concat(candidate.getRefItems(deep));
  5222. }
  5223. }
  5224. return result;
  5225. },
  5226. getAncestors = function(items) {
  5227. var result = [],
  5228. i = 0,
  5229. length = items.length,
  5230. candidate;
  5231. for (; i < length; i++) {
  5232. candidate = items[i];
  5233. while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
  5234. result.push(candidate);
  5235. }
  5236. }
  5237. return result;
  5238. },
  5239. // Filters the passed candidate array and returns only items which match the passed xtype
  5240. filterByXType = function(items, xtype, shallow) {
  5241. if (xtype === '*') {
  5242. return items.slice();
  5243. }
  5244. else {
  5245. var result = [],
  5246. i = 0,
  5247. length = items.length,
  5248. candidate;
  5249. for (; i < length; i++) {
  5250. candidate = items[i];
  5251. if (candidate.isXType(xtype, shallow)) {
  5252. result.push(candidate);
  5253. }
  5254. }
  5255. return result;
  5256. }
  5257. },
  5258. // Filters the passed candidate array and returns only items which have the passed className
  5259. filterByClassName = function(items, className) {
  5260. var EA = Ext.Array,
  5261. result = [],
  5262. i = 0,
  5263. length = items.length,
  5264. candidate;
  5265. for (; i < length; i++) {
  5266. candidate = items[i];
  5267. if (candidate.hasCls(className)) {
  5268. result.push(candidate);
  5269. }
  5270. }
  5271. return result;
  5272. },
  5273. // Filters the passed candidate array and returns only items which have the specified property match
  5274. filterByAttribute = function(items, property, operator, value) {
  5275. var result = [],
  5276. i = 0,
  5277. length = items.length,
  5278. candidate;
  5279. for (; i < length; i++) {
  5280. candidate = items[i];
  5281. if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
  5282. result.push(candidate);
  5283. }
  5284. }
  5285. return result;
  5286. },
  5287. // Filters the passed candidate array and returns only items which have the specified itemId or id
  5288. filterById = function(items, id) {
  5289. var result = [],
  5290. i = 0,
  5291. length = items.length,
  5292. candidate;
  5293. for (; i < length; i++) {
  5294. candidate = items[i];
  5295. if (candidate.getItemId() === id) {
  5296. result.push(candidate);
  5297. }
  5298. }
  5299. return result;
  5300. },
  5301. // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
  5302. filterByPseudo = function(items, name, value) {
  5303. return cq.pseudos[name](items, value);
  5304. },
  5305. // Determines leading mode
  5306. // > for direct child, and ^ to switch to ownerCt axis
  5307. modeRe = /^(\s?([>\^])\s?|\s|$)/,
  5308. // Matches a token with possibly (true|false) appended for the "shallow" parameter
  5309. tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
  5310. matchers = [{
  5311. // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
  5312. re: /^\.([\w\-]+)(?:\((true|false)\))?/,
  5313. method: filterByXType
  5314. },{
  5315. // checks for [attribute=value]
  5316. re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
  5317. method: filterByAttribute
  5318. }, {
  5319. // checks for #cmpItemId
  5320. re: /^#([\w\-]+)/,
  5321. method: filterById
  5322. }, {
  5323. // checks for :<pseudo_class>(<selector>)
  5324. re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
  5325. method: filterByPseudo
  5326. }, {
  5327. // checks for {<member_expression>}
  5328. re: /^(?:\{([^\}]+)\})/,
  5329. method: filterFnPattern
  5330. }];
  5331. // Internal class Ext.ComponentQuery.Query
  5332. cq.Query = Ext.extend(Object, {
  5333. constructor: function(cfg) {
  5334. cfg = cfg || {};
  5335. Ext.apply(this, cfg);
  5336. },
  5337. // Executes this Query upon the selected root.
  5338. // The root provides the initial source of candidate Component matches which are progressively
  5339. // filtered by iterating through this Query's operations cache.
  5340. // If no root is provided, all registered Components are searched via the ComponentManager.
  5341. // root may be a Container who's descendant Components are filtered
  5342. // root may be a Component with an implementation of getRefItems which provides some nested Components such as the
  5343. // docked items within a Panel.
  5344. // root may be an array of candidate Components to filter using this Query.
  5345. execute : function(root) {
  5346. var operations = this.operations,
  5347. i = 0,
  5348. length = operations.length,
  5349. operation,
  5350. workingItems;
  5351. // no root, use all Components in the document
  5352. if (!root) {
  5353. workingItems = Ext.ComponentManager.all.getArray();
  5354. }
  5355. // Root is a candidate Array
  5356. else if (Ext.isArray(root)) {
  5357. workingItems = root;
  5358. }
  5359. // We are going to loop over our operations and take care of them
  5360. // one by one.
  5361. for (; i < length; i++) {
  5362. operation = operations[i];
  5363. // The mode operation requires some custom handling.
  5364. // All other operations essentially filter down our current
  5365. // working items, while mode replaces our current working
  5366. // items by getting children from each one of our current
  5367. // working items. The type of mode determines the type of
  5368. // children we get. (e.g. > only gets direct children)
  5369. if (operation.mode === '^') {
  5370. workingItems = getAncestors(workingItems || [root]);
  5371. }
  5372. else if (operation.mode) {
  5373. workingItems = getItems(workingItems || [root], operation.mode);
  5374. }
  5375. else {
  5376. workingItems = filterItems(workingItems || getItems([root]), operation);
  5377. }
  5378. // If this is the last operation, it means our current working
  5379. // items are the final matched items. Thus return them!
  5380. if (i === length -1) {
  5381. return workingItems;
  5382. }
  5383. }
  5384. return [];
  5385. },
  5386. is: function(component) {
  5387. var operations = this.operations,
  5388. components = Ext.isArray(component) ? component : [component],
  5389. originalLength = components.length,
  5390. lastOperation = operations[operations.length-1],
  5391. ln, i;
  5392. components = filterItems(components, lastOperation);
  5393. if (components.length === originalLength) {
  5394. if (operations.length > 1) {
  5395. for (i = 0, ln = components.length; i < ln; i++) {
  5396. if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
  5397. return false;
  5398. }
  5399. }
  5400. }
  5401. return true;
  5402. }
  5403. return false;
  5404. }
  5405. });
  5406. Ext.apply(this, {
  5407. // private cache of selectors and matching ComponentQuery.Query objects
  5408. cache: {},
  5409. // private cache of pseudo class filter functions
  5410. pseudos: {
  5411. not: function(components, selector){
  5412. var CQ = Ext.ComponentQuery,
  5413. i = 0,
  5414. length = components.length,
  5415. results = [],
  5416. index = -1,
  5417. component;
  5418. for(; i < length; ++i) {
  5419. component = components[i];
  5420. if (!CQ.is(component, selector)) {
  5421. results[++index] = component;
  5422. }
  5423. }
  5424. return results;
  5425. },
  5426. last: function(components) {
  5427. return components[components.length - 1];
  5428. }
  5429. },
  5430. /**
  5431. * Returns an array of matched Components from within the passed root object.
  5432. *
  5433. * This method filters returned Components in a similar way to how CSS selector based DOM
  5434. * queries work using a textual selector string.
  5435. *
  5436. * See class summary for details.
  5437. *
  5438. * @param {String} selector The selector string to filter returned Components
  5439. * @param {Ext.container.Container} root The Container within which to perform the query.
  5440. * If omitted, all Components within the document are included in the search.
  5441. *
  5442. * This parameter may also be an array of Components to filter according to the selector.</p>
  5443. * @returns {Ext.Component[]} The matched Components.
  5444. *
  5445. * @member Ext.ComponentQuery
  5446. */
  5447. query: function(selector, root) {
  5448. var selectors = selector.split(','),
  5449. length = selectors.length,
  5450. i = 0,
  5451. results = [],
  5452. noDupResults = [],
  5453. dupMatcher = {},
  5454. query, resultsLn, cmp;
  5455. for (; i < length; i++) {
  5456. selector = Ext.String.trim(selectors[i]);
  5457. query = this.cache[selector];
  5458. if (!query) {
  5459. this.cache[selector] = query = this.parse(selector);
  5460. }
  5461. results = results.concat(query.execute(root));
  5462. }
  5463. // multiple selectors, potential to find duplicates
  5464. // lets filter them out.
  5465. if (length > 1) {
  5466. resultsLn = results.length;
  5467. for (i = 0; i < resultsLn; i++) {
  5468. cmp = results[i];
  5469. if (!dupMatcher[cmp.id]) {
  5470. noDupResults.push(cmp);
  5471. dupMatcher[cmp.id] = true;
  5472. }
  5473. }
  5474. results = noDupResults;
  5475. }
  5476. return results;
  5477. },
  5478. /**
  5479. * Tests whether the passed Component matches the selector string.
  5480. * @param {Ext.Component} component The Component to test
  5481. * @param {String} selector The selector string to test against.
  5482. * @return {Boolean} True if the Component matches the selector.
  5483. * @member Ext.ComponentQuery
  5484. */
  5485. is: function(component, selector) {
  5486. if (!selector) {
  5487. return true;
  5488. }
  5489. var query = this.cache[selector];
  5490. if (!query) {
  5491. this.cache[selector] = query = this.parse(selector);
  5492. }
  5493. return query.is(component);
  5494. },
  5495. parse: function(selector) {
  5496. var operations = [],
  5497. length = matchers.length,
  5498. lastSelector,
  5499. tokenMatch,
  5500. matchedChar,
  5501. modeMatch,
  5502. selectorMatch,
  5503. i, matcher, method;
  5504. // We are going to parse the beginning of the selector over and
  5505. // over again, slicing off the selector any portions we converted into an
  5506. // operation, until it is an empty string.
  5507. while (selector && lastSelector !== selector) {
  5508. lastSelector = selector;
  5509. // First we check if we are dealing with a token like #, * or an xtype
  5510. tokenMatch = selector.match(tokenRe);
  5511. if (tokenMatch) {
  5512. matchedChar = tokenMatch[1];
  5513. // If the token is prefixed with a # we push a filterById operation to our stack
  5514. if (matchedChar === '#') {
  5515. operations.push({
  5516. method: filterById,
  5517. args: [Ext.String.trim(tokenMatch[2])]
  5518. });
  5519. }
  5520. // If the token is prefixed with a . we push a filterByClassName operation to our stack
  5521. // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
  5522. else if (matchedChar === '.') {
  5523. operations.push({
  5524. method: filterByClassName,
  5525. args: [Ext.String.trim(tokenMatch[2])]
  5526. });
  5527. }
  5528. // If the token is a * or an xtype string, we push a filterByXType
  5529. // operation to the stack.
  5530. else {
  5531. operations.push({
  5532. method: filterByXType,
  5533. args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
  5534. });
  5535. }
  5536. // Now we slice of the part we just converted into an operation
  5537. selector = selector.replace(tokenMatch[0], '');
  5538. }
  5539. // If the next part of the query is not a space or > or ^, it means we
  5540. // are going to check for more things that our current selection
  5541. // has to comply to.
  5542. while (!(modeMatch = selector.match(modeRe))) {
  5543. // Lets loop over each type of matcher and execute it
  5544. // on our current selector.
  5545. for (i = 0; selector && i < length; i++) {
  5546. matcher = matchers[i];
  5547. selectorMatch = selector.match(matcher.re);
  5548. method = matcher.method;
  5549. // If we have a match, add an operation with the method
  5550. // associated with this matcher, and pass the regular
  5551. // expression matches are arguments to the operation.
  5552. if (selectorMatch) {
  5553. operations.push({
  5554. method: Ext.isString(matcher.method)
  5555. // Turn a string method into a function by formatting the string with our selector matche expression
  5556. // A new method is created for different match expressions, eg {id=='textfield-1024'}
  5557. // Every expression may be different in different selectors.
  5558. ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
  5559. : matcher.method,
  5560. args: selectorMatch.slice(1)
  5561. });
  5562. selector = selector.replace(selectorMatch[0], '');
  5563. break; // Break on match
  5564. }
  5565. // Exhausted all matches: It's an error
  5566. if (i === (length - 1)) {
  5567. Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
  5568. }
  5569. }
  5570. }
  5571. // Now we are going to check for a mode change. This means a space
  5572. // or a > to determine if we are going to select all the children
  5573. // of the currently matched items, or a ^ if we are going to use the
  5574. // ownerCt axis as the candidate source.
  5575. if (modeMatch[1]) { // Assignment, and test for truthiness!
  5576. operations.push({
  5577. mode: modeMatch[2]||modeMatch[1]
  5578. });
  5579. selector = selector.replace(modeMatch[0], '');
  5580. }
  5581. }
  5582. // Now that we have all our operations in an array, we are going
  5583. // to create a new Query using these operations.
  5584. return new cq.Query({
  5585. operations: operations
  5586. });
  5587. }
  5588. });
  5589. });
  5590. /**
  5591. * Manages certain element-like data prior to rendering. These values are passed
  5592. * on to the render process. This is currently used to manage the "class" and "style" attributes
  5593. * of a component's primary el as well as the bodyEl of panels. This allows things like
  5594. * addBodyCls in Panel to share logic with addCls in AbstractComponent.
  5595. * @private
  5596. */
  5597. /*
  5598. * The dirty implementation in this class is quite naive. The reasoning for this is that the dirty state
  5599. * will only be used in very specific circumstances, specifically, after the render process has begun but
  5600. * the component is not yet rendered to the DOM. As such, we want it to perform as quickly as possible
  5601. * so it's not as fully featured as you may expect.
  5602. */
  5603. Ext.define('Ext.util.ProtoElement', function () {
  5604. var splitWords = Ext.String.splitWords,
  5605. toMap = Ext.Array.toMap;
  5606. return {
  5607. isProtoEl: true,
  5608. /**
  5609. * The property name for the className on the data object passed to {@link #writeTo}.
  5610. */
  5611. clsProp: 'cls',
  5612. /**
  5613. * The property name for the style on the data object passed to {@link #writeTo}.
  5614. */
  5615. styleProp: 'style',
  5616. /**
  5617. * The property name for the removed classes on the data object passed to {@link #writeTo}.
  5618. */
  5619. removedProp: 'removed',
  5620. /**
  5621. * True if the style must be converted to text during {@link #writeTo}. When used to
  5622. * populate tpl data, this will be true. When used to populate {@link Ext.DomHelper}
  5623. * specs, this will be false (the default).
  5624. */
  5625. styleIsText: false,
  5626. constructor: function (config) {
  5627. var me = this;
  5628. Ext.apply(me, config);
  5629. me.classList = splitWords(me.cls);
  5630. me.classMap = toMap(me.classList);
  5631. delete me.cls;
  5632. if (Ext.isFunction(me.style)) {
  5633. me.styleFn = me.style;
  5634. delete me.style;
  5635. } else if (typeof me.style == 'string') {
  5636. me.style = Ext.Element.parseStyles(me.style);
  5637. } else if (me.style) {
  5638. me.style = Ext.apply({}, me.style); // don't edit the given object
  5639. }
  5640. },
  5641. /**
  5642. * Indicates that the current state of the object has been flushed to the DOM, so we need
  5643. * to track any subsequent changes
  5644. */
  5645. flush: function(){
  5646. this.flushClassList = [];
  5647. this.removedClasses = {};
  5648. // clear the style, it will be recreated if we add anything new
  5649. delete this.style;
  5650. },
  5651. /**
  5652. * Adds class to the element.
  5653. * @param {String} cls One or more classnames separated with spaces.
  5654. * @return {Ext.util.ProtoElement} this
  5655. */
  5656. addCls: function (cls) {
  5657. var me = this,
  5658. add = splitWords(cls),
  5659. length = add.length,
  5660. list = me.classList,
  5661. map = me.classMap,
  5662. flushList = me.flushClassList,
  5663. i = 0,
  5664. c;
  5665. for (; i < length; ++i) {
  5666. c = add[i];
  5667. if (!map[c]) {
  5668. map[c] = true;
  5669. list.push(c);
  5670. if (flushList) {
  5671. flushList.push(c);
  5672. delete me.removedClasses[c];
  5673. }
  5674. }
  5675. }
  5676. return me;
  5677. },
  5678. /**
  5679. * True if the element has given class.
  5680. * @param {String} cls
  5681. * @return {Boolean}
  5682. */
  5683. hasCls: function (cls) {
  5684. return cls in this.classMap;
  5685. },
  5686. /**
  5687. * Removes class from the element.
  5688. * @param {String} cls One or more classnames separated with spaces.
  5689. * @return {Ext.util.ProtoElement} this
  5690. */
  5691. removeCls: function (cls) {
  5692. var me = this,
  5693. list = me.classList,
  5694. newList = (me.classList = []),
  5695. remove = toMap(splitWords(cls)),
  5696. length = list.length,
  5697. map = me.classMap,
  5698. removedClasses = me.removedClasses,
  5699. i, c;
  5700. for (i = 0; i < length; ++i) {
  5701. c = list[i];
  5702. if (remove[c]) {
  5703. if (removedClasses) {
  5704. if (map[c]) {
  5705. removedClasses[c] = true;
  5706. Ext.Array.remove(me.flushClassList, c);
  5707. }
  5708. }
  5709. delete map[c];
  5710. } else {
  5711. newList.push(c);
  5712. }
  5713. }
  5714. return me;
  5715. },
  5716. /**
  5717. * Adds styles to the element.
  5718. * @param {String/Object} prop The style property to be set, or an object of multiple styles.
  5719. * @param {String} [value] The value to apply to the given property.
  5720. * @return {Ext.util.ProtoElement} this
  5721. */
  5722. setStyle: function (prop, value) {
  5723. var me = this,
  5724. style = me.style || (me.style = {});
  5725. if (typeof prop == 'string') {
  5726. if (arguments.length === 1) {
  5727. me.setStyle(Ext.Element.parseStyles(prop));
  5728. } else {
  5729. style[prop] = value;
  5730. }
  5731. } else {
  5732. Ext.apply(style, prop);
  5733. }
  5734. return me;
  5735. },
  5736. /**
  5737. * Writes style and class properties to given object.
  5738. * Styles will be written to {@link #styleProp} and class names to {@link #clsProp}.
  5739. * @param {Object} to
  5740. * @return {Object} to
  5741. */
  5742. writeTo: function (to) {
  5743. var me = this,
  5744. classList = me.flushClassList || me.classList,
  5745. removedClasses = me.removedClasses,
  5746. style;
  5747. if (me.styleFn) {
  5748. style = Ext.apply({}, me.styleFn());
  5749. Ext.apply(style, me.style);
  5750. } else {
  5751. style = me.style;
  5752. }
  5753. to[me.clsProp] = classList.join(' ');
  5754. if (style) {
  5755. to[me.styleProp] = me.styleIsText ? Ext.DomHelper.generateStyles(style) : style;
  5756. }
  5757. if (removedClasses) {
  5758. removedClasses = Ext.Object.getKeys(removedClasses);
  5759. if (removedClasses.length) {
  5760. to[me.removedProp] = removedClasses.join(' ');
  5761. }
  5762. }
  5763. return to;
  5764. }
  5765. };
  5766. }());
  5767. /**
  5768. * @author Ed Spencer
  5769. *
  5770. * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
  5771. * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
  5772. * the Operations.
  5773. *
  5774. * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
  5775. * the config options passed to the JsonWriter's constructor.
  5776. *
  5777. * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
  5778. * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
  5779. * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
  5780. */
  5781. Ext.define('Ext.data.writer.Writer', {
  5782. alias: 'writer.base',
  5783. alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
  5784. /**
  5785. * @cfg {Boolean} writeAllFields
  5786. * True to write all fields from the record to the server. If set to false it will only send the fields that were
  5787. * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
  5788. */
  5789. writeAllFields: true,
  5790. /**
  5791. * @cfg {String} nameProperty
  5792. * This property is used to read the key for each value that will be sent to the server. For example:
  5793. *
  5794. * Ext.define('Person', {
  5795. * extend: 'Ext.data.Model',
  5796. * fields: [{
  5797. * name: 'first',
  5798. * mapping: 'firstName'
  5799. * }, {
  5800. * name: 'last',
  5801. * mapping: 'lastName'
  5802. * }, {
  5803. * name: 'age'
  5804. * }]
  5805. * });
  5806. * new Ext.data.writer.Writer({
  5807. * writeAllFields: true,
  5808. * nameProperty: 'mapping'
  5809. * });
  5810. *
  5811. * // This will be sent to the server
  5812. * {
  5813. * firstName: 'first name value',
  5814. * lastName: 'last name value',
  5815. * age: 1
  5816. * }
  5817. *
  5818. * If the value is not present, the field name will always be used.
  5819. */
  5820. nameProperty: 'name',
  5821. /**
  5822. * Creates new Writer.
  5823. * @param {Object} [config] Config object.
  5824. */
  5825. constructor: function(config) {
  5826. Ext.apply(this, config);
  5827. },
  5828. /**
  5829. * Prepares a Proxy's Ext.data.Request object
  5830. * @param {Ext.data.Request} request The request object
  5831. * @return {Ext.data.Request} The modified request object
  5832. */
  5833. write: function(request) {
  5834. var operation = request.operation,
  5835. records = operation.records || [],
  5836. len = records.length,
  5837. i = 0,
  5838. data = [];
  5839. for (; i < len; i++) {
  5840. data.push(this.getRecordData(records[i], operation));
  5841. }
  5842. return this.writeRecords(request, data);
  5843. },
  5844. /**
  5845. * Formats the data for each record before sending it to the server. This
  5846. * method should be overridden to format the data in a way that differs from the default.
  5847. * @param {Ext.data.Model} record The record that we are writing to the server.
  5848. * @param {Ext.data.Operation} [operation] An operation object.
  5849. * @return {Object} An object literal of name/value keys to be written to the server.
  5850. * By default this method returns the data property on the record.
  5851. */
  5852. getRecordData: function(record, operation) {
  5853. var isPhantom = record.phantom === true,
  5854. writeAll = this.writeAllFields || isPhantom,
  5855. nameProperty = this.nameProperty,
  5856. fields = record.fields,
  5857. fieldItems = fields.items,
  5858. data = {},
  5859. changes,
  5860. name,
  5861. field,
  5862. key,
  5863. f, fLen;
  5864. if (writeAll) {
  5865. fLen = fieldItems.length;
  5866. for (f = 0; f < fLen; f++) {
  5867. field = fieldItems[f];
  5868. if (field.persist) {
  5869. name = field[nameProperty] || field.name;
  5870. data[name] = record.get(field.name);
  5871. }
  5872. }
  5873. } else {
  5874. // Only write the changes
  5875. changes = record.getChanges();
  5876. for (key in changes) {
  5877. if (changes.hasOwnProperty(key)) {
  5878. field = fields.get(key);
  5879. name = field[nameProperty] || field.name;
  5880. data[name] = changes[key];
  5881. }
  5882. }
  5883. }
  5884. if(isPhantom) {
  5885. if(operation && operation.records.length > 1) {
  5886. // include clientId for phantom records, if multiple records are being written to the server in one operation.
  5887. // The server can then return the clientId with each record so the operation can match the server records with the client records
  5888. data[record.clientIdProperty] = record.internalId;
  5889. }
  5890. } else {
  5891. // always include the id for non phantoms
  5892. data[record.idProperty] = record.getId();
  5893. }
  5894. return data;
  5895. }
  5896. });
  5897. /**
  5898. * Handles mapping key events to handling functions for an element or a Component. One KeyMap can be used for multiple
  5899. * actions.
  5900. *
  5901. * A KeyMap must be configured with a {@link #target} as an event source which may be an Element or a Component.
  5902. *
  5903. * If the target is an element, then the `keydown` event will trigger the invocation of {@link #binding}s.
  5904. *
  5905. * It is possible to configure the KeyMap with a custom {@link #eventName} to listen for. This may be useful when the
  5906. * {@link #target} is a Component.
  5907. *
  5908. * The KeyMap's event handling requires that the first parameter passed is a key event. So if the Component's event
  5909. * signature is different, specify a {@link #processEvent} configuration which accepts the event's parameters and
  5910. * returns a key event.
  5911. *
  5912. * Functions specified in {@link #binding}s are called with this signature : `(String key, Ext.EventObject e)` (if the
  5913. * match is a multi-key combination the callback will still be called only once). A KeyMap can also handle a string
  5914. * representation of keys. By default KeyMap starts enabled.
  5915. *
  5916. * Usage:
  5917. *
  5918. * // map one key by key code
  5919. * var map = new Ext.util.KeyMap({
  5920. * target: "my-element",
  5921. * key: 13, // or Ext.EventObject.ENTER
  5922. * fn: myHandler,
  5923. * scope: myObject
  5924. * });
  5925. *
  5926. * // map multiple keys to one action by string
  5927. * var map = new Ext.util.KeyMap({
  5928. * target: "my-element",
  5929. * key: "a\r\n\t",
  5930. * fn: myHandler,
  5931. * scope: myObject
  5932. * });
  5933. *
  5934. * // map multiple keys to multiple actions by strings and array of codes
  5935. * var map = new Ext.util.KeyMap({
  5936. * target: "my-element",
  5937. * binding: [{
  5938. * key: [10,13],
  5939. * fn: function(){ alert("Return was pressed"); }
  5940. * }, {
  5941. * key: "abc",
  5942. * fn: function(){ alert('a, b or c was pressed'); }
  5943. * }, {
  5944. * key: "\t",
  5945. * ctrl:true,
  5946. * shift:true,
  5947. * fn: function(){ alert('Control + shift + tab was pressed.'); }
  5948. * }]
  5949. * });
  5950. *
  5951. * Since 4.1.0, KeyMaps can bind to Components and process key-based events fired by Components.
  5952. *
  5953. * To bind to a Component, use the single parameter form of constructor:
  5954. *
  5955. * var map = new Ext.util.KeyMap({
  5956. * target: myGridView,
  5957. * eventName: 'itemkeydown',
  5958. * processEvent: function(view, record, node, index, event) {
  5959. *
  5960. * // Load the event with the extra information needed by the mappings
  5961. * event.view = view;
  5962. * event.store = view.getStore();
  5963. * event.record = record;
  5964. * event.index = index;
  5965. * return event;
  5966. * },
  5967. * binding: {
  5968. * key: Ext.EventObject.DELETE,
  5969. * fn: function(keyCode, e) {
  5970. * e.store.remove(e.record);
  5971. *
  5972. * // Attempt to select the record that's now in its place
  5973. * e.view.getSelectionModel().select(e,index);
  5974. * e.view.el.focus();
  5975. * }
  5976. * }
  5977. * });
  5978. */
  5979. Ext.define('Ext.util.KeyMap', {
  5980. alternateClassName: 'Ext.KeyMap',
  5981. /**
  5982. * @cfg {Ext.Component/Ext.Element/HTMLElement/String} target
  5983. * The object on which to listen for the event specified by the {@link #eventName} config option.
  5984. */
  5985. /**
  5986. * @cfg {Object/Object[][]} binding
  5987. * Either a single object describing a handling function for s specified key (or set of keys), or
  5988. * an array of such objects.
  5989. * @cfg {String/String[]} binding.key A single keycode or an array of keycodes to handle
  5990. * @cfg {Boolean} binding.shift True to handle key only when shift is pressed, False to handle the
  5991. * key only when shift is not pressed (defaults to undefined)
  5992. * @cfg {Boolean} binding.ctrl True to handle key only when ctrl is pressed, False to handle the
  5993. * key only when ctrl is not pressed (defaults to undefined)
  5994. * @cfg {Boolean} binding.alt True to handle key only when alt is pressed, False to handle the key
  5995. * only when alt is not pressed (defaults to undefined)
  5996. * @cfg {Function} binding.handler The function to call when KeyMap finds the expected key combination
  5997. * @cfg {Function} binding.fn Alias of handler (for backwards-compatibility)
  5998. * @cfg {Object} binding.scope The scope of the callback function
  5999. * @cfg {String} binding.defaultEventAction A default action to apply to the event. Possible values
  6000. * are: stopEvent, stopPropagation, preventDefault. If no value is set no action is performed.
  6001. */
  6002. /**
  6003. * @cfg {Object} [processEventScope=this]
  6004. * The scope (`this` context) in which the {@link #processEvent} method is executed.
  6005. */
  6006. /**
  6007. * @cfg {String} eventName
  6008. * The event to listen for to pick up key events.
  6009. */
  6010. eventName: 'keydown',
  6011. constructor: function(config) {
  6012. var me = this;
  6013. // Handle legacy arg list in which the first argument is the target.
  6014. // TODO: Deprecate in V5
  6015. if ((arguments.length !== 1) || (typeof config === 'string') || config.dom || config.tagName || config === document || config.isComponent) {
  6016. me.legacyConstructor.apply(me, arguments);
  6017. return;
  6018. }
  6019. Ext.apply(me, config);
  6020. me.bindings = [];
  6021. if (!me.target.isComponent) {
  6022. me.target = Ext.get(me.target);
  6023. }
  6024. if (me.binding) {
  6025. me.addBinding(me.binding);
  6026. } else if (config.key) {
  6027. me.addBinding(config);
  6028. }
  6029. me.enable();
  6030. },
  6031. /**
  6032. * @private
  6033. * Old constructor signature
  6034. * @param {String/HTMLElement/Ext.Element/Ext.Component} el The element or its ID, or Component to bind to
  6035. * @param {Object} binding The binding (see {@link #addBinding})
  6036. * @param {String} [eventName="keydown"] The event to bind to
  6037. */
  6038. legacyConstructor: function(el, binding, eventName){
  6039. var me = this;
  6040. Ext.apply(me, {
  6041. target: Ext.get(el),
  6042. eventName: eventName || me.eventName,
  6043. bindings: []
  6044. });
  6045. if (binding) {
  6046. me.addBinding(binding);
  6047. }
  6048. me.enable();
  6049. },
  6050. /**
  6051. * Add a new binding to this KeyMap.
  6052. *
  6053. * Usage:
  6054. *
  6055. * // Create a KeyMap
  6056. * var map = new Ext.util.KeyMap(document, {
  6057. * key: Ext.EventObject.ENTER,
  6058. * fn: handleKey,
  6059. * scope: this
  6060. * });
  6061. *
  6062. * //Add a new binding to the existing KeyMap later
  6063. * map.addBinding({
  6064. * key: 'abc',
  6065. * shift: true,
  6066. * fn: handleKey,
  6067. * scope: this
  6068. * });
  6069. *
  6070. * @param {Object/Object[]} binding A single KeyMap config or an array of configs.
  6071. * The following config object properties are supported:
  6072. * @param {String/Array} binding.key A single keycode or an array of keycodes to handle.
  6073. * @param {Boolean} binding.shift True to handle key only when shift is pressed,
  6074. * False to handle the keyonly when shift is not pressed (defaults to undefined).
  6075. * @param {Boolean} binding.ctrl True to handle key only when ctrl is pressed,
  6076. * False to handle the key only when ctrl is not pressed (defaults to undefined).
  6077. * @param {Boolean} binding.alt True to handle key only when alt is pressed,
  6078. * False to handle the key only when alt is not pressed (defaults to undefined).
  6079. * @param {Function} binding.handler The function to call when KeyMap finds the
  6080. * expected key combination.
  6081. * @param {Function} binding.fn Alias of handler (for backwards-compatibility).
  6082. * @param {Object} binding.scope The scope of the callback function.
  6083. * @param {String} binding.defaultEventAction A default action to apply to the event.
  6084. * Possible values are: stopEvent, stopPropagation, preventDefault. If no value is
  6085. * set no action is performed..
  6086. */
  6087. addBinding : function(binding){
  6088. var keyCode = binding.key,
  6089. processed = false,
  6090. key,
  6091. keys,
  6092. keyString,
  6093. i,
  6094. len;
  6095. if (Ext.isArray(binding)) {
  6096. for (i = 0, len = binding.length; i < len; i++) {
  6097. this.addBinding(binding[i]);
  6098. }
  6099. return;
  6100. }
  6101. if (Ext.isString(keyCode)) {
  6102. keys = [];
  6103. keyString = keyCode.toUpperCase();
  6104. for (i = 0, len = keyString.length; i < len; ++i){
  6105. keys.push(keyString.charCodeAt(i));
  6106. }
  6107. keyCode = keys;
  6108. processed = true;
  6109. }
  6110. if (!Ext.isArray(keyCode)) {
  6111. keyCode = [keyCode];
  6112. }
  6113. if (!processed) {
  6114. for (i = 0, len = keyCode.length; i < len; ++i) {
  6115. key = keyCode[i];
  6116. if (Ext.isString(key)) {
  6117. keyCode[i] = key.toUpperCase().charCodeAt(0);
  6118. }
  6119. }
  6120. }
  6121. this.bindings.push(Ext.apply({
  6122. keyCode: keyCode
  6123. }, binding));
  6124. },
  6125. /**
  6126. * Process any keydown events on the element
  6127. * @private
  6128. * @param {Ext.EventObject} event
  6129. */
  6130. handleKeyDown: function(event) {
  6131. var me = this;
  6132. if (this.enabled) { //just in case
  6133. var bindings = this.bindings,
  6134. i = 0,
  6135. len = bindings.length;
  6136. // Process the event
  6137. event = me.processEvent.apply(me||me.processEventScope, arguments);
  6138. // If the processor does not return a keyEvent, we can't process it.
  6139. // Allow them to return false to cancel processing of the event
  6140. if (!event.getKey) {
  6141. return event;
  6142. }
  6143. for(; i < len; ++i){
  6144. this.processBinding(bindings[i], event);
  6145. }
  6146. }
  6147. },
  6148. /**
  6149. * @cfg {Function} processEvent
  6150. * An optional event processor function which accepts the argument list provided by the
  6151. * {@link #eventName configured event} of the {@link #target}, and returns a keyEvent for processing by the KeyMap.
  6152. *
  6153. * This may be useful when the {@link #target} is a Component with s complex event signature. Extra information from
  6154. * the event arguments may be injected into the event for use by the handler functions before returning it.
  6155. */
  6156. processEvent: function(event){
  6157. return event;
  6158. },
  6159. /**
  6160. * Process a particular binding and fire the handler if necessary.
  6161. * @private
  6162. * @param {Object} binding The binding information
  6163. * @param {Ext.EventObject} event
  6164. */
  6165. processBinding: function(binding, event){
  6166. if (this.checkModifiers(binding, event)) {
  6167. var key = event.getKey(),
  6168. handler = binding.fn || binding.handler,
  6169. scope = binding.scope || this,
  6170. keyCode = binding.keyCode,
  6171. defaultEventAction = binding.defaultEventAction,
  6172. i,
  6173. len,
  6174. keydownEvent = new Ext.EventObjectImpl(event);
  6175. for (i = 0, len = keyCode.length; i < len; ++i) {
  6176. if (key === keyCode[i]) {
  6177. if (handler.call(scope, key, event) !== true && defaultEventAction) {
  6178. keydownEvent[defaultEventAction]();
  6179. }
  6180. break;
  6181. }
  6182. }
  6183. }
  6184. },
  6185. /**
  6186. * Check if the modifiers on the event match those on the binding
  6187. * @private
  6188. * @param {Object} binding
  6189. * @param {Ext.EventObject} event
  6190. * @return {Boolean} True if the event matches the binding
  6191. */
  6192. checkModifiers: function(binding, e) {
  6193. var keys = ['shift', 'ctrl', 'alt'],
  6194. i = 0,
  6195. len = keys.length,
  6196. val, key;
  6197. for (; i < len; ++i){
  6198. key = keys[i];
  6199. val = binding[key];
  6200. if (!(val === undefined || (val === e[key + 'Key']))) {
  6201. return false;
  6202. }
  6203. }
  6204. return true;
  6205. },
  6206. /**
  6207. * Shorthand for adding a single key listener.
  6208. *
  6209. * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
  6210. * following options: `{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}`
  6211. * @param {Function} fn The function to call
  6212. * @param {Object} [scope] The scope (`this` reference) in which the function is executed.
  6213. * Defaults to the browser window.
  6214. */
  6215. on: function(key, fn, scope) {
  6216. var keyCode, shift, ctrl, alt;
  6217. if (Ext.isObject(key) && !Ext.isArray(key)) {
  6218. keyCode = key.key;
  6219. shift = key.shift;
  6220. ctrl = key.ctrl;
  6221. alt = key.alt;
  6222. } else {
  6223. keyCode = key;
  6224. }
  6225. this.addBinding({
  6226. key: keyCode,
  6227. shift: shift,
  6228. ctrl: ctrl,
  6229. alt: alt,
  6230. fn: fn,
  6231. scope: scope
  6232. });
  6233. },
  6234. /**
  6235. * Returns true if this KeyMap is enabled
  6236. * @return {Boolean}
  6237. */
  6238. isEnabled : function() {
  6239. return this.enabled;
  6240. },
  6241. /**
  6242. * Enables this KeyMap
  6243. */
  6244. enable: function() {
  6245. var me = this;
  6246. if (!me.enabled) {
  6247. me.target.on(me.eventName, me.handleKeyDown, me);
  6248. me.enabled = true;
  6249. }
  6250. },
  6251. /**
  6252. * Disable this KeyMap
  6253. */
  6254. disable: function() {
  6255. var me = this;
  6256. if (me.enabled) {
  6257. me.target.removeListener(me.eventName, me.handleKeyDown, me);
  6258. me.enabled = false;
  6259. }
  6260. },
  6261. /**
  6262. * Convenience function for setting disabled/enabled by boolean.
  6263. * @param {Boolean} disabled
  6264. */
  6265. setDisabled : function(disabled) {
  6266. if (disabled) {
  6267. this.disable();
  6268. } else {
  6269. this.enable();
  6270. }
  6271. },
  6272. /**
  6273. * Destroys the KeyMap instance and removes all handlers.
  6274. * @param {Boolean} removeTarget True to also remove the {@link #target}
  6275. */
  6276. destroy: function(removeTarget) {
  6277. var me = this;
  6278. me.bindings = [];
  6279. me.disable();
  6280. if (removeTarget === true) {
  6281. me.target.isComponent ? me.target.destroy() : me.target.remove();
  6282. }
  6283. delete me.target;
  6284. }
  6285. });
  6286. /**
  6287. * @class Ext.util.Memento
  6288. * This class manages a set of captured properties from an object. These captured properties
  6289. * can later be restored to an object.
  6290. */
  6291. Ext.define('Ext.util.Memento', function () {
  6292. function captureOne (src, target, prop, prefix) {
  6293. src[prefix ? prefix + prop : prop] = target[prop];
  6294. }
  6295. function removeOne (src, target, prop) {
  6296. delete src[prop];
  6297. }
  6298. function restoreOne (src, target, prop, prefix) {
  6299. var name = prefix ? prefix + prop : prop,
  6300. value = src[name];
  6301. if (value || src.hasOwnProperty(name)) {
  6302. restoreValue(target, prop, value);
  6303. }
  6304. }
  6305. function restoreValue (target, prop, value) {
  6306. if (Ext.isDefined(value)) {
  6307. target[prop] = value;
  6308. } else {
  6309. delete target[prop];
  6310. }
  6311. }
  6312. function doMany (doOne, src, target, props, prefix) {
  6313. if (src) {
  6314. if (Ext.isArray(props)) {
  6315. var p, pLen = props.length;
  6316. for (p = 0; p < pLen; p++) {
  6317. doOne(src, target, props[p], prefix);
  6318. }
  6319. } else {
  6320. doOne(src, target, props, prefix);
  6321. }
  6322. }
  6323. }
  6324. return {
  6325. /**
  6326. * @property data
  6327. * The collection of captured properties.
  6328. * @private
  6329. */
  6330. data: null,
  6331. /**
  6332. * @property target
  6333. * The default target object for capture/restore (passed to the constructor).
  6334. */
  6335. target: null,
  6336. /**
  6337. * Creates a new memento and optionally captures properties from the target object.
  6338. * @param {Object} target The target from which to capture properties. If specified in the
  6339. * constructor, this target becomes the default target for all other operations.
  6340. * @param {String/String[]} props The property or array of properties to capture.
  6341. */
  6342. constructor: function (target, props) {
  6343. if (target) {
  6344. this.target = target;
  6345. if (props) {
  6346. this.capture(props);
  6347. }
  6348. }
  6349. },
  6350. /**
  6351. * Captures the specified properties from the target object in this memento.
  6352. * @param {String/String[]} props The property or array of properties to capture.
  6353. * @param {Object} target The object from which to capture properties.
  6354. */
  6355. capture: function (props, target, prefix) {
  6356. var me = this;
  6357. doMany(captureOne, me.data || (me.data = {}), target || me.target, props, prefix);
  6358. },
  6359. /**
  6360. * Removes the specified properties from this memento. These properties will not be
  6361. * restored later without re-capturing their values.
  6362. * @param {String/String[]} props The property or array of properties to remove.
  6363. */
  6364. remove: function (props) {
  6365. doMany(removeOne, this.data, null, props);
  6366. },
  6367. /**
  6368. * Restores the specified properties from this memento to the target object.
  6369. * @param {String/String[]} props The property or array of properties to restore.
  6370. * @param {Boolean} clear True to remove the restored properties from this memento or
  6371. * false to keep them (default is true).
  6372. * @param {Object} target The object to which to restore properties.
  6373. */
  6374. restore: function (props, clear, target, prefix) {
  6375. doMany(restoreOne, this.data, target || this.target, props, prefix);
  6376. if (clear !== false) {
  6377. this.remove(props);
  6378. }
  6379. },
  6380. /**
  6381. * Restores all captured properties in this memento to the target object.
  6382. * @param {Boolean} clear True to remove the restored properties from this memento or
  6383. * false to keep them (default is true).
  6384. * @param {Object} target The object to which to restore properties.
  6385. */
  6386. restoreAll: function (clear, target) {
  6387. var me = this,
  6388. t = target || this.target,
  6389. data = me.data,
  6390. prop;
  6391. for (prop in data) {
  6392. if (data.hasOwnProperty(prop)) {
  6393. restoreValue(t, prop, data[prop]);
  6394. }
  6395. }
  6396. if (clear !== false) {
  6397. delete me.data;
  6398. }
  6399. }
  6400. };
  6401. }());
  6402. /**
  6403. * @class Ext.state.Provider
  6404. * <p>Abstract base class for state provider implementations. The provider is responsible
  6405. * for setting values and extracting values to/from the underlying storage source. The
  6406. * storage source can vary and the details should be implemented in a subclass. For example
  6407. * a provider could use a server side database or the browser localstorage where supported.</p>
  6408. *
  6409. * <p>This class provides methods for encoding and decoding <b>typed</b> variables including
  6410. * dates and defines the Provider interface. By default these methods put the value and the
  6411. * type information into a delimited string that can be stored. These should be overridden in
  6412. * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
  6413. */
  6414. Ext.define('Ext.state.Provider', {
  6415. mixins: {
  6416. observable: 'Ext.util.Observable'
  6417. },
  6418. /**
  6419. * @cfg {String} prefix A string to prefix to items stored in the underlying state store.
  6420. * Defaults to <tt>'ext-'</tt>
  6421. */
  6422. prefix: 'ext-',
  6423. constructor : function(config){
  6424. config = config || {};
  6425. var me = this;
  6426. Ext.apply(me, config);
  6427. /**
  6428. * @event statechange
  6429. * Fires when a state change occurs.
  6430. * @param {Ext.state.Provider} this This state provider
  6431. * @param {String} key The state key which was changed
  6432. * @param {String} value The encoded value for the state
  6433. */
  6434. me.addEvents("statechange");
  6435. me.state = {};
  6436. me.mixins.observable.constructor.call(me);
  6437. },
  6438. /**
  6439. * Returns the current value for a key
  6440. * @param {String} name The key name
  6441. * @param {Object} defaultValue A default value to return if the key's value is not found
  6442. * @return {Object} The state data
  6443. */
  6444. get : function(name, defaultValue){
  6445. return typeof this.state[name] == "undefined" ?
  6446. defaultValue : this.state[name];
  6447. },
  6448. /**
  6449. * Clears a value from the state
  6450. * @param {String} name The key name
  6451. */
  6452. clear : function(name){
  6453. var me = this;
  6454. delete me.state[name];
  6455. me.fireEvent("statechange", me, name, null);
  6456. },
  6457. /**
  6458. * Sets the value for a key
  6459. * @param {String} name The key name
  6460. * @param {Object} value The value to set
  6461. */
  6462. set : function(name, value){
  6463. var me = this;
  6464. me.state[name] = value;
  6465. me.fireEvent("statechange", me, name, value);
  6466. },
  6467. /**
  6468. * Decodes a string previously encoded with {@link #encodeValue}.
  6469. * @param {String} value The value to decode
  6470. * @return {Object} The decoded value
  6471. */
  6472. decodeValue : function(value){
  6473. // a -> Array
  6474. // n -> Number
  6475. // d -> Date
  6476. // b -> Boolean
  6477. // s -> String
  6478. // o -> Object
  6479. // -> Empty (null)
  6480. var me = this,
  6481. re = /^(a|n|d|b|s|o|e)\:(.*)$/,
  6482. matches = re.exec(unescape(value)),
  6483. all,
  6484. type,
  6485. value,
  6486. keyValue,
  6487. values,
  6488. vLen,
  6489. v;
  6490. if(!matches || !matches[1]){
  6491. return; // non state
  6492. }
  6493. type = matches[1];
  6494. value = matches[2];
  6495. switch (type) {
  6496. case 'e':
  6497. return null;
  6498. case 'n':
  6499. return parseFloat(value);
  6500. case 'd':
  6501. return new Date(Date.parse(value));
  6502. case 'b':
  6503. return (value == '1');
  6504. case 'a':
  6505. all = [];
  6506. if(value != ''){
  6507. values = value.split('^');
  6508. vLen = values.length;
  6509. for (v = 0; v < vLen; v++) {
  6510. value = values[v];
  6511. all.push(me.decodeValue(value));
  6512. }
  6513. }
  6514. return all;
  6515. case 'o':
  6516. all = {};
  6517. if(value != ''){
  6518. values = value.split('^');
  6519. vLen = values.length;
  6520. for (v = 0; v < vLen; v++) {
  6521. value = values[v];
  6522. keyValue = value.split('=');
  6523. all[keyValue[0]] = me.decodeValue(keyValue[1]);
  6524. }
  6525. }
  6526. return all;
  6527. default:
  6528. return value;
  6529. }
  6530. },
  6531. /**
  6532. * Encodes a value including type information. Decode with {@link #decodeValue}.
  6533. * @param {Object} value The value to encode
  6534. * @return {String} The encoded value
  6535. */
  6536. encodeValue : function(value){
  6537. var flat = '',
  6538. i = 0,
  6539. enc,
  6540. len,
  6541. key;
  6542. if (value == null) {
  6543. return 'e:1';
  6544. } else if(typeof value == 'number') {
  6545. enc = 'n:' + value;
  6546. } else if(typeof value == 'boolean') {
  6547. enc = 'b:' + (value ? '1' : '0');
  6548. } else if(Ext.isDate(value)) {
  6549. enc = 'd:' + value.toGMTString();
  6550. } else if(Ext.isArray(value)) {
  6551. for (len = value.length; i < len; i++) {
  6552. flat += this.encodeValue(value[i]);
  6553. if (i != len - 1) {
  6554. flat += '^';
  6555. }
  6556. }
  6557. enc = 'a:' + flat;
  6558. } else if (typeof value == 'object') {
  6559. for (key in value) {
  6560. if (typeof value[key] != 'function' && value[key] !== undefined) {
  6561. flat += key + '=' + this.encodeValue(value[key]) + '^';
  6562. }
  6563. }
  6564. enc = 'o:' + flat.substring(0, flat.length-1);
  6565. } else {
  6566. enc = 's:' + value;
  6567. }
  6568. return escape(enc);
  6569. }
  6570. });
  6571. /**
  6572. * @author Ed Spencer
  6573. *
  6574. * Simple wrapper class that represents a set of records returned by a Proxy.
  6575. */
  6576. Ext.define('Ext.data.ResultSet', {
  6577. /**
  6578. * @cfg {Boolean} loaded
  6579. * True if the records have already been loaded. This is only meaningful when dealing with
  6580. * SQL-backed proxies.
  6581. */
  6582. loaded: true,
  6583. /**
  6584. * @cfg {Number} count
  6585. * The number of records in this ResultSet. Note that total may differ from this number.
  6586. */
  6587. count: 0,
  6588. /**
  6589. * @cfg {Number} total
  6590. * The total number of records reported by the data source. This ResultSet may form a subset of
  6591. * those records (see {@link #count}).
  6592. */
  6593. total: 0,
  6594. /**
  6595. * @cfg {Boolean} success
  6596. * True if the ResultSet loaded successfully, false if any errors were encountered.
  6597. */
  6598. success: false,
  6599. /**
  6600. * @cfg {Ext.data.Model[]} records (required)
  6601. * The array of record instances.
  6602. */
  6603. /**
  6604. * Creates the resultSet
  6605. * @param {Object} [config] Config object.
  6606. */
  6607. constructor: function(config) {
  6608. Ext.apply(this, config);
  6609. /**
  6610. * @property {Number} totalRecords
  6611. * Copy of this.total.
  6612. * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
  6613. */
  6614. this.totalRecords = this.total;
  6615. if (config.count === undefined) {
  6616. this.count = this.records.length;
  6617. }
  6618. }
  6619. });
  6620. /**
  6621. * @class Ext.fx.CubicBezier
  6622. * @ignore
  6623. */
  6624. Ext.define('Ext.fx.CubicBezier', {
  6625. /* Begin Definitions */
  6626. singleton: true,
  6627. /* End Definitions */
  6628. cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
  6629. var cx = 3 * p1x,
  6630. bx = 3 * (p2x - p1x) - cx,
  6631. ax = 1 - cx - bx,
  6632. cy = 3 * p1y,
  6633. by = 3 * (p2y - p1y) - cy,
  6634. ay = 1 - cy - by;
  6635. function sampleCurveX(t) {
  6636. return ((ax * t + bx) * t + cx) * t;
  6637. }
  6638. function solve(x, epsilon) {
  6639. var t = solveCurveX(x, epsilon);
  6640. return ((ay * t + by) * t + cy) * t;
  6641. }
  6642. function solveCurveX(x, epsilon) {
  6643. var t0, t1, t2, x2, d2, i;
  6644. for (t2 = x, i = 0; i < 8; i++) {
  6645. x2 = sampleCurveX(t2) - x;
  6646. if (Math.abs(x2) < epsilon) {
  6647. return t2;
  6648. }
  6649. d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
  6650. if (Math.abs(d2) < 1e-6) {
  6651. break;
  6652. }
  6653. t2 = t2 - x2 / d2;
  6654. }
  6655. t0 = 0;
  6656. t1 = 1;
  6657. t2 = x;
  6658. if (t2 < t0) {
  6659. return t0;
  6660. }
  6661. if (t2 > t1) {
  6662. return t1;
  6663. }
  6664. while (t0 < t1) {
  6665. x2 = sampleCurveX(t2);
  6666. if (Math.abs(x2 - x) < epsilon) {
  6667. return t2;
  6668. }
  6669. if (x > x2) {
  6670. t0 = t2;
  6671. } else {
  6672. t1 = t2;
  6673. }
  6674. t2 = (t1 - t0) / 2 + t0;
  6675. }
  6676. return t2;
  6677. }
  6678. return solve(t, 1 / (200 * duration));
  6679. },
  6680. cubicBezier: function(x1, y1, x2, y2) {
  6681. var fn = function(pos) {
  6682. return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
  6683. };
  6684. fn.toCSS3 = function() {
  6685. return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
  6686. };
  6687. fn.reverse = function() {
  6688. return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
  6689. };
  6690. return fn;
  6691. }
  6692. });
  6693. /**
  6694. * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
  6695. * is primarily used internally for the Panel's drag drop implementation, and
  6696. * should never need to be created directly.
  6697. * @private
  6698. */
  6699. Ext.define('Ext.panel.Proxy', {
  6700. alternateClassName: 'Ext.dd.PanelProxy',
  6701. /**
  6702. * @cfg {Boolean} [moveOnDrag=true]
  6703. * True to move the panel to the dragged position when dropped
  6704. */
  6705. moveOnDrag: true,
  6706. /**
  6707. * Creates new panel proxy.
  6708. * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
  6709. * @param {Object} [config] Config object
  6710. */
  6711. constructor: function(panel, config){
  6712. var me = this;
  6713. /**
  6714. * @property panel
  6715. * @type Ext.panel.Panel
  6716. */
  6717. me.panel = panel;
  6718. me.id = me.panel.id +'-ddproxy';
  6719. Ext.apply(me, config);
  6720. },
  6721. /**
  6722. * @cfg {Boolean} insertProxy
  6723. * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
  6724. * Most Panels are not absolute positioned and therefore we need to reserve this space.
  6725. */
  6726. insertProxy: true,
  6727. // private overrides
  6728. setStatus: Ext.emptyFn,
  6729. reset: Ext.emptyFn,
  6730. update: Ext.emptyFn,
  6731. stop: Ext.emptyFn,
  6732. sync: Ext.emptyFn,
  6733. /**
  6734. * Gets the proxy's element
  6735. * @return {Ext.Element} The proxy's element
  6736. */
  6737. getEl: function(){
  6738. return this.ghost.el;
  6739. },
  6740. /**
  6741. * Gets the proxy's ghost Panel
  6742. * @return {Ext.panel.Panel} The proxy's ghost Panel
  6743. */
  6744. getGhost: function(){
  6745. return this.ghost;
  6746. },
  6747. /**
  6748. * Gets the proxy element. This is the element that represents where the
  6749. * Panel was before we started the drag operation.
  6750. * @return {Ext.Element} The proxy's element
  6751. */
  6752. getProxy: function(){
  6753. return this.proxy;
  6754. },
  6755. /**
  6756. * Hides the proxy
  6757. */
  6758. hide : function(){
  6759. var me = this;
  6760. if (me.ghost) {
  6761. if (me.proxy) {
  6762. me.proxy.remove();
  6763. delete me.proxy;
  6764. }
  6765. // Unghost the Panel, do not move the Panel to where the ghost was
  6766. me.panel.unghost(null, me.moveOnDrag);
  6767. delete me.ghost;
  6768. }
  6769. },
  6770. /**
  6771. * Shows the proxy
  6772. */
  6773. show: function(){
  6774. var me = this,
  6775. panelSize;
  6776. if (!me.ghost) {
  6777. panelSize = me.panel.getSize();
  6778. me.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
  6779. me.ghost = me.panel.ghost();
  6780. if (me.insertProxy) {
  6781. // bc Panels aren't absolute positioned we need to take up the space
  6782. // of where the panel previously was
  6783. me.proxy = me.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
  6784. me.proxy.setSize(panelSize);
  6785. }
  6786. }
  6787. },
  6788. // private
  6789. repair: function(xy, callback, scope) {
  6790. this.hide();
  6791. Ext.callback(callback, scope || this);
  6792. },
  6793. /**
  6794. * Moves the proxy to a different position in the DOM. This is typically
  6795. * called while dragging the Panel to keep the proxy sync'd to the Panel's
  6796. * location.
  6797. * @param {HTMLElement} parentNode The proxy's parent DOM node
  6798. * @param {HTMLElement} [before] The sibling node before which the
  6799. * proxy should be inserted. Defaults to the parent's last child if not
  6800. * specified.
  6801. */
  6802. moveProxy : function(parentNode, before){
  6803. if (this.proxy) {
  6804. parentNode.insertBefore(this.proxy.dom, before);
  6805. }
  6806. }
  6807. });
  6808. /**
  6809. * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
  6810. *
  6811. * An instance of this class may be created by passing to the constructor either a single argument, or multiple
  6812. * arguments:
  6813. *
  6814. * # Single argument: String/Array
  6815. *
  6816. * The single argument may be either a String or an Array:
  6817. *
  6818. * - String:
  6819. *
  6820. * var t = new Ext.Template("<div>Hello {0}.</div>");
  6821. * t.{@link #append}('some-element', ['foo']);
  6822. *
  6823. * - Array:
  6824. *
  6825. * An Array will be combined with `join('')`.
  6826. *
  6827. * var t = new Ext.Template([
  6828. * '<div name="{id}">',
  6829. * '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
  6830. * '</div>',
  6831. * ]);
  6832. * t.{@link #compile}();
  6833. * t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
  6834. *
  6835. * # Multiple arguments: String, Object, Array, ...
  6836. *
  6837. * Multiple arguments will be combined with `join('')`.
  6838. *
  6839. * var t = new Ext.Template(
  6840. * '<div name="{id}">',
  6841. * '<span class="{cls}">{name} {value}</span>',
  6842. * '</div>',
  6843. * // a configuration object:
  6844. * {
  6845. * compiled: true, // {@link #compile} immediately
  6846. * }
  6847. * );
  6848. *
  6849. * # Notes
  6850. *
  6851. * - For a list of available format functions, see {@link Ext.util.Format}.
  6852. * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
  6853. */
  6854. Ext.define('Ext.Template', {
  6855. /* Begin Definitions */
  6856. requires: ['Ext.dom.Helper', 'Ext.util.Format'],
  6857. inheritableStatics: {
  6858. /**
  6859. * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
  6860. * @param {String/HTMLElement} el A DOM element or its id
  6861. * @param {Object} config (optional) Config object
  6862. * @return {Ext.Template} The created template
  6863. * @static
  6864. * @inheritable
  6865. */
  6866. from: function(el, config) {
  6867. el = Ext.getDom(el);
  6868. return new this(el.value || el.innerHTML, config || '');
  6869. }
  6870. },
  6871. /* End Definitions */
  6872. /**
  6873. * Creates new template.
  6874. *
  6875. * @param {String...} html List of strings to be concatenated into template.
  6876. * Alternatively an array of strings can be given, but then no config object may be passed.
  6877. * @param {Object} config (optional) Config object
  6878. */
  6879. constructor: function(html) {
  6880. var me = this,
  6881. args = arguments,
  6882. buffer = [],
  6883. i = 0,
  6884. length = args.length,
  6885. value;
  6886. me.initialConfig = {};
  6887. if (length > 1) {
  6888. for (; i < length; i++) {
  6889. value = args[i];
  6890. if (typeof value == 'object') {
  6891. Ext.apply(me.initialConfig, value);
  6892. Ext.apply(me, value);
  6893. } else {
  6894. buffer.push(value);
  6895. }
  6896. }
  6897. html = buffer.join('');
  6898. } else {
  6899. if (Ext.isArray(html)) {
  6900. buffer.push(html.join(''));
  6901. } else {
  6902. buffer.push(html);
  6903. }
  6904. }
  6905. // @private
  6906. me.html = buffer.join('');
  6907. if (me.compiled) {
  6908. me.compile();
  6909. }
  6910. },
  6911. /**
  6912. * @property {Boolean} isTemplate
  6913. * `true` in this class to identify an objact as an instantiated Template, or subclass thereof.
  6914. */
  6915. isTemplate: true,
  6916. /**
  6917. * @cfg {Boolean} compiled
  6918. * True to immediately compile the template. Defaults to false.
  6919. */
  6920. /**
  6921. * @cfg {Boolean} disableFormats
  6922. * True to disable format functions in the template. If the template doesn't contain
  6923. * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
  6924. */
  6925. disableFormats: false,
  6926. re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
  6927. /**
  6928. * Returns an HTML fragment of this template with the specified values applied.
  6929. *
  6930. * @param {Object/Array} values The template values. Can be an array if your params are numeric:
  6931. *
  6932. * var tpl = new Ext.Template('Name: {0}, Age: {1}');
  6933. * tpl.apply(['John', 25]);
  6934. *
  6935. * or an object:
  6936. *
  6937. * var tpl = new Ext.Template('Name: {name}, Age: {age}');
  6938. * tpl.apply({name: 'John', age: 25});
  6939. *
  6940. * @return {String} The HTML fragment
  6941. */
  6942. apply: function(values) {
  6943. var me = this,
  6944. useFormat = me.disableFormats !== true,
  6945. fm = Ext.util.Format,
  6946. tpl = me,
  6947. ret;
  6948. if (me.compiled) {
  6949. return me.compiled(values).join('');
  6950. }
  6951. function fn(m, name, format, args) {
  6952. if (format && useFormat) {
  6953. if (args) {
  6954. args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
  6955. } else {
  6956. args = [values[name]];
  6957. }
  6958. if (format.substr(0, 5) == "this.") {
  6959. return tpl[format.substr(5)].apply(tpl, args);
  6960. }
  6961. else {
  6962. return fm[format].apply(fm, args);
  6963. }
  6964. }
  6965. else {
  6966. return values[name] !== undefined ? values[name] : "";
  6967. }
  6968. }
  6969. ret = me.html.replace(me.re, fn);
  6970. return ret;
  6971. },
  6972. /**
  6973. * Appends the result of this template to the provided output array.
  6974. * @param {Object/Array} values The template values. See {@link #apply}.
  6975. * @param {Array} out The array to which output is pushed.
  6976. * @return {Array} The given out array.
  6977. */
  6978. applyOut: function(values, out) {
  6979. var me = this;
  6980. if (me.compiled) {
  6981. out.push.apply(out, me.compiled(values));
  6982. } else {
  6983. out.push(me.apply(values));
  6984. }
  6985. return out;
  6986. },
  6987. /**
  6988. * @method applyTemplate
  6989. * @member Ext.Template
  6990. * Alias for {@link #apply}.
  6991. * @inheritdoc Ext.Template#apply
  6992. */
  6993. applyTemplate: function () {
  6994. return this.apply.apply(this, arguments);
  6995. },
  6996. /**
  6997. * Sets the HTML used as the template and optionally compiles it.
  6998. * @param {String} html
  6999. * @param {Boolean} compile (optional) True to compile the template.
  7000. * @return {Ext.Template} this
  7001. */
  7002. set: function(html, compile) {
  7003. var me = this;
  7004. me.html = html;
  7005. me.compiled = null;
  7006. return compile ? me.compile() : me;
  7007. },
  7008. compileARe: /\\/g,
  7009. compileBRe: /(\r\n|\n)/g,
  7010. compileCRe: /'/g,
  7011. /**
  7012. * Compiles the template into an internal function, eliminating the RegEx overhead.
  7013. * @return {Ext.Template} this
  7014. */
  7015. compile: function() {
  7016. var me = this,
  7017. fm = Ext.util.Format,
  7018. useFormat = me.disableFormats !== true,
  7019. body, bodyReturn;
  7020. function fn(m, name, format, args) {
  7021. if (format && useFormat) {
  7022. args = args ? ',' + args: "";
  7023. if (format.substr(0, 5) != "this.") {
  7024. format = "fm." + format + '(';
  7025. }
  7026. else {
  7027. format = 'this.' + format.substr(5) + '(';
  7028. }
  7029. }
  7030. else {
  7031. args = '';
  7032. format = "(values['" + name + "'] == undefined ? '' : ";
  7033. }
  7034. return "'," + format + "values['" + name + "']" + args + ") ,'";
  7035. }
  7036. bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
  7037. body = "this.compiled = function(values){ return ['" + bodyReturn + "'];};";
  7038. eval(body);
  7039. return me;
  7040. },
  7041. /**
  7042. * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
  7043. *
  7044. * @param {String/HTMLElement/Ext.Element} el The context element
  7045. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7046. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7047. * @return {HTMLElement/Ext.Element} The new node or Element
  7048. */
  7049. insertFirst: function(el, values, returnElement) {
  7050. return this.doInsert('afterBegin', el, values, returnElement);
  7051. },
  7052. /**
  7053. * Applies the supplied values to the template and inserts the new node(s) before el.
  7054. *
  7055. * @param {String/HTMLElement/Ext.Element} el The context element
  7056. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7057. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7058. * @return {HTMLElement/Ext.Element} The new node or Element
  7059. */
  7060. insertBefore: function(el, values, returnElement) {
  7061. return this.doInsert('beforeBegin', el, values, returnElement);
  7062. },
  7063. /**
  7064. * Applies the supplied values to the template and inserts the new node(s) after el.
  7065. *
  7066. * @param {String/HTMLElement/Ext.Element} el The context element
  7067. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7068. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7069. * @return {HTMLElement/Ext.Element} The new node or Element
  7070. */
  7071. insertAfter: function(el, values, returnElement) {
  7072. return this.doInsert('afterEnd', el, values, returnElement);
  7073. },
  7074. /**
  7075. * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
  7076. *
  7077. * For example usage see {@link Ext.Template Ext.Template class docs}.
  7078. *
  7079. * @param {String/HTMLElement/Ext.Element} el The context element
  7080. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7081. * @param {Boolean} returnElement (optional) true to return an Ext.Element.
  7082. * @return {HTMLElement/Ext.Element} The new node or Element
  7083. */
  7084. append: function(el, values, returnElement) {
  7085. return this.doInsert('beforeEnd', el, values, returnElement);
  7086. },
  7087. doInsert: function(where, el, values, returnEl) {
  7088. el = Ext.getDom(el);
  7089. var newNode = Ext.DomHelper.insertHtml(where, el, this.apply(values));
  7090. return returnEl ? Ext.get(newNode, true) : newNode;
  7091. },
  7092. /**
  7093. * Applies the supplied values to the template and overwrites the content of el with the new node(s).
  7094. *
  7095. * @param {String/HTMLElement/Ext.Element} el The context element
  7096. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  7097. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  7098. * @return {HTMLElement/Ext.Element} The new node or Element
  7099. */
  7100. overwrite: function(el, values, returnElement) {
  7101. el = Ext.getDom(el);
  7102. el.innerHTML = this.apply(values);
  7103. return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
  7104. }
  7105. });
  7106. /**
  7107. * @class Ext.fx.Queue
  7108. * Animation Queue mixin to handle chaining and queueing by target.
  7109. * @private
  7110. */
  7111. Ext.define('Ext.fx.Queue', {
  7112. requires: ['Ext.util.HashMap'],
  7113. constructor: function() {
  7114. this.targets = new Ext.util.HashMap();
  7115. this.fxQueue = {};
  7116. },
  7117. // @private
  7118. getFxDefaults: function(targetId) {
  7119. var target = this.targets.get(targetId);
  7120. if (target) {
  7121. return target.fxDefaults;
  7122. }
  7123. return {};
  7124. },
  7125. // @private
  7126. setFxDefaults: function(targetId, obj) {
  7127. var target = this.targets.get(targetId);
  7128. if (target) {
  7129. target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
  7130. }
  7131. },
  7132. // @private
  7133. stopAnimation: function(targetId) {
  7134. var me = this,
  7135. queue = me.getFxQueue(targetId),
  7136. ln = queue.length;
  7137. while (ln) {
  7138. queue[ln - 1].end();
  7139. ln--;
  7140. }
  7141. },
  7142. /**
  7143. * @private
  7144. * Returns current animation object if the element has any effects actively running or queued, else returns false.
  7145. */
  7146. getActiveAnimation: function(targetId) {
  7147. var queue = this.getFxQueue(targetId);
  7148. return (queue && !!queue.length) ? queue[0] : false;
  7149. },
  7150. // @private
  7151. hasFxBlock: function(targetId) {
  7152. var queue = this.getFxQueue(targetId);
  7153. return queue && queue[0] && queue[0].block;
  7154. },
  7155. // @private get fx queue for passed target, create if needed.
  7156. getFxQueue: function(targetId) {
  7157. if (!targetId) {
  7158. return false;
  7159. }
  7160. var me = this,
  7161. queue = me.fxQueue[targetId],
  7162. target = me.targets.get(targetId);
  7163. if (!target) {
  7164. return false;
  7165. }
  7166. if (!queue) {
  7167. me.fxQueue[targetId] = [];
  7168. // GarbageCollector will need to clean up Elements since they aren't currently observable
  7169. if (target.type != 'element') {
  7170. target.target.on('destroy', function() {
  7171. me.fxQueue[targetId] = [];
  7172. });
  7173. }
  7174. }
  7175. return me.fxQueue[targetId];
  7176. },
  7177. // @private
  7178. queueFx: function(anim) {
  7179. var me = this,
  7180. target = anim.target,
  7181. queue, ln;
  7182. if (!target) {
  7183. return;
  7184. }
  7185. queue = me.getFxQueue(target.getId());
  7186. ln = queue.length;
  7187. if (ln) {
  7188. if (anim.concurrent) {
  7189. anim.paused = false;
  7190. }
  7191. else {
  7192. queue[ln - 1].on('afteranimate', function() {
  7193. anim.paused = false;
  7194. });
  7195. }
  7196. }
  7197. else {
  7198. anim.paused = false;
  7199. }
  7200. anim.on('afteranimate', function() {
  7201. Ext.Array.remove(queue, anim);
  7202. if (anim.remove) {
  7203. if (target.type == 'element') {
  7204. var el = Ext.get(target.id);
  7205. if (el) {
  7206. el.remove();
  7207. }
  7208. }
  7209. }
  7210. }, this);
  7211. queue.push(anim);
  7212. }
  7213. });
  7214. /**
  7215. * This class parses the XTemplate syntax and calls abstract methods to process the parts.
  7216. * @private
  7217. */
  7218. Ext.define('Ext.XTemplateParser', {
  7219. constructor: function (config) {
  7220. Ext.apply(this, config);
  7221. },
  7222. /**
  7223. * @property {Number} level The 'for' loop context level. This is adjusted up by one
  7224. * prior to calling {@link #doFor} and down by one after calling the corresponding
  7225. * {@link #doEnd} that closes the loop. This will be 1 on the first {@link #doFor}
  7226. * call.
  7227. */
  7228. /**
  7229. * This method is called to process a piece of raw text from the tpl.
  7230. * @param {String} text
  7231. * @method doText
  7232. */
  7233. // doText: function (text)
  7234. /**
  7235. * This method is called to process expressions (like `{[expr]}`).
  7236. * @param {String} expr The body of the expression (inside "{[" and "]}").
  7237. * @method doExpr
  7238. */
  7239. // doExpr: function (expr)
  7240. /**
  7241. * This method is called to process simple tags (like `{tag}`).
  7242. * @method doTag
  7243. */
  7244. // doTag: function (tag)
  7245. /**
  7246. * This method is called to process `<tpl else>`.
  7247. * @method doElse
  7248. */
  7249. // doElse: function ()
  7250. /**
  7251. * This method is called to process `{% text %}`.
  7252. * @param {String} text
  7253. * @method doEval
  7254. */
  7255. // doEval: function (text)
  7256. /**
  7257. * This method is called to process `<tpl if="action">`. If there are other attributes,
  7258. * these are passed in the actions object.
  7259. * @param {String} action
  7260. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  7261. * @method doIf
  7262. */
  7263. // doIf: function (action, actions)
  7264. /**
  7265. * This method is called to process `<tpl elseif="action">`. If there are other attributes,
  7266. * these are passed in the actions object.
  7267. * @param {String} action
  7268. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  7269. * @method doElseIf
  7270. */
  7271. // doElseIf: function (action, actions)
  7272. /**
  7273. * This method is called to process `<tpl switch="action">`. If there are other attributes,
  7274. * these are passed in the actions object.
  7275. * @param {String} action
  7276. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  7277. * @method doSwitch
  7278. */
  7279. // doSwitch: function (action, actions)
  7280. /**
  7281. * This method is called to process `<tpl case="action">`. If there are other attributes,
  7282. * these are passed in the actions object.
  7283. * @param {String} action
  7284. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  7285. * @method doCase
  7286. */
  7287. // doCase: function (action, actions)
  7288. /**
  7289. * This method is called to process `<tpl default>`.
  7290. * @method doDefault
  7291. */
  7292. // doDefault: function ()
  7293. /**
  7294. * This method is called to process `</tpl>`. It is given the action type that started
  7295. * the tpl and the set of additional actions.
  7296. * @param {String} type The type of action that is being ended.
  7297. * @param {Object} actions The other actions keyed by the attribute name (such as 'exec').
  7298. * @method doEnd
  7299. */
  7300. // doEnd: function (type, actions)
  7301. /**
  7302. * This method is called to process `<tpl for="action">`. If there are other attributes,
  7303. * these are passed in the actions object.
  7304. * @param {String} action
  7305. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  7306. * @method doFor
  7307. */
  7308. // doFor: function (action, actions)
  7309. /**
  7310. * This method is called to process `<tpl exec="action">`. If there are other attributes,
  7311. * these are passed in the actions object.
  7312. * @param {String} action
  7313. * @param {Object} actions Other actions keyed by the attribute name.
  7314. * @method doExec
  7315. */
  7316. // doExec: function (action, actions)
  7317. /**
  7318. * This method is called to process an empty `<tpl>`. This is unlikely to need to be
  7319. * implemented, so a default (do nothing) version is provided.
  7320. * @method
  7321. */
  7322. doTpl: Ext.emptyFn,
  7323. parse: function (str) {
  7324. var me = this,
  7325. len = str.length,
  7326. aliases = { elseif: 'elif' },
  7327. topRe = me.topRe,
  7328. actionsRe = me.actionsRe,
  7329. index, stack, s, m, t, prev, frame, subMatch, begin, end, actions;
  7330. me.level = 0;
  7331. me.stack = stack = [];
  7332. for (index = 0; index < len; index = end) {
  7333. topRe.lastIndex = index;
  7334. m = topRe.exec(str);
  7335. if (!m) {
  7336. me.doText(str.substring(index, len));
  7337. break;
  7338. }
  7339. begin = m.index;
  7340. end = topRe.lastIndex;
  7341. if (index < begin) {
  7342. me.doText(str.substring(index, begin));
  7343. }
  7344. if (m[1]) {
  7345. end = str.indexOf('%}', begin+2);
  7346. me.doEval(str.substring(begin+2, end));
  7347. end += 2;
  7348. } else if (m[2]) {
  7349. end = str.indexOf(']}', begin+2);
  7350. me.doExpr(str.substring(begin+2, end));
  7351. end += 2;
  7352. } else if (m[3]) { // if ('{' token)
  7353. me.doTag(m[3]);
  7354. } else if (m[4]) { // content of a <tpl xxxxxx> tag
  7355. actions = null;
  7356. while ((subMatch = actionsRe.exec(m[4])) !== null) {
  7357. s = subMatch[2] || subMatch[3];
  7358. if (s) {
  7359. s = Ext.String.htmlDecode(s); // decode attr value
  7360. t = subMatch[1];
  7361. t = aliases[t] || t;
  7362. actions = actions || {};
  7363. prev = actions[t];
  7364. if (typeof prev == 'string') {
  7365. actions[t] = [prev, s];
  7366. } else if (prev) {
  7367. actions[t].push(s);
  7368. } else {
  7369. actions[t] = s;
  7370. }
  7371. }
  7372. }
  7373. if (!actions) {
  7374. if (me.elseRe.test(m[4])) {
  7375. me.doElse();
  7376. } else if (me.defaultRe.test(m[4])) {
  7377. me.doDefault();
  7378. } else {
  7379. me.doTpl();
  7380. stack.push({ type: 'tpl' });
  7381. }
  7382. }
  7383. else if (actions['if']) {
  7384. me.doIf(actions['if'], actions)
  7385. stack.push({ type: 'if' });
  7386. }
  7387. else if (actions['switch']) {
  7388. me.doSwitch(actions['switch'], actions)
  7389. stack.push({ type: 'switch' });
  7390. }
  7391. else if (actions['case']) {
  7392. me.doCase(actions['case'], actions);
  7393. }
  7394. else if (actions['elif']) {
  7395. me.doElseIf(actions['elif'], actions);
  7396. }
  7397. else if (actions['for']) {
  7398. ++me.level;
  7399. me.doFor(actions['for'], actions);
  7400. stack.push({ type: 'for', actions: actions });
  7401. }
  7402. else if (actions.exec) {
  7403. me.doExec(actions.exec, actions);
  7404. stack.push({ type: 'exec', actions: actions });
  7405. }
  7406. /*
  7407. else {
  7408. // todo - error
  7409. }
  7410. /**/
  7411. } else {
  7412. frame = stack.pop();
  7413. me.doEnd(frame.type, frame.actions);
  7414. if (frame.type == 'for') {
  7415. --me.level;
  7416. }
  7417. }
  7418. }
  7419. },
  7420. // Internal regexes
  7421. topRe: /(?:(\{\%)|(\{\[)|\{([^{}]*)\})|(?:<tpl([^>]*)\>)|(?:<\/tpl>)/g,
  7422. actionsRe: /\s*(elif|elseif|if|for|exec|switch|case|eval)\s*\=\s*(?:(?:["]([^"]*)["])|(?:[']([^']*)[']))\s*/g,
  7423. defaultRe: /^\s*default\s*$/,
  7424. elseRe: /^\s*else\s*$/
  7425. });
  7426. /**
  7427. * A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
  7428. * and Component activation behavior, including masking below the active (topmost) Component.
  7429. *
  7430. * {@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as
  7431. * {@link Ext.window.Window Window}s) which are {@link Ext.Component#method-show show}n are managed by a
  7432. * {@link Ext.WindowManager global instance}.
  7433. *
  7434. * {@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating}
  7435. * *Containers* (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window},
  7436. * or a {@link Ext.menu.Menu Menu}), are managed by a ZIndexManager owned by that floating Container. Therefore
  7437. * ComboBox dropdowns within Windows will have managed z-indices guaranteed to be correct, relative to the Window.
  7438. */
  7439. Ext.define('Ext.ZIndexManager', {
  7440. alternateClassName: 'Ext.WindowGroup',
  7441. statics: {
  7442. zBase : 9000
  7443. },
  7444. constructor: function(container) {
  7445. var me = this;
  7446. me.list = {};
  7447. me.zIndexStack = [];
  7448. me.front = null;
  7449. if (container) {
  7450. // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
  7451. if (container.isContainer) {
  7452. container.on('resize', me._onContainerResize, me);
  7453. me.zseed = Ext.Number.from(me.rendered ? container.getEl().getStyle('zIndex') : undefined, me.getNextZSeed());
  7454. // The containing element we will be dealing with (eg masking) is the content target
  7455. me.targetEl = container.getTargetEl();
  7456. me.container = container;
  7457. }
  7458. // This is the ZIndexManager for a DOM element
  7459. else {
  7460. Ext.EventManager.onWindowResize(me._onContainerResize, me);
  7461. me.zseed = me.getNextZSeed();
  7462. me.targetEl = Ext.get(container);
  7463. }
  7464. }
  7465. // No container passed means we are the global WindowManager. Our target is the doc body.
  7466. // DOM must be ready to collect that ref.
  7467. else {
  7468. Ext.EventManager.onWindowResize(me._onContainerResize, me);
  7469. me.zseed = me.getNextZSeed();
  7470. Ext.onDocumentReady(function() {
  7471. me.targetEl = Ext.getBody();
  7472. });
  7473. }
  7474. },
  7475. getNextZSeed: function() {
  7476. return (Ext.ZIndexManager.zBase += 10000);
  7477. },
  7478. setBase: function(baseZIndex) {
  7479. this.zseed = baseZIndex;
  7480. var result = this.assignZIndices();
  7481. this._activateLast();
  7482. return result;
  7483. },
  7484. // private
  7485. assignZIndices: function() {
  7486. var a = this.zIndexStack,
  7487. len = a.length,
  7488. i = 0,
  7489. zIndex = this.zseed,
  7490. comp;
  7491. for (; i < len; i++) {
  7492. comp = a[i];
  7493. if (comp && !comp.hidden) {
  7494. // Setting the zIndex of a Component returns the topmost zIndex consumed by
  7495. // that Component.
  7496. // If it's just a plain floating Component such as a BoundList, then the
  7497. // return value is the passed value plus 10, ready for the next item.
  7498. // If a floating *Container* has its zIndex set, it re-orders its managed
  7499. // floating children, starting from that new base, and returns a value 10000 above
  7500. // the highest zIndex which it allocates.
  7501. zIndex = comp.setZIndex(zIndex);
  7502. }
  7503. }
  7504. // Activate new topmost
  7505. this._activateLast();
  7506. return zIndex;
  7507. },
  7508. // private
  7509. _setActiveChild: function(comp, oldFront) {
  7510. var front = this.front;
  7511. if (comp !== front) {
  7512. if (front && !front.destroying) {
  7513. front.setActive(false, comp);
  7514. }
  7515. this.front = comp;
  7516. if (comp && comp != oldFront) {
  7517. comp.setActive(true);
  7518. if (comp.modal) {
  7519. this._showModalMask(comp);
  7520. }
  7521. }
  7522. }
  7523. },
  7524. onComponentHide: function(comp){
  7525. comp.setActive(false);
  7526. this._activateLast();
  7527. },
  7528. // private
  7529. _activateLast: function() {
  7530. var me = this,
  7531. stack = me.zIndexStack,
  7532. i = stack.length - 1,
  7533. oldFront = me.front,
  7534. comp;
  7535. // There may be no visible floater to activate
  7536. me.front = undefined;
  7537. // Go down through the z-index stack.
  7538. // Activate the next visible one down.
  7539. // If that was modal, then we're done
  7540. for (; i >= 0 && stack[i].hidden; --i);
  7541. if ((comp = stack[i])) {
  7542. me._setActiveChild(comp, oldFront);
  7543. if (comp.modal) {
  7544. return;
  7545. }
  7546. }
  7547. // If the new top one was not modal, keep going down to find the next visible
  7548. // modal one to shift the modal mask down under
  7549. for (; i >= 0; --i) {
  7550. comp = stack[i];
  7551. // If we find a visible modal further down the zIndex stack, move the mask to just under it.
  7552. if (comp.isVisible() && comp.modal) {
  7553. me._showModalMask(comp);
  7554. return;
  7555. }
  7556. }
  7557. // No visible modal Component was found in the run down the stack.
  7558. // So hide the modal mask
  7559. me._hideModalMask();
  7560. },
  7561. _showModalMask: function(comp) {
  7562. var me = this,
  7563. zIndex = comp.el.getStyle('zIndex') - 4,
  7564. maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : comp.container,
  7565. viewSize = maskTarget.getBox();
  7566. if (maskTarget.dom === document.body) {
  7567. viewSize.height = Math.max(document.body.scrollHeight, Ext.dom.Element.getDocumentHeight());
  7568. viewSize.width = Math.max(document.body.scrollWidth, viewSize.width);
  7569. }
  7570. if (!me.mask) {
  7571. me.mask = Ext.getBody().createChild({
  7572. cls: Ext.baseCSSPrefix + 'mask'
  7573. });
  7574. me.mask.setVisibilityMode(Ext.Element.DISPLAY);
  7575. me.mask.on('click', me._onMaskClick, me);
  7576. }
  7577. me.mask.maskTarget = maskTarget;
  7578. maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
  7579. me.mask.setBox(viewSize);
  7580. me.mask.setStyle('zIndex', zIndex);
  7581. me.mask.show();
  7582. },
  7583. _hideModalMask: function() {
  7584. var mask = this.mask;
  7585. if (mask && mask.isVisible()) {
  7586. mask.maskTarget.removeCls(Ext.baseCSSPrefix + 'body-masked');
  7587. mask.maskTarget = undefined;
  7588. mask.hide();
  7589. }
  7590. },
  7591. _onMaskClick: function() {
  7592. if (this.front) {
  7593. this.front.focus();
  7594. }
  7595. },
  7596. _onContainerResize: function() {
  7597. var mask = this.mask,
  7598. maskTarget,
  7599. viewSize;
  7600. if (mask && mask.isVisible()) {
  7601. // At the new container size, the mask might be *causing* the scrollbar, so to find the valid
  7602. // client size to mask, we must temporarily unmask the parent node.
  7603. mask.hide();
  7604. maskTarget = mask.maskTarget;
  7605. if (maskTarget.dom === document.body) {
  7606. viewSize = {
  7607. height: Math.max(document.body.scrollHeight, Ext.dom.Element.getDocumentHeight()),
  7608. width: Math.max(document.body.scrollWidth, document.documentElement.clientWidth)
  7609. }
  7610. } else {
  7611. viewSize = maskTarget.getViewSize(true);
  7612. }
  7613. mask.setSize(viewSize);
  7614. mask.show();
  7615. }
  7616. },
  7617. /**
  7618. * Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
  7619. * need to be called under normal circumstances. Floating Components (such as Windows,
  7620. * BoundLists and Menus) are automatically registered with a
  7621. * {@link Ext.Component#zIndexManager zIndexManager} at render time.
  7622. *
  7623. * Where this may be useful is moving Windows between two ZIndexManagers. For example,
  7624. * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
  7625. * ZIndexManager in the desktop sample app:
  7626. *
  7627. * MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
  7628. *
  7629. * @param {Ext.Component} comp The Component to register.
  7630. */
  7631. register : function(comp) {
  7632. var me = this;
  7633. if (comp.zIndexManager) {
  7634. comp.zIndexManager.unregister(comp);
  7635. }
  7636. comp.zIndexManager = me;
  7637. me.list[comp.id] = comp;
  7638. me.zIndexStack.push(comp);
  7639. comp.on('hide', me.onComponentHide, me);
  7640. },
  7641. /**
  7642. * Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
  7643. * need to be called. Components are automatically unregistered upon destruction.
  7644. * See {@link #register}.
  7645. * @param {Ext.Component} comp The Component to unregister.
  7646. */
  7647. unregister : function(comp) {
  7648. var me = this,
  7649. list = me.list;
  7650. delete comp.zIndexManager;
  7651. if (list && list[comp.id]) {
  7652. delete list[comp.id];
  7653. comp.un('hide', me.onComponentHide);
  7654. Ext.Array.remove(me.zIndexStack, comp);
  7655. // Destruction requires that the topmost visible floater be activated. Same as hiding.
  7656. me._activateLast();
  7657. }
  7658. },
  7659. /**
  7660. * Gets a registered Component by id.
  7661. * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
  7662. * @return {Ext.Component}
  7663. */
  7664. get : function(id) {
  7665. return id.isComponent ? id : this.list[id];
  7666. },
  7667. /**
  7668. * Brings the specified Component to the front of any other active Components in this ZIndexManager.
  7669. * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
  7670. * @return {Boolean} True if the dialog was brought to the front, else false
  7671. * if it was already in front
  7672. */
  7673. bringToFront : function(comp) {
  7674. var me = this,
  7675. result = false;
  7676. comp = me.get(comp);
  7677. if (comp !== me.front) {
  7678. Ext.Array.remove(me.zIndexStack, comp);
  7679. me.zIndexStack.push(comp);
  7680. me.assignZIndices();
  7681. result = true;
  7682. this.front = comp;
  7683. }
  7684. if (result && comp.modal) {
  7685. me._showModalMask(comp);
  7686. }
  7687. return result;
  7688. },
  7689. /**
  7690. * Sends the specified Component to the back of other active Components in this ZIndexManager.
  7691. * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
  7692. * @return {Ext.Component} The Component
  7693. */
  7694. sendToBack : function(comp) {
  7695. var me = this;
  7696. comp = me.get(comp);
  7697. Ext.Array.remove(me.zIndexStack, comp);
  7698. me.zIndexStack.unshift(comp);
  7699. me.assignZIndices();
  7700. this._activateLast();
  7701. return comp;
  7702. },
  7703. /**
  7704. * Hides all Components managed by this ZIndexManager.
  7705. */
  7706. hideAll : function() {
  7707. var list = this.list,
  7708. item,
  7709. id;
  7710. for (id in list) {
  7711. if (list.hasOwnProperty(id)) {
  7712. item = list[id];
  7713. if (item.isComponent && item.isVisible()) {
  7714. item.hide();
  7715. }
  7716. }
  7717. }
  7718. },
  7719. /**
  7720. * @private
  7721. * Temporarily hides all currently visible managed Components. This is for when
  7722. * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
  7723. * they should all be hidden just for the duration of the drag.
  7724. */
  7725. hide: function() {
  7726. var i = 0,
  7727. stack = this.zIndexStack,
  7728. len = stack.length,
  7729. comp;
  7730. this.tempHidden = [];
  7731. for (; i < len; i++) {
  7732. comp = stack[i];
  7733. if (comp.isVisible()) {
  7734. this.tempHidden.push(comp);
  7735. comp.el.hide();
  7736. }
  7737. }
  7738. },
  7739. /**
  7740. * @private
  7741. * Restores temporarily hidden managed Components to visibility.
  7742. */
  7743. show: function() {
  7744. var i = 0,
  7745. tempHidden = this.tempHidden,
  7746. len = tempHidden ? tempHidden.length : 0,
  7747. comp;
  7748. for (; i < len; i++) {
  7749. comp = tempHidden[i];
  7750. comp.el.show();
  7751. comp.setPosition(comp.x, comp.y);
  7752. }
  7753. delete this.tempHidden;
  7754. },
  7755. /**
  7756. * Gets the currently-active Component in this ZIndexManager.
  7757. * @return {Ext.Component} The active Component
  7758. */
  7759. getActive : function() {
  7760. return this.front;
  7761. },
  7762. /**
  7763. * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
  7764. * The function should accept a single {@link Ext.Component} reference as its only argument and should
  7765. * return true if the Component matches the search criteria, otherwise it should return false.
  7766. * @param {Function} fn The search function
  7767. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  7768. * Defaults to the Component being tested. That gets passed to the function if not specified.
  7769. * @return {Array} An array of zero or more matching windows
  7770. */
  7771. getBy : function(fn, scope) {
  7772. var r = [],
  7773. i = 0,
  7774. stack = this.zIndexStack,
  7775. len = stack.length,
  7776. comp;
  7777. for (; i < len; i++) {
  7778. comp = stack[i];
  7779. if (fn.call(scope||comp, comp) !== false) {
  7780. r.push(comp);
  7781. }
  7782. }
  7783. return r;
  7784. },
  7785. /**
  7786. * Executes the specified function once for every Component in this ZIndexManager, passing each
  7787. * Component as the only parameter. Returning false from the function will stop the iteration.
  7788. * @param {Function} fn The function to execute for each item
  7789. * @param {Object} [scope] The scope (this reference) in which the function
  7790. * is executed. Defaults to the current Component in the iteration.
  7791. */
  7792. each : function(fn, scope) {
  7793. var list = this.list,
  7794. id,
  7795. comp;
  7796. for (id in list) {
  7797. if (list.hasOwnProperty(id)) {
  7798. comp = list[id];
  7799. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  7800. return;
  7801. }
  7802. }
  7803. }
  7804. },
  7805. /**
  7806. * Executes the specified function once for every Component in this ZIndexManager, passing each
  7807. * Component as the only parameter. Returning false from the function will stop the iteration.
  7808. * The components are passed to the function starting at the bottom and proceeding to the top.
  7809. * @param {Function} fn The function to execute for each item
  7810. * @param {Object} scope (optional) The scope (this reference) in which the function
  7811. * is executed. Defaults to the current Component in the iteration.
  7812. */
  7813. eachBottomUp: function (fn, scope) {
  7814. var stack = this.zIndexStack,
  7815. i = 0,
  7816. len = stack.length,
  7817. comp;
  7818. for (; i < len; i++) {
  7819. comp = stack[i];
  7820. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  7821. return;
  7822. }
  7823. }
  7824. },
  7825. /**
  7826. * Executes the specified function once for every Component in this ZIndexManager, passing each
  7827. * Component as the only parameter. Returning false from the function will stop the iteration.
  7828. * The components are passed to the function starting at the top and proceeding to the bottom.
  7829. * @param {Function} fn The function to execute for each item
  7830. * @param {Object} [scope] The scope (this reference) in which the function
  7831. * is executed. Defaults to the current Component in the iteration.
  7832. */
  7833. eachTopDown: function (fn, scope) {
  7834. var stack = this.zIndexStack,
  7835. i = stack.length,
  7836. comp;
  7837. for (; i-- > 0; ) {
  7838. comp = stack[i];
  7839. if (comp.isComponent && fn.call(scope || comp, comp) === false) {
  7840. return;
  7841. }
  7842. }
  7843. },
  7844. destroy: function() {
  7845. var me = this,
  7846. list = me.list,
  7847. comp;
  7848. for (var id in list) {
  7849. if (list.hasOwnProperty(id)) {
  7850. comp = list[id];
  7851. if (comp.isComponent) {
  7852. comp.destroy();
  7853. }
  7854. }
  7855. }
  7856. delete me.zIndexStack;
  7857. delete me.list;
  7858. delete me.container;
  7859. delete me.targetEl;
  7860. }
  7861. }, function() {
  7862. /**
  7863. * @class Ext.WindowManager
  7864. * @extends Ext.ZIndexManager
  7865. *
  7866. * The default global floating Component group that is available automatically.
  7867. *
  7868. * This manages instances of floating Components which were rendered programatically without
  7869. * being added to a {@link Ext.container.Container Container}, and for floating Components
  7870. * which were added into non-floating Containers.
  7871. *
  7872. * *Floating* Containers create their own instance of ZIndexManager, and floating Components
  7873. * added at any depth below there are managed by that ZIndexManager.
  7874. *
  7875. * @singleton
  7876. */
  7877. Ext.WindowManager = Ext.WindowMgr = new this();
  7878. });
  7879. /**
  7880. * @class Ext.fx.target.Target
  7881. This class specifies a generic target for an animation. It provides a wrapper around a
  7882. series of different types of objects to allow for a generic animation API.
  7883. A target can be a single object or a Composite object containing other objects that are
  7884. to be animated. This class and it's subclasses are generally not created directly, the
  7885. underlying animation will create the appropriate Ext.fx.target.Target object by passing
  7886. the instance to be animated.
  7887. The following types of objects can be animated:
  7888. - {@link Ext.fx.target.Component Components}
  7889. - {@link Ext.fx.target.Element Elements}
  7890. - {@link Ext.fx.target.Sprite Sprites}
  7891. * @markdown
  7892. * @abstract
  7893. */
  7894. Ext.define('Ext.fx.target.Target', {
  7895. isAnimTarget: true,
  7896. /**
  7897. * Creates new Target.
  7898. * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
  7899. */
  7900. constructor: function(target) {
  7901. this.target = target;
  7902. this.id = this.getId();
  7903. },
  7904. getId: function() {
  7905. return this.target.id;
  7906. }
  7907. });
  7908. /**
  7909. * Represents an RGB color and provides helper functions get
  7910. * color components in HSL color space.
  7911. */
  7912. Ext.define('Ext.draw.Color', {
  7913. /* Begin Definitions */
  7914. /* End Definitions */
  7915. colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
  7916. rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
  7917. 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*/,
  7918. /**
  7919. * @cfg {Number} lightnessFactor
  7920. *
  7921. * The default factor to compute the lighter or darker color. Defaults to 0.2.
  7922. */
  7923. lightnessFactor: 0.2,
  7924. /**
  7925. * Creates new Color.
  7926. * @param {Number} red Red component (0..255)
  7927. * @param {Number} green Green component (0..255)
  7928. * @param {Number} blue Blue component (0..255)
  7929. */
  7930. constructor : function(red, green, blue) {
  7931. var me = this,
  7932. clamp = Ext.Number.constrain;
  7933. me.r = clamp(red, 0, 255);
  7934. me.g = clamp(green, 0, 255);
  7935. me.b = clamp(blue, 0, 255);
  7936. },
  7937. /**
  7938. * Get the red component of the color, in the range 0..255.
  7939. * @return {Number}
  7940. */
  7941. getRed: function() {
  7942. return this.r;
  7943. },
  7944. /**
  7945. * Get the green component of the color, in the range 0..255.
  7946. * @return {Number}
  7947. */
  7948. getGreen: function() {
  7949. return this.g;
  7950. },
  7951. /**
  7952. * Get the blue component of the color, in the range 0..255.
  7953. * @return {Number}
  7954. */
  7955. getBlue: function() {
  7956. return this.b;
  7957. },
  7958. /**
  7959. * Get the RGB values.
  7960. * @return {Number[]}
  7961. */
  7962. getRGB: function() {
  7963. var me = this;
  7964. return [me.r, me.g, me.b];
  7965. },
  7966. /**
  7967. * Get the equivalent HSL components of the color.
  7968. * @return {Number[]}
  7969. */
  7970. getHSL: function() {
  7971. var me = this,
  7972. r = me.r / 255,
  7973. g = me.g / 255,
  7974. b = me.b / 255,
  7975. max = Math.max(r, g, b),
  7976. min = Math.min(r, g, b),
  7977. delta = max - min,
  7978. h,
  7979. s = 0,
  7980. l = 0.5 * (max + min);
  7981. // min==max means achromatic (hue is undefined)
  7982. if (min != max) {
  7983. s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
  7984. if (r == max) {
  7985. h = 60 * (g - b) / delta;
  7986. } else if (g == max) {
  7987. h = 120 + 60 * (b - r) / delta;
  7988. } else {
  7989. h = 240 + 60 * (r - g) / delta;
  7990. }
  7991. if (h < 0) {
  7992. h += 360;
  7993. }
  7994. if (h >= 360) {
  7995. h -= 360;
  7996. }
  7997. }
  7998. return [h, s, l];
  7999. },
  8000. /**
  8001. * Return a new color that is lighter than this color.
  8002. * @param {Number} factor Lighter factor (0..1), default to 0.2
  8003. * @return Ext.draw.Color
  8004. */
  8005. getLighter: function(factor) {
  8006. var hsl = this.getHSL();
  8007. factor = factor || this.lightnessFactor;
  8008. hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
  8009. return this.fromHSL(hsl[0], hsl[1], hsl[2]);
  8010. },
  8011. /**
  8012. * Return a new color that is darker than this color.
  8013. * @param {Number} factor Darker factor (0..1), default to 0.2
  8014. * @return Ext.draw.Color
  8015. */
  8016. getDarker: function(factor) {
  8017. factor = factor || this.lightnessFactor;
  8018. return this.getLighter(-factor);
  8019. },
  8020. /**
  8021. * Return the color in the hex format, i.e. '#rrggbb'.
  8022. * @return {String}
  8023. */
  8024. toString: function() {
  8025. var me = this,
  8026. round = Math.round,
  8027. r = round(me.r).toString(16),
  8028. g = round(me.g).toString(16),
  8029. b = round(me.b).toString(16);
  8030. r = (r.length == 1) ? '0' + r : r;
  8031. g = (g.length == 1) ? '0' + g : g;
  8032. b = (b.length == 1) ? '0' + b : b;
  8033. return ['#', r, g, b].join('');
  8034. },
  8035. /**
  8036. * Convert a color to hexadecimal format.
  8037. *
  8038. * **Note:** This method is both static and instance.
  8039. *
  8040. * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
  8041. * Can also be an Array, in this case the function handles the first member.
  8042. * @returns {String} The color in hexadecimal format.
  8043. * @static
  8044. */
  8045. toHex: function(color) {
  8046. if (Ext.isArray(color)) {
  8047. color = color[0];
  8048. }
  8049. if (!Ext.isString(color)) {
  8050. return '';
  8051. }
  8052. if (color.substr(0, 1) === '#') {
  8053. return color;
  8054. }
  8055. var digits = this.colorToHexRe.exec(color);
  8056. if (Ext.isArray(digits)) {
  8057. var red = parseInt(digits[2], 10),
  8058. green = parseInt(digits[3], 10),
  8059. blue = parseInt(digits[4], 10),
  8060. rgb = blue | (green << 8) | (red << 16);
  8061. return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
  8062. }
  8063. else {
  8064. return color;
  8065. }
  8066. },
  8067. /**
  8068. * Parse the string and create a new color.
  8069. *
  8070. * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
  8071. *
  8072. * If the string is not recognized, an undefined will be returned instead.
  8073. *
  8074. * **Note:** This method is both static and instance.
  8075. *
  8076. * @param {String} str Color in string.
  8077. * @returns Ext.draw.Color
  8078. * @static
  8079. */
  8080. fromString: function(str) {
  8081. var values, r, g, b,
  8082. parse = parseInt;
  8083. if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
  8084. values = str.match(this.hexRe);
  8085. if (values) {
  8086. r = parse(values[1], 16) >> 0;
  8087. g = parse(values[2], 16) >> 0;
  8088. b = parse(values[3], 16) >> 0;
  8089. if (str.length == 4) {
  8090. r += (r * 16);
  8091. g += (g * 16);
  8092. b += (b * 16);
  8093. }
  8094. }
  8095. }
  8096. else {
  8097. values = str.match(this.rgbRe);
  8098. if (values) {
  8099. r = values[1];
  8100. g = values[2];
  8101. b = values[3];
  8102. }
  8103. }
  8104. return (typeof r == 'undefined') ? undefined : new Ext.draw.Color(r, g, b);
  8105. },
  8106. /**
  8107. * Returns the gray value (0 to 255) of the color.
  8108. *
  8109. * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
  8110. *
  8111. * @returns {Number}
  8112. */
  8113. getGrayscale: function() {
  8114. // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
  8115. return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
  8116. },
  8117. /**
  8118. * Create a new color based on the specified HSL values.
  8119. *
  8120. * **Note:** This method is both static and instance.
  8121. *
  8122. * @param {Number} h Hue component (0..359)
  8123. * @param {Number} s Saturation component (0..1)
  8124. * @param {Number} l Lightness component (0..1)
  8125. * @returns Ext.draw.Color
  8126. * @static
  8127. */
  8128. fromHSL: function(h, s, l) {
  8129. var C, X, m, i, rgb = [],
  8130. abs = Math.abs,
  8131. floor = Math.floor;
  8132. if (s == 0 || h == null) {
  8133. // achromatic
  8134. rgb = [l, l, l];
  8135. }
  8136. else {
  8137. // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
  8138. // C is the chroma
  8139. // X is the second largest component
  8140. // m is the lightness adjustment
  8141. h /= 60;
  8142. C = s * (1 - abs(2 * l - 1));
  8143. X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
  8144. m = l - C / 2;
  8145. switch (floor(h)) {
  8146. case 0:
  8147. rgb = [C, X, 0];
  8148. break;
  8149. case 1:
  8150. rgb = [X, C, 0];
  8151. break;
  8152. case 2:
  8153. rgb = [0, C, X];
  8154. break;
  8155. case 3:
  8156. rgb = [0, X, C];
  8157. break;
  8158. case 4:
  8159. rgb = [X, 0, C];
  8160. break;
  8161. case 5:
  8162. rgb = [C, 0, X];
  8163. break;
  8164. }
  8165. rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
  8166. }
  8167. return new Ext.draw.Color(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
  8168. }
  8169. }, function() {
  8170. var prototype = this.prototype;
  8171. //These functions are both static and instance. TODO: find a more elegant way of copying them
  8172. this.addStatics({
  8173. fromHSL: function() {
  8174. return prototype.fromHSL.apply(prototype, arguments);
  8175. },
  8176. fromString: function() {
  8177. return prototype.fromString.apply(prototype, arguments);
  8178. },
  8179. toHex: function() {
  8180. return prototype.toHex.apply(prototype, arguments);
  8181. }
  8182. });
  8183. });
  8184. /**
  8185. * @private
  8186. * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
  8187. * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
  8188. * for its container.
  8189. */
  8190. Ext.define('Ext.layout.container.boxOverflow.None', {
  8191. alternateClassName: 'Ext.layout.boxOverflow.None',
  8192. constructor: function(layout, config) {
  8193. this.layout = layout;
  8194. Ext.apply(this, config);
  8195. },
  8196. handleOverflow: Ext.emptyFn,
  8197. clearOverflow: Ext.emptyFn,
  8198. beginLayout: Ext.emptyFn,
  8199. beginLayoutCycle: Ext.emptyFn,
  8200. completeLayout: function (ownerContext) {
  8201. var me = this,
  8202. plan = ownerContext.state.boxPlan,
  8203. overflow;
  8204. if (plan && plan.tooNarrow) {
  8205. overflow = me.handleOverflow(ownerContext);
  8206. if (overflow) {
  8207. if (overflow.reservedSpace) {
  8208. me.layout.publishInnerCtSize(ownerContext, overflow.reservedSpace);
  8209. }
  8210. // TODO: If we need to use the code below then we will need to pass along
  8211. // the new targetSize as state and use it calculate somehow...
  8212. //
  8213. //if (overflow.recalculate) {
  8214. // ownerContext.invalidate({
  8215. // state: {
  8216. // overflow: overflow
  8217. // }
  8218. // });
  8219. //}
  8220. }
  8221. } else {
  8222. me.clearOverflow();
  8223. }
  8224. },
  8225. onRemove: Ext.emptyFn,
  8226. /**
  8227. * @private
  8228. * Normalizes an item reference, string id or numerical index into a reference to the item
  8229. * @param {Ext.Component/String/Number} item The item reference, id or index
  8230. * @return {Ext.Component} The item
  8231. */
  8232. getItem: function(item) {
  8233. return this.layout.owner.getComponent(item);
  8234. },
  8235. getOwnerType: function(owner){
  8236. var type = '';
  8237. if (owner.is('toolbar')) {
  8238. type = 'toolbar';
  8239. } else if (owner.is('tabbar')) {
  8240. type = 'tabbar';
  8241. } else {
  8242. type = owner.getXType();
  8243. }
  8244. return type;
  8245. },
  8246. getPrefixConfig: Ext.emptyFn,
  8247. getSuffixConfig: Ext.emptyFn,
  8248. getOverflowCls: function() {
  8249. return '';
  8250. }
  8251. });
  8252. /**
  8253. * @class Ext.util.Offset
  8254. * @ignore
  8255. */
  8256. Ext.define('Ext.util.Offset', {
  8257. /* Begin Definitions */
  8258. statics: {
  8259. fromObject: function(obj) {
  8260. return new this(obj.x, obj.y);
  8261. }
  8262. },
  8263. /* End Definitions */
  8264. constructor: function(x, y) {
  8265. this.x = (x != null && !isNaN(x)) ? x : 0;
  8266. this.y = (y != null && !isNaN(y)) ? y : 0;
  8267. return this;
  8268. },
  8269. copy: function() {
  8270. return new Ext.util.Offset(this.x, this.y);
  8271. },
  8272. copyFrom: function(p) {
  8273. this.x = p.x;
  8274. this.y = p.y;
  8275. },
  8276. toString: function() {
  8277. return "Offset[" + this.x + "," + this.y + "]";
  8278. },
  8279. equals: function(offset) {
  8280. if(!(offset instanceof this.statics())) {
  8281. Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
  8282. }
  8283. return (this.x == offset.x && this.y == offset.y);
  8284. },
  8285. round: function(to) {
  8286. if (!isNaN(to)) {
  8287. var factor = Math.pow(10, to);
  8288. this.x = Math.round(this.x * factor) / factor;
  8289. this.y = Math.round(this.y * factor) / factor;
  8290. } else {
  8291. this.x = Math.round(this.x);
  8292. this.y = Math.round(this.y);
  8293. }
  8294. },
  8295. isZero: function() {
  8296. return this.x == 0 && this.y == 0;
  8297. }
  8298. });
  8299. /**
  8300. * A wrapper class which can be applied to any element. Fires a "click" event while the
  8301. * mouse is pressed. The interval between firings may be specified in the config but
  8302. * defaults to 20 milliseconds.
  8303. *
  8304. * Optionally, a CSS class may be applied to the element during the time it is pressed.
  8305. */
  8306. Ext.define('Ext.util.ClickRepeater', {
  8307. extend: 'Ext.util.Observable',
  8308. /**
  8309. * Creates new ClickRepeater.
  8310. * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
  8311. * @param {Object} [config] Config object.
  8312. */
  8313. constructor : function(el, config){
  8314. var me = this;
  8315. me.el = Ext.get(el);
  8316. me.el.unselectable();
  8317. Ext.apply(me, config);
  8318. me.callParent();
  8319. me.addEvents(
  8320. /**
  8321. * @event mousedown
  8322. * Fires when the mouse button is depressed.
  8323. * @param {Ext.util.ClickRepeater} this
  8324. * @param {Ext.EventObject} e
  8325. */
  8326. "mousedown",
  8327. /**
  8328. * @event click
  8329. * Fires on a specified interval during the time the element is pressed.
  8330. * @param {Ext.util.ClickRepeater} this
  8331. * @param {Ext.EventObject} e
  8332. */
  8333. "click",
  8334. /**
  8335. * @event mouseup
  8336. * Fires when the mouse key is released.
  8337. * @param {Ext.util.ClickRepeater} this
  8338. * @param {Ext.EventObject} e
  8339. */
  8340. "mouseup"
  8341. );
  8342. if(!me.disabled){
  8343. me.disabled = true;
  8344. me.enable();
  8345. }
  8346. // allow inline handler
  8347. if(me.handler){
  8348. me.on("click", me.handler, me.scope || me);
  8349. }
  8350. },
  8351. /**
  8352. * @cfg {String/HTMLElement/Ext.Element} el
  8353. * The element to act as a button.
  8354. */
  8355. /**
  8356. * @cfg {String} pressedCls
  8357. * A CSS class name to be applied to the element while pressed.
  8358. */
  8359. /**
  8360. * @cfg {Boolean} accelerate
  8361. * True if autorepeating should start slowly and accelerate.
  8362. * "interval" and "delay" are ignored.
  8363. */
  8364. /**
  8365. * @cfg {Number} interval
  8366. * The interval between firings of the "click" event (in milliseconds).
  8367. */
  8368. interval : 20,
  8369. /**
  8370. * @cfg {Number} delay
  8371. * The initial delay before the repeating event begins firing.
  8372. * Similar to an autorepeat key delay.
  8373. */
  8374. delay: 250,
  8375. /**
  8376. * @cfg {Boolean} preventDefault
  8377. * True to prevent the default click event
  8378. */
  8379. preventDefault : true,
  8380. /**
  8381. * @cfg {Boolean} stopDefault
  8382. * True to stop the default click event
  8383. */
  8384. stopDefault : false,
  8385. timer : 0,
  8386. /**
  8387. * Enables the repeater and allows events to fire.
  8388. */
  8389. enable: function(){
  8390. if(this.disabled){
  8391. this.el.on('mousedown', this.handleMouseDown, this);
  8392. if (Ext.isIE){
  8393. this.el.on('dblclick', this.handleDblClick, this);
  8394. }
  8395. if(this.preventDefault || this.stopDefault){
  8396. this.el.on('click', this.eventOptions, this);
  8397. }
  8398. }
  8399. this.disabled = false;
  8400. },
  8401. /**
  8402. * Disables the repeater and stops events from firing.
  8403. */
  8404. disable: function(/* private */ force){
  8405. if(force || !this.disabled){
  8406. clearTimeout(this.timer);
  8407. if(this.pressedCls){
  8408. this.el.removeCls(this.pressedCls);
  8409. }
  8410. Ext.getDoc().un('mouseup', this.handleMouseUp, this);
  8411. this.el.removeAllListeners();
  8412. }
  8413. this.disabled = true;
  8414. },
  8415. /**
  8416. * Convenience function for setting disabled/enabled by boolean.
  8417. * @param {Boolean} disabled
  8418. */
  8419. setDisabled: function(disabled){
  8420. this[disabled ? 'disable' : 'enable']();
  8421. },
  8422. eventOptions: function(e){
  8423. if(this.preventDefault){
  8424. e.preventDefault();
  8425. }
  8426. if(this.stopDefault){
  8427. e.stopEvent();
  8428. }
  8429. },
  8430. // private
  8431. destroy : function() {
  8432. this.disable(true);
  8433. Ext.destroy(this.el);
  8434. this.clearListeners();
  8435. },
  8436. handleDblClick : function(e){
  8437. clearTimeout(this.timer);
  8438. this.el.blur();
  8439. this.fireEvent("mousedown", this, e);
  8440. this.fireEvent("click", this, e);
  8441. },
  8442. // private
  8443. handleMouseDown : function(e){
  8444. clearTimeout(this.timer);
  8445. this.el.blur();
  8446. if(this.pressedCls){
  8447. this.el.addCls(this.pressedCls);
  8448. }
  8449. this.mousedownTime = new Date();
  8450. Ext.getDoc().on("mouseup", this.handleMouseUp, this);
  8451. this.el.on("mouseout", this.handleMouseOut, this);
  8452. this.fireEvent("mousedown", this, e);
  8453. this.fireEvent("click", this, e);
  8454. // Do not honor delay or interval if acceleration wanted.
  8455. if (this.accelerate) {
  8456. this.delay = 400;
  8457. }
  8458. // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
  8459. // the global shared EventObject gets a new Event put into it before the timer fires.
  8460. e = new Ext.EventObjectImpl(e);
  8461. this.timer = Ext.defer(this.click, this.delay || this.interval, this, [e]);
  8462. },
  8463. // private
  8464. click : function(e){
  8465. this.fireEvent("click", this, e);
  8466. this.timer = Ext.defer(this.click, this.accelerate ?
  8467. this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
  8468. 400,
  8469. -390,
  8470. 12000) :
  8471. this.interval, this, [e]);
  8472. },
  8473. easeOutExpo : function (t, b, c, d) {
  8474. return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
  8475. },
  8476. // private
  8477. handleMouseOut : function(){
  8478. clearTimeout(this.timer);
  8479. if(this.pressedCls){
  8480. this.el.removeCls(this.pressedCls);
  8481. }
  8482. this.el.on("mouseover", this.handleMouseReturn, this);
  8483. },
  8484. // private
  8485. handleMouseReturn : function(){
  8486. this.el.un("mouseover", this.handleMouseReturn, this);
  8487. if(this.pressedCls){
  8488. this.el.addCls(this.pressedCls);
  8489. }
  8490. this.click();
  8491. },
  8492. // private
  8493. handleMouseUp : function(e){
  8494. clearTimeout(this.timer);
  8495. this.el.un("mouseover", this.handleMouseReturn, this);
  8496. this.el.un("mouseout", this.handleMouseOut, this);
  8497. Ext.getDoc().un("mouseup", this.handleMouseUp, this);
  8498. if(this.pressedCls){
  8499. this.el.removeCls(this.pressedCls);
  8500. }
  8501. this.fireEvent("mouseup", this, e);
  8502. }
  8503. });
  8504. /**
  8505. * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
  8506. * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
  8507. * should not contain any HTML, otherwise it may not be measured correctly.
  8508. *
  8509. * The measurement works by copying the relevant CSS styles that can affect the font related display,
  8510. * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must
  8511. * provide a **fixed width** when doing the measurement.
  8512. *
  8513. * If multiple measurements are being done on the same element, you create a new instance to initialize
  8514. * to avoid the overhead of copying the styles to the element repeatedly.
  8515. */
  8516. Ext.define('Ext.util.TextMetrics', {
  8517. statics: {
  8518. shared: null,
  8519. /**
  8520. * Measures the size of the specified text
  8521. * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
  8522. * that can affect the size of the rendered text
  8523. * @param {String} text The text to measure
  8524. * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
  8525. * in order to accurately measure the text height
  8526. * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
  8527. * @static
  8528. */
  8529. measure: function(el, text, fixedWidth){
  8530. var me = this,
  8531. shared = me.shared;
  8532. if(!shared){
  8533. shared = me.shared = new me(el, fixedWidth);
  8534. }
  8535. shared.bind(el);
  8536. shared.setFixedWidth(fixedWidth || 'auto');
  8537. return shared.getSize(text);
  8538. },
  8539. /**
  8540. * Destroy the TextMetrics instance created by {@link #measure}.
  8541. * @static
  8542. */
  8543. destroy: function(){
  8544. var me = this;
  8545. Ext.destroy(me.shared);
  8546. me.shared = null;
  8547. }
  8548. },
  8549. /**
  8550. * Creates new TextMetrics.
  8551. * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
  8552. * @param {Number} [fixedWidth] A fixed width to apply to the measuring element.
  8553. */
  8554. constructor: function(bindTo, fixedWidth){
  8555. var measure = this.measure = Ext.getBody().createChild({
  8556. cls: Ext.baseCSSPrefix + 'textmetrics'
  8557. });
  8558. this.el = Ext.get(bindTo);
  8559. measure.position('absolute');
  8560. measure.setLeftTop(-1000, -1000);
  8561. measure.hide();
  8562. if (fixedWidth) {
  8563. measure.setWidth(fixedWidth);
  8564. }
  8565. },
  8566. /**
  8567. * Returns the size of the specified text based on the internal element's style and width properties
  8568. * @param {String} text The text to measure
  8569. * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
  8570. */
  8571. getSize: function(text){
  8572. var measure = this.measure,
  8573. size;
  8574. measure.update(text);
  8575. size = measure.getSize();
  8576. measure.update('');
  8577. return size;
  8578. },
  8579. /**
  8580. * Binds this TextMetrics instance to a new element
  8581. * @param {String/HTMLElement/Ext.Element} el The element or its ID.
  8582. */
  8583. bind: function(el){
  8584. var me = this;
  8585. me.el = Ext.get(el);
  8586. me.measure.setStyle(
  8587. me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
  8588. );
  8589. },
  8590. /**
  8591. * Sets a fixed width on the internal measurement element. If the text will be multiline, you have
  8592. * to set a fixed width in order to accurately measure the text height.
  8593. * @param {Number} width The width to set on the element
  8594. */
  8595. setFixedWidth : function(width){
  8596. this.measure.setWidth(width);
  8597. },
  8598. /**
  8599. * Returns the measured width of the specified text
  8600. * @param {String} text The text to measure
  8601. * @return {Number} width The width in pixels
  8602. */
  8603. getWidth : function(text){
  8604. this.measure.dom.style.width = 'auto';
  8605. return this.getSize(text).width;
  8606. },
  8607. /**
  8608. * Returns the measured height of the specified text
  8609. * @param {String} text The text to measure
  8610. * @return {Number} height The height in pixels
  8611. */
  8612. getHeight : function(text){
  8613. return this.getSize(text).height;
  8614. },
  8615. /**
  8616. * Destroy this instance
  8617. */
  8618. destroy: function(){
  8619. var me = this;
  8620. me.measure.remove();
  8621. delete me.el;
  8622. delete me.measure;
  8623. }
  8624. }, function(){
  8625. Ext.Element.addMethods({
  8626. /**
  8627. * Returns the width in pixels of the passed text, or the width of the text in this Element.
  8628. * @param {String} text The text to measure. Defaults to the innerHTML of the element.
  8629. * @param {Number} [min] The minumum value to return.
  8630. * @param {Number} [max] The maximum value to return.
  8631. * @return {Number} The text width in pixels.
  8632. * @member Ext.dom.Element
  8633. */
  8634. getTextWidth : function(text, min, max){
  8635. return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
  8636. }
  8637. });
  8638. });
  8639. /**
  8640. * @class Ext.app.Controller
  8641. *
  8642. * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
  8643. * views) and take some action. Here's how we might create a Controller to manage Users:
  8644. *
  8645. * Ext.define('MyApp.controller.Users', {
  8646. * extend: 'Ext.app.Controller',
  8647. *
  8648. * init: function() {
  8649. * console.log('Initialized Users! This happens before the Application launch function is called');
  8650. * }
  8651. * });
  8652. *
  8653. * The init function is a special method that is called when your application boots. It is called before the
  8654. * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
  8655. * your Viewport is created.
  8656. *
  8657. * The init function is a great place to set up how your controller interacts with the view, and is usually used in
  8658. * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
  8659. * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
  8660. * our Users controller to tell us when the panel is rendered:
  8661. *
  8662. * Ext.define('MyApp.controller.Users', {
  8663. * extend: 'Ext.app.Controller',
  8664. *
  8665. * init: function() {
  8666. * this.control({
  8667. * 'viewport > panel': {
  8668. * render: this.onPanelRendered
  8669. * }
  8670. * });
  8671. * },
  8672. *
  8673. * onPanelRendered: function() {
  8674. * console.log('The panel was rendered');
  8675. * }
  8676. * });
  8677. *
  8678. * We've updated the init function to use this.control to set up listeners on views in our application. The control
  8679. * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
  8680. * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
  8681. * it allows us to pass a CSS-like selector that will find every matching component on the page.
  8682. *
  8683. * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
  8684. * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
  8685. * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
  8686. * onPanelRendered function is called.
  8687. *
  8688. * <u>Using refs</u>
  8689. *
  8690. * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
  8691. * make it really easy to get references to Views on your page. Let's look at an example of this now:
  8692. *
  8693. * Ext.define('MyApp.controller.Users', {
  8694. * extend: 'Ext.app.Controller',
  8695. *
  8696. * refs: [
  8697. * {
  8698. * ref: 'list',
  8699. * selector: 'grid'
  8700. * }
  8701. * ],
  8702. *
  8703. * init: function() {
  8704. * this.control({
  8705. * 'button': {
  8706. * click: this.refreshGrid
  8707. * }
  8708. * });
  8709. * },
  8710. *
  8711. * refreshGrid: function() {
  8712. * this.getList().store.load();
  8713. * }
  8714. * });
  8715. *
  8716. * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
  8717. * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
  8718. * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
  8719. * assigns it to the reference 'list'.
  8720. *
  8721. * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
  8722. * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
  8723. * was capitalized and prepended with get to go from 'list' to 'getList'.
  8724. *
  8725. * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
  8726. * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
  8727. * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
  8728. * match a single View in your application (in the case above our selector will match any grid on the page).
  8729. *
  8730. * Bringing it all together, our init function is called when the application boots, at which time we call this.control
  8731. * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
  8732. * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
  8733. * simplicity). When the button is clicked we use out getList function to refresh the grid.
  8734. *
  8735. * You can create any number of refs and control any number of components this way, simply adding more functions to
  8736. * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
  8737. * examples/app/feed-viewer folder in the SDK download.
  8738. *
  8739. * <u>Generated getter methods</u>
  8740. *
  8741. * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
  8742. * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
  8743. *
  8744. * Ext.define('MyApp.controller.Users', {
  8745. * extend: 'Ext.app.Controller',
  8746. *
  8747. * models: ['User'],
  8748. * stores: ['AllUsers', 'AdminUsers'],
  8749. *
  8750. * init: function() {
  8751. * var User = this.getUserModel(),
  8752. * allUsers = this.getAllUsersStore();
  8753. *
  8754. * var ed = new User({name: 'Ed'});
  8755. * allUsers.add(ed);
  8756. * }
  8757. * });
  8758. *
  8759. * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
  8760. * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
  8761. * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
  8762. * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
  8763. * functionality.
  8764. *
  8765. * <u>Further Reading</u>
  8766. *
  8767. * For more information about writing Ext JS 4 applications, please see the
  8768. * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
  8769. *
  8770. * @docauthor Ed Spencer
  8771. */
  8772. Ext.define('Ext.app.Controller', {
  8773. mixins: {
  8774. observable: 'Ext.util.Observable'
  8775. },
  8776. /**
  8777. * @cfg {String} id The id of this controller. You can use this id when dispatching.
  8778. */
  8779. /**
  8780. * @cfg {String[]} models
  8781. * Array of models to require from AppName.model namespace. For example:
  8782. *
  8783. * Ext.define("MyApp.controller.Foo", {
  8784. * extend: "Ext.app.Controller",
  8785. * models: ['User', 'Vehicle']
  8786. * });
  8787. *
  8788. * This is equivalent of:
  8789. *
  8790. * Ext.define("MyApp.controller.Foo", {
  8791. * extend: "Ext.app.Controller",
  8792. * requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
  8793. * });
  8794. *
  8795. */
  8796. /**
  8797. * @cfg {String[]} views
  8798. * Array of views to require from AppName.view namespace. For example:
  8799. *
  8800. * Ext.define("MyApp.controller.Foo", {
  8801. * extend: "Ext.app.Controller",
  8802. * views: ['List', 'Detail']
  8803. * });
  8804. *
  8805. * This is equivalent of:
  8806. *
  8807. * Ext.define("MyApp.controller.Foo", {
  8808. * extend: "Ext.app.Controller",
  8809. * requires: ['MyApp.view.List', 'MyApp.view.Detail']
  8810. * });
  8811. *
  8812. */
  8813. /**
  8814. * @cfg {String[]} stores
  8815. * Array of stores to require from AppName.store namespace. For example:
  8816. *
  8817. * Ext.define("MyApp.controller.Foo", {
  8818. * extend: "Ext.app.Controller",
  8819. * stores: ['Users', 'Vehicles']
  8820. * });
  8821. *
  8822. * This is equivalent of:
  8823. *
  8824. * Ext.define("MyApp.controller.Foo", {
  8825. * extend: "Ext.app.Controller",
  8826. * requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
  8827. * });
  8828. *
  8829. */
  8830. /**
  8831. * @cfg {Object[]} refs
  8832. * Array of configs to build up references to views on page. For example:
  8833. *
  8834. * Ext.define("MyApp.controller.Foo", {
  8835. * extend: "Ext.app.Controller",
  8836. * refs: [
  8837. * {
  8838. * ref: 'list',
  8839. * selector: 'grid'
  8840. * }
  8841. * ],
  8842. * });
  8843. *
  8844. * This will add method `getList` to the controller which will internally use
  8845. * Ext.ComponentQuery to reference the grid component on page.
  8846. */
  8847. onClassExtended: function(cls, data, hooks) {
  8848. var className = Ext.getClassName(cls),
  8849. match = className.match(/^(.*)\.controller\./);
  8850. if (match !== null) {
  8851. var namespace = Ext.Loader.getPrefix(className) || match[1],
  8852. onBeforeClassCreated = hooks.onBeforeCreated,
  8853. requires = [],
  8854. modules = ['model', 'view', 'store'],
  8855. prefix;
  8856. hooks.onBeforeCreated = function(cls, data) {
  8857. var i, ln, module,
  8858. items, j, subLn, item;
  8859. for (i = 0,ln = modules.length; i < ln; i++) {
  8860. module = modules[i];
  8861. items = Ext.Array.from(data[module + 's']);
  8862. for (j = 0,subLn = items.length; j < subLn; j++) {
  8863. item = items[j];
  8864. prefix = Ext.Loader.getPrefix(item);
  8865. if (prefix === '' || prefix === item) {
  8866. requires.push(namespace + '.' + module + '.' + item);
  8867. }
  8868. else {
  8869. requires.push(item);
  8870. }
  8871. }
  8872. }
  8873. Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
  8874. };
  8875. }
  8876. },
  8877. /**
  8878. * Creates new Controller.
  8879. * @param {Object} config (optional) Config object.
  8880. */
  8881. constructor: function(config) {
  8882. this.mixins.observable.constructor.call(this, config);
  8883. Ext.apply(this, config || {});
  8884. this.createGetters('model', this.models);
  8885. this.createGetters('store', this.stores);
  8886. this.createGetters('view', this.views);
  8887. if (this.refs) {
  8888. this.ref(this.refs);
  8889. }
  8890. },
  8891. /**
  8892. * A template method that is called when your application boots. It is called before the
  8893. * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
  8894. * your Viewport is created.
  8895. *
  8896. * @param {Ext.app.Application} application
  8897. * @template
  8898. */
  8899. init: function(application) {},
  8900. /**
  8901. * A template method like {@link #init}, but called after the viewport is created.
  8902. * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
  8903. *
  8904. * @param {Ext.app.Application} application
  8905. * @template
  8906. */
  8907. onLaunch: function(application) {},
  8908. createGetters: function(type, refs) {
  8909. type = Ext.String.capitalize(type);
  8910. var i = 0,
  8911. length = (refs) ? refs.length : 0,
  8912. fn, ref, parts, x, numparts;
  8913. for (; i < length; i++) {
  8914. fn = 'get';
  8915. ref = refs[i];
  8916. parts = ref.split('.');
  8917. numParts = parts.length;
  8918. // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
  8919. for (x = 0 ; x < numParts; x++) {
  8920. fn += Ext.String.capitalize(parts[x]);
  8921. }
  8922. fn += type;
  8923. if (!this[fn]) {
  8924. this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
  8925. }
  8926. // Execute it right away
  8927. this[fn](ref);
  8928. }
  8929. },
  8930. ref: function(refs) {
  8931. refs = Ext.Array.from(refs);
  8932. var me = this,
  8933. i = 0,
  8934. length = refs.length,
  8935. info, ref, fn;
  8936. for (; i < length; i++) {
  8937. info = refs[i];
  8938. ref = info.ref;
  8939. fn = 'get' + Ext.String.capitalize(ref);
  8940. if (!me[fn]) {
  8941. me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
  8942. }
  8943. me.references = me.references || [];
  8944. me.references.push(ref.toLowerCase());
  8945. }
  8946. },
  8947. addRef: function(ref) {
  8948. return this.ref([ref]);
  8949. },
  8950. getRef: function(ref, info, config) {
  8951. this.refCache = this.refCache || {};
  8952. info = info || {};
  8953. config = config || {};
  8954. Ext.apply(info, config);
  8955. if (info.forceCreate) {
  8956. return Ext.ComponentManager.create(info, 'component');
  8957. }
  8958. var me = this,
  8959. cached = me.refCache[ref];
  8960. if (!cached) {
  8961. me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
  8962. if (!cached && info.autoCreate) {
  8963. me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
  8964. }
  8965. if (cached) {
  8966. cached.on('beforedestroy', function() {
  8967. me.refCache[ref] = null;
  8968. });
  8969. }
  8970. }
  8971. return cached;
  8972. },
  8973. hasRef: function(ref) {
  8974. return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
  8975. },
  8976. /**
  8977. * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
  8978. * object containing component paths mapped to a hash of listener functions.
  8979. *
  8980. * In the following example the `updateUser` function is mapped to to the `click`
  8981. * event on a button component, which is a child of the `useredit` component.
  8982. *
  8983. * Ext.define('AM.controller.Users', {
  8984. * init: function() {
  8985. * this.control({
  8986. * 'useredit button[action=save]': {
  8987. * click: this.updateUser
  8988. * }
  8989. * });
  8990. * },
  8991. *
  8992. * updateUser: function(button) {
  8993. * console.log('clicked the Save button');
  8994. * }
  8995. * });
  8996. *
  8997. * See {@link Ext.ComponentQuery} for more information on component selectors.
  8998. *
  8999. * @param {String/Object} selectors If a String, the second argument is used as the
  9000. * listeners, otherwise an object of selectors -> listeners is assumed
  9001. * @param {Object} listeners
  9002. */
  9003. control: function(selectors, listeners) {
  9004. this.application.control(selectors, listeners, this);
  9005. },
  9006. /**
  9007. * Returns instance of a {@link Ext.app.Controller controller} with the given name.
  9008. * When controller doesn't exist yet, it's created.
  9009. * @param {String} name
  9010. * @return {Ext.app.Controller} a controller instance.
  9011. */
  9012. getController: function(name) {
  9013. return this.application.getController(name);
  9014. },
  9015. /**
  9016. * Returns instance of a {@link Ext.data.Store Store} with the given name.
  9017. * When store doesn't exist yet, it's created.
  9018. * @param {String} name
  9019. * @return {Ext.data.Store} a store instance.
  9020. */
  9021. getStore: function(name) {
  9022. return this.application.getStore(name);
  9023. },
  9024. /**
  9025. * Returns a {@link Ext.data.Model Model} class with the given name.
  9026. * A shorthand for using {@link Ext.ModelManager#getModel}.
  9027. * @param {String} name
  9028. * @return {Ext.data.Model} a model class.
  9029. */
  9030. getModel: function(model) {
  9031. return this.application.getModel(model);
  9032. },
  9033. /**
  9034. * Returns a View class with the given name. To create an instance of the view,
  9035. * you can use it like it's used by Application to create the Viewport:
  9036. *
  9037. * this.getView('Viewport').create();
  9038. *
  9039. * @param {String} name
  9040. * @return {Ext.Base} a view class.
  9041. */
  9042. getView: function(view) {
  9043. return this.application.getView(view);
  9044. }
  9045. });
  9046. /**
  9047. * Base Manager class
  9048. */
  9049. Ext.define('Ext.AbstractManager', {
  9050. /* Begin Definitions */
  9051. requires: ['Ext.util.HashMap'],
  9052. /* End Definitions */
  9053. typeName: 'type',
  9054. constructor: function(config) {
  9055. Ext.apply(this, config || {});
  9056. /**
  9057. * @property {Ext.util.HashMap} all
  9058. * Contains all of the items currently managed
  9059. */
  9060. this.all = new Ext.util.HashMap();
  9061. this.types = {};
  9062. },
  9063. /**
  9064. * Returns an item by id.
  9065. * For additional details see {@link Ext.util.HashMap#get}.
  9066. * @param {String} id The id of the item
  9067. * @return {Object} The item, undefined if not found.
  9068. */
  9069. get : function(id) {
  9070. return this.all.get(id);
  9071. },
  9072. /**
  9073. * Registers an item to be managed
  9074. * @param {Object} item The item to register
  9075. */
  9076. register: function(item) {
  9077. var all = this.all,
  9078. key = all.getKey(item);
  9079. if (all.containsKey(key)) {
  9080. Ext.Error.raise('Registering duplicate id "' + key + '" with this manager');
  9081. }
  9082. this.all.add(item);
  9083. },
  9084. /**
  9085. * Unregisters an item by removing it from this manager
  9086. * @param {Object} item The item to unregister
  9087. */
  9088. unregister: function(item) {
  9089. this.all.remove(item);
  9090. },
  9091. /**
  9092. * Registers a new item constructor, keyed by a type key.
  9093. * @param {String} type The mnemonic string by which the class may be looked up.
  9094. * @param {Function} cls The new instance class.
  9095. */
  9096. registerType : function(type, cls) {
  9097. this.types[type] = cls;
  9098. cls[this.typeName] = type;
  9099. },
  9100. /**
  9101. * Checks if an item type is registered.
  9102. * @param {String} type The mnemonic string by which the class may be looked up
  9103. * @return {Boolean} Whether the type is registered.
  9104. */
  9105. isRegistered : function(type){
  9106. return this.types[type] !== undefined;
  9107. },
  9108. /**
  9109. * Creates and returns an instance of whatever this manager manages, based on the supplied type and
  9110. * config object.
  9111. * @param {Object} config The config object
  9112. * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
  9113. * @return {Object} The instance of whatever this manager is managing
  9114. */
  9115. create: function(config, defaultType) {
  9116. var type = config[this.typeName] || config.type || defaultType,
  9117. Constructor = this.types[type];
  9118. if (Constructor === undefined) {
  9119. Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
  9120. }
  9121. return new Constructor(config);
  9122. },
  9123. /**
  9124. * Registers a function that will be called when an item with the specified id is added to the manager.
  9125. * This will happen on instantiation.
  9126. * @param {String} id The item id
  9127. * @param {Function} fn The callback function. Called with a single parameter, the item.
  9128. * @param {Object} scope The scope (this reference) in which the callback is executed.
  9129. * Defaults to the item.
  9130. */
  9131. onAvailable : function(id, fn, scope){
  9132. var all = this.all,
  9133. item;
  9134. if (all.containsKey(id)) {
  9135. item = all.get(id);
  9136. fn.call(scope || item, item);
  9137. } else {
  9138. all.on('add', function(map, key, item){
  9139. if (key == id) {
  9140. fn.call(scope || item, item);
  9141. all.un('add', fn, scope);
  9142. }
  9143. });
  9144. }
  9145. },
  9146. /**
  9147. * Executes the specified function once for each item in the collection.
  9148. * @param {Function} fn The function to execute.
  9149. * @param {String} fn.key The key of the item
  9150. * @param {Number} fn.value The value of the item
  9151. * @param {Number} fn.length The total number of items in the collection
  9152. * @param {Boolean} fn.return False to cease iteration.
  9153. * @param {Object} scope The scope to execute in. Defaults to `this`.
  9154. */
  9155. each: function(fn, scope){
  9156. this.all.each(fn, scope || this);
  9157. },
  9158. /**
  9159. * Gets the number of items in the collection.
  9160. * @return {Number} The number of items in the collection.
  9161. */
  9162. getCount: function(){
  9163. return this.all.getCount();
  9164. }
  9165. });
  9166. /**
  9167. * @author Ed Spencer
  9168. * @class Ext.ModelManager
  9169. The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
  9170. __Creating Model Instances__
  9171. Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
  9172. the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
  9173. this by using the Model type directly. The following 3 snippets are equivalent:
  9174. Ext.define('User', {
  9175. extend: 'Ext.data.Model',
  9176. fields: ['first', 'last']
  9177. });
  9178. // method 1, create using Ext.create (recommended)
  9179. Ext.create('User', {
  9180. first: 'Ed',
  9181. last: 'Spencer'
  9182. });
  9183. // method 2, create through the manager (deprecated)
  9184. Ext.ModelManager.create({
  9185. first: 'Ed',
  9186. last: 'Spencer'
  9187. }, 'User');
  9188. // method 3, create on the type directly
  9189. new User({
  9190. first: 'Ed',
  9191. last: 'Spencer'
  9192. });
  9193. __Accessing Model Types__
  9194. A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
  9195. are normal classes, you can access the type directly. The following snippets are equivalent:
  9196. Ext.define('User', {
  9197. extend: 'Ext.data.Model',
  9198. fields: ['first', 'last']
  9199. });
  9200. // method 1, access model type through the manager
  9201. var UserType = Ext.ModelManager.getModel('User');
  9202. // method 2, reference the type directly
  9203. var UserType = User;
  9204. * @markdown
  9205. * @singleton
  9206. */
  9207. Ext.define('Ext.ModelManager', {
  9208. extend: 'Ext.AbstractManager',
  9209. alternateClassName: 'Ext.ModelMgr',
  9210. requires: ['Ext.data.association.Association'],
  9211. singleton: true,
  9212. typeName: 'mtype',
  9213. /**
  9214. * Private stack of associations that must be created once their associated model has been defined
  9215. * @property {Ext.data.association.Association[]} associationStack
  9216. */
  9217. associationStack: [],
  9218. /**
  9219. * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
  9220. * immediately, as are any addition plugins defined in the model config.
  9221. * @private
  9222. */
  9223. registerType: function(name, config) {
  9224. var proto = config.prototype,
  9225. model;
  9226. if (proto && proto.isModel) {
  9227. // registering an already defined model
  9228. model = config;
  9229. } else {
  9230. // passing in a configuration
  9231. if (!config.extend) {
  9232. config.extend = 'Ext.data.Model';
  9233. }
  9234. model = Ext.define(name, config);
  9235. }
  9236. this.types[name] = model;
  9237. return model;
  9238. },
  9239. /**
  9240. * @private
  9241. * Private callback called whenever a model has just been defined. This sets up any associations
  9242. * that were waiting for the given model to be defined
  9243. * @param {Function} model The model that was just created
  9244. */
  9245. onModelDefined: function(model) {
  9246. var stack = this.associationStack,
  9247. length = stack.length,
  9248. create = [],
  9249. association, i, created;
  9250. for (i = 0; i < length; i++) {
  9251. association = stack[i];
  9252. if (association.associatedModel == model.modelName) {
  9253. create.push(association);
  9254. }
  9255. }
  9256. for (i = 0, length = create.length; i < length; i++) {
  9257. created = create[i];
  9258. this.types[created.ownerModel].prototype.associations.add(Ext.data.association.Association.create(created));
  9259. Ext.Array.remove(stack, created);
  9260. }
  9261. },
  9262. /**
  9263. * Registers an association where one of the models defined doesn't exist yet.
  9264. * The ModelManager will check when new models are registered if it can link them
  9265. * together
  9266. * @private
  9267. * @param {Ext.data.association.Association} association The association
  9268. */
  9269. registerDeferredAssociation: function(association){
  9270. this.associationStack.push(association);
  9271. },
  9272. /**
  9273. * Returns the {@link Ext.data.Model} for a given model name
  9274. * @param {String/Object} id The id of the model or the model instance.
  9275. * @return {Ext.data.Model} a model class.
  9276. */
  9277. getModel: function(id) {
  9278. var model = id;
  9279. if (typeof model == 'string') {
  9280. model = this.types[model];
  9281. }
  9282. return model;
  9283. },
  9284. /**
  9285. * Creates a new instance of a Model using the given data.
  9286. *
  9287. * This method is deprecated. Use {@link Ext#create Ext.create} instead. For example:
  9288. *
  9289. * Ext.create('User', {
  9290. * first: 'Ed',
  9291. * last: 'Spencer'
  9292. * });
  9293. *
  9294. * @param {Object} data Data to initialize the Model's fields with
  9295. * @param {String} name The name of the model to create
  9296. * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
  9297. */
  9298. create: function(config, name, id) {
  9299. var con = typeof name == 'function' ? name : this.types[name || config.name];
  9300. return new con(config, id);
  9301. }
  9302. }, function() {
  9303. /**
  9304. * Old way for creating Model classes. Instead use:
  9305. *
  9306. * Ext.define("MyModel", {
  9307. * extend: "Ext.data.Model",
  9308. * fields: []
  9309. * });
  9310. *
  9311. * @param {String} name Name of the Model class.
  9312. * @param {Object} config A configuration object for the Model you wish to create.
  9313. * @return {Ext.data.Model} The newly registered Model
  9314. * @member Ext
  9315. * @deprecated 4.0.0 Use {@link Ext#define} instead.
  9316. */
  9317. Ext.regModel = function() {
  9318. if (Ext.isDefined(Ext.global.console)) {
  9319. 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: []});.');
  9320. }
  9321. return this.ModelManager.registerType.apply(this.ModelManager, arguments);
  9322. };
  9323. });
  9324. /**
  9325. * @class Ext.ComponentManager
  9326. * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
  9327. * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
  9328. * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
  9329. * <p>This object also provides a registry of available Component <i>classes</i>
  9330. * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
  9331. * The <code>xtype</code> provides a way to avoid instantiating child Components
  9332. * when creating a full, nested config object for a complete Ext page.</p>
  9333. * <p>A child Component may be specified simply as a <i>config object</i>
  9334. * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
  9335. * needs rendering, the correct type can be looked up for lazy instantiation.</p>
  9336. * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
  9337. * @singleton
  9338. */
  9339. Ext.define('Ext.ComponentManager', {
  9340. extend: 'Ext.AbstractManager',
  9341. alternateClassName: 'Ext.ComponentMgr',
  9342. singleton: true,
  9343. typeName: 'xtype',
  9344. /**
  9345. * Creates a new Component from the specified config object using the
  9346. * config object's xtype to determine the class to instantiate.
  9347. * @param {Object} config A configuration object for the Component you wish to create.
  9348. * @param {String} defaultType (optional) The xtype to use if the config object does not
  9349. * contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
  9350. * @return {Ext.Component} The newly instantiated Component.
  9351. */
  9352. create: function(component, defaultType){
  9353. if (typeof component == 'string') {
  9354. return Ext.widget(component);
  9355. }
  9356. if (component.isComponent) {
  9357. return component;
  9358. }
  9359. return Ext.widget(component.xtype || defaultType, component);
  9360. },
  9361. registerType: function(type, cls) {
  9362. this.types[type] = cls;
  9363. cls[this.typeName] = type;
  9364. cls.prototype[this.typeName] = type;
  9365. }
  9366. });
  9367. /**
  9368. * @class Ext.data.Types
  9369. * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
  9370. * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
  9371. * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
  9372. * of this class.</p>
  9373. * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
  9374. * each type definition must contain three properties:</p>
  9375. * <div class="mdetail-params"><ul>
  9376. * <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
  9377. * to be stored in the Field. The function is passed the collowing parameters:
  9378. * <div class="mdetail-params"><ul>
  9379. * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
  9380. * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
  9381. * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
  9382. * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
  9383. * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
  9384. * </ul></div></div></li>
  9385. * <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>
  9386. * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
  9387. * </ul></div>
  9388. * <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
  9389. * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
  9390. *<pre><code>
  9391. // Add a new Field data type which stores a VELatLong object in the Record.
  9392. Ext.data.Types.VELATLONG = {
  9393. convert: function(v, data) {
  9394. return new VELatLong(data.lat, data.long);
  9395. },
  9396. sortType: function(v) {
  9397. return v.Latitude; // When sorting, order by latitude
  9398. },
  9399. type: 'VELatLong'
  9400. };
  9401. </code></pre>
  9402. * <p>Then, when declaring a Model, use: <pre><code>
  9403. var types = Ext.data.Types; // allow shorthand type access
  9404. Ext.define('Unit',
  9405. extend: 'Ext.data.Model',
  9406. fields: [
  9407. { name: 'unitName', mapping: 'UnitName' },
  9408. { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
  9409. { name: 'latitude', mapping: 'lat', type: types.FLOAT },
  9410. { name: 'longitude', mapping: 'long', type: types.FLOAT },
  9411. { name: 'position', type: types.VELATLONG }
  9412. ]
  9413. });
  9414. </code></pre>
  9415. * @singleton
  9416. */
  9417. Ext.define('Ext.data.Types', {
  9418. singleton: true,
  9419. requires: ['Ext.data.SortTypes']
  9420. }, function() {
  9421. var st = Ext.data.SortTypes;
  9422. Ext.apply(Ext.data.Types, {
  9423. /**
  9424. * @property {RegExp} stripRe
  9425. * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
  9426. * This should be overridden for localization.
  9427. */
  9428. stripRe: /[\$,%]/g,
  9429. /**
  9430. * @property {Object} AUTO
  9431. * This data type means that no conversion is applied to the raw data before it is placed into a Record.
  9432. */
  9433. AUTO: {
  9434. sortType: st.none,
  9435. type: 'auto'
  9436. },
  9437. /**
  9438. * @property {Object} STRING
  9439. * This data type means that the raw data is converted into a String before it is placed into a Record.
  9440. */
  9441. STRING: {
  9442. convert: function(v) {
  9443. var defaultValue = this.useNull ? null : '';
  9444. return (v === undefined || v === null) ? defaultValue : String(v);
  9445. },
  9446. sortType: st.asUCString,
  9447. type: 'string'
  9448. },
  9449. /**
  9450. * @property {Object} INT
  9451. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  9452. * <p>The synonym <code>INTEGER</code> is equivalent.</p>
  9453. */
  9454. INT: {
  9455. convert: function(v) {
  9456. return v !== undefined && v !== null && v !== '' ?
  9457. parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
  9458. },
  9459. sortType: st.none,
  9460. type: 'int'
  9461. },
  9462. /**
  9463. * @property {Object} FLOAT
  9464. * This data type means that the raw data is converted into a number before it is placed into a Record.
  9465. * <p>The synonym <code>NUMBER</code> is equivalent.</p>
  9466. */
  9467. FLOAT: {
  9468. convert: function(v) {
  9469. return v !== undefined && v !== null && v !== '' ?
  9470. parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
  9471. },
  9472. sortType: st.none,
  9473. type: 'float'
  9474. },
  9475. /**
  9476. * @property {Object} BOOL
  9477. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  9478. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  9479. * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
  9480. */
  9481. BOOL: {
  9482. convert: function(v) {
  9483. if (this.useNull && (v === undefined || v === null || v === '')) {
  9484. return null;
  9485. }
  9486. return v === true || v === 'true' || v == 1;
  9487. },
  9488. sortType: st.none,
  9489. type: 'bool'
  9490. },
  9491. /**
  9492. * @property {Object} DATE
  9493. * This data type means that the raw data is converted into a Date before it is placed into a Record.
  9494. * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
  9495. * being applied.
  9496. */
  9497. DATE: {
  9498. convert: function(v) {
  9499. var df = this.dateFormat,
  9500. parsed;
  9501. if (!v) {
  9502. return null;
  9503. }
  9504. if (Ext.isDate(v)) {
  9505. return v;
  9506. }
  9507. if (df) {
  9508. if (df == 'timestamp') {
  9509. return new Date(v*1000);
  9510. }
  9511. if (df == 'time') {
  9512. return new Date(parseInt(v, 10));
  9513. }
  9514. return Ext.Date.parse(v, df);
  9515. }
  9516. parsed = Date.parse(v);
  9517. return parsed ? new Date(parsed) : null;
  9518. },
  9519. sortType: st.asDate,
  9520. type: 'date'
  9521. }
  9522. });
  9523. Ext.apply(Ext.data.Types, {
  9524. /**
  9525. * @property {Object} BOOLEAN
  9526. * <p>This data type means that the raw data is converted into a boolean before it is placed into
  9527. * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
  9528. * <p>The synonym <code>BOOL</code> is equivalent.</p>
  9529. */
  9530. BOOLEAN: this.BOOL,
  9531. /**
  9532. * @property {Object} INTEGER
  9533. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  9534. * <p>The synonym <code>INT</code> is equivalent.</p>
  9535. */
  9536. INTEGER: this.INT,
  9537. /**
  9538. * @property {Object} NUMBER
  9539. * This data type means that the raw data is converted into a number before it is placed into a Record.
  9540. * <p>The synonym <code>FLOAT</code> is equivalent.</p>
  9541. */
  9542. NUMBER: this.FLOAT
  9543. });
  9544. });
  9545. /**
  9546. * @author Ed Spencer
  9547. *
  9548. * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
  9549. * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
  9550. * Ext.data.Model Model}. For example, we might set up a model like this:
  9551. *
  9552. * Ext.define('User', {
  9553. * extend: 'Ext.data.Model',
  9554. * fields: [
  9555. * 'name', 'email',
  9556. * {name: 'age', type: 'int'},
  9557. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  9558. * ]
  9559. * });
  9560. *
  9561. * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
  9562. * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
  9563. * up with the 'auto' type. It's as if we'd done this instead:
  9564. *
  9565. * Ext.define('User', {
  9566. * extend: 'Ext.data.Model',
  9567. * fields: [
  9568. * {name: 'name', type: 'auto'},
  9569. * {name: 'email', type: 'auto'},
  9570. * {name: 'age', type: 'int'},
  9571. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  9572. * ]
  9573. * });
  9574. *
  9575. * # Types and conversion
  9576. *
  9577. * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
  9578. * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
  9579. * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
  9580. *
  9581. * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
  9582. * this using a {@link #convert} function. Here, we're going to create a new field based on another:
  9583. *
  9584. * Ext.define('User', {
  9585. * extend: 'Ext.data.Model',
  9586. * fields: [
  9587. * 'name', 'email',
  9588. * {name: 'age', type: 'int'},
  9589. * {name: 'gender', type: 'string', defaultValue: 'Unknown'},
  9590. *
  9591. * {
  9592. * name: 'firstName',
  9593. * convert: function(value, record) {
  9594. * var fullName = record.get('name'),
  9595. * splits = fullName.split(" "),
  9596. * firstName = splits[0];
  9597. *
  9598. * return firstName;
  9599. * }
  9600. * }
  9601. * ]
  9602. * });
  9603. *
  9604. * Now when we create a new User, the firstName is populated automatically based on the name:
  9605. *
  9606. * var ed = Ext.create('User', {name: 'Ed Spencer'});
  9607. *
  9608. * console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
  9609. *
  9610. * In fact, if we log out all of the data inside ed, we'll see this:
  9611. *
  9612. * console.log(ed.data);
  9613. *
  9614. * //outputs this:
  9615. * {
  9616. * age: 0,
  9617. * email: "",
  9618. * firstName: "Ed",
  9619. * gender: "Unknown",
  9620. * name: "Ed Spencer"
  9621. * }
  9622. *
  9623. * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
  9624. * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
  9625. * that now. Let's correct that and satisfy ourselves that the types work as we expect:
  9626. *
  9627. * ed.set('gender', 'Male');
  9628. * ed.get('gender'); //returns 'Male'
  9629. *
  9630. * ed.set('age', 25.4);
  9631. * ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
  9632. */
  9633. Ext.define('Ext.data.Field', {
  9634. requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
  9635. alias: 'data.field',
  9636. constructor : function(config) {
  9637. if (Ext.isString(config)) {
  9638. config = {name: config};
  9639. }
  9640. Ext.apply(this, config);
  9641. var types = Ext.data.Types,
  9642. st = this.sortType,
  9643. t;
  9644. if (this.type) {
  9645. if (Ext.isString(this.type)) {
  9646. this.type = types[this.type.toUpperCase()] || types.AUTO;
  9647. }
  9648. } else {
  9649. this.type = types.AUTO;
  9650. }
  9651. // named sortTypes are supported, here we look them up
  9652. if (Ext.isString(st)) {
  9653. this.sortType = Ext.data.SortTypes[st];
  9654. } else if(Ext.isEmpty(st)) {
  9655. this.sortType = this.type.sortType;
  9656. }
  9657. if (!this.convert) {
  9658. this.convert = this.type.convert;
  9659. }
  9660. },
  9661. /**
  9662. * @cfg {String} name
  9663. *
  9664. * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
  9665. * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
  9666. *
  9667. * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
  9668. * just a String for the field name.
  9669. */
  9670. /**
  9671. * @cfg {String/Object} type
  9672. *
  9673. * The data type for automatic conversion from received data to the *stored* value if
  9674. * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
  9675. * Possible values are
  9676. *
  9677. * - auto (Default, implies no conversion)
  9678. * - string
  9679. * - int
  9680. * - float
  9681. * - boolean
  9682. * - date
  9683. *
  9684. * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
  9685. *
  9686. * Developers may create their own application-specific data types by defining new members of the {@link
  9687. * Ext.data.Types} class.
  9688. */
  9689. /**
  9690. * @cfg {Function} convert
  9691. *
  9692. * A function which converts the value provided by the Reader into an object that will be stored in the Model.
  9693. * It is passed the following parameters:
  9694. *
  9695. * - **v** : Mixed
  9696. *
  9697. * The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
  9698. * defaultValue}`.
  9699. *
  9700. * - **rec** : Ext.data.Model
  9701. *
  9702. * The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
  9703. * at this point as the fields are read in the order that they are defined in your
  9704. * {@link Ext.data.Model#cfg-fields fields} array.
  9705. *
  9706. * Example of convert functions:
  9707. *
  9708. * function fullName(v, record){
  9709. * return record.name.last + ', ' + record.name.first;
  9710. * }
  9711. *
  9712. * function location(v, record){
  9713. * return !record.city ? '' : (record.city + ', ' + record.state);
  9714. * }
  9715. *
  9716. * Ext.define('Dude', {
  9717. * extend: 'Ext.data.Model',
  9718. * fields: [
  9719. * {name: 'fullname', convert: fullName},
  9720. * {name: 'firstname', mapping: 'name.first'},
  9721. * {name: 'lastname', mapping: 'name.last'},
  9722. * {name: 'city', defaultValue: 'homeless'},
  9723. * 'state',
  9724. * {name: 'location', convert: location}
  9725. * ]
  9726. * });
  9727. *
  9728. * // create the data store
  9729. * var store = Ext.create('Ext.data.Store', {
  9730. * reader: {
  9731. * type: 'json',
  9732. * model: 'Dude',
  9733. * idProperty: 'key',
  9734. * root: 'daRoot',
  9735. * totalProperty: 'total'
  9736. * }
  9737. * });
  9738. *
  9739. * var myData = [
  9740. * { key: 1,
  9741. * name: { first: 'Fat', last: 'Albert' }
  9742. * // notice no city, state provided in data object
  9743. * },
  9744. * { key: 2,
  9745. * name: { first: 'Barney', last: 'Rubble' },
  9746. * city: 'Bedrock', state: 'Stoneridge'
  9747. * },
  9748. * { key: 3,
  9749. * name: { first: 'Cliff', last: 'Claven' },
  9750. * city: 'Boston', state: 'MA'
  9751. * }
  9752. * ];
  9753. */
  9754. /**
  9755. * @cfg {String} dateFormat
  9756. *
  9757. * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
  9758. *
  9759. * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
  9760. * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
  9761. * timestamp. See {@link Ext.Date}.
  9762. */
  9763. dateFormat: null,
  9764. /**
  9765. * @cfg {Boolean} useNull
  9766. *
  9767. * Use when converting received data into a Number type (either int or float). If the value cannot be
  9768. * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
  9769. */
  9770. useNull: false,
  9771. /**
  9772. * @cfg {Object} defaultValue
  9773. *
  9774. * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
  9775. * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
  9776. * (i.e. undefined). Defaults to "".
  9777. */
  9778. defaultValue: "",
  9779. /**
  9780. * @cfg {String/Number} mapping
  9781. *
  9782. * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
  9783. * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
  9784. * as the field name, the mapping may be omitted.
  9785. *
  9786. * The form of the mapping expression depends on the Reader being used.
  9787. *
  9788. * - {@link Ext.data.reader.Json}
  9789. *
  9790. * The mapping is a string containing the javascript expression to reference the data from an element of the data
  9791. * item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
  9792. *
  9793. * - {@link Ext.data.reader.Xml}
  9794. *
  9795. * The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
  9796. * {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
  9797. *
  9798. * - {@link Ext.data.reader.Array}
  9799. *
  9800. * The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
  9801. * Array position.
  9802. *
  9803. * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
  9804. * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
  9805. * return the desired data.
  9806. */
  9807. mapping: null,
  9808. /**
  9809. * @cfg {Function} sortType
  9810. *
  9811. * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
  9812. * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
  9813. *
  9814. * // current sort after sort we want
  9815. * // +-+------+ +-+------+
  9816. * // |1|First | |1|First |
  9817. * // |2|Last | |3|Second|
  9818. * // |3|Second| |2|Last |
  9819. * // +-+------+ +-+------+
  9820. *
  9821. * sortType: function(value) {
  9822. * switch (value.toLowerCase()) // native toLowerCase():
  9823. * {
  9824. * case 'first': return 1;
  9825. * case 'second': return 2;
  9826. * default: return 3;
  9827. * }
  9828. * }
  9829. */
  9830. sortType : null,
  9831. /**
  9832. * @cfg {String} sortDir
  9833. *
  9834. * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
  9835. */
  9836. sortDir : "ASC",
  9837. /**
  9838. * @cfg {Boolean} allowBlank
  9839. * @private
  9840. *
  9841. * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
  9842. * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
  9843. */
  9844. allowBlank : true,
  9845. /**
  9846. * @cfg {Boolean} persist
  9847. *
  9848. * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
  9849. * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
  9850. * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
  9851. */
  9852. persist: true
  9853. });
  9854. /**
  9855. * @class Ext.Ajax
  9856. * @singleton
  9857. * @markdown
  9858. A singleton instance of an {@link Ext.data.Connection}. This class
  9859. is used to communicate with your server side code. It can be used as follows:
  9860. Ext.Ajax.request({
  9861. url: 'page.php',
  9862. params: {
  9863. id: 1
  9864. },
  9865. success: function(response){
  9866. var text = response.responseText;
  9867. // process server response here
  9868. }
  9869. });
  9870. Default options for all requests can be set by changing a property on the Ext.Ajax class:
  9871. Ext.Ajax.timeout = 60000; // 60 seconds
  9872. Any options specified in the request method for the Ajax request will override any
  9873. defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
  9874. request will be 60 seconds.
  9875. Ext.Ajax.timeout = 120000; // 120 seconds
  9876. Ext.Ajax.request({
  9877. url: 'page.aspx',
  9878. timeout: 60000
  9879. });
  9880. In general, this class will be used for all Ajax requests in your application.
  9881. The main reason for creating a separate {@link Ext.data.Connection} is for a
  9882. series of requests that share common settings that are different to all other
  9883. requests in the application.
  9884. */
  9885. Ext.define('Ext.Ajax', {
  9886. extend: 'Ext.data.Connection',
  9887. singleton: true,
  9888. /**
  9889. * @cfg {Object} extraParams @hide
  9890. */
  9891. /**
  9892. * @cfg {Object} defaultHeaders @hide
  9893. */
  9894. /**
  9895. * @cfg {String} method (Optional) @hide
  9896. */
  9897. /**
  9898. * @cfg {Number} timeout (Optional) @hide
  9899. */
  9900. /**
  9901. * @cfg {Boolean} autoAbort (Optional) @hide
  9902. */
  9903. /**
  9904. * @cfg {Boolean} disableCaching (Optional) @hide
  9905. */
  9906. /**
  9907. * @property {Boolean} disableCaching
  9908. * True to add a unique cache-buster param to GET requests. Defaults to true.
  9909. */
  9910. /**
  9911. * @property {String} url
  9912. * The default URL to be used for requests to the server.
  9913. * If the server receives all requests through one URL, setting this once is easier than
  9914. * entering it on every request.
  9915. */
  9916. /**
  9917. * @property {Object} extraParams
  9918. * An object containing properties which are used as extra parameters to each request made
  9919. * by this object. Session information and other data that you need
  9920. * to pass with each request are commonly put here.
  9921. */
  9922. /**
  9923. * @property {Object} defaultHeaders
  9924. * An object containing request headers which are added to each request made by this object.
  9925. */
  9926. /**
  9927. * @property {String} method
  9928. * The default HTTP method to be used for requests. Note that this is case-sensitive and
  9929. * should be all caps (if not set but params are present will use
  9930. * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
  9931. */
  9932. /**
  9933. * @property {Number} timeout
  9934. * The timeout in milliseconds to be used for requests. Defaults to 30000.
  9935. */
  9936. /**
  9937. * @property {Boolean} autoAbort
  9938. * Whether a new request should abort any pending requests.
  9939. */
  9940. autoAbort : false
  9941. });
  9942. /**
  9943. * @class Ext.util.AbstractMixedCollection
  9944. * @private
  9945. */
  9946. Ext.define('Ext.util.AbstractMixedCollection', {
  9947. requires: ['Ext.util.Filter'],
  9948. mixins: {
  9949. observable: 'Ext.util.Observable'
  9950. },
  9951. /**
  9952. * @private Mutation counter which is incremented upon add and remove.
  9953. */
  9954. generation: 0,
  9955. constructor: function(allowFunctions, keyFn) {
  9956. var me = this;
  9957. me.items = [];
  9958. me.map = {};
  9959. me.keys = [];
  9960. me.length = 0;
  9961. /**
  9962. * @event clear
  9963. * Fires when the collection is cleared.
  9964. */
  9965. /**
  9966. * @event add
  9967. * Fires when an item is added to the collection.
  9968. * @param {Number} index The index at which the item was added.
  9969. * @param {Object} o The item added.
  9970. * @param {String} key The key associated with the added item.
  9971. */
  9972. /**
  9973. * @event replace
  9974. * Fires when an item is replaced in the collection.
  9975. * @param {String} key he key associated with the new added.
  9976. * @param {Object} old The item being replaced.
  9977. * @param {Object} new The new item.
  9978. */
  9979. /**
  9980. * @event remove
  9981. * Fires when an item is removed from the collection.
  9982. * @param {Object} o The item being removed.
  9983. * @param {String} key (optional) The key associated with the removed item.
  9984. */
  9985. me.allowFunctions = allowFunctions === true;
  9986. if (keyFn) {
  9987. me.getKey = keyFn;
  9988. }
  9989. me.mixins.observable.constructor.call(me);
  9990. },
  9991. /**
  9992. * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
  9993. * function should add function references to the collection. Defaults to
  9994. * <tt>false</tt>.
  9995. */
  9996. allowFunctions : false,
  9997. /**
  9998. * Adds an item to the collection. Fires the {@link #event-add} event when complete.
  9999. * @param {String} key <p>The key to associate with the item, or the new item.</p>
  10000. * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
  10001. * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
  10002. * the MixedCollection will be able to <i>derive</i> the key for the new item.
  10003. * In this case just pass the new item in this parameter.</p>
  10004. * @param {Object} o The item to add.
  10005. * @return {Object} The item added.
  10006. */
  10007. add : function(key, obj){
  10008. var me = this,
  10009. myObj = obj,
  10010. myKey = key,
  10011. old;
  10012. if (arguments.length == 1) {
  10013. myObj = myKey;
  10014. myKey = me.getKey(myObj);
  10015. }
  10016. if (typeof myKey != 'undefined' && myKey !== null) {
  10017. old = me.map[myKey];
  10018. if (typeof old != 'undefined') {
  10019. return me.replace(myKey, myObj);
  10020. }
  10021. me.map[myKey] = myObj;
  10022. }
  10023. me.generation++;
  10024. me.length++;
  10025. me.items.push(myObj);
  10026. me.keys.push(myKey);
  10027. if (me.hasListeners.add) {
  10028. me.fireEvent('add', me.length - 1, myObj, myKey);
  10029. }
  10030. return myObj;
  10031. },
  10032. /**
  10033. * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
  10034. * simply returns <b><code>item.id</code></b> but you can provide your own implementation
  10035. * to return a different value as in the following examples:<pre><code>
  10036. // normal way
  10037. var mc = new Ext.util.MixedCollection();
  10038. mc.add(someEl.dom.id, someEl);
  10039. mc.add(otherEl.dom.id, otherEl);
  10040. //and so on
  10041. // using getKey
  10042. var mc = new Ext.util.MixedCollection();
  10043. mc.getKey = function(el){
  10044. return el.dom.id;
  10045. };
  10046. mc.add(someEl);
  10047. mc.add(otherEl);
  10048. // or via the constructor
  10049. var mc = new Ext.util.MixedCollection(false, function(el){
  10050. return el.dom.id;
  10051. });
  10052. mc.add(someEl);
  10053. mc.add(otherEl);
  10054. * </code></pre>
  10055. * @param {Object} item The item for which to find the key.
  10056. * @return {Object} The key for the passed item.
  10057. */
  10058. getKey : function(o){
  10059. return o.id;
  10060. },
  10061. /**
  10062. * Replaces an item in the collection. Fires the {@link #event-replace} event when complete.
  10063. * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
  10064. * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
  10065. * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
  10066. * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
  10067. * with one having the same key value, then just pass the replacement item in this parameter.</p>
  10068. * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
  10069. * with that key.
  10070. * @return {Object} The new item.
  10071. */
  10072. replace : function(key, o){
  10073. var me = this,
  10074. old,
  10075. index;
  10076. if (arguments.length == 1) {
  10077. o = arguments[0];
  10078. key = me.getKey(o);
  10079. }
  10080. old = me.map[key];
  10081. if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
  10082. return me.add(key, o);
  10083. }
  10084. me.generation++;
  10085. index = me.indexOfKey(key);
  10086. me.items[index] = o;
  10087. me.map[key] = o;
  10088. if (me.hasListeners.replace) {
  10089. me.fireEvent('replace', key, old, o);
  10090. }
  10091. return o;
  10092. },
  10093. /**
  10094. * Adds all elements of an Array or an Object to the collection.
  10095. * @param {Object/Array} objs An Object containing properties which will be added
  10096. * to the collection, or an Array of values, each of which are added to the collection.
  10097. * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
  10098. * has been set to <tt>true</tt>.
  10099. */
  10100. addAll : function(objs){
  10101. var me = this,
  10102. i = 0,
  10103. args,
  10104. len,
  10105. key;
  10106. if (arguments.length > 1 || Ext.isArray(objs)) {
  10107. args = arguments.length > 1 ? arguments : objs;
  10108. for (len = args.length; i < len; i++) {
  10109. me.add(args[i]);
  10110. }
  10111. } else {
  10112. for (key in objs) {
  10113. if (objs.hasOwnProperty(key)) {
  10114. if (me.allowFunctions || typeof objs[key] != 'function') {
  10115. me.add(key, objs[key]);
  10116. }
  10117. }
  10118. }
  10119. }
  10120. },
  10121. /**
  10122. * Executes the specified function once for every item in the collection, passing the following arguments:
  10123. * <div class="mdetail-params"><ul>
  10124. * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
  10125. * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
  10126. * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
  10127. * </ul></div>
  10128. * The function should return a boolean value. Returning false from the function will stop the iteration.
  10129. * @param {Function} fn The function to execute for each item.
  10130. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
  10131. */
  10132. each : function(fn, scope){
  10133. var items = [].concat(this.items), // each safe for removal
  10134. i = 0,
  10135. len = items.length,
  10136. item;
  10137. for (; i < len; i++) {
  10138. item = items[i];
  10139. if (fn.call(scope || item, item, i, len) === false) {
  10140. break;
  10141. }
  10142. }
  10143. },
  10144. /**
  10145. * Executes the specified function once for every key in the collection, passing each
  10146. * key, and its associated item as the first two parameters.
  10147. * @param {Function} fn The function to execute for each item.
  10148. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
  10149. */
  10150. eachKey : function(fn, scope){
  10151. var keys = this.keys,
  10152. items = this.items,
  10153. i = 0,
  10154. len = keys.length;
  10155. for (; i < len; i++) {
  10156. fn.call(scope || window, keys[i], items[i], i, len);
  10157. }
  10158. },
  10159. /**
  10160. * Returns the first item in the collection which elicits a true return value from the
  10161. * passed selection function.
  10162. * @param {Function} fn The selection function to execute for each item.
  10163. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
  10164. * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
  10165. */
  10166. findBy : function(fn, scope) {
  10167. var keys = this.keys,
  10168. items = this.items,
  10169. i = 0,
  10170. len = items.length;
  10171. for (; i < len; i++) {
  10172. if (fn.call(scope || window, items[i], keys[i])) {
  10173. return items[i];
  10174. }
  10175. }
  10176. return null;
  10177. },
  10178. find : function() {
  10179. if (Ext.isDefined(Ext.global.console)) {
  10180. Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
  10181. }
  10182. return this.findBy.apply(this, arguments);
  10183. },
  10184. /**
  10185. * Inserts an item at the specified index in the collection. Fires the {@link #event-add} event when complete.
  10186. * @param {Number} index The index to insert the item at.
  10187. * @param {String} key The key to associate with the new item, or the item itself.
  10188. * @param {Object} o (optional) If the second parameter was a key, the new item.
  10189. * @return {Object} The item inserted.
  10190. */
  10191. insert : function(index, key, obj){
  10192. var me = this,
  10193. myKey = key,
  10194. myObj = obj;
  10195. if (arguments.length == 2) {
  10196. myObj = myKey;
  10197. myKey = me.getKey(myObj);
  10198. }
  10199. if (me.containsKey(myKey)) {
  10200. me.suspendEvents();
  10201. me.removeAtKey(myKey);
  10202. me.resumeEvents();
  10203. }
  10204. if (index >= me.length) {
  10205. return me.add(myKey, myObj);
  10206. }
  10207. me.generation++;
  10208. me.length++;
  10209. Ext.Array.splice(me.items, index, 0, myObj);
  10210. if (typeof myKey != 'undefined' && myKey !== null) {
  10211. me.map[myKey] = myObj;
  10212. }
  10213. Ext.Array.splice(me.keys, index, 0, myKey);
  10214. if (me.hasListeners.add) {
  10215. me.fireEvent('add', index, myObj, myKey);
  10216. }
  10217. return myObj;
  10218. },
  10219. /**
  10220. * Remove an item from the collection.
  10221. * @param {Object} o The item to remove.
  10222. * @return {Object} The item removed or false if no item was removed.
  10223. */
  10224. remove : function(o) {
  10225. this.generation++;
  10226. return this.removeAt(this.indexOf(o));
  10227. },
  10228. /**
  10229. * Remove all items in the passed array from the collection.
  10230. * @param {Array} items An array of items to be removed.
  10231. * @return {Ext.util.MixedCollection} this object
  10232. */
  10233. removeAll : function(items) {
  10234. items = [].concat(items);
  10235. var i, iLen = items.length;
  10236. for (i = 0; i < iLen; i++) {
  10237. this.remove(items[i]);
  10238. }
  10239. return this;
  10240. },
  10241. /**
  10242. * Remove an item from a specified index in the collection. Fires the {@link #event-remove} event when complete.
  10243. * @param {Number} index The index within the collection of the item to remove.
  10244. * @return {Object} The item removed or false if no item was removed.
  10245. */
  10246. removeAt : function(index) {
  10247. var me = this,
  10248. o,
  10249. key;
  10250. if (index < me.length && index >= 0) {
  10251. me.length--;
  10252. o = me.items[index];
  10253. Ext.Array.erase(me.items, index, 1);
  10254. key = me.keys[index];
  10255. if (typeof key != 'undefined') {
  10256. delete me.map[key];
  10257. }
  10258. Ext.Array.erase(me.keys, index, 1);
  10259. if (me.hasListeners.remove) {
  10260. me.fireEvent('remove', o, key);
  10261. }
  10262. me.generation++;
  10263. return o;
  10264. }
  10265. return false;
  10266. },
  10267. /**
  10268. * Removed an item associated with the passed key fom the collection.
  10269. * @param {String} key The key of the item to remove.
  10270. * @return {Object} The item removed or false if no item was removed.
  10271. */
  10272. removeAtKey : function(key){
  10273. return this.removeAt(this.indexOfKey(key));
  10274. },
  10275. /**
  10276. * Returns the number of items in the collection.
  10277. * @return {Number} the number of items in the collection.
  10278. */
  10279. getCount : function(){
  10280. return this.length;
  10281. },
  10282. /**
  10283. * Returns index within the collection of the passed Object.
  10284. * @param {Object} o The item to find the index of.
  10285. * @return {Number} index of the item. Returns -1 if not found.
  10286. */
  10287. indexOf : function(o){
  10288. return Ext.Array.indexOf(this.items, o);
  10289. },
  10290. /**
  10291. * Returns index within the collection of the passed key.
  10292. * @param {String} key The key to find the index of.
  10293. * @return {Number} index of the key.
  10294. */
  10295. indexOfKey : function(key){
  10296. return Ext.Array.indexOf(this.keys, key);
  10297. },
  10298. /**
  10299. * Returns the item associated with the passed key OR index.
  10300. * Key has priority over index. This is the equivalent
  10301. * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
  10302. * @param {String/Number} key The key or index of the item.
  10303. * @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
  10304. * If an item was found, but is a Class, returns <tt>null</tt>.
  10305. */
  10306. get : function(key) {
  10307. var me = this,
  10308. mk = me.map[key],
  10309. item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
  10310. return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
  10311. },
  10312. /**
  10313. * Returns the item at the specified index.
  10314. * @param {Number} index The index of the item.
  10315. * @return {Object} The item at the specified index.
  10316. */
  10317. getAt : function(index) {
  10318. return this.items[index];
  10319. },
  10320. /**
  10321. * Returns the item associated with the passed key.
  10322. * @param {String/Number} key The key of the item.
  10323. * @return {Object} The item associated with the passed key.
  10324. */
  10325. getByKey : function(key) {
  10326. return this.map[key];
  10327. },
  10328. /**
  10329. * Returns true if the collection contains the passed Object as an item.
  10330. * @param {Object} o The Object to look for in the collection.
  10331. * @return {Boolean} True if the collection contains the Object as an item.
  10332. */
  10333. contains : function(o){
  10334. return typeof this.map[this.getKey(o)] != 'undefined';
  10335. },
  10336. /**
  10337. * Returns true if the collection contains the passed Object as a key.
  10338. * @param {String} key The key to look for in the collection.
  10339. * @return {Boolean} True if the collection contains the Object as a key.
  10340. */
  10341. containsKey : function(key){
  10342. return typeof this.map[key] != 'undefined';
  10343. },
  10344. /**
  10345. * Removes all items from the collection. Fires the {@link #event-clear} event when complete.
  10346. */
  10347. clear : function(){
  10348. var me = this;
  10349. me.length = 0;
  10350. me.items = [];
  10351. me.keys = [];
  10352. me.map = {};
  10353. me.generation++;
  10354. if (me.hasListeners.clear) {
  10355. me.fireEvent('clear');
  10356. }
  10357. },
  10358. /**
  10359. * Returns the first item in the collection.
  10360. * @return {Object} the first item in the collection..
  10361. */
  10362. first : function() {
  10363. return this.items[0];
  10364. },
  10365. /**
  10366. * Returns the last item in the collection.
  10367. * @return {Object} the last item in the collection..
  10368. */
  10369. last : function() {
  10370. return this.items[this.length - 1];
  10371. },
  10372. /**
  10373. * Collects all of the values of the given property and returns their sum
  10374. * @param {String} property The property to sum by
  10375. * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
  10376. * summing fields in records, where the fields are all stored inside the 'data' object
  10377. * @param {Number} [start=0] The record index to start at
  10378. * @param {Number} [end=-1] The record index to end at
  10379. * @return {Number} The total
  10380. */
  10381. sum: function(property, root, start, end) {
  10382. var values = this.extractValues(property, root),
  10383. length = values.length,
  10384. sum = 0,
  10385. i;
  10386. start = start || 0;
  10387. end = (end || end === 0) ? end : length - 1;
  10388. for (i = start; i <= end; i++) {
  10389. sum += values[i];
  10390. }
  10391. return sum;
  10392. },
  10393. /**
  10394. * Collects unique values of a particular property in this MixedCollection
  10395. * @param {String} property The property to collect on
  10396. * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
  10397. * summing fields in records, where the fields are all stored inside the 'data' object
  10398. * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
  10399. * @return {Array} The unique values
  10400. */
  10401. collect: function(property, root, allowNull) {
  10402. var values = this.extractValues(property, root),
  10403. length = values.length,
  10404. hits = {},
  10405. unique = [],
  10406. value, strValue, i;
  10407. for (i = 0; i < length; i++) {
  10408. value = values[i];
  10409. strValue = String(value);
  10410. if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
  10411. hits[strValue] = true;
  10412. unique.push(value);
  10413. }
  10414. }
  10415. return unique;
  10416. },
  10417. /**
  10418. * @private
  10419. * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
  10420. * functions like sum and collect.
  10421. * @param {String} property The property to extract
  10422. * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
  10423. * extracting field data from Model instances, where the fields are stored inside the 'data' object
  10424. * @return {Array} The extracted values
  10425. */
  10426. extractValues: function(property, root) {
  10427. var values = this.items;
  10428. if (root) {
  10429. values = Ext.Array.pluck(values, root);
  10430. }
  10431. return Ext.Array.pluck(values, property);
  10432. },
  10433. /**
  10434. * Returns a range of items in this collection
  10435. * @param {Number} startIndex (optional) The starting index. Defaults to 0.
  10436. * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
  10437. * @return {Array} An array of items
  10438. */
  10439. getRange : function(start, end){
  10440. var me = this,
  10441. items = me.items,
  10442. range = [],
  10443. i;
  10444. if (items.length < 1) {
  10445. return range;
  10446. }
  10447. start = start || 0;
  10448. end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
  10449. if (start <= end) {
  10450. for (i = start; i <= end; i++) {
  10451. range[range.length] = items[i];
  10452. }
  10453. } else {
  10454. for (i = start; i >= end; i--) {
  10455. range[range.length] = items[i];
  10456. }
  10457. }
  10458. return range;
  10459. },
  10460. /**
  10461. * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
  10462. * property/value pair with optional parameters for substring matching and case sensitivity. See
  10463. * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
  10464. * MixedCollection can be easily filtered by property like this:</p>
  10465. <pre><code>
  10466. //create a simple store with a few people defined
  10467. var people = new Ext.util.MixedCollection();
  10468. people.addAll([
  10469. {id: 1, age: 25, name: 'Ed'},
  10470. {id: 2, age: 24, name: 'Tommy'},
  10471. {id: 3, age: 24, name: 'Arne'},
  10472. {id: 4, age: 26, name: 'Aaron'}
  10473. ]);
  10474. //a new MixedCollection containing only the items where age == 24
  10475. var middleAged = people.filter('age', 24);
  10476. </code></pre>
  10477. *
  10478. *
  10479. * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
  10480. * @param {String/RegExp} value Either string that the property values
  10481. * should start with or a RegExp to test against the property
  10482. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
  10483. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
  10484. * @return {Ext.util.MixedCollection} The new filtered collection
  10485. */
  10486. filter : function(property, value, anyMatch, caseSensitive) {
  10487. var filters = [],
  10488. filterFn;
  10489. //support for the simple case of filtering by property/value
  10490. if (Ext.isString(property)) {
  10491. filters.push(new Ext.util.Filter({
  10492. property : property,
  10493. value : value,
  10494. anyMatch : anyMatch,
  10495. caseSensitive: caseSensitive
  10496. }));
  10497. } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
  10498. filters = filters.concat(property);
  10499. }
  10500. //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
  10501. //so here we construct a function that combines these filters by ANDing them together
  10502. filterFn = function(record) {
  10503. var isMatch = true,
  10504. length = filters.length,
  10505. i;
  10506. for (i = 0; i < length; i++) {
  10507. var filter = filters[i],
  10508. fn = filter.filterFn,
  10509. scope = filter.scope;
  10510. isMatch = isMatch && fn.call(scope, record);
  10511. }
  10512. return isMatch;
  10513. };
  10514. return this.filterBy(filterFn);
  10515. },
  10516. /**
  10517. * Filter by a function. Returns a <i>new</i> collection that has been filtered.
  10518. * The passed function will be called with each object in the collection.
  10519. * If the function returns true, the value is included otherwise it is filtered.
  10520. * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
  10521. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
  10522. * @return {Ext.util.MixedCollection} The new filtered collection
  10523. */
  10524. filterBy : function(fn, scope) {
  10525. var me = this,
  10526. newMC = new this.self(),
  10527. keys = me.keys,
  10528. items = me.items,
  10529. length = items.length,
  10530. i;
  10531. newMC.getKey = me.getKey;
  10532. for (i = 0; i < length; i++) {
  10533. if (fn.call(scope || me, items[i], keys[i])) {
  10534. newMC.add(keys[i], items[i]);
  10535. }
  10536. }
  10537. return newMC;
  10538. },
  10539. /**
  10540. * Finds the index of the first matching object in this collection by a specific property/value.
  10541. * @param {String} property The name of a property on your objects.
  10542. * @param {String/RegExp} value A string that the property values
  10543. * should start with or a RegExp to test against the property.
  10544. * @param {Number} [start=0] The index to start searching at.
  10545. * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
  10546. * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
  10547. * @return {Number} The matched index or -1
  10548. */
  10549. findIndex : function(property, value, start, anyMatch, caseSensitive){
  10550. if(Ext.isEmpty(value, false)){
  10551. return -1;
  10552. }
  10553. value = this.createValueMatcher(value, anyMatch, caseSensitive);
  10554. return this.findIndexBy(function(o){
  10555. return o && value.test(o[property]);
  10556. }, null, start);
  10557. },
  10558. /**
  10559. * Find the index of the first matching object in this collection by a function.
  10560. * If the function returns <i>true</i> it is considered a match.
  10561. * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
  10562. * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
  10563. * @param {Number} [start=0] The index to start searching at.
  10564. * @return {Number} The matched index or -1
  10565. */
  10566. findIndexBy : function(fn, scope, start){
  10567. var me = this,
  10568. keys = me.keys,
  10569. items = me.items,
  10570. i = start || 0,
  10571. len = items.length;
  10572. for (; i < len; i++) {
  10573. if (fn.call(scope || me, items[i], keys[i])) {
  10574. return i;
  10575. }
  10576. }
  10577. return -1;
  10578. },
  10579. /**
  10580. * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
  10581. * and by Ext.data.Store#filter
  10582. * @private
  10583. * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
  10584. * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
  10585. * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
  10586. * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
  10587. */
  10588. createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
  10589. if (!value.exec) { // not a regex
  10590. var er = Ext.String.escapeRegex;
  10591. value = String(value);
  10592. if (anyMatch === true) {
  10593. value = er(value);
  10594. } else {
  10595. value = '^' + er(value);
  10596. if (exactMatch === true) {
  10597. value += '$';
  10598. }
  10599. }
  10600. value = new RegExp(value, caseSensitive ? '' : 'i');
  10601. }
  10602. return value;
  10603. },
  10604. /**
  10605. * Creates a shallow copy of this collection
  10606. * @return {Ext.util.MixedCollection}
  10607. */
  10608. clone : function() {
  10609. var me = this,
  10610. copy = new this.self(),
  10611. keys = me.keys,
  10612. items = me.items,
  10613. i = 0,
  10614. len = items.length;
  10615. for(; i < len; i++){
  10616. copy.add(keys[i], items[i]);
  10617. }
  10618. copy.getKey = me.getKey;
  10619. return copy;
  10620. }
  10621. });
  10622. /**
  10623. * @docauthor Tommy Maintz <tommy@sencha.com>
  10624. *
  10625. * 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}.
  10626. *
  10627. * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
  10628. * is more likely you will want to use one of the component classes that import this mixin, such as
  10629. * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
  10630. */
  10631. Ext.define("Ext.util.Sortable", {
  10632. /**
  10633. * @property {Boolean} isSortable
  10634. * `true` in this class to identify an objact as an instantiated Sortable, or subclass thereof.
  10635. */
  10636. isSortable: true,
  10637. /**
  10638. * @property {String} defaultSortDirection
  10639. * The default sort direction to use if one is not specified.
  10640. */
  10641. defaultSortDirection: "ASC",
  10642. requires: [
  10643. 'Ext.util.Sorter'
  10644. ],
  10645. /**
  10646. * @property {String} sortRoot
  10647. * The property in each item that contains the data to sort.
  10648. */
  10649. /**
  10650. * Performs initialization of this mixin. Component classes using this mixin should call this method during their
  10651. * own initialization.
  10652. */
  10653. initSortable: function() {
  10654. var me = this,
  10655. sorters = me.sorters;
  10656. /**
  10657. * @property {Ext.util.MixedCollection} sorters
  10658. * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
  10659. */
  10660. me.sorters = new Ext.util.AbstractMixedCollection(false, function(item) {
  10661. return item.id || item.property;
  10662. });
  10663. if (sorters) {
  10664. me.sorters.addAll(me.decodeSorters(sorters));
  10665. }
  10666. },
  10667. /**
  10668. * Sorts the data in the Store by one or more of its properties. Example usage:
  10669. *
  10670. * //sort by a single field
  10671. * myStore.sort('myField', 'DESC');
  10672. *
  10673. * //sorting by multiple fields
  10674. * myStore.sort([
  10675. * {
  10676. * property : 'age',
  10677. * direction: 'ASC'
  10678. * },
  10679. * {
  10680. * property : 'name',
  10681. * direction: 'DESC'
  10682. * }
  10683. * ]);
  10684. *
  10685. * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
  10686. * the actual sorting to its internal {@link Ext.util.MixedCollection}.
  10687. *
  10688. * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
  10689. *
  10690. * store.sort('myField');
  10691. * store.sort('myField');
  10692. *
  10693. * Is equivalent to this code, because Store handles the toggling automatically:
  10694. *
  10695. * store.sort('myField', 'ASC');
  10696. * store.sort('myField', 'DESC');
  10697. *
  10698. * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
  10699. * {@link Ext.data.Model Model}, or an array of sorter configurations.
  10700. * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
  10701. * @return {Ext.util.Sorter[]}
  10702. */
  10703. sort: function(sorters, direction, where, doSort) {
  10704. var me = this,
  10705. sorter, sorterFn,
  10706. newSorters;
  10707. if (Ext.isArray(sorters)) {
  10708. doSort = where;
  10709. where = direction;
  10710. newSorters = sorters;
  10711. }
  10712. else if (Ext.isObject(sorters)) {
  10713. doSort = where;
  10714. where = direction;
  10715. newSorters = [sorters];
  10716. }
  10717. else if (Ext.isString(sorters)) {
  10718. sorter = me.sorters.get(sorters);
  10719. if (!sorter) {
  10720. sorter = {
  10721. property : sorters,
  10722. direction: direction
  10723. };
  10724. newSorters = [sorter];
  10725. }
  10726. else if (direction === undefined) {
  10727. sorter.toggle();
  10728. }
  10729. else {
  10730. sorter.setDirection(direction);
  10731. }
  10732. }
  10733. if (newSorters && newSorters.length) {
  10734. newSorters = me.decodeSorters(newSorters);
  10735. if (Ext.isString(where)) {
  10736. if (where === 'prepend') {
  10737. sorters = me.sorters.clone().items;
  10738. me.sorters.clear();
  10739. me.sorters.addAll(newSorters);
  10740. me.sorters.addAll(sorters);
  10741. }
  10742. else {
  10743. me.sorters.addAll(newSorters);
  10744. }
  10745. }
  10746. else {
  10747. me.sorters.clear();
  10748. me.sorters.addAll(newSorters);
  10749. }
  10750. }
  10751. if (doSort !== false) {
  10752. me.onBeforeSort(newSorters);
  10753. sorters = me.sorters.items;
  10754. if (sorters.length) {
  10755. // Sort using a generated sorter function which combines all of the Sorters passed
  10756. me.doSort(me.generateComparator());
  10757. }
  10758. }
  10759. return sorters;
  10760. },
  10761. /**
  10762. * <p>Returns a comparator function which compares two items and returns -1, 0, or 1 depending
  10763. * on the currently defined set of {@link #sorters}.</p>
  10764. * <p>If there are no {@link #sorters} defined, it returns a function which returns <code>0</code> meaning that no sorting will occur.</p>
  10765. */
  10766. generateComparator: function() {
  10767. return (this.sorters.items.length) ? (function(sorters) {
  10768. return function(r1, r2) {
  10769. var result = sorters[0].sort(r1, r2),
  10770. length = sorters.length,
  10771. i;
  10772. // if we have more than one sorter, OR any additional sorter functions together
  10773. for (i = 1; i < length; i++) {
  10774. result = result || sorters[i].sort.call(this, r1, r2);
  10775. }
  10776. return result;
  10777. };
  10778. })(this.sorters.items) : function() {
  10779. return 0;
  10780. };
  10781. },
  10782. onBeforeSort: Ext.emptyFn,
  10783. /**
  10784. * @private
  10785. * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
  10786. * @param {Object[]} sorters The sorters array
  10787. * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
  10788. */
  10789. decodeSorters: function(sorters) {
  10790. if (!Ext.isArray(sorters)) {
  10791. if (sorters === undefined) {
  10792. sorters = [];
  10793. } else {
  10794. sorters = [sorters];
  10795. }
  10796. }
  10797. var length = sorters.length,
  10798. Sorter = Ext.util.Sorter,
  10799. fields = this.model ? this.model.prototype.fields : null,
  10800. field,
  10801. config, i;
  10802. for (i = 0; i < length; i++) {
  10803. config = sorters[i];
  10804. if (!(config instanceof Sorter)) {
  10805. if (Ext.isString(config)) {
  10806. config = {
  10807. property: config
  10808. };
  10809. }
  10810. Ext.applyIf(config, {
  10811. root : this.sortRoot,
  10812. direction: "ASC"
  10813. });
  10814. //support for 3.x style sorters where a function can be defined as 'fn'
  10815. if (config.fn) {
  10816. config.sorterFn = config.fn;
  10817. }
  10818. //support a function to be passed as a sorter definition
  10819. if (typeof config == 'function') {
  10820. config = {
  10821. sorterFn: config
  10822. };
  10823. }
  10824. // ensure sortType gets pushed on if necessary
  10825. if (fields && !config.transform) {
  10826. field = fields.get(config.property);
  10827. config.transform = field ? field.sortType : undefined;
  10828. }
  10829. sorters[i] = new Ext.util.Sorter(config);
  10830. }
  10831. }
  10832. return sorters;
  10833. },
  10834. getSorters: function() {
  10835. return this.sorters.items;
  10836. }
  10837. });
  10838. /**
  10839. * @class Ext.util.MixedCollection
  10840. * <p>
  10841. * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
  10842. * must be unique, the same key cannot exist twice. This collection is ordered, items in the
  10843. * collection can be accessed by index or via the key. Newly added items are added to
  10844. * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
  10845. * is heavier and provides more functionality. Sample usage:
  10846. * <pre><code>
  10847. var coll = new Ext.util.MixedCollection();
  10848. coll.add('key1', 'val1');
  10849. coll.add('key2', 'val2');
  10850. coll.add('key3', 'val3');
  10851. console.log(coll.get('key1')); // prints 'val1'
  10852. console.log(coll.indexOfKey('key3')); // prints 2
  10853. * </code></pre>
  10854. *
  10855. * <p>
  10856. * The MixedCollection also has support for sorting and filtering of the values in the collection.
  10857. * <pre><code>
  10858. var coll = new Ext.util.MixedCollection();
  10859. coll.add('key1', 100);
  10860. coll.add('key2', -100);
  10861. coll.add('key3', 17);
  10862. coll.add('key4', 0);
  10863. var biggerThanZero = coll.filterBy(function(value){
  10864. return value > 0;
  10865. });
  10866. console.log(biggerThanZero.getCount()); // prints 2
  10867. * </code></pre>
  10868. * </p>
  10869. */
  10870. Ext.define('Ext.util.MixedCollection', {
  10871. extend: 'Ext.util.AbstractMixedCollection',
  10872. mixins: {
  10873. sortable: 'Ext.util.Sortable'
  10874. },
  10875. /**
  10876. * Creates new MixedCollection.
  10877. * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
  10878. * function should add function references to the collection. Defaults to
  10879. * <tt>false</tt>.
  10880. * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
  10881. * and return the key value for that item. This is used when available to look up the key on items that
  10882. * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
  10883. * equivalent to providing an implementation for the {@link #getKey} method.
  10884. */
  10885. constructor: function() {
  10886. var me = this;
  10887. me.callParent(arguments);
  10888. me.addEvents('sort');
  10889. me.mixins.sortable.initSortable.call(me);
  10890. },
  10891. doSort: function(sorterFn) {
  10892. this.sortBy(sorterFn);
  10893. },
  10894. /**
  10895. * @private
  10896. * Performs the actual sorting based on a direction and a sorting function. Internally,
  10897. * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
  10898. * the sorted array data back into this.items and this.keys
  10899. * @param {String} property Property to sort by ('key', 'value', or 'index')
  10900. * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
  10901. * @param {Function} fn (optional) Comparison function that defines the sort order.
  10902. * Defaults to sorting by numeric value.
  10903. */
  10904. _sort : function(property, dir, fn){
  10905. var me = this,
  10906. i, len,
  10907. dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
  10908. //this is a temporary array used to apply the sorting function
  10909. c = [],
  10910. keys = me.keys,
  10911. items = me.items;
  10912. //default to a simple sorter function if one is not provided
  10913. fn = fn || function(a, b) {
  10914. return a - b;
  10915. };
  10916. //copy all the items into a temporary array, which we will sort
  10917. for(i = 0, len = items.length; i < len; i++){
  10918. c[c.length] = {
  10919. key : keys[i],
  10920. value: items[i],
  10921. index: i
  10922. };
  10923. }
  10924. //sort the temporary array
  10925. Ext.Array.sort(c, function(a, b){
  10926. var v = fn(a[property], b[property]) * dsc;
  10927. if(v === 0){
  10928. v = (a.index < b.index ? -1 : 1);
  10929. }
  10930. return v;
  10931. });
  10932. //copy the temporary array back into the main this.items and this.keys objects
  10933. for(i = 0, len = c.length; i < len; i++){
  10934. items[i] = c[i].value;
  10935. keys[i] = c[i].key;
  10936. }
  10937. me.fireEvent('sort', me);
  10938. },
  10939. /**
  10940. * Sorts the collection by a single sorter function
  10941. * @param {Function} sorterFn The function to sort by
  10942. */
  10943. sortBy: function(sorterFn) {
  10944. var me = this,
  10945. items = me.items,
  10946. keys = me.keys,
  10947. length = items.length,
  10948. temp = [],
  10949. i;
  10950. //first we create a copy of the items array so that we can sort it
  10951. for (i = 0; i < length; i++) {
  10952. temp[i] = {
  10953. key : keys[i],
  10954. value: items[i],
  10955. index: i
  10956. };
  10957. }
  10958. Ext.Array.sort(temp, function(a, b) {
  10959. var v = sorterFn(a.value, b.value);
  10960. if (v === 0) {
  10961. v = (a.index < b.index ? -1 : 1);
  10962. }
  10963. return v;
  10964. });
  10965. //copy the temporary array back into the main this.items and this.keys objects
  10966. for (i = 0; i < length; i++) {
  10967. items[i] = temp[i].value;
  10968. keys[i] = temp[i].key;
  10969. }
  10970. me.fireEvent('sort', me, items, keys);
  10971. },
  10972. /**
  10973. * Calculates the insertion index of the new item based upon the comparison function passed, or the current sort order.
  10974. * @param {Object} newItem The new object to find the insertion position of.
  10975. * @param {Function} [sorterFn] The function to sort by. This is the same as the sorting function
  10976. * passed to {@link #sortBy}. It accepts 2 items from this MixedCollection, and returns -1 0, or 1
  10977. * depending on the relative sort positions of the 2 compared items.
  10978. *
  10979. * If omitted, a function {@link #generateComparator generated} from the currently defined set of
  10980. * {@link #sorters} will be used.
  10981. *
  10982. * @return {Number} The insertion point to add the new item into this MixedCollection at using {@link #insert}
  10983. */
  10984. findInsertionIndex: function(newItem, sorterFn) {
  10985. var me = this,
  10986. items = me.items,
  10987. start = 0,
  10988. end = items.length - 1,
  10989. middle,
  10990. comparison;
  10991. if (!sorterFn) {
  10992. sorterFn = me.generateComparator();
  10993. }
  10994. while (start <= end) {
  10995. middle = (start + end) >> 1;
  10996. comparison = sorterFn(newItem, items[middle]);
  10997. if (comparison >= 0) {
  10998. start = middle + 1;
  10999. } else if (comparison < 0) {
  11000. end = middle - 1;
  11001. }
  11002. }
  11003. return start;
  11004. },
  11005. /**
  11006. * Reorders each of the items based on a mapping from old index to new index. Internally this
  11007. * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
  11008. * @param {Object} mapping Mapping from old item index to new item index
  11009. */
  11010. reorder: function(mapping) {
  11011. var me = this,
  11012. items = me.items,
  11013. index = 0,
  11014. length = items.length,
  11015. order = [],
  11016. remaining = [],
  11017. oldIndex;
  11018. me.suspendEvents();
  11019. //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
  11020. for (oldIndex in mapping) {
  11021. order[mapping[oldIndex]] = items[oldIndex];
  11022. }
  11023. for (index = 0; index < length; index++) {
  11024. if (mapping[index] == undefined) {
  11025. remaining.push(items[index]);
  11026. }
  11027. }
  11028. for (index = 0; index < length; index++) {
  11029. if (order[index] == undefined) {
  11030. order[index] = remaining.shift();
  11031. }
  11032. }
  11033. me.clear();
  11034. me.addAll(order);
  11035. me.resumeEvents();
  11036. me.fireEvent('sort', me);
  11037. },
  11038. /**
  11039. * Sorts this collection by <b>key</b>s.
  11040. * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
  11041. * @param {Function} fn (optional) Comparison function that defines the sort order.
  11042. * Defaults to sorting by case insensitive string.
  11043. */
  11044. sortByKey : function(dir, fn){
  11045. this._sort('key', dir, fn || function(a, b){
  11046. var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
  11047. return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  11048. });
  11049. }
  11050. });
  11051. /**
  11052. * @docauthor Evan Trimboli <evan@sencha.com>
  11053. *
  11054. * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
  11055. * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
  11056. * referred to via it's identifier:
  11057. *
  11058. * Ext.create('Ext.data.Store', {
  11059. * model: 'SomeModel',
  11060. * storeId: 'myStore'
  11061. * });
  11062. *
  11063. * var store = Ext.data.StoreManager.lookup('myStore');
  11064. *
  11065. * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
  11066. *
  11067. * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
  11068. * it with any Component that consumes data from a store:
  11069. *
  11070. * Ext.create('Ext.data.Store', {
  11071. * model: 'SomeModel',
  11072. * storeId: 'myStore'
  11073. * });
  11074. *
  11075. * Ext.create('Ext.view.View', {
  11076. * store: 'myStore',
  11077. * // other configuration here
  11078. * });
  11079. *
  11080. */
  11081. Ext.define('Ext.data.StoreManager', {
  11082. extend: 'Ext.util.MixedCollection',
  11083. alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
  11084. singleton: true,
  11085. uses: ['Ext.data.ArrayStore'],
  11086. /**
  11087. * @cfg {Object} listeners
  11088. * @private
  11089. */
  11090. /**
  11091. * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
  11092. * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
  11093. * @param {Ext.data.Store...} stores Any number of Store instances
  11094. */
  11095. register : function() {
  11096. for (var i = 0, s; (s = arguments[i]); i++) {
  11097. this.add(s);
  11098. }
  11099. },
  11100. /**
  11101. * Unregisters one or more Stores with the StoreManager
  11102. * @param {String/Object...} stores Any number of Store instances or ID-s
  11103. */
  11104. unregister : function() {
  11105. for (var i = 0, s; (s = arguments[i]); i++) {
  11106. this.remove(this.lookup(s));
  11107. }
  11108. },
  11109. /**
  11110. * Gets a registered Store by id
  11111. * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
  11112. * @return {Ext.data.Store}
  11113. */
  11114. lookup : function(store) {
  11115. // handle the case when we are given an array or an array of arrays.
  11116. if (Ext.isArray(store)) {
  11117. var fields = ['field1'],
  11118. expand = !Ext.isArray(store[0]),
  11119. data = store,
  11120. i,
  11121. len;
  11122. if(expand){
  11123. data = [];
  11124. for (i = 0, len = store.length; i < len; ++i) {
  11125. data.push([store[i]]);
  11126. }
  11127. } else {
  11128. for(i = 2, len = store[0].length; i <= len; ++i){
  11129. fields.push('field' + i);
  11130. }
  11131. }
  11132. return new Ext.data.ArrayStore({
  11133. data : data,
  11134. fields: fields,
  11135. autoDestroy: true,
  11136. autoCreated: true,
  11137. expanded: expand
  11138. });
  11139. }
  11140. if (Ext.isString(store)) {
  11141. // store id
  11142. return this.get(store);
  11143. } else {
  11144. // store instance or store config
  11145. return Ext.data.AbstractStore.create(store);
  11146. }
  11147. },
  11148. // getKey implementation for MixedCollection
  11149. getKey : function(o) {
  11150. return o.storeId;
  11151. }
  11152. }, function() {
  11153. /**
  11154. * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}.
  11155. * Sample usage:
  11156. *
  11157. * Ext.regStore('AllUsers', {
  11158. * model: 'User'
  11159. * });
  11160. *
  11161. * // the store can now easily be used throughout the application
  11162. * new Ext.List({
  11163. * store: 'AllUsers',
  11164. * ... other config
  11165. * });
  11166. *
  11167. * @param {String} id The id to set on the new store
  11168. * @param {Object} config The store config
  11169. * @member Ext
  11170. * @method regStore
  11171. */
  11172. Ext.regStore = function(name, config) {
  11173. var store;
  11174. if (Ext.isObject(name)) {
  11175. config = name;
  11176. } else {
  11177. config.storeId = name;
  11178. }
  11179. if (config instanceof Ext.data.Store) {
  11180. store = config;
  11181. } else {
  11182. store = new Ext.data.Store(config);
  11183. }
  11184. return Ext.data.StoreManager.register(store);
  11185. };
  11186. /**
  11187. * Shortcut to {@link Ext.data.StoreManager#lookup}.
  11188. * @member Ext
  11189. * @method getStore
  11190. * @inheritdoc Ext.data.StoreManager#lookup
  11191. */
  11192. Ext.getStore = function(name) {
  11193. return Ext.data.StoreManager.lookup(name);
  11194. };
  11195. });
  11196. /**
  11197. * @author Ed Spencer
  11198. * @class Ext.data.Errors
  11199. *
  11200. * <p>Wraps a collection of validation error responses and provides convenient functions for
  11201. * accessing and errors for specific fields.</p>
  11202. *
  11203. * <p>Usually this class does not need to be instantiated directly - instances are instead created
  11204. * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
  11205. *
  11206. <pre><code>
  11207. //validate some existing model instance - in this case it returned 2 failures messages
  11208. var errors = myModel.validate();
  11209. errors.isValid(); //false
  11210. errors.length; //2
  11211. errors.getByField('name'); // [{field: 'name', message: 'must be present'}]
  11212. errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
  11213. </code></pre>
  11214. */
  11215. Ext.define('Ext.data.Errors', {
  11216. extend: 'Ext.util.MixedCollection',
  11217. /**
  11218. * Returns true if there are no errors in the collection
  11219. * @return {Boolean}
  11220. */
  11221. isValid: function() {
  11222. return this.length === 0;
  11223. },
  11224. /**
  11225. * Returns all of the errors for the given field
  11226. * @param {String} fieldName The field to get errors for
  11227. * @return {Object[]} All errors for the given field
  11228. */
  11229. getByField: function(fieldName) {
  11230. var errors = [],
  11231. error, field, i;
  11232. for (i = 0; i < this.length; i++) {
  11233. error = this.items[i];
  11234. if (error.field == fieldName) {
  11235. errors.push(error);
  11236. }
  11237. }
  11238. return errors;
  11239. }
  11240. });
  11241. /**
  11242. * @class Ext.data.writer.Json
  11243. This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
  11244. The {@link #allowSingle} configuration can be set to false to force the records to always be
  11245. encoded in an array, even if there is only a single record being sent.
  11246. * @markdown
  11247. */
  11248. Ext.define('Ext.data.writer.Json', {
  11249. extend: 'Ext.data.writer.Writer',
  11250. alternateClassName: 'Ext.data.JsonWriter',
  11251. alias: 'writer.json',
  11252. /**
  11253. * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
  11254. * Example generated request, using root: 'records':
  11255. <pre><code>
  11256. {'records': [{name: 'my record'}, {name: 'another record'}]}
  11257. </code></pre>
  11258. */
  11259. root: undefined,
  11260. /**
  11261. * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
  11262. * The encode option should only be set to true when a {@link #root} is defined, because the values will be
  11263. * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
  11264. * sent to the server.
  11265. */
  11266. encode: false,
  11267. /**
  11268. * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
  11269. * one record being sent. When there is more than one record, they will always be encoded into an array.
  11270. * Defaults to <tt>true</tt>. Example:
  11271. * <pre><code>
  11272. // with allowSingle: true
  11273. "root": {
  11274. "first": "Mark",
  11275. "last": "Corrigan"
  11276. }
  11277. // with allowSingle: false
  11278. "root": [{
  11279. "first": "Mark",
  11280. "last": "Corrigan"
  11281. }]
  11282. * </code></pre>
  11283. */
  11284. allowSingle: true,
  11285. //inherit docs
  11286. writeRecords: function(request, data) {
  11287. var root = this.root;
  11288. if (this.allowSingle && data.length == 1) {
  11289. // convert to single object format
  11290. data = data[0];
  11291. }
  11292. if (this.encode) {
  11293. if (root) {
  11294. // sending as a param, need to encode
  11295. request.params[root] = Ext.encode(data);
  11296. } else {
  11297. Ext.Error.raise('Must specify a root when using encode');
  11298. }
  11299. } else {
  11300. // send as jsonData
  11301. request.jsonData = request.jsonData || {};
  11302. if (root) {
  11303. request.jsonData[root] = data;
  11304. } else {
  11305. request.jsonData = data;
  11306. }
  11307. }
  11308. return request;
  11309. }
  11310. });
  11311. /**
  11312. * @class Ext.state.Manager
  11313. * This is the global state manager. By default all components that are "state aware" check this class
  11314. * for state information if you don't pass them a custom state provider. In order for this class
  11315. * to be useful, it must be initialized with a provider when your application initializes. Example usage:
  11316. <pre><code>
  11317. // in your initialization function
  11318. init : function(){
  11319. Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
  11320. var win = new Window(...);
  11321. win.restoreState();
  11322. }
  11323. </code></pre>
  11324. * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
  11325. * there is a common interface that can be used without needing to refer to a specific provider instance
  11326. * in every component.
  11327. * @singleton
  11328. * @docauthor Evan Trimboli <evan@sencha.com>
  11329. */
  11330. Ext.define('Ext.state.Manager', {
  11331. singleton: true,
  11332. requires: ['Ext.state.Provider'],
  11333. constructor: function() {
  11334. this.provider = new Ext.state.Provider();
  11335. },
  11336. /**
  11337. * Configures the default state provider for your application
  11338. * @param {Ext.state.Provider} stateProvider The state provider to set
  11339. */
  11340. setProvider : function(stateProvider){
  11341. this.provider = stateProvider;
  11342. },
  11343. /**
  11344. * Returns the current value for a key
  11345. * @param {String} name The key name
  11346. * @param {Object} defaultValue The default value to return if the key lookup does not match
  11347. * @return {Object} The state data
  11348. */
  11349. get : function(key, defaultValue){
  11350. return this.provider.get(key, defaultValue);
  11351. },
  11352. /**
  11353. * Sets the value for a key
  11354. * @param {String} name The key name
  11355. * @param {Object} value The state data
  11356. */
  11357. set : function(key, value){
  11358. this.provider.set(key, value);
  11359. },
  11360. /**
  11361. * Clears a value from the state
  11362. * @param {String} name The key name
  11363. */
  11364. clear : function(key){
  11365. this.provider.clear(key);
  11366. },
  11367. /**
  11368. * Gets the currently configured state provider
  11369. * @return {Ext.state.Provider} The state provider
  11370. */
  11371. getProvider : function(){
  11372. return this.provider;
  11373. }
  11374. });
  11375. /**
  11376. * @class Ext.state.Stateful
  11377. * A mixin for being able to save the state of an object to an underlying
  11378. * {@link Ext.state.Provider}.
  11379. */
  11380. Ext.define('Ext.state.Stateful', {
  11381. /* Begin Definitions */
  11382. mixins: {
  11383. observable: 'Ext.util.Observable'
  11384. },
  11385. requires: ['Ext.state.Manager'],
  11386. /* End Definitions */
  11387. /**
  11388. * @cfg {Boolean} stateful
  11389. * <p>A flag which causes the object to attempt to restore the state of
  11390. * internal properties from a saved state on startup. The object must have
  11391. * a <code>{@link #stateId}</code> for state to be managed.
  11392. * Auto-generated ids are not guaranteed to be stable across page loads and
  11393. * cannot be relied upon to save and restore the same state for a object.<p>
  11394. * <p>For state saving to work, the state manager's provider must have been
  11395. * set to an implementation of {@link Ext.state.Provider} which overrides the
  11396. * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
  11397. * methods to save and recall name/value pairs. A built-in implementation,
  11398. * {@link Ext.state.CookieProvider} is available.</p>
  11399. * <p>To set the state provider for the current page:</p>
  11400. * <pre><code>
  11401. Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
  11402. expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
  11403. }));
  11404. * </code></pre>
  11405. * <p>A stateful object attempts to save state when one of the events
  11406. * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
  11407. * <p>To save state, a stateful object first serializes its state by
  11408. * calling <b><code>{@link #getState}</code></b>. By default, this function does
  11409. * nothing. The developer must provide an implementation which returns an
  11410. * object hash which represents the restorable state of the object.</p>
  11411. * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
  11412. * which uses the configured {@link Ext.state.Provider} to save the object
  11413. * keyed by the <code>{@link #stateId}</code>.</p>
  11414. * <p>During construction, a stateful object attempts to <i>restore</i>
  11415. * its state by calling {@link Ext.state.Manager#get} passing the
  11416. * <code>{@link #stateId}</code></p>
  11417. * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
  11418. * The default implementation of <code>{@link #applyState}</code> simply copies
  11419. * properties into the object, but a developer may override this to support
  11420. * more behaviour.</p>
  11421. * <p>You can perform extra processing on state save and restore by attaching
  11422. * handlers to the {@link #beforestaterestore}, {@link #staterestore},
  11423. * {@link #beforestatesave} and {@link #statesave} events.</p>
  11424. */
  11425. stateful: false,
  11426. /**
  11427. * @cfg {String} stateId
  11428. * The unique id for this object to use for state management purposes.
  11429. * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
  11430. */
  11431. /**
  11432. * @cfg {String[]} stateEvents
  11433. * <p>An array of events that, when fired, should trigger this object to
  11434. * save its state. Defaults to none. <code>stateEvents</code> may be any type
  11435. * of event supported by this object, including browser or custom events
  11436. * (e.g., <tt>['click', 'customerchange']</tt>).</p>
  11437. * <p>See <code>{@link #stateful}</code> for an explanation of saving and
  11438. * restoring object state.</p>
  11439. */
  11440. /**
  11441. * @cfg {Number} saveDelay
  11442. * A buffer to be applied if many state events are fired within a short period.
  11443. */
  11444. saveDelay: 100,
  11445. constructor: function(config) {
  11446. var me = this;
  11447. config = config || {};
  11448. if (config.stateful !== undefined) {
  11449. me.stateful = config.stateful;
  11450. }
  11451. if (config.saveDelay !== undefined) {
  11452. me.saveDelay = config.saveDelay;
  11453. }
  11454. me.stateId = me.stateId || config.stateId;
  11455. if (!me.stateEvents) {
  11456. me.stateEvents = [];
  11457. }
  11458. if (config.stateEvents) {
  11459. me.stateEvents.concat(config.stateEvents);
  11460. }
  11461. this.addEvents(
  11462. /**
  11463. * @event beforestaterestore
  11464. * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
  11465. * @param {Ext.state.Stateful} this
  11466. * @param {Object} state The hash of state values returned from the StateProvider. If this
  11467. * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
  11468. * that simply copies property values into this object. The method maybe overriden to
  11469. * provide custom state restoration.
  11470. */
  11471. 'beforestaterestore',
  11472. /**
  11473. * @event staterestore
  11474. * Fires after the state of the object is restored.
  11475. * @param {Ext.state.Stateful} this
  11476. * @param {Object} state The hash of state values returned from the StateProvider. This is passed
  11477. * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
  11478. * object. The method maybe overriden to provide custom state restoration.
  11479. */
  11480. 'staterestore',
  11481. /**
  11482. * @event beforestatesave
  11483. * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
  11484. * @param {Ext.state.Stateful} this
  11485. * @param {Object} state The hash of state values. This is determined by calling
  11486. * <b><tt>getState()</tt></b> on the object. This method must be provided by the
  11487. * developer to return whetever representation of state is required, by default, Ext.state.Stateful
  11488. * has a null implementation.
  11489. */
  11490. 'beforestatesave',
  11491. /**
  11492. * @event statesave
  11493. * Fires after the state of the object is saved to the configured state provider.
  11494. * @param {Ext.state.Stateful} this
  11495. * @param {Object} state The hash of state values. This is determined by calling
  11496. * <b><tt>getState()</tt></b> on the object. This method must be provided by the
  11497. * developer to return whetever representation of state is required, by default, Ext.state.Stateful
  11498. * has a null implementation.
  11499. */
  11500. 'statesave'
  11501. );
  11502. me.mixins.observable.constructor.call(me);
  11503. if (me.stateful !== false) {
  11504. me.addStateEvents(me.stateEvents);
  11505. me.initState();
  11506. }
  11507. },
  11508. /**
  11509. * Add events that will trigger the state to be saved. If the first argument is an
  11510. * array, each element of that array is the name of a state event. Otherwise, each
  11511. * argument passed to this method is the name of a state event.
  11512. *
  11513. * @param {String/String[]} events The event name or an array of event names.
  11514. */
  11515. addStateEvents: function (events) {
  11516. var me = this,
  11517. i, event, stateEventsByName;
  11518. if (me.stateful && me.getStateId()) {
  11519. if (typeof events == 'string') {
  11520. events = Array.prototype.slice.call(arguments, 0);
  11521. }
  11522. stateEventsByName = me.stateEventsByName || (me.stateEventsByName = {});
  11523. for (i = events.length; i--; ) {
  11524. event = events[i];
  11525. if (!stateEventsByName[event]) {
  11526. stateEventsByName[event] = 1;
  11527. me.on(event, me.onStateChange, me);
  11528. }
  11529. }
  11530. }
  11531. },
  11532. /**
  11533. * This method is called when any of the {@link #stateEvents} are fired.
  11534. * @private
  11535. */
  11536. onStateChange: function(){
  11537. var me = this,
  11538. delay = me.saveDelay,
  11539. statics, runner;
  11540. if (!me.stateful) {
  11541. return;
  11542. }
  11543. if (delay) {
  11544. if (!me.stateTask) {
  11545. statics = Ext.state.Stateful;
  11546. runner = statics.runner || (statics.runner = new Ext.util.TaskRunner());
  11547. me.stateTask = runner.newTask({
  11548. run: me.saveState,
  11549. scope: me,
  11550. interval: delay,
  11551. repeat: 1
  11552. });
  11553. }
  11554. me.stateTask.start();
  11555. } else {
  11556. me.saveState();
  11557. }
  11558. },
  11559. /**
  11560. * Saves the state of the object to the persistence store.
  11561. */
  11562. saveState: function() {
  11563. var me = this,
  11564. id = me.stateful && me.getStateId(),
  11565. hasListeners = me.hasListeners,
  11566. state;
  11567. if (id) {
  11568. state = me.getState() || {}; //pass along for custom interactions
  11569. if (!hasListeners.beforestatesave || me.fireEvent('beforestatesave', me, state) !== false) {
  11570. Ext.state.Manager.set(id, state);
  11571. if (hasListeners.statesave) {
  11572. me.fireEvent('statesave', me, state);
  11573. }
  11574. }
  11575. }
  11576. },
  11577. /**
  11578. * Gets the current state of the object. By default this function returns null,
  11579. * it should be overridden in subclasses to implement methods for getting the state.
  11580. * @return {Object} The current state
  11581. */
  11582. getState: function(){
  11583. return null;
  11584. },
  11585. /**
  11586. * Applies the state to the object. This should be overridden in subclasses to do
  11587. * more complex state operations. By default it applies the state properties onto
  11588. * the current object.
  11589. * @param {Object} state The state
  11590. */
  11591. applyState: function(state) {
  11592. if (state) {
  11593. Ext.apply(this, state);
  11594. }
  11595. },
  11596. /**
  11597. * Gets the state id for this object.
  11598. * @return {String} The 'stateId' or the implicit 'id' specified by component configuration.
  11599. * @private
  11600. */
  11601. getStateId: function() {
  11602. var me = this;
  11603. return me.stateId || (me.autoGenId ? null : me.id);
  11604. },
  11605. /**
  11606. * Initializes the state of the object upon construction.
  11607. * @private
  11608. */
  11609. initState: function(){
  11610. var me = this,
  11611. id = me.stateful && me.getStateId(),
  11612. hasListeners = me.hasListeners,
  11613. state;
  11614. if (id) {
  11615. state = Ext.state.Manager.get(id);
  11616. if (state) {
  11617. state = Ext.apply({}, state);
  11618. if (!hasListeners.beforestaterestore || me.fireEvent('beforestaterestore', me, state) !== false) {
  11619. me.applyState(state);
  11620. if (hasListeners.staterestore) {
  11621. me.fireEvent('staterestore', me, state);
  11622. }
  11623. }
  11624. }
  11625. }
  11626. },
  11627. /**
  11628. * Conditionally saves a single property from this object to the given state object.
  11629. * The idea is to only save state which has changed from the initial state so that
  11630. * current software settings do not override future software settings. Only those
  11631. * values that are user-changed state should be saved.
  11632. *
  11633. * @param {String} propName The name of the property to save.
  11634. * @param {Object} state The state object in to which to save the property.
  11635. * @param {String} stateName (optional) The name to use for the property in state.
  11636. * @return {Boolean} True if the property was saved, false if not.
  11637. */
  11638. savePropToState: function (propName, state, stateName) {
  11639. var me = this,
  11640. value = me[propName],
  11641. config = me.initialConfig;
  11642. if (me.hasOwnProperty(propName)) {
  11643. if (!config || config[propName] !== value) {
  11644. if (state) {
  11645. state[stateName || propName] = value;
  11646. }
  11647. return true;
  11648. }
  11649. }
  11650. return false;
  11651. },
  11652. /**
  11653. * Gathers additional named properties of the instance and adds their current values
  11654. * to the passed state object.
  11655. * @param {String/String[]} propNames The name (or array of names) of the property to save.
  11656. * @param {Object} state The state object in to which to save the property values.
  11657. * @return {Object} state
  11658. */
  11659. savePropsToState: function (propNames, state) {
  11660. var me = this,
  11661. i, n;
  11662. if (typeof propNames == 'string') {
  11663. me.savePropToState(propNames, state);
  11664. } else {
  11665. for (i = 0, n = propNames.length; i < n; ++i) {
  11666. me.savePropToState(propNames[i], state);
  11667. }
  11668. }
  11669. return state;
  11670. },
  11671. /**
  11672. * Destroys this stateful object.
  11673. */
  11674. destroy: function(){
  11675. var me = this,
  11676. task = me.stateTask;
  11677. if (task) {
  11678. task.destroy();
  11679. me.stateTask = null;
  11680. }
  11681. me.clearListeners();
  11682. }
  11683. });
  11684. /**
  11685. * An abstract base class which provides shared methods for Components across the Sencha product line.
  11686. *
  11687. * Please refer to sub class's documentation
  11688. * @private
  11689. */
  11690. Ext.define('Ext.AbstractComponent', {
  11691. /* Begin Definitions */
  11692. requires: [
  11693. 'Ext.ComponentQuery',
  11694. 'Ext.ComponentManager',
  11695. 'Ext.util.ProtoElement'
  11696. ],
  11697. mixins: {
  11698. observable: 'Ext.util.Observable',
  11699. animate: 'Ext.util.Animate',
  11700. elementCt: 'Ext.util.ElementContainer',
  11701. renderable: 'Ext.util.Renderable',
  11702. state: 'Ext.state.Stateful'
  11703. },
  11704. // The "uses" property specifies class which are used in an instantiated AbstractComponent.
  11705. // They do *not* have to be loaded before this class may be defined - that is what "requires" is for.
  11706. uses: [
  11707. 'Ext.PluginManager',
  11708. 'Ext.Element',
  11709. 'Ext.DomHelper',
  11710. 'Ext.XTemplate',
  11711. 'Ext.ComponentQuery',
  11712. 'Ext.ComponentLoader',
  11713. 'Ext.EventManager',
  11714. 'Ext.layout.Context',
  11715. 'Ext.layout.Layout',
  11716. 'Ext.layout.component.Auto',
  11717. 'Ext.LoadMask',
  11718. 'Ext.ZIndexManager'
  11719. ],
  11720. statics: {
  11721. AUTO_ID: 1000,
  11722. pendingLayouts: null,
  11723. layoutSuspendCount: 0,
  11724. cancelLayout: function(comp) {
  11725. var context = this.runningLayoutContext || this.pendingLayouts;
  11726. if (context) {
  11727. context.cancelComponent(comp);
  11728. }
  11729. },
  11730. flushLayouts: function () {
  11731. var me = this,
  11732. context = me.pendingLayouts;
  11733. if (context && context.invalidQueue.length) {
  11734. me.pendingLayouts = null;
  11735. me.runningLayoutContext = context;
  11736. context.hookMethods({
  11737. runComplete: function () {
  11738. // we need to release the layout queue before running any of the
  11739. // finishedLayout calls because they call afterComponentLayout
  11740. // which can re-enter by calling doLayout/doComponentLayout.
  11741. me.runningLayoutContext = null;
  11742. return this.callParent(); // not "me" here!
  11743. }
  11744. });
  11745. context.run();
  11746. }
  11747. },
  11748. resumeLayouts: function (flush) {
  11749. if (this.layoutSuspendCount && ! --this.layoutSuspendCount) {
  11750. if (flush) {
  11751. this.flushLayouts();
  11752. }
  11753. }
  11754. },
  11755. suspendLayouts: function () {
  11756. ++this.layoutSuspendCount;
  11757. },
  11758. updateLayout: function (comp, defer) {
  11759. var me = this,
  11760. running = me.runningLayoutContext,
  11761. pending;
  11762. if (running) {
  11763. running.queueInvalidate(comp);
  11764. } else {
  11765. pending = me.pendingLayouts || (me.pendingLayouts = new Ext.layout.Context());
  11766. pending.queueInvalidate(comp);
  11767. if (!defer && !me.layoutSuspendCount && !comp.isLayoutSuspended()) {
  11768. me.flushLayouts();
  11769. }
  11770. }
  11771. }
  11772. },
  11773. /* End Definitions */
  11774. /**
  11775. * @property {Boolean} isComponent
  11776. * `true` in this class to identify an objact as an instantiated Component, or subclass thereof.
  11777. */
  11778. isComponent: true,
  11779. /**
  11780. * @private
  11781. */
  11782. getAutoId: function() {
  11783. this.autoGenId = true;
  11784. return ++Ext.AbstractComponent.AUTO_ID;
  11785. },
  11786. deferLayouts: false,
  11787. /**
  11788. * @cfg {String} id
  11789. * The **unique id of this component instance.**
  11790. *
  11791. * It should not be necessary to use this configuration except for singleton objects in your application. Components
  11792. * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
  11793. *
  11794. * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
  11795. * which provides selector-based searching for Sencha Components analogous to DOM querying. The {@link
  11796. * Ext.container.Container Container} class contains {@link Ext.container.Container#down shortcut methods} to query
  11797. * its descendant Components by selector.
  11798. *
  11799. * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
  11800. * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
  11801. * component uniquely, and also to select sub-elements using this component's id as the parent.
  11802. *
  11803. * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
  11804. *
  11805. * **Note**: to access the container of a Component see `{@link #ownerCt}`.
  11806. *
  11807. * Defaults to an {@link #getId auto-assigned id}.
  11808. */
  11809. /**
  11810. * @property {Boolean} autoGenId
  11811. * `true` indicates an id was auto-generated rather than provided by configuration.
  11812. * @private
  11813. */
  11814. autoGenId: false,
  11815. /**
  11816. * @cfg {String} itemId
  11817. * An itemId can be used as an alternative way to get a reference to a component when no object reference is
  11818. * available. Instead of using an `{@link #id}` with {@link Ext}.{@link Ext#getCmp getCmp}, use `itemId` with
  11819. * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
  11820. * `itemId`'s or {@link #id}'s. Since `itemId`'s are an index to the container's internal MixedCollection, the
  11821. * `itemId` is scoped locally to the container -- avoiding potential conflicts with {@link Ext.ComponentManager}
  11822. * which requires a **unique** `{@link #id}`.
  11823. *
  11824. * var c = new Ext.panel.Panel({ //
  11825. * {@link Ext.Component#height height}: 300,
  11826. * {@link #renderTo}: document.body,
  11827. * {@link Ext.container.Container#layout layout}: 'auto',
  11828. * {@link Ext.container.Container#cfg-items items}: [
  11829. * {
  11830. * itemId: 'p1',
  11831. * {@link Ext.panel.Panel#title title}: 'Panel 1',
  11832. * {@link Ext.Component#height height}: 150
  11833. * },
  11834. * {
  11835. * itemId: 'p2',
  11836. * {@link Ext.panel.Panel#title title}: 'Panel 2',
  11837. * {@link Ext.Component#height height}: 150
  11838. * }
  11839. * ]
  11840. * })
  11841. * p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
  11842. * p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
  11843. *
  11844. * Also see {@link #id}, `{@link Ext.container.Container#query}`, `{@link Ext.container.Container#down}` and
  11845. * `{@link Ext.container.Container#child}`.
  11846. *
  11847. * **Note**: to access the container of an item see {@link #ownerCt}.
  11848. */
  11849. /**
  11850. * @property {Ext.Container} ownerCt
  11851. * This Component's owner {@link Ext.container.Container Container} (is set automatically
  11852. * when this Component is added to a Container).
  11853. *
  11854. * **Note**: to access items within the Container see {@link #itemId}.
  11855. * @readonly
  11856. */
  11857. /**
  11858. * @cfg {String/Object} autoEl
  11859. * A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
  11860. * encapsulate this Component.
  11861. *
  11862. * You do not normally need to specify this. For the base classes {@link Ext.Component} and
  11863. * {@link Ext.container.Container}, this defaults to **'div'**. The more complex Sencha classes use a more
  11864. * complex DOM structure specified by their own {@link #renderTpl}s.
  11865. *
  11866. * This is intended to allow the developer to create application-specific utility Components encapsulated by
  11867. * different DOM elements. Example usage:
  11868. *
  11869. * {
  11870. * xtype: 'component',
  11871. * autoEl: {
  11872. * tag: 'img',
  11873. * src: 'http://www.example.com/example.jpg'
  11874. * }
  11875. * }, {
  11876. * xtype: 'component',
  11877. * autoEl: {
  11878. * tag: 'blockquote',
  11879. * html: 'autoEl is cool!'
  11880. * }
  11881. * }, {
  11882. * xtype: 'container',
  11883. * autoEl: 'ul',
  11884. * cls: 'ux-unordered-list',
  11885. * items: {
  11886. * xtype: 'component',
  11887. * autoEl: 'li',
  11888. * html: 'First list item'
  11889. * }
  11890. * }
  11891. */
  11892. /**
  11893. * @cfg {Ext.XTemplate/String/String[]} renderTpl
  11894. * An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's encapsulating
  11895. * {@link #getEl Element}.
  11896. *
  11897. * You do not normally need to specify this. For the base classes {@link Ext.Component} and
  11898. * {@link Ext.container.Container}, this defaults to **`null`** which means that they will be initially rendered
  11899. * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch
  11900. * classes which use a more complex DOM structure, provide their own template definitions.
  11901. *
  11902. * This is intended to allow the developer to create application-specific utility Components with customized
  11903. * internal structure.
  11904. *
  11905. * Upon rendering, any created child elements may be automatically imported into object properties using the
  11906. * {@link #renderSelectors} and {@link #childEls} options.
  11907. * @protected
  11908. */
  11909. renderTpl: '{%this.renderContent(out,values)%}',
  11910. /**
  11911. * @cfg {Object} renderData
  11912. *
  11913. * The data used by {@link #renderTpl} in addition to the following property values of the component:
  11914. *
  11915. * - id
  11916. * - ui
  11917. * - uiCls
  11918. * - baseCls
  11919. * - componentCls
  11920. * - frame
  11921. *
  11922. * See {@link #renderSelectors} and {@link #childEls} for usage examples.
  11923. */
  11924. /**
  11925. * @cfg {Object} renderSelectors
  11926. * An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
  11927. * created by the render process.
  11928. *
  11929. * After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
  11930. * and the found Elements are added as properties to the Component using the `renderSelector` property name.
  11931. *
  11932. * For example, a Component which renderes a title and description into its element:
  11933. *
  11934. * Ext.create('Ext.Component', {
  11935. * renderTo: Ext.getBody(),
  11936. * renderTpl: [
  11937. * '<h1 class="title">{title}</h1>',
  11938. * '<p>{desc}</p>'
  11939. * ],
  11940. * renderData: {
  11941. * title: "Error",
  11942. * desc: "Something went wrong"
  11943. * },
  11944. * renderSelectors: {
  11945. * titleEl: 'h1.title',
  11946. * descEl: 'p'
  11947. * },
  11948. * listeners: {
  11949. * afterrender: function(cmp){
  11950. * // After rendering the component will have a titleEl and descEl properties
  11951. * cmp.titleEl.setStyle({color: "red"});
  11952. * }
  11953. * }
  11954. * });
  11955. *
  11956. * For a faster, but less flexible, alternative that achieves the same end result (properties for child elements on the
  11957. * Component after render), see {@link #childEls} and {@link #addChildEls}.
  11958. */
  11959. /**
  11960. * @cfg {Object[]} childEls
  11961. * An array describing the child elements of the Component. Each member of the array
  11962. * is an object with these properties:
  11963. *
  11964. * - `name` - The property name on the Component for the child element.
  11965. * - `itemId` - The id to combine with the Component's id that is the id of the child element.
  11966. * - `id` - The id of the child element.
  11967. *
  11968. * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
  11969. *
  11970. * For example, a Component which renders a title and body text:
  11971. *
  11972. * Ext.create('Ext.Component', {
  11973. * renderTo: Ext.getBody(),
  11974. * renderTpl: [
  11975. * '<h1 id="{id}-title">{title}</h1>',
  11976. * '<p>{msg}</p>',
  11977. * ],
  11978. * renderData: {
  11979. * title: "Error",
  11980. * msg: "Something went wrong"
  11981. * },
  11982. * childEls: ["title"],
  11983. * listeners: {
  11984. * afterrender: function(cmp){
  11985. * // After rendering the component will have a title property
  11986. * cmp.title.setStyle({color: "red"});
  11987. * }
  11988. * }
  11989. * });
  11990. *
  11991. * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
  11992. */
  11993. /**
  11994. * @cfg {String/HTMLElement/Ext.Element} renderTo
  11995. * Specify the id of the element, a DOM element or an existing Element that this component will be rendered into.
  11996. *
  11997. * **Notes:**
  11998. *
  11999. * Do *not* use this option if the Component is to be a child item of a {@link Ext.container.Container Container}.
  12000. * It is the responsibility of the {@link Ext.container.Container Container}'s
  12001. * {@link Ext.container.Container#layout layout manager} to render and manage its child items.
  12002. *
  12003. * When using this config, a call to render() is not required.
  12004. *
  12005. * See `{@link #render}` also.
  12006. */
  12007. /**
  12008. * @cfg {Boolean} frame
  12009. * Specify as `true` to have the Component inject framing elements within the Component at render time to provide a
  12010. * graphical rounded frame around the Component content.
  12011. *
  12012. * This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet
  12013. * Explorer prior to version 9 which do not support rounded corners natively.
  12014. *
  12015. * The extra space taken up by this framing is available from the read only property {@link #frameSize}.
  12016. */
  12017. /**
  12018. * @property {Object} frameSize
  12019. * @readonly
  12020. * Indicates the width of any framing elements which were added within the encapsulating element
  12021. * to provide graphical, rounded borders. See the {@link #frame} config.
  12022. *
  12023. * This is an object containing the frame width in pixels for all four sides of the Component containing the
  12024. * following properties:
  12025. *
  12026. * @property {Number} frameSize.top The width of the top framing element in pixels.
  12027. * @property {Number} frameSize.right The width of the right framing element in pixels.
  12028. * @property {Number} frameSize.bottom The width of the bottom framing element in pixels.
  12029. * @property {Number} frameSize.left The width of the left framing element in pixels.
  12030. */
  12031. /**
  12032. * @cfg {String/Object} componentLayout
  12033. * The sizing and positioning of a Component's internal Elements is the responsibility of the Component's layout
  12034. * manager which sizes a Component's internal structure in response to the Component being sized.
  12035. *
  12036. * Generally, developers will not use this configuration as all provided Components which need their internal
  12037. * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.
  12038. *
  12039. * The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component
  12040. * class which simply sizes the Component's encapsulating element to the height and width specified in the
  12041. * {@link #setSize} method.
  12042. */
  12043. /**
  12044. * @cfg {Ext.XTemplate/Ext.Template/String/String[]} tpl
  12045. * An {@link Ext.Template}, {@link Ext.XTemplate} or an array of strings to form an Ext.XTemplate. Used in
  12046. * conjunction with the `{@link #data}` and `{@link #tplWriteMode}` configurations.
  12047. */
  12048. /**
  12049. * @cfg {Object} data
  12050. * The initial set of data to apply to the `{@link #tpl}` to update the content area of the Component.
  12051. */
  12052. /**
  12053. * @cfg {String} xtype
  12054. * This property provides a shorter alternative to creating objects than using a full
  12055. * class name. Using `xtype` is the most common way to define component instances,
  12056. * especially in a container. For example, the items in a form containing text fields
  12057. * could be created explicitly like so:
  12058. *
  12059. * items: [
  12060. * Ext.create('Ext.form.field.Text', {
  12061. * fieldLabel: 'Foo'
  12062. * }),
  12063. * Ext.create('Ext.form.field.Text', {
  12064. * fieldLabel: 'Bar'
  12065. * }),
  12066. * Ext.create('Ext.form.field.Number', {
  12067. * fieldLabel: 'Num'
  12068. * })
  12069. * ]
  12070. *
  12071. * But by using `xtype`, the above becomes:
  12072. *
  12073. * items: [
  12074. * {
  12075. * xtype: 'textfield',
  12076. * fieldLabel: 'Foo'
  12077. * },
  12078. * {
  12079. * xtype: 'textfield',
  12080. * fieldLabel: 'Bar'
  12081. * },
  12082. * {
  12083. * xtype: 'numberfield',
  12084. * fieldLabel: 'Num'
  12085. * }
  12086. * ]
  12087. *
  12088. * When the `xtype` is common to many items, {@link Ext.container.AbstractContainer#defaultType}
  12089. * is another way to specify the `xtype` for all items that don't have an explicit `xtype`:
  12090. *
  12091. * defaultType: 'textfield',
  12092. * items: [
  12093. * { fieldLabel: 'Foo' },
  12094. * { fieldLabel: 'Bar' },
  12095. * { fieldLabel: 'Num', xtype: 'numberfield' }
  12096. * ]
  12097. *
  12098. * Each member of the `items` array is now just a "configuration object". These objects
  12099. * are used to create and configure component instances. A configuration object can be
  12100. * manually used to instantiate a component using {@link Ext#widget}:
  12101. *
  12102. * var text1 = Ext.create('Ext.form.field.Text', {
  12103. * fieldLabel: 'Foo'
  12104. * });
  12105. *
  12106. * // or alternatively:
  12107. *
  12108. * var text1 = Ext.widget({
  12109. * xtype: 'textfield',
  12110. * fieldLabel: 'Foo'
  12111. * });
  12112. *
  12113. * This conversion of configuration objects into instantiated components is done when
  12114. * a container is created as part of its {Ext.container.AbstractContainer#initComponent}
  12115. * process. As part of the same process, the `items` array is converted from its raw
  12116. * array form into a {@link Ext.util.MixedCollection} instance.
  12117. *
  12118. * You can define your own `xtype` on a custom {@link Ext.Component component} by specifying
  12119. * the `xtype` property in {@link Ext#define}. For example:
  12120. *
  12121. * Ext.define('MyApp.PressMeButton', {
  12122. * extend: 'Ext.button.Button',
  12123. * xtype: 'pressmebutton',
  12124. * text: 'Press Me'
  12125. * });
  12126. *
  12127. * Care should be taken when naming an `xtype` in a custom component because there is
  12128. * a single, shared scope for all xtypes. Third part components should consider using
  12129. * a prefix to avoid collisions.
  12130. *
  12131. * Ext.define('Foo.form.CoolButton', {
  12132. * extend: 'Ext.button.Button',
  12133. * xtype: 'ux-coolbutton',
  12134. * text: 'Cool!'
  12135. * });
  12136. */
  12137. /**
  12138. * @cfg {String} tplWriteMode
  12139. * The Ext.(X)Template method to use when updating the content area of the Component.
  12140. * See `{@link Ext.XTemplate#overwrite}` for information on default mode.
  12141. */
  12142. tplWriteMode: 'overwrite',
  12143. /**
  12144. * @cfg {String} [baseCls='x-component']
  12145. * The base CSS class to apply to this components's element. This will also be prepended to elements within this
  12146. * component like Panel's body will get a class x-panel-body. This means that if you create a subclass of Panel, and
  12147. * you want it to get all the Panels styling for the element and the body, you leave the baseCls x-panel and use
  12148. * componentCls to add specific styling for this component.
  12149. */
  12150. baseCls: Ext.baseCSSPrefix + 'component',
  12151. /**
  12152. * @cfg {String} componentCls
  12153. * CSS Class to be added to a components root level element to give distinction to it via styling.
  12154. */
  12155. /**
  12156. * @cfg {String} [cls='']
  12157. * An optional extra CSS class that will be added to this component's Element. This can be useful
  12158. * for adding customized styles to the component or any of its children using standard CSS rules.
  12159. */
  12160. /**
  12161. * @cfg {String} [overCls='']
  12162. * An optional extra CSS class that will be added to this component's Element when the mouse moves over the Element,
  12163. * and removed when the mouse moves out. This can be useful for adding customized 'active' or 'hover' styles to the
  12164. * component or any of its children using standard CSS rules.
  12165. */
  12166. /**
  12167. * @cfg {String} [disabledCls='x-item-disabled']
  12168. * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
  12169. */
  12170. disabledCls: Ext.baseCSSPrefix + 'item-disabled',
  12171. /**
  12172. * @cfg {String/String[]} ui
  12173. * A set style for a component. Can be a string or an Array of multiple strings (UIs)
  12174. */
  12175. ui: 'default',
  12176. /**
  12177. * @cfg {String[]} uiCls
  12178. * An array of of classNames which are currently applied to this component
  12179. * @private
  12180. */
  12181. uiCls: [],
  12182. /**
  12183. * @cfg {String/Object} style
  12184. * A custom style specification to be applied to this component's Element. Should be a valid argument to
  12185. * {@link Ext.Element#applyStyles}.
  12186. *
  12187. * new Ext.panel.Panel({
  12188. * title: 'Some Title',
  12189. * renderTo: Ext.getBody(),
  12190. * width: 400, height: 300,
  12191. * layout: 'form',
  12192. * items: [{
  12193. * xtype: 'textarea',
  12194. * style: {
  12195. * width: '95%',
  12196. * marginBottom: '10px'
  12197. * }
  12198. * },
  12199. * new Ext.button.Button({
  12200. * text: 'Send',
  12201. * minWidth: '100',
  12202. * style: {
  12203. * marginBottom: '10px'
  12204. * }
  12205. * })
  12206. * ]
  12207. * });
  12208. */
  12209. /**
  12210. * @cfg {Number} width
  12211. * The width of this component in pixels.
  12212. */
  12213. /**
  12214. * @cfg {Number} height
  12215. * The height of this component in pixels.
  12216. */
  12217. /**
  12218. * @cfg {Number/String} border
  12219. * Specifies the border for this component. The border can be a single numeric value to apply to all sides or it can
  12220. * be a CSS style specification for each style, for example: '10 5 3 10'.
  12221. */
  12222. /**
  12223. * @cfg {Number/String} padding
  12224. * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or it
  12225. * can be a CSS style specification for each style, for example: '10 5 3 10'.
  12226. */
  12227. /**
  12228. * @cfg {Number/String} margin
  12229. * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or it can
  12230. * be a CSS style specification for each style, for example: '10 5 3 10'.
  12231. */
  12232. /**
  12233. * @cfg {Boolean} hidden
  12234. * True to hide the component.
  12235. */
  12236. hidden: false,
  12237. /**
  12238. * @cfg {Boolean} disabled
  12239. * True to disable the component.
  12240. */
  12241. disabled: false,
  12242. /**
  12243. * @cfg {Boolean} [draggable=false]
  12244. * Allows the component to be dragged.
  12245. */
  12246. /**
  12247. * @property {Boolean} draggable
  12248. * Indicates whether or not the component can be dragged.
  12249. * @readonly
  12250. */
  12251. draggable: false,
  12252. /**
  12253. * @cfg {Boolean} floating
  12254. * Create the Component as a floating and use absolute positioning.
  12255. *
  12256. * 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
  12257. * by the global {@link Ext.WindowManager WindowManager}.
  12258. *
  12259. * 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
  12260. * ZIndexManager instance to manage its descendant floaters. If no floating ancestor can be found, the global WindowManager will be used.
  12261. *
  12262. * When a floating Component which has a ZindexManager managing descendant floaters is destroyed, those descendant floaters will also be destroyed.
  12263. */
  12264. floating: false,
  12265. /**
  12266. * @cfg {String} hideMode
  12267. * A String which specifies how this Component's encapsulating DOM element will be hidden. Values may be:
  12268. *
  12269. * - `'display'` : The Component will be hidden using the `display: none` style.
  12270. * - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
  12271. * - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area of the document.
  12272. * This is useful when a hidden Component must maintain measurable dimensions. Hiding using `display` results in a
  12273. * Component having zero dimensions.
  12274. */
  12275. hideMode: 'display',
  12276. /**
  12277. * @cfg {String} contentEl
  12278. * Specify an existing HTML element, or the `id` of an existing HTML element to use as the content for this component.
  12279. *
  12280. * This config option is used to take an existing HTML element and place it in the layout element of a new component
  12281. * (it simply moves the specified DOM element _after the Component is rendered_ to use as the content.
  12282. *
  12283. * **Notes:**
  12284. *
  12285. * The specified HTML element is appended to the layout element of the component _after any configured
  12286. * {@link #html HTML} has been inserted_, and so the document will not contain this element at the time
  12287. * the {@link #render} event is fired.
  12288. *
  12289. * The specified HTML element used will not participate in any **`{@link Ext.container.Container#layout layout}`**
  12290. * scheme that the Component may use. It is just HTML. Layouts operate on child
  12291. * **`{@link Ext.container.Container#cfg-items items}`**.
  12292. *
  12293. * Add either the `x-hidden` or the `x-hide-display` CSS class to prevent a brief flicker of the content before it
  12294. * is rendered to the panel.
  12295. */
  12296. /**
  12297. * @cfg {String/Object} [html='']
  12298. * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element content.
  12299. * The HTML content is added after the component is rendered, so the document will not contain this HTML at the time
  12300. * the {@link #render} event is fired. This content is inserted into the body _before_ any configured {@link #contentEl}
  12301. * is appended.
  12302. */
  12303. /**
  12304. * @cfg {Boolean} styleHtmlContent
  12305. * True to automatically style the html inside the content target of this component (body for panels).
  12306. */
  12307. styleHtmlContent: false,
  12308. /**
  12309. * @cfg {String} [styleHtmlCls='x-html']
  12310. * The class that is added to the content target when you set styleHtmlContent to true.
  12311. */
  12312. styleHtmlCls: Ext.baseCSSPrefix + 'html',
  12313. /**
  12314. * @cfg {Number} minHeight
  12315. * The minimum value in pixels which this Component will set its height to.
  12316. *
  12317. * **Warning:** This will override any size management applied by layout managers.
  12318. */
  12319. /**
  12320. * @cfg {Number} minWidth
  12321. * The minimum value in pixels which this Component will set its width to.
  12322. *
  12323. * **Warning:** This will override any size management applied by layout managers.
  12324. */
  12325. /**
  12326. * @cfg {Number} maxHeight
  12327. * The maximum value in pixels which this Component will set its height to.
  12328. *
  12329. * **Warning:** This will override any size management applied by layout managers.
  12330. */
  12331. /**
  12332. * @cfg {Number} maxWidth
  12333. * The maximum value in pixels which this Component will set its width to.
  12334. *
  12335. * **Warning:** This will override any size management applied by layout managers.
  12336. */
  12337. /**
  12338. * @cfg {Ext.ComponentLoader/Object} loader
  12339. * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote content for this Component.
  12340. */
  12341. /**
  12342. * @cfg {Boolean} autoShow
  12343. * True to automatically show the component upon creation. This config option may only be used for
  12344. * {@link #floating} components or components that use {@link #autoRender}. Defaults to false.
  12345. */
  12346. autoShow: false,
  12347. /**
  12348. * @cfg {Boolean/String/HTMLElement/Ext.Element} autoRender
  12349. * This config is intended mainly for non-{@link #floating} Components which may or may not be shown. Instead of using
  12350. * {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component to render itself
  12351. * upon first _{@link #method-show}_. If {@link #floating} is true, the value of this config is omited as if it is `true`.
  12352. *
  12353. * Specify as `true` to have this Component render to the document body upon first show.
  12354. *
  12355. * Specify as an element, or the ID of an element to have this Component render to a specific element upon first
  12356. * show.
  12357. */
  12358. autoRender: false,
  12359. // @private
  12360. allowDomMove: true,
  12361. /**
  12362. * @cfg {Object/Object[]} plugins
  12363. * An object or array of objects that will provide custom functionality for this component. The only requirement for
  12364. * a valid plugin is that it contain an init method that accepts a reference of type Ext.Component. When a component
  12365. * is created, if any plugins are available, the component will call the init method on each plugin, passing a
  12366. * reference to itself. Each plugin can then call methods or respond to events on the component as needed to provide
  12367. * its functionality.
  12368. */
  12369. /**
  12370. * @property {Boolean} rendered
  12371. * Indicates whether or not the component has been rendered.
  12372. * @readonly
  12373. */
  12374. rendered: false,
  12375. /**
  12376. * @property {Number} componentLayoutCounter
  12377. * @private
  12378. * The number of component layout calls made on this object.
  12379. */
  12380. componentLayoutCounter: 0,
  12381. /**
  12382. * @cfg {Boolean/Number} [shrinkWrap=2]
  12383. *
  12384. * If this property is a number, it is interpreted as follows:
  12385. *
  12386. * - 0: Neither width nor height depend on content. This is equivalent to `false`.
  12387. * - 1: Width depends on content (shrink wraps), but height does not.
  12388. * - 2: Height depends on content (shrink wraps), but width does not. The default.
  12389. * - 3: Both width and height depend on content (shrink wrap). This is equivalent to `true`.
  12390. *
  12391. * In CSS terms, shrink-wrap width is analogous to an inline-block element as opposed
  12392. * to a block-level element. Some container layouts always shrink-wrap their children,
  12393. * effectively ignoring this property (e.g., {@link Ext.layout.container.HBox},
  12394. * {@link Ext.layout.container.VBox}, {@link Ext.layout.component.Dock}).
  12395. */
  12396. shrinkWrap: 2,
  12397. weight: 0,
  12398. /**
  12399. * @property {Boolean} maskOnDisable
  12400. * This is an internal flag that you use when creating custom components. By default this is set to true which means
  12401. * that every component gets a mask when its disabled. Components like FieldContainer, FieldSet, Field, Button, Tab
  12402. * override this property to false since they want to implement custom disable logic.
  12403. */
  12404. maskOnDisable: true,
  12405. /**
  12406. * @property {Boolean} [_isLayoutRoot=false]
  12407. * Setting this property to `true` causes the {@link #isLayoutRoot} method to return
  12408. * `true` and stop the search for the top-most component for a layout.
  12409. * @protected
  12410. */
  12411. _isLayoutRoot: false,
  12412. /**
  12413. * Creates new Component.
  12414. * @param {Object} config (optional) Config object.
  12415. */
  12416. constructor : function(config) {
  12417. var me = this,
  12418. i, len, xhooks;
  12419. if (config) {
  12420. Ext.apply(me, config);
  12421. xhooks = me.xhooks;
  12422. if (xhooks) {
  12423. me.hookMethods(xhooks);
  12424. delete me.xhooks;
  12425. }
  12426. } else {
  12427. config = {};
  12428. }
  12429. me.initialConfig = config;
  12430. me.mixins.elementCt.constructor.call(me);
  12431. me.addEvents(
  12432. /**
  12433. * @event beforeactivate
  12434. * Fires before a Component has been visually activated. Returning false from an event listener can prevent
  12435. * the activate from occurring.
  12436. * @param {Ext.Component} this
  12437. */
  12438. 'beforeactivate',
  12439. /**
  12440. * @event activate
  12441. * Fires after a Component has been visually activated.
  12442. * @param {Ext.Component} this
  12443. */
  12444. 'activate',
  12445. /**
  12446. * @event beforedeactivate
  12447. * Fires before a Component has been visually deactivated. Returning false from an event listener can
  12448. * prevent the deactivate from occurring.
  12449. * @param {Ext.Component} this
  12450. */
  12451. 'beforedeactivate',
  12452. /**
  12453. * @event deactivate
  12454. * Fires after a Component has been visually deactivated.
  12455. * @param {Ext.Component} this
  12456. */
  12457. 'deactivate',
  12458. /**
  12459. * @event added
  12460. * Fires after a Component had been added to a Container.
  12461. * @param {Ext.Component} this
  12462. * @param {Ext.container.Container} container Parent Container
  12463. * @param {Number} pos position of Component
  12464. */
  12465. 'added',
  12466. /**
  12467. * @event disable
  12468. * Fires after the component is disabled.
  12469. * @param {Ext.Component} this
  12470. */
  12471. 'disable',
  12472. /**
  12473. * @event enable
  12474. * Fires after the component is enabled.
  12475. * @param {Ext.Component} this
  12476. */
  12477. 'enable',
  12478. /**
  12479. * @event beforeshow
  12480. * Fires before the component is shown when calling the {@link #show} method. Return false from an event
  12481. * handler to stop the show.
  12482. * @param {Ext.Component} this
  12483. */
  12484. 'beforeshow',
  12485. /**
  12486. * @event show
  12487. * Fires after the component is shown when calling the {@link #show} method.
  12488. * @param {Ext.Component} this
  12489. */
  12490. 'show',
  12491. /**
  12492. * @event beforehide
  12493. * Fires before the component is hidden when calling the {@link #hide} method. Return false from an event
  12494. * handler to stop the hide.
  12495. * @param {Ext.Component} this
  12496. */
  12497. 'beforehide',
  12498. /**
  12499. * @event hide
  12500. * Fires after the component is hidden. Fires after the component is hidden when calling the {@link #hide}
  12501. * method.
  12502. * @param {Ext.Component} this
  12503. */
  12504. 'hide',
  12505. /**
  12506. * @event removed
  12507. * Fires when a component is removed from an Ext.container.Container
  12508. * @param {Ext.Component} this
  12509. * @param {Ext.container.Container} ownerCt Container which holds the component
  12510. */
  12511. 'removed',
  12512. /**
  12513. * @event beforerender
  12514. * Fires before the component is {@link #rendered}. Return false from an event handler to stop the
  12515. * {@link #render}.
  12516. * @param {Ext.Component} this
  12517. */
  12518. 'beforerender',
  12519. /**
  12520. * @event render
  12521. * Fires after the component markup is {@link #rendered}.
  12522. * @param {Ext.Component} this
  12523. */
  12524. 'render',
  12525. /**
  12526. * @event afterrender
  12527. * Fires after the component rendering is finished.
  12528. *
  12529. * The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed by any
  12530. * afterRender method defined for the Component.
  12531. * @param {Ext.Component} this
  12532. */
  12533. 'afterrender',
  12534. /**
  12535. * @event beforedestroy
  12536. * Fires before the component is {@link #method-destroy}ed. Return false from an event handler to stop the
  12537. * {@link #method-destroy}.
  12538. * @param {Ext.Component} this
  12539. */
  12540. 'beforedestroy',
  12541. /**
  12542. * @event destroy
  12543. * Fires after the component is {@link #method-destroy}ed.
  12544. * @param {Ext.Component} this
  12545. */
  12546. 'destroy',
  12547. /**
  12548. * @event resize
  12549. * Fires after the component is resized.
  12550. * @param {Ext.Component} this
  12551. * @param {Number} width The new width that was set
  12552. * @param {Number} height The new height that was set
  12553. * @param {Number} oldWidth The previous width
  12554. * @param {Number} oldHeight The previous height
  12555. */
  12556. 'resize',
  12557. /**
  12558. * @event move
  12559. * Fires after the component is moved.
  12560. * @param {Ext.Component} this
  12561. * @param {Number} x The new x position
  12562. * @param {Number} y The new y position
  12563. */
  12564. 'move',
  12565. /**
  12566. * @event focus
  12567. * Fires when this Component receives focus.
  12568. * @param {Ext.Component} this
  12569. * @param {Ext.EventObject} The focus event.
  12570. */
  12571. 'focus',
  12572. /**
  12573. * @event blur
  12574. * Fires when this Component loses focus.
  12575. * @param {Ext.Component} this
  12576. * @param {Ext.EventObject} The blur event.
  12577. */
  12578. 'blur'
  12579. );
  12580. me.getId();
  12581. me.setupProtoEl();
  12582. // initComponent, beforeRender, or event handlers may have set the style or cls property since the protoEl was set up
  12583. // so we must apply styles and classes here too.
  12584. if (me.cls) {
  12585. me.initialCls = me.cls;
  12586. me.protoEl.addCls(me.cls);
  12587. }
  12588. if (me.style) {
  12589. me.initialStyle = me.style;
  12590. me.protoEl.setStyle(me.style);
  12591. }
  12592. me.mons = [];
  12593. me.renderData = me.renderData || {};
  12594. me.renderSelectors = me.renderSelectors || {};
  12595. if (me.plugins) {
  12596. me.plugins = [].concat(me.plugins);
  12597. me.constructPlugins();
  12598. }
  12599. // Hash of event "hasListeners" flags.
  12600. // For repeated events in time-critical code, the firing code should use
  12601. // if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { //code... }
  12602. // Bubbling the events counts as one listener. initComponent may add listeners, so needs setting up now.
  12603. me.hasListeners = {};
  12604. me.initComponent();
  12605. // ititComponent gets a chance to change the id property before registering
  12606. Ext.ComponentManager.register(me);
  12607. // Dont pass the config so that it is not applied to 'this' again
  12608. me.mixins.observable.constructor.call(me);
  12609. me.mixins.state.constructor.call(me, config);
  12610. // Save state on resize.
  12611. this.addStateEvents('resize');
  12612. // Move this into Observable?
  12613. if (me.plugins) {
  12614. me.plugins = [].concat(me.plugins);
  12615. for (i = 0, len = me.plugins.length; i < len; i++) {
  12616. me.plugins[i] = me.initPlugin(me.plugins[i]);
  12617. }
  12618. }
  12619. me.loader = me.getLoader();
  12620. if (me.renderTo) {
  12621. me.render(me.renderTo);
  12622. // EXTJSIV-1935 - should be a way to do afterShow or something, but that
  12623. // won't work. Likewise, rendering hidden and then showing (w/autoShow) has
  12624. // implications to afterRender so we cannot do that.
  12625. }
  12626. if (me.autoShow) {
  12627. me.show();
  12628. }
  12629. if (Ext.isDefined(me.disabledClass)) {
  12630. if (Ext.isDefined(Ext.global.console)) {
  12631. Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
  12632. }
  12633. me.disabledCls = me.disabledClass;
  12634. delete me.disabledClass;
  12635. }
  12636. },
  12637. initComponent: function () {
  12638. // This is called again here to allow derived classes to add plugin configs to the
  12639. // plugins array before calling down to this, the base initComponent.
  12640. this.constructPlugins();
  12641. // this will properly (ignore or) constrain the configured width/height to their
  12642. // min/max values for consistency.
  12643. this.setSize(this.width, this.height);
  12644. },
  12645. /**
  12646. * The supplied default state gathering method for the AbstractComponent class.
  12647. *
  12648. * This method returns dimension settings such as `flex`, `anchor`, `width` and `height` along with `collapsed`
  12649. * state.
  12650. *
  12651. * Subclasses which implement more complex state should call the superclass's implementation, and apply their state
  12652. * to the result if this basic state is to be saved.
  12653. *
  12654. * Note that Component state will only be saved if the Component has a {@link #stateId} and there as a StateProvider
  12655. * configured for the document.
  12656. *
  12657. * @return {Object}
  12658. */
  12659. getState: function() {
  12660. var me = this,
  12661. state = null,
  12662. sizeModel = me.getSizeModel();
  12663. if (sizeModel.width.configured) {
  12664. state = me.addPropertyToState(state, 'width');
  12665. }
  12666. if (sizeModel.height.configured) {
  12667. state = me.addPropertyToState(state, 'height');
  12668. }
  12669. return state;
  12670. },
  12671. /**
  12672. * Save a property to the given state object if it is not its default or configured
  12673. * value.
  12674. *
  12675. * @param {Object} state The state object
  12676. * @param {String} propName The name of the property on this object to save.
  12677. * @param {String} [value] The value of the state property (defaults to `this[propName]`).
  12678. * @return {Boolean} The state object or a new object if state was null and the property
  12679. * was saved.
  12680. * @protected
  12681. */
  12682. addPropertyToState: function (state, propName, value) {
  12683. var me = this,
  12684. len = arguments.length;
  12685. // If the property is inherited, it is a default and we don't want to save it to
  12686. // the state, however if we explicitly specify a value, always save it
  12687. if (len == 3 || me.hasOwnProperty(propName)) {
  12688. if (len < 3) {
  12689. value = me[propName];
  12690. }
  12691. // If the property has the same value as was initially configured, again, we
  12692. // don't want to save it.
  12693. if (value !== me.initialConfig[propName]) {
  12694. (state || (state = {}))[propName] = value;
  12695. }
  12696. }
  12697. return state;
  12698. },
  12699. show: Ext.emptyFn,
  12700. animate: function(animObj) {
  12701. var me = this,
  12702. hasToWidth,
  12703. hasToHeight,
  12704. toHeight,
  12705. toWidth,
  12706. to,
  12707. clearWidth,
  12708. clearHeight;
  12709. animObj = animObj || {};
  12710. to = animObj.to || {};
  12711. if (Ext.fx.Manager.hasFxBlock(me.id)) {
  12712. return me;
  12713. }
  12714. hasToWidth = Ext.isDefined(to.width);
  12715. if (hasToWidth) {
  12716. toWidth = Ext.Number.constrain(to.width, me.minWidth, me.maxWidth);
  12717. }
  12718. hasToHeight = Ext.isDefined(to.height);
  12719. if (hasToHeight) {
  12720. toHeight = Ext.Number.constrain(to.height, me.minHeight, me.maxHeight);
  12721. }
  12722. // Special processing for animating Component dimensions.
  12723. if (!animObj.dynamic && (hasToWidth || hasToHeight)) {
  12724. var curWidth = (animObj.from ? animObj.from.width : undefined) || me.getWidth(),
  12725. w = curWidth,
  12726. curHeight = (animObj.from ? animObj.from.height : undefined) || me.getHeight(),
  12727. h = curHeight,
  12728. needsResize = false;
  12729. if (hasToHeight && toHeight > curHeight) {
  12730. h = toHeight;
  12731. needsResize = true;
  12732. }
  12733. if (hasToWidth && toWidth > curWidth) {
  12734. w = toWidth;
  12735. needsResize = true;
  12736. }
  12737. // If any dimensions are being increased, we must resize the internal structure
  12738. // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
  12739. // The animation will then progressively reveal the larger content.
  12740. if (needsResize) {
  12741. clearWidth = !Ext.isNumber(me.width);
  12742. clearHeight = !Ext.isNumber(me.height);
  12743. me.setSize(w, h);
  12744. me.el.setSize(curWidth, curHeight);
  12745. if (clearWidth) {
  12746. delete me.width;
  12747. }
  12748. if (clearHeight) {
  12749. delete me.height;
  12750. }
  12751. }
  12752. if (hasToWidth) {
  12753. to.width = toWidth;
  12754. }
  12755. if (hasToHeight) {
  12756. to.height = toHeight;
  12757. }
  12758. }
  12759. return me.mixins.animate.animate.apply(me, arguments);
  12760. },
  12761. onHide: function() {
  12762. this.updateLayout({ isRoot: false });
  12763. },
  12764. onShow : function() {
  12765. this.updateLayout({ isRoot: false });
  12766. },
  12767. constructPlugin: function(plugin) {
  12768. if (plugin.ptype && typeof plugin.init != 'function') {
  12769. plugin.cmp = this;
  12770. plugin = Ext.PluginManager.create(plugin);
  12771. }
  12772. else if (typeof plugin == 'string') {
  12773. plugin = Ext.PluginManager.create({
  12774. ptype: plugin,
  12775. cmp: this
  12776. });
  12777. }
  12778. return plugin;
  12779. },
  12780. /**
  12781. * Ensures that the plugins array contains fully constructed plugin instances. This converts any configs into their
  12782. * appropriate instances.
  12783. */
  12784. constructPlugins: function() {
  12785. var me = this,
  12786. plugins = me.plugins,
  12787. i, len;
  12788. if (plugins) {
  12789. for (i = 0, len = plugins.length; i < len; i++) {
  12790. // this just returns already-constructed plugin instances...
  12791. plugins[i] = me.constructPlugin(plugins[i]);
  12792. }
  12793. }
  12794. },
  12795. // @private
  12796. initPlugin : function(plugin) {
  12797. plugin.init(this);
  12798. return plugin;
  12799. },
  12800. /**
  12801. * @private
  12802. * Injected as an override by Ext.Aria.initialize
  12803. */
  12804. updateAria: Ext.emptyFn,
  12805. /**
  12806. * Called by Component#doAutoRender
  12807. *
  12808. * Register a Container configured `floating: true` with this Component's {@link Ext.ZIndexManager ZIndexManager}.
  12809. *
  12810. * Components added in ths way will not participate in any layout, but will be rendered
  12811. * upon first show in the way that {@link Ext.window.Window Window}s are.
  12812. */
  12813. registerFloatingItem: function(cmp) {
  12814. var me = this;
  12815. if (!me.floatingItems) {
  12816. me.floatingItems = new Ext.ZIndexManager(me);
  12817. }
  12818. me.floatingItems.register(cmp);
  12819. },
  12820. unregisterFloatingItem: function(cmp) {
  12821. var me = this;
  12822. if (me.floatingItems) {
  12823. me.floatingItems.unregister(cmp);
  12824. }
  12825. },
  12826. layoutSuspendCount: 0,
  12827. suspendLayouts: function () {
  12828. var me = this;
  12829. if (!me.rendered) {
  12830. return;
  12831. }
  12832. if (++me.layoutSuspendCount == 1) {
  12833. me.suspendLayout = true;
  12834. }
  12835. },
  12836. resumeLayouts: function (flushOptions) {
  12837. var me = this;
  12838. if (!me.rendered) {
  12839. return;
  12840. }
  12841. if (! --me.layoutSuspendCount) {
  12842. me.suspendLayout = false;
  12843. if (flushOptions && !me.isLayoutSuspended()) {
  12844. me.updateLayout(flushOptions);
  12845. }
  12846. }
  12847. },
  12848. setupProtoEl: function() {
  12849. var me = this,
  12850. cls = [ me.baseCls, me.getComponentLayout().targetCls ];
  12851. if (Ext.isDefined(me.cmpCls)) {
  12852. if (Ext.isDefined(Ext.global.console)) {
  12853. Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
  12854. }
  12855. me.componentCls = me.cmpCls;
  12856. delete me.cmpCls;
  12857. }
  12858. if (me.componentCls) {
  12859. cls.push(me.componentCls);
  12860. } else {
  12861. me.componentCls = me.baseCls;
  12862. }
  12863. me.protoEl = new Ext.util.ProtoElement({
  12864. cls: cls.join(' ') // in case any of the parts have multiple classes
  12865. });
  12866. },
  12867. /**
  12868. * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
  12869. * uiCls set on the component and rename them so they include the new UI
  12870. * @param {String} ui The new UI for the component
  12871. */
  12872. setUI: function(ui) {
  12873. var me = this,
  12874. oldUICls = Ext.Array.clone(me.uiCls),
  12875. newUICls = [],
  12876. classes = [],
  12877. cls,
  12878. i;
  12879. //loop through all exisiting uiCls and update the ui in them
  12880. for (i = 0; i < oldUICls.length; i++) {
  12881. cls = oldUICls[i];
  12882. classes = classes.concat(me.removeClsWithUI(cls, true));
  12883. newUICls.push(cls);
  12884. }
  12885. if (classes.length) {
  12886. me.removeCls(classes);
  12887. }
  12888. //remove the UI from the element
  12889. me.removeUIFromElement();
  12890. //set the UI
  12891. me.ui = ui;
  12892. //add the new UI to the elemend
  12893. me.addUIToElement();
  12894. //loop through all exisiting uiCls and update the ui in them
  12895. classes = [];
  12896. for (i = 0; i < newUICls.length; i++) {
  12897. cls = newUICls[i];
  12898. classes = classes.concat(me.addClsWithUI(cls, true));
  12899. }
  12900. if (classes.length) {
  12901. me.addCls(classes);
  12902. }
  12903. },
  12904. /**
  12905. * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
  12906. * component.
  12907. * @param {String/String[]} classes A string or an array of strings to add to the uiCls
  12908. * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
  12909. */
  12910. addClsWithUI: function(classes, skip) {
  12911. var me = this,
  12912. clsArray = [],
  12913. length,
  12914. i = 0,
  12915. cls;
  12916. if (typeof classes === "string") {
  12917. classes = (classes.indexOf(' ') < 0) ? [classes] : Ext.String.splitWords(classes);
  12918. }
  12919. length = classes.length;
  12920. me.uiCls = Ext.Array.clone(me.uiCls);
  12921. for (; i < length; i++) {
  12922. cls = classes[i];
  12923. if (cls && !me.hasUICls(cls)) {
  12924. me.uiCls.push(cls);
  12925. clsArray = clsArray.concat(me.addUIClsToElement(cls));
  12926. }
  12927. }
  12928. if (skip !== true) {
  12929. me.addCls(clsArray);
  12930. }
  12931. return clsArray;
  12932. },
  12933. /**
  12934. * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
  12935. * elements of this component.
  12936. * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
  12937. */
  12938. removeClsWithUI: function(classes, skip) {
  12939. var me = this,
  12940. clsArray = [],
  12941. i = 0,
  12942. length, cls;
  12943. if (typeof classes === "string") {
  12944. classes = (classes.indexOf(' ') < 0) ? [classes] : Ext.String.splitWords(classes);
  12945. }
  12946. length = classes.length;
  12947. for (i = 0; i < length; i++) {
  12948. cls = classes[i];
  12949. if (cls && me.hasUICls(cls)) {
  12950. me.uiCls = Ext.Array.remove(me.uiCls, cls);
  12951. clsArray = clsArray.concat(me.removeUIClsFromElement(cls));
  12952. }
  12953. }
  12954. if (skip !== true) {
  12955. me.removeCls(clsArray);
  12956. }
  12957. return clsArray;
  12958. },
  12959. /**
  12960. * Checks if there is currently a specified uiCls
  12961. * @param {String} cls The cls to check
  12962. */
  12963. hasUICls: function(cls) {
  12964. var me = this,
  12965. uiCls = me.uiCls || [];
  12966. return Ext.Array.contains(uiCls, cls);
  12967. },
  12968. frameElementsArray: ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
  12969. /**
  12970. * Method which adds a specified UI + uiCls to the components element. Can be overridden to remove the UI from more
  12971. * than just the components element.
  12972. * @param {String} ui The UI to remove from the element
  12973. */
  12974. addUIClsToElement: function(cls) {
  12975. var me = this,
  12976. baseClsUi = me.baseCls + '-' + me.ui + '-' + cls,
  12977. result = [Ext.baseCSSPrefix + cls, me.baseCls + '-' + cls, baseClsUi],
  12978. frameElementCls = me.frameElementCls;
  12979. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  12980. // define each element of the frame
  12981. var frameElementsArray = me.frameElementsArray,
  12982. frameElementsLength = frameElementsArray.length,
  12983. i = 0,
  12984. el, frameElement, c;
  12985. // loop through each of them, and if they are defined add the ui
  12986. for (; i < frameElementsLength; i++) {
  12987. frameElement = frameElementsArray[i];
  12988. el = me['frame' + frameElement.toUpperCase()];
  12989. c = baseClsUi + '-' + frameElement;
  12990. if (el && el.dom) {
  12991. el.addCls(c);
  12992. } else if (Ext.Array.indexOf(frameElementCls[frameElement], c) == -1) {
  12993. frameElementCls[frameElement].push(c);
  12994. }
  12995. }
  12996. }
  12997. me.frameElementCls = frameElementCls;
  12998. return result;
  12999. },
  13000. /**
  13001. * Method which removes a specified UI + uiCls from the components element. The cls which is added to the element
  13002. * will be: `this.baseCls + '-' + ui`
  13003. * @param {String} ui The UI to add to the element
  13004. */
  13005. removeUIClsFromElement: function(cls) {
  13006. var me = this,
  13007. baseClsUi = me.baseCls + '-' + me.ui + '-' + cls,
  13008. result = [Ext.baseCSSPrefix + cls, me.baseCls + '-' + cls, baseClsUi],
  13009. frameElementCls = me.frameElementCls;
  13010. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  13011. // define each element of the frame
  13012. var frameElementsArray = me.frameElementsArray,
  13013. frameElementsLength = frameElementsArray.length,
  13014. i = 0,
  13015. el, frameElement, c;
  13016. // loop through each of them, and if they are defined add the ui
  13017. for (; i < frameElementsLength; i++) {
  13018. frameElement = frameElementsArray[i];
  13019. el = me['frame' + frameElement.toUpperCase()];
  13020. c = baseClsUi + '-' + frameElement;
  13021. if (el && el.dom) {
  13022. el.addCls(c);
  13023. } else {
  13024. Ext.Array.remove(frameElementCls[frameElement], c);
  13025. }
  13026. }
  13027. }
  13028. me.frameElementCls = frameElementCls;
  13029. return result;
  13030. },
  13031. /**
  13032. * Method which adds a specified UI to the components element.
  13033. * @private
  13034. */
  13035. addUIToElement: function() {
  13036. var me = this,
  13037. baseClsUI = me.baseCls + '-' + me.ui,
  13038. frameElementCls = me.frameElementCls;
  13039. me.addCls(baseClsUI);
  13040. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  13041. // define each element of the frame
  13042. var frameElementsArray = me.frameElementsArray,
  13043. frameElementsLength = frameElementsArray.length,
  13044. i = 0,
  13045. el, frameElement, c;
  13046. // loop through each of them, and if they are defined add the ui
  13047. for (; i < frameElementsLength; i++) {
  13048. frameElement = frameElementsArray[i];
  13049. el = me['frame' + frameElement.toUpperCase()];
  13050. c = baseClsUI + '-' + frameElement;
  13051. if (el) {
  13052. el.addCls(c);
  13053. } else {
  13054. if (!Ext.Array.contains(frameElementCls[frameElement], c)) {
  13055. frameElementCls[frameElement].push(c);
  13056. }
  13057. }
  13058. }
  13059. }
  13060. },
  13061. /**
  13062. * Method which removes a specified UI from the components element.
  13063. * @private
  13064. */
  13065. removeUIFromElement: function() {
  13066. var me = this,
  13067. baseClsUI = me.baseCls + '-' + me.ui,
  13068. frameElementCls = me.frameElementCls;
  13069. me.removeCls(baseClsUI);
  13070. if (me.frame && !Ext.supports.CSS3BorderRadius) {
  13071. // define each element of the frame
  13072. var frameElementsArray = me.frameElementsArray,
  13073. frameElementsLength = frameElementsArray.length,
  13074. i = 0,
  13075. el, frameElement, c;
  13076. for (; i < frameElementsLength; i++) {
  13077. frameElement = frameElementsArray[i];
  13078. el = me['frame' + frameElement.toUpperCase()];
  13079. c = baseClsUI + '-' + frameElement;
  13080. if (el) {
  13081. el.removeCls(c);
  13082. } else {
  13083. Ext.Array.remove(frameElementCls[frameElement], c);
  13084. }
  13085. }
  13086. }
  13087. },
  13088. /**
  13089. * @private
  13090. */
  13091. getTpl: function(name) {
  13092. return Ext.XTemplate.getTpl(this, name);
  13093. },
  13094. /**
  13095. * Converts style definitions to String.
  13096. * @return {String} A CSS style string with style, padding, margin and border.
  13097. * @private
  13098. */
  13099. initStyles: function(targetEl) {
  13100. var me = this,
  13101. Element = Ext.Element,
  13102. padding = me.padding,
  13103. margin = me.margin,
  13104. x = me.x,
  13105. y = me.y,
  13106. width, height;
  13107. // Convert the padding, margin and border properties from a space seperated string
  13108. // into a proper style string
  13109. if (padding !== undefined) {
  13110. targetEl.setStyle('padding', Element.unitizeBox((padding === true) ? 5 : padding));
  13111. }
  13112. if (margin !== undefined) {
  13113. targetEl.setStyle('margin', Element.unitizeBox((margin === true) ? 5 : margin));
  13114. }
  13115. if (me.border !== undefined) {
  13116. me.setBorder(me.border, targetEl);
  13117. }
  13118. // initComponent, beforeRender, or event handlers may have set the style or cls property since the protoEl was set up
  13119. // so we must apply styles and classes here too.
  13120. if (me.cls && me.cls != me.initialCls) {
  13121. targetEl.addCls(me.cls);
  13122. delete me.cls;
  13123. delete me.initialCls;
  13124. }
  13125. if (me.style && me.style != me.initialStyle) {
  13126. targetEl.setStyle(me.style);
  13127. delete me.style;
  13128. delete me.initialStyle;
  13129. }
  13130. if (x !== undefined) {
  13131. targetEl.setStyle('left', x + 'px');
  13132. }
  13133. if (y !== undefined) {
  13134. targetEl.setStyle('top', y + 'px');
  13135. }
  13136. // Framed components need their width/height to apply to the frame, which is
  13137. // best handled in layout at present.
  13138. // If we're using the content box model, we also cannot assign initial sizes since we do not know the border widths to subtract
  13139. if (!me.getFrameInfo() && Ext.isBorderBox) {
  13140. width = me.width;
  13141. height = me.height;
  13142. // framed components need their width/height to apply to the frame, which is
  13143. // best handled in layout at present
  13144. if (typeof width == 'number') {
  13145. targetEl.setStyle('width', width + 'px');
  13146. }
  13147. if (typeof height == 'number') {
  13148. targetEl.setStyle('height', height + 'px');
  13149. }
  13150. }
  13151. },
  13152. // @private
  13153. initEvents : function() {
  13154. var me = this,
  13155. afterRenderEvents = me.afterRenderEvents,
  13156. el,
  13157. property,
  13158. fn = function(listeners){
  13159. me.mon(el, listeners);
  13160. };
  13161. if (afterRenderEvents) {
  13162. for (property in afterRenderEvents) {
  13163. if (afterRenderEvents.hasOwnProperty(property)) {
  13164. el = me[property];
  13165. if (el && el.on) {
  13166. Ext.each(afterRenderEvents[property], fn);
  13167. }
  13168. }
  13169. }
  13170. }
  13171. // This will add focus/blur listeners to the getFocusEl() element if that is naturally focusable.
  13172. // If *not* naturally focusable, then the FocusManager must be enabled to get it to listen for focus so that
  13173. // the FocusManager can track and highlight focus.
  13174. me.addFocusListener();
  13175. },
  13176. /**
  13177. * @private
  13178. * <p>Sets up the focus listener on this Component's {@link #getFocusEl focusEl} if it has one.</p>
  13179. * <p>Form Components which must implicitly participate in tabbing order usually have a naturally focusable
  13180. * element as their {@link #getFocusEl focusEl}, and it is the DOM event of that recieving focus which drives
  13181. * the Component's onFocus handling, and the DOM event of it being blurred which drives the onBlur handling.</p>
  13182. * <p>If the {@link #getFocusEl focusEl} is <b>not</b> naturally focusable, then the listeners are only added
  13183. * if the {@link Ext.FocusManager FocusManager} is enabled.</p>
  13184. */
  13185. addFocusListener: function() {
  13186. var me = this,
  13187. focusEl = me.getFocusEl(),
  13188. needsTabIndex;
  13189. // All Containers may be focusable, not only "form" type elements, but also
  13190. // Panels, Toolbars, Windows etc.
  13191. // Usually, the <DIV> element they will return as their focusEl will not be able to recieve focus
  13192. // However, if the FocusManager is invoked, its non-default navigation handlers (invoked when
  13193. // tabbing/arrowing off of certain Components) may explicitly focus a Panel or Container or FieldSet etc.
  13194. // Add listeners to the focus and blur events on the focus element
  13195. // If this Component returns a focusEl, we might need to add a focus listener to it.
  13196. if (focusEl) {
  13197. // getFocusEl might return a Component if a Container wishes to delegate focus to a descendant.
  13198. // Window can do this via its defaultFocus configuration which can reference a Button.
  13199. if (focusEl.isComponent) {
  13200. return focusEl.addFocusListener();
  13201. }
  13202. // If the focusEl is naturally focusable, then we always need a focus listener to drive the Component's
  13203. // onFocus handling.
  13204. // If *not* naturally focusable, then we only need the focus listener if the FocusManager is enabled.
  13205. needsTabIndex = focusEl.needsTabIndex();
  13206. if (!me.focusListenerAdded && (!needsTabIndex || Ext.FocusManager.enabled)) {
  13207. if (needsTabIndex) {
  13208. focusEl.dom.tabIndex = -1;
  13209. }
  13210. focusEl.on({
  13211. focus: me.onFocus,
  13212. blur: me.onBlur,
  13213. scope: me
  13214. });
  13215. me.focusListenerAdded = true;
  13216. }
  13217. }
  13218. },
  13219. /**
  13220. * @private
  13221. * <p>Returns the focus holder element associated with this Component. At the Component base class level, this function returns <code>undefined</code>.</p>
  13222. * <p>Subclasses which use embedded focusable elements (such as Window, Field and Button) should override this for use by the {@link #focus} method.</p>
  13223. * <p>Containers which need to participate in the {@link Ext.FocusManager FocusManager}'s navigation and Container focusing scheme also
  13224. * need to return a focusEl, although focus is only listened for in this case if the {@link Ext.FocusManager FocusManager} is {@link Ext.FocusManager#method-enable enable}d.</p>
  13225. * @returns {undefined} <code>undefined</code> because raw Components cannot by default hold focus.
  13226. */
  13227. getFocusEl: Ext.emptyFn,
  13228. isFocusable: function(c) {
  13229. var me = this,
  13230. focusEl;
  13231. if ((me.focusable !== false) && (focusEl = me.getFocusEl()) && me.rendered && !me.destroying && !me.isDestroyed && !me.disabled && me.isVisible(true)) {
  13232. // getFocusEl might return a Component if a Container wishes to delegate focus to a descendant.
  13233. // Window can do this via its defaultFocus configuration which can reference a Button.
  13234. if (focusEl.isComponent) {
  13235. return focusEl.isFocusable();
  13236. }
  13237. return focusEl && focusEl.dom && focusEl.isVisible();
  13238. }
  13239. },
  13240. // private
  13241. preFocus: Ext.emptyFn,
  13242. // private
  13243. onFocus: function(e) {
  13244. var me = this,
  13245. focusCls = me.focusCls,
  13246. focusEl = me.getFocusEl();
  13247. if (!me.disabled) {
  13248. me.preFocus(e);
  13249. if (focusCls && focusEl) {
  13250. focusEl.addCls(me.addClsWithUI(focusCls, true));
  13251. }
  13252. if (!me.hasFocus) {
  13253. me.hasFocus = true;
  13254. me.fireEvent('focus', me, e);
  13255. }
  13256. }
  13257. },
  13258. // private
  13259. beforeBlur : Ext.emptyFn,
  13260. // private
  13261. onBlur : function(e) {
  13262. var me = this,
  13263. focusCls = me.focusCls,
  13264. focusEl = me.getFocusEl();
  13265. if (me.destroying) {
  13266. return;
  13267. }
  13268. me.beforeBlur(e);
  13269. if (focusCls && focusEl) {
  13270. focusEl.removeCls(me.removeClsWithUI(focusCls, true));
  13271. }
  13272. if (me.validateOnBlur) {
  13273. me.validate();
  13274. }
  13275. me.hasFocus = false;
  13276. me.fireEvent('blur', me, e);
  13277. me.postBlur(e);
  13278. },
  13279. // private
  13280. postBlur : Ext.emptyFn,
  13281. /**
  13282. * Tests whether this Component matches the selector string.
  13283. * @param {String} selector The selector string to test against.
  13284. * @return {Boolean} True if this Component matches the selector.
  13285. */
  13286. is: function(selector) {
  13287. return Ext.ComponentQuery.is(this, selector);
  13288. },
  13289. /**
  13290. * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
  13291. *
  13292. * Example:
  13293. *
  13294. * var owningTabPanel = grid.up('tabpanel');
  13295. *
  13296. * @param {String} [selector] The simple selector to test.
  13297. * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
  13298. */
  13299. up: function(selector) {
  13300. // Use bubble target to navigate upwards so that Components can implement their own hierarchy.
  13301. // For example Menus implement getBubbleTarget because they have a parentMenu or ownerButton as an
  13302. // upward link depending upon how they are owned and triggered.
  13303. var result = this.getBubbleTarget();
  13304. if (selector) {
  13305. for (; result; result = result.getBubbleTarget()) {
  13306. if (Ext.ComponentQuery.is(result, selector)) {
  13307. return result;
  13308. }
  13309. }
  13310. }
  13311. return result;
  13312. },
  13313. /**
  13314. * Returns the next sibling of this Component.
  13315. *
  13316. * Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.
  13317. *
  13318. * May also be refered to as **`next()`**
  13319. *
  13320. * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
  13321. * {@link #nextNode}
  13322. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
  13323. * @return {Ext.Component} The next sibling (or the next sibling which matches the selector).
  13324. * Returns null if there is no matching sibling.
  13325. */
  13326. nextSibling: function(selector) {
  13327. var o = this.ownerCt, it, last, idx, c;
  13328. if (o) {
  13329. it = o.items;
  13330. idx = it.indexOf(this) + 1;
  13331. if (idx) {
  13332. if (selector) {
  13333. for (last = it.getCount(); idx < last; idx++) {
  13334. if ((c = it.getAt(idx)).is(selector)) {
  13335. return c;
  13336. }
  13337. }
  13338. } else {
  13339. if (idx < it.getCount()) {
  13340. return it.getAt(idx);
  13341. }
  13342. }
  13343. }
  13344. }
  13345. return null;
  13346. },
  13347. /**
  13348. * Returns the previous sibling of this Component.
  13349. *
  13350. * Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery}
  13351. * selector.
  13352. *
  13353. * May also be refered to as **`prev()`**
  13354. *
  13355. * Note that this is limited to siblings, and if no siblings of the item match, `null` is returned. Contrast with
  13356. * {@link #previousNode}
  13357. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
  13358. * @return {Ext.Component} The previous sibling (or the previous sibling which matches the selector).
  13359. * Returns null if there is no matching sibling.
  13360. */
  13361. previousSibling: function(selector) {
  13362. var o = this.ownerCt, it, idx, c;
  13363. if (o) {
  13364. it = o.items;
  13365. idx = it.indexOf(this);
  13366. if (idx != -1) {
  13367. if (selector) {
  13368. for (--idx; idx >= 0; idx--) {
  13369. if ((c = it.getAt(idx)).is(selector)) {
  13370. return c;
  13371. }
  13372. }
  13373. } else {
  13374. if (idx) {
  13375. return it.getAt(--idx);
  13376. }
  13377. }
  13378. }
  13379. }
  13380. return null;
  13381. },
  13382. /**
  13383. * Returns the previous node in the Component tree in tree traversal order.
  13384. *
  13385. * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
  13386. * tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.
  13387. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
  13388. * @return {Ext.Component} The previous node (or the previous node which matches the selector).
  13389. * Returns null if there is no matching node.
  13390. */
  13391. previousNode: function(selector, includeSelf) {
  13392. var node = this,
  13393. result,
  13394. it, len, i;
  13395. // If asked to include self, test me
  13396. if (includeSelf && node.is(selector)) {
  13397. return node;
  13398. }
  13399. result = this.prev(selector);
  13400. if (result) {
  13401. return result;
  13402. }
  13403. if (node.ownerCt) {
  13404. for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
  13405. if (it[i].query) {
  13406. result = it[i].query(selector);
  13407. result = result[result.length - 1];
  13408. if (result) {
  13409. return result;
  13410. }
  13411. }
  13412. }
  13413. return node.ownerCt.previousNode(selector, true);
  13414. }
  13415. },
  13416. /**
  13417. * Returns the next node in the Component tree in tree traversal order.
  13418. *
  13419. * Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will walk the
  13420. * tree to attempt to find a match. Contrast with {@link #nextSibling}.
  13421. * @param {String} [selector] A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
  13422. * @return {Ext.Component} The next node (or the next node which matches the selector).
  13423. * Returns null if there is no matching node.
  13424. */
  13425. nextNode: function(selector, includeSelf) {
  13426. var node = this,
  13427. result,
  13428. it, len, i;
  13429. // If asked to include self, test me
  13430. if (includeSelf && node.is(selector)) {
  13431. return node;
  13432. }
  13433. result = this.next(selector);
  13434. if (result) {
  13435. return result;
  13436. }
  13437. if (node.ownerCt) {
  13438. for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
  13439. if (it[i].down) {
  13440. result = it[i].down(selector);
  13441. if (result) {
  13442. return result;
  13443. }
  13444. }
  13445. }
  13446. return node.ownerCt.nextNode(selector);
  13447. }
  13448. },
  13449. /**
  13450. * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
  13451. * @return {String}
  13452. */
  13453. getId : function() {
  13454. return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
  13455. },
  13456. getItemId : function() {
  13457. return this.itemId || this.id;
  13458. },
  13459. /**
  13460. * Retrieves the top level element representing this component.
  13461. * @return {Ext.dom.Element}
  13462. */
  13463. getEl : function() {
  13464. return this.el;
  13465. },
  13466. /**
  13467. * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
  13468. * @private
  13469. */
  13470. getTargetEl: function() {
  13471. return this.frameBody || this.el;
  13472. },
  13473. /**
  13474. * @private
  13475. * Returns the CSS style object which will set the Component's scroll styles. This must be applied
  13476. * to the {@link #getTargetEl target element}.
  13477. */
  13478. getOverflowStyle: function() {
  13479. var me = this,
  13480. result = null;
  13481. if (typeof me.autoScroll == 'boolean') {
  13482. result = {
  13483. overflow: me.autoScroll ? 'auto' : ''
  13484. };
  13485. } else if (me.overflowX !== undefined || me.overflowY !== undefined) {
  13486. result = {
  13487. 'overflow-x': (me.overflowX||''),
  13488. 'overflow-y': (me.overflowY||'')
  13489. };
  13490. }
  13491. // The scrollable container element must be non-statically positioned or IE6/7 will make
  13492. // positioned children stay in place rather than scrolling with the rest of the content
  13493. if (result && (Ext.isIE6 || Ext.isIE7)) {
  13494. result.position = 'relative';
  13495. }
  13496. return result;
  13497. },
  13498. /**
  13499. * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
  13500. * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).
  13501. *
  13502. * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
  13503. * determination of inherited xtypes.**
  13504. *
  13505. * For a list of all available xtypes, see the {@link Ext.Component} header.
  13506. *
  13507. * Example usage:
  13508. *
  13509. * var t = new Ext.form.field.Text();
  13510. * var isText = t.isXType('textfield'); // true
  13511. * var isBoxSubclass = t.isXType('field'); // true, descended from Ext.form.field.Base
  13512. * var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
  13513. *
  13514. * @param {String} xtype The xtype to check for this Component
  13515. * @param {Boolean} [shallow=false] True to check whether this Component is directly of the specified xtype, false to
  13516. * check whether this Component is descended from the xtype.
  13517. * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
  13518. */
  13519. isXType: function(xtype, shallow) {
  13520. if (shallow) {
  13521. return this.xtype === xtype;
  13522. }
  13523. else {
  13524. return this.xtypesMap[xtype];
  13525. }
  13526. },
  13527. /**
  13528. * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all available xtypes, see the
  13529. * {@link Ext.Component} header.
  13530. *
  13531. * **If using your own subclasses, be aware that a Component must register its own xtype to participate in
  13532. * determination of inherited xtypes.**
  13533. *
  13534. * Example usage:
  13535. *
  13536. * var t = new Ext.form.field.Text();
  13537. * alert(t.getXTypes()); // alerts 'component/field/textfield'
  13538. *
  13539. * @return {String} The xtype hierarchy string
  13540. */
  13541. getXTypes: function() {
  13542. var self = this.self,
  13543. xtypes, parentPrototype, parentXtypes;
  13544. if (!self.xtypes) {
  13545. xtypes = [];
  13546. parentPrototype = this;
  13547. while (parentPrototype) {
  13548. parentXtypes = parentPrototype.xtypes;
  13549. if (parentXtypes !== undefined) {
  13550. xtypes.unshift.apply(xtypes, parentXtypes);
  13551. }
  13552. parentPrototype = parentPrototype.superclass;
  13553. }
  13554. self.xtypeChain = xtypes;
  13555. self.xtypes = xtypes.join('/');
  13556. }
  13557. return self.xtypes;
  13558. },
  13559. /**
  13560. * Update the content area of a component.
  13561. * @param {String/Object} htmlOrData If this component has been configured with a template via the tpl config then
  13562. * it will use this argument as data to populate the template. If this component was not configured with a template,
  13563. * the components content area will be updated via Ext.Element update
  13564. * @param {Boolean} [loadScripts=false] Only legitimate when using the html configuration.
  13565. * @param {Function} [callback] Only legitimate when using the html configuration. Callback to execute when
  13566. * scripts have finished loading
  13567. */
  13568. update : function(htmlOrData, loadScripts, cb) {
  13569. var me = this;
  13570. if (me.tpl && !Ext.isString(htmlOrData)) {
  13571. me.data = htmlOrData;
  13572. if (me.rendered) {
  13573. me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
  13574. }
  13575. } else {
  13576. me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
  13577. if (me.rendered) {
  13578. me.getTargetEl().update(me.html, loadScripts, cb);
  13579. }
  13580. }
  13581. if (me.rendered) {
  13582. me.updateLayout();
  13583. }
  13584. },
  13585. /**
  13586. * Convenience function to hide or show this component by boolean.
  13587. * @param {Boolean} visible True to show, false to hide
  13588. * @return {Ext.Component} this
  13589. */
  13590. setVisible : function(visible) {
  13591. return this[visible ? 'show': 'hide']();
  13592. },
  13593. /**
  13594. * Returns true if this component is visible.
  13595. *
  13596. * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
  13597. * determine whether this Component is truly visible to the user.
  13598. *
  13599. * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
  13600. * dynamically laid out UIs in a hidden Container before showing them.
  13601. *
  13602. * @return {Boolean} True if this component is visible, false otherwise.
  13603. */
  13604. isVisible: function(deep) {
  13605. var me = this,
  13606. child = me,
  13607. visible = me.rendered && !me.hidden,
  13608. ancestor = me.ownerCt;
  13609. // Clear hiddenOwnerCt property
  13610. me.hiddenAncestor = false;
  13611. if (me.destroyed) {
  13612. return false;
  13613. }
  13614. if (deep && visible && ancestor) {
  13615. while (ancestor) {
  13616. // If any ancestor is hidden, then this is hidden.
  13617. // If an ancestor Panel (only Panels have a collapse method) is collapsed,
  13618. // then its layoutTarget (body) is hidden, so this is hidden unless its within a
  13619. // docked item; they are still visible when collapsed (Unless they themseves are hidden)
  13620. if (ancestor.hidden || (ancestor.collapsed &&
  13621. !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
  13622. // Store hiddenOwnerCt property if needed
  13623. me.hiddenAncestor = ancestor;
  13624. visible = false;
  13625. break;
  13626. }
  13627. child = ancestor;
  13628. ancestor = ancestor.ownerCt;
  13629. }
  13630. }
  13631. return visible;
  13632. },
  13633. onBoxReady: function(){
  13634. var me = this;
  13635. if (me.disableOnBoxReady) {
  13636. me.onDisable();
  13637. } else if (me.enableOnBoxReady) {
  13638. me.onEnable();
  13639. }
  13640. },
  13641. /**
  13642. * Enable the component
  13643. * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
  13644. */
  13645. enable: function(silent) {
  13646. var me = this;
  13647. delete me.disableOnBoxReady;
  13648. me.removeCls(me.disabledCls);
  13649. if (me.rendered) {
  13650. me.onEnable();
  13651. } else {
  13652. me.enableOnBoxReady = true;
  13653. }
  13654. me.disabled = false;
  13655. delete me.resetDisable;
  13656. if (silent !== true) {
  13657. me.fireEvent('enable', me);
  13658. }
  13659. return me;
  13660. },
  13661. /**
  13662. * Disable the component.
  13663. * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
  13664. */
  13665. disable: function(silent) {
  13666. var me = this;
  13667. delete me.enableOnBoxReady;
  13668. me.addCls(me.disabledCls);
  13669. if (me.rendered) {
  13670. me.onDisable();
  13671. } else {
  13672. me.disableOnBoxReady = true;
  13673. }
  13674. me.disabled = true;
  13675. if (silent !== true) {
  13676. delete me.resetDisable;
  13677. me.fireEvent('disable', me);
  13678. }
  13679. return me;
  13680. },
  13681. /**
  13682. * Allows addition of behavior to the enable operation.
  13683. * After calling the superclass’s onEnable, the Component will be enabled.
  13684. *
  13685. * @template
  13686. * @protected
  13687. */
  13688. onEnable: function() {
  13689. if (this.maskOnDisable) {
  13690. this.el.dom.disabled = false;
  13691. this.unmask();
  13692. }
  13693. },
  13694. /**
  13695. * Allows addition of behavior to the disable operation.
  13696. * After calling the superclass’s onDisable, the Component will be disabled.
  13697. *
  13698. * @template
  13699. * @protected
  13700. */
  13701. onDisable : function() {
  13702. if (this.maskOnDisable) {
  13703. this.el.dom.disabled = true;
  13704. this.mask();
  13705. }
  13706. },
  13707. mask: function() {
  13708. var box = this.lastBox,
  13709. target = this.getMaskTarget(),
  13710. args = [];
  13711. // Pass it the height of our element if we know it.
  13712. if (box) {
  13713. args[2] = box.height;
  13714. }
  13715. target.mask.apply(target, args);
  13716. },
  13717. unmask: function() {
  13718. this.getMaskTarget().unmask();
  13719. },
  13720. getMaskTarget: function(){
  13721. return this.el
  13722. },
  13723. /**
  13724. * Method to determine whether this Component is currently disabled.
  13725. * @return {Boolean} the disabled state of this Component.
  13726. */
  13727. isDisabled : function() {
  13728. return this.disabled;
  13729. },
  13730. /**
  13731. * Enable or disable the component.
  13732. * @param {Boolean} disabled True to disable.
  13733. */
  13734. setDisabled : function(disabled) {
  13735. return this[disabled ? 'disable': 'enable']();
  13736. },
  13737. /**
  13738. * Method to determine whether this Component is currently set to hidden.
  13739. * @return {Boolean} the hidden state of this Component.
  13740. */
  13741. isHidden : function() {
  13742. return this.hidden;
  13743. },
  13744. /**
  13745. * Adds a CSS class to the top level element representing this component.
  13746. * @param {String/String[]} cls The CSS class name to add
  13747. * @return {Ext.Component} Returns the Component to allow method chaining.
  13748. */
  13749. addCls : function(cls) {
  13750. var me = this,
  13751. el = me.rendered ? me.el : me.protoEl;
  13752. el.addCls.apply(el, arguments);
  13753. return me;
  13754. },
  13755. /**
  13756. * @inheritdoc Ext.AbstractComponent#addCls
  13757. * @deprecated 4.1 Use {@link #addCls} instead.
  13758. */
  13759. addClass : function() {
  13760. return this.addCls.apply(this, arguments);
  13761. },
  13762. /**
  13763. * Checks if the specified CSS class exists on this element's DOM node.
  13764. * @param {String} className The CSS class to check for
  13765. * @return {Boolean} True if the class exists, else false
  13766. * @method
  13767. */
  13768. hasCls: function (cls) {
  13769. var me = this,
  13770. el = me.rendered ? me.el : me.protoEl;
  13771. return el.hasCls.apply(el, arguments);
  13772. },
  13773. /**
  13774. * Removes a CSS class from the top level element representing this component.
  13775. * @param {String/String[]} cls The CSS class name to remove
  13776. * @returns {Ext.Component} Returns the Component to allow method chaining.
  13777. */
  13778. removeCls : function(cls) {
  13779. var me = this,
  13780. el = me.rendered ? me.el : me.protoEl;
  13781. el.removeCls.apply(el, arguments);
  13782. return me;
  13783. },
  13784. removeClass : function() {
  13785. if (Ext.isDefined(Ext.global.console)) {
  13786. Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
  13787. }
  13788. return this.removeCls.apply(this, arguments);
  13789. },
  13790. addOverCls: function() {
  13791. var me = this;
  13792. if (!me.disabled) {
  13793. me.el.addCls(me.overCls);
  13794. }
  13795. },
  13796. removeOverCls: function() {
  13797. this.el.removeCls(this.overCls);
  13798. },
  13799. addListener : function(element, listeners, scope, options) {
  13800. var me = this,
  13801. fn,
  13802. option;
  13803. if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
  13804. if (options.element) {
  13805. fn = listeners;
  13806. listeners = {};
  13807. listeners[element] = fn;
  13808. element = options.element;
  13809. if (scope) {
  13810. listeners.scope = scope;
  13811. }
  13812. for (option in options) {
  13813. if (options.hasOwnProperty(option)) {
  13814. if (me.eventOptionsRe.test(option)) {
  13815. listeners[option] = options[option];
  13816. }
  13817. }
  13818. }
  13819. }
  13820. // At this point we have a variable called element,
  13821. // and a listeners object that can be passed to on
  13822. if (me[element] && me[element].on) {
  13823. me.mon(me[element], listeners);
  13824. } else {
  13825. me.afterRenderEvents = me.afterRenderEvents || {};
  13826. if (!me.afterRenderEvents[element]) {
  13827. me.afterRenderEvents[element] = [];
  13828. }
  13829. me.afterRenderEvents[element].push(listeners);
  13830. }
  13831. }
  13832. return me.mixins.observable.addListener.apply(me, arguments);
  13833. },
  13834. // inherit docs
  13835. removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
  13836. var me = this,
  13837. element = managedListener.options ? managedListener.options.element : null;
  13838. if (element) {
  13839. element = me[element];
  13840. if (element && element.un) {
  13841. if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
  13842. element.un(managedListener.ename, managedListener.fn, managedListener.scope);
  13843. if (!isClear) {
  13844. Ext.Array.remove(me.managedListeners, managedListener);
  13845. }
  13846. }
  13847. }
  13848. } else {
  13849. return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
  13850. }
  13851. },
  13852. /**
  13853. * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
  13854. * @return {Ext.container.Container} the Container which owns this Component.
  13855. */
  13856. getBubbleTarget : function() {
  13857. return this.ownerCt;
  13858. },
  13859. /**
  13860. * Method to determine whether this Component is floating.
  13861. * @return {Boolean} the floating state of this component.
  13862. */
  13863. isFloating : function() {
  13864. return this.floating;
  13865. },
  13866. /**
  13867. * Method to determine whether this Component is draggable.
  13868. * @return {Boolean} the draggable state of this component.
  13869. */
  13870. isDraggable : function() {
  13871. return !!this.draggable;
  13872. },
  13873. /**
  13874. * Method to determine whether this Component is droppable.
  13875. * @return {Boolean} the droppable state of this component.
  13876. */
  13877. isDroppable : function() {
  13878. return !!this.droppable;
  13879. },
  13880. /**
  13881. * Method to manage awareness of when components are added to their
  13882. * respective Container, firing an #added event. References are
  13883. * established at add time rather than at render time.
  13884. *
  13885. * Allows addition of behavior when a Component is added to a
  13886. * Container. At this stage, the Component is in the parent
  13887. * Container's collection of child items. After calling the
  13888. * superclass's onAdded, the ownerCt reference will be present,
  13889. * and if configured with a ref, the refOwner will be set.
  13890. *
  13891. * @param {Ext.container.Container} container Container which holds the component
  13892. * @param {Number} pos Position at which the component was added
  13893. *
  13894. * @template
  13895. * @protected
  13896. */
  13897. onAdded : function(container, pos) {
  13898. var me = this;
  13899. me.ownerCt = container;
  13900. if (me.hasListeners.added) {
  13901. me.fireEvent('added', me, container, pos);
  13902. }
  13903. },
  13904. /**
  13905. * Method to manage awareness of when components are removed from their
  13906. * respective Container, firing a #removed event. References are properly
  13907. * cleaned up after removing a component from its owning container.
  13908. *
  13909. * Allows addition of behavior when a Component is removed from
  13910. * its parent Container. At this stage, the Component has been
  13911. * removed from its parent Container's collection of child items,
  13912. * but has not been destroyed (It will be destroyed if the parent
  13913. * Container's autoDestroy is true, or if the remove call was
  13914. * passed a truthy second parameter). After calling the
  13915. * superclass's onRemoved, the ownerCt and the refOwner will not
  13916. * be present.
  13917. * @param {Boolean} destroying Will be passed as true if the Container performing the remove operation will delete this
  13918. * Component upon remove.
  13919. *
  13920. * @template
  13921. * @protected
  13922. */
  13923. onRemoved : function(destroying) {
  13924. var me = this;
  13925. if (me.hasListeners.removed) {
  13926. me.fireEvent('removed', me, me.ownerCt);
  13927. }
  13928. delete me.ownerCt;
  13929. },
  13930. /**
  13931. * Invoked before the Component is destroyed.
  13932. *
  13933. * @method
  13934. * @template
  13935. * @protected
  13936. */
  13937. beforeDestroy : Ext.emptyFn,
  13938. /**
  13939. * Allows addition of behavior to the resize operation.
  13940. *
  13941. * Called when Ext.resizer.Resizer#drag event is fired.
  13942. *
  13943. * @method
  13944. * @template
  13945. * @protected
  13946. */
  13947. onResize : Ext.emptyFn,
  13948. /**
  13949. * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
  13950. * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
  13951. *
  13952. * @param {Number/String/Object} width The new width to set. This may be one of:
  13953. *
  13954. * - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  13955. * - A String used to set the CSS width style.
  13956. * - A size object in the format `{width: widthValue, height: heightValue}`.
  13957. * - `undefined` to leave the width unchanged.
  13958. *
  13959. * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
  13960. * This may be one of:
  13961. *
  13962. * - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  13963. * - A String used to set the CSS height style. Animation may **not** be used.
  13964. * - `undefined` to leave the height unchanged.
  13965. *
  13966. * @return {Ext.Component} this
  13967. */
  13968. setSize : function(width, height) {
  13969. var me = this;
  13970. // support for standard size objects
  13971. if (width && typeof width == 'object') {
  13972. height = width.height;
  13973. width = width.width;
  13974. }
  13975. // Constrain within configured maxima
  13976. if (typeof width == 'number') {
  13977. me.width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
  13978. } else if (width === null) {
  13979. delete me.width;
  13980. }
  13981. if (typeof height == 'number') {
  13982. me.height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
  13983. } else if (height === null) {
  13984. delete me.height;
  13985. }
  13986. // If not rendered, all we need to is set the properties.
  13987. // The initial layout will set the size
  13988. if (me.rendered && me.isVisible()) {
  13989. // If we are changing size, then we are not the root.
  13990. me.updateLayout({
  13991. isRoot: false
  13992. });
  13993. }
  13994. return me;
  13995. },
  13996. /**
  13997. * Determines whether this Component is the root of a layout. This returns `true` if
  13998. * this component can run its layout without assistance from or impact on its owner.
  13999. * If this component cannot run its layout given these restrictions, `false` is returned
  14000. * and its owner will be considered as the next candidate for the layout root.
  14001. *
  14002. * Setting the {@link #_isLayoutRoot} property to `true` causes this method to always
  14003. * return `true`. This may be useful when updating a layout of a Container which shrink
  14004. * wraps content, and you know that it will not change size, and so can safely be the
  14005. * topmost participant in the layout run.
  14006. * @protected
  14007. */
  14008. isLayoutRoot: function() {
  14009. var me = this,
  14010. ownerLayout = me.ownerLayout;
  14011. // Return true if we have been explicitly flagged as the layout root, or if we are floating.
  14012. // Sometimes floating Components get an ownerCt ref injected into them which is *not* a true ownerCt, merely
  14013. // an upward link for reference purposes. For example a grid column menu is linked to the
  14014. // owning header via an ownerCt reference.
  14015. if (!ownerLayout || me._isLayoutRoot || me.floating) {
  14016. return true;
  14017. }
  14018. return ownerLayout.isItemLayoutRoot(me);
  14019. },
  14020. /**
  14021. * Returns true if layout is suspended for this component. This can come from direct
  14022. * suspension of this component's layout activity ({@link #suspendLayouts}) or if one
  14023. * of this component's containers is suspended.
  14024. *
  14025. * @return {Boolean} True layout of this component is suspended.
  14026. */
  14027. isLayoutSuspended: function () {
  14028. var comp = this,
  14029. ownerLayout;
  14030. while (comp) {
  14031. if (comp.layoutSuspendCount || comp.suspendLayout) {
  14032. return true;
  14033. }
  14034. ownerLayout = comp.ownerLayout;
  14035. if (!ownerLayout) {
  14036. break;
  14037. }
  14038. // TODO - what about suspending a Layout instance?
  14039. // this works better than ownerCt since ownerLayout means "is managed by" in
  14040. // the proper sense... some floating components have ownerCt but won't have an
  14041. // ownerLayout
  14042. comp = ownerLayout.owner;
  14043. }
  14044. return false;
  14045. },
  14046. /**
  14047. * Updates this component's layout. If this update effects this components {@link #ownerCt},
  14048. * that component's `updateLayout` method will be called to perform the layout instead.
  14049. * Otherwise, just this component (and its child items) will layout.
  14050. *
  14051. * @param {Object} options An object with layout options.
  14052. * @param {Boolean} options.defer True if this layout should be deferred.
  14053. * @param {Boolean} options.isRoot True if this layout should be the root of the layout.
  14054. */
  14055. updateLayout: function (options) {
  14056. var me = this,
  14057. defer,
  14058. isRoot = options && options.isRoot;
  14059. if (!me.rendered || me.layoutSuspendCount || me.suspendLayout) {
  14060. return;
  14061. }
  14062. if (me.hidden) {
  14063. Ext.AbstractComponent.cancelLayout(me);
  14064. } else if (typeof isRoot != 'boolean') {
  14065. isRoot = me.isLayoutRoot();
  14066. }
  14067. // if we aren't the root, see if our ownerLayout will handle it...
  14068. if (isRoot || !me.ownerLayout || !me.ownerLayout.onContentChange(me)) {
  14069. // either we are the root or our ownerLayout doesn't care
  14070. if (!me.isLayoutSuspended()) {
  14071. // we aren't suspended (knew that), but neither is any of our ownerCt's...
  14072. defer = (options && options.hasOwnProperty('defer')) ? options.defer : me.deferLayouts;
  14073. Ext.AbstractComponent.updateLayout(me, defer);
  14074. }
  14075. }
  14076. },
  14077. /**
  14078. * Returns an object that describes how this component's width and height is managed. These
  14079. * objects are shared and should not be modified.
  14080. *
  14081. * @return {Object} The size model for this component.
  14082. * @return {Object} return.width The width aspect of this component's size model.
  14083. * @return {Boolean} return.width.auto True if width is either natural or shrinkWrap (not fixed).
  14084. * @return {Boolean} return.width.calculated True if width is calculated by a layout.
  14085. * @return {Boolean} return.width.configured True if width is specified on this component.
  14086. * @return {Boolean} return.width.fixed True if width is either calculated or configured.
  14087. * @return {Boolean} return.width.natural True if width is determined by CSS and does not depend on content.
  14088. * @return {Boolean} return.width.shrinkWrap True if width is determined by content.
  14089. * @return {Object} return.height The height aspect of this component's size model.
  14090. * @return {Boolean} return.height.auto True if height is either natural or shrinkWrap (not fixed).
  14091. * @return {Boolean} return.height.calculated True if height is calculated by a layout.
  14092. * @return {Boolean} return.height.configured True if height is specified on this component.
  14093. * @return {Boolean} return.height.fixed True if height is either calculated or configured.
  14094. * @return {Boolean} return.height.natural True if height is determined by CSS and does not depend on content.
  14095. * @return {Boolean} return.height.shrinkWrap True if height is determined by content.
  14096. */
  14097. getSizeModel: function (ownerCtSizeModel) {
  14098. var me = this,
  14099. Layout = Ext.layout.Layout.prototype,
  14100. models = Layout.sizeModels,
  14101. heightModel, ownerLayout, policy, shrinkWrap, widthModel;
  14102. if (typeof me.width == 'number') {
  14103. widthModel = models.configured;
  14104. }
  14105. if (typeof me.height == 'number') {
  14106. heightModel = models.configured;
  14107. }
  14108. if (!widthModel || !heightModel) {
  14109. if (me.floating) {
  14110. policy = Layout.autoSizePolicy;
  14111. shrinkWrap = 3;
  14112. } else {
  14113. if (!(ownerLayout = me.ownerLayout)) {
  14114. policy = Layout.autoSizePolicy;
  14115. shrinkWrap = me.shrinkWrap;
  14116. } else {
  14117. policy = ownerLayout.getItemSizePolicy(me);
  14118. shrinkWrap = ownerLayout.isItemShrinkWrap(me);
  14119. }
  14120. shrinkWrap = (shrinkWrap === true) ? 3 : (shrinkWrap || 0); // false->0, true->3
  14121. if (shrinkWrap !== 3) {
  14122. if (!ownerCtSizeModel) {
  14123. ownerCtSizeModel = me.ownerCt && me.ownerCt.getSizeModel();
  14124. }
  14125. if (ownerCtSizeModel) {
  14126. shrinkWrap |= (ownerCtSizeModel.width.shrinkWrap ? 1 : 0) | (ownerCtSizeModel.height.shrinkWrap ? 2 : 0);
  14127. }
  14128. }
  14129. }
  14130. if (!widthModel) {
  14131. if (!policy.setsWidth) {
  14132. widthModel = (shrinkWrap & 1) ? models.shrinkWrap : models.natural;
  14133. } else if (policy.readsWidth) {
  14134. widthModel = (shrinkWrap & 1) ? models.calculatedFromShrinkWrap :
  14135. models.calculatedFromNatural;
  14136. } else {
  14137. widthModel = models.calculated;
  14138. }
  14139. }
  14140. if (!heightModel) {
  14141. if (!policy.setsHeight) {
  14142. heightModel = (shrinkWrap & 2) ? models.shrinkWrap : models.natural;
  14143. } else if (policy.readsHeight) {
  14144. heightModel = (shrinkWrap & 2) ? models.calculatedFromShrinkWrap :
  14145. models.calculatedFromNatural;
  14146. } else {
  14147. heightModel = models.calculated;
  14148. }
  14149. }
  14150. }
  14151. return {
  14152. width: widthModel,
  14153. height: heightModel
  14154. };
  14155. },
  14156. isDescendant: function(ancestor) {
  14157. if (ancestor.isContainer) {
  14158. for (var c = this.ownerCt; c; c = c.ownerCt) {
  14159. if (c === ancestor) {
  14160. return true;
  14161. }
  14162. }
  14163. }
  14164. return false;
  14165. },
  14166. /**
  14167. * This method needs to be called whenever you change something on this component that requires the Component's
  14168. * layout to be recalculated.
  14169. * @return {Ext.container.Container} this
  14170. */
  14171. doComponentLayout : function() {
  14172. this.updateLayout();
  14173. return this;
  14174. },
  14175. /**
  14176. * Forces this component to redo its componentLayout.
  14177. * @deprecated 4.1.0 Use {@link #updateLayout} instead.
  14178. */
  14179. forceComponentLayout: function () {
  14180. this.updateLayout();
  14181. },
  14182. // @private
  14183. setComponentLayout : function(layout) {
  14184. var currentLayout = this.componentLayout;
  14185. if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
  14186. currentLayout.setOwner(null);
  14187. }
  14188. this.componentLayout = layout;
  14189. layout.setOwner(this);
  14190. },
  14191. getComponentLayout : function() {
  14192. var me = this;
  14193. if (!me.componentLayout || !me.componentLayout.isLayout) {
  14194. me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
  14195. }
  14196. return me.componentLayout;
  14197. },
  14198. /**
  14199. * Called by the layout system after the Component has been layed out.
  14200. *
  14201. * @param {Number} width The width that was set
  14202. * @param {Number} height The height that was set
  14203. * @param {Number} oldWidth The old width. <code>undefined</code> if this was the initial layout.
  14204. * @param {Number} oldHeight The old height. <code>undefined</code> if this was the initial layout.
  14205. *
  14206. * @template
  14207. * @protected
  14208. */
  14209. afterComponentLayout: function(width, height, oldWidth, oldHeight) {
  14210. var me = this;
  14211. if (++me.componentLayoutCounter === 1) {
  14212. me.afterFirstLayout();
  14213. }
  14214. if (me.hasListeners.resize && (width !== oldWidth || height !== oldHeight)) {
  14215. me.fireEvent('resize', me, width, height, oldWidth, oldHeight);
  14216. }
  14217. },
  14218. /**
  14219. * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout from
  14220. * being executed.
  14221. *
  14222. * @param {Number} adjWidth The box-adjusted width that was set
  14223. * @param {Number} adjHeight The box-adjusted height that was set
  14224. *
  14225. * @template
  14226. * @protected
  14227. */
  14228. beforeComponentLayout: function(width, height) {
  14229. return true;
  14230. },
  14231. /**
  14232. * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
  14233. * method fires the {@link #move} event.
  14234. * @param {Number} left The new left
  14235. * @param {Number} top The new top
  14236. * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
  14237. * animation configuration.
  14238. * @return {Ext.Component} this
  14239. */
  14240. setPosition : function(x, y, animate) {
  14241. var me = this,
  14242. pos = me.beforeSetPosition.apply(me, arguments);
  14243. if (pos && me.rendered) {
  14244. // Convert position WRT RTL
  14245. pos = me.convertPosition(pos);
  14246. if (animate) {
  14247. me.stopAnimation();
  14248. me.animate(Ext.apply({
  14249. duration: 1000,
  14250. listeners: {
  14251. afteranimate: Ext.Function.bind(me.afterSetPosition, me, [pos.left, pos.top])
  14252. },
  14253. to: pos
  14254. }, animate));
  14255. } else {
  14256. // Must use Element's methods to set element position because, if it is a Layer (floater), it may need to sync a shadow
  14257. // We must also only set the properties which are defined because Element.setLeftTop autos any undefined coordinates
  14258. if (pos.left !== undefined && pos.top !== undefined) {
  14259. me.el.setLeftTop(pos.left, pos.top);
  14260. } else if (pos.left !== undefined) {
  14261. me.el.setLeft(pos.left);
  14262. } else if (pos.top !==undefined) {
  14263. me.el.setTop(pos.top);
  14264. }
  14265. me.afterSetPosition(pos.left, pos.top);
  14266. }
  14267. }
  14268. return me;
  14269. },
  14270. /**
  14271. * @private Template method called before a Component is positioned.
  14272. */
  14273. beforeSetPosition: function (x, y, animate) {
  14274. var pos, x0;
  14275. // decode the position arguments:
  14276. if (!x || Ext.isNumber(x)) {
  14277. pos = { x: x, y : y, anim: animate };
  14278. } else if (Ext.isNumber(x0 = x[0])) { // an array of [x, y]
  14279. pos = { x : x0, y : x[1], anim: y };
  14280. } else {
  14281. pos = { x: x.x, y: x.y, anim: y }; // already an object w/ x & y properties
  14282. }
  14283. pos.hasX = Ext.isNumber(pos.x);
  14284. pos.hasY = Ext.isNumber(pos.y);
  14285. // store the position as specified:
  14286. this.x = pos.x;
  14287. this.y = pos.y;
  14288. return (pos.hasX || pos.hasY) ? pos : null;
  14289. },
  14290. /**
  14291. * Template method called after a Component has been positioned.
  14292. *
  14293. * @param {Number} x
  14294. * @param {Number} y
  14295. *
  14296. * @template
  14297. * @protected
  14298. */
  14299. afterSetPosition: function(x, y) {
  14300. var me = this;
  14301. me.onPosition(x, y);
  14302. if (me.hasListeners.move) {
  14303. me.fireEvent('move', me, x, y);
  14304. }
  14305. },
  14306. /**
  14307. * This method converts an "{x: x, y: y}" object to a "{left: x+'px', top: y+'px'}" object.
  14308. * The returned object contains the styles to set to effect the position. This is
  14309. * overridden in RTL mode to be "{right: x, top: y}".
  14310. * @private
  14311. */
  14312. convertPosition: function (pos, withUnits) {
  14313. var ret = {},
  14314. El = Ext.Element;
  14315. if (pos.hasX) {
  14316. ret.left = withUnits ? El.addUnits(pos.x) : pos.x;
  14317. }
  14318. if (pos.hasY) {
  14319. ret.top = withUnits ? El.addUnits(pos.y) : pos.y;
  14320. }
  14321. return ret;
  14322. },
  14323. /**
  14324. * Called after the component is moved, this method is empty by default but can be implemented by any
  14325. * subclass that needs to perform custom logic after a move occurs.
  14326. *
  14327. * @param {Number} x The new x position
  14328. * @param {Number} y The new y position
  14329. *
  14330. * @template
  14331. * @protected
  14332. */
  14333. onPosition: Ext.emptyFn,
  14334. /**
  14335. * Sets the width of the component. This method fires the {@link #resize} event.
  14336. *
  14337. * @param {Number} width The new width to setThis may be one of:
  14338. *
  14339. * - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  14340. * - A String used to set the CSS width style.
  14341. *
  14342. * @return {Ext.Component} this
  14343. */
  14344. setWidth : function(width) {
  14345. return this.setSize(width);
  14346. },
  14347. /**
  14348. * Sets the height of the component. This method fires the {@link #resize} event.
  14349. *
  14350. * @param {Number} height The new height to set. This may be one of:
  14351. *
  14352. * - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
  14353. * - A String used to set the CSS height style.
  14354. * - _undefined_ to leave the height unchanged.
  14355. *
  14356. * @return {Ext.Component} this
  14357. */
  14358. setHeight : function(height) {
  14359. return this.setSize(undefined, height);
  14360. },
  14361. /**
  14362. * Gets the current size of the component's underlying element.
  14363. * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
  14364. */
  14365. getSize : function() {
  14366. return this.el.getSize();
  14367. },
  14368. /**
  14369. * Gets the current width of the component's underlying element.
  14370. * @return {Number}
  14371. */
  14372. getWidth : function() {
  14373. return this.el.getWidth();
  14374. },
  14375. /**
  14376. * Gets the current height of the component's underlying element.
  14377. * @return {Number}
  14378. */
  14379. getHeight : function() {
  14380. return this.el.getHeight();
  14381. },
  14382. /**
  14383. * Gets the {@link Ext.ComponentLoader} for this Component.
  14384. * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
  14385. */
  14386. getLoader: function(){
  14387. var me = this,
  14388. autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
  14389. loader = me.loader || autoLoad;
  14390. if (loader) {
  14391. if (!loader.isLoader) {
  14392. me.loader = new Ext.ComponentLoader(Ext.apply({
  14393. target: me,
  14394. autoLoad: autoLoad
  14395. }, loader));
  14396. } else {
  14397. loader.setTarget(me);
  14398. }
  14399. return me.loader;
  14400. }
  14401. return null;
  14402. },
  14403. /**
  14404. * Sets the dock position of this component in its parent panel. Note that this only has effect if this item is part
  14405. * of the dockedItems collection of a parent that has a DockLayout (note that any Panel has a DockLayout by default)
  14406. * @param {Object} dock The dock position.
  14407. * @param {Boolean} [layoutParent=false] True to re-layout parent.
  14408. * @return {Ext.Component} this
  14409. */
  14410. setDocked : function(dock, layoutParent) {
  14411. var me = this;
  14412. me.dock = dock;
  14413. if (layoutParent && me.ownerCt && me.rendered) {
  14414. me.ownerCt.updateLayout();
  14415. }
  14416. return me;
  14417. },
  14418. /**
  14419. *
  14420. * @param {String/Number} border The border, see {@link #border}. If a falsey value is passed
  14421. * the border will be removed.
  14422. */
  14423. setBorder: function(border, /* private */ targetEl) {
  14424. var me = this,
  14425. initial = !!targetEl;
  14426. if (me.rendered || initial) {
  14427. if (!initial) {
  14428. targetEl = me.el;
  14429. }
  14430. if (!border) {
  14431. border = 0;
  14432. } else {
  14433. border = Ext.Element.unitizeBox((border === true) ? 1 : border);
  14434. }
  14435. targetEl.setStyle('border-width', border);
  14436. if (!initial) {
  14437. me.updateLayout();
  14438. }
  14439. }
  14440. me.border = border;
  14441. },
  14442. onDestroy : function() {
  14443. var me = this;
  14444. if (me.monitorResize && Ext.EventManager.resizeEvent) {
  14445. Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
  14446. }
  14447. // Destroying the floatingItems ZIndexManager will also destroy descendant floating Components
  14448. Ext.destroy(
  14449. me.componentLayout,
  14450. me.loadMask,
  14451. me.floatingItems
  14452. );
  14453. },
  14454. /**
  14455. * Destroys the Component.
  14456. */
  14457. destroy : function() {
  14458. var me = this,
  14459. selectors = me.renderSelectors,
  14460. selector,
  14461. el;
  14462. if (!me.isDestroyed) {
  14463. if (!me.hasListeners.beforedestroy || me.fireEvent('beforedestroy', me) !== false) {
  14464. me.destroying = true;
  14465. me.beforeDestroy();
  14466. if (me.floating) {
  14467. delete me.floatParent;
  14468. // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
  14469. // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
  14470. if (me.zIndexManager) {
  14471. me.zIndexManager.unregister(me);
  14472. }
  14473. } else if (me.ownerCt && me.ownerCt.remove) {
  14474. me.ownerCt.remove(me, false);
  14475. }
  14476. me.onDestroy();
  14477. // Attempt to destroy all plugins
  14478. Ext.destroy(me.plugins);
  14479. if (me.hasListeners.destroy) {
  14480. me.fireEvent('destroy', me);
  14481. }
  14482. Ext.ComponentManager.unregister(me);
  14483. me.mixins.state.destroy.call(me);
  14484. me.clearListeners();
  14485. // make sure we clean up the element references after removing all events
  14486. if (me.rendered) {
  14487. // In case we are queued for a layout.
  14488. Ext.AbstractComponent.cancelLayout(me);
  14489. if (!me.preserveElOnDestroy) {
  14490. me.el.remove();
  14491. }
  14492. me.mixins.elementCt.destroy.call(me); // removes childEls
  14493. if (selectors) {
  14494. for (selector in selectors) {
  14495. if (selectors.hasOwnProperty(selector)) {
  14496. el = me[selector];
  14497. if (el) { // in case any other code may have already removed it
  14498. delete me[selector];
  14499. el.remove();
  14500. }
  14501. }
  14502. }
  14503. }
  14504. delete me.el;
  14505. delete me.frameBody;
  14506. delete me.rendered;
  14507. }
  14508. me.destroying = false;
  14509. me.isDestroyed = true;
  14510. }
  14511. }
  14512. },
  14513. /**
  14514. * Retrieves a plugin by its pluginId which has been bound to this component.
  14515. * @param {Object} pluginId
  14516. * @return {Ext.AbstractPlugin} plugin instance.
  14517. */
  14518. getPlugin: function(pluginId) {
  14519. var i = 0,
  14520. plugins = this.plugins,
  14521. ln = plugins.length;
  14522. for (; i < ln; i++) {
  14523. if (plugins[i].pluginId === pluginId) {
  14524. return plugins[i];
  14525. }
  14526. }
  14527. },
  14528. /**
  14529. * Determines whether this component is the descendant of a particular container.
  14530. * @param {Ext.Container} container
  14531. * @return {Boolean} True if it is.
  14532. */
  14533. isDescendantOf: function(container) {
  14534. return !!this.findParentBy(function(p){
  14535. return p === container;
  14536. });
  14537. }
  14538. }, function() {
  14539. var abstractComponent = this;
  14540. abstractComponent.createAlias({
  14541. on: 'addListener',
  14542. prev: 'previousSibling',
  14543. next: 'nextSibling'
  14544. });
  14545. Ext.resumeLayouts = function (flush) {
  14546. abstractComponent.resumeLayouts(flush);
  14547. };
  14548. Ext.suspendLayouts = function () {
  14549. abstractComponent.suspendLayouts();
  14550. };
  14551. /**
  14552. *
  14553. * Utility wrapper that suspends layouts of all components for the duration of a given function.
  14554. * @param {Function} fn The function to execute.
  14555. * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
  14556. */
  14557. Ext.batchLayouts = function(fn, scope) {
  14558. abstractComponent.suspendLayouts();
  14559. // Invoke the function
  14560. fn.call(scope);
  14561. abstractComponent.resumeLayouts(true);
  14562. };
  14563. });
  14564. /**
  14565. * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
  14566. * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
  14567. * class. Components may be added to a Container through the {@link Ext.container.Container#cfg-items items} config option
  14568. * at the time the Container is created, or they may be added dynamically via the
  14569. * {@link Ext.container.Container#method-add add} method.
  14570. *
  14571. * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
  14572. *
  14573. * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
  14574. * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
  14575. *
  14576. * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
  14577. * subclass Component.
  14578. *
  14579. * See the Creating new UI controls chapter in [Component Guide][1] for details on how and to either extend
  14580. * or augment Ext JS base classes to create custom Components.
  14581. *
  14582. * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
  14583. * like {@link #getXType} and {@link #isXType}. See the [Component Guide][1] for more information on xtypes and the
  14584. * Component hierarchy.
  14585. *
  14586. * This is the list of all valid xtypes:
  14587. *
  14588. * xtype Class
  14589. * ------------- ------------------
  14590. * button {@link Ext.button.Button}
  14591. * buttongroup {@link Ext.container.ButtonGroup}
  14592. * colorpalette {@link Ext.picker.Color}
  14593. * component {@link Ext.Component}
  14594. * container {@link Ext.container.Container}
  14595. * cycle {@link Ext.button.Cycle}
  14596. * dataview {@link Ext.view.View}
  14597. * datepicker {@link Ext.picker.Date}
  14598. * editor {@link Ext.Editor}
  14599. * editorgrid {@link Ext.grid.plugin.Editing}
  14600. * grid {@link Ext.grid.Panel}
  14601. * multislider {@link Ext.slider.Multi}
  14602. * panel {@link Ext.panel.Panel}
  14603. * progressbar {@link Ext.ProgressBar}
  14604. * slider {@link Ext.slider.Single}
  14605. * splitbutton {@link Ext.button.Split}
  14606. * tabpanel {@link Ext.tab.Panel}
  14607. * treepanel {@link Ext.tree.Panel}
  14608. * viewport {@link Ext.container.Viewport}
  14609. * window {@link Ext.window.Window}
  14610. *
  14611. * Toolbar components
  14612. * ---------------------------------------
  14613. * pagingtoolbar {@link Ext.toolbar.Paging}
  14614. * toolbar {@link Ext.toolbar.Toolbar}
  14615. * tbfill {@link Ext.toolbar.Fill}
  14616. * tbitem {@link Ext.toolbar.Item}
  14617. * tbseparator {@link Ext.toolbar.Separator}
  14618. * tbspacer {@link Ext.toolbar.Spacer}
  14619. * tbtext {@link Ext.toolbar.TextItem}
  14620. *
  14621. * Menu components
  14622. * ---------------------------------------
  14623. * menu {@link Ext.menu.Menu}
  14624. * menucheckitem {@link Ext.menu.CheckItem}
  14625. * menuitem {@link Ext.menu.Item}
  14626. * menuseparator {@link Ext.menu.Separator}
  14627. * menutextitem {@link Ext.menu.Item}
  14628. *
  14629. * Form components
  14630. * ---------------------------------------
  14631. * form {@link Ext.form.Panel}
  14632. * checkbox {@link Ext.form.field.Checkbox}
  14633. * combo {@link Ext.form.field.ComboBox}
  14634. * datefield {@link Ext.form.field.Date}
  14635. * displayfield {@link Ext.form.field.Display}
  14636. * field {@link Ext.form.field.Base}
  14637. * fieldset {@link Ext.form.FieldSet}
  14638. * hidden {@link Ext.form.field.Hidden}
  14639. * htmleditor {@link Ext.form.field.HtmlEditor}
  14640. * label {@link Ext.form.Label}
  14641. * numberfield {@link Ext.form.field.Number}
  14642. * radio {@link Ext.form.field.Radio}
  14643. * radiogroup {@link Ext.form.RadioGroup}
  14644. * textarea {@link Ext.form.field.TextArea}
  14645. * textfield {@link Ext.form.field.Text}
  14646. * timefield {@link Ext.form.field.Time}
  14647. * trigger {@link Ext.form.field.Trigger}
  14648. *
  14649. * Chart components
  14650. * ---------------------------------------
  14651. * chart {@link Ext.chart.Chart}
  14652. * barchart {@link Ext.chart.series.Bar}
  14653. * columnchart {@link Ext.chart.series.Column}
  14654. * linechart {@link Ext.chart.series.Line}
  14655. * piechart {@link Ext.chart.series.Pie}
  14656. *
  14657. * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
  14658. * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
  14659. * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
  14660. *
  14661. * @example
  14662. * Ext.create('Ext.Component', {
  14663. * html: 'Hello world!',
  14664. * width: 300,
  14665. * height: 200,
  14666. * padding: 20,
  14667. * style: {
  14668. * color: '#FFFFFF',
  14669. * backgroundColor:'#000000'
  14670. * },
  14671. * renderTo: Ext.getBody()
  14672. * });
  14673. *
  14674. * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
  14675. * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
  14676. * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
  14677. * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
  14678. *
  14679. * [2]: #!/guide/components
  14680. */
  14681. Ext.define('Ext.Component', {
  14682. /* Begin Definitions */
  14683. alias: ['widget.component', 'widget.box'],
  14684. extend: 'Ext.AbstractComponent',
  14685. requires: [
  14686. 'Ext.util.DelayedTask'
  14687. ],
  14688. uses: [
  14689. 'Ext.Layer',
  14690. 'Ext.resizer.Resizer',
  14691. 'Ext.util.ComponentDragger'
  14692. ],
  14693. mixins: {
  14694. floating: 'Ext.util.Floating'
  14695. },
  14696. statics: {
  14697. // Collapse/expand directions
  14698. DIRECTION_TOP: 'top',
  14699. DIRECTION_RIGHT: 'right',
  14700. DIRECTION_BOTTOM: 'bottom',
  14701. DIRECTION_LEFT: 'left',
  14702. VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
  14703. // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
  14704. // This includes dot, comma and whitespace
  14705. INVALID_ID_CHARS_Re: /[\.,\s]/g
  14706. },
  14707. /* End Definitions */
  14708. /**
  14709. * @cfg {Boolean/Object} resizable
  14710. * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
  14711. *
  14712. * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
  14713. * to override any defaults. By default the Component passes its minimum and maximum size, and uses
  14714. * `{@link Ext.resizer.Resizer#dynamic}: false`
  14715. */
  14716. /**
  14717. * @cfg {String} resizeHandles
  14718. * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
  14719. */
  14720. resizeHandles: 'all',
  14721. /**
  14722. * @cfg {Boolean} [autoScroll=false]
  14723. * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
  14724. * `false` to clip any overflowing content.
  14725. * This should not be combined with {@link #overflowX} or {@link #overflowY}.
  14726. */
  14727. /**
  14728. * @cfg {String} overflowX
  14729. * Possible values are:
  14730. * * `'auto'` to enable automatic horizontal scrollbar (overflow-x: 'auto').
  14731. * * `'scroll'` to always enable horizontal scrollbar (overflow-x: 'scroll').
  14732. * The default is overflow-x: 'hidden'. This should not be combined with {@link #autoScroll}.
  14733. */
  14734. /**
  14735. * @cfg {String} overflowY
  14736. * Possible values are:
  14737. * * `'auto'` to enable automatic vertical scrollbar (overflow-y: 'auto').
  14738. * * `'scroll'` to always enable vertical scrollbar (overflow-y: 'scroll').
  14739. * The default is overflow-y: 'hidden'. This should not be combined with {@link #autoScroll}.
  14740. */
  14741. /**
  14742. * @cfg {Boolean} floating
  14743. * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
  14744. *
  14745. * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
  14746. *
  14747. * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
  14748. * the global {@link Ext.WindowManager ZIndexManager}
  14749. *
  14750. * ### Floating Components as child items of a Container
  14751. *
  14752. * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
  14753. * a ZIndexManager by examining the ownerCt chain.
  14754. *
  14755. * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
  14756. * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
  14757. * of its stack when the Component's {@link #toFront} method is called.
  14758. *
  14759. * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
  14760. * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
  14761. * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
  14762. * ancestor Container.
  14763. *
  14764. * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
  14765. * ZIndexManager}.
  14766. *
  14767. * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
  14768. * you explicitly {@link #method-show} them.
  14769. *
  14770. * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
  14771. * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
  14772. * not be set.
  14773. */
  14774. floating: false,
  14775. /**
  14776. * @cfg {Boolean} toFrontOnShow
  14777. * True to automatically call {@link #toFront} when the {@link #method-show} method is called on an already visible,
  14778. * floating component.
  14779. */
  14780. toFrontOnShow: true,
  14781. /**
  14782. * @property {Ext.ZIndexManager} zIndexManager
  14783. * Only present for {@link #floating} Components after they have been rendered.
  14784. *
  14785. * A reference to the ZIndexManager which is managing this Component's z-index.
  14786. *
  14787. * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
  14788. * a single modal mask which is insert just beneath the topmost visible modal floating Component.
  14789. *
  14790. * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
  14791. * z-index stack.
  14792. *
  14793. * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
  14794. * programatically {@link Ext.Component#render rendered}.
  14795. *
  14796. * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
  14797. * ancestor Container found which is floating. If no floating ancestor is found, the global {@link Ext.WindowManager ZIndexManager} is
  14798. * used.
  14799. *
  14800. * See {@link #floating} and {@link #zIndexParent}
  14801. * @readonly
  14802. */
  14803. /**
  14804. * @property {Ext.Container} floatParent
  14805. * Only present for {@link #floating} Components which were inserted as child items of Containers.
  14806. *
  14807. * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
  14808. * property.
  14809. *
  14810. * For {@link #floating} Components which are child items of a Container, the floatParent will be the owning Container.
  14811. *
  14812. * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
  14813. * Window as its `floatParent`
  14814. *
  14815. * See {@link #floating} and {@link #zIndexManager}
  14816. * @readonly
  14817. */
  14818. /**
  14819. * @property {Ext.Container} zIndexParent
  14820. * Only present for {@link #floating} Components which were inserted as child items of Containers, and which have a floating
  14821. * Container in their containment ancestry.
  14822. *
  14823. * For {@link #floating} Components which are child items of a Container, the zIndexParent will be a floating
  14824. * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
  14825. * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
  14826. * Components.
  14827. *
  14828. * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `zIndexParent`
  14829. * property.
  14830. *
  14831. * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
  14832. * Window as its `zIndexParent`, and will always show above that Window, wherever the Window is placed in the z-index stack.
  14833. *
  14834. * See {@link #floating} and {@link #zIndexManager}
  14835. * @readonly
  14836. */
  14837. /**
  14838. * @cfg {Boolean/Object} [draggable=false]
  14839. * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
  14840. * the drag handle.
  14841. *
  14842. * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
  14843. * instantiated to perform dragging.
  14844. *
  14845. * For example to create a Component which may only be dragged around using a certain internal element as the drag
  14846. * handle, use the delegate option:
  14847. *
  14848. * new Ext.Component({
  14849. * constrain: true,
  14850. * floating: true,
  14851. * style: {
  14852. * backgroundColor: '#fff',
  14853. * border: '1px solid black'
  14854. * },
  14855. * html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
  14856. * draggable: {
  14857. * delegate: 'h1'
  14858. * }
  14859. * }).show();
  14860. */
  14861. hideMode: 'display',
  14862. // Deprecate 5.0
  14863. hideParent: false,
  14864. bubbleEvents: [],
  14865. actionMode: 'el',
  14866. monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
  14867. //renderTpl: new Ext.XTemplate(
  14868. // '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
  14869. // compiled: true,
  14870. // disableFormats: true
  14871. // }
  14872. //),
  14873. /**
  14874. * Creates new Component.
  14875. * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
  14876. *
  14877. * - **an element** : it is set as the internal element and its id used as the component id
  14878. * - **a string** : it is assumed to be the id of an existing element and is used as the component id
  14879. * - **anything else** : it is assumed to be a standard config object and is applied to the component
  14880. */
  14881. constructor: function(config) {
  14882. var me = this;
  14883. config = config || {};
  14884. if (config.initialConfig) {
  14885. // Being initialized from an Ext.Action instance...
  14886. if (config.isAction) {
  14887. me.baseAction = config;
  14888. }
  14889. config = config.initialConfig;
  14890. // component cloning / action set up
  14891. }
  14892. else if (config.tagName || config.dom || Ext.isString(config)) {
  14893. // element object
  14894. config = {
  14895. applyTo: config,
  14896. id: config.id || config
  14897. };
  14898. }
  14899. me.callParent([config]);
  14900. // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
  14901. // register this Component as one of its items
  14902. if (me.baseAction){
  14903. me.baseAction.addComponent(me);
  14904. }
  14905. },
  14906. /**
  14907. * The initComponent template method is an important initialization step for a Component. It is intended to be
  14908. * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
  14909. * initComponent method of the class being created is called first, with each initComponent method
  14910. * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
  14911. * if needed, override the constructor logic of the Component at any step in the hierarchy.
  14912. *
  14913. * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
  14914. * to ensure that the parent class' initComponent method is also called.
  14915. *
  14916. * All config options passed to the constructor are applied to `this` before initComponent is called,
  14917. * so you can simply access them with `this.someOption`.
  14918. *
  14919. * The following example demonstrates using a dynamic string for the text of a button at the time of
  14920. * instantiation of the class.
  14921. *
  14922. * Ext.define('DynamicButtonText', {
  14923. * extend: 'Ext.button.Button',
  14924. *
  14925. * initComponent: function() {
  14926. * this.text = new Date();
  14927. * this.renderTo = Ext.getBody();
  14928. * this.callParent();
  14929. * }
  14930. * });
  14931. *
  14932. * Ext.onReady(function() {
  14933. * Ext.create('DynamicButtonText');
  14934. * });
  14935. *
  14936. * @template
  14937. * @protected
  14938. */
  14939. initComponent: function() {
  14940. var me = this;
  14941. me.callParent();
  14942. if (me.listeners) {
  14943. me.on(me.listeners);
  14944. me.listeners = null; //change the value to remove any on prototype
  14945. }
  14946. me.enableBubble(me.bubbleEvents);
  14947. me.mons = [];
  14948. },
  14949. // private
  14950. afterRender: function() {
  14951. var me = this;
  14952. me.callParent();
  14953. if (!(me.x && me.y) && (me.pageX || me.pageY)) {
  14954. me.setPagePosition(me.pageX, me.pageY);
  14955. }
  14956. if (me.resizable) {
  14957. me.initResizable(me.resizable);
  14958. }
  14959. if (me.draggable) {
  14960. me.initDraggable();
  14961. }
  14962. },
  14963. /**
  14964. * Sets the overflow on the content element of the component.
  14965. * @param {Boolean} scroll True to allow the Component to auto scroll.
  14966. * @return {Ext.Component} this
  14967. */
  14968. setAutoScroll : function(scroll) {
  14969. var me = this,
  14970. layout,
  14971. targetEl;
  14972. me.autoScroll = !!scroll;
  14973. // Scrolling styles must be applied to the element into which content is rendered.
  14974. // This means the layout's target if we are using a layout.
  14975. if (me.rendered) {
  14976. targetEl = (layout = me.getLayout && me.getLayout()) ? layout.getRenderTarget() : me.getTargetEl();
  14977. targetEl.setStyle(me.getOverflowStyle());
  14978. }
  14979. return me;
  14980. },
  14981. /**
  14982. * Sets the overflow x/y on the content element of the component. The x/y overflow
  14983. * values can be any valid CSS overflow (e.g., 'auto' or 'scroll'). By default, the
  14984. * value is 'hidden'. Passing null for one of the values will erase the inline style.
  14985. * Passing `undefined` will preserve the current value.
  14986. *
  14987. * @param {String} overflowX The overflow-x value.
  14988. * @param {String} overflowY The overflow-y value.
  14989. * @return {Ext.Component} this
  14990. */
  14991. setOverflowXY: function(overflowX, overflowY) {
  14992. var me = this,
  14993. layout,
  14994. targetEl,
  14995. argCount = arguments.length;
  14996. if (argCount) {
  14997. me.overflowX = overflowX || '';
  14998. if (argCount > 1) {
  14999. me.overflowY = overflowY || '';
  15000. }
  15001. }
  15002. // Scrolling styles must be applied to the element into which content is rendered.
  15003. // This means the layout's target if we are using a layout.
  15004. if (me.rendered) {
  15005. targetEl = (layout = me.getLayout && me.getLayout()) ? layout.getRenderTarget() : me.getTargetEl();
  15006. targetEl.setStyle(me.getOverflowStyle());
  15007. }
  15008. return me;
  15009. },
  15010. beforeRender: function () {
  15011. var me = this,
  15012. floating = me.floating,
  15013. cls;
  15014. if (floating) {
  15015. me.addCls(Ext.baseCSSPrefix + 'layer');
  15016. cls = floating.cls;
  15017. if (cls) {
  15018. me.addCls(cls);
  15019. }
  15020. }
  15021. return me.callParent();
  15022. },
  15023. // private
  15024. makeFloating : function (dom) {
  15025. this.mixins.floating.constructor.call(this, dom);
  15026. },
  15027. wrapPrimaryEl: function (dom) {
  15028. if (this.floating) {
  15029. this.makeFloating(dom);
  15030. } else {
  15031. this.callParent(arguments);
  15032. }
  15033. },
  15034. initResizable: function(resizable) {
  15035. var me = this;
  15036. resizable = Ext.apply({
  15037. target: me,
  15038. dynamic: false,
  15039. constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : null),
  15040. handles: me.resizeHandles
  15041. }, resizable);
  15042. resizable.target = me;
  15043. me.resizer = new Ext.resizer.Resizer(resizable);
  15044. },
  15045. getDragEl: function() {
  15046. return this.el;
  15047. },
  15048. initDraggable: function() {
  15049. var me = this,
  15050. ddConfig = Ext.applyIf({
  15051. el: me.getDragEl(),
  15052. constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
  15053. }, me.draggable);
  15054. // Add extra configs if Component is specified to be constrained
  15055. if (me.constrain || me.constrainDelegate) {
  15056. ddConfig.constrain = me.constrain;
  15057. ddConfig.constrainDelegate = me.constrainDelegate;
  15058. }
  15059. me.dd = new Ext.util.ComponentDragger(me, ddConfig);
  15060. },
  15061. /**
  15062. * Scrolls this Component's {@link #getTargetEl target element} by the passed delta values, optionally animating.
  15063. *
  15064. * All of the following are equivalent:
  15065. *
  15066. * comp.scrollBy(10, 10, true);
  15067. * comp.scrollBy([10, 10], true);
  15068. * comp.scrollBy({ x: 10, y: 10 }, true);
  15069. *
  15070. * @param {Number/Number[]/Object} deltaX Either the x delta, an Array specifying x and y deltas or
  15071. * an object with "x" and "y" properties.
  15072. * @param {Number/Boolean/Object} deltaY Either the y delta, or an animate flag or config object.
  15073. * @param {Boolean/Object} animate Animate flag/config object if the delta values were passed separately.
  15074. */
  15075. scrollBy: function(deltaX, deltaY, animate) {
  15076. var el;
  15077. if ((el = this.getTargetEl()) && el.dom) {
  15078. el.scrollBy.apply(el, arguments);
  15079. }
  15080. },
  15081. /**
  15082. * This method allows you to show or hide a LoadMask on top of this component.
  15083. *
  15084. * @param {Boolean/Object/String} load True to show the default LoadMask, a config object that will be passed to the
  15085. * LoadMask constructor, or a message String to show. False to hide the current LoadMask.
  15086. * @param {Boolean} [targetEl=false] True to mask the targetEl of this Component instead of the `this.el`. For example,
  15087. * setting this to true on a Panel will cause only the body to be masked.
  15088. * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
  15089. */
  15090. setLoading : function(load, targetEl) {
  15091. var me = this,
  15092. config;
  15093. if (me.rendered) {
  15094. Ext.destroy(me.loadMask);
  15095. me.loadMask = null;
  15096. if (load !== false && !me.collapsed) {
  15097. if (Ext.isObject(load)) {
  15098. config = Ext.apply({}, load);
  15099. } else if (Ext.isString(load)) {
  15100. config = {msg: load};
  15101. } else {
  15102. config = {};
  15103. }
  15104. if (targetEl) {
  15105. Ext.applyIf(config, {
  15106. useTargetEl: true
  15107. });
  15108. }
  15109. me.loadMask = new Ext.LoadMask(me, config);
  15110. me.loadMask.show();
  15111. }
  15112. }
  15113. return me.loadMask;
  15114. },
  15115. beforeSetPosition: function () {
  15116. var me = this,
  15117. pos = me.callParent(arguments), // pass all args on for signature decoding
  15118. adj;
  15119. if (pos) {
  15120. adj = me.adjustPosition(pos.x, pos.y);
  15121. pos.x = adj.x;
  15122. pos.y = adj.y;
  15123. }
  15124. return pos || null;
  15125. },
  15126. afterSetPosition: function(ax, ay) {
  15127. this.onPosition(ax, ay);
  15128. this.fireEvent('move', this, ax, ay);
  15129. },
  15130. /**
  15131. * Displays component at specific xy position.
  15132. * A floating component (like a menu) is positioned relative to its ownerCt if any.
  15133. * Useful for popping up a context menu:
  15134. *
  15135. * listeners: {
  15136. * itemcontextmenu: function(view, record, item, index, event, options) {
  15137. * Ext.create('Ext.menu.Menu', {
  15138. * width: 100,
  15139. * height: 100,
  15140. * margin: '0 0 10 0',
  15141. * items: [{
  15142. * text: 'regular item 1'
  15143. * },{
  15144. * text: 'regular item 2'
  15145. * },{
  15146. * text: 'regular item 3'
  15147. * }]
  15148. * }).showAt(event.getXY());
  15149. * }
  15150. * }
  15151. *
  15152. * @param {Number} x The new x position
  15153. * @param {Number} y The new y position
  15154. * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
  15155. * animation configuration.
  15156. */
  15157. showAt: function(x, y, animate) {
  15158. var me = this;
  15159. if (!me.rendered && (me.autoRender || me.floating)) {
  15160. me.doAutoRender();
  15161. }
  15162. if (me.floating) {
  15163. me.setPosition(x, y, animate);
  15164. } else {
  15165. me.setPagePosition(x, y, animate);
  15166. }
  15167. me.show();
  15168. },
  15169. /**
  15170. * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
  15171. * This method fires the {@link #move} event.
  15172. * @param {Number} x The new x position
  15173. * @param {Number} y The new y position
  15174. * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
  15175. * animation configuration.
  15176. * @return {Ext.Component} this
  15177. */
  15178. setPagePosition: function(x, y, animate) {
  15179. var me = this,
  15180. p,
  15181. floatParentBox;
  15182. if (Ext.isArray(x)) {
  15183. y = x[1];
  15184. x = x[0];
  15185. }
  15186. me.pageX = x;
  15187. me.pageY = y;
  15188. if (me.floating) {
  15189. // Floating Components which are registered with a Container have to have their x and y properties made relative
  15190. if (me.isContainedFloater()) {
  15191. floatParentBox = me.floatParent.getTargetEl().getViewRegion();
  15192. if (Ext.isNumber(x) && Ext.isNumber(floatParentBox.left)) {
  15193. x -= floatParentBox.left;
  15194. }
  15195. if (Ext.isNumber(y) && Ext.isNumber(floatParentBox.top)) {
  15196. y -= floatParentBox.top;
  15197. }
  15198. }
  15199. me.setPosition(x, y, animate);
  15200. } else {
  15201. p = me.el.translatePoints(x, y);
  15202. me.setPosition(p.left, p.top, animate);
  15203. }
  15204. return me;
  15205. },
  15206. // Utility method to determine if a Component is floating, and has an owning Container whose coordinate system
  15207. // it must be positioned in when using setPosition.
  15208. isContainedFloater: function() {
  15209. return (this.floating && this.floatParent);
  15210. },
  15211. /**
  15212. * Gets the current box measurements of the component's underlying element.
  15213. * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
  15214. * @return {Object} box An object in the format {x, y, width, height}
  15215. */
  15216. getBox : function(local){
  15217. var pos = local ? this.getPosition(local) : this.el.getXY(),
  15218. size = this.getSize();
  15219. size.x = pos[0];
  15220. size.y = pos[1];
  15221. return size;
  15222. },
  15223. /**
  15224. * Sets the current box measurements of the component's underlying element.
  15225. * @param {Object} box An object in the format {x, y, width, height}
  15226. * @return {Ext.Component} this
  15227. */
  15228. updateBox : function(box){
  15229. this.setSize(box.width, box.height);
  15230. this.setPagePosition(box.x, box.y);
  15231. return this;
  15232. },
  15233. // Include margins
  15234. getOuterSize: function() {
  15235. var el = this.el;
  15236. return {
  15237. width: el.getWidth() + el.getMargin('lr'),
  15238. height: el.getHeight() + el.getMargin('tb')
  15239. };
  15240. },
  15241. // private
  15242. adjustPosition: function(x, y) {
  15243. var me = this,
  15244. floatParentBox;
  15245. // Floating Components being positioned in their ownerCt have to be made absolute.
  15246. if (me.isContainedFloater()) {
  15247. floatParentBox = me.floatParent.getTargetEl().getViewRegion();
  15248. x += floatParentBox.left;
  15249. y += floatParentBox.top;
  15250. }
  15251. return {
  15252. x: x,
  15253. y: y
  15254. };
  15255. },
  15256. /**
  15257. * Gets the current XY position of the component's underlying element.
  15258. * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
  15259. * @return {Number[]} The XY position of the element (e.g., [100, 200])
  15260. */
  15261. getPosition: function(local) {
  15262. var me = this,
  15263. el = me.el,
  15264. xy,
  15265. isContainedFloater = me.isContainedFloater(),
  15266. floatParentBox;
  15267. // Floating Components which were just rendered with no ownerCt return local position.
  15268. if ((local === true) || !isContainedFloater) {
  15269. return [el.getLeft(true), el.getTop(true)];
  15270. }
  15271. // Use our previously set x and y properties if possible.
  15272. if (me.x !== undefined && me.y !== undefined) {
  15273. xy = [me.x, me.y];
  15274. } else {
  15275. xy = me.el.getXY();
  15276. // Floating Components in an ownerCt have to have their positions made relative
  15277. if (isContainedFloater) {
  15278. floatParentBox = me.floatParent.getTargetEl().getViewRegion();
  15279. xy[0] -= floatParentBox.left;
  15280. xy[1] -= floatParentBox.top;
  15281. }
  15282. }
  15283. return xy;
  15284. },
  15285. getId: function() {
  15286. var me = this,
  15287. xtype;
  15288. if (!me.id) {
  15289. xtype = me.getXType();
  15290. if (xtype) {
  15291. xtype = xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-');
  15292. } else {
  15293. xtype = Ext.name.toLowerCase() + '-comp';
  15294. }
  15295. me.id = xtype + '-' + me.getAutoId();
  15296. }
  15297. return me.id;
  15298. },
  15299. /**
  15300. * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
  15301. *
  15302. * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
  15303. * brought to the front of its {@link #zIndexManager z-index stack}.
  15304. *
  15305. * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
  15306. * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
  15307. * with `floating: true`.** The target from which the Component should animate from while opening.
  15308. * @param {Function} [callback] A callback function to call after the Component is displayed.
  15309. * Only necessary if animation was specified.
  15310. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  15311. * Defaults to this Component.
  15312. * @return {Ext.Component} this
  15313. */
  15314. show: function(animateTarget, cb, scope) {
  15315. var me = this;
  15316. if (me.rendered && me.isVisible()) {
  15317. if (me.toFrontOnShow && me.floating) {
  15318. me.toFront();
  15319. }
  15320. } else if (me.fireEvent('beforeshow', me) !== false) {
  15321. me.hidden = false;
  15322. // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
  15323. if (!me.rendered && (me.autoRender || me.floating)) {
  15324. me.doAutoRender();
  15325. }
  15326. if (me.rendered) {
  15327. me.beforeShow();
  15328. me.onShow.apply(me, arguments);
  15329. me.afterShow.apply(me, arguments);
  15330. }
  15331. }
  15332. return me;
  15333. },
  15334. /**
  15335. * Invoked before the Component is shown.
  15336. *
  15337. * @method
  15338. * @template
  15339. * @protected
  15340. */
  15341. beforeShow: Ext.emptyFn,
  15342. /**
  15343. * Allows addition of behavior to the show operation. After
  15344. * calling the superclass's onShow, the Component will be visible.
  15345. *
  15346. * Override in subclasses where more complex behaviour is needed.
  15347. *
  15348. * Gets passed the same parameters as #show.
  15349. *
  15350. * @param {String/Ext.Element} [animateTarget]
  15351. * @param {Function} [callback]
  15352. * @param {Object} [scope]
  15353. *
  15354. * @template
  15355. * @protected
  15356. */
  15357. onShow: function() {
  15358. var me = this;
  15359. me.el.show();
  15360. me.callParent(arguments);
  15361. if (me.floating && me.constrain) {
  15362. me.doConstrain();
  15363. }
  15364. },
  15365. /**
  15366. * Invoked after the Component is shown (after #onShow is called).
  15367. *
  15368. * Gets passed the same parameters as #show.
  15369. *
  15370. * @param {String/Ext.Element} [animateTarget]
  15371. * @param {Function} [callback]
  15372. * @param {Object} [scope]
  15373. *
  15374. * @template
  15375. * @protected
  15376. */
  15377. afterShow: function(animateTarget, cb, scope) {
  15378. var me = this,
  15379. fromBox,
  15380. toBox,
  15381. ghostPanel;
  15382. // Default to configured animate target if none passed
  15383. animateTarget = animateTarget || me.animateTarget;
  15384. // Need to be able to ghost the Component
  15385. if (!me.ghost) {
  15386. animateTarget = null;
  15387. }
  15388. // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
  15389. if (animateTarget) {
  15390. animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
  15391. toBox = me.el.getBox();
  15392. fromBox = animateTarget.getBox();
  15393. me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
  15394. ghostPanel = me.ghost();
  15395. ghostPanel.el.stopAnimation();
  15396. // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
  15397. ghostPanel.el.setX(-10000);
  15398. ghostPanel.el.animate({
  15399. from: fromBox,
  15400. to: toBox,
  15401. listeners: {
  15402. afteranimate: function() {
  15403. delete ghostPanel.componentLayout.lastComponentSize;
  15404. me.unghost();
  15405. me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
  15406. me.onShowComplete(cb, scope);
  15407. }
  15408. }
  15409. });
  15410. }
  15411. else {
  15412. me.onShowComplete(cb, scope);
  15413. }
  15414. },
  15415. /**
  15416. * Invoked after the #afterShow method is complete.
  15417. *
  15418. * Gets passed the same `callback` and `scope` parameters that #afterShow received.
  15419. *
  15420. * @param {Function} [callback]
  15421. * @param {Object} [scope]
  15422. *
  15423. * @template
  15424. * @protected
  15425. */
  15426. onShowComplete: function(cb, scope) {
  15427. var me = this;
  15428. if (me.floating) {
  15429. me.toFront();
  15430. me.onFloatShow();
  15431. }
  15432. Ext.callback(cb, scope || me);
  15433. me.fireEvent('show', me);
  15434. delete me.hiddenByLayout;
  15435. },
  15436. /**
  15437. * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
  15438. * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
  15439. * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
  15440. * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
  15441. * @param {Function} [callback] A callback function to call after the Component is hidden.
  15442. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  15443. * Defaults to this Component.
  15444. * @return {Ext.Component} this
  15445. */
  15446. hide: function() {
  15447. var me = this;
  15448. // Clear the flag which is set if a floatParent was hidden while this is visible.
  15449. // If a hide operation was subsequently called, that pending show must be hidden.
  15450. me.showOnParentShow = false;
  15451. if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
  15452. me.hidden = true;
  15453. if (me.rendered) {
  15454. me.onHide.apply(me, arguments);
  15455. }
  15456. }
  15457. return me;
  15458. },
  15459. /**
  15460. * Possibly animates down to a target element.
  15461. *
  15462. * Allows addition of behavior to the hide operation. After
  15463. * calling the superclass’s onHide, the Component will be hidden.
  15464. *
  15465. * Gets passed the same parameters as #hide.
  15466. *
  15467. * @param {String/Ext.Element/Ext.Component} [animateTarget]
  15468. * @param {Function} [callback]
  15469. * @param {Object} [scope]
  15470. *
  15471. * @template
  15472. * @protected
  15473. */
  15474. onHide: function(animateTarget, cb, scope) {
  15475. var me = this,
  15476. ghostPanel,
  15477. toBox;
  15478. // Default to configured animate target if none passed
  15479. animateTarget = animateTarget || me.animateTarget;
  15480. // Need to be able to ghost the Component
  15481. if (!me.ghost) {
  15482. animateTarget = null;
  15483. }
  15484. // If we're animating, kick off an animation of the ghost down to the target
  15485. if (animateTarget) {
  15486. animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
  15487. ghostPanel = me.ghost();
  15488. ghostPanel.el.stopAnimation();
  15489. toBox = animateTarget.getBox();
  15490. toBox.width += 'px';
  15491. toBox.height += 'px';
  15492. ghostPanel.el.animate({
  15493. to: toBox,
  15494. listeners: {
  15495. afteranimate: function() {
  15496. delete ghostPanel.componentLayout.lastComponentSize;
  15497. ghostPanel.el.hide();
  15498. me.afterHide(cb, scope);
  15499. }
  15500. }
  15501. });
  15502. }
  15503. me.el.hide();
  15504. if (!animateTarget) {
  15505. me.afterHide(cb, scope);
  15506. }
  15507. },
  15508. /**
  15509. * Invoked after the Component has been hidden.
  15510. *
  15511. * Gets passed the same `callback` and `scope` parameters that #onHide received.
  15512. *
  15513. * @param {Function} [callback]
  15514. * @param {Object} [scope]
  15515. *
  15516. * @template
  15517. * @protected
  15518. */
  15519. afterHide: function(cb, scope) {
  15520. var me = this;
  15521. delete me.hiddenByLayout;
  15522. // we are the back-end method of onHide at this level, but our call to our parent
  15523. // may need to be async... so callParent won't quite work here...
  15524. Ext.AbstractComponent.prototype.onHide.call(this);
  15525. Ext.callback(cb, scope || me);
  15526. me.fireEvent('hide', me);
  15527. },
  15528. /**
  15529. * Allows addition of behavior to the destroy operation.
  15530. * After calling the superclass’s onDestroy, the Component will be destroyed.
  15531. *
  15532. * @template
  15533. * @protected
  15534. */
  15535. onDestroy: function() {
  15536. var me = this;
  15537. // Ensure that any ancillary components are destroyed.
  15538. if (me.rendered) {
  15539. Ext.destroy(
  15540. me.proxy,
  15541. me.proxyWrap,
  15542. me.resizer
  15543. );
  15544. // Different from AbstractComponent
  15545. if (me.actionMode == 'container' || me.removeMode == 'container') {
  15546. me.container.remove();
  15547. }
  15548. }
  15549. delete me.focusTask;
  15550. me.callParent();
  15551. },
  15552. deleteMembers: function() {
  15553. var args = arguments,
  15554. len = args.length,
  15555. i = 0;
  15556. for (; i < len; ++i) {
  15557. delete this[args[i]];
  15558. }
  15559. },
  15560. /**
  15561. * Try to focus this component.
  15562. * @param {Boolean} [selectText] If applicable, true to also select the text in this component
  15563. * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
  15564. * @return {Ext.Component} The focused Component. Usually <code>this</code> Component. Some Containers may
  15565. * delegate focus to a descendant Component ({@link Ext.window.Window Window}s can do this through their
  15566. * {@link Ext.window.Window#defaultFocus defaultFocus} config option.
  15567. */
  15568. focus: function(selectText, delay) {
  15569. var me = this,
  15570. focusEl,
  15571. focusElDom;
  15572. if (me.rendered && !me.isDestroyed && me.isVisible(true) && (focusEl = me.getFocusEl())) {
  15573. // getFocusEl might return a Component if a Container wishes to delegate focus to a descendant.
  15574. // Window can do this via its defaultFocus configuration which can reference a Button.
  15575. if (focusEl.isComponent) {
  15576. return focusEl.focus(selectText, delay);
  15577. }
  15578. // If delay is wanted, queue a call to this function.
  15579. if (delay) {
  15580. if (!me.focusTask) {
  15581. me.focusTask = new Ext.util.DelayedTask(me.focus);
  15582. }
  15583. me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
  15584. return me;
  15585. }
  15586. // If it was an Element with a dom property
  15587. if ((focusElDom = focusEl.dom)) {
  15588. // Not a natural focus holding element, add a tab index to make it programatically focusable.
  15589. if (focusEl.needsTabIndex()) {
  15590. focusElDom.tabIndex = -1;
  15591. }
  15592. // Focus the element.
  15593. // The focusEl has a DOM focus listener on it which invokes the Component's onFocus method
  15594. // to perform Component-specific focus processing
  15595. focusEl.focus();
  15596. if (selectText === true) {
  15597. focusElDom.select();
  15598. }
  15599. }
  15600. // Focusing a floating Component brings it to the front of its stack.
  15601. // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
  15602. if (me.floating) {
  15603. me.toFront(true);
  15604. }
  15605. }
  15606. return me;
  15607. },
  15608. // private
  15609. blur: function() {
  15610. var focusEl;
  15611. if (this.rendered && (focusEl = this.getFocusEl())) {
  15612. focusEl.blur();
  15613. }
  15614. return this;
  15615. },
  15616. getEl: function() {
  15617. return this.el;
  15618. },
  15619. // Deprecate 5.0
  15620. getResizeEl: function() {
  15621. return this.el;
  15622. },
  15623. // Deprecate 5.0
  15624. getPositionEl: function() {
  15625. return this.el;
  15626. },
  15627. // Deprecate 5.0
  15628. getActionEl: function() {
  15629. return this.el;
  15630. },
  15631. // Deprecate 5.0
  15632. getVisibilityEl: function() {
  15633. return this.el;
  15634. },
  15635. // Deprecate 5.0
  15636. onResize: Ext.emptyFn,
  15637. // private
  15638. getBubbleTarget: function() {
  15639. return this.ownerCt;
  15640. },
  15641. // private
  15642. getContentTarget: function() {
  15643. return this.el;
  15644. },
  15645. /**
  15646. * Clone the current component using the original config values passed into this instance by default.
  15647. * @param {Object} overrides A new config containing any properties to override in the cloned version.
  15648. * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
  15649. * @return {Ext.Component} clone The cloned copy of this component
  15650. */
  15651. cloneConfig: function(overrides) {
  15652. overrides = overrides || {};
  15653. var id = overrides.id || Ext.id(),
  15654. cfg = Ext.applyIf(overrides, this.initialConfig),
  15655. self;
  15656. cfg.id = id;
  15657. self = Ext.getClass(this);
  15658. // prevent dup id
  15659. return new self(cfg);
  15660. },
  15661. /**
  15662. * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
  15663. * xtypes, see the {@link Ext.Component} header. Example usage:
  15664. *
  15665. * var t = new Ext.form.field.Text();
  15666. * alert(t.getXType()); // alerts 'textfield'
  15667. *
  15668. * @return {String} The xtype
  15669. */
  15670. getXType: function() {
  15671. return this.self.xtype;
  15672. },
  15673. /**
  15674. * Find a container above this component at any level by a custom function. If the passed function returns true, the
  15675. * container will be returned.
  15676. * @param {Function} fn The custom function to call with the arguments (container, this component).
  15677. * @return {Ext.container.Container} The first Container for which the custom function returns true
  15678. */
  15679. findParentBy: function(fn) {
  15680. var p;
  15681. // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
  15682. for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt) {
  15683. // do nothing
  15684. }
  15685. return p || null;
  15686. },
  15687. /**
  15688. * Find a container above this component at any level by xtype or class
  15689. *
  15690. * See also the {@link Ext.Component#up up} method.
  15691. *
  15692. * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
  15693. * @return {Ext.container.Container} The first Container which matches the given xtype or class
  15694. */
  15695. findParentByType: function(xtype) {
  15696. return Ext.isFunction(xtype) ?
  15697. this.findParentBy(function(p) {
  15698. return p.constructor === xtype;
  15699. })
  15700. :
  15701. this.up(xtype);
  15702. },
  15703. /**
  15704. * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
  15705. * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
  15706. * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
  15707. *
  15708. * @param {Function} fn The function to call
  15709. * @param {Object} [scope] The scope of the function. Defaults to current node.
  15710. * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
  15711. * @return {Ext.Component} this
  15712. */
  15713. bubble: function(fn, scope, args) {
  15714. var p = this;
  15715. while (p) {
  15716. if (fn.apply(scope || p, args || [p]) === false) {
  15717. break;
  15718. }
  15719. p = p.ownerCt;
  15720. }
  15721. return this;
  15722. },
  15723. getProxy: function() {
  15724. var me = this,
  15725. target;
  15726. if (!me.proxy) {
  15727. target = Ext.getBody();
  15728. if (Ext.scopeResetCSS) {
  15729. me.proxyWrap = target = Ext.getBody().createChild({
  15730. cls: Ext.resetCls
  15731. });
  15732. }
  15733. me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
  15734. }
  15735. return me.proxy;
  15736. }
  15737. });
  15738. /**
  15739. * @class Ext.app.EventBus
  15740. * @private
  15741. */
  15742. Ext.define('Ext.app.EventBus', {
  15743. requires: [
  15744. 'Ext.util.Event',
  15745. 'Ext.Component'
  15746. ],
  15747. mixins: {
  15748. observable: 'Ext.util.Observable'
  15749. },
  15750. constructor: function() {
  15751. this.mixins.observable.constructor.call(this);
  15752. this.bus = {};
  15753. var me = this;
  15754. Ext.override(Ext.Component, {
  15755. fireEvent: function(ev) {
  15756. if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
  15757. return me.dispatch.call(me, ev, this, arguments);
  15758. }
  15759. return false;
  15760. }
  15761. });
  15762. },
  15763. dispatch: function(ev, target, args) {
  15764. var bus = this.bus,
  15765. selectors = bus[ev],
  15766. selector, controllers, id, events, event, i, ln;
  15767. if (selectors) {
  15768. // Loop over all the selectors that are bound to this event
  15769. for (selector in selectors) {
  15770. // Check if the target matches the selector
  15771. if (selectors.hasOwnProperty(selector) && target.is(selector)) {
  15772. // Loop over all the controllers that are bound to this selector
  15773. controllers = selectors[selector];
  15774. for (id in controllers) {
  15775. if (controllers.hasOwnProperty(id)) {
  15776. // Loop over all the events that are bound to this selector on this controller
  15777. events = controllers[id];
  15778. for (i = 0, ln = events.length; i < ln; i++) {
  15779. event = events[i];
  15780. // Fire the event!
  15781. if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
  15782. return false;
  15783. }
  15784. }
  15785. }
  15786. }
  15787. }
  15788. }
  15789. }
  15790. return true;
  15791. },
  15792. control: function(selectors, listeners, controller) {
  15793. var bus = this.bus,
  15794. selector, options, listener, scope, event, listenerList, ev;
  15795. if (Ext.isString(selectors)) {
  15796. selector = selectors;
  15797. selectors = {};
  15798. selectors[selector] = listeners;
  15799. this.control(selectors, null, controller);
  15800. return;
  15801. }
  15802. for (selector in selectors) {
  15803. if (selectors.hasOwnProperty(selector)) {
  15804. listenerList = selectors[selector] || {};
  15805. //inner loop
  15806. for (ev in listenerList) {
  15807. if (listenerList.hasOwnProperty(ev)) {
  15808. options = {};
  15809. listener = listenerList[ev];
  15810. scope = controller;
  15811. event = new Ext.util.Event(controller, ev);
  15812. // Normalize the listener
  15813. if (Ext.isObject(listener)) {
  15814. options = listener;
  15815. listener = options.fn;
  15816. scope = options.scope || controller;
  15817. delete options.fn;
  15818. delete options.scope;
  15819. }
  15820. event.addListener(listener, scope, options);
  15821. // Create the bus tree if it is not there yet
  15822. bus[ev] = bus[ev] || {};
  15823. bus[ev][selector] = bus[ev][selector] || {};
  15824. bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
  15825. // Push our listener in our bus
  15826. bus[ev][selector][controller.id].push(event);
  15827. }
  15828. } //end inner loop
  15829. }
  15830. } //end outer loop
  15831. }
  15832. });
  15833. /**
  15834. * @author Ed Spencer
  15835. *
  15836. * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
  15837. * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
  15838. * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
  15839. * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
  15840. *
  15841. * Ext.create('Ext.data.Store', {
  15842. * model: 'User',
  15843. * proxy: {
  15844. * type: 'ajax',
  15845. * url : 'users.json',
  15846. * reader: {
  15847. * type: 'json',
  15848. * root: 'users'
  15849. * }
  15850. * },
  15851. * });
  15852. *
  15853. * The above reader is configured to consume a JSON string that looks something like this:
  15854. *
  15855. * {
  15856. * "success": true,
  15857. * "users": [
  15858. * { "name": "User 1" },
  15859. * { "name": "User 2" }
  15860. * ]
  15861. * }
  15862. *
  15863. *
  15864. * # Loading Nested Data
  15865. *
  15866. * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.association.Association
  15867. * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
  15868. * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
  15869. *
  15870. * Ext.define("User", {
  15871. * extend: 'Ext.data.Model',
  15872. * fields: [
  15873. * 'id', 'name'
  15874. * ],
  15875. *
  15876. * hasMany: {model: 'Order', name: 'orders'},
  15877. *
  15878. * proxy: {
  15879. * type: 'rest',
  15880. * url : 'users.json',
  15881. * reader: {
  15882. * type: 'json',
  15883. * root: 'users'
  15884. * }
  15885. * }
  15886. * });
  15887. *
  15888. * Ext.define("Order", {
  15889. * extend: 'Ext.data.Model',
  15890. * fields: [
  15891. * 'id', 'total'
  15892. * ],
  15893. *
  15894. * hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
  15895. * belongsTo: 'User'
  15896. * });
  15897. *
  15898. * Ext.define("OrderItem", {
  15899. * extend: 'Ext.data.Model',
  15900. * fields: [
  15901. * 'id', 'price', 'quantity', 'order_id', 'product_id'
  15902. * ],
  15903. *
  15904. * belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
  15905. * });
  15906. *
  15907. * Ext.define("Product", {
  15908. * extend: 'Ext.data.Model',
  15909. * fields: [
  15910. * 'id', 'name'
  15911. * ],
  15912. *
  15913. * hasMany: 'OrderItem'
  15914. * });
  15915. *
  15916. * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
  15917. * Finally, each OrderItem has a single Product. This allows us to consume data like this:
  15918. *
  15919. * {
  15920. * "users": [
  15921. * {
  15922. * "id": 123,
  15923. * "name": "Ed",
  15924. * "orders": [
  15925. * {
  15926. * "id": 50,
  15927. * "total": 100,
  15928. * "order_items": [
  15929. * {
  15930. * "id" : 20,
  15931. * "price" : 40,
  15932. * "quantity": 2,
  15933. * "product" : {
  15934. * "id": 1000,
  15935. * "name": "MacBook Pro"
  15936. * }
  15937. * },
  15938. * {
  15939. * "id" : 21,
  15940. * "price" : 20,
  15941. * "quantity": 3,
  15942. * "product" : {
  15943. * "id": 1001,
  15944. * "name": "iPhone"
  15945. * }
  15946. * }
  15947. * ]
  15948. * }
  15949. * ]
  15950. * }
  15951. * ]
  15952. * }
  15953. *
  15954. * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
  15955. * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
  15956. * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
  15957. *
  15958. * var store = Ext.create('Ext.data.Store', {
  15959. * model: "User"
  15960. * });
  15961. *
  15962. * store.load({
  15963. * callback: function() {
  15964. * //the user that was loaded
  15965. * var user = store.first();
  15966. *
  15967. * console.log("Orders for " + user.get('name') + ":")
  15968. *
  15969. * //iterate over the Orders for each User
  15970. * user.orders().each(function(order) {
  15971. * console.log("Order ID: " + order.getId() + ", which contains items:");
  15972. *
  15973. * //iterate over the OrderItems for each Order
  15974. * order.orderItems().each(function(orderItem) {
  15975. * //we know that the Product data is already loaded, so we can use the synchronous getProduct
  15976. * //usually, we would use the asynchronous version (see {@link Ext.data.association.BelongsTo})
  15977. * var product = orderItem.getProduct();
  15978. *
  15979. * console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
  15980. * });
  15981. * });
  15982. * }
  15983. * });
  15984. *
  15985. * Running the code above results in the following:
  15986. *
  15987. * Orders for Ed:
  15988. * Order ID: 50, which contains items:
  15989. * 2 orders of MacBook Pro
  15990. * 3 orders of iPhone
  15991. */
  15992. Ext.define('Ext.data.reader.Reader', {
  15993. requires: ['Ext.data.ResultSet'],
  15994. alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
  15995. /**
  15996. * @cfg {String} idProperty
  15997. * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
  15998. * model. If an idProperty is explicitly specified it will override that of the one specified on the model
  15999. */
  16000. /**
  16001. * @cfg {String} totalProperty
  16002. * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
  16003. * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
  16004. */
  16005. totalProperty: 'total',
  16006. /**
  16007. * @cfg {String} successProperty
  16008. * Name of the property from which to retrieve the success attribute. Defaults to success. See
  16009. * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
  16010. */
  16011. successProperty: 'success',
  16012. /**
  16013. * @cfg {String} root
  16014. * The name of the property which contains the Array of row objects. For JSON reader it's dot-separated list
  16015. * of property names. For XML reader it's a CSS selector. For array reader it's not applicable.
  16016. *
  16017. * By default the natural root of the data will be used. The root Json array, the root XML element, or the array.
  16018. *
  16019. * The data packet value for this property should be an empty array to clear the data or show no data.
  16020. */
  16021. root: '',
  16022. /**
  16023. * @cfg {String} messageProperty
  16024. * The name of the property which contains a response message. This property is optional.
  16025. */
  16026. /**
  16027. * @cfg {Boolean} implicitIncludes
  16028. * True to automatically parse models nested within other models in a response object. See the
  16029. * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
  16030. */
  16031. implicitIncludes: true,
  16032. /**
  16033. * @property {Object} metaData
  16034. * The raw meta data that was most recently read, if any. Meta data can include existing
  16035. * Reader config options like {@link #idProperty}, {@link #totalProperty}, etc. that get
  16036. * automatically applied to the Reader, and those can still be accessed directly from the Reader
  16037. * if needed. However, meta data is also often used to pass other custom data to be processed
  16038. * by application code. For example, it is common when reconfiguring the data model of a grid to
  16039. * also pass a corresponding column model config to be applied to the grid. Any such data will
  16040. * not get applied to the Reader directly (it just gets passed through and is ignored by Ext).
  16041. * This metaData property gives you access to all meta data that was passed, including any such
  16042. * custom data ignored by the reader.
  16043. *
  16044. * This is a read-only property, and it will get replaced each time a new meta data object is
  16045. * passed to the reader. Note that typically you would handle proxy's
  16046. * {@link Ext.data.proxy.Proxy#metachange metachange} event which passes this exact same meta
  16047. * object to listeners. However this property is available if it's more convenient to access it
  16048. * via the reader directly in certain cases.
  16049. * @readonly
  16050. */
  16051. /*
  16052. * @property {Boolean} isReader
  16053. * `true` in this class to identify an objact as an instantiated Reader, or subclass thereof.
  16054. */
  16055. isReader: true,
  16056. /**
  16057. * Creates new Reader.
  16058. * @param {Object} config (optional) Config object.
  16059. */
  16060. constructor: function(config) {
  16061. var me = this;
  16062. Ext.apply(me, config || {});
  16063. me.fieldCount = 0;
  16064. me.model = Ext.ModelManager.getModel(config.model);
  16065. if (me.model) {
  16066. me.buildExtractors();
  16067. }
  16068. },
  16069. /**
  16070. * Sets a new model for the reader.
  16071. * @private
  16072. * @param {Object} model The model to set.
  16073. * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
  16074. */
  16075. setModel: function(model, setOnProxy) {
  16076. var me = this;
  16077. me.model = Ext.ModelManager.getModel(model);
  16078. me.buildExtractors(true);
  16079. if (setOnProxy && me.proxy) {
  16080. me.proxy.setModel(me.model, true);
  16081. }
  16082. },
  16083. /**
  16084. * Reads the given response object. This method normalizes the different types of response object that may be passed
  16085. * to it, before handing off the reading of records to the {@link #readRecords} function.
  16086. * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
  16087. * @return {Ext.data.ResultSet} The parsed ResultSet object
  16088. */
  16089. read: function(response) {
  16090. var data = response;
  16091. if (response && response.responseText) {
  16092. data = this.getResponseData(response);
  16093. }
  16094. if (data) {
  16095. return this.readRecords(data);
  16096. } else {
  16097. return this.nullResultSet;
  16098. }
  16099. },
  16100. /**
  16101. * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
  16102. * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
  16103. * processing should not be needed.
  16104. * @param {Object} data The raw data object
  16105. * @return {Ext.data.ResultSet} A ResultSet object
  16106. */
  16107. readRecords: function(data) {
  16108. var me = this;
  16109. /*
  16110. * We check here whether the number of fields has changed since the last read.
  16111. * This works around an issue when a Model is used for both a Tree and another
  16112. * source, because the tree decorates the model with extra fields and it causes
  16113. * issues because the readers aren't notified.
  16114. */
  16115. if (me.fieldCount !== me.getFields().length) {
  16116. me.buildExtractors(true);
  16117. }
  16118. /**
  16119. * @property {Object} rawData
  16120. * The raw data object that was last passed to readRecords. Stored for further processing if needed
  16121. */
  16122. me.rawData = data;
  16123. data = me.getData(data);
  16124. var success = true,
  16125. recordCount = 0,
  16126. records = [],
  16127. root, total, value, message;
  16128. if (me.successProperty) {
  16129. value = me.getSuccess(data);
  16130. if (value === false || value === 'false') {
  16131. success = false;
  16132. }
  16133. }
  16134. if (me.messageProperty) {
  16135. message = me.getMessage(data);
  16136. }
  16137. // Only try and extract other data if call was successful
  16138. if (success) {
  16139. // If we pass an array as the data, we dont use getRoot on the data.
  16140. // Instead the root equals to the data.
  16141. root = Ext.isArray(data) ? data : me.getRoot(data);
  16142. if (root) {
  16143. total = root.length;
  16144. }
  16145. if (me.totalProperty) {
  16146. value = parseInt(me.getTotal(data), 10);
  16147. if (!isNaN(value)) {
  16148. total = value;
  16149. }
  16150. }
  16151. if (root) {
  16152. records = me.extractData(root);
  16153. recordCount = records.length;
  16154. }
  16155. }
  16156. return new Ext.data.ResultSet({
  16157. total : total || recordCount,
  16158. count : recordCount,
  16159. records: records,
  16160. success: success,
  16161. message: message
  16162. });
  16163. },
  16164. /**
  16165. * Returns extracted, type-cast rows of data.
  16166. * @param {Object[]/Object} root from server response
  16167. * @private
  16168. */
  16169. extractData : function(root) {
  16170. var me = this,
  16171. records = [],
  16172. Model = me.model,
  16173. length = root.length,
  16174. convertedValues, node, record, i;
  16175. if (!root.length && Ext.isObject(root)) {
  16176. root = [root];
  16177. length = 1;
  16178. }
  16179. for (i = 0; i < length; i++) {
  16180. node = root[i];
  16181. // Create a record with an empty data object.
  16182. // Populate that data object by extracting and converting field values from raw data
  16183. record = new Model(undefined, me.getId(node), node, convertedValues = {});
  16184. // If the server did not include an id in the response data, the Model constructor will mark the record as phantom.
  16185. // We need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.
  16186. record.phantom = false;
  16187. // Use generated function to extract all fields at once
  16188. me.convertRecordData(convertedValues, node, record);
  16189. records.push(record);
  16190. if (me.implicitIncludes) {
  16191. me.readAssociated(record, node);
  16192. }
  16193. }
  16194. return records;
  16195. },
  16196. /**
  16197. * @private
  16198. * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
  16199. * on the record provided.
  16200. * @param {Ext.data.Model} record The record to load associations for
  16201. * @param {Object} data The data object
  16202. * @return {String} Return value description
  16203. */
  16204. readAssociated: function(record, data) {
  16205. var associations = record.associations.items,
  16206. i = 0,
  16207. length = associations.length,
  16208. association, associationData, proxy, reader;
  16209. for (; i < length; i++) {
  16210. association = associations[i];
  16211. associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
  16212. if (associationData) {
  16213. reader = association.getReader();
  16214. if (!reader) {
  16215. proxy = association.associatedModel.proxy;
  16216. // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
  16217. if (proxy) {
  16218. reader = proxy.getReader();
  16219. } else {
  16220. reader = new this.constructor({
  16221. model: association.associatedName
  16222. });
  16223. }
  16224. }
  16225. association.read(record, reader, associationData);
  16226. }
  16227. }
  16228. },
  16229. /**
  16230. * @private
  16231. * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
  16232. * record, this should return the relevant part of that data for the given association name. This is only really
  16233. * needed to support the XML Reader, which has to do a query to get the associated data object
  16234. * @param {Object} data The raw data object
  16235. * @param {String} associationName The name of the association to get data for (uses associationKey if present)
  16236. * @return {Object} The root
  16237. */
  16238. getAssociatedDataRoot: function(data, associationName) {
  16239. return data[associationName];
  16240. },
  16241. getFields: function() {
  16242. return this.model.prototype.fields.items;
  16243. },
  16244. /**
  16245. * @private
  16246. * By default this function just returns what is passed to it. It can be overridden in a subclass
  16247. * to return something else. See XmlReader for an example.
  16248. * @param {Object} data The data object
  16249. * @return {Object} The normalized data object
  16250. */
  16251. getData: function(data) {
  16252. return data;
  16253. },
  16254. /**
  16255. * @private
  16256. * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
  16257. * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
  16258. * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
  16259. * @param {Object} data The data object
  16260. * @return {Object} The same data object
  16261. */
  16262. getRoot: function(data) {
  16263. return data;
  16264. },
  16265. /**
  16266. * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
  16267. * implemented by each subclass
  16268. * @param {Object} response The responce object
  16269. * @return {Object} The useful data from the response
  16270. */
  16271. getResponseData: function(response) {
  16272. Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
  16273. },
  16274. /**
  16275. * @private
  16276. * Reconfigures the meta data tied to this Reader
  16277. */
  16278. onMetaChange : function(meta) {
  16279. var fields = meta.fields,
  16280. me = this,
  16281. newModel;
  16282. // save off the raw meta data
  16283. me.metaData = meta;
  16284. // set any reader-specific configs from meta if available
  16285. me.root = meta.root || me.root;
  16286. me.idProperty = meta.idProperty || me.idProperty;
  16287. me.totalProperty = meta.totalProperty || me.totalProperty;
  16288. me.successProperty = meta.successProperty || me.successProperty;
  16289. me.messageProperty = meta.messageProperty || me.messageProperty;
  16290. if (fields) {
  16291. if (me.model) {
  16292. me.model.setFields(fields);
  16293. me.setModel(me.model, true);
  16294. }
  16295. else {
  16296. newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
  16297. extend: 'Ext.data.Model',
  16298. fields: fields
  16299. });
  16300. if (me.idProperty) {
  16301. // We only do this if the reader actually has a custom idProperty set,
  16302. // otherwise let the model use its own default value. It is valid for
  16303. // the reader idProperty to be undefined, in which case it will use the
  16304. // model's idProperty (in getIdProperty()).
  16305. newModel.idProperty = me.idProperty;
  16306. }
  16307. me.setModel(newModel, true);
  16308. }
  16309. }
  16310. else {
  16311. me.buildExtractors(true);
  16312. }
  16313. },
  16314. /**
  16315. * Get the idProperty to use for extracting data
  16316. * @private
  16317. * @return {String} The id property
  16318. */
  16319. getIdProperty: function(){
  16320. return this.idProperty || this.model.prototype.idProperty;
  16321. },
  16322. /**
  16323. * @private
  16324. * This builds optimized functions for retrieving record data and meta data from an object.
  16325. * Subclasses may need to implement their own getRoot function.
  16326. * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
  16327. */
  16328. buildExtractors: function(force) {
  16329. var me = this,
  16330. idProp = me.getIdProperty(),
  16331. totalProp = me.totalProperty,
  16332. successProp = me.successProperty,
  16333. messageProp = me.messageProperty,
  16334. accessor,
  16335. idField,
  16336. map;
  16337. if (force === true) {
  16338. delete me.convertRecordData;
  16339. }
  16340. if (me.convertRecordData) {
  16341. return;
  16342. }
  16343. //build the extractors for all the meta data
  16344. if (totalProp) {
  16345. me.getTotal = me.createAccessor(totalProp);
  16346. }
  16347. if (successProp) {
  16348. me.getSuccess = me.createAccessor(successProp);
  16349. }
  16350. if (messageProp) {
  16351. me.getMessage = me.createAccessor(messageProp);
  16352. }
  16353. if (idProp) {
  16354. idField = me.model.prototype.fields.get(idProp);
  16355. if (idField) {
  16356. map = idField.mapping;
  16357. idProp = (map !== undefined && map !== null) ? map : idProp;
  16358. }
  16359. accessor = me.createAccessor(idProp);
  16360. me.getId = function(record) {
  16361. var id = accessor.call(me, record);
  16362. return (id === undefined || id === '') ? null : id;
  16363. };
  16364. } else {
  16365. me.getId = function() {
  16366. return null;
  16367. };
  16368. }
  16369. me.convertRecordData = me.buildRecordDataExtractor();
  16370. },
  16371. /**
  16372. * @private
  16373. * Return a function which will read a raw row object in the format this Reader accepts, and populates
  16374. * a record's data object with converted data values.
  16375. *
  16376. * The returned function must be passed the following parameters:
  16377. *
  16378. * - dest A record's empty data object into which the new field value properties are injected.
  16379. * - source A raw row data object of whatever type this Reader consumes
  16380. * - record The record which is being populated.
  16381. *
  16382. */
  16383. buildRecordDataExtractor: function() {
  16384. var me = this,
  16385. modelProto = me.model.prototype,
  16386. clientIdProp = modelProto.clientIdProperty,
  16387. fields = modelProto.fields.items,
  16388. numFields = fields.length,
  16389. fieldVarName = [],
  16390. prefix = '__field',
  16391. varName,
  16392. i = 0,
  16393. field,
  16394. code = [
  16395. 'var me = this,\n',
  16396. ' fields = me.model.prototype.fields,\n',
  16397. ' value,\n',
  16398. ' internalId'
  16399. ];
  16400. for (; i < numFields; i++) {
  16401. field = fields[i];
  16402. fieldVarName[i] = '__field' + i;
  16403. code.push(',\n ', fieldVarName[i], ' = fields.get("', field.name, '")');
  16404. }
  16405. code.push(';\n\n return function(dest, source, record) {\n');
  16406. for (i = 0; i < numFields; i++) {
  16407. field = fields[i];
  16408. varName = fieldVarName[i];
  16409. // createFieldAccessExpression must be implemented in subclasses to extract data from the source object in the correct way.
  16410. code.push(' dest["' + field.name + '"]', ' = ', me.createFieldAccessExpression(field, varName, 'source'), ';\n');
  16411. }
  16412. // set the client id as the internalId of the record.
  16413. // clientId handles the case where a client side record did not previously exist on the server,
  16414. // so the server is passing back a client id that can be used to pair the server side record up with the client record
  16415. if (clientIdProp) {
  16416. code.push(' if (internalId = ' + me.createFieldAccessExpression({mapping: clientIdProp}, null, 'source') + ') {\n');
  16417. code.push(' record.internalId = internalId;\n }\n');
  16418. }
  16419. code.push(' };');
  16420. // Here we are creating a new Function and invoking it immediately in the scope of this Reader
  16421. // It declares several vars capturing the configured context of this Reader, and returns a function
  16422. // which, when passed a record data object, a raw data row in the format this Reader is configured to read,
  16423. // and the record which is being created, will populate the record's data object from the raw row data.
  16424. return Ext.functionFactory(code.join('')).call(me);
  16425. },
  16426. destroyReader: function() {
  16427. var me = this;
  16428. delete me.proxy;
  16429. delete me.model;
  16430. delete me.convertRecordData;
  16431. delete me.getId;
  16432. delete me.getTotal;
  16433. delete me.getSuccess;
  16434. delete me.getMessage;
  16435. }
  16436. }, function() {
  16437. Ext.apply(this.prototype, {
  16438. // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
  16439. nullResultSet: new Ext.data.ResultSet({
  16440. total : 0,
  16441. count : 0,
  16442. records: [],
  16443. success: true
  16444. })
  16445. });
  16446. });
  16447. /**
  16448. * @author Ed Spencer
  16449. * @class Ext.data.reader.Json
  16450. *
  16451. * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
  16452. * happens as a result of loading a Store - for example we might create something like this:</p>
  16453. *
  16454. <pre><code>
  16455. Ext.define('User', {
  16456. extend: 'Ext.data.Model',
  16457. fields: ['id', 'name', 'email']
  16458. });
  16459. var store = Ext.create('Ext.data.Store', {
  16460. model: 'User',
  16461. proxy: {
  16462. type: 'ajax',
  16463. url : 'users.json',
  16464. reader: {
  16465. type: 'json'
  16466. }
  16467. }
  16468. });
  16469. </code></pre>
  16470. *
  16471. * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
  16472. * not already familiar with them.</p>
  16473. *
  16474. * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
  16475. * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
  16476. * Store, so it is as if we passed this instead:
  16477. *
  16478. <pre><code>
  16479. reader: {
  16480. type : 'json',
  16481. model: 'User'
  16482. }
  16483. </code></pre>
  16484. *
  16485. * <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>
  16486. *
  16487. <pre><code>
  16488. [
  16489. {
  16490. "id": 1,
  16491. "name": "Ed Spencer",
  16492. "email": "ed@sencha.com"
  16493. },
  16494. {
  16495. "id": 2,
  16496. "name": "Abe Elias",
  16497. "email": "abe@sencha.com"
  16498. }
  16499. ]
  16500. </code></pre>
  16501. *
  16502. * <p><u>Reading other JSON formats</u></p>
  16503. *
  16504. * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
  16505. * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
  16506. * {@link #root} configuration to parse data that comes back like this:</p>
  16507. *
  16508. <pre><code>
  16509. {
  16510. "users": [
  16511. {
  16512. "id": 1,
  16513. "name": "Ed Spencer",
  16514. "email": "ed@sencha.com"
  16515. },
  16516. {
  16517. "id": 2,
  16518. "name": "Abe Elias",
  16519. "email": "abe@sencha.com"
  16520. }
  16521. ]
  16522. }
  16523. </code></pre>
  16524. *
  16525. * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
  16526. *
  16527. <pre><code>
  16528. reader: {
  16529. type: 'json',
  16530. root: 'users'
  16531. }
  16532. </code></pre>
  16533. *
  16534. * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
  16535. * around each record inside a nested structure like this:</p>
  16536. *
  16537. <pre><code>
  16538. {
  16539. "total": 122,
  16540. "offset": 0,
  16541. "users": [
  16542. {
  16543. "id": "ed-spencer-1",
  16544. "value": 1,
  16545. "user": {
  16546. "id": 1,
  16547. "name": "Ed Spencer",
  16548. "email": "ed@sencha.com"
  16549. }
  16550. }
  16551. ]
  16552. }
  16553. </code></pre>
  16554. *
  16555. * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
  16556. * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
  16557. * JSON above we need to specify the {@link #record} configuration like this:</p>
  16558. *
  16559. <pre><code>
  16560. reader: {
  16561. type : 'json',
  16562. root : 'users',
  16563. record: 'user'
  16564. }
  16565. </code></pre>
  16566. *
  16567. * <p><u>Response MetaData</u></p>
  16568. *
  16569. * The server can return metadata in its response, in addition to the record data, that describe attributes
  16570. * of the data set itself or are used to reconfigure the Reader. To pass metadata in the response you simply
  16571. * add a `metaData` attribute to the root of the response data. The metaData attribute can contain anything,
  16572. * but supports a specific set of properties that are handled by the Reader if they are present:
  16573. *
  16574. * - {@link #root}: the property name of the root response node containing the record data
  16575. * - {@link #idProperty}: property name for the primary key field of the data
  16576. * - {@link #totalProperty}: property name for the total number of records in the data
  16577. * - {@link #successProperty}: property name for the success status of the response
  16578. * - {@link #messageProperty}: property name for an optional response message
  16579. * - {@link Ext.data.Model#cfg-fields fields}: Config used to reconfigure the Model's fields before converting the
  16580. * response data into records
  16581. *
  16582. * An initial Reader configuration containing all of these properties might look like this ("fields" would be
  16583. * included in the Model definition, not shown):
  16584. reader: {
  16585. type : 'json',
  16586. root : 'root',
  16587. idProperty : 'id',
  16588. totalProperty : 'total',
  16589. successProperty: 'success',
  16590. messageProperty: 'message'
  16591. }
  16592. If you were to pass a response object containing attributes different from those initially defined above, you could
  16593. use the `metaData` attribute to reconifgure the Reader on the fly. For example:
  16594. {
  16595. "count": 1,
  16596. "ok": true,
  16597. "msg": "Users found",
  16598. "users": [{
  16599. "userId": 123,
  16600. "name": "Ed Spencer",
  16601. "email": "ed@sencha.com"
  16602. }],
  16603. "metaData": {
  16604. "root": "users",
  16605. "idProperty": 'userId',
  16606. "totalProperty": 'count',
  16607. "successProperty": 'ok',
  16608. "messageProperty": 'msg'
  16609. }
  16610. }
  16611. * You can also place any other arbitrary data you need into the `metaData` attribute which will be ignored by the Reader,
  16612. * but will be accessible via the Reader's {@link #metaData} property (which is also passed to listeners via the Proxy's
  16613. * {@link Ext.data.proxy.Proxy#metachange metachange} event (also relayed by the {@link Ext.data.AbstractStore#metachange
  16614. * store}). Application code can then process the passed metadata in any way it chooses.
  16615. *
  16616. * A simple example for how this can be used would be customizing the fields for a Model that is bound to a grid. By passing
  16617. * the `fields` property the Model will be automatically updated by the Reader internally, but that change will not be
  16618. * reflected automatically in the grid unless you also update the column configuration. You could do this manually, or you
  16619. * could simply pass a standard grid {@link Ext.panel.Table#columns column} config object as part of the `metaData` attribute
  16620. * and then pass that along to the grid. Here's a very simple example for how that could be accomplished:
  16621. // response format:
  16622. {
  16623. ...
  16624. "metaData": {
  16625. "fields": [
  16626. { "name": "userId", "type": "int" },
  16627. { "name": "name", "type": "string" },
  16628. { "name": "birthday", "type": "date", "dateFormat": "Y-j-m" },
  16629. ],
  16630. "columns": [
  16631. { "text": "User ID", "dataIndex": "userId", "width": 40 },
  16632. { "text": "User Name", "dataIndex": "name", "flex": 1 },
  16633. { "text": "Birthday", "dataIndex": "birthday", "flex": 1, "format": 'Y-j-m', "xtype": "datecolumn" }
  16634. ]
  16635. }
  16636. }
  16637. The Reader will automatically read the meta fields config and rebuild the Model based on the new fields, but to handle
  16638. the new column configuration you would need to handle the metadata within the application code. This is done simply enough
  16639. by handling the metachange event on either the store or the proxy, e.g.:
  16640. var store = Ext.create('Ext.data.Store', {
  16641. ...
  16642. listeners: {
  16643. 'metachange': function(store, meta) {
  16644. myGrid.reconfigure(store, meta.columns);
  16645. }
  16646. }
  16647. });
  16648. */
  16649. Ext.define('Ext.data.reader.Json', {
  16650. extend: 'Ext.data.reader.Reader',
  16651. alternateClassName: 'Ext.data.JsonReader',
  16652. alias : 'reader.json',
  16653. root: '',
  16654. /**
  16655. * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
  16656. * See the JsonReader intro docs for more details. This is not often needed.
  16657. */
  16658. /**
  16659. * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
  16660. * reading values. Defalts to <tt>false</tt>.
  16661. * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
  16662. * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
  16663. * "foo.bar.baz" direct from the root object.
  16664. */
  16665. useSimpleAccessors: false,
  16666. /**
  16667. * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
  16668. * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
  16669. * @param {Object} data The raw JSON data
  16670. * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
  16671. */
  16672. readRecords: function(data) {
  16673. //this has to be before the call to super because we use the meta data in the superclass readRecords
  16674. if (data.metaData) {
  16675. this.onMetaChange(data.metaData);
  16676. }
  16677. /**
  16678. * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
  16679. * @property {Object} jsonData
  16680. */
  16681. this.jsonData = data;
  16682. return this.callParent([data]);
  16683. },
  16684. //inherit docs
  16685. getResponseData: function(response) {
  16686. var data;
  16687. try {
  16688. data = Ext.decode(response.responseText);
  16689. }
  16690. catch (ex) {
  16691. Ext.Error.raise({
  16692. response: response,
  16693. json: response.responseText,
  16694. parseError: ex,
  16695. msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
  16696. });
  16697. }
  16698. if (!data) {
  16699. Ext.Error.raise('JSON object not found');
  16700. }
  16701. return data;
  16702. },
  16703. //inherit docs
  16704. buildExtractors : function() {
  16705. var me = this;
  16706. me.callParent(arguments);
  16707. if (me.root) {
  16708. me.getRoot = me.createAccessor(me.root);
  16709. } else {
  16710. me.getRoot = function(root) {
  16711. return root;
  16712. };
  16713. }
  16714. },
  16715. /**
  16716. * @private
  16717. * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
  16718. * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
  16719. * @param {Object} root The JSON root node
  16720. * @return {Ext.data.Model[]} The records
  16721. */
  16722. extractData: function(root) {
  16723. var recordName = this.record,
  16724. data = [],
  16725. length, i;
  16726. if (recordName) {
  16727. length = root.length;
  16728. if (!length && Ext.isObject(root)) {
  16729. length = 1;
  16730. root = [root];
  16731. }
  16732. for (i = 0; i < length; i++) {
  16733. data[i] = root[i][recordName];
  16734. }
  16735. } else {
  16736. data = root;
  16737. }
  16738. return this.callParent([data]);
  16739. },
  16740. /**
  16741. * @private
  16742. * Returns an accessor function for the given property string. Gives support for properties such as the following:
  16743. * 'someProperty'
  16744. * 'some.property'
  16745. * 'some["property"]'
  16746. * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
  16747. */
  16748. createAccessor: function() {
  16749. var re = /[\[\.]/;
  16750. return function(expr) {
  16751. if (Ext.isEmpty(expr)) {
  16752. return Ext.emptyFn;
  16753. }
  16754. if (Ext.isFunction(expr)) {
  16755. return expr;
  16756. }
  16757. if (this.useSimpleAccessors !== true) {
  16758. var i = String(expr).search(re);
  16759. if (i >= 0) {
  16760. return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
  16761. }
  16762. }
  16763. return function(obj) {
  16764. return obj[expr];
  16765. };
  16766. };
  16767. }(),
  16768. /**
  16769. * @private
  16770. * Returns an accessor expression for the passed Field. Gives support for properties such as the following:
  16771. * 'someProperty'
  16772. * 'some.property'
  16773. * 'some["property"]'
  16774. * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
  16775. */
  16776. createFieldAccessExpression: function() {
  16777. var re = /[\[\.]/;
  16778. return function(field, fieldVarName, dataName) {
  16779. var me = this,
  16780. hasMap = (field.mapping !== null),
  16781. map = hasMap ? field.mapping : field.name,
  16782. result,
  16783. operatorSearch;
  16784. if (typeof map === 'function') {
  16785. result = fieldVarName + '.mapping(' + dataName + ', this)';
  16786. } else if (this.useSimpleAccessors === true || ((operatorSearch = String(map).search(re)) < 0)) {
  16787. if (!hasMap || isNaN(map)) {
  16788. // If we don't provide a mapping, we may have a field name that is numeric
  16789. map = '"' + map + '"';
  16790. }
  16791. result = dataName + "[" + map + "]";
  16792. } else {
  16793. result = dataName + (operatorSearch > 0 ? '.' : '') + map;
  16794. }
  16795. if (field.defaultValue !== undefined) {
  16796. result = '(' + result + ' === undefined) ? ' + fieldVarName + '.defaultValue : ' + result;
  16797. }
  16798. if (field.convert) {
  16799. result = fieldVarName + '.convert(' + result + ', record)';
  16800. }
  16801. return result;
  16802. };
  16803. }()
  16804. });
  16805. /**
  16806. * @author Ed Spencer
  16807. *
  16808. * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
  16809. * data. Usually developers will not need to create or interact with proxies directly.
  16810. *
  16811. * # Types of Proxy
  16812. *
  16813. * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
  16814. * The Client proxies save their data locally and include the following subclasses:
  16815. *
  16816. * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
  16817. * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
  16818. * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
  16819. *
  16820. * The Server proxies save their data by sending requests to some remote server. These proxies include:
  16821. *
  16822. * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
  16823. * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
  16824. * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
  16825. *
  16826. * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
  16827. * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
  16828. * respectively. Each Proxy subclass implements these functions.
  16829. *
  16830. * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
  16831. * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
  16832. * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
  16833. * method also accepts a callback function to be called asynchronously on completion.
  16834. *
  16835. * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
  16836. * method.
  16837. */
  16838. Ext.define('Ext.data.proxy.Proxy', {
  16839. alias: 'proxy.proxy',
  16840. alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
  16841. requires: [
  16842. 'Ext.data.reader.Json',
  16843. 'Ext.data.writer.Json'
  16844. ],
  16845. uses: [
  16846. 'Ext.data.Batch',
  16847. 'Ext.data.Operation',
  16848. 'Ext.data.Model'
  16849. ],
  16850. mixins: {
  16851. observable: 'Ext.util.Observable'
  16852. },
  16853. /**
  16854. * @cfg {String} batchOrder
  16855. * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
  16856. * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
  16857. */
  16858. batchOrder: 'create,update,destroy',
  16859. /**
  16860. * @cfg {Boolean} batchActions
  16861. * True to batch actions of a particular type when synchronizing the store. Defaults to true.
  16862. */
  16863. batchActions: true,
  16864. /**
  16865. * @cfg {String} defaultReaderType
  16866. * The default registered reader type. Defaults to 'json'.
  16867. * @private
  16868. */
  16869. defaultReaderType: 'json',
  16870. /**
  16871. * @cfg {String} defaultWriterType
  16872. * The default registered writer type. Defaults to 'json'.
  16873. * @private
  16874. */
  16875. defaultWriterType: 'json',
  16876. /**
  16877. * @cfg {String/Ext.data.Model} model
  16878. * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
  16879. * Model constructor. Required.
  16880. */
  16881. /**
  16882. * @cfg {Object/String/Ext.data.reader.Reader} reader
  16883. * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
  16884. * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
  16885. */
  16886. /**
  16887. * @cfg {Object/String/Ext.data.writer.Writer} writer
  16888. * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
  16889. * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
  16890. */
  16891. /**
  16892. * @property {Boolean} isProxy
  16893. * `true` in this class to identify an objact as an instantiated Proxy, or subclass thereof.
  16894. */
  16895. isProxy: true,
  16896. /**
  16897. * Creates the Proxy
  16898. * @param {Object} config (optional) Config object.
  16899. */
  16900. constructor: function(config) {
  16901. config = config || {};
  16902. if (config.model === undefined) {
  16903. delete config.model;
  16904. }
  16905. Ext.apply(this, config);
  16906. this.mixins.observable.constructor.call(this);
  16907. if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
  16908. this.setModel(this.model);
  16909. }
  16910. /**
  16911. * @event metachange
  16912. * Fires when this proxy's reader provides new metadata. Metadata usually consists
  16913. * of new field definitions, but can include any configuration data required by an
  16914. * application, and can be processed as needed in the event handler.
  16915. * This event is currently only fired for JsonReaders. Note that this event is also
  16916. * propagated by {@link Ext.data.Store}, which is typically where it would be handled.
  16917. * @param {Ext.data.proxy.Proxy} this
  16918. * @param {Object} meta The JSON metadata
  16919. */
  16920. },
  16921. /**
  16922. * Sets the model associated with this proxy. This will only usually be called by a Store
  16923. *
  16924. * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
  16925. * or a reference to the model's constructor
  16926. * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
  16927. */
  16928. setModel: function(model, setOnStore) {
  16929. this.model = Ext.ModelManager.getModel(model);
  16930. var reader = this.reader,
  16931. writer = this.writer;
  16932. this.setReader(reader);
  16933. this.setWriter(writer);
  16934. if (setOnStore && this.store) {
  16935. this.store.setModel(this.model);
  16936. }
  16937. },
  16938. /**
  16939. * Returns the model attached to this Proxy
  16940. * @return {Ext.data.Model} The model
  16941. */
  16942. getModel: function() {
  16943. return this.model;
  16944. },
  16945. /**
  16946. * Sets the Proxy's Reader by string, config object or Reader instance
  16947. *
  16948. * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
  16949. * a configuration object or an Ext.data.reader.Reader instance
  16950. * @return {Ext.data.reader.Reader} The attached Reader object
  16951. */
  16952. setReader: function(reader) {
  16953. var me = this;
  16954. if (reader === undefined || typeof reader == 'string') {
  16955. reader = {
  16956. type: reader
  16957. };
  16958. }
  16959. if (reader.isReader) {
  16960. reader.setModel(me.model);
  16961. } else {
  16962. Ext.applyIf(reader, {
  16963. proxy: me,
  16964. model: me.model,
  16965. type : me.defaultReaderType
  16966. });
  16967. reader = Ext.createByAlias('reader.' + reader.type, reader);
  16968. }
  16969. if (reader.onMetaChange) {
  16970. reader.onMetaChange = Ext.Function.createSequence(reader.onMetaChange, this.onMetaChange, this);
  16971. }
  16972. me.reader = reader;
  16973. return me.reader;
  16974. },
  16975. /**
  16976. * Returns the reader currently attached to this proxy instance
  16977. * @return {Ext.data.reader.Reader} The Reader instance
  16978. */
  16979. getReader: function() {
  16980. return this.reader;
  16981. },
  16982. /**
  16983. * @private
  16984. * Called each time the reader's onMetaChange is called so that the proxy can fire the metachange event
  16985. */
  16986. onMetaChange: function(meta) {
  16987. this.fireEvent('metachange', this, meta);
  16988. },
  16989. /**
  16990. * Sets the Proxy's Writer by string, config object or Writer instance
  16991. *
  16992. * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
  16993. * a configuration object or an Ext.data.writer.Writer instance
  16994. * @return {Ext.data.writer.Writer} The attached Writer object
  16995. */
  16996. setWriter: function(writer) {
  16997. if (writer === undefined || typeof writer == 'string') {
  16998. writer = {
  16999. type: writer
  17000. };
  17001. }
  17002. if (!(writer instanceof Ext.data.writer.Writer)) {
  17003. Ext.applyIf(writer, {
  17004. model: this.model,
  17005. type : this.defaultWriterType
  17006. });
  17007. writer = Ext.createByAlias('writer.' + writer.type, writer);
  17008. }
  17009. this.writer = writer;
  17010. return this.writer;
  17011. },
  17012. /**
  17013. * Returns the writer currently attached to this proxy instance
  17014. * @return {Ext.data.writer.Writer} The Writer instance
  17015. */
  17016. getWriter: function() {
  17017. return this.writer;
  17018. },
  17019. /**
  17020. * Performs the given create operation.
  17021. * @param {Ext.data.Operation} operation The Operation to perform
  17022. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  17023. * successful or not)
  17024. * @param {Object} scope Scope to execute the callback function in
  17025. * @method
  17026. */
  17027. create: Ext.emptyFn,
  17028. /**
  17029. * Performs the given read operation.
  17030. * @param {Ext.data.Operation} operation The Operation to perform
  17031. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  17032. * successful or not)
  17033. * @param {Object} scope Scope to execute the callback function in
  17034. * @method
  17035. */
  17036. read: Ext.emptyFn,
  17037. /**
  17038. * Performs the given update operation.
  17039. * @param {Ext.data.Operation} operation The Operation to perform
  17040. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  17041. * successful or not)
  17042. * @param {Object} scope Scope to execute the callback function in
  17043. * @method
  17044. */
  17045. update: Ext.emptyFn,
  17046. /**
  17047. * Performs the given destroy operation.
  17048. * @param {Ext.data.Operation} operation The Operation to perform
  17049. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  17050. * successful or not)
  17051. * @param {Object} scope Scope to execute the callback function in
  17052. * @method
  17053. */
  17054. destroy: Ext.emptyFn,
  17055. /**
  17056. * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
  17057. * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
  17058. *
  17059. * myProxy.batch({
  17060. * create : [myModel1, myModel2],
  17061. * update : [myModel3],
  17062. * destroy: [myModel4, myModel5]
  17063. * });
  17064. *
  17065. * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
  17066. * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
  17067. * saved but should now be destroyed.
  17068. *
  17069. * Note that the previous version of this method took 2 arguments (operations and listeners). While this is still
  17070. * supported for now, the current signature is now a single `options` argument that can contain both operations and
  17071. * listeners, in addition to other options. The multi-argument signature will likely be deprecated in a future release.
  17072. *
  17073. * @param {Object} options Object containing one or more properties supported by the batch method:
  17074. *
  17075. * @param {Object} options.operations Object containing the Model instances to act upon, keyed by action name
  17076. *
  17077. * @param {Object} [options.listeners] Event listeners object passed straight through to the Batch -
  17078. * see {@link Ext.data.Batch} for details
  17079. *
  17080. * @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch config to apply
  17081. * to the created batch). If unspecified a default batch will be auto-created.
  17082. *
  17083. * @param {Function} [options.callback] The function to be called upon completion of processing the batch.
  17084. * The callback is called regardless of success or failure and is passed the following parameters:
  17085. * @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was processed,
  17086. * containing all operations in their current state after processing
  17087. * @param {Object} options.callback.options The options argument that was originally passed into batch
  17088. *
  17089. * @param {Function} [options.success] The function to be called upon successful completion of the batch. The
  17090. * success function is called only if no exceptions were reported in any operations. If one or more exceptions
  17091. * occurred then the `failure` function will be called instead. The success function is called
  17092. * with the following parameters:
  17093. * @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was processed,
  17094. * containing all operations in their current state after processing
  17095. * @param {Object} options.success.options The options argument that was originally passed into batch
  17096. *
  17097. * @param {Function} [options.failure] The function to be called upon unsuccessful completion of the batch. The
  17098. * failure function is called when one or more operations returns an exception during processing (even if some
  17099. * operations were also successful). In this case you can check the batch's {@link Ext.data.Batch#exceptions
  17100. * exceptions} array to see exactly which operations had exceptions. The failure function is called with the
  17101. * following parameters:
  17102. * @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch batch} that was processed,
  17103. * containing all operations in their current state after processing
  17104. * @param {Object} options.failure.options The options argument that was originally passed into batch
  17105. *
  17106. * @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this` object inside
  17107. * the callback, success and/or failure functions). Defaults to the proxy.
  17108. *
  17109. * @return {Ext.data.Batch} The newly created Batch
  17110. */
  17111. batch: function(options, /* deprecated */listeners) {
  17112. var me = this,
  17113. useBatch = me.batchActions,
  17114. batch,
  17115. records;
  17116. if (options.operations === undefined) {
  17117. // the old-style (operations, listeners) signature was called
  17118. // so convert to the single options argument syntax
  17119. options = {
  17120. operations: options,
  17121. listeners: listeners
  17122. }
  17123. }
  17124. if (options.batch) {
  17125. if (Ext.isDefined(options.batch.runOperation)) {
  17126. batch = Ext.applyIf(options.batch, {
  17127. proxy: me,
  17128. listeners: {}
  17129. });
  17130. }
  17131. } else {
  17132. options.batch = {
  17133. proxy: me,
  17134. listeners: options.listeners || {}
  17135. };
  17136. }
  17137. if (!batch) {
  17138. batch = new Ext.data.Batch(options.batch);
  17139. }
  17140. batch.on('complete', Ext.bind(me.onBatchComplete, me, [options], 0));
  17141. var actions = me.batchOrder.split(','),
  17142. aLen = actions.length,
  17143. action, a, r, rLen, record;
  17144. for (a = 0; a < aLen; a++) {
  17145. action = actions[a];
  17146. records = options.operations[action];
  17147. if (records) {
  17148. if (useBatch) {
  17149. batch.add(new Ext.data.Operation({
  17150. action : action,
  17151. records : records
  17152. }));
  17153. } else {
  17154. rLen = records.length;
  17155. for (r = 0; r < rLen; r++) {
  17156. record = records[r];
  17157. batch.add(new Ext.data.Operation({
  17158. action : action,
  17159. records : [record]
  17160. }));
  17161. }
  17162. }
  17163. }
  17164. }
  17165. batch.start();
  17166. return batch;
  17167. },
  17168. /**
  17169. * @private
  17170. * The internal callback that the proxy uses to call any specified user callbacks after completion of a batch
  17171. */
  17172. onBatchComplete: function(batchOptions, batch) {
  17173. var scope = batchOptions.scope || this;
  17174. if (batch.hasException) {
  17175. if (Ext.isFunction(batchOptions.failure)) {
  17176. Ext.callback(batchOptions.failure, scope, [batch, batchOptions]);
  17177. }
  17178. } else if (Ext.isFunction(batchOptions.success)) {
  17179. Ext.callback(batchOptions.success, scope, [batch, batchOptions]);
  17180. }
  17181. if (Ext.isFunction(batchOptions.callback)) {
  17182. Ext.callback(batchOptions.callback, scope, [batch, batchOptions]);
  17183. }
  17184. }
  17185. }, function() {
  17186. //backwards compatibility
  17187. Ext.data.DataProxy = this;
  17188. });
  17189. /**
  17190. * @author Ed Spencer
  17191. *
  17192. * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
  17193. * would not usually be used directly.
  17194. *
  17195. * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
  17196. * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
  17197. * an alias of AjaxProxy).
  17198. * @private
  17199. */
  17200. Ext.define('Ext.data.proxy.Server', {
  17201. extend: 'Ext.data.proxy.Proxy',
  17202. alias : 'proxy.server',
  17203. alternateClassName: 'Ext.data.ServerProxy',
  17204. uses : ['Ext.data.Request'],
  17205. /**
  17206. * @cfg {String} url
  17207. * The URL from which to request the data object.
  17208. */
  17209. /**
  17210. * @cfg {String} pageParam
  17211. * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
  17212. * want to send a page parameter.
  17213. */
  17214. pageParam: 'page',
  17215. /**
  17216. * @cfg {String} startParam
  17217. * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
  17218. * want to send a start parameter.
  17219. */
  17220. startParam: 'start',
  17221. /**
  17222. * @cfg {String} limitParam
  17223. * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
  17224. * want to send a limit parameter.
  17225. */
  17226. limitParam: 'limit',
  17227. /**
  17228. * @cfg {String} groupParam
  17229. * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
  17230. * want to send a group parameter.
  17231. */
  17232. groupParam: 'group',
  17233. /**
  17234. * @cfg {String} sortParam
  17235. * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
  17236. * want to send a sort parameter.
  17237. */
  17238. sortParam: 'sort',
  17239. /**
  17240. * @cfg {String} filterParam
  17241. * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
  17242. * want to send a filter parameter.
  17243. */
  17244. filterParam: 'filter',
  17245. /**
  17246. * @cfg {String} directionParam
  17247. * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
  17248. * true.** Defaults to 'dir'.
  17249. */
  17250. directionParam: 'dir',
  17251. /**
  17252. * @cfg {Boolean} simpleSortMode
  17253. * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
  17254. * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
  17255. * or 'DESC'.
  17256. */
  17257. simpleSortMode: false,
  17258. /**
  17259. * @cfg {Boolean} noCache
  17260. * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
  17261. */
  17262. noCache : true,
  17263. /**
  17264. * @cfg {String} cacheString
  17265. * The name of the cache param added to the url when using noCache. Defaults to "_dc".
  17266. */
  17267. cacheString: "_dc",
  17268. /**
  17269. * @cfg {Number} timeout
  17270. * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
  17271. */
  17272. timeout : 30000,
  17273. /**
  17274. * @cfg {Object} api
  17275. * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
  17276. *
  17277. * api: {
  17278. * create : undefined,
  17279. * read : undefined,
  17280. * update : undefined,
  17281. * destroy : undefined
  17282. * }
  17283. *
  17284. * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
  17285. * {@link #api} property, or if undefined default to the configured
  17286. * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
  17287. *
  17288. * For example:
  17289. *
  17290. * api: {
  17291. * create : '/controller/new',
  17292. * read : '/controller/load',
  17293. * update : '/controller/update',
  17294. * destroy : '/controller/destroy_action'
  17295. * }
  17296. *
  17297. * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
  17298. * configured {@link Ext.data.proxy.Server#url url}.
  17299. */
  17300. constructor: function(config) {
  17301. var me = this;
  17302. config = config || {};
  17303. /**
  17304. * @event exception
  17305. * Fires when the server returns an exception
  17306. * @param {Ext.data.proxy.Proxy} this
  17307. * @param {Object} response The response from the AJAX request
  17308. * @param {Ext.data.Operation} operation The operation that triggered request
  17309. */
  17310. me.callParent([config]);
  17311. /**
  17312. * @cfg {Object} extraParams
  17313. * Extra parameters that will be included on every request. Individual requests with params of the same name
  17314. * will override these params when they are in conflict.
  17315. */
  17316. me.extraParams = config.extraParams || {};
  17317. me.api = Ext.apply({}, config.api || me.api);
  17318. //backwards compatibility, will be deprecated in 5.0
  17319. me.nocache = me.noCache;
  17320. },
  17321. //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
  17322. create: function() {
  17323. return this.doRequest.apply(this, arguments);
  17324. },
  17325. read: function() {
  17326. return this.doRequest.apply(this, arguments);
  17327. },
  17328. update: function() {
  17329. return this.doRequest.apply(this, arguments);
  17330. },
  17331. destroy: function() {
  17332. return this.doRequest.apply(this, arguments);
  17333. },
  17334. /**
  17335. * Sets a value in the underlying {@link #extraParams}.
  17336. * @param {String} name The key for the new value
  17337. * @param {Object} value The value
  17338. */
  17339. setExtraParam: function(name, value) {
  17340. this.extraParams[name] = value;
  17341. },
  17342. /**
  17343. * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
  17344. * that this Proxy is attached to.
  17345. * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
  17346. * @return {Ext.data.Request} The request object
  17347. */
  17348. buildRequest: function(operation) {
  17349. var me = this,
  17350. params = Ext.applyIf(operation.params || {}, me.extraParams || {}),
  17351. request;
  17352. //copy any sorters, filters etc into the params so they can be sent over the wire
  17353. params = Ext.applyIf(params, me.getParams(operation));
  17354. if (operation.id && !params.id) {
  17355. params.id = operation.id;
  17356. }
  17357. request = new Ext.data.Request({
  17358. params : params,
  17359. action : operation.action,
  17360. records : operation.records,
  17361. operation: operation,
  17362. url : operation.url,
  17363. // this is needed by JsonSimlet in order to properly construct responses for
  17364. // requests from this proxy
  17365. proxy: me
  17366. });
  17367. request.url = me.buildUrl(request);
  17368. /*
  17369. * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
  17370. * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
  17371. */
  17372. operation.request = request;
  17373. return request;
  17374. },
  17375. // Should this be documented as protected method?
  17376. processResponse: function(success, operation, request, response, callback, scope) {
  17377. var me = this,
  17378. reader,
  17379. result;
  17380. if (success === true) {
  17381. reader = me.getReader();
  17382. result = reader.read(me.extractResponseData(response));
  17383. if (result.success !== false) {
  17384. //see comment in buildRequest for why we include the response object here
  17385. Ext.apply(operation, {
  17386. response: response,
  17387. resultSet: result
  17388. });
  17389. operation.commitRecords(result.records);
  17390. operation.setCompleted();
  17391. operation.setSuccessful();
  17392. } else {
  17393. operation.setException(result.message);
  17394. me.fireEvent('exception', this, response, operation);
  17395. }
  17396. } else {
  17397. me.setException(operation, response);
  17398. me.fireEvent('exception', this, response, operation);
  17399. }
  17400. //this callback is the one that was passed to the 'read' or 'write' function above
  17401. if (typeof callback == 'function') {
  17402. callback.call(scope || me, operation);
  17403. }
  17404. me.afterRequest(request, success);
  17405. },
  17406. /**
  17407. * Sets up an exception on the operation
  17408. * @private
  17409. * @param {Ext.data.Operation} operation The operation
  17410. * @param {Object} response The response
  17411. */
  17412. setException: function(operation, response) {
  17413. operation.setException({
  17414. status: response.status,
  17415. statusText: response.statusText
  17416. });
  17417. },
  17418. /**
  17419. * Template method to allow subclasses to specify how to get the response for the reader.
  17420. * @template
  17421. * @private
  17422. * @param {Object} response The server response
  17423. * @return {Object} The response data to be used by the reader
  17424. */
  17425. extractResponseData: function(response) {
  17426. return response;
  17427. },
  17428. /**
  17429. * Encode any values being sent to the server. Can be overridden in subclasses.
  17430. * @private
  17431. * @param {Array} An array of sorters/filters.
  17432. * @return {Object} The encoded value
  17433. */
  17434. applyEncoding: function(value) {
  17435. return Ext.encode(value);
  17436. },
  17437. /**
  17438. * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
  17439. * this simply JSON-encodes the sorter data
  17440. * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
  17441. * @return {String} The encoded sorters
  17442. */
  17443. encodeSorters: function(sorters) {
  17444. var min = [],
  17445. length = sorters.length,
  17446. i = 0;
  17447. for (; i < length; i++) {
  17448. min[i] = {
  17449. property : sorters[i].property,
  17450. direction: sorters[i].direction
  17451. };
  17452. }
  17453. return this.applyEncoding(min);
  17454. },
  17455. /**
  17456. * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
  17457. * this simply JSON-encodes the filter data
  17458. * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
  17459. * @return {String} The encoded filters
  17460. */
  17461. encodeFilters: function(filters) {
  17462. var min = [],
  17463. length = filters.length,
  17464. i = 0;
  17465. for (; i < length; i++) {
  17466. min[i] = {
  17467. property: filters[i].property,
  17468. value : filters[i].value
  17469. };
  17470. }
  17471. return this.applyEncoding(min);
  17472. },
  17473. /**
  17474. * @private
  17475. * Copy any sorters, filters etc into the params so they can be sent over the wire
  17476. */
  17477. getParams: function(operation) {
  17478. var me = this,
  17479. params = {},
  17480. isDef = Ext.isDefined,
  17481. groupers = operation.groupers,
  17482. sorters = operation.sorters,
  17483. filters = operation.filters,
  17484. page = operation.page,
  17485. start = operation.start,
  17486. limit = operation.limit,
  17487. simpleSortMode = me.simpleSortMode,
  17488. pageParam = me.pageParam,
  17489. startParam = me.startParam,
  17490. limitParam = me.limitParam,
  17491. groupParam = me.groupParam,
  17492. sortParam = me.sortParam,
  17493. filterParam = me.filterParam,
  17494. directionParam = me.directionParam;
  17495. if (pageParam && isDef(page)) {
  17496. params[pageParam] = page;
  17497. }
  17498. if (startParam && isDef(start)) {
  17499. params[startParam] = start;
  17500. }
  17501. if (limitParam && isDef(limit)) {
  17502. params[limitParam] = limit;
  17503. }
  17504. if (groupParam && groupers && groupers.length > 0) {
  17505. // Grouper is a subclass of sorter, so we can just use the sorter method
  17506. params[groupParam] = me.encodeSorters(groupers);
  17507. }
  17508. if (sortParam && sorters && sorters.length > 0) {
  17509. if (simpleSortMode) {
  17510. params[sortParam] = sorters[0].property;
  17511. params[directionParam] = sorters[0].direction;
  17512. } else {
  17513. params[sortParam] = me.encodeSorters(sorters);
  17514. }
  17515. }
  17516. if (filterParam && filters && filters.length > 0) {
  17517. params[filterParam] = me.encodeFilters(filters);
  17518. }
  17519. return params;
  17520. },
  17521. /**
  17522. * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
  17523. * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
  17524. * @param {Ext.data.Request} request The request object
  17525. * @return {String} The url
  17526. */
  17527. buildUrl: function(request) {
  17528. var me = this,
  17529. url = me.getUrl(request);
  17530. if (!url) {
  17531. Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
  17532. }
  17533. if (me.noCache) {
  17534. url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
  17535. }
  17536. return url;
  17537. },
  17538. /**
  17539. * Get the url for the request taking into account the order of priority,
  17540. * - The request
  17541. * - The api
  17542. * - The url
  17543. * @private
  17544. * @param {Ext.data.Request} request The request
  17545. * @return {String} The url
  17546. */
  17547. getUrl: function(request) {
  17548. return request.url || this.api[request.action] || this.url;
  17549. },
  17550. /**
  17551. * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
  17552. * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
  17553. * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
  17554. * each of the methods that delegate to it.
  17555. *
  17556. * @param {Ext.data.Operation} operation The Ext.data.Operation object
  17557. * @param {Function} callback The callback function to call when the Operation has completed
  17558. * @param {Object} scope The scope in which to execute the callback
  17559. */
  17560. doRequest: function(operation, callback, scope) {
  17561. Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
  17562. },
  17563. /**
  17564. * Optional callback function which can be used to clean up after a request has been completed.
  17565. * @param {Ext.data.Request} request The Request object
  17566. * @param {Boolean} success True if the request was successful
  17567. * @method
  17568. */
  17569. afterRequest: Ext.emptyFn,
  17570. onDestroy: function() {
  17571. Ext.destroy(this.reader, this.writer);
  17572. }
  17573. });
  17574. /**
  17575. * @author Ed Spencer
  17576. *
  17577. * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
  17578. * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
  17579. * 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
  17580. * Model}:
  17581. *
  17582. * Ext.define('User', {
  17583. * extend: 'Ext.data.Model',
  17584. * fields: ['id', 'name', 'email']
  17585. * });
  17586. *
  17587. * //The Store contains the AjaxProxy as an inline configuration
  17588. * var store = Ext.create('Ext.data.Store', {
  17589. * model: 'User',
  17590. * proxy: {
  17591. * type: 'ajax',
  17592. * url : 'users.json'
  17593. * }
  17594. * });
  17595. *
  17596. * store.load();
  17597. *
  17598. * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
  17599. * the fields that we expect the server to return. Next we set up the Store itself, along with a
  17600. * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
  17601. * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
  17602. * It's as if we'd done this:
  17603. *
  17604. * new Ext.data.proxy.Ajax({
  17605. * url: 'users.json',
  17606. * model: 'User',
  17607. * reader: 'json'
  17608. * });
  17609. *
  17610. * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
  17611. * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
  17612. * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
  17613. *
  17614. * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
  17615. * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
  17616. * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
  17617. * will be sent as a POST request).
  17618. *
  17619. * # Limitations
  17620. *
  17621. * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
  17622. * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
  17623. * talking to each other via AJAX.
  17624. *
  17625. * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
  17626. * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
  17627. * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
  17628. * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
  17629. * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
  17630. *
  17631. * # Readers and Writers
  17632. *
  17633. * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
  17634. * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
  17635. * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
  17636. * Ext.data.reader.Reader Reader} instance:
  17637. *
  17638. * var proxy = new Ext.data.proxy.Ajax({
  17639. * model: 'User',
  17640. * reader: {
  17641. * type: 'xml',
  17642. * root: 'users'
  17643. * }
  17644. * });
  17645. *
  17646. * proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
  17647. *
  17648. * # Url generation
  17649. *
  17650. * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
  17651. * each request. These are controlled with the following configuration options:
  17652. *
  17653. * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
  17654. * - {@link #sortParam} - controls how sort information is sent to the server
  17655. * - {@link #groupParam} - controls how grouping information is sent to the server
  17656. * - {@link #filterParam} - controls how filter information is sent to the server
  17657. *
  17658. * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
  17659. * the generated urls, let's say we're loading the Proxy with the following Operation:
  17660. *
  17661. * var operation = new Ext.data.Operation({
  17662. * action: 'read',
  17663. * page : 2
  17664. * });
  17665. *
  17666. * Now we'll issue the request for this Operation by calling {@link #read}:
  17667. *
  17668. * var proxy = new Ext.data.proxy.Ajax({
  17669. * url: '/users'
  17670. * });
  17671. *
  17672. * proxy.read(operation); //GET /users?page=2
  17673. *
  17674. * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
  17675. * to the server:
  17676. *
  17677. * var proxy = new Ext.data.proxy.Ajax({
  17678. * url: '/users',
  17679. * pageParam: 'pageNumber'
  17680. * });
  17681. *
  17682. * proxy.read(operation); //GET /users?pageNumber=2
  17683. *
  17684. * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
  17685. *
  17686. * var operation = new Ext.data.Operation({
  17687. * action: 'read',
  17688. * start : 50,
  17689. * limit : 25
  17690. * });
  17691. *
  17692. * var proxy = new Ext.data.proxy.Ajax({
  17693. * url: '/users'
  17694. * });
  17695. *
  17696. * proxy.read(operation); //GET /users?start=50&limit;=25
  17697. *
  17698. * Again we can customize this url:
  17699. *
  17700. * var proxy = new Ext.data.proxy.Ajax({
  17701. * url: '/users',
  17702. * startParam: 'startIndex',
  17703. * limitParam: 'limitIndex'
  17704. * });
  17705. *
  17706. * proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
  17707. *
  17708. * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
  17709. * expressive Operation object:
  17710. *
  17711. * var operation = new Ext.data.Operation({
  17712. * action: 'read',
  17713. * sorters: [
  17714. * new Ext.util.Sorter({
  17715. * property : 'name',
  17716. * direction: 'ASC'
  17717. * }),
  17718. * new Ext.util.Sorter({
  17719. * property : 'age',
  17720. * direction: 'DESC'
  17721. * })
  17722. * ],
  17723. * filters: [
  17724. * new Ext.util.Filter({
  17725. * property: 'eyeColor',
  17726. * value : 'brown'
  17727. * })
  17728. * ]
  17729. * });
  17730. *
  17731. * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
  17732. * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
  17733. * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
  17734. *
  17735. * var proxy = new Ext.data.proxy.Ajax({
  17736. * url: '/users'
  17737. * });
  17738. *
  17739. * proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
  17740. *
  17741. * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
  17742. * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
  17743. * that format like this:
  17744. *
  17745. * var proxy = new Ext.data.proxy.Ajax({
  17746. * url: '/users',
  17747. * sortParam: 'sortBy',
  17748. * filterParam: 'filterBy',
  17749. *
  17750. * //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
  17751. * encodeSorters: function(sorters) {
  17752. * var length = sorters.length,
  17753. * sortStrs = [],
  17754. * sorter, i;
  17755. *
  17756. * for (i = 0; i < length; i++) {
  17757. * sorter = sorters[i];
  17758. *
  17759. * sortStrs[i] = sorter.property + '#' + sorter.direction
  17760. * }
  17761. *
  17762. * return sortStrs.join(",");
  17763. * }
  17764. * });
  17765. *
  17766. * proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
  17767. *
  17768. * We can also provide a custom {@link #encodeFilters} function to encode our filters.
  17769. *
  17770. * @constructor
  17771. * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
  17772. * {@link Ext.data.Store#method-load load} will override any specified callback and params options. In this case, use the
  17773. * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
  17774. *
  17775. * @param {Object} config (optional) Config object.
  17776. * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
  17777. */
  17778. Ext.define('Ext.data.proxy.Ajax', {
  17779. requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
  17780. extend: 'Ext.data.proxy.Server',
  17781. alias: 'proxy.ajax',
  17782. alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
  17783. /**
  17784. * @property {Object} actionMethods
  17785. * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
  17786. * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
  17787. * correct RESTful methods.
  17788. */
  17789. actionMethods: {
  17790. create : 'POST',
  17791. read : 'GET',
  17792. update : 'POST',
  17793. destroy: 'POST'
  17794. },
  17795. /**
  17796. * @cfg {Object} headers
  17797. * Any headers to add to the Ajax request. Defaults to undefined.
  17798. */
  17799. /**
  17800. * @ignore
  17801. */
  17802. doRequest: function(operation, callback, scope) {
  17803. var writer = this.getWriter(),
  17804. request = this.buildRequest(operation, callback, scope);
  17805. if (operation.allowWrite()) {
  17806. request = writer.write(request);
  17807. }
  17808. Ext.apply(request, {
  17809. headers : this.headers,
  17810. timeout : this.timeout,
  17811. scope : this,
  17812. callback : this.createRequestCallback(request, operation, callback, scope),
  17813. method : this.getMethod(request),
  17814. disableCaching: false // explicitly set it to false, ServerProxy handles caching
  17815. });
  17816. Ext.Ajax.request(request);
  17817. return request;
  17818. },
  17819. /**
  17820. * Returns the HTTP method name for a given request. By default this returns based on a lookup on
  17821. * {@link #actionMethods}.
  17822. * @param {Ext.data.Request} request The request object
  17823. * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
  17824. */
  17825. getMethod: function(request) {
  17826. return this.actionMethods[request.action];
  17827. },
  17828. /**
  17829. * @private
  17830. * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
  17831. * of code duplication inside the returned function so we need to find a way to DRY this up.
  17832. * @param {Ext.data.Request} request The Request object
  17833. * @param {Ext.data.Operation} operation The Operation being executed
  17834. * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
  17835. * passed to doRequest
  17836. * @param {Object} scope The scope in which to execute the callback function
  17837. * @return {Function} The callback function
  17838. */
  17839. createRequestCallback: function(request, operation, callback, scope) {
  17840. var me = this;
  17841. return function(options, success, response) {
  17842. me.processResponse(success, operation, request, response, callback, scope);
  17843. };
  17844. }
  17845. }, function() {
  17846. //backwards compatibility, remove in Ext JS 5.0
  17847. Ext.data.HttpProxy = this;
  17848. });
  17849. /**
  17850. * @author Ed Spencer
  17851. *
  17852. * A Model represents some object that your application manages. For example, one might define a Model for Users,
  17853. * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
  17854. * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
  17855. * of the data-bound components in Ext.
  17856. *
  17857. * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
  17858. *
  17859. * Ext.define('User', {
  17860. * extend: 'Ext.data.Model',
  17861. * fields: [
  17862. * {name: 'name', type: 'string'},
  17863. * {name: 'age', type: 'int'},
  17864. * {name: 'phone', type: 'string'},
  17865. * {name: 'alive', type: 'boolean', defaultValue: true}
  17866. * ],
  17867. *
  17868. * changeName: function() {
  17869. * var oldName = this.get('name'),
  17870. * newName = oldName + " The Barbarian";
  17871. *
  17872. * this.set('name', newName);
  17873. * }
  17874. * });
  17875. *
  17876. * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
  17877. * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
  17878. *
  17879. * Now we can create instances of our User model and call any model logic we defined:
  17880. *
  17881. * var user = Ext.create('User', {
  17882. * name : 'Conan',
  17883. * age : 24,
  17884. * phone: '555-555-5555'
  17885. * });
  17886. *
  17887. * user.changeName();
  17888. * user.get('name'); //returns "Conan The Barbarian"
  17889. *
  17890. * # Validations
  17891. *
  17892. * Models have built-in support for validations, which are executed against the validator functions in {@link
  17893. * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
  17894. * models:
  17895. *
  17896. * Ext.define('User', {
  17897. * extend: 'Ext.data.Model',
  17898. * fields: [
  17899. * {name: 'name', type: 'string'},
  17900. * {name: 'age', type: 'int'},
  17901. * {name: 'phone', type: 'string'},
  17902. * {name: 'gender', type: 'string'},
  17903. * {name: 'username', type: 'string'},
  17904. * {name: 'alive', type: 'boolean', defaultValue: true}
  17905. * ],
  17906. *
  17907. * validations: [
  17908. * {type: 'presence', field: 'age'},
  17909. * {type: 'length', field: 'name', min: 2},
  17910. * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
  17911. * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
  17912. * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
  17913. * ]
  17914. * });
  17915. *
  17916. * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
  17917. * object:
  17918. *
  17919. * var instance = Ext.create('User', {
  17920. * name: 'Ed',
  17921. * gender: 'Male',
  17922. * username: 'edspencer'
  17923. * });
  17924. *
  17925. * var errors = instance.validate();
  17926. *
  17927. * # Associations
  17928. *
  17929. * Models can have associations with other Models via {@link Ext.data.association.HasOne},
  17930. * {@link Ext.data.association.BelongsTo belongsTo} and {@link Ext.data.association.HasMany hasMany} associations.
  17931. * For example, let's say we're writing a blog administration application which deals with Users, Posts and Comments.
  17932. * We can express the relationships between these models like this:
  17933. *
  17934. * Ext.define('Post', {
  17935. * extend: 'Ext.data.Model',
  17936. * fields: ['id', 'user_id'],
  17937. *
  17938. * belongsTo: 'User',
  17939. * hasMany : {model: 'Comment', name: 'comments'}
  17940. * });
  17941. *
  17942. * Ext.define('Comment', {
  17943. * extend: 'Ext.data.Model',
  17944. * fields: ['id', 'user_id', 'post_id'],
  17945. *
  17946. * belongsTo: 'Post'
  17947. * });
  17948. *
  17949. * Ext.define('User', {
  17950. * extend: 'Ext.data.Model',
  17951. * fields: ['id'],
  17952. *
  17953. * hasMany: [
  17954. * 'Post',
  17955. * {model: 'Comment', name: 'comments'}
  17956. * ]
  17957. * });
  17958. *
  17959. * See the docs for {@link Ext.data.association.HasOne}, {@link Ext.data.association.BelongsTo} and
  17960. * {@link Ext.data.association.HasMany} for details on the usage and configuration of associations.
  17961. * Note that associations can also be specified like this:
  17962. *
  17963. * Ext.define('User', {
  17964. * extend: 'Ext.data.Model',
  17965. * fields: ['id'],
  17966. *
  17967. * associations: [
  17968. * {type: 'hasMany', model: 'Post', name: 'posts'},
  17969. * {type: 'hasMany', model: 'Comment', name: 'comments'}
  17970. * ]
  17971. * });
  17972. *
  17973. * # Using a Proxy
  17974. *
  17975. * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
  17976. * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
  17977. * can be set directly on the Model:
  17978. *
  17979. * Ext.define('User', {
  17980. * extend: 'Ext.data.Model',
  17981. * fields: ['id', 'name', 'email'],
  17982. *
  17983. * proxy: {
  17984. * type: 'rest',
  17985. * url : '/users'
  17986. * }
  17987. * });
  17988. *
  17989. * 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
  17990. * RESTful backend. Let's see how this works:
  17991. *
  17992. * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
  17993. *
  17994. * user.save(); //POST /users
  17995. *
  17996. * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
  17997. * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
  17998. * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
  17999. * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
  18000. *
  18001. * Loading data via the Proxy is equally easy:
  18002. *
  18003. * //get a reference to the User model class
  18004. * var User = Ext.ModelManager.getModel('User');
  18005. *
  18006. * //Uses the configured RestProxy to make a GET request to /users/123
  18007. * User.load(123, {
  18008. * success: function(user) {
  18009. * console.log(user.getId()); //logs 123
  18010. * }
  18011. * });
  18012. *
  18013. * Models can also be updated and destroyed easily:
  18014. *
  18015. * //the user Model we loaded in the last snippet:
  18016. * user.set('name', 'Edward Spencer');
  18017. *
  18018. * //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
  18019. * user.save({
  18020. * success: function() {
  18021. * console.log('The User was updated');
  18022. * }
  18023. * });
  18024. *
  18025. * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
  18026. * user.destroy({
  18027. * success: function() {
  18028. * console.log('The User was destroyed!');
  18029. * }
  18030. * });
  18031. *
  18032. * # Usage in Stores
  18033. *
  18034. * 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
  18035. * creating a {@link Ext.data.Store Store}:
  18036. *
  18037. * var store = Ext.create('Ext.data.Store', {
  18038. * model: 'User'
  18039. * });
  18040. *
  18041. * //uses the Proxy we set up on Model to load the Store data
  18042. * store.load();
  18043. *
  18044. * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
  18045. * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
  18046. * Ext.data.Store Store docs} for more information on Stores.
  18047. *
  18048. * @constructor
  18049. * Creates new Model instance.
  18050. * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
  18051. */
  18052. Ext.define('Ext.data.Model', {
  18053. alternateClassName: 'Ext.data.Record',
  18054. mixins: {
  18055. observable: 'Ext.util.Observable'
  18056. },
  18057. requires: [
  18058. 'Ext.ModelManager',
  18059. 'Ext.data.IdGenerator',
  18060. 'Ext.data.Field',
  18061. 'Ext.data.Errors',
  18062. 'Ext.data.Operation',
  18063. 'Ext.data.validations',
  18064. 'Ext.data.proxy.Ajax',
  18065. 'Ext.util.MixedCollection'
  18066. ],
  18067. sortConvertFields: function(f1, f2) {
  18068. var f1SpecialConvert = f1.type && f1.convert !== f1.type.convert,
  18069. f2SpecialConvert = f2.type && f2.convert !== f2.type.convert;
  18070. if (f1SpecialConvert && !f2SpecialConvert) {
  18071. return 1;
  18072. }
  18073. if (!f1SpecialConvert && f2SpecialConvert) {
  18074. return -1;
  18075. }
  18076. return 0;
  18077. },
  18078. itemNameFn: function(item) {
  18079. return item.name;
  18080. },
  18081. onClassExtended: function(cls, data, hooks) {
  18082. var onBeforeClassCreated = hooks.onBeforeCreated;
  18083. hooks.onBeforeCreated = function(cls, data) {
  18084. var me = this,
  18085. name = Ext.getClassName(cls),
  18086. prototype = cls.prototype,
  18087. superCls = cls.prototype.superclass,
  18088. validations = data.validations || [],
  18089. fields = data.fields || [],
  18090. associations = data.associations || [],
  18091. belongsTo = data.belongsTo,
  18092. hasMany = data.hasMany,
  18093. hasOne = data.hasOne,
  18094. addAssociations = function(items, type) {
  18095. var i = 0,
  18096. len,
  18097. item;
  18098. if (items) {
  18099. items = Ext.Array.from(items);
  18100. for (len = items.length; i < len; ++i) {
  18101. item = items[i];
  18102. if (!Ext.isObject(item)) {
  18103. item = {model: item};
  18104. }
  18105. item.type = type;
  18106. associations.push(item);
  18107. }
  18108. }
  18109. },
  18110. idgen = data.idgen,
  18111. fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
  18112. associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
  18113. superValidations = superCls.validations,
  18114. superFields = superCls.fields,
  18115. superAssociations = superCls.associations,
  18116. association, i, ln,
  18117. dependencies = [],
  18118. idProperty = data.idProperty || cls.prototype.idProperty,
  18119. fieldConvertSortFn = Ext.Function.bind(
  18120. fieldsMixedCollection.sortBy,
  18121. fieldsMixedCollection,
  18122. [prototype.sortConvertFields], false);
  18123. // Save modelName on class and its prototype
  18124. cls.modelName = name;
  18125. prototype.modelName = name;
  18126. // Merge the validations of the superclass and the new subclass
  18127. if (superValidations) {
  18128. validations = superValidations.concat(validations);
  18129. }
  18130. data.validations = validations;
  18131. // Merge the fields of the superclass and the new subclass
  18132. if (superFields) {
  18133. fields = superFields.items.concat(fields);
  18134. }
  18135. fieldsMixedCollection.on({
  18136. add: fieldConvertSortFn,
  18137. replace: fieldConvertSortFn
  18138. });
  18139. for (i = 0, ln = fields.length; i < ln; ++i) {
  18140. fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
  18141. }
  18142. if (!fieldsMixedCollection.get(idProperty)) {
  18143. fieldsMixedCollection.add(new Ext.data.Field(idProperty));
  18144. }
  18145. data.fields = fieldsMixedCollection;
  18146. if (idgen) {
  18147. data.idgen = Ext.data.IdGenerator.get(idgen);
  18148. }
  18149. //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
  18150. //we support that here
  18151. addAssociations(data.belongsTo, 'belongsTo');
  18152. delete data.belongsTo;
  18153. addAssociations(data.hasMany, 'hasMany');
  18154. delete data.hasMany;
  18155. addAssociations(data.hasOne, 'hasOne');
  18156. delete data.hasOne;
  18157. if (superAssociations) {
  18158. associations = superAssociations.items.concat(associations);
  18159. }
  18160. for (i = 0, ln = associations.length; i < ln; ++i) {
  18161. dependencies.push('association.' + associations[i].type.toLowerCase());
  18162. }
  18163. if (data.proxy) {
  18164. if (typeof data.proxy === 'string') {
  18165. dependencies.push('proxy.' + data.proxy);
  18166. }
  18167. else if (typeof data.proxy.type === 'string') {
  18168. dependencies.push('proxy.' + data.proxy.type);
  18169. }
  18170. }
  18171. Ext.require(dependencies, function() {
  18172. Ext.ModelManager.registerType(name, cls);
  18173. for (i = 0, ln = associations.length; i < ln; ++i) {
  18174. association = associations[i];
  18175. Ext.apply(association, {
  18176. ownerModel: name,
  18177. associatedModel: association.model
  18178. });
  18179. if (Ext.ModelManager.getModel(association.model) === undefined) {
  18180. Ext.ModelManager.registerDeferredAssociation(association);
  18181. } else {
  18182. associationsMixedCollection.add(Ext.data.association.Association.create(association));
  18183. }
  18184. }
  18185. data.associations = associationsMixedCollection;
  18186. onBeforeClassCreated.call(me, cls, data, hooks);
  18187. cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
  18188. // Fire the onModelDefined template method on ModelManager
  18189. Ext.ModelManager.onModelDefined(cls);
  18190. });
  18191. };
  18192. },
  18193. inheritableStatics: {
  18194. /**
  18195. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  18196. * {@link Ext#createByAlias Ext.createByAlias}.
  18197. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  18198. * @return {Ext.data.proxy.Proxy}
  18199. * @static
  18200. * @inheritable
  18201. */
  18202. setProxy: function(proxy) {
  18203. //make sure we have an Ext.data.proxy.Proxy object
  18204. if (!proxy.isProxy) {
  18205. if (typeof proxy == "string") {
  18206. proxy = {
  18207. type: proxy
  18208. };
  18209. }
  18210. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  18211. }
  18212. proxy.setModel(this);
  18213. this.proxy = this.prototype.proxy = proxy;
  18214. return proxy;
  18215. },
  18216. /**
  18217. * Returns the configured Proxy for this Model
  18218. * @return {Ext.data.proxy.Proxy} The proxy
  18219. * @static
  18220. * @inheritable
  18221. */
  18222. getProxy: function() {
  18223. return this.proxy;
  18224. },
  18225. /**
  18226. * Apply a new set of field definitions to the existing model. This will replace any existing
  18227. * fields, including fields inherited from superclasses. Mainly for reconfiguring the
  18228. * model based on changes in meta data (called from Reader's onMetaChange method).
  18229. * @static
  18230. * @inheritable
  18231. */
  18232. setFields: function(fields) {
  18233. var me = this,
  18234. prototypeFields = me.prototype.fields,
  18235. len = fields.length,
  18236. i = 0;
  18237. if (prototypeFields) {
  18238. prototypeFields.clear();
  18239. }
  18240. else {
  18241. prototypeFields = me.prototype.fields = new Ext.util.MixedCollection(false, function(field) {
  18242. return field.name;
  18243. });
  18244. }
  18245. for (; i < len; i++) {
  18246. prototypeFields.add(new Ext.data.Field(fields[i]));
  18247. }
  18248. me.fields = prototypeFields;
  18249. return prototypeFields;
  18250. },
  18251. getFields: function() {
  18252. return this.fields;
  18253. },
  18254. /**
  18255. * Asynchronously loads a model instance by id. Sample usage:
  18256. *
  18257. * Ext.define('MyApp.User', {
  18258. * extend: 'Ext.data.Model',
  18259. * fields: [
  18260. * {name: 'id', type: 'int'},
  18261. * {name: 'name', type: 'string'}
  18262. * ]
  18263. * });
  18264. *
  18265. * MyApp.User.load(10, {
  18266. * scope: this,
  18267. * failure: function(record, operation) {
  18268. * //do something if the load failed
  18269. * },
  18270. * success: function(record, operation) {
  18271. * //do something if the load succeeded
  18272. * },
  18273. * callback: function(record, operation) {
  18274. * //do something whether the load succeeded or failed
  18275. * }
  18276. * });
  18277. *
  18278. * @param {Number/String} id The id of the model to load
  18279. * @param {Object} config (optional) config object containing success, failure and callback functions, plus
  18280. * optional scope
  18281. * @static
  18282. * @inheritable
  18283. */
  18284. load: function(id, config) {
  18285. config = Ext.apply({}, config);
  18286. config = Ext.applyIf(config, {
  18287. action: 'read',
  18288. id : id
  18289. });
  18290. var operation = new Ext.data.Operation(config),
  18291. scope = config.scope || this,
  18292. record = null,
  18293. callback;
  18294. callback = function(operation) {
  18295. if (operation.wasSuccessful()) {
  18296. record = operation.getRecords()[0];
  18297. Ext.callback(config.success, scope, [record, operation]);
  18298. } else {
  18299. Ext.callback(config.failure, scope, [record, operation]);
  18300. }
  18301. Ext.callback(config.callback, scope, [record, operation]);
  18302. };
  18303. this.proxy.read(operation, callback, this);
  18304. }
  18305. },
  18306. statics: {
  18307. PREFIX : 'ext-record',
  18308. AUTO_ID: 1,
  18309. EDIT : 'edit',
  18310. REJECT : 'reject',
  18311. COMMIT : 'commit',
  18312. /**
  18313. * Generates a sequential id. This method is typically called when a record is {@link Ext#create
  18314. * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
  18315. * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
  18316. *
  18317. * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
  18318. * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
  18319. *
  18320. * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
  18321. * @return {String} auto-generated string id, `"ext-record-i++"`;
  18322. * @static
  18323. */
  18324. id: function(rec) {
  18325. var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
  18326. rec.phantom = true;
  18327. rec.internalId = id;
  18328. return id;
  18329. }
  18330. },
  18331. /**
  18332. * @cfg {String/Object} idgen
  18333. * The id generator to use for this model. The default id generator does not generate
  18334. * values for the {@link #idProperty}.
  18335. *
  18336. * This can be overridden at the model level to provide a custom generator for a model.
  18337. * The simplest form of this would be:
  18338. *
  18339. * Ext.define('MyApp.data.MyModel', {
  18340. * extend: 'Ext.data.Model',
  18341. * requires: ['Ext.data.SequentialIdGenerator'],
  18342. * idgen: 'sequential',
  18343. * ...
  18344. * });
  18345. *
  18346. * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
  18347. * as 1, 2, 3 etc..
  18348. *
  18349. * Another useful id generator is {@link Ext.data.UuidGenerator}:
  18350. *
  18351. * Ext.define('MyApp.data.MyModel', {
  18352. * extend: 'Ext.data.Model',
  18353. * requires: ['Ext.data.UuidGenerator'],
  18354. * idgen: 'uuid',
  18355. * ...
  18356. * });
  18357. *
  18358. * An id generation can also be further configured:
  18359. *
  18360. * Ext.define('MyApp.data.MyModel', {
  18361. * extend: 'Ext.data.Model',
  18362. * idgen: {
  18363. * type: 'sequential',
  18364. * seed: 1000,
  18365. * prefix: 'ID_'
  18366. * }
  18367. * });
  18368. *
  18369. * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
  18370. *
  18371. * If multiple models share an id space, a single generator can be shared:
  18372. *
  18373. * Ext.define('MyApp.data.MyModelX', {
  18374. * extend: 'Ext.data.Model',
  18375. * idgen: {
  18376. * type: 'sequential',
  18377. * id: 'xy'
  18378. * }
  18379. * });
  18380. *
  18381. * Ext.define('MyApp.data.MyModelY', {
  18382. * extend: 'Ext.data.Model',
  18383. * idgen: {
  18384. * type: 'sequential',
  18385. * id: 'xy'
  18386. * }
  18387. * });
  18388. *
  18389. * For more complex, shared id generators, a custom generator is the best approach.
  18390. * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
  18391. *
  18392. * @markdown
  18393. */
  18394. idgen: {
  18395. isGenerator: true,
  18396. type: 'default',
  18397. generate: function () {
  18398. return null;
  18399. },
  18400. getRecId: function (rec) {
  18401. return rec.modelName + '-' + rec.internalId;
  18402. }
  18403. },
  18404. /**
  18405. * @property {Boolean} editing
  18406. * Internal flag used to track whether or not the model instance is currently being edited.
  18407. * @readonly
  18408. */
  18409. editing : false,
  18410. /**
  18411. * @property {Boolean} dirty
  18412. * True if this Record has been modified.
  18413. * @readonly
  18414. */
  18415. dirty : false,
  18416. /**
  18417. * @cfg {String} persistenceProperty
  18418. * The name of the property on this Persistable object that its data is saved to. Defaults to 'data'
  18419. * (i.e: all persistable data resides in `this.data`.)
  18420. */
  18421. persistenceProperty: 'data',
  18422. evented: false,
  18423. /**
  18424. * @property {Boolean} isModel
  18425. * `true` in this class to identify an objact as an instantiated Model, or subclass thereof.
  18426. */
  18427. isModel: true,
  18428. /**
  18429. * @property {Boolean} phantom
  18430. * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
  18431. * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
  18432. */
  18433. phantom : false,
  18434. /**
  18435. * @cfg {String} idProperty
  18436. * The name of the field treated as this Model's unique id. Defaults to 'id'.
  18437. */
  18438. idProperty: 'id',
  18439. /**
  18440. * @cfg {String} [clientIdProperty='clientId']
  18441. * The name of a property that is used for submitting this Model's unique client-side identifier
  18442. * to the server when multiple phantom records are saved as part of the same {@link Ext.data.Operation Operation}.
  18443. * In such a case, the server response should include the client id for each record
  18444. * so that the server response data can be used to update the client-side records if necessary.
  18445. * This property cannot have the same name as any of this Model's fields.
  18446. */
  18447. clientIdProperty: 'clientId',
  18448. /**
  18449. * @cfg {String} defaultProxyType
  18450. * The string type of the default Model Proxy. Defaults to 'ajax'.
  18451. */
  18452. defaultProxyType: 'ajax',
  18453. // Fields config and property
  18454. /**
  18455. * @cfg {Object[]/String[]} fields
  18456. * The fields for this model. This is an Array of **{@link Ext.data.Field Field}** definition objects. A Field
  18457. * definition may simply be the *name* of the Field, but a Field encapsulates {@link Ext.data.Field#type data type},
  18458. * {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
  18459. * property to specify by name of index, how to extract a field's value from a raw data object, so it is best practice
  18460. * to specify a full set of {@link Ext.data.Field Field} config objects.
  18461. */
  18462. /**
  18463. * @property {Ext.util.MixedCollection} fields
  18464. * A {@link Ext.util.MixedCollection Collection} of the fields defined for this Model (including fields defined in superclasses)
  18465. *
  18466. * This is a collection of {@link Ext.data.Field} instances, each of which encapsulates information that the field was configured with.
  18467. * By default, you can specify a field as simply a String, representing the *name* of the field, but a Field encapsulates
  18468. * {@link Ext.data.Field#type data type}, {@link Ext.data.Field#convert custom conversion} of raw data, and a {@link Ext.data.Field#mapping mapping}
  18469. * property to specify by name of index, how to extract a field's value from a raw data object.
  18470. */
  18471. /**
  18472. * @cfg {Object[]} validations
  18473. * An array of {@link Ext.data.validations validations} for this model.
  18474. */
  18475. // Associations configs and properties
  18476. /**
  18477. * @cfg {Object[]} associations
  18478. * An array of {@link Ext.data.Association associations} for this model.
  18479. */
  18480. /**
  18481. * @cfg {String/Object/String[]/Object[]} hasMany
  18482. * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
  18483. */
  18484. /**
  18485. * @cfg {String/Object/String[]/Object[]} belongsTo
  18486. * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
  18487. */
  18488. /**
  18489. * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
  18490. * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
  18491. */
  18492. /**
  18493. * @event idchanged
  18494. * Fired when this model's id changes
  18495. * @param {Ext.data.Model} this
  18496. * @param {Number/String} oldId The old id
  18497. * @param {Number/String} newId The new id
  18498. */
  18499. // id, raw and convertedData not documented intentionally, meant to be used internally.
  18500. constructor: function(data, id, raw, convertedData) {
  18501. data = data || {};
  18502. var me = this,
  18503. fields,
  18504. length,
  18505. field,
  18506. name,
  18507. value,
  18508. newId,
  18509. persistenceProperty,
  18510. i;
  18511. /**
  18512. * @property {Number/String} internalId
  18513. * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
  18514. * @private
  18515. */
  18516. me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
  18517. /**
  18518. * @property {Object} raw The raw data used to create this model if created via a reader.
  18519. */
  18520. me.raw = raw;
  18521. if (!me.data) {
  18522. me.data = {};
  18523. }
  18524. /**
  18525. * @property {Object} modified Key: value pairs of all fields whose values have changed
  18526. */
  18527. me.modified = {};
  18528. // Deal with spelling error in previous releases
  18529. if (me.persistanceProperty) {
  18530. if (Ext.isDefined(Ext.global.console)) {
  18531. Ext.global.console.warn('Ext.data.Model: persistanceProperty has been deprecated. Use persistenceProperty instead.');
  18532. }
  18533. me.persistenceProperty = me.persistanceProperty;
  18534. }
  18535. me[me.persistenceProperty] = convertedData || {};
  18536. me.mixins.observable.constructor.call(me);
  18537. if (!convertedData) {
  18538. //add default field values if present
  18539. fields = me.fields.items;
  18540. length = fields.length;
  18541. i = 0;
  18542. persistenceProperty = me[me.persistenceProperty];
  18543. if (Ext.isArray(data)) {
  18544. for (; i < length; i++) {
  18545. field = fields[i];
  18546. name = field.name;
  18547. value = data[i];
  18548. if (value === undefined) {
  18549. value = field.defaultValue;
  18550. }
  18551. // Have to map array data so the values get assigned to the named fields
  18552. // rather than getting set as the field names with undefined values.
  18553. if (field.convert) {
  18554. value = field.convert(value, me);
  18555. }
  18556. persistenceProperty[name] = value ;
  18557. }
  18558. } else {
  18559. for (; i < length; i++) {
  18560. field = fields[i];
  18561. name = field.name;
  18562. value = data[name];
  18563. if (value === undefined) {
  18564. value = field.defaultValue;
  18565. }
  18566. if (field.convert) {
  18567. value = field.convert(value, me);
  18568. }
  18569. persistenceProperty[name] = value ;
  18570. }
  18571. }
  18572. }
  18573. /**
  18574. * @property {Array} stores
  18575. * An array of {@link Ext.data.AbstractStore} objects that this record is bound to.
  18576. */
  18577. me.stores = [];
  18578. if (me.getId()) {
  18579. me.phantom = false;
  18580. } else if (me.phantom) {
  18581. newId = me.idgen.generate();
  18582. if (newId !== null) {
  18583. me.setId(newId);
  18584. }
  18585. }
  18586. // clear any dirty/modified since we're initializing
  18587. me.dirty = false;
  18588. me.modified = {};
  18589. if (typeof me.init == 'function') {
  18590. me.init();
  18591. }
  18592. me.id = me.idgen.getRecId(me);
  18593. },
  18594. /**
  18595. * Returns the value of the given field
  18596. * @param {String} fieldName The field to fetch the value for
  18597. * @return {Object} The value
  18598. */
  18599. get: function(field) {
  18600. return this[this.persistenceProperty][field];
  18601. },
  18602. /**
  18603. * Sets the given field to the given value, marks the instance as dirty
  18604. * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
  18605. * @param {Object} value The value to set
  18606. */
  18607. set: function(fieldName, value) {
  18608. var me = this,
  18609. fields = me.fields,
  18610. modified = me.modified,
  18611. modifiedFieldNames = [],
  18612. field, key, i, currentValue, notEditing, count, length;
  18613. /*
  18614. * If we're passed an object, iterate over that object.
  18615. */
  18616. if (arguments.length == 1 && Ext.isObject(fieldName)) {
  18617. notEditing = !me.editing;
  18618. count = 0;
  18619. fields = me.fields.items;
  18620. length = fields.length;
  18621. for (i = 0; i < length; i++) {
  18622. field = fields[i].name;
  18623. if (fieldName.hasOwnProperty(field)) {
  18624. if (!count && notEditing) {
  18625. me.beginEdit();
  18626. }
  18627. ++count;
  18628. me.set(field, fieldName[field]);
  18629. }
  18630. }
  18631. if (notEditing && count) {
  18632. me.endEdit(false, modifiedFieldNames);
  18633. }
  18634. } else {
  18635. fields = me.fields;
  18636. if (fields) {
  18637. field = fields.get(fieldName);
  18638. if (field && field.convert) {
  18639. value = field.convert(value, me);
  18640. }
  18641. }
  18642. currentValue = me.get(fieldName);
  18643. me[me.persistenceProperty][fieldName] = value;
  18644. if (field && field.persist && !me.isEqual(currentValue, value)) {
  18645. if (me.isModified(fieldName)) {
  18646. if (me.isEqual(modified[fieldName], value)) {
  18647. // the original value in me.modified equals the new value, so the
  18648. // field is no longer modified
  18649. delete modified[fieldName];
  18650. // we might have removed the last modified field, so check to see if
  18651. // there are any modified fields remaining and correct me.dirty:
  18652. me.dirty = false;
  18653. for (key in modified) {
  18654. if (modified.hasOwnProperty(key)){
  18655. me.dirty = true;
  18656. break;
  18657. }
  18658. }
  18659. }
  18660. } else {
  18661. me.dirty = true;
  18662. modified[fieldName] = currentValue;
  18663. }
  18664. }
  18665. if(fieldName === me.idProperty && currentValue !== value) {
  18666. me.fireEvent('idchanged', me, currentValue, value);
  18667. }
  18668. if (!me.editing) {
  18669. me.afterEdit([fieldName]);
  18670. }
  18671. }
  18672. },
  18673. /**
  18674. * Checks if two values are equal, taking into account certain
  18675. * special factors, for example dates.
  18676. * @private
  18677. * @param {Object} a The first value
  18678. * @param {Object} b The second value
  18679. * @return {Boolean} True if the values are equal
  18680. */
  18681. isEqual: function(a, b){
  18682. if (Ext.isDate(a) && Ext.isDate(b)) {
  18683. return Ext.Date.isEqual(a, b);
  18684. }
  18685. return a === b;
  18686. },
  18687. /**
  18688. * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
  18689. * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
  18690. */
  18691. beginEdit : function(){
  18692. var me = this;
  18693. if (!me.editing) {
  18694. me.editing = true;
  18695. me.dirtySave = me.dirty;
  18696. me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
  18697. me.modifiedSave = Ext.apply({}, me.modified);
  18698. }
  18699. },
  18700. /**
  18701. * Cancels all changes made in the current edit operation.
  18702. */
  18703. cancelEdit : function(){
  18704. var me = this;
  18705. if (me.editing) {
  18706. me.editing = false;
  18707. // reset the modified state, nothing changed since the edit began
  18708. me.modified = me.modifiedSave;
  18709. me[me.persistenceProperty] = me.dataSave;
  18710. me.dirty = me.dirtySave;
  18711. delete me.modifiedSave;
  18712. delete me.dataSave;
  18713. delete me.dirtySave;
  18714. }
  18715. },
  18716. /**
  18717. * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
  18718. * fire).
  18719. * @param {Boolean} silent True to not notify the store of the change
  18720. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  18721. */
  18722. endEdit : function(silent, modifiedFieldNames){
  18723. var me = this,
  18724. changed;
  18725. if (me.editing) {
  18726. me.editing = false;
  18727. if(!modifiedFieldNames) {
  18728. modifiedFieldNames = me.getModifiedFieldNames();
  18729. }
  18730. changed = me.dirty || modifiedFieldNames.length > 0;
  18731. delete me.modifiedSave;
  18732. delete me.dataSave;
  18733. delete me.dirtySave;
  18734. if (changed && silent !== true) {
  18735. me.afterEdit(modifiedFieldNames);
  18736. }
  18737. }
  18738. },
  18739. /**
  18740. * Gets the names of all the fields that were modified during an edit
  18741. * @private
  18742. * @return {String[]} An array of modified field names
  18743. */
  18744. getModifiedFieldNames: function(){
  18745. var me = this,
  18746. saved = me.dataSave,
  18747. data = me[me.persistenceProperty],
  18748. modified = [],
  18749. key;
  18750. for (key in data) {
  18751. if (data.hasOwnProperty(key)) {
  18752. if (!me.isEqual(data[key], saved[key])) {
  18753. modified.push(key);
  18754. }
  18755. }
  18756. }
  18757. return modified;
  18758. },
  18759. /**
  18760. * Gets a hash of only the fields that have been modified since this Model was created or commited.
  18761. * @return {Object}
  18762. */
  18763. getChanges : function(){
  18764. var modified = this.modified,
  18765. changes = {},
  18766. field;
  18767. for (field in modified) {
  18768. if (modified.hasOwnProperty(field)){
  18769. changes[field] = this.get(field);
  18770. }
  18771. }
  18772. return changes;
  18773. },
  18774. /**
  18775. * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
  18776. * @param {String} fieldName {@link Ext.data.Field#name}
  18777. * @return {Boolean}
  18778. */
  18779. isModified : function(fieldName) {
  18780. return this.modified.hasOwnProperty(fieldName);
  18781. },
  18782. /**
  18783. * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
  18784. * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
  18785. *
  18786. * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
  18787. * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
  18788. */
  18789. setDirty : function() {
  18790. var me = this,
  18791. fields = me.fields.items,
  18792. fLen = fields.length,
  18793. field, name, f;
  18794. me.dirty = true;
  18795. for (f = 0; f < fLen; f++) {
  18796. field = fields[f];
  18797. if (field.persist) {
  18798. name = field.name;
  18799. me.modified[name] = me.get(name);
  18800. }
  18801. }
  18802. },
  18803. markDirty : function() {
  18804. if (Ext.isDefined(Ext.global.console)) {
  18805. Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
  18806. }
  18807. return this.setDirty.apply(this, arguments);
  18808. },
  18809. /**
  18810. * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
  18811. * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
  18812. * reverted to their original values.
  18813. *
  18814. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
  18815. * operations.
  18816. *
  18817. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  18818. * Defaults to false.
  18819. */
  18820. reject : function(silent) {
  18821. var me = this,
  18822. modified = me.modified,
  18823. field;
  18824. for (field in modified) {
  18825. if (modified.hasOwnProperty(field)) {
  18826. if (typeof modified[field] != "function") {
  18827. me[me.persistenceProperty][field] = modified[field];
  18828. }
  18829. }
  18830. }
  18831. me.dirty = false;
  18832. me.editing = false;
  18833. me.modified = {};
  18834. if (silent !== true) {
  18835. me.afterReject();
  18836. }
  18837. },
  18838. /**
  18839. * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
  18840. * instance since either creation or the last commit operation.
  18841. *
  18842. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
  18843. * operations.
  18844. *
  18845. * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
  18846. * Defaults to false.
  18847. */
  18848. commit : function(silent) {
  18849. var me = this;
  18850. me.phantom = me.dirty = me.editing = false;
  18851. me.modified = {};
  18852. if (silent !== true) {
  18853. me.afterCommit();
  18854. }
  18855. },
  18856. /**
  18857. * Creates a copy (clone) of this Model instance.
  18858. *
  18859. * @param {String} [id] A new id, defaults to the id of the instance being copied.
  18860. * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
  18861. *
  18862. * var rec = record.copy(); // clone the record
  18863. * Ext.data.Model.id(rec); // automatically generate a unique sequential id
  18864. *
  18865. * @return {Ext.data.Model}
  18866. */
  18867. copy : function(newId) {
  18868. var me = this;
  18869. return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId);
  18870. },
  18871. /**
  18872. * Sets the Proxy to use for this model. Accepts any options that can be accepted by
  18873. * {@link Ext#createByAlias Ext.createByAlias}.
  18874. *
  18875. * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
  18876. * @return {Ext.data.proxy.Proxy}
  18877. */
  18878. setProxy: function(proxy) {
  18879. //make sure we have an Ext.data.proxy.Proxy object
  18880. if (!proxy.isProxy) {
  18881. if (typeof proxy === "string") {
  18882. proxy = {
  18883. type: proxy
  18884. };
  18885. }
  18886. proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
  18887. }
  18888. proxy.setModel(this.self);
  18889. this.proxy = proxy;
  18890. return proxy;
  18891. },
  18892. /**
  18893. * Returns the configured Proxy for this Model.
  18894. * @return {Ext.data.proxy.Proxy} The proxy
  18895. */
  18896. getProxy: function() {
  18897. return this.proxy;
  18898. },
  18899. /**
  18900. * Validates the current data against all of its configured {@link #validations}.
  18901. * @return {Ext.data.Errors} The errors object
  18902. */
  18903. validate: function() {
  18904. var errors = new Ext.data.Errors(),
  18905. validations = this.validations,
  18906. validators = Ext.data.validations,
  18907. length, validation, field, valid, type, i;
  18908. if (validations) {
  18909. length = validations.length;
  18910. for (i = 0; i < length; i++) {
  18911. validation = validations[i];
  18912. field = validation.field || validation.name;
  18913. type = validation.type;
  18914. valid = validators[type](validation, this.get(field));
  18915. if (!valid) {
  18916. errors.add({
  18917. field : field,
  18918. message: validation.message || validators[type + 'Message']
  18919. });
  18920. }
  18921. }
  18922. }
  18923. return errors;
  18924. },
  18925. /**
  18926. * Checks if the model is valid. See {@link #validate}.
  18927. * @return {Boolean} True if the model is valid.
  18928. */
  18929. isValid: function(){
  18930. return this.validate().isValid();
  18931. },
  18932. /**
  18933. * Saves the model instance using the configured proxy.
  18934. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  18935. * @return {Ext.data.Model} The Model instance
  18936. */
  18937. save: function(options) {
  18938. options = Ext.apply({}, options);
  18939. var me = this,
  18940. action = me.phantom ? 'create' : 'update',
  18941. scope = options.scope || me,
  18942. stores = me.stores,
  18943. i = 0,
  18944. storeCount,
  18945. store,
  18946. args,
  18947. operation,
  18948. callback;
  18949. Ext.apply(options, {
  18950. records: [me],
  18951. action : action
  18952. });
  18953. operation = new Ext.data.Operation(options);
  18954. callback = function(operation) {
  18955. args = [me, operation];
  18956. if (operation.wasSuccessful()) {
  18957. for(storeCount = stores.length; i < storeCount; i++) {
  18958. store = stores[i];
  18959. store.fireEvent('write', store, operation);
  18960. store.fireEvent('datachanged', store);
  18961. // Not firing refresh here, since it's a single record
  18962. }
  18963. Ext.callback(options.success, scope, args);
  18964. } else {
  18965. Ext.callback(options.failure, scope, args);
  18966. }
  18967. Ext.callback(options.callback, scope, args);
  18968. };
  18969. me.getProxy()[action](operation, callback, me);
  18970. return me;
  18971. },
  18972. /**
  18973. * Destroys the model using the configured proxy.
  18974. * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  18975. * @return {Ext.data.Model} The Model instance
  18976. */
  18977. destroy: function(options){
  18978. options = Ext.apply({}, options);
  18979. var me = this,
  18980. scope = options.scope || me,
  18981. stores = me.stores,
  18982. i = 0,
  18983. storeCount,
  18984. store,
  18985. args,
  18986. operation,
  18987. callback;
  18988. Ext.apply(options, {
  18989. records: [me],
  18990. action : 'destroy'
  18991. });
  18992. operation = new Ext.data.Operation(options);
  18993. callback = function(operation) {
  18994. args = [me, operation];
  18995. if (operation.wasSuccessful()) {
  18996. for(storeCount = stores.length; i < storeCount; i++) {
  18997. store = stores[i];
  18998. store.fireEvent('write', store, operation);
  18999. store.fireEvent('datachanged', store);
  19000. // Not firing refresh here, since it's a single record
  19001. }
  19002. me.clearListeners();
  19003. Ext.callback(options.success, scope, args);
  19004. } else {
  19005. Ext.callback(options.failure, scope, args);
  19006. }
  19007. Ext.callback(options.callback, scope, args);
  19008. };
  19009. me.getProxy().destroy(operation, callback, me);
  19010. return me;
  19011. },
  19012. /**
  19013. * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
  19014. * @return {Number/String} The id
  19015. */
  19016. getId: function() {
  19017. return this.get(this.idProperty);
  19018. },
  19019. /**
  19020. * @private
  19021. */
  19022. getObservableId: function() {
  19023. return this.id;
  19024. },
  19025. /**
  19026. * Sets the model instance's id field to the given id.
  19027. * @param {Number/String} id The new id
  19028. */
  19029. setId: function(id) {
  19030. this.set(this.idProperty, id);
  19031. this.phantom = !(id || id === 0);
  19032. },
  19033. /**
  19034. * Tells this model instance that it has been added to a store.
  19035. * @param {Ext.data.Store} store The store to which this model has been added.
  19036. */
  19037. join : function(store) {
  19038. Ext.Array.include(this.stores, store);
  19039. },
  19040. /**
  19041. * Tells this model instance that it has been removed from the store.
  19042. * @param {Ext.data.Store} store The store from which this model has been removed.
  19043. */
  19044. unjoin: function(store) {
  19045. Ext.Array.remove(this.stores, store);
  19046. },
  19047. /**
  19048. * @private
  19049. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  19050. * afterEdit method is called
  19051. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  19052. */
  19053. afterEdit : function(modifiedFieldNames) {
  19054. this.callStore('afterEdit', modifiedFieldNames);
  19055. },
  19056. /**
  19057. * @private
  19058. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  19059. * afterReject method is called
  19060. */
  19061. afterReject : function() {
  19062. this.callStore("afterReject");
  19063. },
  19064. /**
  19065. * @private
  19066. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  19067. * afterCommit method is called
  19068. */
  19069. afterCommit: function() {
  19070. this.callStore('afterCommit');
  19071. },
  19072. /**
  19073. * @private
  19074. * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
  19075. * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
  19076. * will always be called with the model instance as its single argument. If this model is joined to
  19077. * a Ext.data.NodeStore, then this method calls the given method on the NodeStore and the associated Ext.data.TreeStore
  19078. * @param {String} fn The function to call on the store
  19079. */
  19080. callStore: function(fn) {
  19081. var args = Ext.Array.clone(arguments),
  19082. stores = this.stores,
  19083. i = 0,
  19084. len = stores.length,
  19085. store, treeStore;
  19086. args[0] = this;
  19087. for (; i < len; ++i) {
  19088. store = stores[i];
  19089. if (store && typeof store[fn] == "function") {
  19090. store[fn].apply(store, args);
  19091. }
  19092. // if the record is bound to a NodeStore call the TreeStore's method as well
  19093. treeStore = store.treeStore;
  19094. if (treeStore && typeof treeStore[fn] == "function") {
  19095. treeStore[fn].apply(treeStore, args);
  19096. }
  19097. }
  19098. },
  19099. /**
  19100. * Gets all values for each field in this model and returns an object
  19101. * containing the current data.
  19102. * @param {Boolean} includeAssociated True to also include associated data. Defaults to false.
  19103. * @return {Object} An object hash containing all the values in this model
  19104. */
  19105. getData: function(includeAssociated){
  19106. var me = this,
  19107. fields = me.fields.items,
  19108. fLen = fields.length,
  19109. data = {},
  19110. name, f;
  19111. for (f = 0; f < fLen; f++) {
  19112. name = fields[f].name;
  19113. data[name] = me.get(name);
  19114. }
  19115. if (includeAssociated === true) {
  19116. Ext.apply(data, me.getAssociatedData());
  19117. }
  19118. return data;
  19119. },
  19120. /**
  19121. * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
  19122. * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
  19123. *
  19124. * {
  19125. * orders: [
  19126. * {
  19127. * id: 123,
  19128. * status: 'shipped',
  19129. * orderItems: [
  19130. * ...
  19131. * ]
  19132. * }
  19133. * ]
  19134. * }
  19135. *
  19136. * @return {Object} The nested data set for the Model's loaded associations
  19137. */
  19138. getAssociatedData: function(){
  19139. return this.prepareAssociatedData(this, [], null);
  19140. },
  19141. /**
  19142. * @private
  19143. * This complex-looking method takes a given Model instance and returns an object containing all data from
  19144. * all of that Model's *loaded* associations. See {@link #getAssociatedData}
  19145. * @param {Ext.data.Model} record The Model instance
  19146. * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
  19147. * @param {String} associationType (optional) The name of the type of association to limit to.
  19148. * @return {Object} The nested data set for the Model's loaded associations
  19149. */
  19150. prepareAssociatedData: function(record, ids, associationType) {
  19151. //we keep track of all of the internalIds of the models that we have loaded so far in here
  19152. var associations = record.associations.items,
  19153. associationCount = associations.length,
  19154. associationData = {},
  19155. associatedStore, associatedRecords, associatedRecord,
  19156. associatedRecordCount, association, id, i, j, type, allow;
  19157. for (i = 0; i < associationCount; i++) {
  19158. association = associations[i];
  19159. type = association.type;
  19160. allow = true;
  19161. if (associationType) {
  19162. allow = type == associationType;
  19163. }
  19164. if (allow && type == 'hasMany') {
  19165. //this is the hasMany store filled with the associated data
  19166. associatedStore = record[association.storeName];
  19167. //we will use this to contain each associated record's data
  19168. associationData[association.name] = [];
  19169. //if it's loaded, put it into the association data
  19170. if (associatedStore && associatedStore.getCount() > 0) {
  19171. associatedRecords = associatedStore.data.items;
  19172. associatedRecordCount = associatedRecords.length;
  19173. //now we're finally iterating over the records in the association. We do this recursively
  19174. for (j = 0; j < associatedRecordCount; j++) {
  19175. associatedRecord = associatedRecords[j];
  19176. // Use the id, since it is prefixed with the model name, guaranteed to be unique
  19177. id = associatedRecord.id;
  19178. //when we load the associations for a specific model instance we add it to the set of loaded ids so that
  19179. //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
  19180. if (Ext.Array.indexOf(ids, id) == -1) {
  19181. ids.push(id);
  19182. associationData[association.name][j] = associatedRecord.getData();
  19183. Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
  19184. }
  19185. }
  19186. }
  19187. } else if (allow && (type == 'belongsTo' || type == 'hasOne')) {
  19188. associatedRecord = record[association.instanceName];
  19189. if (associatedRecord !== undefined) {
  19190. id = associatedRecord.id;
  19191. if (Ext.Array.indexOf(ids, id) === -1) {
  19192. ids.push(id);
  19193. associationData[association.name] = associatedRecord.getData();
  19194. Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
  19195. }
  19196. }
  19197. }
  19198. }
  19199. return associationData;
  19200. }
  19201. });
  19202. /**
  19203. *
  19204. */
  19205. Ext.define('Ext.container.DockingContainer', {
  19206. /* Begin Definitions */
  19207. requires: ['Ext.util.MixedCollection', 'Ext.Element' ],
  19208. /* End Definitions */
  19209. isDockingContainer: true,
  19210. /**
  19211. * @cfg {Object} defaultDockWeights
  19212. * This object holds the default weights applied to dockedItems that have no weight. These start with a
  19213. * weight of 1, to allow negative weights to insert before top items and are odd numbers
  19214. * so that even weights can be used to get between different dock orders.
  19215. *
  19216. * To make default docking order match border layout, do this:
  19217. *
  19218. * Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
  19219. *
  19220. * Changing these defaults as above or individually on this object will effect all Panels.
  19221. * To change the defaults on a single panel, you should replace the entire object:
  19222. *
  19223. * initComponent: function () {
  19224. * // NOTE: Don't change members of defaultDockWeights since the object is shared.
  19225. * this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
  19226. *
  19227. * this.callParent();
  19228. * }
  19229. *
  19230. * To change only one of the default values, you do this:
  19231. *
  19232. * initComponent: function () {
  19233. * // NOTE: Don't change members of defaultDockWeights since the object is shared.
  19234. * this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
  19235. *
  19236. * this.callParent();
  19237. * }
  19238. */
  19239. defaultDockWeights: {
  19240. top: { render: 1, visual: 1 },
  19241. left: { render: 3, visual: 5 },
  19242. right: { render: 5, visual: 7 },
  19243. bottom: { render: 7, visual: 3 }
  19244. },
  19245. // @private
  19246. // Values to decide which side of the body element docked items must go
  19247. // This overides any weight. A left/top will *always* sort before a right/bottom
  19248. // regardless of any weight value. Weights sort at either side of the "body" dividing point.
  19249. dockOrder: {
  19250. top: -1,
  19251. left: -1,
  19252. right: 1,
  19253. bottom: 1
  19254. },
  19255. /**
  19256. * Adds docked item(s) to the container.
  19257. * @param {Object/Object[]} component The Component or array of components to add. The components
  19258. * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
  19259. * 'bottom', 'left').
  19260. * @param {Number} pos (optional) The index at which the Component will be added
  19261. */
  19262. addDocked : function(items, pos) {
  19263. var me = this,
  19264. i = 0,
  19265. item, length;
  19266. items = me.prepareItems(items);
  19267. length = items.length;
  19268. for (; i < length; i++) {
  19269. item = items[i];
  19270. item.dock = item.dock || 'top';
  19271. // Allow older browsers to target docked items to style without borders
  19272. if (me.border === false) {
  19273. // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
  19274. }
  19275. if (pos !== undefined) {
  19276. me.dockedItems.insert(pos + i, item);
  19277. } else {
  19278. me.dockedItems.add(item);
  19279. }
  19280. if (item.onAdded !== Ext.emptyFn) {
  19281. item.onAdded(me, i);
  19282. }
  19283. if (me.onDockedAdd !== Ext.emptyFn) {
  19284. me.onDockedAdd(item);
  19285. }
  19286. }
  19287. if (me.rendered && !me.suspendLayout) {
  19288. me.updateLayout();
  19289. }
  19290. return items;
  19291. },
  19292. destroyDockedItems: function(){
  19293. var dockedItems = this.dockedItems,
  19294. c;
  19295. if (dockedItems) {
  19296. while ((c = dockedItems.first())) {
  19297. this.removeDocked(c, true);
  19298. }
  19299. }
  19300. },
  19301. doRenderDockedItems: function (out, renderData, after) {
  19302. // Careful! This method is bolted on to the frameTpl and renderTpl so all we get for
  19303. // context is the renderData! The "this" pointer is either the frameTpl or the
  19304. // renderTpl instance!
  19305. // Due to framing, we will be called in two different ways: in the frameTpl or in
  19306. // the renderTpl. The frameTpl version enters via doRenderFramingDockedItems which
  19307. // sets "$skipDockedItems" on the renderTpl's renderData.
  19308. //
  19309. var me = renderData.$comp,
  19310. layout = me.componentLayout;
  19311. if (layout.getDockedItems && !renderData.$skipDockedItems) {
  19312. var items = layout.getDockedItems('render', !after),
  19313. tree = items && layout.getItemsRenderTree(items);
  19314. if (tree) {
  19315. Ext.DomHelper.generateMarkup(tree, out);
  19316. }
  19317. }
  19318. },
  19319. /**
  19320. * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
  19321. * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
  19322. * @return {Ext.Component} The docked component (if found)
  19323. */
  19324. getDockedComponent: function(comp) {
  19325. if (Ext.isObject(comp)) {
  19326. comp = comp.getItemId();
  19327. }
  19328. return this.dockedItems.get(comp);
  19329. },
  19330. /**
  19331. * Retrieves an array of all currently docked Components.
  19332. *
  19333. * For example to find a toolbar that has been docked at top:
  19334. *
  19335. * panel.getDockedItems('toolbar[dock="top"]');
  19336. *
  19337. * @param {String} selector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
  19338. * @param {Boolean} beforeBody An optional flag to limit the set of items to only those
  19339. * before the body (true) or after the body (false). All components are returned by
  19340. * default.
  19341. * @return {Ext.Component[]} The array of docked components meeting the specified criteria.
  19342. */
  19343. getDockedItems : function(selector, beforeBody) {
  19344. var dockedItems = this.getComponentLayout().getDockedItems('render', beforeBody);
  19345. if (selector && dockedItems.length) {
  19346. dockedItems = Ext.ComponentQuery.query(selector, dockedItems);
  19347. }
  19348. return dockedItems;
  19349. },
  19350. getDockingRefItems: function(deep, containerItems) {
  19351. // deep fetches the docked items and their descendants using '*' and then '* *'
  19352. var selector = deep && '*,* *',
  19353. // start with only the top/left docked items (and maybe their children)
  19354. dockedItems = this.getDockedItems(selector, true),
  19355. items;
  19356. // push container items (and maybe their children) after top/left docked items:
  19357. dockedItems.push.apply(dockedItems, containerItems);
  19358. // push right/bottom docked items (and maybe their children) after container items:
  19359. items = this.getDockedItems(selector, false);
  19360. dockedItems.push.apply(dockedItems, items);
  19361. return dockedItems;
  19362. },
  19363. initDockingItems: function() {
  19364. var me = this,
  19365. items = me.dockedItems;
  19366. me.dockedItems = new Ext.util.AbstractMixedCollection(false, me.getComponentId);
  19367. if (items) {
  19368. me.addDocked(items);
  19369. }
  19370. },
  19371. /**
  19372. * Inserts docked item(s) to the panel at the indicated position.
  19373. * @param {Number} pos The index at which the Component will be inserted
  19374. * @param {Object/Object[]} component. The Component or array of components to add. The components
  19375. * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
  19376. * 'bottom', 'left').
  19377. */
  19378. insertDocked : function(pos, items) {
  19379. this.addDocked(items, pos);
  19380. },
  19381. // Placeholder empty functions
  19382. /**
  19383. * Invoked after a docked item is added to the Panel.
  19384. * @param {Ext.Component} component
  19385. * @template
  19386. * @protected
  19387. */
  19388. onDockedAdd : Ext.emptyFn,
  19389. /**
  19390. * Invoked after a docked item is removed from the Panel.
  19391. * @param {Ext.Component} component
  19392. * @template
  19393. * @protected
  19394. */
  19395. onDockedRemove : Ext.emptyFn,
  19396. /**
  19397. * Removes the docked item from the panel.
  19398. * @param {Ext.Component} item. The Component to remove.
  19399. * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
  19400. */
  19401. removeDocked : function(item, autoDestroy) {
  19402. var me = this,
  19403. layout,
  19404. hasLayout;
  19405. if (!me.dockedItems.contains(item)) {
  19406. return item;
  19407. }
  19408. layout = me.componentLayout;
  19409. hasLayout = layout && me.rendered;
  19410. if (hasLayout) {
  19411. layout.onRemove(item);
  19412. }
  19413. me.dockedItems.remove(item);
  19414. item.onRemoved();
  19415. me.onDockedRemove(item);
  19416. if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
  19417. item.destroy();
  19418. } else if (hasLayout) {
  19419. // not destroying, make any layout related removals
  19420. layout.afterRemove(item);
  19421. }
  19422. if (!me.destroying && !me.suspendLayout) {
  19423. me.updateLayout();
  19424. }
  19425. return item;
  19426. },
  19427. setupDockingRenderTpl: function (renderTpl) {
  19428. renderTpl.renderDockedItems = this.doRenderDockedItems;
  19429. }
  19430. });
  19431. /**
  19432. * @class Ext.fx.Easing
  19433. *
  19434. * This class contains a series of function definitions used to modify values during an animation.
  19435. * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
  19436. * speed over its duration. The following options are available:
  19437. *
  19438. * - linear The default easing type
  19439. * - backIn
  19440. * - backOut
  19441. * - bounceIn
  19442. * - bounceOut
  19443. * - ease
  19444. * - easeIn
  19445. * - easeOut
  19446. * - easeInOut
  19447. * - elasticIn
  19448. * - elasticOut
  19449. * - cubic-bezier(x1, y1, x2, y2)
  19450. *
  19451. * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
  19452. * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
  19453. * be in the range [0, 1] or the definition is invalid.
  19454. *
  19455. * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
  19456. *
  19457. * @singleton
  19458. */
  19459. Ext.ns('Ext.fx');
  19460. Ext.require('Ext.fx.CubicBezier', function() {
  19461. var math = Math,
  19462. pi = math.PI,
  19463. pow = math.pow,
  19464. sin = math.sin,
  19465. sqrt = math.sqrt,
  19466. abs = math.abs,
  19467. backInSeed = 1.70158;
  19468. Ext.fx.Easing = {
  19469. // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
  19470. // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
  19471. // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
  19472. // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
  19473. // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
  19474. // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
  19475. // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
  19476. // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
  19477. };
  19478. Ext.apply(Ext.fx.Easing, {
  19479. linear: function(n) {
  19480. return n;
  19481. },
  19482. ease: function(n) {
  19483. var q = 0.07813 - n / 2,
  19484. alpha = -0.25,
  19485. Q = sqrt(0.0066 + q * q),
  19486. x = Q - q,
  19487. X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
  19488. y = -Q - q,
  19489. Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
  19490. t = X + Y + 0.25;
  19491. return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
  19492. },
  19493. easeIn: function (n) {
  19494. return pow(n, 1.7);
  19495. },
  19496. easeOut: function (n) {
  19497. return pow(n, 0.48);
  19498. },
  19499. easeInOut: function(n) {
  19500. var q = 0.48 - n / 1.04,
  19501. Q = sqrt(0.1734 + q * q),
  19502. x = Q - q,
  19503. X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
  19504. y = -Q - q,
  19505. Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
  19506. t = X + Y + 0.5;
  19507. return (1 - t) * 3 * t * t + t * t * t;
  19508. },
  19509. backIn: function (n) {
  19510. return n * n * ((backInSeed + 1) * n - backInSeed);
  19511. },
  19512. backOut: function (n) {
  19513. n = n - 1;
  19514. return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
  19515. },
  19516. elasticIn: function (n) {
  19517. if (n === 0 || n === 1) {
  19518. return n;
  19519. }
  19520. var p = 0.3,
  19521. s = p / 4;
  19522. return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
  19523. },
  19524. elasticOut: function (n) {
  19525. return 1 - Ext.fx.Easing.elasticIn(1 - n);
  19526. },
  19527. bounceIn: function (n) {
  19528. return 1 - Ext.fx.Easing.bounceOut(1 - n);
  19529. },
  19530. bounceOut: function (n) {
  19531. var s = 7.5625,
  19532. p = 2.75,
  19533. l;
  19534. if (n < (1 / p)) {
  19535. l = s * n * n;
  19536. } else {
  19537. if (n < (2 / p)) {
  19538. n -= (1.5 / p);
  19539. l = s * n * n + 0.75;
  19540. } else {
  19541. if (n < (2.5 / p)) {
  19542. n -= (2.25 / p);
  19543. l = s * n * n + 0.9375;
  19544. } else {
  19545. n -= (2.625 / p);
  19546. l = s * n * n + 0.984375;
  19547. }
  19548. }
  19549. }
  19550. return l;
  19551. }
  19552. });
  19553. Ext.apply(Ext.fx.Easing, {
  19554. 'back-in': Ext.fx.Easing.backIn,
  19555. 'back-out': Ext.fx.Easing.backOut,
  19556. 'ease-in': Ext.fx.Easing.easeIn,
  19557. 'ease-out': Ext.fx.Easing.easeOut,
  19558. 'elastic-in': Ext.fx.Easing.elasticIn,
  19559. 'elastic-out': Ext.fx.Easing.elasticIn,
  19560. 'bounce-in': Ext.fx.Easing.bounceIn,
  19561. 'bounce-out': Ext.fx.Easing.bounceOut,
  19562. 'ease-in-out': Ext.fx.Easing.easeInOut
  19563. });
  19564. });
  19565. /**
  19566. * This class compiles the XTemplate syntax into a function object. The function is used
  19567. * like so:
  19568. *
  19569. * function (out, values, parent, xindex, xcount) {
  19570. * // out is the output array to store results
  19571. * // values, parent, xindex and xcount have their historical meaning
  19572. * }
  19573. *
  19574. * @markdown
  19575. * @private
  19576. */
  19577. Ext.define('Ext.XTemplateCompiler', {
  19578. extend: 'Ext.XTemplateParser',
  19579. // Chrome really likes "new Function" to realize the code block (as in it is
  19580. // 2x-3x faster to call it than using eval), but Firefox chokes on it badly.
  19581. // IE and Opera are also fine with the "new Function" technique.
  19582. useEval: Ext.isGecko,
  19583. // See http://jsperf.com/nige-array-append for quickest way to append to an array of unknown length
  19584. // (Due to arbitrary code execution inside a template, we cannot easily track the length in var)
  19585. // On IE6 and 7 myArray[myArray.length]='foo' is better. On other browsers myArray.push('foo') is better.
  19586. useIndex: Ext.isIE6 || Ext.isIE7,
  19587. useFormat: true,
  19588. propNameRe: /^[\w\d\$]*$/,
  19589. compile: function (tpl) {
  19590. var me = this,
  19591. code = me.generate(tpl);
  19592. // When using "new Function", we have to pass our "Ext" variable to it in order to
  19593. // support sandboxing. If we did not, the generated function would use the global
  19594. // "Ext", not the "Ext" from our sandbox (scope chain).
  19595. //
  19596. return me.useEval ? me.evalTpl(code) : (new Function('Ext', code))(Ext);
  19597. },
  19598. generate: function (tpl) {
  19599. var me = this;
  19600. me.body = [
  19601. 'var c0=values, a0 = Ext.isArray(c0), p0=parent, n0=xcount, i0=xindex, v;\n'
  19602. ];
  19603. me.funcs = [
  19604. // note: Ext here is properly sandboxed
  19605. 'var fm=Ext.util.Format;'
  19606. ];
  19607. me.switches = [];
  19608. me.parse(tpl);
  19609. me.funcs.push(
  19610. (me.useEval ? '$=' : 'return') + ' function (' + me.fnArgs + ') {',
  19611. me.body.join(''),
  19612. '}'
  19613. );
  19614. var code = me.funcs.join('\n');
  19615. return code;
  19616. },
  19617. //-----------------------------------
  19618. // XTemplateParser callouts
  19619. doText: function (text) {
  19620. var me = this,
  19621. out = me.body;
  19622. text = text.replace(me.aposRe, "\\'").replace(me.newLineRe, '\\n');
  19623. if (me.useIndex) {
  19624. out.push('out[out.length]=\'', text, '\'\n');
  19625. } else {
  19626. out.push('out.push(\'', text, '\')\n');
  19627. }
  19628. },
  19629. doExpr: function (expr) {
  19630. var out = this.body;
  19631. out.push('if ((v=' + expr + ')!==undefined) out');
  19632. if (this.useIndex) {
  19633. out.push('[out.length]=String(v)\n');
  19634. } else {
  19635. out.push('.push(String(v))\n');
  19636. }
  19637. },
  19638. doTag: function (tag) {
  19639. this.doExpr(this.parseTag(tag));
  19640. },
  19641. doElse: function () {
  19642. this.body.push('} else {\n');
  19643. },
  19644. doEval: function (text) {
  19645. this.body.push(text, '\n');
  19646. },
  19647. doIf: function (action, actions) {
  19648. var me = this;
  19649. // If it's just a propName, use it directly in the if
  19650. if (me.propNameRe.test(action)) {
  19651. me.body.push('if (', me.parseTag(action), ') {\n');
  19652. }
  19653. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  19654. else {
  19655. me.body.push('if (', me.addFn(action), me.callFn, ') {\n');
  19656. }
  19657. if (actions.exec) {
  19658. me.doExec(actions.exec);
  19659. }
  19660. },
  19661. doElseIf: function (action, actions) {
  19662. var me = this;
  19663. // If it's just a propName, use it directly in the else if
  19664. if (me.propNameRe.test(action)) {
  19665. me.body.push('} else if (', me.parseTag(action), ') {\n');
  19666. }
  19667. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  19668. else {
  19669. me.body.push('} else if (', me.addFn(action), me.callFn, ') {\n');
  19670. }
  19671. if (actions.exec) {
  19672. me.doExec(actions.exec);
  19673. }
  19674. },
  19675. doSwitch: function (action) {
  19676. var me = this;
  19677. // If it's just a propName, use it directly in the switch
  19678. if (me.propNameRe.test(action)) {
  19679. me.body.push('switch (', me.parseTag(action), ') {\n');
  19680. }
  19681. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  19682. else {
  19683. me.body.push('switch (', me.addFn(action), me.callFn, ') {\n');
  19684. }
  19685. me.switches.push(0);
  19686. },
  19687. doCase: function (action) {
  19688. var me = this,
  19689. cases = Ext.isArray(action) ? action : [action],
  19690. n = me.switches.length - 1,
  19691. match, i;
  19692. if (me.switches[n]) {
  19693. me.body.push('break;\n');
  19694. } else {
  19695. me.switches[n]++;
  19696. }
  19697. for (i = 0, n = cases.length; i < n; ++i) {
  19698. match = me.intRe.exec(cases[i]);
  19699. cases[i] = match ? match[1] : ("'" + cases[i].replace(me.aposRe,"\\'") + "'");
  19700. }
  19701. me.body.push('case ', cases.join(': case '), ':\n');
  19702. },
  19703. doDefault: function () {
  19704. var me = this,
  19705. n = me.switches.length - 1;
  19706. if (me.switches[n]) {
  19707. me.body.push('break;\n');
  19708. } else {
  19709. me.switches[n]++;
  19710. }
  19711. me.body.push('default:\n');
  19712. },
  19713. doEnd: function (type, actions) {
  19714. var me = this,
  19715. L = me.level-1;
  19716. if (type == 'for') {
  19717. /*
  19718. To exit a for loop we must restore the outer loop's context. The code looks
  19719. like this (which goes with that produced by doFor:
  19720. for (...) { // the part generated by doFor
  19721. ... // the body of the for loop
  19722. // ... any tpl for exec statement goes here...
  19723. }
  19724. parent = p1;
  19725. values = r2;
  19726. xcount = n1;
  19727. xindex = i1
  19728. */
  19729. if (actions.exec) {
  19730. me.doExec(actions.exec);
  19731. }
  19732. me.body.push('}\n');
  19733. me.body.push('parent=p',L,';values=r',L+1,';xcount=n',L,';xindex=i',L,'\n');
  19734. } else if (type == 'if' || type == 'switch') {
  19735. me.body.push('}\n');
  19736. }
  19737. },
  19738. doFor: function (action, actions) {
  19739. var me = this,
  19740. s = me.addFn(action),
  19741. L = me.level,
  19742. up = L-1;
  19743. /*
  19744. We are trying to produce a block of code that looks like below. We use the nesting
  19745. level to uniquely name the control variables.
  19746. var c2 = f5.call(this, out, values, parent, xindex, xcount),
  19747. // c2 is the context object for the for loop
  19748. a2 = Ext.isArray(c2),
  19749. // a2 is the isArray result for the context
  19750. p2 = c1,
  19751. // p2 is the parent context (of the outer for loop)
  19752. r2 = values
  19753. // r2 is the values object to
  19754. parent = a1 ? c1[i1] : p2 // set parent
  19755. // i2 is the loop index and n2 is the number (xcount) of this for loop
  19756. for (var i2 = 0, n2 = a2 ? c2.length : (c2 ? 1 : 0), xcount = n2; i2 < n2; ++i2) {
  19757. values = a2 ? c2[i2] : c2 // adjust special vars to inner scope
  19758. xindex = i2 + 1 // xindex is 1-based
  19759. The body of the loop is whatever comes between the tpl and /tpl statements (which
  19760. is handled by doEnd).
  19761. */
  19762. me.body.push('var c',L,'=',s,me.callFn,', a',L,'=Ext.isArray(c',L,'), p',L,'=c',up,',r',L,'=values\n',
  19763. 'parent=a',up,'?c',up,'[i',up,']:p',L,'\n',
  19764. 'for (var i',L,'=0,n',L,'=a',L,'?c',L,'.length:(c',L,'?1:0), xcount=n',L,';i',L,'<n'+L+';++i',L,'){\n',
  19765. 'values=a',L,'?c',L,'[i',L,']:c',L,'\n',
  19766. 'xindex=i',L,'+1\n');
  19767. },
  19768. doExec: function (action, actions) {
  19769. var me = this,
  19770. name = 'f' + me.funcs.length;
  19771. me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
  19772. ' try { with(values) {',
  19773. ' ' + action,
  19774. ' }} catch(e) {}',
  19775. '}');
  19776. me.body.push(name + me.callFn + '\n');
  19777. },
  19778. //-----------------------------------
  19779. // Internal
  19780. addFn: function (body) {
  19781. var me = this,
  19782. name = 'f' + me.funcs.length;
  19783. if (body === '.') {
  19784. me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
  19785. ' return values',
  19786. '}');
  19787. } else if (body === '..') {
  19788. me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
  19789. ' return parent',
  19790. '}');
  19791. } else {
  19792. me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
  19793. ' try { with(values) {',
  19794. ' return(' + body + ')',
  19795. ' }} catch(e) {}',
  19796. '}');
  19797. }
  19798. return name;
  19799. },
  19800. parseTag: function (tag) {
  19801. var m = this.tagRe.exec(tag),
  19802. name = m[1],
  19803. format = m[2],
  19804. args = m[3],
  19805. math = m[4],
  19806. v;
  19807. // name = "." - Just use the values object.
  19808. if (name == '.') {
  19809. // filter to not include arrays/objects/nulls
  19810. v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
  19811. }
  19812. // name = "#" - Use the xindex
  19813. else if (name == '#') {
  19814. v = 'xindex';
  19815. }
  19816. else if (name.substr(0, 7) == "parent.") {
  19817. v = name;
  19818. }
  19819. // compound Javascript property name (e.g., "foo.bar")
  19820. else if (isNaN(name) && name.indexOf('-') == -1 && name.indexOf('.') != -1) {
  19821. v = "values." + name;
  19822. }
  19823. // number or a '-' in it or a single word (maybe a keyword): use array notation
  19824. // (http://jsperf.com/string-property-access/4)
  19825. else {
  19826. v = "values['" + name + "']";
  19827. }
  19828. if (math) {
  19829. v = '(' + v + math + ')';
  19830. }
  19831. if (format && this.useFormat) {
  19832. args = args ? ',' + args : "";
  19833. if (format.substr(0, 5) != "this.") {
  19834. format = "fm." + format + '(';
  19835. } else {
  19836. format += '(';
  19837. }
  19838. } else {
  19839. return v;
  19840. }
  19841. return format + v + args + ')';
  19842. },
  19843. // @private
  19844. evalTpl: function ($) {
  19845. // We have to use eval to realize the code block and capture the inner func we also
  19846. // don't want a deep scope chain. We only do this in Firefox and it is also unhappy
  19847. // with eval containing a return statement, so instead we assign to "$" and return
  19848. // that. Because we use "eval", we are automatically sandboxed properly.
  19849. eval($);
  19850. return $;
  19851. },
  19852. newLineRe: /\r\n|\r|\n/g,
  19853. aposRe: /[']/g,
  19854. intRe: /^\s*(\d+)\s*$/,
  19855. tagRe: /([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?/
  19856. }, function () {
  19857. var proto = this.prototype;
  19858. proto.fnArgs = 'out,values,parent,xindex,xcount';
  19859. proto.callFn = '.call(this,' + proto.fnArgs + ')';
  19860. });
  19861. /**
  19862. * A template class that supports advanced functionality like:
  19863. *
  19864. * - Autofilling arrays using templates and sub-templates
  19865. * - Conditional processing with basic comparison operators
  19866. * - Basic math function support
  19867. * - Execute arbitrary inline code with special built-in template variables
  19868. * - Custom member functions
  19869. * - 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
  19870. *
  19871. * XTemplate provides the templating mechanism built into {@link Ext.view.View}.
  19872. *
  19873. * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
  19874. * demonstrate all of the supported features.
  19875. *
  19876. * # Sample Data
  19877. *
  19878. * This is the data object used for reference in each code example:
  19879. *
  19880. * var data = {
  19881. * name: 'Don Griffin',
  19882. * title: 'Senior Technomage',
  19883. * company: 'Sencha Inc.',
  19884. * drinks: ['Coffee', 'Water', 'More Coffee'],
  19885. * kids: [
  19886. * { name: 'Aubrey', age: 17 },
  19887. * { name: 'Joshua', age: 13 },
  19888. * { name: 'Cale', age: 10 },
  19889. * { name: 'Nikol', age: 5 },
  19890. * { name: 'Solomon', age: 0 }
  19891. * ]
  19892. * };
  19893. *
  19894. * # Auto filling of arrays
  19895. *
  19896. * The **tpl** tag and the **for** operator are used to process the provided data object:
  19897. *
  19898. * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
  19899. * tag for each item in the array.
  19900. * - If for="." is specified, the data object provided is examined.
  19901. * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
  19902. *
  19903. * Examples:
  19904. *
  19905. * <tpl for=".">...</tpl> // loop through array at root node
  19906. * <tpl for="foo">...</tpl> // loop through array at foo node
  19907. * <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
  19908. *
  19909. * Using the sample data above:
  19910. *
  19911. * var tpl = new Ext.XTemplate(
  19912. * '<p>Kids: ',
  19913. * '<tpl for=".">', // process the data.kids node
  19914. * '<p>{#}. {name}</p>', // use current array index to autonumber
  19915. * '</tpl></p>'
  19916. * );
  19917. * tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
  19918. *
  19919. * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
  19920. * object to populate the template:
  19921. *
  19922. * var tpl = new Ext.XTemplate(
  19923. * '<p>Name: {name}</p>',
  19924. * '<p>Title: {title}</p>',
  19925. * '<p>Company: {company}</p>',
  19926. * '<p>Kids: ',
  19927. * '<tpl for="kids">', // interrogate the kids property within the data
  19928. * '<p>{name}</p>',
  19929. * '</tpl></p>'
  19930. * );
  19931. * tpl.overwrite(panel.body, data); // pass the root node of the data object
  19932. *
  19933. * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
  19934. * loop. This variable will represent the value of the array at the current index:
  19935. *
  19936. * var tpl = new Ext.XTemplate(
  19937. * '<p>{name}\'s favorite beverages:</p>',
  19938. * '<tpl for="drinks">',
  19939. * '<div> - {.}</div>',
  19940. * '</tpl>'
  19941. * );
  19942. * tpl.overwrite(panel.body, data);
  19943. *
  19944. * When processing a sub-template, for example while looping through a child array, you can access the parent object's
  19945. * members via the **parent** object:
  19946. *
  19947. * var tpl = new Ext.XTemplate(
  19948. * '<p>Name: {name}</p>',
  19949. * '<p>Kids: ',
  19950. * '<tpl for="kids">',
  19951. * '<tpl if="age &gt; 1">',
  19952. * '<p>{name}</p>',
  19953. * '<p>Dad: {parent.name}</p>',
  19954. * '</tpl>',
  19955. * '</tpl></p>'
  19956. * );
  19957. * tpl.overwrite(panel.body, data);
  19958. *
  19959. * # Conditional processing with basic comparison operators
  19960. *
  19961. * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
  19962. * specific parts of the template.
  19963. *
  19964. * Using the sample data above:
  19965. *
  19966. * var tpl = new Ext.XTemplate(
  19967. * '<p>Name: {name}</p>',
  19968. * '<p>Kids: ',
  19969. * '<tpl for="kids">',
  19970. * '<tpl if="age &gt; 1">',
  19971. * '<p>{name}</p>',
  19972. * '</tpl>',
  19973. * '</tpl></p>'
  19974. * );
  19975. * tpl.overwrite(panel.body, data);
  19976. *
  19977. * More advanced conditionals are also supported:
  19978. *
  19979. * var tpl = new Ext.XTemplate(
  19980. * '<p>Name: {name}</p>',
  19981. * '<p>Kids: ',
  19982. * '<tpl for="kids">',
  19983. * '<p>{name} is a ',
  19984. * '<tpl if="age &gt;= 13">',
  19985. * '<p>teenager</p>',
  19986. * '<tpl elseif="age &gt;= 2">',
  19987. * '<p>kid</p>',
  19988. * '<tpl else">',
  19989. * '<p>baby</p>',
  19990. * '</tpl>',
  19991. * '</tpl></p>'
  19992. * );
  19993. *
  19994. * var tpl = new Ext.XTemplate(
  19995. * '<p>Name: {name}</p>',
  19996. * '<p>Kids: ',
  19997. * '<tpl for="kids">',
  19998. * '<p>{name} is a ',
  19999. * '<tpl switch="name">',
  20000. * '<tpl case="Aubrey" case="Nikol">',
  20001. * '<p>girl</p>',
  20002. * '<tpl default">',
  20003. * '<p>boy</p>',
  20004. * '</tpl>',
  20005. * '</tpl></p>'
  20006. * );
  20007. *
  20008. * A `break` is implied between each case and default, however, multiple cases can be listed
  20009. * in a single &lt;tpl&gt; tag.
  20010. *
  20011. * # Using double quotes
  20012. *
  20013. * Examples:
  20014. *
  20015. * var tpl = new Ext.XTemplate(
  20016. * "<tpl if='age &gt; 1 && age &lt; 10'>Child</tpl>",
  20017. * "<tpl if='age &gt;= 10 && age &lt; 18'>Teenager</tpl>",
  20018. * "<tpl if='this.isGirl(name)'>...</tpl>",
  20019. * '<tpl if="id == \'download\'">...</tpl>',
  20020. * "<tpl if='needsIcon'><img src='{icon}' class='{iconCls}'/></tpl>",
  20021. * "<tpl if='name == \"Don\"'>Hello</tpl>"
  20022. * );
  20023. *
  20024. * # Basic math support
  20025. *
  20026. * The following basic math operators may be applied directly on numeric data values:
  20027. *
  20028. * + - * /
  20029. *
  20030. * For example:
  20031. *
  20032. * var tpl = new Ext.XTemplate(
  20033. * '<p>Name: {name}</p>',
  20034. * '<p>Kids: ',
  20035. * '<tpl for="kids">',
  20036. * '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
  20037. * '<p>{#}: {name}</p>', // <-- Auto-number each item
  20038. * '<p>In 5 Years: {age+5}</p>', // <-- Basic math
  20039. * '<p>Dad: {parent.name}</p>',
  20040. * '</tpl>',
  20041. * '</tpl></p>'
  20042. * );
  20043. * tpl.overwrite(panel.body, data);
  20044. *
  20045. * # Execute arbitrary inline code with special built-in template variables
  20046. *
  20047. * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template.
  20048. * The expression is evaluated and the result is included in the generated result. There are
  20049. * some special variables available in that code:
  20050. *
  20051. * - **out**: The output array into which the template is being appended (using `push` to later
  20052. * `join`).
  20053. * - **values**: The values in the current scope. If you are using scope changing sub-templates,
  20054. * you can change what values is.
  20055. * - **parent**: The scope (values) of the ancestor template.
  20056. * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
  20057. * - **xcount**: If you are in a looping template, the total length of the array you are looping.
  20058. *
  20059. * This example demonstrates basic row striping using an inline code block and the xindex variable:
  20060. *
  20061. * var tpl = new Ext.XTemplate(
  20062. * '<p>Name: {name}</p>',
  20063. * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
  20064. * '<p>Kids: ',
  20065. * '<tpl for="kids">',
  20066. * '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
  20067. * '{name}',
  20068. * '</div>',
  20069. * '</tpl></p>'
  20070. * );
  20071. *
  20072. * Any code contained in "verbatim" blocks (using "{% ... %}") will be inserted directly in
  20073. * the generated code for the template. These blocks are not included in the output. This
  20074. * can be used for simple things like break/continue in a loop, or control structures or
  20075. * method calls (when they don't produce output). The `this` references the template instance.
  20076. *
  20077. * var tpl = new Ext.XTemplate(
  20078. * '<p>Name: {name}</p>',
  20079. * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
  20080. * '<p>Kids: ',
  20081. * '<tpl for="kids">',
  20082. * '{% if (xindex % 2 === 0) continue; %}',
  20083. * '{name}',
  20084. * '{% if (xindex > 100) break; %}',
  20085. * '</div>',
  20086. * '</tpl></p>'
  20087. * );
  20088. *
  20089. * # Template member functions
  20090. *
  20091. * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
  20092. * more complex processing:
  20093. *
  20094. * var tpl = new Ext.XTemplate(
  20095. * '<p>Name: {name}</p>',
  20096. * '<p>Kids: ',
  20097. * '<tpl for="kids">',
  20098. * '<tpl if="this.isGirl(name)">',
  20099. * '<p>Girl: {name} - {age}</p>',
  20100. * '<tpl else>',
  20101. * '<p>Boy: {name} - {age}</p>',
  20102. * '</tpl>',
  20103. * '<tpl if="this.isBaby(age)">',
  20104. * '<p>{name} is a baby!</p>',
  20105. * '</tpl>',
  20106. * '</tpl></p>',
  20107. * {
  20108. * // XTemplate configuration:
  20109. * disableFormats: true,
  20110. * // member functions:
  20111. * isGirl: function(name){
  20112. * return name == 'Sara Grace';
  20113. * },
  20114. * isBaby: function(age){
  20115. * return age < 1;
  20116. * }
  20117. * }
  20118. * );
  20119. * tpl.overwrite(panel.body, data);
  20120. */
  20121. Ext.define('Ext.XTemplate', {
  20122. extend: 'Ext.Template',
  20123. requires: 'Ext.XTemplateCompiler',
  20124. /**
  20125. * @cfg {Boolean} compiled
  20126. * Only applies to {@link Ext.Template}, XTemplates are compiled automatically on the
  20127. * first call to {@link #apply} or {@link #applyOut}.
  20128. */
  20129. apply: function(values) {
  20130. return this.applyOut(values, []).join('');
  20131. },
  20132. applyOut: function(values, out) {
  20133. var me = this,
  20134. compiler;
  20135. if (!me.fn) {
  20136. compiler = new Ext.XTemplateCompiler({
  20137. useFormat: me.disableFormats !== true
  20138. });
  20139. me.fn = compiler.compile(me.html);
  20140. }
  20141. try {
  20142. me.fn.call(me, out, values, {}, 1, 1);
  20143. } catch (e) {
  20144. Ext.log('Error: ' + e.message);
  20145. }
  20146. return out;
  20147. },
  20148. /**
  20149. * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
  20150. * @return {Ext.XTemplate} this
  20151. */
  20152. compile: function() {
  20153. return this;
  20154. },
  20155. statics: {
  20156. /**
  20157. * Gets an `XTemplate` from an object (an instance of an {@link Ext#define}'d class).
  20158. * Many times, templates are configured high in the class hierarchy and are to be
  20159. * shared by all classes that derive from that base. To further complicate matters,
  20160. * these templates are seldom actual instances but are rather configurations. For
  20161. * example:
  20162. *
  20163. * Ext.define('MyApp.Class', {
  20164. * someTpl: [
  20165. * 'tpl text here'
  20166. * ]
  20167. * });
  20168. *
  20169. * The goal being to share that template definition with all instances and even
  20170. * instances of derived classes, until `someTpl` is overridden. This method will
  20171. * "upgrade" these configurations to be real `XTemplate` instances *in place* (to
  20172. * avoid creating one instance per object).
  20173. *
  20174. * @param {Object} instance The object from which to get the `XTemplate` (must be
  20175. * an instance of an {@link Ext#define}'d class).
  20176. * @param {String} name The name of the property by which to get the `XTemplate`.
  20177. * @return {Ext.XTemplate} The `XTemplate` instance or null if not found.
  20178. * @protected
  20179. */
  20180. getTpl: function (instance, name) {
  20181. var tpl = instance[name], // go for it! 99% of the time we will get it!
  20182. proto;
  20183. if (tpl && !tpl.isTemplate) { // tpl is just a configuration (not an instance)
  20184. // create the template instance from the configuration:
  20185. tpl = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
  20186. // and replace the reference with the new instance:
  20187. if (instance.hasOwnProperty(name)) { // the tpl is on the instance
  20188. instance[name] = tpl;
  20189. } else { // must be somewhere in the prototype chain
  20190. for (proto = instance.self.prototype; proto; proto = proto.superclass) {
  20191. if (proto.hasOwnProperty(name)) {
  20192. proto[name] = tpl;
  20193. break;
  20194. }
  20195. }
  20196. }
  20197. }
  20198. // else !tpl (no such tpl) or the tpl is an instance already... either way, tpl
  20199. // is ready to return
  20200. return tpl || null;
  20201. }
  20202. }
  20203. });
  20204. /**
  20205. * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
  20206. * the right-justified button container.
  20207. *
  20208. * @example
  20209. * Ext.create('Ext.panel.Panel', {
  20210. * title: 'Toolbar Fill Example',
  20211. * width: 300,
  20212. * height: 200,
  20213. * tbar : [
  20214. * 'Item 1',
  20215. * { xtype: 'tbfill' },
  20216. * 'Item 2'
  20217. * ],
  20218. * renderTo: Ext.getBody()
  20219. * });
  20220. */
  20221. Ext.define('Ext.toolbar.Fill', {
  20222. extend: 'Ext.Component',
  20223. alias: 'widget.tbfill',
  20224. alternateClassName: 'Ext.Toolbar.Fill',
  20225. /**
  20226. * @property {Boolean} isFill
  20227. * `true` in this class to identify an objact as an instantiated Fill, or subclass thereof.
  20228. */
  20229. isFill : true,
  20230. flex: 1
  20231. });
  20232. /**
  20233. * @class Ext.fx.target.Element
  20234. *
  20235. * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
  20236. * created directly, the {@link Ext.Element} will be passed to the animation and
  20237. * and the appropriate target will be created.
  20238. */
  20239. Ext.define('Ext.fx.target.Element', {
  20240. /* Begin Definitions */
  20241. extend: 'Ext.fx.target.Target',
  20242. /* End Definitions */
  20243. type: 'element',
  20244. getElVal: function(el, attr, val) {
  20245. if (val == undefined) {
  20246. if (attr === 'x') {
  20247. val = el.getX();
  20248. }
  20249. else if (attr === 'y') {
  20250. val = el.getY();
  20251. }
  20252. else if (attr === 'scrollTop') {
  20253. val = el.getScroll().top;
  20254. }
  20255. else if (attr === 'scrollLeft') {
  20256. val = el.getScroll().left;
  20257. }
  20258. else if (attr === 'height') {
  20259. val = el.getHeight();
  20260. }
  20261. else if (attr === 'width') {
  20262. val = el.getWidth();
  20263. }
  20264. else {
  20265. val = el.getStyle(attr);
  20266. }
  20267. }
  20268. return val;
  20269. },
  20270. getAttr: function(attr, val) {
  20271. var el = this.target;
  20272. return [[ el, this.getElVal(el, attr, val)]];
  20273. },
  20274. setAttr: function(targetData) {
  20275. var target = this.target,
  20276. ln = targetData.length,
  20277. attrs, attr, o, i, j, ln2, element, value;
  20278. for (i = 0; i < ln; i++) {
  20279. attrs = targetData[i].attrs;
  20280. for (attr in attrs) {
  20281. if (attrs.hasOwnProperty(attr)) {
  20282. ln2 = attrs[attr].length;
  20283. for (j = 0; j < ln2; j++) {
  20284. o = attrs[attr][j];
  20285. element = o[0];
  20286. value = o[1];
  20287. if (attr === 'x') {
  20288. element.setX(value);
  20289. } else if (attr === 'y') {
  20290. element.setY(value);
  20291. } else if (attr === 'scrollTop') {
  20292. element.scrollTo('top', value);
  20293. } else if (attr === 'scrollLeft') {
  20294. element.scrollTo('left',value);
  20295. } else if (attr === 'width') {
  20296. element.setWidth(value);
  20297. } else if (attr === 'height') {
  20298. element.setHeight(value);
  20299. } else {
  20300. element.setStyle(attr, value);
  20301. }
  20302. }
  20303. }
  20304. }
  20305. }
  20306. }
  20307. });
  20308. /**
  20309. * @class Ext.fx.target.ElementCSS
  20310. *
  20311. * This class represents a animation target for an {@link Ext.Element} that supports CSS
  20312. * based animation. In general this class will not be created directly, the {@link Ext.Element}
  20313. * will be passed to the animation and the appropriate target will be created.
  20314. */
  20315. Ext.define('Ext.fx.target.ElementCSS', {
  20316. /* Begin Definitions */
  20317. extend: 'Ext.fx.target.Element',
  20318. /* End Definitions */
  20319. setAttr: function(targetData, isFirstFrame) {
  20320. var cssArr = {
  20321. attrs: [],
  20322. duration: [],
  20323. easing: []
  20324. },
  20325. ln = targetData.length,
  20326. attributes,
  20327. attrs,
  20328. attr,
  20329. easing,
  20330. duration,
  20331. o,
  20332. i,
  20333. j,
  20334. ln2;
  20335. for (i = 0; i < ln; i++) {
  20336. attrs = targetData[i];
  20337. duration = attrs.duration;
  20338. easing = attrs.easing;
  20339. attrs = attrs.attrs;
  20340. for (attr in attrs) {
  20341. if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
  20342. cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
  20343. return '-' + v.toLowerCase();
  20344. }));
  20345. cssArr.duration.push(duration + 'ms');
  20346. cssArr.easing.push(easing);
  20347. }
  20348. }
  20349. }
  20350. attributes = cssArr.attrs.join(',');
  20351. duration = cssArr.duration.join(',');
  20352. easing = cssArr.easing.join(', ');
  20353. for (i = 0; i < ln; i++) {
  20354. attrs = targetData[i].attrs;
  20355. for (attr in attrs) {
  20356. ln2 = attrs[attr].length;
  20357. for (j = 0; j < ln2; j++) {
  20358. o = attrs[attr][j];
  20359. o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
  20360. o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
  20361. o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
  20362. o[0].setStyle(attr, o[1]);
  20363. // Must trigger reflow to make this get used as the start point for the transition that follows
  20364. if (isFirstFrame) {
  20365. o = o[0].dom.offsetWidth;
  20366. }
  20367. else {
  20368. // Remove transition properties when completed.
  20369. o[0].on(Ext.supports.CSS3TransitionEnd, function() {
  20370. this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
  20371. this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
  20372. this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
  20373. }, o[0], { single: true });
  20374. }
  20375. }
  20376. }
  20377. }
  20378. }
  20379. });
  20380. /**
  20381. * @class Ext.fx.target.CompositeElement
  20382. *
  20383. * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
  20384. * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
  20385. * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
  20386. * and the appropriate target will be created.
  20387. */
  20388. Ext.define('Ext.fx.target.CompositeElement', {
  20389. /* Begin Definitions */
  20390. extend: 'Ext.fx.target.Element',
  20391. /* End Definitions */
  20392. /**
  20393. * @property {Boolean} isComposite
  20394. * `true` in this class to identify an objact as an instantiated CompositeElement, or subclass thereof.
  20395. */
  20396. isComposite: true,
  20397. constructor: function(target) {
  20398. target.id = target.id || Ext.id(null, 'ext-composite-');
  20399. this.callParent([target]);
  20400. },
  20401. getAttr: function(attr, val) {
  20402. var out = [],
  20403. elements = this.target.elements,
  20404. length = elements.length,
  20405. i;
  20406. for (i = 0; i < length; i++) {
  20407. var el = elements[i];
  20408. if (el) {
  20409. el = this.target.getElement(el);
  20410. out.push([el, this.getElVal(el, attr, val)]);
  20411. }
  20412. }
  20413. return out;
  20414. }
  20415. });
  20416. /**
  20417. * @class Ext.fx.target.CompositeElementCSS
  20418. *
  20419. * This class represents a animation target for a {@link Ext.CompositeElement}, where the
  20420. * constituent elements support CSS based animation. It allows each {@link Ext.Element} in
  20421. * the group to be animated as a whole. In general this class will not be created directly,
  20422. * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target
  20423. * will be created.
  20424. */
  20425. Ext.define('Ext.fx.target.CompositeElementCSS', {
  20426. /* Begin Definitions */
  20427. extend: 'Ext.fx.target.CompositeElement',
  20428. requires: ['Ext.fx.target.ElementCSS'],
  20429. /* End Definitions */
  20430. setAttr: function() {
  20431. return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
  20432. }
  20433. });
  20434. /**
  20435. * @class Ext.fx.target.Sprite
  20436. This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
  20437. created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
  20438. and the appropriate target will be created.
  20439. * @markdown
  20440. */
  20441. Ext.define('Ext.fx.target.Sprite', {
  20442. /* Begin Definitions */
  20443. extend: 'Ext.fx.target.Target',
  20444. /* End Definitions */
  20445. type: 'draw',
  20446. getFromPrim: function(sprite, attr) {
  20447. var o;
  20448. if (attr == 'translate') {
  20449. o = {
  20450. x: sprite.attr.translation.x || 0,
  20451. y: sprite.attr.translation.y || 0
  20452. };
  20453. }
  20454. else if (attr == 'rotate') {
  20455. o = {
  20456. degrees: sprite.attr.rotation.degrees || 0,
  20457. x: sprite.attr.rotation.x,
  20458. y: sprite.attr.rotation.y
  20459. };
  20460. }
  20461. else {
  20462. o = sprite.attr[attr];
  20463. }
  20464. return o;
  20465. },
  20466. getAttr: function(attr, val) {
  20467. return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
  20468. },
  20469. setAttr: function(targetData) {
  20470. var ln = targetData.length,
  20471. spriteArr = [],
  20472. attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
  20473. for (i = 0; i < ln; i++) {
  20474. attrs = targetData[i].attrs;
  20475. for (attr in attrs) {
  20476. attrArr = attrs[attr];
  20477. ln2 = attrArr.length;
  20478. for (j = 0; j < ln2; j++) {
  20479. spritePtr = attrArr[j][0];
  20480. attPtr = attrArr[j][1];
  20481. if (attr === 'translate') {
  20482. value = {
  20483. x: attPtr.x,
  20484. y: attPtr.y
  20485. };
  20486. }
  20487. else if (attr === 'rotate') {
  20488. x = attPtr.x;
  20489. if (isNaN(x)) {
  20490. x = null;
  20491. }
  20492. y = attPtr.y;
  20493. if (isNaN(y)) {
  20494. y = null;
  20495. }
  20496. value = {
  20497. degrees: attPtr.degrees,
  20498. x: x,
  20499. y: y
  20500. };
  20501. }
  20502. else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
  20503. value = parseFloat(attPtr);
  20504. }
  20505. else {
  20506. value = attPtr;
  20507. }
  20508. idx = Ext.Array.indexOf(spriteArr, spritePtr);
  20509. if (idx == -1) {
  20510. spriteArr.push([spritePtr, {}]);
  20511. idx = spriteArr.length - 1;
  20512. }
  20513. spriteArr[idx][1][attr] = value;
  20514. }
  20515. }
  20516. }
  20517. ln = spriteArr.length;
  20518. for (i = 0; i < ln; i++) {
  20519. spritePtr = spriteArr[i];
  20520. spritePtr[0].setAttributes(spritePtr[1]);
  20521. }
  20522. this.target.redraw();
  20523. }
  20524. });
  20525. /**
  20526. * @class Ext.fx.target.CompositeSprite
  20527. This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
  20528. each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
  20529. created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
  20530. and the appropriate target will be created.
  20531. * @markdown
  20532. */
  20533. Ext.define('Ext.fx.target.CompositeSprite', {
  20534. /* Begin Definitions */
  20535. extend: 'Ext.fx.target.Sprite',
  20536. /* End Definitions */
  20537. getAttr: function(attr, val) {
  20538. var out = [],
  20539. sprites = [].concat(this.target.items),
  20540. length = sprites.length,
  20541. i;
  20542. for (i = 0; i < length; i++) {
  20543. var sprite = sprites[i];
  20544. out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
  20545. }
  20546. return out;
  20547. }
  20548. });
  20549. /**
  20550. * @class Ext.fx.target.Component
  20551. *
  20552. * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
  20553. * created directly, the {@link Ext.Component} will be passed to the animation and
  20554. * and the appropriate target will be created.
  20555. */
  20556. Ext.define('Ext.fx.target.Component', {
  20557. /* Begin Definitions */
  20558. extend: 'Ext.fx.target.Target',
  20559. /* End Definitions */
  20560. type: 'component',
  20561. // Methods to call to retrieve unspecified "from" values from a target Component
  20562. getPropMethod: {
  20563. top: function() {
  20564. return this.getPosition(true)[1];
  20565. },
  20566. left: function() {
  20567. return this.getPosition(true)[0];
  20568. },
  20569. x: function() {
  20570. return this.getPosition()[0];
  20571. },
  20572. y: function() {
  20573. return this.getPosition()[1];
  20574. },
  20575. height: function() {
  20576. return this.getHeight();
  20577. },
  20578. width: function() {
  20579. return this.getWidth();
  20580. },
  20581. opacity: function() {
  20582. return this.el.getStyle('opacity');
  20583. }
  20584. },
  20585. compMethod: {
  20586. top: 'setPosition',
  20587. left: 'setPosition',
  20588. x: 'setPagePosition',
  20589. y: 'setPagePosition',
  20590. height: 'setSize',
  20591. width: 'setSize',
  20592. opacity: 'setOpacity'
  20593. },
  20594. // Read the named attribute from the target Component. Use the defined getter for the attribute
  20595. getAttr: function(attr, val) {
  20596. return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
  20597. },
  20598. setAttr: function(targetData, isFirstFrame, isLastFrame) {
  20599. var me = this,
  20600. target = me.target,
  20601. ln = targetData.length,
  20602. attrs, attr, o, i, j, meth, targets, left, top, w, h;
  20603. for (i = 0; i < ln; i++) {
  20604. attrs = targetData[i].attrs;
  20605. for (attr in attrs) {
  20606. targets = attrs[attr].length;
  20607. meth = {
  20608. setPosition: {},
  20609. setPagePosition: {},
  20610. setSize: {},
  20611. setOpacity: {}
  20612. };
  20613. for (j = 0; j < targets; j++) {
  20614. o = attrs[attr][j];
  20615. // We REALLY want a single function call, so push these down to merge them: eg
  20616. // meth.setPagePosition.target = <targetComponent>
  20617. // meth.setPagePosition['x'] = 100
  20618. // meth.setPagePosition['y'] = 100
  20619. meth[me.compMethod[attr]].target = o[0];
  20620. meth[me.compMethod[attr]][attr] = o[1];
  20621. }
  20622. if (meth.setPosition.target) {
  20623. o = meth.setPosition;
  20624. left = (o.left === undefined) ? undefined : parseFloat(o.left);
  20625. top = (o.top === undefined) ? undefined : parseFloat(o.top);
  20626. o.target.setPosition(left, top);
  20627. }
  20628. if (meth.setPagePosition.target) {
  20629. o = meth.setPagePosition;
  20630. o.target.setPagePosition(o.x, o.y);
  20631. }
  20632. if (meth.setSize.target) {
  20633. o = meth.setSize;
  20634. // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
  20635. w = (o.width === undefined) ? o.target.getWidth() : parseFloat(o.width);
  20636. h = (o.height === undefined) ? o.target.getHeight() : parseFloat(o.height);
  20637. // Only set the size of the Component on the last frame, or if the animation was
  20638. // configured with dynamic: true.
  20639. // In other cases, we just set the target element size.
  20640. // This will result in either clipping if animating a reduction in size, or the revealing of
  20641. // the inner elements of the Component if animating an increase in size.
  20642. // Component's animate function initially resizes to the larger size before resizing the
  20643. // outer element to clip the contents.
  20644. if (isLastFrame || me.dynamic) {
  20645. o.target.setSize(w, h);
  20646. } else {
  20647. o.target.el.setSize(w, h);
  20648. }
  20649. }
  20650. if (meth.setOpacity.target) {
  20651. o = meth.setOpacity;
  20652. o.target.el.setStyle('opacity', o.opacity);
  20653. }
  20654. }
  20655. }
  20656. }
  20657. });
  20658. /**
  20659. * @class Ext.fx.Manager
  20660. * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
  20661. * @private
  20662. * @singleton
  20663. */
  20664. Ext.define('Ext.fx.Manager', {
  20665. /* Begin Definitions */
  20666. singleton: true,
  20667. requires: ['Ext.util.MixedCollection',
  20668. 'Ext.fx.target.Element',
  20669. 'Ext.fx.target.ElementCSS',
  20670. 'Ext.fx.target.CompositeElement',
  20671. 'Ext.fx.target.CompositeElementCSS',
  20672. 'Ext.fx.target.Sprite',
  20673. 'Ext.fx.target.CompositeSprite',
  20674. 'Ext.fx.target.Component'],
  20675. mixins: {
  20676. queue: 'Ext.fx.Queue'
  20677. },
  20678. /* End Definitions */
  20679. constructor: function() {
  20680. this.items = new Ext.util.MixedCollection();
  20681. this.mixins.queue.constructor.call(this);
  20682. // this.requestAnimFrame = (function() {
  20683. // var raf = window.requestAnimationFrame ||
  20684. // window.webkitRequestAnimationFrame ||
  20685. // window.mozRequestAnimationFrame ||
  20686. // window.oRequestAnimationFrame ||
  20687. // window.msRequestAnimationFrame;
  20688. // if (raf) {
  20689. // return function(callback, element) {
  20690. // raf(callback);
  20691. // };
  20692. // }
  20693. // else {
  20694. // return function(callback, element) {
  20695. // window.setTimeout(callback, Ext.fx.Manager.interval);
  20696. // };
  20697. // }
  20698. // })();
  20699. },
  20700. /**
  20701. * @cfg {Number} interval Default interval in miliseconds to calculate each frame. Defaults to 16ms (~60fps)
  20702. */
  20703. interval: 16,
  20704. /**
  20705. * @cfg {Boolean} forceJS Force the use of JavaScript-based animation instead of CSS3 animation, even when CSS3
  20706. * animation is supported by the browser. This defaults to true currently, as CSS3 animation support is still
  20707. * considered experimental at this time, and if used should be thouroughly tested across all targeted browsers.
  20708. * @protected
  20709. */
  20710. forceJS: true,
  20711. // @private Target factory
  20712. createTarget: function(target) {
  20713. var me = this,
  20714. useCSS3 = !me.forceJS && Ext.supports.Transitions,
  20715. targetObj;
  20716. me.useCSS3 = useCSS3;
  20717. if (target) {
  20718. // dom element, string or fly
  20719. if (target.tagName || Ext.isString(target) || target.isFly) {
  20720. target = Ext.get(target);
  20721. targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target);
  20722. }
  20723. // Element
  20724. else if (target.dom) {
  20725. targetObj = new Ext.fx.target['Element' + (useCSS3 ? 'CSS' : '')](target);
  20726. }
  20727. // Element Composite
  20728. else if (target.isComposite) {
  20729. targetObj = new Ext.fx.target['CompositeElement' + (useCSS3 ? 'CSS' : '')](target);
  20730. }
  20731. // Draw Sprite
  20732. else if (target.isSprite) {
  20733. targetObj = new Ext.fx.target.Sprite(target);
  20734. }
  20735. // Draw Sprite Composite
  20736. else if (target.isCompositeSprite) {
  20737. targetObj = new Ext.fx.target.CompositeSprite(target);
  20738. }
  20739. // Component
  20740. else if (target.isComponent) {
  20741. targetObj = new Ext.fx.target.Component(target);
  20742. }
  20743. else if (target.isAnimTarget) {
  20744. return target;
  20745. }
  20746. else {
  20747. return null;
  20748. }
  20749. me.targets.add(targetObj);
  20750. return targetObj;
  20751. }
  20752. else {
  20753. return null;
  20754. }
  20755. },
  20756. /**
  20757. * Add an Anim to the manager. This is done automatically when an Anim instance is created.
  20758. * @param {Ext.fx.Anim} anim
  20759. */
  20760. addAnim: function(anim) {
  20761. var items = this.items,
  20762. task = this.task
  20763. // Make sure we use the anim's id, not the anim target's id here. The anim id will be unique on
  20764. // each call to addAnim. `anim.target` is the DOM element being targeted, and since multiple animations
  20765. // can target a single DOM node concurrently, the target id cannot be assumned to be unique.
  20766. items.add(anim.id, anim);
  20767. //Ext.log('+ added anim ', anim.id, ', target: ', anim.target.getId(), ', duration: ', anim.duration);
  20768. // Start the timer if not already running
  20769. if (!task && items.length) {
  20770. task = this.task = {
  20771. run: this.runner,
  20772. interval: this.interval,
  20773. scope: this
  20774. };
  20775. //Ext.log('--->> Starting task');
  20776. Ext.TaskManager.start(task);
  20777. }
  20778. },
  20779. /**
  20780. * Remove an Anim from the manager. This is done automatically when an Anim ends.
  20781. * @param {Ext.fx.Anim} anim
  20782. */
  20783. removeAnim: function(anim) {
  20784. var me = this,
  20785. items = me.items,
  20786. task = me.task;
  20787. items.removeAtKey(anim.id);
  20788. //Ext.log(' X removed anim ', anim.id, ', target: ', anim.target.getId(), ', frames: ', anim.frameCount, ', item count: ', items.length);
  20789. // Stop the timer if there are no more managed Anims
  20790. if (task && !items.length) {
  20791. //Ext.log('[]--- Stopping task');
  20792. Ext.TaskManager.stop(task);
  20793. delete me.task;
  20794. }
  20795. },
  20796. /**
  20797. * @private
  20798. * Runner function being called each frame
  20799. */
  20800. runner: function() {
  20801. var me = this,
  20802. items = me.items.getRange(),
  20803. i = 0,
  20804. len = items.length,
  20805. anim;
  20806. //Ext.log(' executing anim runner task with ', len, ' items');
  20807. me.targetArr = {};
  20808. // Single timestamp for all animations this interval
  20809. me.timestamp = new Date();
  20810. // Loop to start any new animations first before looping to
  20811. // execute running animations (which will also include all animations
  20812. // started in this loop). This is a subtle difference from simply
  20813. // iterating in one loop and starting then running each animation,
  20814. // but separating the loops is necessary to ensure that all new animations
  20815. // actually kick off prior to existing ones regardless of array order.
  20816. // Otherwise in edge cases when there is excess latency in overall
  20817. // performance, allowing existing animations to run before new ones can
  20818. // lead to dropped frames and subtle race conditions when they are
  20819. // interdependent, which is often the case with certain Element fx.
  20820. for (; i < len; i++) {
  20821. anim = items[i];
  20822. if (anim.isReady()) {
  20823. //Ext.log(' starting anim ', anim.id, ', target: ', anim.target.id);
  20824. me.startAnim(anim);
  20825. }
  20826. }
  20827. for (i = 0; i < len; i++) {
  20828. anim = items[i];
  20829. if (anim.isRunning()) {
  20830. //Ext.log(' running anim ', anim.target.id);
  20831. me.runAnim(anim);
  20832. } else if (!me.useCSS3) {
  20833. // When using CSS3 transitions the animations get paused since they are not
  20834. // needed once the transition is handed over to the browser, so we can
  20835. // ignore this case. However if we are doing JS animations and something is
  20836. // paused here it's possibly unintentional.
  20837. //Ext.log(' (i) anim ', anim.id, ' is active but not running...');
  20838. }
  20839. }
  20840. // Apply all the pending changes to their targets
  20841. me.applyPendingAttrs();
  20842. },
  20843. /**
  20844. * @private
  20845. * Start the individual animation (initialization)
  20846. */
  20847. startAnim: function(anim) {
  20848. anim.start(this.timestamp);
  20849. },
  20850. /**
  20851. * @private
  20852. * Run the individual animation for this frame
  20853. */
  20854. runAnim: function(anim) {
  20855. if (!anim) {
  20856. return;
  20857. }
  20858. var me = this,
  20859. targetId = anim.target.getId(),
  20860. useCSS3 = me.useCSS3 && anim.target.type == 'element',
  20861. elapsedTime = me.timestamp - anim.startTime,
  20862. lastFrame = (elapsedTime >= anim.duration),
  20863. target, o;
  20864. target = this.collectTargetData(anim, elapsedTime, useCSS3, lastFrame);
  20865. // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
  20866. // to get a good initial state, then add the transition properties and set the final attributes.
  20867. if (useCSS3) {
  20868. //Ext.log(' (i) using CSS3 transitions');
  20869. // Flush the collected attributes, without transition
  20870. anim.target.setAttr(target.anims[anim.id].attributes, true);
  20871. // Add the end frame data
  20872. me.collectTargetData(anim, anim.duration, useCSS3, lastFrame);
  20873. // Pause the animation so runAnim doesn't keep getting called
  20874. anim.paused = true;
  20875. target = anim.target.target;
  20876. // We only want to attach an event on the last element in a composite
  20877. if (anim.target.isComposite) {
  20878. target = anim.target.target.last();
  20879. }
  20880. // Listen for the transitionend event
  20881. o = {};
  20882. o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
  20883. o.scope = anim;
  20884. o.single = true;
  20885. target.on(o);
  20886. }
  20887. },
  20888. /**
  20889. * @private
  20890. * Collect target attributes for the given Anim object at the given timestamp
  20891. * @param {Ext.fx.Anim} anim The Anim instance
  20892. * @param {Number} timestamp Time after the anim's start time
  20893. * @param {Boolean} [useCSS3=false] True if using CSS3-based animation, else false
  20894. * @param {Boolean} [isLastFrame=false] True if this is the last frame of animation to be run, else false
  20895. * @return {Object} The animation target wrapper object containing the passed animation along with the
  20896. * new attributes to set on the target's element in the next animation frame.
  20897. */
  20898. collectTargetData: function(anim, elapsedTime, useCSS3, isLastFrame) {
  20899. var targetId = anim.target.getId(),
  20900. target = this.targetArr[targetId];
  20901. if (!target) {
  20902. // Create a thin wrapper around the target so that we can create a link between the
  20903. // target element and its associated animations. This is important later when applying
  20904. // attributes to the target so that each animation can be independently run with its own
  20905. // duration and stopped at any point without affecting other animations for the same target.
  20906. target = this.targetArr[targetId] = {
  20907. id: targetId,
  20908. el: anim.target,
  20909. anims: {}
  20910. }
  20911. }
  20912. // This is a wrapper for the animation so that we can also save state along with it,
  20913. // including the current elapsed time and lastFrame status. Even though this method only
  20914. // adds a single anim object per call, each target element could have multiple animations
  20915. // associated with it, which is why the anim is added to the target's `anims` hash by id.
  20916. target.anims[anim.id] = {
  20917. id: anim.id,
  20918. anim: anim,
  20919. elapsed: elapsedTime,
  20920. isLastFrame: isLastFrame,
  20921. // This is the object that gets applied to the target element below in applyPendingAttrs():
  20922. attributes: [{
  20923. duration: anim.duration,
  20924. easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
  20925. // This is where the magic happens. The anim calculates what its new attributes should
  20926. // be based on the current frame and returns those as a hash of values.
  20927. attrs: anim.runAnim(elapsedTime)
  20928. }]
  20929. };
  20930. return target;
  20931. },
  20932. /**
  20933. * @private
  20934. * Apply all pending attribute changes to their targets
  20935. */
  20936. applyPendingAttrs: function() {
  20937. var targetArr = this.targetArr,
  20938. target, targetId, animWrap, anim, animId;
  20939. // Loop through each target
  20940. for (targetId in targetArr) {
  20941. if (targetArr.hasOwnProperty(targetId)) {
  20942. target = targetArr[targetId];
  20943. // Each target could have multiple associated animations, so iterate those
  20944. for (animId in target.anims) {
  20945. if (target.anims.hasOwnProperty(animId)) {
  20946. animWrap = target.anims[animId],
  20947. anim = animWrap.anim;
  20948. // If the animation has valid attributes, set them on the target
  20949. if (animWrap.attributes && anim.isRunning()) {
  20950. //Ext.log(' > applying attributes for anim ', animWrap.id, ', target: ', target.id, ', elapsed: ', animWrap.elapsed);
  20951. target.el.setAttr(animWrap.attributes, false, animWrap.isLastFrame);
  20952. // If this particular anim is at the last frame end it
  20953. if (animWrap.isLastFrame) {
  20954. //Ext.log(' running last frame for ', animWrap.id, ', target: ', targetId);
  20955. anim.lastFrame();
  20956. }
  20957. }
  20958. }
  20959. }
  20960. }
  20961. }
  20962. }
  20963. });
  20964. /**
  20965. * @class Ext.fx.Animator
  20966. *
  20967. * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
  20968. * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
  20969. * at various points throughout the animation.
  20970. *
  20971. * ## Using Keyframes
  20972. *
  20973. * The {@link #keyframes} option is the most important part of specifying an animation when using this
  20974. * class. A key frame is a point in a particular animation. We represent this as a percentage of the
  20975. * total animation duration. At each key frame, we can specify the target values at that time. Note that
  20976. * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
  20977. * event that fires after each key frame is reached.
  20978. *
  20979. * ## Example
  20980. *
  20981. * In the example below, we modify the values of the element at each fifth throughout the animation.
  20982. *
  20983. * @example
  20984. * Ext.create('Ext.fx.Animator', {
  20985. * target: Ext.getBody().createChild({
  20986. * style: {
  20987. * width: '100px',
  20988. * height: '100px',
  20989. * 'background-color': 'red'
  20990. * }
  20991. * }),
  20992. * duration: 10000, // 10 seconds
  20993. * keyframes: {
  20994. * 0: {
  20995. * opacity: 1,
  20996. * backgroundColor: 'FF0000'
  20997. * },
  20998. * 20: {
  20999. * x: 30,
  21000. * opacity: 0.5
  21001. * },
  21002. * 40: {
  21003. * x: 130,
  21004. * backgroundColor: '0000FF'
  21005. * },
  21006. * 60: {
  21007. * y: 80,
  21008. * opacity: 0.3
  21009. * },
  21010. * 80: {
  21011. * width: 200,
  21012. * y: 200
  21013. * },
  21014. * 100: {
  21015. * opacity: 1,
  21016. * backgroundColor: '00FF00'
  21017. * }
  21018. * }
  21019. * });
  21020. */
  21021. Ext.define('Ext.fx.Animator', {
  21022. /* Begin Definitions */
  21023. mixins: {
  21024. observable: 'Ext.util.Observable'
  21025. },
  21026. requires: ['Ext.fx.Manager'],
  21027. /* End Definitions */
  21028. /**
  21029. * @property {Boolean} isAnimator
  21030. * `true` in this class to identify an objact as an instantiated Animator, or subclass thereof.
  21031. */
  21032. isAnimator: true,
  21033. /**
  21034. * @cfg {Number} duration
  21035. * Time in milliseconds for the animation to last. Defaults to 250.
  21036. */
  21037. duration: 250,
  21038. /**
  21039. * @cfg {Number} delay
  21040. * Time to delay before starting the animation. Defaults to 0.
  21041. */
  21042. delay: 0,
  21043. /* private used to track a delayed starting time */
  21044. delayStart: 0,
  21045. /**
  21046. * @cfg {Boolean} dynamic
  21047. * Currently only for Component Animation: Only set a component's outer element size bypassing layouts. Set to true to do full layouts for every frame of the animation. Defaults to false.
  21048. */
  21049. dynamic: false,
  21050. /**
  21051. * @cfg {String} easing
  21052. *
  21053. * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
  21054. * speed over its duration.
  21055. *
  21056. * - backIn
  21057. * - backOut
  21058. * - bounceIn
  21059. * - bounceOut
  21060. * - ease
  21061. * - easeIn
  21062. * - easeOut
  21063. * - easeInOut
  21064. * - elasticIn
  21065. * - elasticOut
  21066. * - cubic-bezier(x1, y1, x2, y2)
  21067. *
  21068. * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
  21069. * specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
  21070. * be in the range [0, 1] or the definition is invalid.
  21071. *
  21072. * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
  21073. */
  21074. easing: 'ease',
  21075. /**
  21076. * Flag to determine if the animation has started
  21077. * @property running
  21078. * @type Boolean
  21079. */
  21080. running: false,
  21081. /**
  21082. * Flag to determine if the animation is paused. Only set this to true if you need to
  21083. * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
  21084. * @property paused
  21085. * @type Boolean
  21086. */
  21087. paused: false,
  21088. /**
  21089. * @private
  21090. */
  21091. damper: 1,
  21092. /**
  21093. * @cfg {Number} iterations
  21094. * Number of times to execute the animation. Defaults to 1.
  21095. */
  21096. iterations: 1,
  21097. /**
  21098. * Current iteration the animation is running.
  21099. * @property currentIteration
  21100. * @type Number
  21101. */
  21102. currentIteration: 0,
  21103. /**
  21104. * Current keyframe step of the animation.
  21105. * @property keyframeStep
  21106. * @type Number
  21107. */
  21108. keyframeStep: 0,
  21109. /**
  21110. * @private
  21111. */
  21112. animKeyFramesRE: /^(from|to|\d+%?)$/,
  21113. /**
  21114. * @cfg {Ext.fx.target.Target} target
  21115. * The Ext.fx.target to apply the animation to. If not specified during initialization, this can be passed to the applyAnimator
  21116. * method to apply the same animation to many targets.
  21117. */
  21118. /**
  21119. * @cfg {Object} keyframes
  21120. * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
  21121. * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
  21122. * "from" or "to"</b>. A keyframe declaration without these keyframe selectors is invalid and will not be available for
  21123. * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
  21124. * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
  21125. <pre><code>
  21126. keyframes : {
  21127. '0%': {
  21128. left: 100
  21129. },
  21130. '40%': {
  21131. left: 150
  21132. },
  21133. '60%': {
  21134. left: 75
  21135. },
  21136. '100%': {
  21137. left: 100
  21138. }
  21139. }
  21140. </code></pre>
  21141. */
  21142. constructor: function(config) {
  21143. var me = this;
  21144. config = Ext.apply(me, config || {});
  21145. me.config = config;
  21146. me.id = Ext.id(null, 'ext-animator-');
  21147. me.addEvents(
  21148. /**
  21149. * @event beforeanimate
  21150. * Fires before the animation starts. A handler can return false to cancel the animation.
  21151. * @param {Ext.fx.Animator} this
  21152. */
  21153. 'beforeanimate',
  21154. /**
  21155. * @event keyframe
  21156. * Fires at each keyframe.
  21157. * @param {Ext.fx.Animator} this
  21158. * @param {Number} keyframe step number
  21159. */
  21160. 'keyframe',
  21161. /**
  21162. * @event afteranimate
  21163. * Fires when the animation is complete.
  21164. * @param {Ext.fx.Animator} this
  21165. * @param {Date} startTime
  21166. */
  21167. 'afteranimate'
  21168. );
  21169. me.mixins.observable.constructor.call(me, config);
  21170. me.timeline = [];
  21171. me.createTimeline(me.keyframes);
  21172. if (me.target) {
  21173. me.applyAnimator(me.target);
  21174. Ext.fx.Manager.addAnim(me);
  21175. }
  21176. },
  21177. /**
  21178. * @private
  21179. */
  21180. sorter: function (a, b) {
  21181. return a.pct - b.pct;
  21182. },
  21183. /**
  21184. * @private
  21185. * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
  21186. * or applying the 'to' configuration to all keyframes. Also calculates the proper animation duration per keyframe.
  21187. */
  21188. createTimeline: function(keyframes) {
  21189. var me = this,
  21190. attrs = [],
  21191. to = me.to || {},
  21192. duration = me.duration,
  21193. prevMs, ms, i, ln, pct, anim, nextAnim, attr;
  21194. for (pct in keyframes) {
  21195. if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
  21196. attr = {attrs: Ext.apply(keyframes[pct], to)};
  21197. // CSS3 spec allow for from/to to be specified.
  21198. if (pct == "from") {
  21199. pct = 0;
  21200. }
  21201. else if (pct == "to") {
  21202. pct = 100;
  21203. }
  21204. // convert % values into integers
  21205. attr.pct = parseInt(pct, 10);
  21206. attrs.push(attr);
  21207. }
  21208. }
  21209. // Sort by pct property
  21210. Ext.Array.sort(attrs, me.sorter);
  21211. // Only an end
  21212. //if (attrs[0].pct) {
  21213. // attrs.unshift({pct: 0, attrs: element.attrs});
  21214. //}
  21215. ln = attrs.length;
  21216. for (i = 0; i < ln; i++) {
  21217. prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
  21218. ms = duration * (attrs[i].pct / 100);
  21219. me.timeline.push({
  21220. duration: ms - prevMs,
  21221. attrs: attrs[i].attrs
  21222. });
  21223. }
  21224. },
  21225. /**
  21226. * Applies animation to the Ext.fx.target
  21227. * @private
  21228. * @param target
  21229. * @type String/Object
  21230. */
  21231. applyAnimator: function(target) {
  21232. var me = this,
  21233. anims = [],
  21234. timeline = me.timeline,
  21235. reverse = me.reverse,
  21236. ln = timeline.length,
  21237. anim, easing, damper, initial, attrs, lastAttrs, i;
  21238. if (me.fireEvent('beforeanimate', me) !== false) {
  21239. for (i = 0; i < ln; i++) {
  21240. anim = timeline[i];
  21241. attrs = anim.attrs;
  21242. easing = attrs.easing || me.easing;
  21243. damper = attrs.damper || me.damper;
  21244. delete attrs.easing;
  21245. delete attrs.damper;
  21246. anim = new Ext.fx.Anim({
  21247. target: target,
  21248. easing: easing,
  21249. damper: damper,
  21250. duration: anim.duration,
  21251. paused: true,
  21252. to: attrs
  21253. });
  21254. anims.push(anim);
  21255. }
  21256. me.animations = anims;
  21257. me.target = anim.target;
  21258. for (i = 0; i < ln - 1; i++) {
  21259. anim = anims[i];
  21260. anim.nextAnim = anims[i + 1];
  21261. anim.on('afteranimate', function() {
  21262. this.nextAnim.paused = false;
  21263. });
  21264. anim.on('afteranimate', function() {
  21265. this.fireEvent('keyframe', this, ++this.keyframeStep);
  21266. }, me);
  21267. }
  21268. anims[ln - 1].on('afteranimate', function() {
  21269. this.lastFrame();
  21270. }, me);
  21271. }
  21272. },
  21273. /**
  21274. * @private
  21275. * Fires beforeanimate and sets the running flag.
  21276. */
  21277. start: function(startTime) {
  21278. var me = this,
  21279. delay = me.delay,
  21280. delayStart = me.delayStart,
  21281. delayDelta;
  21282. if (delay) {
  21283. if (!delayStart) {
  21284. me.delayStart = startTime;
  21285. return;
  21286. }
  21287. else {
  21288. delayDelta = startTime - delayStart;
  21289. if (delayDelta < delay) {
  21290. return;
  21291. }
  21292. else {
  21293. // Compensate for frame delay;
  21294. startTime = new Date(delayStart.getTime() + delay);
  21295. }
  21296. }
  21297. }
  21298. if (me.fireEvent('beforeanimate', me) !== false) {
  21299. me.startTime = startTime;
  21300. me.running = true;
  21301. me.animations[me.keyframeStep].paused = false;
  21302. }
  21303. },
  21304. /**
  21305. * @private
  21306. * Perform lastFrame cleanup and handle iterations
  21307. * @returns a hash of the new attributes.
  21308. */
  21309. lastFrame: function() {
  21310. var me = this,
  21311. iter = me.iterations,
  21312. iterCount = me.currentIteration;
  21313. iterCount++;
  21314. if (iterCount < iter) {
  21315. me.startTime = new Date();
  21316. me.currentIteration = iterCount;
  21317. me.keyframeStep = 0;
  21318. me.applyAnimator(me.target);
  21319. me.animations[me.keyframeStep].paused = false;
  21320. }
  21321. else {
  21322. me.currentIteration = 0;
  21323. me.end();
  21324. }
  21325. },
  21326. /**
  21327. * Fire afteranimate event and end the animation. Usually called automatically when the
  21328. * animation reaches its final frame, but can also be called manually to pre-emptively
  21329. * stop and destroy the running animation.
  21330. */
  21331. end: function() {
  21332. var me = this;
  21333. me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
  21334. },
  21335. isReady: function() {
  21336. return this.paused === false && this.running === false && this.iterations > 0;
  21337. },
  21338. isRunning: function() {
  21339. // Explicitly return false, we don't want to be run continuously by the manager
  21340. return false;
  21341. }
  21342. });
  21343. /**
  21344. * @class Ext.draw.Draw
  21345. * Base Drawing class. Provides base drawing functions.
  21346. * @private
  21347. */
  21348. Ext.define('Ext.draw.Draw', {
  21349. /* Begin Definitions */
  21350. singleton: true,
  21351. requires: ['Ext.draw.Color'],
  21352. /* End Definitions */
  21353. pathToStringRE: /,?([achlmqrstvxz]),?/gi,
  21354. pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
  21355. pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
  21356. stopsRE: /^(\d+%?)$/,
  21357. radian: Math.PI / 180,
  21358. availableAnimAttrs: {
  21359. along: "along",
  21360. blur: null,
  21361. "clip-rect": "csv",
  21362. cx: null,
  21363. cy: null,
  21364. fill: "color",
  21365. "fill-opacity": null,
  21366. "font-size": null,
  21367. height: null,
  21368. opacity: null,
  21369. path: "path",
  21370. r: null,
  21371. rotation: "csv",
  21372. rx: null,
  21373. ry: null,
  21374. scale: "csv",
  21375. stroke: "color",
  21376. "stroke-opacity": null,
  21377. "stroke-width": null,
  21378. translation: "csv",
  21379. width: null,
  21380. x: null,
  21381. y: null
  21382. },
  21383. is: function(o, type) {
  21384. type = String(type).toLowerCase();
  21385. return (type == "object" && o === Object(o)) ||
  21386. (type == "undefined" && typeof o == type) ||
  21387. (type == "null" && o === null) ||
  21388. (type == "array" && Array.isArray && Array.isArray(o)) ||
  21389. (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
  21390. },
  21391. ellipsePath: function(sprite) {
  21392. var attr = sprite.attr;
  21393. return Ext.String.format("M{0},{1}A{2},{3},0,1,1,{0},{4}A{2},{3},0,1,1,{0},{1}z", attr.x, attr.y - attr.ry, attr.rx, attr.ry, attr.y + attr.ry);
  21394. },
  21395. rectPath: function(sprite) {
  21396. var attr = sprite.attr;
  21397. if (attr.radius) {
  21398. return Ext.String.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z", attr.x + attr.radius, attr.y, attr.width - attr.radius * 2, attr.radius, -attr.radius, attr.height - attr.radius * 2, attr.radius * 2 - attr.width, attr.radius * 2 - attr.height);
  21399. }
  21400. else {
  21401. return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
  21402. }
  21403. },
  21404. // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
  21405. path2string: function () {
  21406. return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
  21407. },
  21408. // Convert the passed arrayPath to a proper SVG path string (d attribute)
  21409. pathToString: function(arrayPath) {
  21410. return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
  21411. },
  21412. parsePathString: function (pathString) {
  21413. if (!pathString) {
  21414. return null;
  21415. }
  21416. var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
  21417. data = [],
  21418. me = this;
  21419. if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
  21420. data = me.pathClone(pathString);
  21421. }
  21422. if (!data.length) {
  21423. String(pathString).replace(me.pathCommandRE, function (a, b, c) {
  21424. var params = [],
  21425. name = b.toLowerCase();
  21426. c.replace(me.pathValuesRE, function (a, b) {
  21427. b && params.push(+b);
  21428. });
  21429. if (name == "m" && params.length > 2) {
  21430. data.push([b].concat(Ext.Array.splice(params, 0, 2)));
  21431. name = "l";
  21432. b = (b == "m") ? "l" : "L";
  21433. }
  21434. while (params.length >= paramCounts[name]) {
  21435. data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
  21436. if (!paramCounts[name]) {
  21437. break;
  21438. }
  21439. }
  21440. });
  21441. }
  21442. data.toString = me.path2string;
  21443. return data;
  21444. },
  21445. mapPath: function (path, matrix) {
  21446. if (!matrix) {
  21447. return path;
  21448. }
  21449. var x, y, i, ii, j, jj, pathi;
  21450. path = this.path2curve(path);
  21451. for (i = 0, ii = path.length; i < ii; i++) {
  21452. pathi = path[i];
  21453. for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
  21454. x = matrix.x(pathi[j], pathi[j + 1]);
  21455. y = matrix.y(pathi[j], pathi[j + 1]);
  21456. pathi[j] = x;
  21457. pathi[j + 1] = y;
  21458. }
  21459. }
  21460. return path;
  21461. },
  21462. pathClone: function(pathArray) {
  21463. var res = [],
  21464. j, jj, i, ii;
  21465. if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
  21466. pathArray = this.parsePathString(pathArray);
  21467. }
  21468. for (i = 0, ii = pathArray.length; i < ii; i++) {
  21469. res[i] = [];
  21470. for (j = 0, jj = pathArray[i].length; j < jj; j++) {
  21471. res[i][j] = pathArray[i][j];
  21472. }
  21473. }
  21474. res.toString = this.path2string;
  21475. return res;
  21476. },
  21477. pathToAbsolute: function (pathArray) {
  21478. if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
  21479. pathArray = this.parsePathString(pathArray);
  21480. }
  21481. var res = [],
  21482. x = 0,
  21483. y = 0,
  21484. mx = 0,
  21485. my = 0,
  21486. i = 0,
  21487. ln = pathArray.length,
  21488. r, pathSegment, j, ln2;
  21489. // MoveTo initial x/y position
  21490. if (ln && pathArray[0][0] == "M") {
  21491. x = +pathArray[0][1];
  21492. y = +pathArray[0][2];
  21493. mx = x;
  21494. my = y;
  21495. i++;
  21496. res[0] = ["M", x, y];
  21497. }
  21498. for (; i < ln; i++) {
  21499. r = res[i] = [];
  21500. pathSegment = pathArray[i];
  21501. if (pathSegment[0] != pathSegment[0].toUpperCase()) {
  21502. r[0] = pathSegment[0].toUpperCase();
  21503. switch (r[0]) {
  21504. // Elliptical Arc
  21505. case "A":
  21506. r[1] = pathSegment[1];
  21507. r[2] = pathSegment[2];
  21508. r[3] = pathSegment[3];
  21509. r[4] = pathSegment[4];
  21510. r[5] = pathSegment[5];
  21511. r[6] = +(pathSegment[6] + x);
  21512. r[7] = +(pathSegment[7] + y);
  21513. break;
  21514. // Vertical LineTo
  21515. case "V":
  21516. r[1] = +pathSegment[1] + y;
  21517. break;
  21518. // Horizontal LineTo
  21519. case "H":
  21520. r[1] = +pathSegment[1] + x;
  21521. break;
  21522. case "M":
  21523. // MoveTo
  21524. mx = +pathSegment[1] + x;
  21525. my = +pathSegment[2] + y;
  21526. default:
  21527. j = 1;
  21528. ln2 = pathSegment.length;
  21529. for (; j < ln2; j++) {
  21530. r[j] = +pathSegment[j] + ((j % 2) ? x : y);
  21531. }
  21532. }
  21533. }
  21534. else {
  21535. j = 0;
  21536. ln2 = pathSegment.length;
  21537. for (; j < ln2; j++) {
  21538. res[i][j] = pathSegment[j];
  21539. }
  21540. }
  21541. switch (r[0]) {
  21542. // ClosePath
  21543. case "Z":
  21544. x = mx;
  21545. y = my;
  21546. break;
  21547. // Horizontal LineTo
  21548. case "H":
  21549. x = r[1];
  21550. break;
  21551. // Vertical LineTo
  21552. case "V":
  21553. y = r[1];
  21554. break;
  21555. // MoveTo
  21556. case "M":
  21557. pathSegment = res[i];
  21558. ln2 = pathSegment.length;
  21559. mx = pathSegment[ln2 - 2];
  21560. my = pathSegment[ln2 - 1];
  21561. default:
  21562. pathSegment = res[i];
  21563. ln2 = pathSegment.length;
  21564. x = pathSegment[ln2 - 2];
  21565. y = pathSegment[ln2 - 1];
  21566. }
  21567. }
  21568. res.toString = this.path2string;
  21569. return res;
  21570. },
  21571. // TO BE DEPRECATED
  21572. pathToRelative: function (pathArray) {
  21573. if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
  21574. pathArray = this.parsePathString(pathArray);
  21575. }
  21576. var res = [],
  21577. x = 0,
  21578. y = 0,
  21579. mx = 0,
  21580. my = 0,
  21581. start = 0;
  21582. if (pathArray[0][0] == "M") {
  21583. x = pathArray[0][1];
  21584. y = pathArray[0][2];
  21585. mx = x;
  21586. my = y;
  21587. start++;
  21588. res.push(["M", x, y]);
  21589. }
  21590. for (var i = start, ii = pathArray.length; i < ii; i++) {
  21591. var r = res[i] = [],
  21592. pa = pathArray[i];
  21593. if (pa[0] != pa[0].toLowerCase()) {
  21594. r[0] = pa[0].toLowerCase();
  21595. switch (r[0]) {
  21596. case "a":
  21597. r[1] = pa[1];
  21598. r[2] = pa[2];
  21599. r[3] = pa[3];
  21600. r[4] = pa[4];
  21601. r[5] = pa[5];
  21602. r[6] = +(pa[6] - x).toFixed(3);
  21603. r[7] = +(pa[7] - y).toFixed(3);
  21604. break;
  21605. case "v":
  21606. r[1] = +(pa[1] - y).toFixed(3);
  21607. break;
  21608. case "m":
  21609. mx = pa[1];
  21610. my = pa[2];
  21611. default:
  21612. for (var j = 1, jj = pa.length; j < jj; j++) {
  21613. r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
  21614. }
  21615. }
  21616. } else {
  21617. r = res[i] = [];
  21618. if (pa[0] == "m") {
  21619. mx = pa[1] + x;
  21620. my = pa[2] + y;
  21621. }
  21622. for (var k = 0, kk = pa.length; k < kk; k++) {
  21623. res[i][k] = pa[k];
  21624. }
  21625. }
  21626. var len = res[i].length;
  21627. switch (res[i][0]) {
  21628. case "z":
  21629. x = mx;
  21630. y = my;
  21631. break;
  21632. case "h":
  21633. x += +res[i][len - 1];
  21634. break;
  21635. case "v":
  21636. y += +res[i][len - 1];
  21637. break;
  21638. default:
  21639. x += +res[i][len - 2];
  21640. y += +res[i][len - 1];
  21641. }
  21642. }
  21643. res.toString = this.path2string;
  21644. return res;
  21645. },
  21646. // Returns a path converted to a set of curveto commands
  21647. path2curve: function (path) {
  21648. var me = this,
  21649. points = me.pathToAbsolute(path),
  21650. ln = points.length,
  21651. attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
  21652. i, seg, segLn, point;
  21653. for (i = 0; i < ln; i++) {
  21654. points[i] = me.command2curve(points[i], attrs);
  21655. if (points[i].length > 7) {
  21656. points[i].shift();
  21657. point = points[i];
  21658. while (point.length) {
  21659. Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
  21660. }
  21661. Ext.Array.erase(points, i, 1);
  21662. ln = points.length;
  21663. i--;
  21664. }
  21665. seg = points[i];
  21666. segLn = seg.length;
  21667. attrs.x = seg[segLn - 2];
  21668. attrs.y = seg[segLn - 1];
  21669. attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
  21670. attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
  21671. }
  21672. return points;
  21673. },
  21674. interpolatePaths: function (path, path2) {
  21675. var me = this,
  21676. p = me.pathToAbsolute(path),
  21677. p2 = me.pathToAbsolute(path2),
  21678. attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
  21679. attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
  21680. fixArc = function (pp, i) {
  21681. if (pp[i].length > 7) {
  21682. pp[i].shift();
  21683. var pi = pp[i];
  21684. while (pi.length) {
  21685. Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
  21686. }
  21687. Ext.Array.erase(pp, i, 1);
  21688. ii = Math.max(p.length, p2.length || 0);
  21689. }
  21690. },
  21691. fixM = function (path1, path2, a1, a2, i) {
  21692. if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
  21693. Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
  21694. a1.bx = 0;
  21695. a1.by = 0;
  21696. a1.x = path1[i][1];
  21697. a1.y = path1[i][2];
  21698. ii = Math.max(p.length, p2.length || 0);
  21699. }
  21700. };
  21701. for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
  21702. p[i] = me.command2curve(p[i], attrs);
  21703. fixArc(p, i);
  21704. (p2[i] = me.command2curve(p2[i], attrs2));
  21705. fixArc(p2, i);
  21706. fixM(p, p2, attrs, attrs2, i);
  21707. fixM(p2, p, attrs2, attrs, i);
  21708. var seg = p[i],
  21709. seg2 = p2[i],
  21710. seglen = seg.length,
  21711. seg2len = seg2.length;
  21712. attrs.x = seg[seglen - 2];
  21713. attrs.y = seg[seglen - 1];
  21714. attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
  21715. attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
  21716. attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
  21717. attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
  21718. attrs2.x = seg2[seg2len - 2];
  21719. attrs2.y = seg2[seg2len - 1];
  21720. }
  21721. return [p, p2];
  21722. },
  21723. //Returns any path command as a curveto command based on the attrs passed
  21724. command2curve: function (pathCommand, d) {
  21725. var me = this;
  21726. if (!pathCommand) {
  21727. return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
  21728. }
  21729. if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
  21730. d.qx = d.qy = null;
  21731. }
  21732. switch (pathCommand[0]) {
  21733. case "M":
  21734. d.X = pathCommand[1];
  21735. d.Y = pathCommand[2];
  21736. break;
  21737. case "A":
  21738. pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
  21739. break;
  21740. case "S":
  21741. pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
  21742. break;
  21743. case "T":
  21744. d.qx = d.x + (d.x - (d.qx || d.x));
  21745. d.qy = d.y + (d.y - (d.qy || d.y));
  21746. pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
  21747. break;
  21748. case "Q":
  21749. d.qx = pathCommand[1];
  21750. d.qy = pathCommand[2];
  21751. pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
  21752. break;
  21753. case "L":
  21754. pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
  21755. break;
  21756. case "H":
  21757. pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
  21758. break;
  21759. case "V":
  21760. pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
  21761. break;
  21762. case "Z":
  21763. pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
  21764. break;
  21765. }
  21766. return pathCommand;
  21767. },
  21768. quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
  21769. var _13 = 1 / 3,
  21770. _23 = 2 / 3;
  21771. return [
  21772. _13 * x1 + _23 * ax,
  21773. _13 * y1 + _23 * ay,
  21774. _13 * x2 + _23 * ax,
  21775. _13 * y2 + _23 * ay,
  21776. x2,
  21777. y2
  21778. ];
  21779. },
  21780. rotate: function (x, y, rad) {
  21781. var cos = Math.cos(rad),
  21782. sin = Math.sin(rad),
  21783. X = x * cos - y * sin,
  21784. Y = x * sin + y * cos;
  21785. return {x: X, y: Y};
  21786. },
  21787. arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
  21788. // for more information of where this Math came from visit:
  21789. // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  21790. var me = this,
  21791. PI = Math.PI,
  21792. radian = me.radian,
  21793. _120 = PI * 120 / 180,
  21794. rad = radian * (+angle || 0),
  21795. res = [],
  21796. math = Math,
  21797. mcos = math.cos,
  21798. msin = math.sin,
  21799. msqrt = math.sqrt,
  21800. mabs = math.abs,
  21801. masin = math.asin,
  21802. xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
  21803. t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
  21804. if (!recursive) {
  21805. xy = me.rotate(x1, y1, -rad);
  21806. x1 = xy.x;
  21807. y1 = xy.y;
  21808. xy = me.rotate(x2, y2, -rad);
  21809. x2 = xy.x;
  21810. y2 = xy.y;
  21811. cos = mcos(radian * angle);
  21812. sin = msin(radian * angle);
  21813. x = (x1 - x2) / 2;
  21814. y = (y1 - y2) / 2;
  21815. h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
  21816. if (h > 1) {
  21817. h = msqrt(h);
  21818. rx = h * rx;
  21819. ry = h * ry;
  21820. }
  21821. rx2 = rx * rx;
  21822. ry2 = ry * ry;
  21823. k = (large_arc_flag == sweep_flag ? -1 : 1) *
  21824. msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
  21825. cx = k * rx * y / ry + (x1 + x2) / 2;
  21826. cy = k * -ry * x / rx + (y1 + y2) / 2;
  21827. f1 = masin(((y1 - cy) / ry).toFixed(7));
  21828. f2 = masin(((y2 - cy) / ry).toFixed(7));
  21829. f1 = x1 < cx ? PI - f1 : f1;
  21830. f2 = x2 < cx ? PI - f2 : f2;
  21831. if (f1 < 0) {
  21832. f1 = PI * 2 + f1;
  21833. }
  21834. if (f2 < 0) {
  21835. f2 = PI * 2 + f2;
  21836. }
  21837. if (sweep_flag && f1 > f2) {
  21838. f1 = f1 - PI * 2;
  21839. }
  21840. if (!sweep_flag && f2 > f1) {
  21841. f2 = f2 - PI * 2;
  21842. }
  21843. }
  21844. else {
  21845. f1 = recursive[0];
  21846. f2 = recursive[1];
  21847. cx = recursive[2];
  21848. cy = recursive[3];
  21849. }
  21850. df = f2 - f1;
  21851. if (mabs(df) > _120) {
  21852. f2old = f2;
  21853. x2old = x2;
  21854. y2old = y2;
  21855. f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
  21856. x2 = cx + rx * mcos(f2);
  21857. y2 = cy + ry * msin(f2);
  21858. res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
  21859. }
  21860. df = f2 - f1;
  21861. c1 = mcos(f1);
  21862. s1 = msin(f1);
  21863. c2 = mcos(f2);
  21864. s2 = msin(f2);
  21865. t = math.tan(df / 4);
  21866. hx = 4 / 3 * rx * t;
  21867. hy = 4 / 3 * ry * t;
  21868. m1 = [x1, y1];
  21869. m2 = [x1 + hx * s1, y1 - hy * c1];
  21870. m3 = [x2 + hx * s2, y2 - hy * c2];
  21871. m4 = [x2, y2];
  21872. m2[0] = 2 * m1[0] - m2[0];
  21873. m2[1] = 2 * m1[1] - m2[1];
  21874. if (recursive) {
  21875. return [m2, m3, m4].concat(res);
  21876. }
  21877. else {
  21878. res = [m2, m3, m4].concat(res).join().split(",");
  21879. newres = [];
  21880. ln = res.length;
  21881. for (i = 0; i < ln; i++) {
  21882. newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
  21883. }
  21884. return newres;
  21885. }
  21886. },
  21887. // TO BE DEPRECATED
  21888. rotateAndTranslatePath: function (sprite) {
  21889. var alpha = sprite.rotation.degrees,
  21890. cx = sprite.rotation.x,
  21891. cy = sprite.rotation.y,
  21892. dx = sprite.translation.x,
  21893. dy = sprite.translation.y,
  21894. path,
  21895. i,
  21896. p,
  21897. xy,
  21898. j,
  21899. res = [];
  21900. if (!alpha && !dx && !dy) {
  21901. return this.pathToAbsolute(sprite.attr.path);
  21902. }
  21903. dx = dx || 0;
  21904. dy = dy || 0;
  21905. path = this.pathToAbsolute(sprite.attr.path);
  21906. for (i = path.length; i--;) {
  21907. p = res[i] = path[i].slice();
  21908. if (p[0] == "A") {
  21909. xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
  21910. p[6] = xy.x + dx;
  21911. p[7] = xy.y + dy;
  21912. } else {
  21913. j = 1;
  21914. while (p[j + 1] != null) {
  21915. xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
  21916. p[j] = xy.x + dx;
  21917. p[j + 1] = xy.y + dy;
  21918. j += 2;
  21919. }
  21920. }
  21921. }
  21922. return res;
  21923. },
  21924. // TO BE DEPRECATED
  21925. rotatePoint: function (x, y, alpha, cx, cy) {
  21926. if (!alpha) {
  21927. return {
  21928. x: x,
  21929. y: y
  21930. };
  21931. }
  21932. cx = cx || 0;
  21933. cy = cy || 0;
  21934. x = x - cx;
  21935. y = y - cy;
  21936. alpha = alpha * this.radian;
  21937. var cos = Math.cos(alpha),
  21938. sin = Math.sin(alpha);
  21939. return {
  21940. x: x * cos - y * sin + cx,
  21941. y: x * sin + y * cos + cy
  21942. };
  21943. },
  21944. pathDimensions: function (path) {
  21945. if (!path || !(path + "")) {
  21946. return {x: 0, y: 0, width: 0, height: 0};
  21947. }
  21948. path = this.path2curve(path);
  21949. var x = 0,
  21950. y = 0,
  21951. X = [],
  21952. Y = [],
  21953. i = 0,
  21954. ln = path.length,
  21955. p, xmin, ymin, dim;
  21956. for (; i < ln; i++) {
  21957. p = path[i];
  21958. if (p[0] == "M") {
  21959. x = p[1];
  21960. y = p[2];
  21961. X.push(x);
  21962. Y.push(y);
  21963. }
  21964. else {
  21965. dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
  21966. X = X.concat(dim.min.x, dim.max.x);
  21967. Y = Y.concat(dim.min.y, dim.max.y);
  21968. x = p[5];
  21969. y = p[6];
  21970. }
  21971. }
  21972. xmin = Math.min.apply(0, X);
  21973. ymin = Math.min.apply(0, Y);
  21974. return {
  21975. x: xmin,
  21976. y: ymin,
  21977. path: path,
  21978. width: Math.max.apply(0, X) - xmin,
  21979. height: Math.max.apply(0, Y) - ymin
  21980. };
  21981. },
  21982. intersectInside: function(path, cp1, cp2) {
  21983. return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
  21984. },
  21985. intersectIntersection: function(s, e, cp1, cp2) {
  21986. var p = [],
  21987. dcx = cp1[0] - cp2[0],
  21988. dcy = cp1[1] - cp2[1],
  21989. dpx = s[0] - e[0],
  21990. dpy = s[1] - e[1],
  21991. n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
  21992. n2 = s[0] * e[1] - s[1] * e[0],
  21993. n3 = 1 / (dcx * dpy - dcy * dpx);
  21994. p[0] = (n1 * dpx - n2 * dcx) * n3;
  21995. p[1] = (n1 * dpy - n2 * dcy) * n3;
  21996. return p;
  21997. },
  21998. intersect: function(subjectPolygon, clipPolygon) {
  21999. var me = this,
  22000. i = 0,
  22001. ln = clipPolygon.length,
  22002. cp1 = clipPolygon[ln - 1],
  22003. outputList = subjectPolygon,
  22004. cp2, s, e, point, ln2, inputList, j;
  22005. for (; i < ln; ++i) {
  22006. cp2 = clipPolygon[i];
  22007. inputList = outputList;
  22008. outputList = [];
  22009. s = inputList[inputList.length - 1];
  22010. j = 0;
  22011. ln2 = inputList.length;
  22012. for (; j < ln2; j++) {
  22013. e = inputList[j];
  22014. if (me.intersectInside(e, cp1, cp2)) {
  22015. if (!me.intersectInside(s, cp1, cp2)) {
  22016. outputList.push(me.intersectIntersection(s, e, cp1, cp2));
  22017. }
  22018. outputList.push(e);
  22019. }
  22020. else if (me.intersectInside(s, cp1, cp2)) {
  22021. outputList.push(me.intersectIntersection(s, e, cp1, cp2));
  22022. }
  22023. s = e;
  22024. }
  22025. cp1 = cp2;
  22026. }
  22027. return outputList;
  22028. },
  22029. bezier : function (a, b, c, d, x) {
  22030. if (x === 0) {
  22031. return a;
  22032. }
  22033. else if (x === 1) {
  22034. return d;
  22035. }
  22036. var du = 1 - x,
  22037. d3 = du * du * du,
  22038. r = x / du;
  22039. return d3 * (a + r * (3 * b + r * (3 * c + d * r)));
  22040. },
  22041. bezierDim : function (a, b, c, d) {
  22042. var points = [], r;
  22043. // The min and max happens on boundary or b' == 0
  22044. if (a + 3 * c == d + 3 * b) {
  22045. r = a - b;
  22046. r /= 2 * (a - b - b + c);
  22047. if ( r < 1 && r > 0) {
  22048. points.push(r);
  22049. }
  22050. } else {
  22051. // b'(x) / -3 = (a-3b+3c-d)x^2+ (-2a+4b-2c)x + (a-b)
  22052. // delta = -4 (-b^2+a c+b c-c^2-a d+b d)
  22053. var A = a - 3 * b + 3 * c - d,
  22054. top = 2 * (a - b - b + c),
  22055. C = a - b,
  22056. delta = top * top - 4 * A * C,
  22057. bottom = A + A,
  22058. s;
  22059. if (delta === 0) {
  22060. r = top / bottom;
  22061. if (r < 1 && r > 0) {
  22062. points.push(r);
  22063. }
  22064. } else if (delta > 0) {
  22065. s = Math.sqrt(delta);
  22066. r = (s + top) / bottom;
  22067. if (r < 1 && r > 0) {
  22068. points.push(r);
  22069. }
  22070. r = (top - s) / bottom;
  22071. if (r < 1 && r > 0) {
  22072. points.push(r);
  22073. }
  22074. }
  22075. }
  22076. var min = Math.min(a, d), max = Math.max(a, d);
  22077. for (var i = 0; i < points.length; i++) {
  22078. min = Math.min(min, this.bezier(a, b, c, d, points[i]));
  22079. max = Math.max(max, this.bezier(a, b, c, d, points[i]));
  22080. }
  22081. return [min, max];
  22082. },
  22083. curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
  22084. var x = this.bezierDim(p1x, c1x, c2x, p2x),
  22085. y = this.bezierDim(p1y, c1y, c2y, p2y);
  22086. return {
  22087. min: {
  22088. x: x[0],
  22089. y: y[0]
  22090. },
  22091. max: {
  22092. x: x[1],
  22093. y: y[1]
  22094. }
  22095. };
  22096. },
  22097. /**
  22098. * @private
  22099. *
  22100. * Calculates bezier curve control anchor points for a particular point in a path, with a
  22101. * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
  22102. * Note that this algorithm assumes that the line being smoothed is normalized going from left
  22103. * to right; it makes special adjustments assuming this orientation.
  22104. *
  22105. * @param {Number} prevX X coordinate of the previous point in the path
  22106. * @param {Number} prevY Y coordinate of the previous point in the path
  22107. * @param {Number} curX X coordinate of the current point in the path
  22108. * @param {Number} curY Y coordinate of the current point in the path
  22109. * @param {Number} nextX X coordinate of the next point in the path
  22110. * @param {Number} nextY Y coordinate of the next point in the path
  22111. * @param {Number} value A value to control the smoothness of the curve; this is used to
  22112. * divide the distance between points, so a value of 2 corresponds to
  22113. * half the distance between points (a very smooth line) while higher values
  22114. * result in less smooth curves. Defaults to 4.
  22115. * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
  22116. * are the control point for the curve toward the previous path point, and
  22117. * x2 and y2 are the control point for the curve toward the next path point.
  22118. */
  22119. getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
  22120. value = value || 4;
  22121. var M = Math,
  22122. PI = M.PI,
  22123. halfPI = PI / 2,
  22124. abs = M.abs,
  22125. sin = M.sin,
  22126. cos = M.cos,
  22127. atan = M.atan,
  22128. control1Length, control2Length, control1Angle, control2Angle,
  22129. control1X, control1Y, control2X, control2Y, alpha;
  22130. // Find the length of each control anchor line, by dividing the horizontal distance
  22131. // between points by the value parameter.
  22132. control1Length = (curX - prevX) / value;
  22133. control2Length = (nextX - curX) / value;
  22134. // Determine the angle of each control anchor line. If the middle point is a vertical
  22135. // turnaround then we force it to a flat horizontal angle to prevent the curve from
  22136. // dipping above or below the middle point. Otherwise we use an angle that points
  22137. // toward the previous/next target point.
  22138. if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
  22139. control1Angle = control2Angle = halfPI;
  22140. } else {
  22141. control1Angle = atan((curX - prevX) / abs(curY - prevY));
  22142. if (prevY < curY) {
  22143. control1Angle = PI - control1Angle;
  22144. }
  22145. control2Angle = atan((nextX - curX) / abs(curY - nextY));
  22146. if (nextY < curY) {
  22147. control2Angle = PI - control2Angle;
  22148. }
  22149. }
  22150. // Adjust the calculated angles so they point away from each other on the same line
  22151. alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
  22152. if (alpha > halfPI) {
  22153. alpha -= PI;
  22154. }
  22155. control1Angle += alpha;
  22156. control2Angle += alpha;
  22157. // Find the control anchor points from the angles and length
  22158. control1X = curX - control1Length * sin(control1Angle);
  22159. control1Y = curY + control1Length * cos(control1Angle);
  22160. control2X = curX + control2Length * sin(control2Angle);
  22161. control2Y = curY + control2Length * cos(control2Angle);
  22162. // One last adjustment, make sure that no control anchor point extends vertically past
  22163. // its target prev/next point, as that results in curves dipping above or below and
  22164. // bending back strangely. If we find this happening we keep the control angle but
  22165. // reduce the length of the control line so it stays within bounds.
  22166. if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
  22167. control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
  22168. control1Y = prevY;
  22169. }
  22170. if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
  22171. control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
  22172. control2Y = nextY;
  22173. }
  22174. return {
  22175. x1: control1X,
  22176. y1: control1Y,
  22177. x2: control2X,
  22178. y2: control2Y
  22179. };
  22180. },
  22181. /* Smoothing function for a path. Converts a path into cubic beziers. Value defines the divider of the distance between points.
  22182. * Defaults to a value of 4.
  22183. */
  22184. smooth: function (originalPath, value) {
  22185. var path = this.path2curve(originalPath),
  22186. newp = [path[0]],
  22187. x = path[0][1],
  22188. y = path[0][2],
  22189. j,
  22190. points,
  22191. i = 1,
  22192. ii = path.length,
  22193. beg = 1,
  22194. mx = x,
  22195. my = y,
  22196. cx = 0,
  22197. cy = 0;
  22198. for (; i < ii; i++) {
  22199. var pathi = path[i],
  22200. pathil = pathi.length,
  22201. pathim = path[i - 1],
  22202. pathiml = pathim.length,
  22203. pathip = path[i + 1],
  22204. pathipl = pathip && pathip.length;
  22205. if (pathi[0] == "M") {
  22206. mx = pathi[1];
  22207. my = pathi[2];
  22208. j = i + 1;
  22209. while (path[j][0] != "C") {
  22210. j++;
  22211. }
  22212. cx = path[j][5];
  22213. cy = path[j][6];
  22214. newp.push(["M", mx, my]);
  22215. beg = newp.length;
  22216. x = mx;
  22217. y = my;
  22218. continue;
  22219. }
  22220. if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
  22221. var begl = newp[beg].length;
  22222. points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
  22223. newp[beg][1] = points.x2;
  22224. newp[beg][2] = points.y2;
  22225. }
  22226. else if (!pathip || pathip[0] == "M") {
  22227. points = {
  22228. x1: pathi[pathil - 2],
  22229. y1: pathi[pathil - 1]
  22230. };
  22231. } else {
  22232. points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
  22233. }
  22234. newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
  22235. x = points.x2;
  22236. y = points.y2;
  22237. }
  22238. return newp;
  22239. },
  22240. findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
  22241. var t1 = 1 - t;
  22242. return {
  22243. x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
  22244. y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
  22245. };
  22246. },
  22247. snapEnds: function (from, to, stepsMax, prettyNumbers) {
  22248. if (Ext.isDate(from)) {
  22249. return this.snapEndsByDate(from, to, stepsMax);
  22250. }
  22251. var step = (to - from) / stepsMax,
  22252. level = Math.floor(Math.log(step) / Math.LN10) + 1,
  22253. m = Math.pow(10, level),
  22254. cur,
  22255. modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
  22256. interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
  22257. stepCount = 0,
  22258. value,
  22259. weight,
  22260. i,
  22261. topValue,
  22262. topWeight = 1e9,
  22263. ln = interval.length;
  22264. cur = from = Math.floor(from / m) * m;
  22265. if(prettyNumbers){
  22266. for (i = 0; i < ln; i++) {
  22267. value = interval[i][0];
  22268. weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
  22269. if (weight < topWeight) {
  22270. topValue = value;
  22271. topWeight = weight;
  22272. }
  22273. }
  22274. step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
  22275. while (cur < to) {
  22276. cur += step;
  22277. stepCount++;
  22278. }
  22279. to = +cur.toFixed(10);
  22280. }else{
  22281. stepCount = stepsMax;
  22282. }
  22283. return {
  22284. from: from,
  22285. to: to,
  22286. power: level,
  22287. step: step,
  22288. steps: stepCount
  22289. };
  22290. },
  22291. /**
  22292. * snapEndsByDate is a utility method to deduce an appropriate tick configuration for the data set of given
  22293. * feature. Refer to {@link #snapEnds}.
  22294. *
  22295. * @param {Date} from The minimum value in the data
  22296. * @param {Date} to The maximum value in the data
  22297. * @param {Number} stepsMax The maximum number of ticks
  22298. * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
  22299. * and will not be adjusted
  22300. * @return {Object} The calculated step and ends info; properties are:
  22301. * - from: The result start value, which may be lower than the original start value
  22302. * - to: The result end value, which may be higher than the original end value
  22303. * - step: The value size of each step
  22304. * - steps: The number of steps. NOTE: the steps may not divide the from/to range perfectly evenly;
  22305. * there may be a smaller distance between the last step and the end value than between prior
  22306. * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
  22307. * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
  22308. * `from` to find the correct point for each tick.
  22309. */
  22310. snapEndsByDate: function (from, to, stepsMax, lockEnds) {
  22311. var selectedStep = false,
  22312. scales = [
  22313. [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
  22314. [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
  22315. [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
  22316. [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
  22317. [Ext.Date.DAY, [1, 2, 3, 7, 14]],
  22318. [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
  22319. ],
  22320. sLen = scales.length,
  22321. stop = false,
  22322. scale, j, yearDiff, s;
  22323. // Find the most desirable scale
  22324. for (s = 0; s < sLen; s++) {
  22325. scale = scales[s];
  22326. if (!stop) {
  22327. for (j = 0; j < scale[1].length; j++) {
  22328. if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
  22329. selectedStep = [scale[0], scale[1][j]];
  22330. stop = true;
  22331. break;
  22332. }
  22333. }
  22334. }
  22335. }
  22336. if (!selectedStep) {
  22337. yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
  22338. selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
  22339. }
  22340. return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
  22341. },
  22342. /**
  22343. * snapEndsByDateAndStep is a utility method to deduce an appropriate tick configuration for the data set of given
  22344. * feature and specific step size.
  22345. * @param {Date} from The minimum value in the data
  22346. * @param {Date} to The maximum value in the data
  22347. * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc). The second one is the number of units for the step (1, 2, etc.).
  22348. * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
  22349. * and will not be adjusted
  22350. */
  22351. snapEndsByDateAndStep: function(from, to, step, lockEnds) {
  22352. var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
  22353. from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
  22354. steps = 0, testFrom, testTo;
  22355. if (lockEnds) {
  22356. testFrom = from;
  22357. } else {
  22358. switch (step[0]) {
  22359. case Ext.Date.MILLI:
  22360. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
  22361. fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
  22362. break;
  22363. case Ext.Date.SECOND:
  22364. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
  22365. fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
  22366. break;
  22367. case Ext.Date.MINUTE:
  22368. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
  22369. Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
  22370. break;
  22371. case Ext.Date.HOUR:
  22372. testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
  22373. Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
  22374. break;
  22375. case Ext.Date.DAY:
  22376. testFrom = new Date(fromStat[0], fromStat[1],
  22377. Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
  22378. break;
  22379. case Ext.Date.MONTH:
  22380. testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
  22381. break;
  22382. default: // Ext.Date.YEAR
  22383. testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
  22384. break;
  22385. }
  22386. }
  22387. testTo = testFrom;
  22388. // TODO(zhangbei) : We can do it better somehow...
  22389. while (testTo < to) {
  22390. testTo = Ext.Date.add(testTo, step[0], step[1]);
  22391. steps++;
  22392. }
  22393. if (lockEnds) {
  22394. testTo = to;
  22395. }
  22396. return {
  22397. from : +testFrom,
  22398. to : +testTo,
  22399. step : (testTo - testFrom) / steps,
  22400. steps : steps
  22401. };
  22402. },
  22403. sorter: function (a, b) {
  22404. return a.offset - b.offset;
  22405. },
  22406. rad: function(degrees) {
  22407. return degrees % 360 * Math.PI / 180;
  22408. },
  22409. degrees: function(radian) {
  22410. return radian * 180 / Math.PI % 360;
  22411. },
  22412. withinBox: function(x, y, bbox) {
  22413. bbox = bbox || {};
  22414. return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
  22415. },
  22416. parseGradient: function(gradient) {
  22417. var me = this,
  22418. type = gradient.type || 'linear',
  22419. angle = gradient.angle || 0,
  22420. radian = me.radian,
  22421. stops = gradient.stops,
  22422. stopsArr = [],
  22423. stop,
  22424. vector,
  22425. max,
  22426. stopObj;
  22427. if (type == 'linear') {
  22428. vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
  22429. max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
  22430. vector[2] *= max;
  22431. vector[3] *= max;
  22432. if (vector[2] < 0) {
  22433. vector[0] = -vector[2];
  22434. vector[2] = 0;
  22435. }
  22436. if (vector[3] < 0) {
  22437. vector[1] = -vector[3];
  22438. vector[3] = 0;
  22439. }
  22440. }
  22441. for (stop in stops) {
  22442. if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
  22443. stopObj = {
  22444. offset: parseInt(stop, 10),
  22445. color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
  22446. opacity: stops[stop].opacity || 1
  22447. };
  22448. stopsArr.push(stopObj);
  22449. }
  22450. }
  22451. // Sort by pct property
  22452. Ext.Array.sort(stopsArr, me.sorter);
  22453. if (type == 'linear') {
  22454. return {
  22455. id: gradient.id,
  22456. type: type,
  22457. vector: vector,
  22458. stops: stopsArr
  22459. };
  22460. }
  22461. else {
  22462. return {
  22463. id: gradient.id,
  22464. type: type,
  22465. centerX: gradient.centerX,
  22466. centerY: gradient.centerY,
  22467. focalX: gradient.focalX,
  22468. focalY: gradient.focalY,
  22469. radius: gradient.radius,
  22470. vector: vector,
  22471. stops: stopsArr
  22472. };
  22473. }
  22474. }
  22475. });
  22476. /**
  22477. * @class Ext.fx.PropertyHandler
  22478. * @ignore
  22479. */
  22480. Ext.define('Ext.fx.PropertyHandler', {
  22481. /* Begin Definitions */
  22482. requires: ['Ext.draw.Draw'],
  22483. statics: {
  22484. defaultHandler: {
  22485. pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
  22486. unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
  22487. scrollRE: /^scroll/i,
  22488. computeDelta: function(from, end, damper, initial, attr) {
  22489. damper = (typeof damper == 'number') ? damper : 1;
  22490. var unitRE = this.unitRE,
  22491. match = unitRE.exec(from),
  22492. start, units;
  22493. if (match) {
  22494. from = match[1];
  22495. units = match[2];
  22496. if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
  22497. units = 'px';
  22498. }
  22499. }
  22500. from = +from || 0;
  22501. match = unitRE.exec(end);
  22502. if (match) {
  22503. end = match[1];
  22504. units = match[2] || units;
  22505. }
  22506. end = +end || 0;
  22507. start = (initial != null) ? initial : from;
  22508. return {
  22509. from: from,
  22510. delta: (end - start) * damper,
  22511. units: units
  22512. };
  22513. },
  22514. get: function(from, end, damper, initialFrom, attr) {
  22515. var ln = from.length,
  22516. out = [],
  22517. i, initial, res, j, len;
  22518. for (i = 0; i < ln; i++) {
  22519. if (initialFrom) {
  22520. initial = initialFrom[i][1].from;
  22521. }
  22522. if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
  22523. res = [];
  22524. j = 0;
  22525. len = from[i][1].length;
  22526. for (; j < len; j++) {
  22527. res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
  22528. }
  22529. out.push([from[i][0], res]);
  22530. }
  22531. else {
  22532. out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
  22533. }
  22534. }
  22535. return out;
  22536. },
  22537. set: function(values, easing) {
  22538. var ln = values.length,
  22539. out = [],
  22540. i, val, res, len, j;
  22541. for (i = 0; i < ln; i++) {
  22542. val = values[i][1];
  22543. if (Ext.isArray(val)) {
  22544. res = [];
  22545. j = 0;
  22546. len = val.length;
  22547. for (; j < len; j++) {
  22548. res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
  22549. }
  22550. out.push([values[i][0], res]);
  22551. } else {
  22552. out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
  22553. }
  22554. }
  22555. return out;
  22556. }
  22557. },
  22558. color: {
  22559. rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
  22560. hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
  22561. hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
  22562. parseColor : function(color, damper) {
  22563. damper = (typeof damper == 'number') ? damper : 1;
  22564. var out = false,
  22565. reList = [this.hexRE, this.rgbRE, this.hex3RE],
  22566. length = reList.length,
  22567. match, base, re, i;
  22568. for (i = 0; i < length; i++) {
  22569. re = reList[i];
  22570. base = (i % 2 === 0) ? 16 : 10;
  22571. match = re.exec(color);
  22572. if (match && match.length === 4) {
  22573. if (i === 2) {
  22574. match[1] += match[1];
  22575. match[2] += match[2];
  22576. match[3] += match[3];
  22577. }
  22578. out = {
  22579. red: parseInt(match[1], base),
  22580. green: parseInt(match[2], base),
  22581. blue: parseInt(match[3], base)
  22582. };
  22583. break;
  22584. }
  22585. }
  22586. return out || color;
  22587. },
  22588. computeDelta: function(from, end, damper, initial) {
  22589. from = this.parseColor(from);
  22590. end = this.parseColor(end, damper);
  22591. var start = initial ? initial : from,
  22592. tfrom = typeof start,
  22593. tend = typeof end;
  22594. //Extra check for when the color string is not recognized.
  22595. if (tfrom == 'string' || tfrom == 'undefined'
  22596. || tend == 'string' || tend == 'undefined') {
  22597. return end || start;
  22598. }
  22599. return {
  22600. from: from,
  22601. delta: {
  22602. red: Math.round((end.red - start.red) * damper),
  22603. green: Math.round((end.green - start.green) * damper),
  22604. blue: Math.round((end.blue - start.blue) * damper)
  22605. }
  22606. };
  22607. },
  22608. get: function(start, end, damper, initialFrom) {
  22609. var ln = start.length,
  22610. out = [],
  22611. i, initial;
  22612. for (i = 0; i < ln; i++) {
  22613. if (initialFrom) {
  22614. initial = initialFrom[i][1].from;
  22615. }
  22616. out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
  22617. }
  22618. return out;
  22619. },
  22620. set: function(values, easing) {
  22621. var ln = values.length,
  22622. out = [],
  22623. i, val, parsedString, from, delta;
  22624. for (i = 0; i < ln; i++) {
  22625. val = values[i][1];
  22626. if (val) {
  22627. from = val.from;
  22628. delta = val.delta;
  22629. //multiple checks to reformat the color if it can't recognized by computeDelta.
  22630. val = (typeof val == 'object' && 'red' in val)?
  22631. 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
  22632. val = (typeof val == 'object' && val.length)? val[0] : val;
  22633. if (typeof val == 'undefined') {
  22634. return [];
  22635. }
  22636. parsedString = typeof val == 'string'? val :
  22637. 'rgb(' + [
  22638. (from.red + Math.round(delta.red * easing)) % 256,
  22639. (from.green + Math.round(delta.green * easing)) % 256,
  22640. (from.blue + Math.round(delta.blue * easing)) % 256
  22641. ].join(',') + ')';
  22642. out.push([
  22643. values[i][0],
  22644. parsedString
  22645. ]);
  22646. }
  22647. }
  22648. return out;
  22649. }
  22650. },
  22651. object: {
  22652. interpolate: function(prop, damper) {
  22653. damper = (typeof damper == 'number') ? damper : 1;
  22654. var out = {},
  22655. p;
  22656. for(p in prop) {
  22657. out[p] = parseInt(prop[p], 10) * damper;
  22658. }
  22659. return out;
  22660. },
  22661. computeDelta: function(from, end, damper, initial) {
  22662. from = this.interpolate(from);
  22663. end = this.interpolate(end, damper);
  22664. var start = initial ? initial : from,
  22665. delta = {},
  22666. p;
  22667. for(p in end) {
  22668. delta[p] = end[p] - start[p];
  22669. }
  22670. return {
  22671. from: from,
  22672. delta: delta
  22673. };
  22674. },
  22675. get: function(start, end, damper, initialFrom) {
  22676. var ln = start.length,
  22677. out = [],
  22678. i, initial;
  22679. for (i = 0; i < ln; i++) {
  22680. if (initialFrom) {
  22681. initial = initialFrom[i][1].from;
  22682. }
  22683. out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
  22684. }
  22685. return out;
  22686. },
  22687. set: function(values, easing) {
  22688. var ln = values.length,
  22689. out = [],
  22690. outObject = {},
  22691. i, from, delta, val, p;
  22692. for (i = 0; i < ln; i++) {
  22693. val = values[i][1];
  22694. from = val.from;
  22695. delta = val.delta;
  22696. for (p in from) {
  22697. outObject[p] = Math.round(from[p] + delta[p] * easing);
  22698. }
  22699. out.push([
  22700. values[i][0],
  22701. outObject
  22702. ]);
  22703. }
  22704. return out;
  22705. }
  22706. },
  22707. path: {
  22708. computeDelta: function(from, end, damper, initial) {
  22709. damper = (typeof damper == 'number') ? damper : 1;
  22710. var start;
  22711. from = +from || 0;
  22712. end = +end || 0;
  22713. start = (initial != null) ? initial : from;
  22714. return {
  22715. from: from,
  22716. delta: (end - start) * damper
  22717. };
  22718. },
  22719. forcePath: function(path) {
  22720. if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
  22721. path = Ext.draw.Draw.parsePathString(path);
  22722. }
  22723. return path;
  22724. },
  22725. get: function(start, end, damper, initialFrom) {
  22726. var endPath = this.forcePath(end),
  22727. out = [],
  22728. startLn = start.length,
  22729. startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
  22730. for (i = 0; i < startLn; i++) {
  22731. startPath = this.forcePath(start[i][1]);
  22732. deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
  22733. startPath = deltaPath[0];
  22734. endPath = deltaPath[1];
  22735. startPathLn = startPath.length;
  22736. path = [];
  22737. for (j = 0; j < startPathLn; j++) {
  22738. deltaPath = [startPath[j][0]];
  22739. pointsLn = startPath[j].length;
  22740. for (k = 1; k < pointsLn; k++) {
  22741. initial = initialFrom && initialFrom[0][1][j][k].from;
  22742. deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
  22743. }
  22744. path.push(deltaPath);
  22745. }
  22746. out.push([start[i][0], path]);
  22747. }
  22748. return out;
  22749. },
  22750. set: function(values, easing) {
  22751. var ln = values.length,
  22752. out = [],
  22753. i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
  22754. for (i = 0; i < ln; i++) {
  22755. deltaPath = values[i][1];
  22756. newPath = [];
  22757. deltaPathLn = deltaPath.length;
  22758. for (j = 0; j < deltaPathLn; j++) {
  22759. calcPath = [deltaPath[j][0]];
  22760. pointsLn = deltaPath[j].length;
  22761. for (k = 1; k < pointsLn; k++) {
  22762. calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
  22763. }
  22764. newPath.push(calcPath.join(','));
  22765. }
  22766. out.push([values[i][0], newPath.join(',')]);
  22767. }
  22768. return out;
  22769. }
  22770. }
  22771. /* End Definitions */
  22772. }
  22773. }, function() {
  22774. var props = [
  22775. 'outlineColor',
  22776. 'backgroundColor',
  22777. 'borderColor',
  22778. 'borderTopColor',
  22779. 'borderRightColor',
  22780. 'borderBottomColor',
  22781. 'borderLeftColor',
  22782. 'fill',
  22783. 'stroke'
  22784. ],
  22785. length = props.length,
  22786. i = 0,
  22787. prop;
  22788. for (; i<length; i++) {
  22789. prop = props[i];
  22790. this[prop] = this.color;
  22791. }
  22792. });
  22793. /**
  22794. * @class Ext.fx.Anim
  22795. *
  22796. * This class manages animation for a specific {@link #target}. The animation allows
  22797. * animation of various properties on the target, such as size, position, color and others.
  22798. *
  22799. * ## Starting Conditions
  22800. * The starting conditions for the animation are provided by the {@link #from} configuration.
  22801. * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
  22802. * property is not defined, the starting value for that property will be read directly from the target.
  22803. *
  22804. * ## End Conditions
  22805. * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
  22806. * the final values once the animations has finished. The values in the {@link #from} can mirror
  22807. * those in the {@link #to} configuration to provide a starting point.
  22808. *
  22809. * ## Other Options
  22810. * - {@link #duration}: Specifies the time period of the animation.
  22811. * - {@link #easing}: Specifies the easing of the animation.
  22812. * - {@link #iterations}: Allows the animation to repeat a number of times.
  22813. * - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
  22814. *
  22815. * ## Example Code
  22816. *
  22817. * @example
  22818. * var myComponent = Ext.create('Ext.Component', {
  22819. * renderTo: document.body,
  22820. * width: 200,
  22821. * height: 200,
  22822. * style: 'border: 1px solid red;'
  22823. * });
  22824. *
  22825. * Ext.create('Ext.fx.Anim', {
  22826. * target: myComponent,
  22827. * duration: 1000,
  22828. * from: {
  22829. * width: 400 //starting width 400
  22830. * },
  22831. * to: {
  22832. * width: 300, //end width 300
  22833. * height: 300 // end width 300
  22834. * }
  22835. * });
  22836. */
  22837. Ext.define('Ext.fx.Anim', {
  22838. /* Begin Definitions */
  22839. mixins: {
  22840. observable: 'Ext.util.Observable'
  22841. },
  22842. requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
  22843. /* End Definitions */
  22844. /**
  22845. * @property {Boolean} isAnimation
  22846. * `true` in this class to identify an objact as an instantiated Anim, or subclass thereof.
  22847. */
  22848. isAnimation: true,
  22849. /**
  22850. * @cfg {Function} callback
  22851. * A function to be run after the animation has completed.
  22852. */
  22853. /**
  22854. * @cfg {Function} scope
  22855. * The scope that the {@link #callback} function will be called with
  22856. */
  22857. /**
  22858. * @cfg {Number} duration
  22859. * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
  22860. * specified, then each animate will take the same duration for each iteration.
  22861. */
  22862. duration: 250,
  22863. /**
  22864. * @cfg {Number} delay
  22865. * Time to delay before starting the animation. Defaults to 0.
  22866. */
  22867. delay: 0,
  22868. /* private used to track a delayed starting time */
  22869. delayStart: 0,
  22870. /**
  22871. * @cfg {Boolean} dynamic
  22872. * Currently only for Component Animation: Only set a component's outer element size bypassing layouts. Set to true to do full layouts for every frame of the animation. Defaults to false.
  22873. */
  22874. dynamic: false,
  22875. /**
  22876. * @cfg {String} easing
  22877. This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
  22878. speed over its duration.
  22879. -backIn
  22880. -backOut
  22881. -bounceIn
  22882. -bounceOut
  22883. -ease
  22884. -easeIn
  22885. -easeOut
  22886. -easeInOut
  22887. -elasticIn
  22888. -elasticOut
  22889. -cubic-bezier(x1, y1, x2, y2)
  22890. Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
  22891. specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
  22892. be in the range [0, 1] or the definition is invalid.
  22893. [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
  22894. * @markdown
  22895. */
  22896. easing: 'ease',
  22897. /**
  22898. * @cfg {Object} keyframes
  22899. * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
  22900. * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
  22901. * "from" or "to"</b>. A keyframe declaration without these keyframe selectors is invalid and will not be available for
  22902. * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
  22903. * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
  22904. <pre><code>
  22905. keyframes : {
  22906. '0%': {
  22907. left: 100
  22908. },
  22909. '40%': {
  22910. left: 150
  22911. },
  22912. '60%': {
  22913. left: 75
  22914. },
  22915. '100%': {
  22916. left: 100
  22917. }
  22918. }
  22919. </code></pre>
  22920. */
  22921. /**
  22922. * @private
  22923. */
  22924. damper: 1,
  22925. /**
  22926. * @private
  22927. */
  22928. bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
  22929. /**
  22930. * Run the animation from the end to the beginning
  22931. * Defaults to false.
  22932. * @cfg {Boolean} reverse
  22933. */
  22934. reverse: false,
  22935. /**
  22936. * Flag to determine if the animation has started
  22937. * @property running
  22938. * @type Boolean
  22939. */
  22940. running: false,
  22941. /**
  22942. * Flag to determine if the animation is paused. Only set this to true if you need to
  22943. * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
  22944. * @property paused
  22945. * @type Boolean
  22946. */
  22947. paused: false,
  22948. /**
  22949. * Number of times to execute the animation. Defaults to 1.
  22950. * @cfg {Number} iterations
  22951. */
  22952. iterations: 1,
  22953. /**
  22954. * Used in conjunction with iterations to reverse the animation each time an iteration completes.
  22955. * @cfg {Boolean} alternate
  22956. * Defaults to false.
  22957. */
  22958. alternate: false,
  22959. /**
  22960. * Current iteration the animation is running.
  22961. * @property currentIteration
  22962. * @type Number
  22963. */
  22964. currentIteration: 0,
  22965. /**
  22966. * Starting time of the animation.
  22967. * @property startTime
  22968. * @type Date
  22969. */
  22970. startTime: 0,
  22971. /**
  22972. * Contains a cache of the interpolators to be used.
  22973. * @private
  22974. * @property propHandlers
  22975. * @type Object
  22976. */
  22977. /**
  22978. * @cfg {String/Object} target
  22979. * The {@link Ext.fx.target.Target} to apply the animation to. This should only be specified when creating an Ext.fx.Anim directly.
  22980. * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
  22981. * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
  22982. * automatically.
  22983. */
  22984. /**
  22985. * @cfg {Object} from
  22986. * An object containing property/value pairs for the beginning of the animation. If not specified, the current state of the
  22987. * Ext.fx.target will be used. For example:
  22988. <pre><code>
  22989. from : {
  22990. opacity: 0, // Transparent
  22991. color: '#ffffff', // White
  22992. left: 0
  22993. }
  22994. </code></pre>
  22995. */
  22996. /**
  22997. * @cfg {Object} to
  22998. * An object containing property/value pairs for the end of the animation. For example:
  22999. <pre><code>
  23000. to : {
  23001. opacity: 1, // Opaque
  23002. color: '#00ff00', // Green
  23003. left: 500
  23004. }
  23005. </code></pre>
  23006. */
  23007. // private
  23008. frameCount: 0,
  23009. // @private
  23010. constructor: function(config) {
  23011. var me = this,
  23012. curve;
  23013. config = config || {};
  23014. // If keyframes are passed, they really want an Animator instead.
  23015. if (config.keyframes) {
  23016. return new Ext.fx.Animator(config);
  23017. }
  23018. Ext.apply(me, config);
  23019. if (me.from === undefined) {
  23020. me.from = {};
  23021. }
  23022. me.propHandlers = {};
  23023. me.config = config;
  23024. me.target = Ext.fx.Manager.createTarget(me.target);
  23025. me.easingFn = Ext.fx.Easing[me.easing];
  23026. me.target.dynamic = me.dynamic;
  23027. // If not a pre-defined curve, try a cubic-bezier
  23028. if (!me.easingFn) {
  23029. me.easingFn = String(me.easing).match(me.bezierRE);
  23030. if (me.easingFn && me.easingFn.length == 5) {
  23031. curve = me.easingFn;
  23032. me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
  23033. }
  23034. }
  23035. me.id = Ext.id(null, 'ext-anim-');
  23036. me.addEvents(
  23037. /**
  23038. * @event beforeanimate
  23039. * Fires before the animation starts. A handler can return false to cancel the animation.
  23040. * @param {Ext.fx.Anim} this
  23041. */
  23042. 'beforeanimate',
  23043. /**
  23044. * @event afteranimate
  23045. * Fires when the animation is complete.
  23046. * @param {Ext.fx.Anim} this
  23047. * @param {Date} startTime
  23048. */
  23049. 'afteranimate',
  23050. /**
  23051. * @event lastframe
  23052. * Fires when the animation's last frame has been set.
  23053. * @param {Ext.fx.Anim} this
  23054. * @param {Date} startTime
  23055. */
  23056. 'lastframe'
  23057. );
  23058. me.mixins.observable.constructor.call(me);
  23059. Ext.fx.Manager.addAnim(me);
  23060. },
  23061. /**
  23062. * @private
  23063. * Helper to the target
  23064. */
  23065. setAttr: function(attr, value) {
  23066. return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
  23067. },
  23068. /**
  23069. * @private
  23070. * Set up the initial currentAttrs hash.
  23071. */
  23072. initAttrs: function() {
  23073. var me = this,
  23074. from = me.from,
  23075. to = me.to,
  23076. initialFrom = me.initialFrom || {},
  23077. out = {},
  23078. start, end, propHandler, attr;
  23079. for (attr in to) {
  23080. if (to.hasOwnProperty(attr)) {
  23081. start = me.target.getAttr(attr, from[attr]);
  23082. end = to[attr];
  23083. // Use default (numeric) property handler
  23084. if (!Ext.fx.PropertyHandler[attr]) {
  23085. if (Ext.isObject(end)) {
  23086. propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
  23087. } else {
  23088. propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
  23089. }
  23090. }
  23091. // Use custom handler
  23092. else {
  23093. propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
  23094. }
  23095. out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
  23096. }
  23097. }
  23098. me.currentAttrs = out;
  23099. },
  23100. /**
  23101. * @private
  23102. * Fires beforeanimate and sets the running flag.
  23103. */
  23104. start: function(startTime) {
  23105. var me = this,
  23106. delay = me.delay,
  23107. delayStart = me.delayStart,
  23108. delayDelta;
  23109. if (delay) {
  23110. if (!delayStart) {
  23111. me.delayStart = startTime;
  23112. return;
  23113. }
  23114. else {
  23115. delayDelta = startTime - delayStart;
  23116. if (delayDelta < delay) {
  23117. return;
  23118. }
  23119. else {
  23120. // Compensate for frame delay;
  23121. startTime = new Date(delayStart.getTime() + delay);
  23122. }
  23123. }
  23124. }
  23125. if (me.fireEvent('beforeanimate', me) !== false) {
  23126. me.startTime = startTime;
  23127. if (!me.paused && !me.currentAttrs) {
  23128. me.initAttrs();
  23129. }
  23130. me.running = true;
  23131. me.frameCount = 0;
  23132. }
  23133. },
  23134. /**
  23135. * @private
  23136. * Calculate attribute value at the passed timestamp.
  23137. * @returns a hash of the new attributes.
  23138. */
  23139. runAnim: function(elapsedTime) {
  23140. var me = this,
  23141. attrs = me.currentAttrs,
  23142. duration = me.duration,
  23143. easingFn = me.easingFn,
  23144. propHandlers = me.propHandlers,
  23145. ret = {},
  23146. easing, values, attr, lastFrame;
  23147. if (elapsedTime >= duration) {
  23148. elapsedTime = duration;
  23149. lastFrame = true;
  23150. }
  23151. if (me.reverse) {
  23152. elapsedTime = duration - elapsedTime;
  23153. }
  23154. for (attr in attrs) {
  23155. if (attrs.hasOwnProperty(attr)) {
  23156. values = attrs[attr];
  23157. easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
  23158. ret[attr] = propHandlers[attr].set(values, easing);
  23159. }
  23160. }
  23161. me.frameCount++;
  23162. return ret;
  23163. },
  23164. /**
  23165. * @private
  23166. * Perform lastFrame cleanup and handle iterations
  23167. * @returns a hash of the new attributes.
  23168. */
  23169. lastFrame: function() {
  23170. var me = this,
  23171. iter = me.iterations,
  23172. iterCount = me.currentIteration;
  23173. iterCount++;
  23174. if (iterCount < iter) {
  23175. if (me.alternate) {
  23176. me.reverse = !me.reverse;
  23177. }
  23178. me.startTime = new Date();
  23179. me.currentIteration = iterCount;
  23180. // Turn off paused for CSS3 Transitions
  23181. me.paused = false;
  23182. }
  23183. else {
  23184. me.currentIteration = 0;
  23185. me.end();
  23186. me.fireEvent('lastframe', me, me.startTime);
  23187. }
  23188. },
  23189. /**
  23190. * Fire afteranimate event and end the animation. Usually called automatically when the
  23191. * animation reaches its final frame, but can also be called manually to pre-emptively
  23192. * stop and destroy the running animation.
  23193. */
  23194. end: function() {
  23195. var me = this;
  23196. me.startTime = 0;
  23197. me.paused = false;
  23198. me.running = false;
  23199. Ext.fx.Manager.removeAnim(me);
  23200. me.fireEvent('afteranimate', me, me.startTime);
  23201. Ext.callback(me.callback, me.scope, [me, me.startTime]);
  23202. },
  23203. isReady: function() {
  23204. return this.paused === false && this.running === false && this.iterations > 0;
  23205. },
  23206. isRunning: function() {
  23207. return this.paused === false && this.running === true && this.isAnimator !== true;
  23208. }
  23209. });
  23210. // Set flag to indicate that Fx is available. Class might not be available immediately.
  23211. Ext.enableFx = true;
  23212. /**
  23213. * A specialized floating Component that supports a drop status icon, {@link Ext.Layer} styles
  23214. * and auto-repair. This is the default drag proxy used by all Ext.dd components.
  23215. */
  23216. Ext.define('Ext.dd.StatusProxy', {
  23217. extend: 'Ext.Component',
  23218. animRepair: false,
  23219. childEls: [
  23220. 'ghost'
  23221. ],
  23222. renderTpl: [
  23223. '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
  23224. '<div id="{id}-ghost" class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>'
  23225. ],
  23226. /**
  23227. * Creates new StatusProxy.
  23228. * @param {Object} [config] Config object.
  23229. */
  23230. constructor: function(config) {
  23231. var me = this;
  23232. config = config || {};
  23233. Ext.apply(me, {
  23234. hideMode: 'visibility',
  23235. hidden: true,
  23236. floating: true,
  23237. id: me.id || Ext.id(),
  23238. cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
  23239. shadow: config.shadow || false,
  23240. renderTo: Ext.getDetachedBody()
  23241. });
  23242. me.callParent(arguments);
  23243. this.dropStatus = this.dropNotAllowed;
  23244. },
  23245. /**
  23246. * @cfg {String} dropAllowed
  23247. * The CSS class to apply to the status element when drop is allowed.
  23248. */
  23249. dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
  23250. /**
  23251. * @cfg {String} dropNotAllowed
  23252. * The CSS class to apply to the status element when drop is not allowed.
  23253. */
  23254. dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
  23255. /**
  23256. * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
  23257. * over the current target element.
  23258. * @param {String} cssClass The css class for the new drop status indicator image
  23259. */
  23260. setStatus : function(cssClass){
  23261. cssClass = cssClass || this.dropNotAllowed;
  23262. if (this.dropStatus != cssClass) {
  23263. this.el.replaceCls(this.dropStatus, cssClass);
  23264. this.dropStatus = cssClass;
  23265. }
  23266. },
  23267. /**
  23268. * Resets the status indicator to the default dropNotAllowed value
  23269. * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
  23270. */
  23271. reset : function(clearGhost){
  23272. var me = this,
  23273. clsPrefix = Ext.baseCSSPrefix + 'dd-drag-proxy ';
  23274. me.el.replaceCls(clsPrefix + me.dropAllowed, clsPrefix + me.dropNotAllowed);
  23275. me.dropStatus = me.dropNotAllowed;
  23276. if (clearGhost) {
  23277. me.ghost.update('');
  23278. }
  23279. },
  23280. /**
  23281. * Updates the contents of the ghost element
  23282. * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
  23283. * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
  23284. */
  23285. update : function(html){
  23286. if (typeof html == "string") {
  23287. this.ghost.update(html);
  23288. } else {
  23289. this.ghost.update("");
  23290. html.style.margin = "0";
  23291. this.ghost.dom.appendChild(html);
  23292. }
  23293. var el = this.ghost.dom.firstChild;
  23294. if (el) {
  23295. Ext.fly(el).setStyle('float', 'none');
  23296. }
  23297. },
  23298. /**
  23299. * Returns the ghost element
  23300. * @return {Ext.Element} el
  23301. */
  23302. getGhost : function(){
  23303. return this.ghost;
  23304. },
  23305. /**
  23306. * Hides the proxy
  23307. * @param {Boolean} clear True to reset the status and clear the ghost contents,
  23308. * false to preserve them
  23309. */
  23310. hide : function(clear) {
  23311. this.callParent();
  23312. if (clear) {
  23313. this.reset(true);
  23314. }
  23315. },
  23316. /**
  23317. * Stops the repair animation if it's currently running
  23318. */
  23319. stop : function(){
  23320. if (this.anim && this.anim.isAnimated && this.anim.isAnimated()) {
  23321. this.anim.stop();
  23322. }
  23323. },
  23324. /**
  23325. * Force the Layer to sync its shadow and shim positions to the element
  23326. */
  23327. sync : function(){
  23328. this.el.sync();
  23329. },
  23330. /**
  23331. * Causes the proxy to return to its position of origin via an animation.
  23332. * Should be called after an invalid drop operation by the item being dragged.
  23333. * @param {Number[]} xy The XY position of the element ([x, y])
  23334. * @param {Function} callback The function to call after the repair is complete.
  23335. * @param {Object} scope The scope (`this` reference) in which the callback function is executed.
  23336. * Defaults to the browser window.
  23337. */
  23338. repair : function(xy, callback, scope) {
  23339. var me = this;
  23340. me.callback = callback;
  23341. me.scope = scope;
  23342. if (xy && me.animRepair !== false) {
  23343. me.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
  23344. me.el.hideUnders(true);
  23345. me.anim = me.el.animate({
  23346. duration: me.repairDuration || 500,
  23347. easing: 'ease-out',
  23348. to: {
  23349. x: xy[0],
  23350. y: xy[1]
  23351. },
  23352. stopAnimation: true,
  23353. callback: me.afterRepair,
  23354. scope: me
  23355. });
  23356. } else {
  23357. me.afterRepair();
  23358. }
  23359. },
  23360. // private
  23361. afterRepair : function() {
  23362. var me = this;
  23363. me.hide(true);
  23364. me.el.removeCls(Ext.baseCSSPrefix + 'dd-drag-repair');
  23365. if (typeof me.callback == "function") {
  23366. me.callback.call(me.scope || me);
  23367. }
  23368. delete me.callback;
  23369. delete me.scope;
  23370. }
  23371. });
  23372. /**
  23373. * Base Layout class - extended by ComponentLayout and ContainerLayout
  23374. */
  23375. Ext.define('Ext.layout.Layout', {
  23376. requires: [
  23377. 'Ext.XTemplate'
  23378. ],
  23379. uses: [ 'Ext.layout.Context' ],
  23380. /**
  23381. * @property {Boolean} isLayout
  23382. * `true` in this class to identify an objact as an instantiated Layout, or subclass thereof.
  23383. */
  23384. isLayout: true,
  23385. initialized: false,
  23386. running: false,
  23387. autoSizePolicy: {
  23388. setsWidth: 0,
  23389. setsHeight: 0
  23390. },
  23391. sizeModels: {
  23392. calculated: {
  23393. name: 'calculated',
  23394. auto: false,
  23395. calculated: true,
  23396. configured: false,
  23397. fixed: true,
  23398. natural: false,
  23399. shrinkWrap: false
  23400. },
  23401. calculatedFromNatural: {
  23402. name: 'calculatedFromNatural',
  23403. auto: true,
  23404. calculated: true,
  23405. configured: false,
  23406. fixed: true,
  23407. natural: true,
  23408. shrinkWrap: false
  23409. },
  23410. calculatedFromShrinkWrap: {
  23411. name: 'calculatedFromShrinkWrap',
  23412. auto: true,
  23413. calculated: true,
  23414. configured: false,
  23415. fixed: true,
  23416. natural: false,
  23417. shrinkWrap: true
  23418. },
  23419. configured: {
  23420. name: 'configured',
  23421. auto: false,
  23422. calculated: false,
  23423. configured: true,
  23424. fixed: true,
  23425. natural: false,
  23426. shrinkWrap: false
  23427. },
  23428. natural: {
  23429. name: 'natural',
  23430. auto: true,
  23431. calculated: false,
  23432. configured: false,
  23433. fixed: false,
  23434. natural: true,
  23435. shrinkWrap: false
  23436. },
  23437. shrinkWrap: {
  23438. name: 'shrinkWrap',
  23439. auto: true,
  23440. calculated: false,
  23441. configured: false,
  23442. fixed: false,
  23443. natural: false,
  23444. shrinkWrap: true
  23445. }
  23446. },
  23447. statics: {
  23448. layoutsByType: {},
  23449. create: function(layout, defaultType) {
  23450. var ClassManager = Ext.ClassManager,
  23451. layoutsByType = this.layoutsByType,
  23452. alias, className, config, layoutClass, type, load;
  23453. if (!layout || typeof layout === 'string') {
  23454. type = layout || defaultType;
  23455. config = {};
  23456. } else if (layout.isLayout) {
  23457. return layout;
  23458. } else {
  23459. config = layout;
  23460. type = layout.type || defaultType;
  23461. }
  23462. if (!(layoutClass = layoutsByType[type])) {
  23463. alias = 'layout.' + type;
  23464. className = ClassManager.getNameByAlias(alias);
  23465. // this is needed to support demand loading of the class
  23466. if (!className) {
  23467. load = true;
  23468. }
  23469. layoutClass = ClassManager.get(className);
  23470. if (load || !layoutClass) {
  23471. return ClassManager.instantiateByAlias(alias, config || {});
  23472. }
  23473. layoutsByType[type] = layoutClass;
  23474. }
  23475. return new layoutClass(config);
  23476. }
  23477. },
  23478. constructor : function(config) {
  23479. var me = this;
  23480. me.id = Ext.id(null, me.type + '-');
  23481. Ext.apply(me, config);
  23482. me.layoutCount = 0;
  23483. },
  23484. /**
  23485. * @property {Boolean} done Used only during a layout run, this value indicates that a
  23486. * layout has finished its calculations. This flag is set to true prior to the call to
  23487. * {@link #calculate} and should be set to false if this layout has more work to do.
  23488. */
  23489. /**
  23490. * Called before any calculation cycles to prepare for layout.
  23491. *
  23492. * This is a write phase and DOM reads should be strictly avoided when overridding
  23493. * this method.
  23494. *
  23495. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  23496. * component.
  23497. * @method beginLayout
  23498. */
  23499. beginLayout: Ext.emptyFn,
  23500. /**
  23501. * Called before any calculation cycles to reset DOM values and prepare for calculation.
  23502. *
  23503. * This is a write phase and DOM reads should be strictly avoided when overridding
  23504. * this method.
  23505. *
  23506. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  23507. * component.
  23508. * @method beginLayoutCycle
  23509. */
  23510. beginLayoutCycle: Ext.emptyFn,
  23511. /**
  23512. * Called to perform the calculations for this layout. This method will be called at
  23513. * least once and may be called repeatedly if the {@link #done} property is cleared
  23514. * before return to indicate that this layout is not yet done. The {@link #done} property
  23515. * is always set to `true` before entering this method.
  23516. *
  23517. * This is a read phase and DOM writes should be strictly avoided in derived classes.
  23518. * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
  23519. * be flushed at the next opportunity.
  23520. *
  23521. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  23522. * component.
  23523. * @method calculate
  23524. * @abstract
  23525. */
  23526. /**
  23527. * This method (if implemented) is called at the end of the cycle in which this layout
  23528. * completes (by not setting {@link #done} to `false` in {@link #calculate}). It is
  23529. * possible for the layout to complete and yet become invalid before the end of the cycle,
  23530. * in which case, this method will not be called. It is also possible for this method to
  23531. * be called and then later the layout becomes invalidated. This will result in
  23532. * {@link #calculate} being called again, followed by another call to this method.
  23533. *
  23534. * This is a read phase and DOM writes should be strictly avoided in derived classes.
  23535. * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
  23536. * be flushed at the next opportunity.
  23537. *
  23538. * This method need not be implemented by derived classes and, in fact, should only be
  23539. * implemented when needed.
  23540. *
  23541. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  23542. * component.
  23543. * @method completeLayout
  23544. */
  23545. /**
  23546. * This method (if implemented) is called after all layouts have completed. In most
  23547. * ways this is similar to {@link #completeLayout}. This call can cause this (or any
  23548. * layout) to be become invalid (see {@link Ext.layout.Context#invalidate}), but this
  23549. * is best avoided. This method is intended to be where final reads are made and so it
  23550. * is best to avoidinvalidating layouts at this point whenever possible. Even so, this
  23551. * method can be used to perform final checks that may require all other layouts to be
  23552. * complete and then invalidate some results.
  23553. *
  23554. * This is a read phase and DOM writes should be strictly avoided in derived classes.
  23555. * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
  23556. * be flushed at the next opportunity.
  23557. *
  23558. * This method need not be implemented by derived classes and, in fact, should only be
  23559. * implemented when needed.
  23560. *
  23561. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  23562. * component.
  23563. * @method finalizeLayout
  23564. */
  23565. /**
  23566. * This method is called after all layouts are complete and their calculations flushed
  23567. * to the DOM. No further layouts will be run and this method is only called once per
  23568. * layout run. The base component layout caches {@link #lastComponentSize}.
  23569. *
  23570. * This is a write phase and DOM reads should be avoided if possible when overridding
  23571. * this method.
  23572. *
  23573. * This method need not be implemented by derived classes and, in fact, should only be
  23574. * implemented when needed.
  23575. *
  23576. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  23577. * component.
  23578. */
  23579. finishedLayout: function () {
  23580. this.ownerContext = null;
  23581. },
  23582. /**
  23583. * This method (if implemented) is called after all layouts are finished, and all have
  23584. * a `lastComponentSize` cached. No further layouts will be run and this method is only
  23585. * called once per layout run. It is the bookend to {@link #beginLayout}.
  23586. *
  23587. * This is a write phase and DOM reads should be avoided if possible when overridding
  23588. * this method. This is the catch-all tail method to a layout and so the rules are more
  23589. * relaxed. Even so, for performance reasons, it is best to avoid reading the DOM. If
  23590. * a read is necessary, consider implementing a {@link #finalizeLayout} method to do the
  23591. * required reads.
  23592. *
  23593. * This method need not be implemented by derived classes and, in fact, should only be
  23594. * implemented when needed.
  23595. *
  23596. * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
  23597. * component.
  23598. * @method notifyOwner
  23599. */
  23600. redoLayout: Ext.emptyFn,
  23601. undoLayout: Ext.emptyFn,
  23602. getAnimatePolicy: function() {
  23603. return this.animatePolicy;
  23604. },
  23605. /**
  23606. * Returns an object describing how this layout manages the size of the given component.
  23607. * This method must be implemented by any layout that manages components.
  23608. *
  23609. * @param {Ext.Component} item
  23610. *
  23611. * @return {Object} An object describing the sizing done by the layout for this item or
  23612. * null if the layout mimics the size policy of its ownerCt (e.g., 'fit' and 'card').
  23613. * @return {Boolean} return.readsWidth True if the natural/auto width of this component
  23614. * is used by the ownerLayout.
  23615. * @return {Boolean} return.readsHeight True if the natural/auto height of this component
  23616. * is used by the ownerLayout.
  23617. * @return {Boolean} return.setsWidth True if the ownerLayout set this component's width.
  23618. * @return {Boolean} return.setsHeight True if the ownerLayout set this component's height.
  23619. *
  23620. * @protected
  23621. */
  23622. getItemSizePolicy: function (item) {
  23623. return this.autoSizePolicy;
  23624. },
  23625. isItemBoxParent: function (itemContext) {
  23626. return false;
  23627. },
  23628. isItemLayoutRoot: function (item) {
  23629. var sizeModel = item.getSizeModel(),
  23630. width = sizeModel.width,
  23631. height = sizeModel.height;
  23632. // If this component has never had a layout and some of its dimensions are set by
  23633. // its ownerLayout, we cannot be the layoutRoot...
  23634. if (!item.componentLayout.lastComponentSize && (width.calculated || height.calculated)) {
  23635. return false;
  23636. }
  23637. // otherwise an ownerCt whose size is not effected by its content is a root
  23638. return !width.shrinkWrap && !height.shrinkWrap;
  23639. },
  23640. isItemShrinkWrap: function (item) {
  23641. return item.shrinkWrap;
  23642. },
  23643. isRunning: function () {
  23644. return !!this.ownerContext;
  23645. },
  23646. //-----------------------------------------------------
  23647. /*
  23648. * Clears any styles which must be cleared before layout can take place.
  23649. * Only DOM WRITES must be performed at this stage.
  23650. *
  23651. * An entry for the owner's element ID must be created in the layoutContext containing
  23652. * a reference to the target which must be sized/positioned/styled by the layout at
  23653. * the flush stage:
  23654. *
  23655. * {
  23656. * target: me.owner
  23657. * }
  23658. *
  23659. * Component layouts should iterate through managed Elements,
  23660. * pushing an entry for each element:
  23661. *
  23662. * {
  23663. * target: childElement
  23664. * }
  23665. */
  23666. //-----------------------------------------------------
  23667. getItemsRenderTree: function (items, renderCfgs) {
  23668. var length = items.length,
  23669. i, item, itemConfig, result;
  23670. if (length) {
  23671. result = [];
  23672. for (i = 0; i < length; ++i) {
  23673. item = items[i];
  23674. // If we are being asked to move an already rendered Component, we must not recalculate its renderTree
  23675. // and rerun its render process. The Layout's isValidParent check will ensure that the DOM is moved into place.
  23676. if (!item.rendered) {
  23677. // If we've already calculated the item's element config, don't calculate it again.
  23678. // This may happen if the rendering process mutates the owning Container's items
  23679. // collection, and Ext.layout.Container#getRenderTree runs through the collection again.
  23680. // Note that the config may be null if a beforerender listener vetoed the operation, so
  23681. // we must compare to undefined.
  23682. if (renderCfgs && (renderCfgs[item.id] !== undefined)) {
  23683. itemConfig = renderCfgs[item.id];
  23684. } else {
  23685. // Perform layout preprocessing in the bulk render path
  23686. this.configureItem(item);
  23687. itemConfig = item.getRenderTree();
  23688. if (renderCfgs) {
  23689. renderCfgs[item.id] = itemConfig;
  23690. }
  23691. }
  23692. // itemConfig mey be null if a beforerender listener vetoed the operation.
  23693. if (itemConfig) {
  23694. result.push(itemConfig);
  23695. }
  23696. }
  23697. }
  23698. }
  23699. return result;
  23700. },
  23701. finishRender: Ext.emptyFn,
  23702. finishRenderItems: function (target, items) {
  23703. var length = items.length,
  23704. i, item;
  23705. for (i = 0; i < length; i++) {
  23706. item = items[i];
  23707. // Only postprocess items which are being rendered. deferredRender may mean that only one has been rendered.
  23708. if (item.rendering) {
  23709. // Tell the item at which index in the Container it is
  23710. item.finishRender(i);
  23711. this.afterRenderItem(item);
  23712. }
  23713. }
  23714. },
  23715. renderChildren: function () {
  23716. var me = this,
  23717. items = me.getLayoutItems(),
  23718. target = me.getRenderTarget();
  23719. me.renderItems(items, target);
  23720. },
  23721. /**
  23722. * Iterates over all passed items, ensuring they are rendered. If the items are already rendered,
  23723. * also determines if the items are in the proper place in the dom.
  23724. * @protected
  23725. */
  23726. renderItems : function(items, target) {
  23727. var me = this,
  23728. ln = items.length,
  23729. i = 0,
  23730. item;
  23731. if (ln) {
  23732. Ext.suspendLayouts();
  23733. for (; i < ln; i++) {
  23734. item = items[i];
  23735. if (item && !item.rendered) {
  23736. me.renderItem(item, target, i);
  23737. } else if (!me.isValidParent(item, target, i)) {
  23738. me.moveItem(item, target, i);
  23739. } else {
  23740. // still need to configure the item, it may have moved in the container.
  23741. me.configureItem(item);
  23742. }
  23743. }
  23744. Ext.resumeLayouts(true);
  23745. }
  23746. },
  23747. /**
  23748. * Validates item is in the proper place in the dom.
  23749. * @protected
  23750. */
  23751. isValidParent : function(item, target, position) {
  23752. var itemDom = item.el ? item.el.dom : Ext.getDom(item),
  23753. targetDom = (target && target.dom) || target;
  23754. // Test DOM nodes for equality using "===" : http://jsperf.com/dom-equality-test
  23755. if (itemDom && targetDom) {
  23756. if (typeof position == 'number') {
  23757. return itemDom === targetDom.childNodes[position];
  23758. }
  23759. return itemDom.parentNode === targetDom;
  23760. }
  23761. return false;
  23762. },
  23763. /**
  23764. * Called before an item is rendered to allow the layout to configure the item.
  23765. * @param {Ext.Component} item The item to be configured
  23766. * @protected
  23767. */
  23768. configureItem: function(item) {
  23769. item.ownerLayout = this;
  23770. },
  23771. /**
  23772. * Renders the given Component into the target Element.
  23773. * @param {Ext.Component} item The Component to render
  23774. * @param {Ext.dom.Element} target The target Element
  23775. * @param {Number} position The position within the target to render the item to
  23776. * @private
  23777. */
  23778. renderItem : function(item, target, position) {
  23779. if (!item.rendered) {
  23780. this.configureItem(item);
  23781. item.render(target, position);
  23782. this.afterRenderItem(item);
  23783. }
  23784. },
  23785. /**
  23786. * Moves Component to the provided target instead.
  23787. * @private
  23788. */
  23789. moveItem : function(item, target, position) {
  23790. target = target.dom || target;
  23791. if (typeof position == 'number') {
  23792. position = target.childNodes[position];
  23793. }
  23794. target.insertBefore(item.el.dom, position || null);
  23795. item.container = Ext.get(target);
  23796. this.configureItem(item);
  23797. },
  23798. /**
  23799. * This method is called when a child item changes in some way. By default this calls
  23800. * {@link Ext.AbstractComponent#updateLayout} on this layout's owner.
  23801. *
  23802. * @param {Ext.Component} child The child item that has changed.
  23803. * @return {Boolean} True if this layout has handled the content change.
  23804. */
  23805. onContentChange: function () {
  23806. this.owner.updateLayout();
  23807. return true;
  23808. },
  23809. /**
  23810. * A one-time initialization method called just before rendering.
  23811. * @protected
  23812. */
  23813. initLayout : function() {
  23814. this.initialized = true;
  23815. },
  23816. // @private Sets the layout owner
  23817. setOwner : function(owner) {
  23818. this.owner = owner;
  23819. },
  23820. /**
  23821. * Returns the set of items to layout (empty by default).
  23822. * @protected
  23823. */
  23824. getLayoutItems : function() {
  23825. return [];
  23826. },
  23827. // Placeholder empty functions for subclasses to extend
  23828. afterRenderItem: Ext.emptyFn,
  23829. onAdd : Ext.emptyFn,
  23830. onRemove : Ext.emptyFn,
  23831. onDestroy : Ext.emptyFn,
  23832. /**
  23833. * Removes layout's itemCls and owning Container's itemCls.
  23834. * Clears the managed dimensinos flags
  23835. * @protected
  23836. */
  23837. afterRemove : function(item) {
  23838. var me = this,
  23839. el = item.el,
  23840. owner = me.owner,
  23841. removeClasses;
  23842. if (item.rendered) {
  23843. removeClasses = [].concat(me.itemCls || []);
  23844. if (owner.itemCls) {
  23845. removeClasses = Ext.Array.push(removeClasses, owner.itemCls);
  23846. }
  23847. if (removeClasses.length) {
  23848. el.removeCls(removeClasses);
  23849. }
  23850. }
  23851. delete item.ownerLayout;
  23852. },
  23853. /**
  23854. * Destroys this layout. This method removes a `targetCls` from the `target`
  23855. * element and calls `onDestroy`.
  23856. *
  23857. * A derived class can override either this method or `onDestroy` but in all
  23858. * cases must call the base class versions of these methods to allow the base class to
  23859. * perform its cleanup.
  23860. *
  23861. * This method (or `onDestroy`) are overridden by subclasses most often to purge
  23862. * event handlers or remove unmanged DOM nodes.
  23863. *
  23864. * @protected
  23865. */
  23866. destroy : function() {
  23867. var me = this;
  23868. if (me.targetCls) {
  23869. var target = me.getTarget();
  23870. if (target) {
  23871. target.removeCls(me.targetCls);
  23872. }
  23873. }
  23874. me.onDestroy();
  23875. },
  23876. sortWeightedItems: function (items, reverseProp) {
  23877. for (var i = 0, length = items.length; i < length; ++i) {
  23878. items[i].$i = i;
  23879. }
  23880. Ext.Array.sort(items, function (item1, item2) {
  23881. var ret = item2.weight - item1.weight;
  23882. if (!ret) {
  23883. ret = item1.$i - item2.$i;
  23884. if (item1[reverseProp]) {
  23885. ret = -ret;
  23886. }
  23887. }
  23888. return ret;
  23889. });
  23890. for (i = 0; i < length; ++i) {
  23891. delete items[i].$i;
  23892. }
  23893. }
  23894. });
  23895. /**
  23896. * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
  23897. * configuration property. See {@link Ext.Component#componentLayout} for additional details.
  23898. * @private
  23899. */
  23900. Ext.define('Ext.layout.component.Component', {
  23901. /* Begin Definitions */
  23902. extend: 'Ext.layout.Layout',
  23903. /* End Definitions */
  23904. type: 'component',
  23905. isComponentLayout: true,
  23906. nullBox: {},
  23907. usesContentHeight: true,
  23908. usesContentWidth: true,
  23909. usesHeight: true,
  23910. usesWidth: true,
  23911. beginLayoutCycle: function (ownerContext, firstCycle) {
  23912. var me = this,
  23913. owner = me.owner,
  23914. ownerCtContext = ownerContext.ownerCtContext,
  23915. heightModel = ownerContext.heightModel,
  23916. widthModel = ownerContext.widthModel,
  23917. body = owner.el.dom === document.body,
  23918. lastBox = owner.lastBox || me.nullBox,
  23919. lastSize = owner.el.lastBox || me.nullBox,
  23920. dirty, ownerLayout, v;
  23921. me.callParent(arguments);
  23922. if (firstCycle) {
  23923. if (me.usesContentWidth) {
  23924. ++ownerContext.consumersContentWidth;
  23925. }
  23926. if (me.usesContentHeight) {
  23927. ++ownerContext.consumersContentHeight;
  23928. }
  23929. if (me.usesWidth) {
  23930. ++ownerContext.consumersWidth;
  23931. }
  23932. if (me.usesHeight) {
  23933. ++ownerContext.consumersHeight;
  23934. }
  23935. if (ownerCtContext && !ownerCtContext.hasRawContent) {
  23936. ownerLayout = owner.ownerLayout;
  23937. if (ownerLayout.usesWidth) {
  23938. ++ownerContext.consumersWidth;
  23939. }
  23940. if (ownerLayout.usesHeight) {
  23941. ++ownerContext.consumersHeight;
  23942. }
  23943. }
  23944. }
  23945. // we want to publish configured dimensions as early as possible and since this is
  23946. // a write phase...
  23947. if (widthModel.configured) {
  23948. // If the owner.el is the body, owner.width is not dirty (we don't want to write
  23949. // it to the body el). For other el's, the width may already be correct in the
  23950. // DOM (e.g., it is rendered in the markup initially). If the width is not
  23951. // correct in the DOM, this is only going to be the case on the first cycle.
  23952. dirty = !body && firstCycle && owner.width !== lastSize.width;
  23953. ownerContext.setWidth(owner.width, dirty);
  23954. } else if (ownerContext.isTopLevel && widthModel.calculated) {
  23955. v = lastBox.width;
  23956. ownerContext.setWidth(v, /*dirty=*/v != lastSize.width);
  23957. }
  23958. if (heightModel.configured) {
  23959. dirty = !body && firstCycle && owner.height !== lastSize.height;
  23960. ownerContext.setHeight(owner.height, dirty);
  23961. } else if (ownerContext.isTopLevel && heightModel.calculated) {
  23962. v = lastBox.height;
  23963. ownerContext.setHeight(v, v != lastSize.height);
  23964. }
  23965. },
  23966. finishedLayout: function(ownerContext) {
  23967. var me = this,
  23968. elementChildren = ownerContext.children,
  23969. owner = me.owner,
  23970. len, i, elContext, lastBox, props, v;
  23971. // NOTE: In the code below we cannot use getProp because that will generate a layout dependency
  23972. // Set lastBox on managed child Elements.
  23973. // So that ContextItem.constructor can snag the lastBox for use by its undo method.
  23974. if (elementChildren) {
  23975. len = elementChildren.length;
  23976. for (i = 0; i < len; i++) {
  23977. elContext = elementChildren[i];
  23978. elContext.el.lastBox = elContext.props;
  23979. }
  23980. }
  23981. // Cache the size from which we are changing so that notifyOwner can notify the owningComponent with all essential information
  23982. ownerContext.previousSize = me.lastComponentSize;
  23983. // Cache the currently layed out size
  23984. me.lastComponentSize = owner.el.lastBox = props = ownerContext.props;
  23985. // lastBox is a copy of the defined props to allow save/restore of these (panel
  23986. // collapse needs this)
  23987. owner.lastBox = lastBox = {};
  23988. v = props.x;
  23989. if (v !== undefined) {
  23990. lastBox.x = v;
  23991. }
  23992. v = props.y;
  23993. if (v !== undefined) {
  23994. lastBox.y = v;
  23995. }
  23996. v = props.width;
  23997. if (v !== undefined) {
  23998. lastBox.width = v;
  23999. }
  24000. v = props.height;
  24001. if (v !== undefined) {
  24002. lastBox.height = v;
  24003. }
  24004. me.callParent(arguments);
  24005. },
  24006. notifyOwner: function(ownerContext) {
  24007. var me = this,
  24008. currentSize = me.lastComponentSize,
  24009. prevSize = ownerContext.previousSize,
  24010. args = [currentSize.width, currentSize.height];
  24011. if (prevSize) {
  24012. args.push(prevSize.width, prevSize.height);
  24013. }
  24014. // Call afterComponentLayout passing new size, and only passing old size if there *was* an old size.
  24015. me.owner.afterComponentLayout.apply(me.owner, args);
  24016. },
  24017. /**
  24018. * Returns the owner component's resize element.
  24019. * @return {Ext.Element}
  24020. */
  24021. getTarget : function() {
  24022. return this.owner.el;
  24023. },
  24024. /**
  24025. * Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.
  24026. *
  24027. * May be overridden in Component layout managers which implement an inner element.
  24028. * @return {Ext.Element}
  24029. */
  24030. getRenderTarget : function() {
  24031. return this.owner.el;
  24032. },
  24033. cacheTargetInfo: function(ownerContext) {
  24034. var me = this,
  24035. targetInfo = me.targetInfo,
  24036. target;
  24037. if (!targetInfo) {
  24038. target = ownerContext.getEl('getTarget', me);
  24039. me.targetInfo = targetInfo = {
  24040. padding: target.getPaddingInfo(),
  24041. border: target.getBorderInfo()
  24042. };
  24043. }
  24044. return targetInfo;
  24045. },
  24046. measureAutoDimensions: function (ownerContext, dimensions) {
  24047. // Subtle But Important:
  24048. //
  24049. // We don't want to call getProp/hasProp et.al. unless we in fact need that value
  24050. // for our results! If we call it and don't need it, the layout manager will think
  24051. // we depend on it and will schedule us again should it change.
  24052. var me = this,
  24053. owner = me.owner,
  24054. heightModel = ownerContext.heightModel,
  24055. widthModel = ownerContext.widthModel,
  24056. boxParent = ownerContext.boxParent,
  24057. isBoxParent = ownerContext.isBoxParent,
  24058. props = ownerContext.props,
  24059. isContainer,
  24060. ret = {
  24061. gotWidth: false,
  24062. gotHeight: false,
  24063. isContainer: (isContainer = !ownerContext.hasRawContent)
  24064. },
  24065. hv = dimensions || 3,
  24066. zeroWidth, zeroHeight,
  24067. needed = 0,
  24068. got = 0,
  24069. ready, size;
  24070. // Note: this method is called *a lot*, so we have to be careful not to waste any
  24071. // time or make useless calls or, especially, read the DOM when we can avoid it.
  24072. //---------------------------------------------------------------------
  24073. // Width
  24074. if (widthModel.shrinkWrap && ownerContext.consumersContentWidth) {
  24075. ++needed;
  24076. zeroWidth = !(hv & 1);
  24077. if (isContainer) {
  24078. // as a componentLayout for a container, we rely on the container layout to
  24079. // produce contentWidth...
  24080. if (zeroWidth) {
  24081. ret.contentWidth = 0;
  24082. ret.gotWidth = true;
  24083. ++got;
  24084. } else if ((ret.contentWidth = ownerContext.getProp('contentWidth')) !== undefined) {
  24085. ret.gotWidth = true;
  24086. ++got;
  24087. }
  24088. } else {
  24089. size = props.contentWidth;
  24090. if (typeof size == 'number') { // if (already determined)
  24091. ret.contentWidth = size;
  24092. ret.gotWidth = true;
  24093. ++got;
  24094. } else {
  24095. if (zeroWidth) {
  24096. ready = true;
  24097. } else if (!ownerContext.hasDomProp('containerChildrenDone')) {
  24098. ready = false;
  24099. } else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
  24100. // if we have no boxParent, we are ready, but a shrinkWrap boxParent
  24101. // artificially provides width early in the measurement process so
  24102. // we are ready to go in that case as well...
  24103. ready = true;
  24104. } else {
  24105. // lastly, we have a boxParent that will be given a width, so we
  24106. // can wait for that width to be set in order to properly measure
  24107. // whatever is inside...
  24108. ready = boxParent.hasDomProp('width');
  24109. }
  24110. if (ready) {
  24111. if (!isNaN(ret.contentWidth = zeroWidth ? 0 : me.measureContentWidth(ownerContext))) {
  24112. ownerContext.setContentWidth(ret.contentWidth, true);
  24113. ret.gotWidth = true;
  24114. ++got;
  24115. }
  24116. }
  24117. }
  24118. }
  24119. } else if (widthModel.natural && ownerContext.consumersWidth) {
  24120. ++needed;
  24121. size = props.width;
  24122. // zeroWidth does not apply
  24123. if (typeof size == 'number') { // if (already determined)
  24124. ret.width = size;
  24125. ret.gotWidth = true;
  24126. ++got;
  24127. } else {
  24128. if (isBoxParent || !boxParent) {
  24129. ready = true;
  24130. } else {
  24131. // lastly, we have a boxParent that will be given a width, so we
  24132. // can wait for that width to be set in order to properly measure
  24133. // whatever is inside...
  24134. ready = boxParent.hasDomProp('width');
  24135. }
  24136. if (ready) {
  24137. if (!isNaN(ret.width = me.measureOwnerWidth(ownerContext))) {
  24138. ownerContext.setWidth(ret.width, false);
  24139. ret.gotWidth = true;
  24140. ++got;
  24141. }
  24142. }
  24143. }
  24144. }
  24145. //---------------------------------------------------------------------
  24146. // Height
  24147. if (heightModel.shrinkWrap && ownerContext.consumersContentHeight) {
  24148. ++needed;
  24149. zeroHeight = !(hv & 2);
  24150. if (isContainer) {
  24151. // don't ask unless we need to know...
  24152. if (zeroHeight) {
  24153. ret.contentHeight = 0;
  24154. ret.gotHeight = true;
  24155. ++got;
  24156. } else if ((ret.contentHeight = ownerContext.getProp('contentHeight')) !== undefined) {
  24157. ret.gotHeight = true;
  24158. ++got;
  24159. }
  24160. } else {
  24161. size = props.contentHeight;
  24162. if (typeof size == 'number') { // if (already determined)
  24163. ret.contentHeight = size;
  24164. ret.gotHeight = true;
  24165. ++got;
  24166. } else {
  24167. if (zeroHeight) {
  24168. ready = true;
  24169. } else if (!ownerContext.hasDomProp('containerChildrenDone')) {
  24170. ready = false;
  24171. } else if (owner.noWrap) {
  24172. ready = true;
  24173. } else if (!widthModel.shrinkWrap) {
  24174. // fixed width, so we need the width to determine the height...
  24175. ready = (ownerContext.bodyContext || ownerContext).hasDomProp('width');// && (!ownerContext.bodyContext || ownerContext.bodyContext.hasDomProp('width'));
  24176. } else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
  24177. // if we have no boxParent, we are ready, but an autoWidth boxParent
  24178. // artificially provides width early in the measurement process so
  24179. // we are ready to go in that case as well...
  24180. ready = true;
  24181. } else {
  24182. // lastly, we have a boxParent that will be given a width, so we
  24183. // can wait for that width to be set in order to properly measure
  24184. // whatever is inside...
  24185. ready = boxParent.hasDomProp('width');
  24186. }
  24187. if (ready) {
  24188. if (!isNaN(ret.contentHeight = zeroHeight ? 0 : me.measureContentHeight(ownerContext))) {
  24189. ownerContext.setContentHeight(ret.contentHeight, true);
  24190. ret.gotHeight = true;
  24191. ++got;
  24192. }
  24193. }
  24194. }
  24195. }
  24196. } else if (heightModel.natural && ownerContext.consumersHeight) {
  24197. ++needed;
  24198. size = props.height;
  24199. // zeroHeight does not apply
  24200. if (typeof size == 'number') { // if (already determined)
  24201. ret.height = size;
  24202. ret.gotHeight = true;
  24203. ++got;
  24204. } else {
  24205. if (isBoxParent || !boxParent) {
  24206. ready = true;
  24207. } else {
  24208. // lastly, we have a boxParent that will be given a width, so we
  24209. // can wait for that width to be set in order to properly measure
  24210. // whatever is inside...
  24211. ready = boxParent.hasDomProp('width');
  24212. }
  24213. if (ready) {
  24214. if (!isNaN(ret.height = me.measureOwnerHeight(ownerContext))) {
  24215. ownerContext.setHeight(ret.height, false);
  24216. ret.gotHeight = true;
  24217. ++got;
  24218. }
  24219. }
  24220. }
  24221. }
  24222. if (boxParent) {
  24223. ownerContext.onBoxMeasured();
  24224. }
  24225. ret.gotAll = got == needed;
  24226. // see if we can avoid calling this method by storing something on ownerContext.
  24227. return ret;
  24228. },
  24229. measureContentWidth: function (ownerContext) {
  24230. // contentWidth includes padding, but not border, framing or margins
  24231. return ownerContext.el.getWidth() - ownerContext.getFrameInfo().width;
  24232. },
  24233. measureContentHeight: function (ownerContext) {
  24234. // contentHeight includes padding, but not border, framing or margins
  24235. return ownerContext.el.getHeight() - ownerContext.getFrameInfo().height;
  24236. },
  24237. measureOwnerHeight: function (ownerContext) {
  24238. return ownerContext.el.getHeight();
  24239. },
  24240. measureOwnerWidth: function (ownerContext) {
  24241. return ownerContext.el.getWidth();
  24242. }
  24243. });
  24244. /**
  24245. * This ComponentLayout handles docking for Panels. It takes care of panels that are
  24246. * part of a ContainerLayout that sets this Panel's size and Panels that are part of
  24247. * an AutoContainerLayout in which this panel get his height based of the CSS or
  24248. * or its content.
  24249. * @private
  24250. */
  24251. Ext.define('Ext.layout.component.Dock', {
  24252. /* Begin Definitions */
  24253. extend: 'Ext.layout.component.Component',
  24254. alias: 'layout.dock',
  24255. alternateClassName: 'Ext.layout.component.AbstractDock',
  24256. /* End Definitions */
  24257. type: 'dock',
  24258. initializedBorders: -1,
  24259. horizontalCollapsePolicy: { width: true },
  24260. verticalCollapsePolicy: { height: true },
  24261. finishRender: function () {
  24262. var me = this,
  24263. target, items;
  24264. me.callParent();
  24265. target = me.getRenderTarget();
  24266. items = me.getDockedItems();
  24267. me.finishRenderItems(target, items);
  24268. },
  24269. isItemBoxParent: function (itemContext) {
  24270. return true;
  24271. },
  24272. isItemShrinkWrap: function (item) {
  24273. return true;
  24274. },
  24275. dockOpposites: {
  24276. top: 'bottom',
  24277. right: 'left',
  24278. bottom: 'top',
  24279. left: 'right'
  24280. },
  24281. handleItemBorders: function() {
  24282. var me = this,
  24283. owner = me.owner,
  24284. borders, docked,
  24285. oldBorders = me.borders,
  24286. opposites = me.dockOpposites,
  24287. currentGeneration = owner.dockedItems.generation,
  24288. i, ln, item, dock, side,
  24289. collapsed = me.collapsed;
  24290. if (me.initializedBorders == currentGeneration || (owner.border && !owner.manageBodyBorders)) {
  24291. return;
  24292. }
  24293. me.initializedBorders = currentGeneration;
  24294. // Borders have to be calculated using expanded docked item collection.
  24295. me.collapsed = false;
  24296. docked = me.getLayoutItems();
  24297. me.collapsed = collapsed;
  24298. borders = { top: [], right: [], bottom: [], left: [] };
  24299. for (i = 0, ln = docked.length; i < ln; i++) {
  24300. item = docked[i];
  24301. dock = item.dock;
  24302. if (item.ignoreBorderManagement) {
  24303. continue;
  24304. }
  24305. if (!borders[dock].satisfied) {
  24306. borders[dock].push(item);
  24307. borders[dock].satisfied = true;
  24308. }
  24309. if (!borders.top.satisfied && opposites[dock] !== 'top') {
  24310. borders.top.push(item);
  24311. }
  24312. if (!borders.right.satisfied && opposites[dock] !== 'right') {
  24313. borders.right.push(item);
  24314. }
  24315. if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
  24316. borders.bottom.push(item);
  24317. }
  24318. if (!borders.left.satisfied && opposites[dock] !== 'left') {
  24319. borders.left.push(item);
  24320. }
  24321. }
  24322. if (oldBorders) {
  24323. for (side in oldBorders) {
  24324. if (oldBorders.hasOwnProperty(side)) {
  24325. ln = oldBorders[side].length;
  24326. if (!owner.manageBodyBorders) {
  24327. for (i = 0; i < ln; i++) {
  24328. oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  24329. }
  24330. if (!oldBorders[side].satisfied && !owner.bodyBorder) {
  24331. owner.removeBodyCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  24332. }
  24333. }
  24334. else if (oldBorders[side].satisfied) {
  24335. owner.setBodyStyle('border-' + side + '-width', '');
  24336. }
  24337. }
  24338. }
  24339. }
  24340. for (side in borders) {
  24341. if (borders.hasOwnProperty(side)) {
  24342. ln = borders[side].length;
  24343. if (!owner.manageBodyBorders) {
  24344. for (i = 0; i < ln; i++) {
  24345. borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  24346. }
  24347. if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
  24348. owner.addBodyCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
  24349. }
  24350. }
  24351. else if (borders[side].satisfied) {
  24352. owner.setBodyStyle('border-' + side + '-width', '1px');
  24353. }
  24354. }
  24355. }
  24356. me.borders = borders;
  24357. },
  24358. beginLayout: function(ownerContext) {
  24359. var me = this,
  24360. owner = me.owner,
  24361. docked = me.getLayoutItems(),
  24362. layoutContext = ownerContext.context,
  24363. dockedItemCount = docked.length,
  24364. collapsedVert = false,
  24365. collapsedHorz = false,
  24366. dockedItems, i, item, itemContext, offsets,
  24367. collapsed;
  24368. me.callParent(arguments);
  24369. me.handleItemBorders();
  24370. // Cache the children as ContextItems (like a Container). Also setup to handle
  24371. // collapsed state:
  24372. collapsed = owner.getCollapsed();
  24373. if (Ext.isDefined(me.lastCollapsedState) && (collapsed !== me.lastCollapsedState)) {
  24374. // If we are collapsing...
  24375. if (me.owner.collapsed) {
  24376. ownerContext.isCollapsingOrExpanding = 1;
  24377. // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout.
  24378. owner.addClsWithUI(owner.collapsedCls);
  24379. } else {
  24380. ownerContext.isCollapsingOrExpanding = 2;
  24381. // Remove the collapsed class now, before layout calculations are done.
  24382. owner.removeClsWithUI(owner.collapsedCls);
  24383. ownerContext.lastCollapsedState = me.lastCollapsedState;
  24384. }
  24385. }
  24386. me.lastCollapsedState = collapsed;
  24387. ownerContext.dockedItems = dockedItems = [];
  24388. for (i = 0; i < dockedItemCount; i++) {
  24389. item = docked[i];
  24390. itemContext = layoutContext.getCmp(item);
  24391. itemContext.dockedAt = { x: 0, y: 0 };
  24392. itemContext.offsets = offsets = Ext.Element.parseBox(item.offsets || {});
  24393. offsets.width = offsets.left + offsets.right;
  24394. offsets.height = offsets.top + offsets.bottom;
  24395. dockedItems.push(itemContext);
  24396. }
  24397. if (owner.collapsed) {
  24398. if (owner.collapsedVertical()) {
  24399. collapsedVert = true;
  24400. ownerContext.measureDimensions = 1;
  24401. } else {
  24402. collapsedHorz = true;
  24403. ownerContext.measureDimensions = 2;
  24404. }
  24405. }
  24406. ownerContext.collapsedVert = collapsedVert;
  24407. ownerContext.collapsedHorz = collapsedHorz;
  24408. ownerContext.bodyContext = ownerContext.getEl('body');
  24409. },
  24410. beginLayoutCycle: function(ownerContext) {
  24411. var me = this,
  24412. docked = ownerContext.dockedItems,
  24413. len = docked.length,
  24414. owner = me.owner,
  24415. frameBody = owner.frameBody,
  24416. i, item, dock;
  24417. me.callParent(arguments);
  24418. // If we are collapsed, we want to auto-layout using the placeholder/expander
  24419. // instead of the normal items/dockedItems. This must be done here since we could
  24420. // be in a box layout w/stretchmax which sets the width/heightModel to allow it to
  24421. // control the size.
  24422. if (ownerContext.collapsedVert) {
  24423. ownerContext.heightModel = me.sizeModels.shrinkWrap;
  24424. } else if (ownerContext.collapsedHorz) {
  24425. ownerContext.widthModel = me.sizeModels.shrinkWrap
  24426. }
  24427. if (ownerContext.widthModel.auto) {
  24428. if (ownerContext.widthModel.shrinkWrap) {
  24429. owner.el.setWidth(null);
  24430. }
  24431. owner.body.setWidth(null);
  24432. if (frameBody) {
  24433. frameBody.setWidth(null);
  24434. }
  24435. }
  24436. if (ownerContext.heightModel.auto) {
  24437. owner.body.setHeight(null);
  24438. //owner.el.setHeight(null); Disable this for now
  24439. if (frameBody) {
  24440. frameBody.setHeight(null);
  24441. }
  24442. }
  24443. // Each time we begin (2nd+ would be due to invalidate) we need to publish the
  24444. // known contentWidth/Height if we are collapsed:
  24445. if (ownerContext.collapsedVert) {
  24446. ownerContext.setContentHeight(0);
  24447. } else if (ownerContext.collapsedHorz) {
  24448. ownerContext.setContentWidth(0);
  24449. }
  24450. // dock: 'right' items, when a panel gets narrower get "squished". Moving them to
  24451. // left:0px avoids this!
  24452. for (i = 0; i < len; i++) {
  24453. item = docked[i].target;
  24454. dock = item.dock;
  24455. if (dock == 'right') {
  24456. item.el.setLeft(0);
  24457. } else if (dock != 'left') {
  24458. continue;
  24459. }
  24460. // TODO - clear width/height?
  24461. }
  24462. },
  24463. calculate: function (ownerContext) {
  24464. var me = this,
  24465. measure = me.measureAutoDimensions(ownerContext, ownerContext.measureDimensions),
  24466. state = ownerContext.state,
  24467. horzDone = state.horzDone,
  24468. vertDone = state.vertDone,
  24469. bodyContext = ownerContext.bodyContext,
  24470. horz, vert, forward, backward;
  24471. // make sure we can use these value w/o calling methods to get them
  24472. ownerContext.borderInfo || ownerContext.getBorderInfo();
  24473. ownerContext.paddingInfo || ownerContext.getPaddingInfo();
  24474. ownerContext.framingInfo || ownerContext.getFraming();
  24475. bodyContext.borderInfo || bodyContext.getBorderInfo();
  24476. bodyContext.paddingInfo || bodyContext.getPaddingInfo();
  24477. // Start the axes so they are ready to proceed inwards (fixed-size) or outwards
  24478. // (shrinkWrap) and stash key property names as well:
  24479. horz = !horzDone &&
  24480. me.createAxis(ownerContext, measure.contentWidth, ownerContext.widthModel,
  24481. 'left', 'right', 'x', 'width', 'Width', ownerContext.collapsedHorz);
  24482. vert = !vertDone &&
  24483. me.createAxis(ownerContext, measure.contentHeight, ownerContext.heightModel,
  24484. 'top', 'bottom', 'y', 'height', 'Height', ownerContext.collapsedVert);
  24485. // We iterate forward and backward over the dockedItems at the same time based on
  24486. // whether an axis is shrinkWrap or fixed-size. For a fixed-size axis, the outer box
  24487. // axis is allocated to docked items in forward order and is reduced accordingly.
  24488. // To handle a shrinkWrap axis, the box starts at the inner (body) size and is used to
  24489. // size docked items in backwards order. This is because the last docked item shares
  24490. // an edge with the body. The item size is used to adjust the shrinkWrap axis outwards
  24491. // until the first docked item (at the outermost edge) is processed. This backwards
  24492. // order ensures that docked items never get an incorrect size for any dimension.
  24493. for (forward = 0, backward = ownerContext.dockedItems.length; backward--; ++forward) {
  24494. if (horz) {
  24495. me.dockChild(ownerContext, horz, backward, forward);
  24496. }
  24497. if (vert) {
  24498. me.dockChild(ownerContext, vert, backward, forward);
  24499. }
  24500. }
  24501. if (horz && me.finishAxis(ownerContext, horz)) {
  24502. state.horzDone = horzDone = horz;
  24503. }
  24504. if (vert && me.finishAxis(ownerContext, vert)) {
  24505. state.vertDone = vertDone = vert;
  24506. }
  24507. // Once all items are docked, the final size of the outer panel or inner body can
  24508. // be determined. If we can determine both width and height, we are done.
  24509. if (horzDone && vertDone && me.finishConstraints(ownerContext, horzDone, vertDone)) {
  24510. // Size information is published as we dock items but position is hard to do
  24511. // that way (while avoiding published multiple times) so we publish all the
  24512. // positions at the end.
  24513. me.finishPositions(ownerContext, horzDone, vertDone);
  24514. } else {
  24515. me.done = false;
  24516. }
  24517. },
  24518. /**
  24519. * Creates an axis object given the particulars.
  24520. * @private
  24521. */
  24522. createAxis: function (ownerContext, contentSize, sizeModel, dockBegin, dockEnd, posProp,
  24523. sizeProp, sizePropCap, collapsedAxis) {
  24524. var begin = 0,
  24525. owner = this.owner,
  24526. maxSize = owner['max' + sizePropCap],
  24527. minSize = owner['min' + sizePropCap] || 0,
  24528. hasMaxSize = maxSize != null, // exactly the same as "maxSize !== null && maxSize !== undefined"
  24529. constrainedSize = ownerContext.state['constrained' + sizePropCap],
  24530. isConstrainedSize = constrainedSize != null,
  24531. setSize = 'set' + sizePropCap,
  24532. border, bodyContext, frameSize, padding, end;
  24533. if (sizeModel.shrinkWrap && !isConstrainedSize) {
  24534. // End position before adding docks around the content is content size plus the body borders in this axis.
  24535. // If collapsed in this axis, the body borders will not be shown.
  24536. if (collapsedAxis) {
  24537. end = 0;
  24538. } else {
  24539. bodyContext = ownerContext.bodyContext;
  24540. end = contentSize + bodyContext.borderInfo[sizeProp];
  24541. }
  24542. } else {
  24543. border = ownerContext.borderInfo;
  24544. frameSize = ownerContext.framingInfo;
  24545. padding = ownerContext.paddingInfo;
  24546. if (isConstrainedSize) {
  24547. end = constrainedSize;
  24548. sizeModel = this.sizeModels.calculated; // behave as if calculated
  24549. ownerContext[setSize](constrainedSize);
  24550. } else {
  24551. end = ownerContext.getProp(sizeProp);
  24552. }
  24553. end -= border[dockEnd] + padding[dockEnd] + frameSize[dockEnd];
  24554. begin = border[dockBegin] + padding[dockBegin] + frameSize[dockBegin];
  24555. }
  24556. return {
  24557. shrinkWrap: sizeModel.shrinkWrap,
  24558. sizeModel: sizeModel,
  24559. // An axis tracks start and end+1 px positions. eg 0 to 10 for 10px high
  24560. begin: begin,
  24561. end: end,
  24562. collapsed: collapsedAxis,
  24563. horizontal: posProp == 'x',
  24564. ignoreFrameBegin: false,
  24565. ignoreFrameEnd: false,
  24566. initialSize: end - begin,
  24567. hasMinMaxConstraints: (minSize || hasMaxSize) && sizeModel.shrinkWrap,
  24568. isConstrainedSize: isConstrainedSize,
  24569. minSize: minSize,
  24570. maxSize: hasMaxSize ? maxSize : 1e9,
  24571. dockBegin: dockBegin, // 'left' or 'top'
  24572. dockEnd: dockEnd, // 'right' or 'end'
  24573. posProp: posProp, // 'x' or 'y'
  24574. sizeProp: sizeProp, // 'width' or 'height'
  24575. sizePropCap: sizePropCap, // 'Width' or 'Height'
  24576. setSize: setSize
  24577. };
  24578. },
  24579. /**
  24580. * Docks a child item on the specified axis. This boils down to determining if the item
  24581. * is docked at the "beginning" of the axis ("left" if horizontal, "top" if vertical),
  24582. * the "end" of the axis ("right" if horizontal, "bottom" if vertical) or stretches
  24583. * along the axis ("top" or "bottom" if horizontal, "left" or "right" if vertical). It
  24584. * also has to differentiate between fixed and shrinkWrap sized dimensions.
  24585. * @private
  24586. */
  24587. dockChild: function (ownerContext, axis, backward, forward) {
  24588. var me = this,
  24589. itemContext = ownerContext.dockedItems[axis.shrinkWrap ? backward : forward],
  24590. item = itemContext.target,
  24591. dock = item.dock, // left/top/right/bottom
  24592. pos;
  24593. if(item.ignoreParentFrame && ownerContext.isCollapsingOrExpanding) {
  24594. // collapsed window header margins may differ from expanded window header margins
  24595. // so we need to make sure the old cached values are not used in axis calculations
  24596. itemContext.clearMarginCache();
  24597. }
  24598. if (dock == axis.dockBegin) {
  24599. if (axis.shrinkWrap) {
  24600. pos = me.dockOutwardBegin(ownerContext, itemContext, item, axis);
  24601. } else {
  24602. pos = me.dockInwardBegin(ownerContext, itemContext, item, axis);
  24603. }
  24604. } else if (dock == axis.dockEnd) {
  24605. if (axis.shrinkWrap) {
  24606. pos = me.dockOutwardEnd(ownerContext, itemContext, item, axis);
  24607. } else {
  24608. pos = me.dockInwardEnd(ownerContext, itemContext, item, axis);
  24609. }
  24610. } else {
  24611. pos = me.dockStretch(ownerContext, itemContext, item, axis);
  24612. }
  24613. itemContext.dockedAt[axis.posProp] = pos;
  24614. },
  24615. /**
  24616. * Docks an item on a fixed-size axis at the "beginning". The "beginning" of the horizontal
  24617. * axis is "left" and the vertical is "top". For a fixed-size axis, the size works from
  24618. * the outer element (the panel) towards the body.
  24619. * @private
  24620. */
  24621. dockInwardBegin: function (ownerContext, itemContext, item, axis) {
  24622. var pos = axis.begin,
  24623. sizeProp = axis.sizeProp,
  24624. dock;
  24625. if (item.ignoreParentFrame) {
  24626. dock = item.dock;
  24627. pos -= ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] +
  24628. ownerContext.framingInfo[dock];
  24629. }
  24630. if (!item.overlay) {
  24631. axis.begin += itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp];
  24632. }
  24633. return pos;
  24634. },
  24635. /**
  24636. * Docks an item on a fixed-size axis at the "end". The "end" of the horizontal axis is
  24637. * "right" and the vertical is "bottom".
  24638. * @private
  24639. */
  24640. dockInwardEnd: function (ownerContext, itemContext, item, axis) {
  24641. var sizeProp = axis.sizeProp,
  24642. size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp],
  24643. pos = axis.end - size;
  24644. if (!item.overlay) {
  24645. axis.end = pos;
  24646. }
  24647. if (item.ignoreParentFrame) {
  24648. pos += ownerContext.borderInfo[item.dock] + ownerContext.paddingInfo[item.dock] +
  24649. ownerContext.framingInfo[item.dock];
  24650. }
  24651. return pos;
  24652. },
  24653. /**
  24654. * Docks an item on a shrinkWrap axis at the "beginning". The "beginning" of the horizontal
  24655. * axis is "left" and the vertical is "top". For a shrinkWrap axis, the size works from
  24656. * the body outward to the outermost element (the panel).
  24657. *
  24658. * During the docking process, coordinates are allowed to be negative. We start with the
  24659. * body at (0,0) so items docked "top" or "left" will simply be assigned negative x/y. In
  24660. * the {@link #finishPositions} method these are corrected and framing is added. This way
  24661. * the correction is applied as a simple translation of delta x/y on all coordinates to
  24662. * bring the origin back to (0,0).
  24663. * @private
  24664. */
  24665. dockOutwardBegin: function (ownerContext, itemContext, item, axis) {
  24666. var pos = axis.begin,
  24667. sizeProp = axis.sizeProp,
  24668. dock, size;
  24669. if (axis.collapsed) {
  24670. axis.ignoreFrameBegin = axis.ignoreFrameEnd = true;
  24671. } else if (item.ignoreParentFrame) {
  24672. dock = item.dock;
  24673. pos -= ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] +
  24674. ownerContext.framingInfo[dock];
  24675. axis.ignoreFrameBegin = true;
  24676. }
  24677. if (!item.overlay) {
  24678. size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp];
  24679. pos -= size;
  24680. axis.begin = pos;
  24681. }
  24682. return pos;
  24683. },
  24684. /**
  24685. * Docks an item on a shrinkWrap axis at the "end". The "end" of the horizontal axis is
  24686. * "right" and the vertical is "bottom".
  24687. * @private
  24688. */
  24689. dockOutwardEnd: function (ownerContext, itemContext, item, axis) {
  24690. var pos = axis.end,
  24691. sizeProp = axis.sizeProp,
  24692. dock, size;
  24693. size = itemContext.getProp(sizeProp) + itemContext.getMarginInfo()[sizeProp];
  24694. if (axis.collapsed) {
  24695. axis.ignoreFrameBegin = axis.ignoreFrameEnd = true;
  24696. } else if (item.ignoreParentFrame) {
  24697. dock = item.dock;
  24698. pos += ownerContext.borderInfo[dock] + ownerContext.paddingInfo[dock] +
  24699. ownerContext.framingInfo[dock];
  24700. axis.ignoreFrameEnd = true;
  24701. }
  24702. if (!item.overlay) {
  24703. axis.end = pos + size;
  24704. }
  24705. return pos;
  24706. },
  24707. /**
  24708. * Docks an item that might stretch across an axis. This is done for dock "top" and
  24709. * "bottom" items on the horizontal axis and dock "left" and "right" on the vertical.
  24710. * @private
  24711. */
  24712. dockStretch: function (ownerContext, itemContext, item, axis) {
  24713. var dock = item.dock, // left/top/right/bottom (also used to index padding/border)
  24714. sizeProp = axis.sizeProp, // 'width' or 'height'
  24715. horizontal = dock == 'top' || dock == 'bottom',
  24716. offsets = itemContext.offsets,
  24717. border = ownerContext.borderInfo,
  24718. padding = ownerContext.paddingInfo,
  24719. endProp = horizontal ? 'right' : 'bottom',
  24720. startProp = horizontal ? 'left' : 'top',
  24721. pos = axis.begin + offsets[startProp],
  24722. margin, size, framing;
  24723. if (item.stretch !== false) {
  24724. size = axis.end - pos - offsets[endProp];
  24725. if (item.ignoreParentFrame) {
  24726. framing = ownerContext.framingInfo;
  24727. pos -= border[startProp] + padding[startProp] + framing[startProp];
  24728. size += border[sizeProp] + padding[sizeProp] + framing[sizeProp];
  24729. }
  24730. margin = itemContext.getMarginInfo();
  24731. size -= margin[sizeProp];
  24732. itemContext[axis.setSize](size);
  24733. }
  24734. return pos;
  24735. },
  24736. /**
  24737. * Finishes the calculation of an axis by determining its size. In non-shrink-wrap
  24738. * cases, this is also where we set the body size.
  24739. * @private
  24740. */
  24741. finishAxis: function (ownerContext, axis) {
  24742. var size = axis.end - axis.begin,
  24743. setSizeMethod = axis.setSize,
  24744. beginName = axis.dockBegin, // left or top
  24745. endName = axis.dockEnd, // right or bottom
  24746. border = ownerContext.borderInfo,
  24747. padding = ownerContext.paddingInfo,
  24748. framing = ownerContext.framingInfo,
  24749. frameSize = padding[beginName] + border[beginName] + framing[beginName],
  24750. bodyContext = ownerContext.bodyContext;
  24751. if (axis.shrinkWrap) {
  24752. // Since items docked left/top on a shrinkWrap axis go into negative coordinates,
  24753. // we apply a delta to all coordinates to adjust their relative origin back to
  24754. // (0,0).
  24755. axis.delta = -axis.begin; // either 0 or a positive number
  24756. bodyContext[setSizeMethod](axis.initialSize);
  24757. if (axis.ignoreFrameBegin) {
  24758. axis.delta -= border[beginName];
  24759. bodyContext.setProp(axis.posProp, -axis.begin - frameSize);
  24760. } else {
  24761. size += frameSize;
  24762. axis.delta += padding[beginName] + framing[beginName];
  24763. bodyContext.setProp(axis.posProp, -axis.begin);
  24764. }
  24765. if (!axis.ignoreFrameEnd) {
  24766. size += padding[endName] + border[endName] + framing[endName];
  24767. }
  24768. axis.size = size; // we have to wait for min/maxWidth/Height processing
  24769. } else {
  24770. // For a fixed-size axis, we started at the outer box and already have the
  24771. // proper origin... almost... except for the owner's border.
  24772. axis.delta = -border[axis.dockBegin]; // 'left' or 'top'
  24773. // Body size is remaining space between ends of Axis.
  24774. bodyContext[setSizeMethod](size);
  24775. bodyContext.setProp(axis.posProp, axis.begin - frameSize);
  24776. }
  24777. return !isNaN(size);
  24778. },
  24779. /**
  24780. * Finishes processing of each axis by applying the min/max size constraints.
  24781. * @private
  24782. */
  24783. finishConstraints: function (ownerContext, horz, vert) {
  24784. var horzTooSmall = horz.size < horz.minSize,
  24785. horzTooBig = horz.size > horz.maxSize,
  24786. vertTooSmall = vert.size < vert.minSize,
  24787. vertTooBig = vert.size > vert.maxSize,
  24788. state = ownerContext.state,
  24789. ret = true,
  24790. configured = this.sizeModels.configured;
  24791. // Analysis of the potential constraint feedback given the possibilities for the
  24792. // various constraints:
  24793. //
  24794. // #1: h < min, v > max : (Expand width, Shrink height)
  24795. // In general, making the panel wider could possibly cause the content to
  24796. // be shorter thereby eliminating the need to reduce the height, but we
  24797. // just measured the content width given effectively infinite space in
  24798. // which to expand. This means it is very unlikey (if not impossible) for
  24799. // the height to change given more width, so no special concerns.
  24800. //
  24801. // #2: h < min, v < min : (Expand width, Expand height)
  24802. // Making panel bigger in both directions has no concerns. Again, making
  24803. // the panel wider could only reduce height, so the need to expand the
  24804. // height would remain.
  24805. //
  24806. // #3: h > max, v > max : (Shrink width, Shrink height)
  24807. // Making the panel narrower cannot cause the maxHeight violation to go
  24808. // away, so no special concerns.
  24809. //
  24810. // #4: h > max, v < min : (Shrink width, Expand height)
  24811. // Finally an interesting case! Shrinking the width can cause the height
  24812. // to increase. We cannot know if it will increase enough to avoid the
  24813. // minHeight violation, but if we apply the minHeight constraint, we will
  24814. // not be able to tell that we should not have done so. Which means, in
  24815. // this case, we must only apply the maxWidth constraint, allowing the
  24816. // layout to rerun and perhaps apply the minHeight constraint next time.
  24817. // NOTE: if we are already applying a constraint on a given axis, that axis will
  24818. // *not* be in shrinkWrap mode.
  24819. if (horz.shrinkWrap && horzTooBig && vert.shrinkWrap && vertTooSmall) { // if (#4)
  24820. state.constrainedWidth = horz.maxSize;
  24821. ownerContext.widthModel = configured; // via maxWidth config
  24822. ret = false;
  24823. } else {
  24824. if (horz.shrinkWrap) {
  24825. if (horzTooBig) {
  24826. state.constrainedWidth = horz.maxSize;
  24827. ownerContext.widthModel = configured;
  24828. ret = false;
  24829. } else if (horzTooSmall) {
  24830. state.constrainedWidth = horz.minSize;
  24831. ownerContext.widthModel = configured;
  24832. ret = false;
  24833. }
  24834. }
  24835. if (vert.shrinkWrap) {
  24836. if (vertTooBig) {
  24837. state.constrainedHeight = vert.maxSize;
  24838. ownerContext.heightModel = configured;
  24839. ret = false;
  24840. } else if (vertTooSmall) {
  24841. state.constrainedHeight = vert.minSize;
  24842. ownerContext.heightModel = configured;
  24843. ret = false;
  24844. }
  24845. }
  24846. }
  24847. if (ret) {
  24848. if (horz.shrinkWrap) {
  24849. ownerContext.setWidth(horz.size);
  24850. }
  24851. if (vert.shrinkWrap) {
  24852. ownerContext.setHeight(vert.size);
  24853. }
  24854. } else {
  24855. ownerContext.invalidate({
  24856. state: {
  24857. constrainedWidth: state.constrainedWidth,
  24858. constrainedHeight: state.constrainedHeight
  24859. }
  24860. });
  24861. }
  24862. return ret;
  24863. },
  24864. /**
  24865. * Finishes the calculation by setting positions on the body and all of the items.
  24866. * @private
  24867. */
  24868. finishPositions: function (ownerContext, horz, vert) {
  24869. var dockedItems = ownerContext.dockedItems,
  24870. length = dockedItems.length,
  24871. deltaX = horz.delta,
  24872. deltaY = vert.delta,
  24873. index, itemContext;
  24874. for (index = 0; index < length; ++index) {
  24875. itemContext = dockedItems[index];
  24876. itemContext.setProp('x', deltaX + itemContext.dockedAt.x);
  24877. itemContext.setProp('y', deltaY + itemContext.dockedAt.y);
  24878. }
  24879. },
  24880. finishedLayout: function(ownerContext) {
  24881. var me = this,
  24882. target = ownerContext.target;
  24883. me.callParent(arguments);
  24884. if (!ownerContext.animatePolicy) {
  24885. if (ownerContext.isCollapsingOrExpanding === 1) {
  24886. target.afterCollapse(false);
  24887. } else if (ownerContext.isCollapsingOrExpanding === 2) {
  24888. target.afterExpand(false);
  24889. }
  24890. }
  24891. },
  24892. getAnimatePolicy: function(ownerContext) {
  24893. var me = this,
  24894. lastCollapsedState, policy;
  24895. if (ownerContext.isCollapsingOrExpanding == 1) {
  24896. lastCollapsedState = me.lastCollapsedState;
  24897. } else if (ownerContext.isCollapsingOrExpanding == 2) {
  24898. lastCollapsedState = ownerContext.lastCollapsedState;
  24899. }
  24900. if (lastCollapsedState == 'left' || lastCollapsedState == 'right') {
  24901. policy = me.horizontalCollapsePolicy;
  24902. } else if (lastCollapsedState == 'top' || lastCollapsedState == 'bottom') {
  24903. policy = me.verticalCollapsePolicy;
  24904. }
  24905. return policy;
  24906. },
  24907. /**
  24908. * Retrieve an ordered and/or filtered array of all docked Components.
  24909. * @param {String} [order='render'] The desired ordering of the items ('render' or 'visual').
  24910. * @param {Boolean} [beforeBody] An optional flag to limit the set of items to only those
  24911. * before the body (true) or after the body (false). All components are returned by
  24912. * default.
  24913. * @return {Ext.Component[]} An array of components.
  24914. * @protected
  24915. */
  24916. getDockedItems: function(order, beforeBody) {
  24917. var me = this,
  24918. all = me.owner.dockedItems.items,
  24919. sort = all && all.length && order !== false,
  24920. renderOrder,
  24921. dock, dockedItems, i, isBefore, length;
  24922. if (beforeBody == null) {
  24923. dockedItems = sort ? all.slice() : all;
  24924. } else {
  24925. dockedItems = [];
  24926. for (i = 0, length = all.length; i < length; ++i) {
  24927. dock = all[i].dock;
  24928. isBefore = (dock == 'top' || dock == 'left');
  24929. if (beforeBody ? isBefore : !isBefore) {
  24930. dockedItems.push(all[i]);
  24931. }
  24932. }
  24933. sort = sort && dockedItems.length;
  24934. }
  24935. if (sort) {
  24936. renderOrder = (order = order || 'render') == 'render';
  24937. Ext.Array.sort(dockedItems, function(a, b) {
  24938. var aw,
  24939. bw;
  24940. // If the two items are on opposite sides of the body, they must not be sorted by any weight value:
  24941. // For rendering purposes, left/top *always* sorts before right/bottom
  24942. if (renderOrder && ((aw = me.owner.dockOrder[a.dock]) !== (bw = me.owner.dockOrder[b.dock]))) {
  24943. // The two dockOrder values cancel out when two items are on opposite sides.
  24944. if (!(aw + bw)) {
  24945. return aw - bw;
  24946. }
  24947. }
  24948. aw = me.getItemWeight(a, order);
  24949. bw = me.getItemWeight(b, order);
  24950. if ((aw !== undefined) && (bw !== undefined)) {
  24951. return aw - bw;
  24952. }
  24953. return 0;
  24954. });
  24955. }
  24956. return dockedItems || [];
  24957. },
  24958. getItemWeight: function (item, order) {
  24959. var weight = item.weight || this.owner.defaultDockWeights[item.dock];
  24960. return weight[order] || weight;
  24961. },
  24962. /**
  24963. * @protected
  24964. * Returns an array containing all the **visible** docked items inside this layout's owner Panel
  24965. * @return {Array} An array containing all the **visible** docked items of the Panel
  24966. */
  24967. getLayoutItems : function() {
  24968. var me = this,
  24969. items,
  24970. itemCount,
  24971. item,
  24972. i,
  24973. result;
  24974. if (me.owner.collapsed) {
  24975. result = me.owner.getCollapsedDockedItems();
  24976. } else {
  24977. items = me.getDockedItems('visual');
  24978. itemCount = items.length;
  24979. result = [];
  24980. for (i = 0; i < itemCount; i++) {
  24981. item = items[i];
  24982. if (!item.hidden) {
  24983. result.push(item);
  24984. }
  24985. }
  24986. }
  24987. return result;
  24988. },
  24989. // Content size includes padding but not borders, so subtract them off
  24990. measureContentWidth: function (ownerContext) {
  24991. var bodyContext = ownerContext.bodyContext;
  24992. return bodyContext.el.getWidth() - bodyContext.getBorderInfo().width;
  24993. },
  24994. measureContentHeight: function (ownerContext) {
  24995. var bodyContext = ownerContext.bodyContext;
  24996. return bodyContext.el.getHeight() - bodyContext.getBorderInfo().height;
  24997. },
  24998. redoLayout: function(ownerContext) {
  24999. var me = this,
  25000. owner = me.owner;
  25001. // If we are collapsing...
  25002. if (ownerContext.isCollapsingOrExpanding == 1) {
  25003. if (owner.reExpander) {
  25004. owner.reExpander.el.show();
  25005. }
  25006. // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout.
  25007. owner.addClsWithUI(owner.collapsedCls);
  25008. ownerContext.redo(true);
  25009. } else if (ownerContext.isCollapsingOrExpanding == 2) {
  25010. // Remove the collapsed class now, before layout calculations are done.
  25011. owner.removeClsWithUI(owner.collapsedCls);
  25012. ownerContext.bodyContext.redo();
  25013. }
  25014. },
  25015. // @private override inherited.
  25016. // We need to render in the correct order, top/left before bottom/right
  25017. renderChildren: function() {
  25018. var me = this,
  25019. items = me.getDockedItems(),
  25020. target = me.getRenderTarget();
  25021. me.renderItems(items, target);
  25022. },
  25023. /**
  25024. * @protected
  25025. * Render the top and left docked items before any existing DOM nodes in our render target,
  25026. * and then render the right and bottom docked items after. This is important, for such things
  25027. * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
  25028. * Our collection of docked items will already be ordered via Panel.getDockedItems().
  25029. */
  25030. renderItems: function(items, target) {
  25031. var me = this,
  25032. dockedItemCount = items.length,
  25033. itemIndex = 0,
  25034. correctPosition = 0,
  25035. item,
  25036. staticNodeCount = 0,
  25037. targetNodes = me.getRenderTarget().dom.childNodes,
  25038. targetChildCount = targetNodes.length,
  25039. i, j, targetChildNode, item;
  25040. // Calculate the number of DOM nodes in our target that are not our docked items
  25041. for (i = 0, j = 0; i < targetChildCount; i++) {
  25042. targetChildNode = targetNodes[i];
  25043. if (Ext.fly(targetChildNode).hasCls('x-resizable-handle')) {
  25044. break;
  25045. }
  25046. for (j = 0; j < dockedItemCount; j++) {
  25047. item = items[j];
  25048. if (item.rendered && item.el.dom === targetChildNode) {
  25049. break;
  25050. }
  25051. }
  25052. // Walked off the end of the docked items without matching the found child node;
  25053. // Then it's a static node.
  25054. if (j === dockedItemCount) {
  25055. staticNodeCount++;
  25056. }
  25057. }
  25058. // Now we go through our docked items and render/move them
  25059. for (; itemIndex < dockedItemCount; itemIndex++, correctPosition++) {
  25060. item = items[itemIndex];
  25061. // If we're now at the first right/bottom docked item, we jump over the body element.
  25062. //
  25063. // TODO: This is affected if users provide custom weight values to their
  25064. // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
  25065. // sort operation here, for now, in the name of performance. getDockedItems()
  25066. // needs the sort operation not just for this layout-time rendering, but
  25067. // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
  25068. if (itemIndex === correctPosition && (item.dock === 'right' || item.dock === 'bottom')) {
  25069. correctPosition += staticNodeCount;
  25070. }
  25071. // Same logic as Layout.renderItems()
  25072. if (item && !item.rendered) {
  25073. me.renderItem(item, target, correctPosition);
  25074. }
  25075. else if (!me.isValidParent(item, target, correctPosition)) {
  25076. me.moveItem(item, target, correctPosition);
  25077. }
  25078. }
  25079. },
  25080. undoLayout: function(ownerContext) {
  25081. var me = this,
  25082. owner = me.owner;
  25083. // If we are collapsing...
  25084. if (ownerContext.isCollapsingOrExpanding == 1) {
  25085. // We do not want to see the re-expander header until the final collapse is complete
  25086. if (owner.reExpander) {
  25087. owner.reExpander.el.hide();
  25088. }
  25089. // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken by the layout.
  25090. owner.removeClsWithUI(owner.collapsedCls);
  25091. ownerContext.undo(true);
  25092. } else if (ownerContext.isCollapsingOrExpanding == 2) {
  25093. // Remove the collapsed class now, before layout calculations are done.
  25094. owner.addClsWithUI(owner.collapsedCls);
  25095. ownerContext.bodyContext.undo();
  25096. }
  25097. },
  25098. sizePolicy: {
  25099. nostretch: {
  25100. setsWidth: 0,
  25101. setsHeight: 0
  25102. },
  25103. stretchH: {
  25104. setsWidth: 1,
  25105. setsHeight: 0
  25106. },
  25107. stretchV: {
  25108. setsWidth: 0,
  25109. setsHeight: 1
  25110. },
  25111. // Circular dependency with partial auto-sized panels:
  25112. //
  25113. // If we have an autoHeight docked item being stretched horizontally (top/bottom),
  25114. // that stretching will determine its width and its width must be set before its
  25115. // autoHeight can be determined. If that item is docked in an autoWidth panel, the
  25116. // body will need its height set before it can determine its width, but the height
  25117. // of the docked item is needed to subtract from the panel height in order to set
  25118. // the body height.
  25119. //
  25120. // This same pattern occurs with autoHeight panels with autoWidth docked items on
  25121. // left or right. If the panel is fully auto or fully fixed, these problems don't
  25122. // come up because there is no dependency between the dimensions.
  25123. //
  25124. // Cutting the Gordian Knot: In these cases, we have to allow something to measure
  25125. // itself without full context. This is OK as long as the managed dimension doesn't
  25126. // effect the auto-dimension, which is often the case for things like toolbars. The
  25127. // managed dimension only effects overflow handlers and such and does not change the
  25128. // auto-dimension. To encourage the item to measure itself without waiting for the
  25129. // managed dimension, we have to tell it that the layout will also be reading that
  25130. // dimension. This is similar to how stretchmax works.
  25131. autoStretchH: {
  25132. readsWidth: 1,
  25133. setsWidth: 1,
  25134. setsHeight: 0
  25135. },
  25136. autoStretchV: {
  25137. readsHeight: 1,
  25138. setsWidth: 0,
  25139. setsHeight: 1
  25140. }
  25141. },
  25142. getItemSizePolicy: function (item) {
  25143. var policy = this.sizePolicy,
  25144. dock, vertical;
  25145. if (item.stretch === false) {
  25146. return policy.nostretch;
  25147. }
  25148. dock = item.dock;
  25149. vertical = (dock == 'left' || dock == 'right');
  25150. /*
  25151. owner = this.owner;
  25152. autoWidth = !owner.isFixedWidth();
  25153. autoHeight = !owner.isFixedHeight();
  25154. if (autoWidth !== autoHeight) { // if (partial auto)
  25155. // see above...
  25156. if (vertical) {
  25157. if (autoHeight) {
  25158. return policy.autoStretchV;
  25159. }
  25160. } else if (autoWidth) {
  25161. return policy.autoStretchH;
  25162. }
  25163. }*/
  25164. if (vertical) {
  25165. return policy.stretchV;
  25166. }
  25167. return policy.stretchH;
  25168. },
  25169. /**
  25170. * @protected
  25171. * We are overriding the Ext.layout.Layout configureItem method to also add a class that
  25172. * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
  25173. * An example of a class added to a dock: right item is x-docked-right
  25174. * @param {Ext.Component} item The item we are configuring
  25175. */
  25176. configureItem : function(item, pos) {
  25177. this.callParent(arguments);
  25178. item.addCls(Ext.baseCSSPrefix + 'docked');
  25179. item.addClsWithUI('docked-' + item.dock);
  25180. },
  25181. afterRemove : function(item) {
  25182. this.callParent(arguments);
  25183. if (this.itemCls) {
  25184. item.el.removeCls(this.itemCls + '-' + item.dock);
  25185. }
  25186. var dom = item.el.dom;
  25187. if (!item.destroying && dom) {
  25188. dom.parentNode.removeChild(dom);
  25189. }
  25190. this.childrenChanged = true;
  25191. }
  25192. });
  25193. /**
  25194. * This class represents a rectangular region in X,Y space, and performs geometric
  25195. * transformations or tests upon the region.
  25196. *
  25197. * This class may be used to compare the document regions occupied by elements.
  25198. */
  25199. Ext.define('Ext.util.Region', {
  25200. /* Begin Definitions */
  25201. requires: ['Ext.util.Offset'],
  25202. statics: {
  25203. /**
  25204. * @static
  25205. * Retrieves an Ext.util.Region for a particular element.
  25206. * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
  25207. * @returns {Ext.util.Region} region
  25208. */
  25209. getRegion: function(el) {
  25210. return Ext.fly(el).getPageBox(true);
  25211. },
  25212. /**
  25213. * @static
  25214. * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
  25215. * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
  25216. * @return {Ext.util.Region} region The Region constructed based on the passed object
  25217. */
  25218. from: function(o) {
  25219. return new this(o.top, o.right, o.bottom, o.left);
  25220. }
  25221. },
  25222. /* End Definitions */
  25223. /**
  25224. * Creates a region from the bounding sides.
  25225. * @param {Number} top Top The topmost pixel of the Region.
  25226. * @param {Number} right Right The rightmost pixel of the Region.
  25227. * @param {Number} bottom Bottom The bottom pixel of the Region.
  25228. * @param {Number} left Left The leftmost pixel of the Region.
  25229. */
  25230. constructor : function(t, r, b, l) {
  25231. var me = this;
  25232. me.y = me.top = me[1] = t;
  25233. me.right = r;
  25234. me.bottom = b;
  25235. me.x = me.left = me[0] = l;
  25236. },
  25237. /**
  25238. * Checks if this region completely contains the region that is passed in.
  25239. * @param {Ext.util.Region} region
  25240. * @return {Boolean}
  25241. */
  25242. contains : function(region) {
  25243. var me = this;
  25244. return (region.x >= me.x &&
  25245. region.right <= me.right &&
  25246. region.y >= me.y &&
  25247. region.bottom <= me.bottom);
  25248. },
  25249. /**
  25250. * Checks if this region intersects the region passed in.
  25251. * @param {Ext.util.Region} region
  25252. * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
  25253. */
  25254. intersect : function(region) {
  25255. var me = this,
  25256. t = Math.max(me.y, region.y),
  25257. r = Math.min(me.right, region.right),
  25258. b = Math.min(me.bottom, region.bottom),
  25259. l = Math.max(me.x, region.x);
  25260. if (b > t && r > l) {
  25261. return new this.self(t, r, b, l);
  25262. }
  25263. else {
  25264. return false;
  25265. }
  25266. },
  25267. /**
  25268. * Returns the smallest region that contains the current AND targetRegion.
  25269. * @param {Ext.util.Region} region
  25270. * @return {Ext.util.Region} a new region
  25271. */
  25272. union : function(region) {
  25273. var me = this,
  25274. t = Math.min(me.y, region.y),
  25275. r = Math.max(me.right, region.right),
  25276. b = Math.max(me.bottom, region.bottom),
  25277. l = Math.min(me.x, region.x);
  25278. return new this.self(t, r, b, l);
  25279. },
  25280. /**
  25281. * Modifies the current region to be constrained to the targetRegion.
  25282. * @param {Ext.util.Region} targetRegion
  25283. * @return {Ext.util.Region} this
  25284. */
  25285. constrainTo : function(r) {
  25286. var me = this,
  25287. constrain = Ext.Number.constrain;
  25288. me.top = me.y = constrain(me.top, r.y, r.bottom);
  25289. me.bottom = constrain(me.bottom, r.y, r.bottom);
  25290. me.left = me.x = constrain(me.left, r.x, r.right);
  25291. me.right = constrain(me.right, r.x, r.right);
  25292. return me;
  25293. },
  25294. /**
  25295. * Modifies the current region to be adjusted by offsets.
  25296. * @param {Number} top top offset
  25297. * @param {Number} right right offset
  25298. * @param {Number} bottom bottom offset
  25299. * @param {Number} left left offset
  25300. * @return {Ext.util.Region} this
  25301. */
  25302. adjust : function(t, r, b, l) {
  25303. var me = this;
  25304. me.top = me.y += t;
  25305. me.left = me.x += l;
  25306. me.right += r;
  25307. me.bottom += b;
  25308. return me;
  25309. },
  25310. /**
  25311. * Get the offset amount of a point outside the region
  25312. * @param {String} [axis]
  25313. * @param {Ext.util.Point} [p] the point
  25314. * @return {Ext.util.Offset}
  25315. */
  25316. getOutOfBoundOffset: function(axis, p) {
  25317. if (!Ext.isObject(axis)) {
  25318. if (axis == 'x') {
  25319. return this.getOutOfBoundOffsetX(p);
  25320. } else {
  25321. return this.getOutOfBoundOffsetY(p);
  25322. }
  25323. } else {
  25324. p = axis;
  25325. var d = new Ext.util.Offset();
  25326. d.x = this.getOutOfBoundOffsetX(p.x);
  25327. d.y = this.getOutOfBoundOffsetY(p.y);
  25328. return d;
  25329. }
  25330. },
  25331. /**
  25332. * Get the offset amount on the x-axis
  25333. * @param {Number} p the offset
  25334. * @return {Number}
  25335. */
  25336. getOutOfBoundOffsetX: function(p) {
  25337. if (p <= this.x) {
  25338. return this.x - p;
  25339. } else if (p >= this.right) {
  25340. return this.right - p;
  25341. }
  25342. return 0;
  25343. },
  25344. /**
  25345. * Get the offset amount on the y-axis
  25346. * @param {Number} p the offset
  25347. * @return {Number}
  25348. */
  25349. getOutOfBoundOffsetY: function(p) {
  25350. if (p <= this.y) {
  25351. return this.y - p;
  25352. } else if (p >= this.bottom) {
  25353. return this.bottom - p;
  25354. }
  25355. return 0;
  25356. },
  25357. /**
  25358. * Check whether the point / offset is out of bound
  25359. * @param {String} [axis]
  25360. * @param {Ext.util.Point/Number} [p] the point / offset
  25361. * @return {Boolean}
  25362. */
  25363. isOutOfBound: function(axis, p) {
  25364. if (!Ext.isObject(axis)) {
  25365. if (axis == 'x') {
  25366. return this.isOutOfBoundX(p);
  25367. } else {
  25368. return this.isOutOfBoundY(p);
  25369. }
  25370. } else {
  25371. p = axis;
  25372. return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
  25373. }
  25374. },
  25375. /**
  25376. * Check whether the offset is out of bound in the x-axis
  25377. * @param {Number} p the offset
  25378. * @return {Boolean}
  25379. */
  25380. isOutOfBoundX: function(p) {
  25381. return (p < this.x || p > this.right);
  25382. },
  25383. /**
  25384. * Check whether the offset is out of bound in the y-axis
  25385. * @param {Number} p the offset
  25386. * @return {Boolean}
  25387. */
  25388. isOutOfBoundY: function(p) {
  25389. return (p < this.y || p > this.bottom);
  25390. },
  25391. /**
  25392. * Restrict a point within the region by a certain factor.
  25393. * @param {String} [axis]
  25394. * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
  25395. * @param {Number} [factor]
  25396. * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
  25397. * @private
  25398. */
  25399. restrict: function(axis, p, factor) {
  25400. if (Ext.isObject(axis)) {
  25401. var newP;
  25402. factor = p;
  25403. p = axis;
  25404. if (p.copy) {
  25405. newP = p.copy();
  25406. }
  25407. else {
  25408. newP = {
  25409. x: p.x,
  25410. y: p.y
  25411. };
  25412. }
  25413. newP.x = this.restrictX(p.x, factor);
  25414. newP.y = this.restrictY(p.y, factor);
  25415. return newP;
  25416. } else {
  25417. if (axis == 'x') {
  25418. return this.restrictX(p, factor);
  25419. } else {
  25420. return this.restrictY(p, factor);
  25421. }
  25422. }
  25423. },
  25424. /**
  25425. * Restrict an offset within the region by a certain factor, on the x-axis
  25426. * @param {Number} p
  25427. * @param {Number} [factor=1] The factor.
  25428. * @return {Number}
  25429. * @private
  25430. */
  25431. restrictX : function(p, factor) {
  25432. if (!factor) {
  25433. factor = 1;
  25434. }
  25435. if (p <= this.x) {
  25436. p -= (p - this.x) * factor;
  25437. }
  25438. else if (p >= this.right) {
  25439. p -= (p - this.right) * factor;
  25440. }
  25441. return p;
  25442. },
  25443. /**
  25444. * Restrict an offset within the region by a certain factor, on the y-axis
  25445. * @param {Number} p
  25446. * @param {Number} [factor] The factor, defaults to 1
  25447. * @return {Number}
  25448. * @private
  25449. */
  25450. restrictY : function(p, factor) {
  25451. if (!factor) {
  25452. factor = 1;
  25453. }
  25454. if (p <= this.y) {
  25455. p -= (p - this.y) * factor;
  25456. }
  25457. else if (p >= this.bottom) {
  25458. p -= (p - this.bottom) * factor;
  25459. }
  25460. return p;
  25461. },
  25462. /**
  25463. * Get the width / height of this region
  25464. * @return {Object} an object with width and height properties
  25465. * @private
  25466. */
  25467. getSize: function() {
  25468. return {
  25469. width: this.right - this.x,
  25470. height: this.bottom - this.y
  25471. };
  25472. },
  25473. /**
  25474. * Create a copy of this Region.
  25475. * @return {Ext.util.Region}
  25476. */
  25477. copy: function() {
  25478. return new this.self(this.y, this.right, this.bottom, this.x);
  25479. },
  25480. /**
  25481. * Copy the values of another Region to this Region
  25482. * @param {Ext.util.Region} p The region to copy from.
  25483. * @return {Ext.util.Region} This Region
  25484. */
  25485. copyFrom: function(p) {
  25486. var me = this;
  25487. me.top = me.y = me[1] = p.y;
  25488. me.right = p.right;
  25489. me.bottom = p.bottom;
  25490. me.left = me.x = me[0] = p.x;
  25491. return this;
  25492. },
  25493. /*
  25494. * Dump this to an eye-friendly string, great for debugging
  25495. * @return {String}
  25496. */
  25497. toString: function() {
  25498. return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
  25499. },
  25500. /**
  25501. * Translate this region by the given offset amount
  25502. * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
  25503. * Or the x value is using the two argument form.
  25504. * @param {Number} y The y value unless using an Offset object.
  25505. * @return {Ext.util.Region} this This Region
  25506. */
  25507. translateBy: function(x, y) {
  25508. if (arguments.length == 1) {
  25509. y = x.y;
  25510. x = x.x;
  25511. }
  25512. var me = this;
  25513. me.top = me.y += y;
  25514. me.right += x;
  25515. me.bottom += y;
  25516. me.left = me.x += x;
  25517. return me;
  25518. },
  25519. /**
  25520. * Round all the properties of this region
  25521. * @return {Ext.util.Region} this This Region
  25522. */
  25523. round: function() {
  25524. var me = this;
  25525. me.top = me.y = Math.round(me.y);
  25526. me.right = Math.round(me.right);
  25527. me.bottom = Math.round(me.bottom);
  25528. me.left = me.x = Math.round(me.x);
  25529. return me;
  25530. },
  25531. /**
  25532. * Check whether this region is equivalent to the given region
  25533. * @param {Ext.util.Region} region The region to compare with
  25534. * @return {Boolean}
  25535. */
  25536. equals: function(region) {
  25537. return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
  25538. }
  25539. });
  25540. /*
  25541. * This is a derivative of the similarly named class in the YUI Library.
  25542. * The original license:
  25543. * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
  25544. * Code licensed under the BSD License:
  25545. * http://developer.yahoo.net/yui/license.txt
  25546. */
  25547. /**
  25548. * DragDropManager is a singleton that tracks the element interaction for
  25549. * all DragDrop items in the window. Generally, you will not call
  25550. * this class directly, but it does have helper methods that could
  25551. * be useful in your DragDrop implementations.
  25552. */
  25553. Ext.define('Ext.dd.DragDropManager', {
  25554. singleton: true,
  25555. requires: ['Ext.util.Region'],
  25556. uses: ['Ext.tip.QuickTipManager'],
  25557. // shorter ClassName, to save bytes and use internally
  25558. alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
  25559. /**
  25560. * @property {String[]} ids
  25561. * Two dimensional Array of registered DragDrop objects. The first
  25562. * dimension is the DragDrop item group, the second the DragDrop
  25563. * object.
  25564. * @private
  25565. */
  25566. ids: {},
  25567. /**
  25568. * @property {String[]} handleIds
  25569. * Array of element ids defined as drag handles. Used to determine
  25570. * if the element that generated the mousedown event is actually the
  25571. * handle and not the html element itself.
  25572. * @private
  25573. */
  25574. handleIds: {},