RowEditor.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js">// Currently has the following issues:
  19. // - Does not handle postEditValue
  20. // - Fields without editors need to sync with their values in Store
  21. // - starting to edit another record while already editing and dirty should probably prevent it
  22. // - aggregating validation messages
  23. // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
  24. // - layout issues when changing sizes/width while hidden (layout bug)
  25. <span id='Ext-grid-RowEditor'>/**
  26. </span> * Internal utility class used to provide row editing functionality. For developers, they should use
  27. * the RowEditing plugin to use this functionality with a grid.
  28. *
  29. * @private
  30. */
  31. Ext.define('Ext.grid.RowEditor', {
  32. extend: 'Ext.form.Panel',
  33. requires: [
  34. 'Ext.tip.ToolTip',
  35. 'Ext.util.HashMap',
  36. 'Ext.util.KeyNav'
  37. ],
  38. //&lt;locale&gt;
  39. saveBtnText : 'Update',
  40. //&lt;/locale&gt;
  41. //&lt;locale&gt;
  42. cancelBtnText: 'Cancel',
  43. //&lt;/locale&gt;
  44. //&lt;locale&gt;
  45. errorsText: 'Errors',
  46. //&lt;/locale&gt;
  47. //&lt;locale&gt;
  48. dirtyText: 'You need to commit or cancel your changes',
  49. //&lt;/locale&gt;
  50. lastScrollLeft: 0,
  51. lastScrollTop: 0,
  52. border: false,
  53. // Change the hideMode to offsets so that we get accurate measurements when
  54. // the roweditor is hidden for laying out things like a TriggerField.
  55. hideMode: 'offsets',
  56. initComponent: function() {
  57. var me = this,
  58. form;
  59. me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
  60. me.layout = {
  61. type: 'hbox',
  62. align: 'middle'
  63. };
  64. // Maintain field-to-column mapping
  65. // It's easy to get a field from a column, but not vice versa
  66. me.columns = new Ext.util.HashMap();
  67. me.columns.getKey = function(columnHeader) {
  68. var f;
  69. if (columnHeader.getEditor) {
  70. f = columnHeader.getEditor();
  71. if (f) {
  72. return f.id;
  73. }
  74. }
  75. return columnHeader.id;
  76. };
  77. me.mon(me.columns, {
  78. add: me.onFieldAdd,
  79. remove: me.onFieldRemove,
  80. replace: me.onFieldReplace,
  81. scope: me
  82. });
  83. me.callParent(arguments);
  84. if (me.fields) {
  85. me.setField(me.fields);
  86. delete me.fields;
  87. }
  88. me.mon(Ext.container.Container.hierarchyEventSource, {
  89. scope: me,
  90. show: me.repositionIfVisible
  91. });
  92. form = me.getForm();
  93. form.trackResetOnLoad = true;
  94. },
  95. onFieldChange: function() {
  96. var me = this,
  97. form = me.getForm(),
  98. valid = form.isValid();
  99. if (me.errorSummary &amp;&amp; me.isVisible()) {
  100. me[valid ? 'hideToolTip' : 'showToolTip']();
  101. }
  102. me.updateButton(valid);
  103. me.isValid = valid;
  104. },
  105. updateButton: function(valid){
  106. var buttons = this.floatingButtons;
  107. if (buttons) {
  108. buttons.child('#update').setDisabled(!valid);
  109. }
  110. },
  111. afterRender: function() {
  112. var me = this,
  113. plugin = me.editingPlugin;
  114. me.callParent(arguments);
  115. me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
  116. // Prevent from bubbling click events to the grid view
  117. me.mon(me.el, {
  118. click: Ext.emptyFn,
  119. stopPropagation: true
  120. });
  121. me.el.swallowEvent([
  122. 'keypress',
  123. 'keydown'
  124. ]);
  125. me.keyNav = new Ext.util.KeyNav(me.el, {
  126. enter: plugin.completeEdit,
  127. esc: plugin.onEscKey,
  128. scope: plugin
  129. });
  130. me.mon(plugin.view, {
  131. beforerefresh: me.onBeforeViewRefresh,
  132. refresh: me.onViewRefresh,
  133. itemremove: me.onViewItemRemove,
  134. scope: me
  135. });
  136. },
  137. onBeforeViewRefresh: function(view) {
  138. var me = this,
  139. viewDom = view.el.dom;
  140. if (me.el.dom.parentNode === viewDom) {
  141. viewDom.removeChild(me.el.dom);
  142. }
  143. },
  144. onViewRefresh: function(view) {
  145. var me = this,
  146. viewDom = view.el.dom,
  147. context = me.context,
  148. idx;
  149. viewDom.appendChild(me.el.dom);
  150. // Recover our row node after a view refresh
  151. if (context &amp;&amp; (idx = context.store.indexOf(context.record)) &gt;= 0) {
  152. context.row = view.getNode(idx);
  153. me.reposition();
  154. if (me.tooltip &amp;&amp; me.tooltip.isVisible()) {
  155. me.tooltip.setTarget(context.row);
  156. }
  157. } else {
  158. me.editingPlugin.cancelEdit();
  159. }
  160. },
  161. onViewItemRemove: function(record, index) {
  162. var context = this.context;
  163. if (context &amp;&amp; record === context.record) {
  164. // if the record being edited was removed, cancel editing
  165. this.editingPlugin.cancelEdit();
  166. }
  167. },
  168. onCtScroll: function(e, target) {
  169. var me = this,
  170. scrollTop = target.scrollTop,
  171. scrollLeft = target.scrollLeft;
  172. if (scrollTop !== me.lastScrollTop) {
  173. me.lastScrollTop = scrollTop;
  174. if ((me.tooltip &amp;&amp; me.tooltip.isVisible()) || me.hiddenTip) {
  175. me.repositionTip();
  176. }
  177. }
  178. if (scrollLeft !== me.lastScrollLeft) {
  179. me.lastScrollLeft = scrollLeft;
  180. me.reposition();
  181. }
  182. },
  183. onColumnAdd: function(column) {
  184. if (!column.isGroupHeader) {
  185. this.setField(column);
  186. }
  187. },
  188. onColumnRemove: function(column) {
  189. this.columns.remove(column);
  190. },
  191. onColumnResize: function(column, width) {
  192. if (!column.isGroupHeader) {
  193. column.getEditor().setWidth(width - 2);
  194. this.repositionIfVisible();
  195. }
  196. },
  197. onColumnHide: function(column) {
  198. if (!column.isGroupHeader) {
  199. column.getEditor().hide();
  200. this.repositionIfVisible();
  201. }
  202. },
  203. onColumnShow: function(column) {
  204. var field = column.getEditor();
  205. field.setWidth(column.getWidth() - 2).show();
  206. this.repositionIfVisible();
  207. },
  208. onColumnMove: function(column, fromIdx, toIdx) {
  209. if (!column.isGroupHeader) {
  210. var field = column.getEditor();
  211. if (this.items.indexOf(field) != toIdx) {
  212. this.move(fromIdx, toIdx);
  213. }
  214. }
  215. },
  216. onFieldAdd: function(map, fieldId, column) {
  217. var me = this,
  218. colIdx,
  219. field;
  220. if (!column.isGroupHeader) {
  221. colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column);
  222. field = column.getEditor({ xtype: 'displayfield' });
  223. me.insert(colIdx, field);
  224. }
  225. },
  226. onFieldRemove: function(map, fieldId, column) {
  227. var me = this,
  228. field,
  229. fieldEl;
  230. if (!column.isGroupHeader) {
  231. field = column.getEditor();
  232. fieldEl = field.el;
  233. me.remove(field, false);
  234. if (fieldEl) {
  235. fieldEl.remove();
  236. }
  237. }
  238. },
  239. onFieldReplace: function(map, fieldId, column, oldColumn) {
  240. this.onFieldRemove(map, fieldId, oldColumn);
  241. },
  242. clearFields: function() {
  243. var map = this.columns,
  244. key;
  245. for (key in map) {
  246. if (map.hasOwnProperty(key)) {
  247. map.removeAtKey(key);
  248. }
  249. }
  250. },
  251. getFloatingButtons: function() {
  252. var me = this,
  253. cssPrefix = Ext.baseCSSPrefix,
  254. btnsCss = cssPrefix + 'grid-row-editor-buttons',
  255. plugin = me.editingPlugin,
  256. minWidth = Ext.panel.Panel.prototype.minButtonWidth,
  257. btns;
  258. if (!me.floatingButtons) {
  259. btns = me.floatingButtons = new Ext.Container({
  260. renderTpl: [
  261. '&lt;div class=&quot;{baseCls}-ml&quot;&gt;&lt;/div&gt;',
  262. '&lt;div class=&quot;{baseCls}-mr&quot;&gt;&lt;/div&gt;',
  263. '&lt;div class=&quot;{baseCls}-bl&quot;&gt;&lt;/div&gt;',
  264. '&lt;div class=&quot;{baseCls}-br&quot;&gt;&lt;/div&gt;',
  265. '&lt;div class=&quot;{baseCls}-bc&quot;&gt;&lt;/div&gt;',
  266. '{%this.renderContainer(out,values)%}'
  267. ],
  268. width: 200,
  269. renderTo: me.el,
  270. baseCls: btnsCss,
  271. layout: {
  272. type: 'hbox',
  273. align: 'middle'
  274. },
  275. defaults: {
  276. flex: 1,
  277. margins: '0 1 0 1'
  278. },
  279. items: [{
  280. itemId: 'update',
  281. xtype: 'button',
  282. handler: plugin.completeEdit,
  283. scope: plugin,
  284. text: me.saveBtnText,
  285. minWidth: minWidth
  286. }, {
  287. xtype: 'button',
  288. handler: plugin.cancelEdit,
  289. scope: plugin,
  290. text: me.cancelBtnText,
  291. minWidth: minWidth
  292. }]
  293. });
  294. // Prevent from bubbling click events to the grid view
  295. me.mon(btns.el, {
  296. // BrowserBug: Opera 11.01
  297. // causes the view to scroll when a button is focused from mousedown
  298. mousedown: Ext.emptyFn,
  299. click: Ext.emptyFn,
  300. stopEvent: true
  301. });
  302. }
  303. return me.floatingButtons;
  304. },
  305. repositionIfVisible: function(c){
  306. var me = this,
  307. view = me.view;
  308. // If we're showing ourselves, jump out
  309. // If the component we're showing doesn't contain the view
  310. if (c &amp;&amp; (c == me || !view.isDescendantOf(c))) {
  311. return;
  312. }
  313. if (me.isVisible() &amp;&amp; view.isVisible(true)) {
  314. me.reposition();
  315. }
  316. },
  317. reposition: function(animateConfig) {
  318. var me = this,
  319. context = me.context,
  320. row = context &amp;&amp; Ext.get(context.row),
  321. btns = me.getFloatingButtons(),
  322. btnEl = btns.el,
  323. grid = me.editingPlugin.grid,
  324. viewEl = grid.view.el,
  325. // always get data from ColumnModel as its what drives
  326. // the GridView's sizing
  327. mainBodyWidth = grid.headerCt.getFullWidth(),
  328. scrollerWidth = grid.getWidth(),
  329. // use the minimum as the columns may not fill up the entire grid
  330. // width
  331. width = Math.min(mainBodyWidth, scrollerWidth),
  332. scrollLeft = grid.view.el.dom.scrollLeft,
  333. btnWidth = btns.getWidth(),
  334. left = (width - btnWidth) / 2 + scrollLeft,
  335. y, rowH, newHeight,
  336. invalidateScroller = function() {
  337. btnEl.scrollIntoView(viewEl, false);
  338. if (animateConfig &amp;&amp; animateConfig.callback) {
  339. animateConfig.callback.call(animateConfig.scope || me);
  340. }
  341. },
  342. animObj;
  343. // need to set both top/left
  344. if (row &amp;&amp; Ext.isElement(row.dom)) {
  345. // Bring our row into view if necessary, so a row editor that's already
  346. // visible and animated to the row will appear smooth
  347. row.scrollIntoView(viewEl, false);
  348. // Get the y position of the row relative to its top-most static parent.
  349. // offsetTop will be relative to the table, and is incorrect
  350. // when mixed with certain grid features (e.g., grouping).
  351. y = row.getXY()[1] - 5;
  352. rowH = row.getHeight();
  353. newHeight = rowH + (me.editingPlugin.grid.rowLines ? 9 : 10);
  354. // Set editor height to match the row height
  355. if (me.getHeight() != newHeight) {
  356. me.setHeight(newHeight);
  357. me.el.setLeft(0);
  358. }
  359. if (animateConfig) {
  360. animObj = {
  361. to: {
  362. y: y
  363. },
  364. duration: animateConfig.duration || 125,
  365. listeners: {
  366. afteranimate: function() {
  367. invalidateScroller();
  368. y = row.getXY()[1] - 5;
  369. }
  370. }
  371. };
  372. me.el.animate(animObj);
  373. } else {
  374. me.el.setY(y);
  375. invalidateScroller();
  376. }
  377. }
  378. if (me.getWidth() != mainBodyWidth) {
  379. me.setWidth(mainBodyWidth);
  380. }
  381. btnEl.setLeft(left);
  382. },
  383. getEditor: function(fieldInfo) {
  384. var me = this;
  385. if (Ext.isNumber(fieldInfo)) {
  386. // Query only form fields. This just future-proofs us in case we add
  387. // other components to RowEditor later on. Don't want to mess with
  388. // indices.
  389. return me.query('&gt;[isFormField]')[fieldInfo];
  390. } else if (fieldInfo.isHeader &amp;&amp; !fieldInfo.isGroupHeader) {
  391. return fieldInfo.getEditor();
  392. }
  393. },
  394. removeField: function(field) {
  395. var me = this;
  396. // Incase we pass a column instead, which is fine
  397. field = me.getEditor(field);
  398. me.mun(field, 'validitychange', me.onValidityChange, me);
  399. // Remove field/column from our mapping, which will fire the event to
  400. // remove the field from our container
  401. me.columns.removeAtKey(field.id);
  402. Ext.destroy(field);
  403. },
  404. setField: function(column) {
  405. var me = this,
  406. i,
  407. length, field;
  408. if (Ext.isArray(column)) {
  409. length = column.length;
  410. for (i = 0; i &lt; length; i++) {
  411. me.setField(column[i]);
  412. }
  413. return;
  414. }
  415. // Get a default display field if necessary
  416. field = column.getEditor(null, {
  417. xtype: 'displayfield',
  418. // Override Field's implementation so that the default display fields will not return values. This is done because
  419. // the display field will pick up column renderers from the grid.
  420. getModelData: function() {
  421. return null;
  422. }
  423. });
  424. field.margins = '0 0 0 2';
  425. me.mon(field, 'change', me.onFieldChange, me);
  426. if (me.isVisible() &amp;&amp; me.context) {
  427. if (field.is('displayfield')) {
  428. me.renderColumnData(field, me.context.record, column);
  429. } else {
  430. field.suspendEvents();
  431. field.setValue(me.context.record.get(column.dataIndex));
  432. field.resumeEvents();
  433. }
  434. }
  435. // Maintain mapping of fields-to-columns
  436. // This will fire events that maintain our container items
  437. me.columns.add(field.id, column);
  438. if (column.hidden) {
  439. me.onColumnHide(column);
  440. } else if (column.rendered) {
  441. // Setting after initial render
  442. me.onColumnShow(column);
  443. }
  444. },
  445. loadRecord: function(record) {
  446. var me = this,
  447. form = me.getForm(),
  448. fields = form.getFields(),
  449. items = fields.items,
  450. length = items.length,
  451. i, displayFields,
  452. isValid;
  453. // temporarily suspend events on form fields before loading record to prevent the fields' change events from firing
  454. for (i = 0; i &lt; length; i++) {
  455. items[i].suspendEvents();
  456. }
  457. form.loadRecord(record);
  458. for (i = 0; i &lt; length; i++) {
  459. items[i].resumeEvents();
  460. }
  461. isValid = form.isValid();
  462. if (me.errorSummary) {
  463. if (isValid) {
  464. me.hideToolTip();
  465. } else {
  466. me.showToolTip();
  467. }
  468. }
  469. me.updateButton(isValid);
  470. // render display fields so they honor the column renderer/template
  471. displayFields = me.query('&gt;displayfield');
  472. length = displayFields.length;
  473. for (i = 0; i &lt; length; i++) {
  474. me.renderColumnData(displayFields[i], record);
  475. }
  476. },
  477. renderColumnData: function(field, record, activeColumn) {
  478. var me = this,
  479. grid = me.editingPlugin.grid,
  480. headerCt = grid.headerCt,
  481. view = grid.view,
  482. store = view.store,
  483. column = activeColumn || me.columns.get(field.id),
  484. value = record.get(column.dataIndex),
  485. renderer = column.editRenderer || column.renderer,
  486. metaData,
  487. rowIdx,
  488. colIdx;
  489. // honor our column's renderer (TemplateHeader sets renderer for us!)
  490. if (renderer) {
  491. metaData = { tdCls: '', style: '' };
  492. rowIdx = store.indexOf(record);
  493. colIdx = headerCt.getHeaderIndex(column);
  494. value = renderer.call(
  495. column.scope || headerCt.ownerCt,
  496. value,
  497. metaData,
  498. record,
  499. rowIdx,
  500. colIdx,
  501. store,
  502. view
  503. );
  504. }
  505. field.setRawValue(value);
  506. field.resetOriginalValue();
  507. },
  508. beforeEdit: function() {
  509. var me = this;
  510. if (me.isVisible() &amp;&amp; me.errorSummary &amp;&amp; !me.autoCancel &amp;&amp; me.isDirty()) {
  511. me.showToolTip();
  512. return false;
  513. }
  514. },
  515. <span id='Ext-grid-RowEditor-method-startEdit'> /**
  516. </span> * Start editing the specified grid at the specified position.
  517. * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
  518. * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
  519. */
  520. startEdit: function(record, columnHeader) {
  521. var me = this,
  522. grid = me.editingPlugin.grid,
  523. store = grid.store,
  524. context = me.context = Ext.apply(me.editingPlugin.context, {
  525. view: grid.getView(),
  526. store: store
  527. });
  528. // make sure our row is selected before editing
  529. context.grid.getSelectionModel().select(record);
  530. // Reload the record data
  531. me.loadRecord(record);
  532. if (!me.isVisible()) {
  533. me.show();
  534. me.focusContextCell();
  535. } else {
  536. me.reposition({
  537. callback: this.focusContextCell
  538. });
  539. }
  540. },
  541. // Focus the cell on start edit based upon the current context
  542. focusContextCell: function() {
  543. var field = this.getEditor(this.context.colIdx);
  544. if (field &amp;&amp; field.focus) {
  545. field.focus();
  546. }
  547. },
  548. cancelEdit: function() {
  549. var me = this,
  550. form = me.getForm(),
  551. fields = form.getFields(),
  552. items = fields.items,
  553. length = items.length,
  554. i;
  555. me.hide();
  556. form.clearInvalid();
  557. // temporarily suspend events on form fields before reseting the form to prevent the fields' change events from firing
  558. for (i = 0; i &lt; length; i++) {
  559. items[i].suspendEvents();
  560. }
  561. form.reset();
  562. for (i = 0; i &lt; length; i++) {
  563. items[i].resumeEvents();
  564. }
  565. },
  566. completeEdit: function() {
  567. var me = this,
  568. form = me.getForm();
  569. if (!form.isValid()) {
  570. return;
  571. }
  572. form.updateRecord(me.context.record);
  573. me.hide();
  574. return true;
  575. },
  576. onShow: function() {
  577. this.callParent(arguments);
  578. this.reposition();
  579. },
  580. onHide: function() {
  581. var me = this;
  582. me.callParent(arguments);
  583. if (me.tooltip) {
  584. me.hideToolTip();
  585. }
  586. if (me.context) {
  587. me.context.view.focus();
  588. me.context = null;
  589. }
  590. },
  591. isDirty: function() {
  592. var me = this,
  593. form = me.getForm();
  594. return form.isDirty();
  595. },
  596. getToolTip: function() {
  597. return this.tooltip || (this.tooltip = new Ext.tip.ToolTip({
  598. cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
  599. title: this.errorsText,
  600. autoHide: false,
  601. closable: true,
  602. closeAction: 'disable',
  603. anchor: 'left'
  604. }));
  605. },
  606. hideToolTip: function() {
  607. var me = this,
  608. tip = me.getToolTip();
  609. if (tip.rendered) {
  610. tip.disable();
  611. }
  612. me.hiddenTip = false;
  613. },
  614. showToolTip: function() {
  615. var me = this,
  616. tip = me.getToolTip(),
  617. context = me.context,
  618. row = Ext.get(context.row),
  619. viewEl = context.grid.view.el;
  620. tip.setTarget(row);
  621. tip.showAt([-10000, -10000]);
  622. tip.update(me.getErrors());
  623. tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
  624. me.repositionTip();
  625. tip.doLayout();
  626. tip.enable();
  627. },
  628. repositionTip: function() {
  629. var me = this,
  630. tip = me.getToolTip(),
  631. context = me.context,
  632. row = Ext.get(context.row),
  633. viewEl = context.grid.view.el,
  634. viewHeight = viewEl.getHeight(),
  635. viewTop = me.lastScrollTop,
  636. viewBottom = viewTop + viewHeight,
  637. rowHeight = row.getHeight(),
  638. rowTop = row.dom.offsetTop,
  639. rowBottom = rowTop + rowHeight;
  640. if (rowBottom &gt; viewTop &amp;&amp; rowTop &lt; viewBottom) {
  641. tip.show();
  642. me.hiddenTip = false;
  643. } else {
  644. tip.hide();
  645. me.hiddenTip = true;
  646. }
  647. },
  648. getErrors: function() {
  649. var me = this,
  650. dirtyText = !me.autoCancel &amp;&amp; me.isDirty() ? me.dirtyText + '&lt;br /&gt;' : '',
  651. errors = [],
  652. fields = me.query('&gt;[isFormField]'),
  653. length = fields.length,
  654. i;
  655. function createListItem(e) {
  656. return '&lt;li&gt;' + e + '&lt;/li&gt;';
  657. }
  658. for (i = 0; i &lt; length; i++) {
  659. errors = errors.concat(
  660. Ext.Array.map(fields[i].getErrors(), createListItem)
  661. );
  662. }
  663. return dirtyText + '&lt;ul&gt;' + errors.join('') + '&lt;/ul&gt;';
  664. },
  665. beforeDestroy: function(){
  666. Ext.destroy(this.floatingButtons, this.tooltip);
  667. this.callParent();
  668. }
  669. });</pre>
  670. </body>
  671. </html>