Source: widgets/commonCtrls/kekule.widget.dialogs.js

  1. /**
  2. * @fileoverview
  3. * Implementation of popup dialogs.
  4. * @author Partridge Jiang
  5. */
  6. /*
  7. * requires /lan/classes.js
  8. * requires /utils/kekule.utils.js
  9. * requires /utils/kekule.domUtils.js
  10. * requires /xbrowsers/kekule.x.js
  11. * requires /widgets/kekule.widget.base.js
  12. * requires /localization
  13. */
  14. (function(){
  15. "use strict";
  16. var DU = Kekule.DomUtils;
  17. var EU = Kekule.HtmlElementUtils;
  18. var SU = Kekule.StyleUtils;
  19. var CNS = Kekule.Widget.HtmlClassNames;
  20. /** @ignore */
  21. Kekule.Widget.HtmlClassNames = Object.extend(Kekule.Widget.HtmlClassNames, {
  22. DIALOG: 'K-Dialog',
  23. DIALOG_INSIDE: 'K-Dialog-Inside', // dialog smaller than current view port size
  24. DIALOG_OVERFLOW: 'K-Dialog-Overflow', // dialog larger than current view port size
  25. DIALOG_CLIENT: 'K-Dialog-Client',
  26. DIALOG_CAPTION: 'K-Dialog-Caption',
  27. DIALOG_BTN_PANEL: 'K-Dialog-Button-Panel'
  28. });
  29. /**
  30. * Enumeration of predefined dialog buttons.
  31. * @class
  32. */
  33. Kekule.Widget.DialogButtons = {
  34. OK: 'ok',
  35. CANCEL: 'cancel',
  36. YES: 'yes',
  37. NO: 'no',
  38. /**
  39. * Whether button is a positive one (e.g. Ok, Yes).
  40. * @param {String} btn
  41. * @returns {Bool}
  42. */
  43. isPositive: function(btn)
  44. {
  45. var DB = Kekule.Widget.DialogButtons;
  46. return ([DB.OK, DB.YES].indexOf(btn) >= 0);
  47. },
  48. /**
  49. * Whether button is a negative one (e.g. Cancel, No).
  50. * @param {String} btn
  51. * @returns {Bool}
  52. */
  53. isNegative: function(btn)
  54. {
  55. var DB = Kekule.Widget.DialogButtons;
  56. return ([DB.CANCEL, DB.NO].indexOf(btn) >= 0);
  57. }
  58. };
  59. /**
  60. * Enumeration of location of widget.
  61. * @class
  62. */
  63. Kekule.Widget.Location = {
  64. /** Show widget as is, no special position handling will be done. */
  65. DEFAULT: 1,
  66. /** Show widget at center of browser window visible area. */
  67. CENTER: 2,
  68. /** Widget will fill all area of browser window visible area. */
  69. FULLFILL: 3,
  70. /**
  71. * Widget will be shown at center of window if its initial size smaller than window,
  72. * or fullfill the whole visible area if its intial size larger than window.
  73. */
  74. CENTER_OR_FULLFILL: 4
  75. };
  76. /**
  77. * A popup dialog widget.
  78. * @class
  79. * @augments Kekule.Widget.BaseWidget
  80. *
  81. * @property {String} caption Caption of dialog. If no caption is set, the caption bar will be automatically hidden.
  82. * @property {Array} buttons Array of predefined button names that should be shown in dialog.
  83. * @property {String} result The name of button that close this dialog.
  84. * @property {Int} location Value from {@link Kekule.Widget.Location}, determine the position when dialog is popped up.
  85. */
  86. Kekule.Widget.Dialog = Class.create(Kekule.Widget.BaseWidget,
  87. /** @lends Kekule.Widget.Dialog# */
  88. {
  89. /** @private */
  90. CLASS_NAME: 'Kekule.Widget.Dialog',
  91. /** @private */
  92. BINDABLE_TAG_NAMES: ['div', 'span'],
  93. /** @private */
  94. BTN_NAME_FIELD: '__$btnName__',
  95. /** @constructs */
  96. initialize: function($super, parentOrElementOrDocument, caption, buttons)
  97. {
  98. this._dialogCallback = null;
  99. this._modalInfo = null;
  100. this._childButtons = [];
  101. this.setPropStoreFieldValue('location', Kekule.Widget.Location.CENTER);
  102. $super(parentOrElementOrDocument);
  103. this._dialogOpened = false; // used internally
  104. this.setUseCornerDecoration(true);
  105. if (caption)
  106. this.setCaption(caption);
  107. if (buttons)
  108. this.setButtons(buttons);
  109. this.setDisplayed(false);
  110. },
  111. /** @private */
  112. doFinalize: function($super)
  113. {
  114. //this.unprepareModal(); // if finalize during dialog show, modal preparation should always be unprepared
  115. if (this.getModalInfo())
  116. {
  117. this.getGlobalManager().unprepareModalWidget(this);
  118. }
  119. $super();
  120. },
  121. /** @private */
  122. initProperties: function()
  123. {
  124. this.defineProp('caption', {'dataType': DataType.STRING,
  125. 'getter': function() { return DU.getElementText(this.getCaptionElem()); },
  126. 'setter': function(value)
  127. {
  128. DU.setElementText(this.getCaptionElem(), value);
  129. SU.setDisplay(this.getCaptionElem(), !!value);
  130. }
  131. });
  132. this.defineProp('buttons', {'dataType': DataType.ARRAY,
  133. 'setter': function(value)
  134. {
  135. this.setPropStoreFieldValue('buttons', value);
  136. if (this.getBtnPanelElem())
  137. SU.setDisplay(this.getBtnPanelElem(), value && value.length);
  138. this.buttonsChanged();
  139. }
  140. });
  141. this.defineProp('result', {'dataType': DataType.STRING, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
  142. this.defineProp('location', {'dataType': DataType.INT});
  143. // private properties
  144. this.defineProp('clientElem', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
  145. this.defineProp('captionElem', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
  146. this.defineProp('btnPanelElem', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
  147. },
  148. /** @ignore */
  149. initPropValues: function($super)
  150. {
  151. $super();
  152. if (this.setMovable)
  153. {
  154. this.setMovable(true);
  155. }
  156. },
  157. /** @ignore */
  158. getDefaultMovingGripper: function()
  159. {
  160. return this.getCaptionElem();
  161. },
  162. /** @ignore */
  163. getCoreElement: function()
  164. {
  165. return this.getClientElem();
  166. },
  167. /** @ignore */
  168. doGetWidgetClassName: function()
  169. {
  170. return CNS.DIALOG;
  171. },
  172. /** @ignore */
  173. doCreateRootElement: function(doc)
  174. {
  175. var result = doc.createElement('div');
  176. return result;
  177. },
  178. /** @ignore */
  179. doCreateSubElements: function(doc, rootElem)
  180. {
  181. var result = [];
  182. // caption element
  183. var elem = doc.createElement('div');
  184. elem.className = CNS.DIALOG_CAPTION;
  185. rootElem.appendChild(elem);
  186. this.setPropStoreFieldValue('captionElem', elem);
  187. result.push(elem);
  188. // click on caption to move
  189. if (this.setMovingGripper)
  190. this.setMovingGripper(elem);
  191. // client element
  192. var elem = doc.createElement('div');
  193. elem.className = CNS.DIALOG_CLIENT;
  194. rootElem.appendChild(elem);
  195. this.setPropStoreFieldValue('clientElem', elem);
  196. this.doCreateClientContents(elem);
  197. result.push(elem);
  198. // button panel element
  199. var elem = doc.createElement('div');
  200. elem.className = CNS.DIALOG_BTN_PANEL;
  201. rootElem.appendChild(elem);
  202. this.setPropStoreFieldValue('btnPanelElem', elem);
  203. result.push(elem);
  204. this.buttonsChanged(); // create buttons if essential
  205. return result;
  206. },
  207. /**
  208. * Create essential child widgets (and other elements) in client area.
  209. * Descendants may override this method.
  210. * @param {HTMLElement} clientElem
  211. * @private
  212. */
  213. doCreateClientContents: function(clientElem)
  214. {
  215. // do nothing here
  216. },
  217. /**
  218. * Called when buttons property is changed.
  219. * @private
  220. */
  221. buttonsChanged: function()
  222. {
  223. // clear old buttons
  224. for (var i = this._childButtons.length - 1; i >= 0; --i)
  225. {
  226. var btn = this._childButtons[i];
  227. btn.finalize();
  228. }
  229. var btns = this.getButtons() || [];
  230. for (var i = 0, l = btns.length; i < l; ++i)
  231. {
  232. this.createDialogButton(btns[i]);
  233. }
  234. },
  235. /** @private */
  236. createDialogButton: function(btnName, btnResName, doc, btnPanel)
  237. {
  238. var btnInfo = this._getPredefinedButtonInfo(btnName);
  239. if (!btnInfo) // info can not get, a custom button
  240. {
  241. btnInfo = {'text': btnName};
  242. }
  243. if (btnInfo)
  244. {
  245. var btn = new Kekule.Widget.Button(this, btnInfo.text);
  246. btn[this.BTN_NAME_FIELD] = btnName;
  247. btn.addEventListener('execute', this._reactDialogBtnExec, this);
  248. btn.appendToElem(this.getBtnPanelElem());
  249. btn.__$name__ = btnName;
  250. var resName = btnResName || this._getDialogButtonResName(btnName);
  251. if (resName)
  252. {
  253. btn.linkStyleResource(resName);
  254. }
  255. this._childButtons.push(btn);
  256. return btn;
  257. }
  258. else
  259. return null;
  260. },
  261. /** @private */
  262. _getDialogButtonResName: function(btnName)
  263. {
  264. var DB = Kekule.Widget.DialogButtons;
  265. if ([DB.OK, DB.YES].indexOf(btnName) >= 0)
  266. {
  267. return Kekule.Widget.StyleResourceNames.BUTTON_YES_OK;
  268. }
  269. else if ([DB.CANCEL, DB.NO].indexOf(btnName) >= 0)
  270. {
  271. return Kekule.Widget.StyleResourceNames.BUTTON_NO_CANCEL;
  272. }
  273. else
  274. return null;
  275. },
  276. /** @private */
  277. _reactDialogBtnExec: function(e)
  278. {
  279. var DB = Kekule.Widget.DialogButtons;
  280. var closeButtons = [DB.OK, DB.YES, DB.CANCEL, DB.NO];
  281. var btn = e.target;
  282. if (btn)
  283. {
  284. var btnName = btn.__$name__;
  285. if (closeButtons.indexOf(btnName) >= 0)
  286. {
  287. this.setResult(btnName);
  288. this.close(btnName);
  289. }
  290. }
  291. },
  292. /** @private */
  293. _getPredefinedButtonInfo: function(btnName)
  294. {
  295. var DB = Kekule.Widget.DialogButtons;
  296. //var WT = Kekule.WidgetTexts;
  297. var btnNames = [DB.OK, DB.CANCEL, DB.YES, DB.NO];
  298. var btnTexts = [
  299. //WT.CAPTION_OK, WT.CAPTION_CANCEL, WT.CAPTION_YES, WT.CAPTION_NO
  300. Kekule.$L('WidgetTexts.CAPTION_OK'),
  301. Kekule.$L('WidgetTexts.CAPTION_CANCEL'),
  302. Kekule.$L('WidgetTexts.CAPTION_YES'),
  303. Kekule.$L('WidgetTexts.CAPTION_NO')
  304. ];
  305. var index = btnNames.indexOf(btnName);
  306. return (index >= 0)? {'text': btnTexts[index]}: null;
  307. },
  308. /**
  309. * Return a button object corresponding to btnName in dialog.
  310. * @param {String} btnName
  311. * @returns {Kekule.Widget.Button}
  312. */
  313. getDialogButton: function(btnName)
  314. {
  315. for (var i = this._childButtons.length - 1; i >= 0; --i)
  316. {
  317. var btn = this._childButtons[i];
  318. if (btn[this.BTN_NAME_FIELD] === btnName)
  319. return btn;
  320. }
  321. return null;
  322. },
  323. /** @private */
  324. needAdjustPosition: function()
  325. {
  326. var showHideType = this.getShowHideType();
  327. var ST = Kekule.Widget.ShowHideType;
  328. var result = (showHideType !== ST.DROPDOWN);
  329. //console.log('need adjust pos', result, this.getShowHideType());
  330. return result;
  331. },
  332. /** @private */
  333. _storePositionInfo: function()
  334. {
  335. if (!this.needAdjustPosition())
  336. return;
  337. var elem = this.getElement();
  338. var style = elem.style;
  339. this._posInfo = {
  340. 'left': style.left,
  341. 'top': style.top,
  342. 'right': style.right,
  343. 'bottom': style.bottom,
  344. 'width': style.width,
  345. 'height': style.height,
  346. 'position': style.position
  347. }
  348. },
  349. /** @private */
  350. _restorePositionInfo: function()
  351. {
  352. if (!this.needAdjustPosition())
  353. return;
  354. var elem = this.getElement();
  355. var style = elem.style;
  356. var info = this._posInfo;
  357. if (info)
  358. {
  359. style.left = info.left;
  360. style.top = info.top;
  361. style.right = info.right;
  362. style.bottom = info.bottom;
  363. style.width = info.width;
  364. style.height = info.height;
  365. style.position = info.position;
  366. }
  367. },
  368. /**
  369. * Adjust the size and position of dialog before pop up.
  370. * @private
  371. */
  372. adjustLocation: function()
  373. {
  374. if (!this.needAdjustPosition())
  375. return;
  376. this._storePositionInfo();
  377. var L = Kekule.Widget.Location;
  378. var location = this.getLocation() || L.DEFAULT;
  379. var overflow = false;
  380. if (location !== L.DEFAULT)
  381. {
  382. var l, t, w, h, r, b;
  383. // set display first, otherwise the size may not be set properly
  384. var oldDisplayed = this.getDisplayed();
  385. var oldVisible = this.getVisible();
  386. try
  387. {
  388. this.setDisplayed(true, true); // bypass widgetShowStateChange handle, or recursion
  389. this.setVisible(true, true);
  390. /*
  391. var selfWidth = this.getOffsetWidth();
  392. var selfHeight = this.getOffsetHeight();
  393. */
  394. //var viewPortDim = Kekule.DocumentUtils.getClientDimension(this.getDocument());
  395. var viewPortDim = Kekule.DocumentUtils.getInnerClientDimension(this.getDocument());
  396. //var selfBoundingRect = Kekule.HtmlElementUtils.getElemBoundingClientRect(this.getElement());
  397. var selfBoundingRect = Kekule.HtmlElementUtils.getElemPageRect(this.getElement(), true);
  398. var selfWidth = selfBoundingRect.right - selfBoundingRect.left;
  399. var selfHeight = selfBoundingRect.bottom - selfBoundingRect.top;
  400. var parent = this.getOffsetParent();
  401. //var parentBoundingRect = parent? Kekule.HtmlElementUtils.getElemBoundingClientRect(parent):
  402. var parentBoundingRect = parent? Kekule.HtmlElementUtils.getElemPageRect(parent, true):
  403. {'left': 0, 'top': 0, 'width': 0, 'height': 0};
  404. overflow = (selfWidth >= viewPortDim.width) || (selfHeight >= viewPortDim.height);
  405. }
  406. finally
  407. {
  408. this.setVisible(oldVisible, true);
  409. this.setDisplayed(oldDisplayed, true);
  410. }
  411. if (location === L.CENTER_OR_FULLFILL)
  412. {
  413. if (overflow)
  414. {
  415. var viewPortBox = Kekule.DocumentUtils.getClientVisibleBox(this.getDocument());
  416. //location = L.FULLFILL;
  417. if (selfWidth >= viewPortDim.width)
  418. {
  419. l = 0;
  420. r = 0;
  421. }
  422. else // width not exceed, center the dialog in horizontal direction
  423. {
  424. l = (viewPortDim.width - selfWidth) / 2;
  425. }
  426. if (selfHeight >= viewPortDim.height)
  427. {
  428. t = 0;
  429. b = 0;
  430. }
  431. else // height not exceed, center in vertical direction
  432. {
  433. t = (viewPortDim.height - selfHeight) / 2;
  434. }
  435. }
  436. else
  437. location = L.CENTER;
  438. }
  439. if (location === L.FULLFILL)
  440. {
  441. /*
  442. w = viewPortDim.width;
  443. h = viewPortDim.height;
  444. */
  445. l = 0; //-parentBoundingRect.left;
  446. t = 0; //-parentBoundingRect.top;
  447. r = 0;
  448. b = 0;
  449. }
  450. else if (location === L.CENTER)
  451. {
  452. /*
  453. l = (viewPortDim.width - selfWidth) / 2 - parentBoundingRect.left;
  454. t = (viewPortDim.height - selfHeight) / 2 - parentBoundingRect.top;
  455. */
  456. l = (viewPortDim.width - selfWidth) / 2;
  457. t = (viewPortDim.height - selfHeight) / 2;
  458. //console.log('center', l, t);
  459. }
  460. //overflow = true; // debug
  461. if (overflow) // use absolute position
  462. {
  463. this.removeClassName(CNS.DIALOG_INSIDE);
  464. this.addClassName(CNS.DIALOG_OVERFLOW);
  465. var viewPortScrollPos = Kekule.DocumentUtils.getScrollPosition(this.getDocument());
  466. l += viewPortScrollPos.left;
  467. t += viewPortScrollPos.top;
  468. // ensure left/top are not out of page region
  469. if (l < 0)
  470. l = 0;
  471. if (t < 0)
  472. t = 0;
  473. }
  474. else // use fixed position
  475. {
  476. this.removeClassName(CNS.DIALOG_OVERFLOW);
  477. this.addClassName(CNS.DIALOG_INSIDE);
  478. }
  479. var style = this.getElement().style;
  480. var notUnset = Kekule.ObjUtils.notUnset;
  481. if (notUnset(l))
  482. style.left = l + 'px';
  483. if (notUnset(t))
  484. style.top = t + 'px';
  485. if (notUnset(r))
  486. style.right = r + 'px';
  487. if (notUnset(b))
  488. style.bottom = r + 'px';
  489. if (notUnset(w))
  490. style.width = w + 'px';
  491. if (notUnset(h))
  492. style.height = h + 'px';
  493. //style.position = 'fixed';
  494. this.adjustClientSize(w, h, overflow);
  495. }
  496. this._posAdjusted = true;
  497. },
  498. /** @private */
  499. adjustClientSize: function(dialogWidth, dialogHeight, overflow)
  500. {
  501. // TODO: do nothing here
  502. },
  503. /** @private */
  504. prepareShow: function(callback)
  505. {
  506. // if this dialog has no element parent, just append it to body
  507. var elem = this.getElement();
  508. if (!elem.parentNode)
  509. this.getDocument().body.appendChild(elem);
  510. var self = this;
  511. // defer the function, make sure it be called when elem really in DOM tree
  512. setTimeout(function()
  513. {
  514. self._dialogCallback = callback;
  515. },
  516. 0);
  517. /*
  518. if (!this.isShown())
  519. this.adjustLocation();
  520. */
  521. },
  522. /**
  523. * Show a modal simulation dialog. When the dialog is closed,
  524. * callback(modalResult) will be called.
  525. * @param {Func} callback
  526. * @param {Kekule.Widget.BaseWidget} caller Who calls the show method and make this dialog visible. Can be null.
  527. */
  528. openModal: function(callback, caller)
  529. {
  530. //this.prepareModal();
  531. this.getGlobalManager().prepareModalWidget(this);
  532. // important, must called before prepareShow, or DOM tree change will cause doWidgetShowStateChanged
  533. // and make callback called even before dialog showing
  534. /*
  535. this.prepareShow(callback);
  536. this.show(caller, null, Kekule.Widget.ShowHideType.DIALOG);
  537. */
  538. return this.open(callback, caller);
  539. },
  540. /**
  541. * Show a popup dialog. When the dialog is closed,
  542. * callback(modalResult) will be called.
  543. * @param {Func} callback
  544. * @param {Kekule.Widget.BaseWidget} caller Who calls the show method and make this dialog visible. Can be null.
  545. */
  546. openPopup: function(callback, caller)
  547. {
  548. /*
  549. this.prepareShow(callback);
  550. this.show(caller, null, Kekule.Widget.ShowHideType.POPUP);
  551. */
  552. return this.open(callback, caller, Kekule.Widget.ShowHideType.POPUP);
  553. },
  554. /**
  555. * Show a modeless dialog. When the dialog is closed,
  556. * callback(dialogResult) will be called.
  557. * @param {Func} callback
  558. * @param {Kekule.Widget.BaseWidget} caller Who calls the show method and make this dialog visible. Can be null.
  559. * @param {Int} showType
  560. */
  561. open: function(callback, caller, showType)
  562. {
  563. this.prepareShow(callback);
  564. this.show(caller, null, showType || Kekule.Widget.ShowHideType.DIALOG);
  565. this._dialogOpened = true;
  566. return this;
  567. },
  568. /**
  569. * Close the dialog.
  570. * @param {String} result What result should this dialog return when closed.
  571. */
  572. close: function(result)
  573. {
  574. //var self = this;
  575. this.setResult(result);
  576. this.hide();
  577. },
  578. /**
  579. * Returns whether the dialog result is a positive one (like Ok, Yes).
  580. * @param {String} result Dialog result, if not set, current dialog result will be used.
  581. * @returns {Bool}
  582. */
  583. isPositiveResult: function(result)
  584. {
  585. return Kekule.Widget.DialogButtons.isPositive(result || this.getResult());
  586. },
  587. /**
  588. * Returns whether the dialog result is a negative one (like Cancel, No).
  589. * @param {String} result Dialog result, if not set, current dialog result will be used.
  590. * @returns {Bool}
  591. */
  592. isNegativeResult: function(result)
  593. {
  594. return Kekule.Widget.DialogButtons.isNegative(result || this.getResult());
  595. },
  596. /** @ignore */
  597. widgetShowStateBeforeChanging: function($super, isShown)
  598. {
  599. $super(isShown);
  600. if (isShown /*&& (!this.isShown())*/) // show
  601. {
  602. this.adjustLocation();
  603. this.setResult(null);
  604. }
  605. else
  606. {
  607. // IMPORTANT, can not unprepare modal here, otherwise the modification of DOM tree
  608. // affects the disappear transition
  609. /*
  610. if (this._modalInfo)
  611. this.unprepareModal();
  612. */
  613. }
  614. },
  615. /** @ignore */
  616. doWidgetShowStateChanged: function($super, isShown)
  617. {
  618. $super(isShown);
  619. if (!isShown) // hide
  620. {
  621. if (this._dialogCallback)
  622. {
  623. //console.log('hide and call callback');
  624. this._dialogCallback(this.getResult());
  625. this._dialogCallback = null; // avoid call twice
  626. }
  627. }
  628. },
  629. /** @ignore */
  630. widgetShowStateDone: function($super, isShown)
  631. {
  632. $super(isShown);
  633. if (!isShown && this._dialogOpened) // hide after dialog open
  634. {
  635. if (this.getModalInfo())
  636. {
  637. this.getGlobalManager().unprepareModalWidget(this);
  638. }
  639. if (!this.isShown())
  640. this._restorePositionInfo();
  641. this._dialogOpened = false;
  642. }
  643. }
  644. });
  645. })();