abstractdialog.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Wrapper around {@link goog.ui.Dialog}, to provide
  16. * dialogs that are smarter about interacting with a rich text editor.
  17. *
  18. * @author nicksantos@google.com (Nick Santos)
  19. */
  20. goog.provide('goog.ui.editor.AbstractDialog');
  21. goog.provide('goog.ui.editor.AbstractDialog.Builder');
  22. goog.provide('goog.ui.editor.AbstractDialog.EventType');
  23. goog.require('goog.asserts');
  24. goog.require('goog.dom');
  25. goog.require('goog.dom.classlist');
  26. goog.require('goog.events.EventTarget');
  27. goog.require('goog.string');
  28. goog.require('goog.ui.Dialog');
  29. goog.require('goog.ui.PopupBase');
  30. // *** Public interface ***************************************************** //
  31. /**
  32. * Creates an object that represents a dialog box.
  33. * @param {goog.dom.DomHelper} domHelper DomHelper to be used to create the
  34. * dialog's dom structure.
  35. * @constructor
  36. * @extends {goog.events.EventTarget}
  37. */
  38. goog.ui.editor.AbstractDialog = function(domHelper) {
  39. goog.ui.editor.AbstractDialog.base(this, 'constructor');
  40. this.dom = domHelper;
  41. /** @private {?goog.ui.Dialog} */
  42. this.dialogInternal_ = null;
  43. };
  44. goog.inherits(goog.ui.editor.AbstractDialog, goog.events.EventTarget);
  45. /**
  46. * Causes the dialog box to appear, centered on the screen. Lazily creates the
  47. * dialog if needed.
  48. */
  49. goog.ui.editor.AbstractDialog.prototype.show = function() {
  50. // Lazily create the wrapped dialog to be shown.
  51. if (!this.dialogInternal_) {
  52. this.dialogInternal_ = this.createDialogControl();
  53. this.dialogInternal_.listen(
  54. goog.ui.PopupBase.EventType.HIDE, this.handleAfterHide_, false, this);
  55. }
  56. this.dialogInternal_.setVisible(true);
  57. };
  58. /**
  59. * Hides the dialog, causing AFTER_HIDE to fire.
  60. */
  61. goog.ui.editor.AbstractDialog.prototype.hide = function() {
  62. if (this.dialogInternal_) {
  63. // This eventually fires the wrapped dialog's AFTER_HIDE event, calling our
  64. // handleAfterHide_().
  65. this.dialogInternal_.setVisible(false);
  66. }
  67. };
  68. /**
  69. * @return {boolean} Whether the dialog is open.
  70. */
  71. goog.ui.editor.AbstractDialog.prototype.isOpen = function() {
  72. return !!this.dialogInternal_ && this.dialogInternal_.isVisible();
  73. };
  74. /**
  75. * Runs the handler registered on the OK button event and closes the dialog if
  76. * that handler succeeds.
  77. * This is useful in cases such as double-clicking an item in the dialog is
  78. * equivalent to selecting it and clicking the default button.
  79. * @protected
  80. */
  81. goog.ui.editor.AbstractDialog.prototype.processOkAndClose = function() {
  82. // Fake an OK event from the wrapped dialog control.
  83. var evt = new goog.ui.Dialog.Event(goog.ui.Dialog.DefaultButtonKeys.OK, null);
  84. if (this.handleOk(evt)) {
  85. // handleOk calls dispatchEvent, so if any listener calls preventDefault it
  86. // will return false and we won't hide the dialog.
  87. this.hide();
  88. }
  89. };
  90. // *** Dialog events ******************************************************** //
  91. /**
  92. * Event type constants for events the dialog fires.
  93. * @enum {string}
  94. */
  95. goog.ui.editor.AbstractDialog.EventType = {
  96. // This event is fired after the dialog is hidden, no matter if it was closed
  97. // via OK or Cancel or is being disposed without being hidden first.
  98. AFTER_HIDE: 'afterhide',
  99. // Either the cancel or OK events can be canceled via preventDefault or by
  100. // returning false from their handlers to stop the dialog from closing.
  101. CANCEL: 'cancel',
  102. OK: 'ok'
  103. };
  104. // *** Inner helper class *************************************************** //
  105. /**
  106. * A builder class for the dialog control. All methods except build return this.
  107. * @param {goog.ui.editor.AbstractDialog} editorDialog Editor dialog object
  108. * that will wrap the wrapped dialog object this builder will create.
  109. * @constructor
  110. */
  111. goog.ui.editor.AbstractDialog.Builder = function(editorDialog) {
  112. // We require the editor dialog to be passed in so that the builder can set up
  113. // ok/cancel listeners by default, making it easier for most dialogs.
  114. this.editorDialog_ = editorDialog;
  115. this.wrappedDialog_ = new goog.ui.Dialog('', true, this.editorDialog_.dom);
  116. this.buttonSet_ = new goog.ui.Dialog.ButtonSet(this.editorDialog_.dom);
  117. this.buttonHandlers_ = {};
  118. this.addClassName(goog.getCssName('tr-dialog'));
  119. };
  120. /**
  121. * Sets the title of the dialog.
  122. * @param {string} title Title HTML (escaped).
  123. * @return {!goog.ui.editor.AbstractDialog.Builder} This.
  124. */
  125. goog.ui.editor.AbstractDialog.Builder.prototype.setTitle = function(title) {
  126. this.wrappedDialog_.setTitle(title);
  127. return this;
  128. };
  129. /**
  130. * Adds an OK button to the dialog. Clicking this button will cause {@link
  131. * handleOk} to run, subsequently dispatching an OK event.
  132. * @param {string=} opt_label The caption for the button, if not "OK".
  133. * @return {!goog.ui.editor.AbstractDialog.Builder} This.
  134. */
  135. goog.ui.editor.AbstractDialog.Builder.prototype.addOkButton = function(
  136. opt_label) {
  137. var key = goog.ui.Dialog.DefaultButtonKeys.OK;
  138. /** @desc Label for an OK button in an editor dialog. */
  139. var MSG_TR_DIALOG_OK = goog.getMsg('OK');
  140. // True means this is the default/OK button.
  141. this.buttonSet_.set(key, opt_label || MSG_TR_DIALOG_OK, true);
  142. this.buttonHandlers_[key] =
  143. goog.bind(this.editorDialog_.handleOk, this.editorDialog_);
  144. return this;
  145. };
  146. /**
  147. * Adds a Cancel button to the dialog. Clicking this button will cause {@link
  148. * handleCancel} to run, subsequently dispatching a CANCEL event.
  149. * @param {string=} opt_label The caption for the button, if not "Cancel".
  150. * @return {!goog.ui.editor.AbstractDialog.Builder} This.
  151. */
  152. goog.ui.editor.AbstractDialog.Builder.prototype.addCancelButton = function(
  153. opt_label) {
  154. var key = goog.ui.Dialog.DefaultButtonKeys.CANCEL;
  155. /** @desc Label for a cancel button in an editor dialog. */
  156. var MSG_TR_DIALOG_CANCEL = goog.getMsg('Cancel');
  157. // False means it's not the OK button, true means it's the Cancel button.
  158. this.buttonSet_.set(key, opt_label || MSG_TR_DIALOG_CANCEL, false, true);
  159. this.buttonHandlers_[key] =
  160. goog.bind(this.editorDialog_.handleCancel, this.editorDialog_);
  161. return this;
  162. };
  163. /**
  164. * Adds a custom button to the dialog.
  165. * @param {string} label The caption for the button.
  166. * @param {function(goog.ui.Dialog.EventType):*} handler Function called when
  167. * the button is clicked. It is recommended that this function be a method
  168. * in the concrete subclass of AbstractDialog using this Builder, and that
  169. * it dispatch an event (see {@link handleOk}).
  170. * @param {string=} opt_buttonId Identifier to be used to access the button when
  171. * calling AbstractDialog.getButtonElement().
  172. * @return {!goog.ui.editor.AbstractDialog.Builder} This.
  173. */
  174. goog.ui.editor.AbstractDialog.Builder.prototype.addButton = function(
  175. label, handler, opt_buttonId) {
  176. // We don't care what the key is, just that we can match the button with the
  177. // handler function later.
  178. var key = opt_buttonId || goog.string.createUniqueString();
  179. this.buttonSet_.set(key, label);
  180. this.buttonHandlers_[key] = handler;
  181. return this;
  182. };
  183. /**
  184. * Puts a CSS class on the dialog's main element.
  185. * @param {string} className The class to add.
  186. * @return {!goog.ui.editor.AbstractDialog.Builder} This.
  187. */
  188. goog.ui.editor.AbstractDialog.Builder.prototype.addClassName = function(
  189. className) {
  190. goog.dom.classlist.add(
  191. goog.asserts.assert(this.wrappedDialog_.getDialogElement()), className);
  192. return this;
  193. };
  194. /**
  195. * Sets the content element of the dialog.
  196. * @param {Element} contentElem An element for the main body.
  197. * @return {!goog.ui.editor.AbstractDialog.Builder} This.
  198. */
  199. goog.ui.editor.AbstractDialog.Builder.prototype.setContent = function(
  200. contentElem) {
  201. goog.dom.appendChild(this.wrappedDialog_.getContentElement(), contentElem);
  202. return this;
  203. };
  204. /**
  205. * Builds the wrapped dialog control. May only be called once, after which
  206. * no more methods may be called on this builder.
  207. * @return {!goog.ui.Dialog} The wrapped dialog control.
  208. */
  209. goog.ui.editor.AbstractDialog.Builder.prototype.build = function() {
  210. if (this.buttonSet_.isEmpty()) {
  211. // If caller didn't set any buttons, add an OK and Cancel button by default.
  212. this.addOkButton();
  213. this.addCancelButton();
  214. }
  215. this.wrappedDialog_.setButtonSet(this.buttonSet_);
  216. var handlers = this.buttonHandlers_;
  217. this.buttonHandlers_ = null;
  218. this.wrappedDialog_.listen(
  219. goog.ui.Dialog.EventType.SELECT,
  220. // Listen for the SELECT event, which means a button was clicked, and
  221. // call the handler associated with that button via the key property.
  222. function(e) {
  223. if (handlers[e.key]) {
  224. return handlers[e.key](e);
  225. }
  226. });
  227. // All editor dialogs are modal.
  228. this.wrappedDialog_.setModal(true);
  229. var dialog = this.wrappedDialog_;
  230. this.wrappedDialog_ = null;
  231. return dialog;
  232. };
  233. /**
  234. * Editor dialog that will wrap the wrapped dialog this builder will create.
  235. * @type {goog.ui.editor.AbstractDialog}
  236. * @private
  237. */
  238. goog.ui.editor.AbstractDialog.Builder.prototype.editorDialog_;
  239. /**
  240. * wrapped dialog control being built by this builder.
  241. * @type {goog.ui.Dialog}
  242. * @private
  243. */
  244. goog.ui.editor.AbstractDialog.Builder.prototype.wrappedDialog_;
  245. /**
  246. * Set of buttons to be added to the wrapped dialog control.
  247. * @type {goog.ui.Dialog.ButtonSet}
  248. * @private
  249. */
  250. goog.ui.editor.AbstractDialog.Builder.prototype.buttonSet_;
  251. /**
  252. * Map from keys that will be returned in the wrapped dialog SELECT events to
  253. * handler functions to be called to handle those events.
  254. * @type {Object}
  255. * @private
  256. */
  257. goog.ui.editor.AbstractDialog.Builder.prototype.buttonHandlers_;
  258. // *** Protected interface ************************************************** //
  259. /**
  260. * The DOM helper for the parent document.
  261. * @type {goog.dom.DomHelper}
  262. * @protected
  263. */
  264. goog.ui.editor.AbstractDialog.prototype.dom;
  265. /**
  266. * Creates and returns the goog.ui.Dialog control that is being wrapped
  267. * by this object.
  268. * @return {!goog.ui.Dialog} Created Dialog control.
  269. * @protected
  270. */
  271. goog.ui.editor.AbstractDialog.prototype.createDialogControl =
  272. goog.abstractMethod;
  273. /**
  274. * Returns the HTML Button element for the OK button in this dialog.
  275. * @return {Element} The button element if found, else null.
  276. * @protected
  277. */
  278. goog.ui.editor.AbstractDialog.prototype.getOkButtonElement = function() {
  279. return this.getButtonElement(goog.ui.Dialog.DefaultButtonKeys.OK);
  280. };
  281. /**
  282. * Returns the HTML Button element for the Cancel button in this dialog.
  283. * @return {Element} The button element if found, else null.
  284. * @protected
  285. */
  286. goog.ui.editor.AbstractDialog.prototype.getCancelButtonElement = function() {
  287. return this.getButtonElement(goog.ui.Dialog.DefaultButtonKeys.CANCEL);
  288. };
  289. /**
  290. * Returns the HTML Button element for the button added to this dialog with
  291. * the given button id.
  292. * @param {string} buttonId The id of the button to get.
  293. * @return {Element} The button element if found, else null.
  294. * @protected
  295. */
  296. goog.ui.editor.AbstractDialog.prototype.getButtonElement = function(buttonId) {
  297. return this.dialogInternal_.getButtonSet().getButton(buttonId);
  298. };
  299. /**
  300. * Creates and returns the event object to be used when dispatching the OK
  301. * event to listeners, or returns null to prevent the dialog from closing.
  302. * Subclasses should override this to return their own subclass of
  303. * goog.events.Event that includes all data a plugin would need from the dialog.
  304. * @param {goog.events.Event} e The event object dispatched by the wrapped
  305. * dialog.
  306. * @return {goog.events.Event} The event object to be used when dispatching the
  307. * OK event to listeners.
  308. * @protected
  309. */
  310. goog.ui.editor.AbstractDialog.prototype.createOkEvent = goog.abstractMethod;
  311. /**
  312. * Handles the event dispatched by the wrapped dialog control when the user
  313. * clicks the OK button. Attempts to create the OK event object and dispatches
  314. * it if successful.
  315. * @param {goog.ui.Dialog.Event} e wrapped dialog OK event object.
  316. * @return {boolean} Whether the default action (closing the dialog) should
  317. * still be executed. This will be false if the OK event could not be
  318. * created to be dispatched, or if any listener to that event returs false
  319. * or calls preventDefault.
  320. * @protected
  321. */
  322. goog.ui.editor.AbstractDialog.prototype.handleOk = function(e) {
  323. var eventObj = this.createOkEvent(e);
  324. if (eventObj) {
  325. return this.dispatchEvent(eventObj);
  326. } else {
  327. return false;
  328. }
  329. };
  330. /**
  331. * Handles the event dispatched by the wrapped dialog control when the user
  332. * clicks the Cancel button. Simply dispatches a CANCEL event.
  333. * @return {boolean} Returns false if any of the handlers called prefentDefault
  334. * on the event or returned false themselves.
  335. * @protected
  336. */
  337. goog.ui.editor.AbstractDialog.prototype.handleCancel = function() {
  338. return this.dispatchEvent(goog.ui.editor.AbstractDialog.EventType.CANCEL);
  339. };
  340. /**
  341. * Disposes of the dialog. If the dialog is open, it will be hidden and
  342. * AFTER_HIDE will be dispatched.
  343. * @override
  344. * @protected
  345. */
  346. goog.ui.editor.AbstractDialog.prototype.disposeInternal = function() {
  347. if (this.dialogInternal_) {
  348. this.hide();
  349. this.dialogInternal_.dispose();
  350. this.dialogInternal_ = null;
  351. }
  352. goog.ui.editor.AbstractDialog.superClass_.disposeInternal.call(this);
  353. };
  354. // *** Private implementation *********************************************** //
  355. /**
  356. * The wrapped dialog widget.
  357. * @type {goog.ui.Dialog}
  358. * @private
  359. */
  360. goog.ui.editor.AbstractDialog.prototype.dialogInternal_;
  361. /**
  362. * Cleans up after the dialog is hidden and fires the AFTER_HIDE event. Should
  363. * be a listener for the wrapped dialog's AFTER_HIDE event.
  364. * @private
  365. */
  366. goog.ui.editor.AbstractDialog.prototype.handleAfterHide_ = function() {
  367. this.dispatchEvent(goog.ui.editor.AbstractDialog.EventType.AFTER_HIDE);
  368. };