modx.window.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /* override default Ext.Window component properties */
  2. // these also apply for Windows that do not extend MODx.Window (like console for ex.)
  3. // we use CSS3 box-shadows in 2014, removes clutter from the DOM
  4. Ext.Window.prototype.floating = { shadow: false };
  5. /* override default Ext.Window component methods */
  6. Ext.override(Ext.Window, {
  7. // prevents ugly slow js animations when opening a window
  8. // we cannot do the CSS3 animations stuff in these overrides, as not all windows are animated!
  9. // so they just prevent the normal JS animation to take effect
  10. animShow: function() {
  11. this.afterShow();
  12. // some windows (like migx) don't seem to call onShow
  13. // so we have to do a check here after onShow should have finished
  14. var win = this; // we need a reference to this for setTimeout
  15. // wait for onShow to finish and check if the window is already visible then, if not, try to do that
  16. setTimeout(function() {
  17. if (!win.el.hasClass('anim-ready')) {
  18. win.el.addClass('anim-ready');
  19. setTimeout(function() {
  20. if (win.mask !== undefined) {
  21. // respect that the mask is not always the same object
  22. if (win.mask instanceof Ext.Element) {
  23. win.mask.addClass('fade-in');
  24. } else {
  25. win.mask.el.addClass('fade-in');
  26. }
  27. }
  28. win.el.addClass('zoom-in');
  29. }, 250);
  30. }
  31. }, 300);
  32. }
  33. ,animHide: function() {
  34. //this.el.hide(); // dont hide the window here, we'll do that onHide when the animation is finished!
  35. this.afterHide();
  36. }
  37. ,onShow: function() {
  38. // skip MODx.msg windows, the animations do not work with them as they are always the same element!
  39. if (!this.el.hasClass('x-window-dlg')) {
  40. // first set the class that scales the window down a bit
  41. // this has to be done after the full window is positioned correctly by extjs
  42. this.addClass('anim-ready');
  43. // let the scale transformation to 0.7 finish before animating in
  44. var win = this; // we need a reference to this for setTimeout
  45. setTimeout(function() {
  46. if (win.mask !== undefined) {
  47. // respect that the mask is not always the same object
  48. if (win.mask instanceof Ext.Element) {
  49. win.mask.addClass('fade-in');
  50. } else {
  51. win.mask.el.addClass('fade-in');
  52. }
  53. }
  54. win.el.addClass('zoom-in');
  55. }, 250);
  56. } else {
  57. // we need to handle MODx.msg windows (Ext.Msg singletons, e.g. always the same element, no multiple instances) differently
  58. this.mask.addClass('fade-in');
  59. this.el.applyStyles({'opacity': 1});
  60. }
  61. }
  62. ,onHide: function() {
  63. // for some unknown (to me) reason, onHide() get's called when a window is initialized, e.g. before onShow()
  64. // so we need to prevent the following routine be applied prematurely
  65. if (this.el.hasClass('zoom-in')) {
  66. this.el.removeClass('zoom-in');
  67. if (this.mask !== undefined) {
  68. // respect that the mask is not always the same object
  69. if (this.mask instanceof Ext.Element) {
  70. this.mask.removeClass('fade-in');
  71. } else {
  72. this.mask.el.removeClass('fade-in');
  73. }
  74. }
  75. this.addClass('zoom-out');
  76. // let the CSS animation finish before hiding the window
  77. var win = this; // we need a reference to this for setTimeout
  78. setTimeout(function() {
  79. // we have an unsolved problem with windows that are destroyed on hide
  80. // the zoom-out animation cannot be applied for such windows, as they
  81. // get destroyed too early, if someone knows a solution, please tell =)
  82. if (!win.isDestroyed) {
  83. win.el.hide();
  84. // and remove the CSS3 animation classes
  85. win.el.removeClass('zoom-out');
  86. win.el.removeClass('anim-ready');
  87. }
  88. }, 250);
  89. } else if (this.el.hasClass('x-window-dlg')) {
  90. // we need to handle MODx.msg windows (Ext.Msg singletons, e.g. always the same element, no multiple instances) differently
  91. this.el.applyStyles({'opacity': 0});
  92. if (this.mask !== undefined) {
  93. // respect that the mask is not always the same object
  94. if (this.mask instanceof Ext.Element) {
  95. this.mask.removeClass('fade-in');
  96. } else {
  97. this.mask.el.removeClass('fade-in');
  98. }
  99. }
  100. }
  101. }
  102. });
  103. /**
  104. * Abstract class for Ext.Window creation in MODx
  105. *
  106. * @class MODx.Window
  107. * @extends Ext.Window
  108. * @constructor
  109. * @param {Object} config An object of options.
  110. * @xtype modx-window
  111. */
  112. MODx.Window = function(config) {
  113. config = config || {};
  114. Ext.applyIf(config,{
  115. modal: false
  116. ,layout: 'auto'
  117. ,closeAction: 'hide'
  118. ,shadow: true
  119. ,resizable: true
  120. ,collapsible: true
  121. ,maximizable: true
  122. // ,autoHeight: true // this messes up many windows on smaller screens (e.g. too much height), ex. macbook air 11"
  123. ,autoHeight: false
  124. ,autoScroll: true
  125. ,allowDrop: true
  126. ,width: 400
  127. ,constrain: true
  128. ,constrainHeader: true
  129. ,cls: 'modx-window'
  130. ,buttons: [{
  131. text: config.cancelBtnText || _('cancel')
  132. ,scope: this
  133. ,handler: function() { config.closeAction !== 'close' ? this.hide() : this.close(); }
  134. },{
  135. text: config.saveBtnText || _('save')
  136. ,cls: 'primary-button'
  137. ,scope: this
  138. ,handler: this.submit
  139. }]
  140. ,record: {}
  141. ,keys: [{
  142. key: Ext.EventObject.ENTER
  143. ,fn: function(keyCode, event) {
  144. var elem = event.getTarget();
  145. var component = Ext.getCmp(elem.id);
  146. if (component instanceof Ext.form.TextArea) {
  147. return component.append("\n");
  148. } else {
  149. this.submit();
  150. }
  151. }
  152. ,scope: this
  153. }]
  154. });
  155. MODx.Window.superclass.constructor.call(this,config);
  156. this.options = config;
  157. this.config = config;
  158. this.addEvents({
  159. success: true
  160. ,failure: true
  161. ,beforeSubmit: true
  162. });
  163. this._loadForm();
  164. this.on('show',function() {
  165. if (this.config.blankValues) { this.fp.getForm().reset(); }
  166. if (this.config.allowDrop) { this.loadDropZones(); }
  167. this.syncSize();
  168. this.focusFirstField();
  169. },this);
  170. this.on('afterrender', function() {
  171. this.originalHeight = this.el.getHeight();
  172. this.toolsHeight = this.originalHeight - this.body.getHeight() + 50;
  173. this.resizeWindow();
  174. });
  175. Ext.EventManager.onWindowResize(this.resizeWindow, this);
  176. };
  177. Ext.extend(MODx.Window,Ext.Window,{
  178. _loadForm: function() {
  179. if (this.checkIfLoaded(this.config.record || null)) { return false; }
  180. var r = this.config.record;
  181. /* set values here, since setValue after render seems to be broken */
  182. if (this.config.fields) {
  183. var l = this.config.fields.length;
  184. for (var i=0;i<l;i++) {
  185. var f = this.config.fields[i];
  186. if (r[f.name]) {
  187. if (f.xtype == 'checkbox' || f.xtype == 'radio') {
  188. f.checked = r[f.name];
  189. } else {
  190. f.value = r[f.name];
  191. }
  192. }
  193. }
  194. }
  195. this.fp = this.createForm({
  196. url: this.config.url
  197. ,baseParams: this.config.baseParams || { action: this.config.action || '' }
  198. ,items: this.config.fields || []
  199. });
  200. var w = this;
  201. this.fp.getForm().items.each(function(f) {
  202. f.on('invalid', function(){
  203. w.doLayout();
  204. });
  205. });
  206. this.renderForm();
  207. }
  208. ,focusFirstField: function() {
  209. if (this.fp && this.fp.getForm() && this.fp.getForm().items.getCount() > 0) {
  210. var fld = this.findFirstTextField();
  211. if (fld) { fld.focus(false,200); }
  212. }
  213. }
  214. ,findFirstTextField: function(i) {
  215. i = i || 0;
  216. var fld = this.fp.getForm().items.itemAt(i);
  217. if (!fld) return false;
  218. if (fld.isXType('combo') || fld.isXType('checkbox') || fld.isXType('radio') || fld.isXType('displayfield') || fld.isXType('statictextfield') || fld.isXType('hidden')) {
  219. i = i+1;
  220. fld = this.findFirstTextField(i);
  221. }
  222. return fld;
  223. }
  224. ,submit: function(close) {
  225. close = close === false ? false : true;
  226. var f = this.fp.getForm();
  227. if (f.isValid() && this.fireEvent('beforeSubmit',f.getValues())) {
  228. f.submit({
  229. waitMsg: this.config.waitMsg || _('saving')
  230. ,submitEmptyText: this.config.submitEmptyText !== false
  231. ,scope: this
  232. ,failure: function(frm,a) {
  233. if (this.fireEvent('failure',{f:frm,a:a})) {
  234. MODx.form.Handler.errorExt(a.result,frm);
  235. }
  236. this.doLayout();
  237. }
  238. ,success: function(frm,a) {
  239. if (this.config.success) {
  240. Ext.callback(this.config.success,this.config.scope || this,[frm,a]);
  241. }
  242. this.fireEvent('success',{f:frm,a:a});
  243. if (close) { this.config.closeAction !== 'close' ? this.hide() : this.close(); }
  244. this.doLayout();
  245. }
  246. });
  247. }
  248. }
  249. ,createForm: function(config) {
  250. Ext.applyIf(this.config,{
  251. formFrame: true
  252. ,border: false
  253. ,bodyBorder: false
  254. ,autoHeight: true
  255. });
  256. config = config || {};
  257. Ext.applyIf(config,{
  258. labelAlign: this.config.labelAlign || 'top'
  259. ,labelWidth: this.config.labelWidth || 100
  260. ,labelSeparator: this.config.labelSeparator || ''
  261. ,frame: this.config.formFrame
  262. ,border: this.config.border
  263. ,bodyBorder: this.config.bodyBorder
  264. ,autoHeight: this.config.autoHeight
  265. ,anchor: '100% 100%'
  266. ,errorReader: MODx.util.JSONReader
  267. ,defaults: this.config.formDefaults || {
  268. msgTarget: this.config.msgTarget || 'under'
  269. }
  270. ,url: this.config.url
  271. ,baseParams: this.config.baseParams || {}
  272. ,fileUpload: this.config.fileUpload || false
  273. });
  274. return new Ext.FormPanel(config);
  275. }
  276. ,renderForm: function() {
  277. this.fp.on('destroy', function() {
  278. Ext.EventManager.removeResizeListener(this.resizeWindow, this);
  279. }, this);
  280. this.add(this.fp);
  281. }
  282. ,checkIfLoaded: function(r) {
  283. r = r || {};
  284. if (this.fp && this.fp.getForm()) { /* so as not to duplicate form */
  285. this.fp.getForm().reset();
  286. this.fp.getForm().setValues(r);
  287. return true;
  288. }
  289. return false;
  290. }
  291. ,setValues: function(r) {
  292. if (r === null) { return false; }
  293. this.fp.getForm().setValues(r);
  294. }
  295. ,reset: function() {
  296. this.fp.getForm().reset();
  297. }
  298. ,hideField: function(f) {
  299. f.disable();
  300. f.hide();
  301. var d = f.getEl().up('.x-form-item');
  302. if (d) { d.setDisplayed(false); }
  303. }
  304. ,showField: function(f) {
  305. f.enable();
  306. f.show();
  307. var d = f.getEl().up('.x-form-item');
  308. if (d) { d.setDisplayed(true); }
  309. }
  310. ,loadDropZones: function() {
  311. if (this._dzLoaded) return false;
  312. var flds = this.fp.getForm().items;
  313. flds.each(function(fld) {
  314. if (fld.isFormField && (
  315. fld.isXType('textfield') || fld.isXType('textarea')
  316. ) && !fld.isXType('combo')) {
  317. new MODx.load({
  318. xtype: 'modx-treedrop'
  319. ,target: fld
  320. ,targetEl: fld.getEl().dom
  321. });
  322. }
  323. });
  324. this._dzLoaded = true;
  325. }
  326. ,resizeWindow: function(){
  327. var viewHeight = Ext.getBody().getViewSize().height;
  328. var el = this.fp.getForm().el;
  329. if(viewHeight < this.originalHeight){
  330. el.setStyle('overflow-y', 'scroll');
  331. el.setHeight(viewHeight - this.toolsHeight);
  332. }else{
  333. el.setStyle('overflow-y', 'auto');
  334. el.setHeight('auto');
  335. }
  336. }
  337. });
  338. Ext.reg('modx-window',MODx.Window);