dialog.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618
  1. // Copyright 2006 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 Class for showing simple modal dialog boxes.
  16. *
  17. * TODO(user):
  18. * * Standardize CSS class names with other components
  19. * * Add functionality to "host" other components in content area
  20. * * Abstract out ButtonSet and make it more general
  21. * @see ../demos/dialog.html
  22. */
  23. goog.provide('goog.ui.Dialog');
  24. goog.provide('goog.ui.Dialog.ButtonSet');
  25. goog.provide('goog.ui.Dialog.ButtonSet.DefaultButtons');
  26. goog.provide('goog.ui.Dialog.DefaultButtonCaptions');
  27. goog.provide('goog.ui.Dialog.DefaultButtonKeys');
  28. goog.provide('goog.ui.Dialog.Event');
  29. goog.provide('goog.ui.Dialog.EventType');
  30. goog.require('goog.a11y.aria');
  31. goog.require('goog.a11y.aria.Role');
  32. goog.require('goog.a11y.aria.State');
  33. goog.require('goog.asserts');
  34. goog.require('goog.dom');
  35. goog.require('goog.dom.NodeType');
  36. goog.require('goog.dom.TagName');
  37. goog.require('goog.dom.classlist');
  38. goog.require('goog.dom.safe');
  39. goog.require('goog.events');
  40. goog.require('goog.events.Event');
  41. goog.require('goog.events.EventType');
  42. goog.require('goog.events.KeyCodes');
  43. goog.require('goog.fx.Dragger');
  44. goog.require('goog.html.SafeHtml');
  45. goog.require('goog.math.Rect');
  46. goog.require('goog.string');
  47. goog.require('goog.structs.Map');
  48. goog.require('goog.style');
  49. goog.require('goog.ui.ModalPopup');
  50. /**
  51. * Class for showing simple dialog boxes.
  52. * The Html structure of the dialog box is:
  53. * <pre>
  54. * Element Function Class-name, modal-dialog = default
  55. * ----------------------------------------------------------------------------
  56. * - iframe Iframe mask modal-dialog-bg
  57. * - div Background mask modal-dialog-bg
  58. * - div Dialog area modal-dialog
  59. * - div Title bar modal-dialog-title
  60. * - span modal-dialog-title-text
  61. * - text Title text N/A
  62. * - span modal-dialog-title-close
  63. * - ?? Close box N/A
  64. * - div Content area modal-dialog-content
  65. * - ?? User specified content N/A
  66. * - div Button area modal-dialog-buttons
  67. * - button N/A
  68. * - button
  69. * - ...
  70. * </pre>
  71. * @constructor
  72. * @param {string=} opt_class CSS class name for the dialog element, also used
  73. * as a class name prefix for related elements; defaults to modal-dialog.
  74. * This should be a single, valid CSS class name.
  75. * @param {boolean=} opt_useIframeMask Work around windowed controls z-index
  76. * issue by using an iframe instead of a div for bg element.
  77. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper; see {@link
  78. * goog.ui.Component} for semantics.
  79. * @extends {goog.ui.ModalPopup}
  80. */
  81. goog.ui.Dialog = function(opt_class, opt_useIframeMask, opt_domHelper) {
  82. goog.ui.Dialog.base(this, 'constructor', opt_useIframeMask, opt_domHelper);
  83. /**
  84. * CSS class name for the dialog element, also used as a class name prefix for
  85. * related elements. Defaults to goog.getCssName('modal-dialog').
  86. * @type {string}
  87. * @private
  88. */
  89. this.class_ = opt_class || goog.getCssName('modal-dialog');
  90. this.buttons_ = goog.ui.Dialog.ButtonSet.createOkCancel();
  91. };
  92. goog.inherits(goog.ui.Dialog, goog.ui.ModalPopup);
  93. goog.tagUnsealableClass(goog.ui.Dialog);
  94. /**
  95. * Button set. Default to Ok/Cancel.
  96. * @type {goog.ui.Dialog.ButtonSet}
  97. * @private
  98. */
  99. goog.ui.Dialog.prototype.buttons_;
  100. /**
  101. * Whether the escape key closes this dialog.
  102. * @type {boolean}
  103. * @private
  104. */
  105. goog.ui.Dialog.prototype.escapeToCancel_ = true;
  106. /**
  107. * Whether this dialog should include a title close button.
  108. * @type {boolean}
  109. * @private
  110. */
  111. goog.ui.Dialog.prototype.hasTitleCloseButton_ = true;
  112. /**
  113. * Whether the dialog is modal. Defaults to true.
  114. * @type {boolean}
  115. * @private
  116. */
  117. goog.ui.Dialog.prototype.modal_ = true;
  118. /**
  119. * Whether the dialog is draggable. Defaults to true.
  120. * @type {boolean}
  121. * @private
  122. */
  123. goog.ui.Dialog.prototype.draggable_ = true;
  124. /**
  125. * Opacity for background mask. Defaults to 50%.
  126. * @type {number}
  127. * @private
  128. */
  129. goog.ui.Dialog.prototype.backgroundElementOpacity_ = 0.50;
  130. /**
  131. * Dialog's title.
  132. * @type {string}
  133. * @private
  134. */
  135. goog.ui.Dialog.prototype.title_ = '';
  136. /**
  137. * Dialog's content (HTML).
  138. * @type {goog.html.SafeHtml}
  139. * @private
  140. */
  141. goog.ui.Dialog.prototype.content_ = null;
  142. /**
  143. * Dragger.
  144. * @type {goog.fx.Dragger}
  145. * @private
  146. */
  147. goog.ui.Dialog.prototype.dragger_ = null;
  148. /**
  149. * Whether the dialog should be disposed when it is hidden.
  150. * @type {boolean}
  151. * @private
  152. */
  153. goog.ui.Dialog.prototype.disposeOnHide_ = false;
  154. /**
  155. * Element for the title bar.
  156. * @type {Element}
  157. * @private
  158. */
  159. goog.ui.Dialog.prototype.titleEl_ = null;
  160. /**
  161. * Element for the text area of the title bar.
  162. * @type {Element}
  163. * @private
  164. */
  165. goog.ui.Dialog.prototype.titleTextEl_ = null;
  166. /**
  167. * Id of element for the text area of the title bar.
  168. * @type {?string}
  169. * @private
  170. */
  171. goog.ui.Dialog.prototype.titleTextId_ = null;
  172. /**
  173. * Element for the close box area of the title bar.
  174. * @type {Element}
  175. * @private
  176. */
  177. goog.ui.Dialog.prototype.titleCloseEl_ = null;
  178. /**
  179. * Element for the content area.
  180. * @type {Element}
  181. * @private
  182. */
  183. goog.ui.Dialog.prototype.contentEl_ = null;
  184. /**
  185. * Element for the button bar.
  186. * @type {Element}
  187. * @private
  188. */
  189. goog.ui.Dialog.prototype.buttonEl_ = null;
  190. /**
  191. * The dialog's preferred ARIA role.
  192. * @type {goog.a11y.aria.Role}
  193. * @private
  194. */
  195. goog.ui.Dialog.prototype.preferredAriaRole_ = goog.a11y.aria.Role.DIALOG;
  196. /** @override */
  197. goog.ui.Dialog.prototype.getCssClass = function() {
  198. return this.class_;
  199. };
  200. /**
  201. * Sets the title.
  202. * @param {string} title The title text.
  203. */
  204. goog.ui.Dialog.prototype.setTitle = function(title) {
  205. this.title_ = title;
  206. if (this.titleTextEl_) {
  207. goog.dom.setTextContent(this.titleTextEl_, title);
  208. }
  209. };
  210. /**
  211. * Gets the title.
  212. * @return {string} The title.
  213. */
  214. goog.ui.Dialog.prototype.getTitle = function() {
  215. return this.title_;
  216. };
  217. /**
  218. * Allows plain text to be set in the content element.
  219. * @param {string} text Content plain text. Newlines are preserved.
  220. */
  221. goog.ui.Dialog.prototype.setTextContent = function(text) {
  222. this.setSafeHtmlContent(
  223. goog.html.SafeHtml.htmlEscapePreservingNewlines(text));
  224. };
  225. /**
  226. * Allows arbitrary HTML to be set in the content element.
  227. * @param {!goog.html.SafeHtml} html Content HTML.
  228. */
  229. goog.ui.Dialog.prototype.setSafeHtmlContent = function(html) {
  230. this.content_ = html;
  231. if (this.contentEl_) {
  232. goog.dom.safe.setInnerHtml(this.contentEl_, html);
  233. }
  234. };
  235. /**
  236. * Gets the content HTML of the content element as a plain string.
  237. *
  238. * Note that this method returns the HTML markup that was previously set via
  239. * setContent(). In particular, the HTML returned by this method does not
  240. * reflect any changes to the content element's DOM that were made my means
  241. * other than setContent().
  242. *
  243. * @return {string} Content HTML.
  244. */
  245. goog.ui.Dialog.prototype.getContent = function() {
  246. return this.content_ != null ? goog.html.SafeHtml.unwrap(this.content_) : '';
  247. };
  248. /**
  249. * Gets the content HTML of the content element.
  250. * @return {goog.html.SafeHtml} Content HTML.
  251. */
  252. goog.ui.Dialog.prototype.getSafeHtmlContent = function() {
  253. return this.content_;
  254. };
  255. /**
  256. * Returns the dialog's preferred ARIA role. This can be used to override the
  257. * default dialog role, e.g. with an ARIA role of ALERTDIALOG for a simple
  258. * warning or confirmation dialog.
  259. * @return {goog.a11y.aria.Role} This dialog's preferred ARIA role.
  260. */
  261. goog.ui.Dialog.prototype.getPreferredAriaRole = function() {
  262. return this.preferredAriaRole_;
  263. };
  264. /**
  265. * Sets the dialog's preferred ARIA role. This can be used to override the
  266. * default dialog role, e.g. with an ARIA role of ALERTDIALOG for a simple
  267. * warning or confirmation dialog.
  268. * @param {goog.a11y.aria.Role} role This dialog's preferred ARIA role.
  269. */
  270. goog.ui.Dialog.prototype.setPreferredAriaRole = function(role) {
  271. this.preferredAriaRole_ = role;
  272. };
  273. /**
  274. * Renders if the DOM is not created.
  275. * @private
  276. */
  277. goog.ui.Dialog.prototype.renderIfNoDom_ = function() {
  278. if (!this.getElement()) {
  279. // TODO(gboyer): Ideally we'd only create the DOM, but many applications
  280. // are requiring this behavior. Eventually, it would be best if the
  281. // element getters could return null if the elements have not been
  282. // created.
  283. this.render();
  284. }
  285. };
  286. /**
  287. * Returns the content element so that more complicated things can be done with
  288. * the content area. Renders if the DOM is not yet created. Overrides
  289. * {@link goog.ui.Component#getContentElement}.
  290. * @return {Element} The content element.
  291. * @override
  292. */
  293. goog.ui.Dialog.prototype.getContentElement = function() {
  294. this.renderIfNoDom_();
  295. return this.contentEl_;
  296. };
  297. /**
  298. * Returns the title element so that more complicated things can be done with
  299. * the title. Renders if the DOM is not yet created.
  300. * @return {Element} The title element.
  301. */
  302. goog.ui.Dialog.prototype.getTitleElement = function() {
  303. this.renderIfNoDom_();
  304. return this.titleEl_;
  305. };
  306. /**
  307. * Returns the title text element so that more complicated things can be done
  308. * with the text of the title. Renders if the DOM is not yet created.
  309. * @return {Element} The title text element.
  310. */
  311. goog.ui.Dialog.prototype.getTitleTextElement = function() {
  312. this.renderIfNoDom_();
  313. return this.titleTextEl_;
  314. };
  315. /**
  316. * Returns the title close element so that more complicated things can be done
  317. * with the close area of the title. Renders if the DOM is not yet created.
  318. * @return {Element} The close box.
  319. */
  320. goog.ui.Dialog.prototype.getTitleCloseElement = function() {
  321. this.renderIfNoDom_();
  322. return this.titleCloseEl_;
  323. };
  324. /**
  325. * Returns the button element so that more complicated things can be done with
  326. * the button area. Renders if the DOM is not yet created.
  327. * @return {Element} The button container element.
  328. */
  329. goog.ui.Dialog.prototype.getButtonElement = function() {
  330. this.renderIfNoDom_();
  331. return this.buttonEl_;
  332. };
  333. /**
  334. * Returns the dialog element so that more complicated things can be done with
  335. * the dialog box. Renders if the DOM is not yet created.
  336. * @return {Element} The dialog element.
  337. */
  338. goog.ui.Dialog.prototype.getDialogElement = function() {
  339. this.renderIfNoDom_();
  340. return this.getElement();
  341. };
  342. /**
  343. * Returns the background mask element so that more complicated things can be
  344. * done with the background region. Renders if the DOM is not yet created.
  345. * @return {Element} The background mask element.
  346. * @override
  347. */
  348. goog.ui.Dialog.prototype.getBackgroundElement = function() {
  349. this.renderIfNoDom_();
  350. return goog.ui.Dialog.base(this, 'getBackgroundElement');
  351. };
  352. /**
  353. * Gets the opacity of the background mask.
  354. * @return {number} Background mask opacity.
  355. */
  356. goog.ui.Dialog.prototype.getBackgroundElementOpacity = function() {
  357. return this.backgroundElementOpacity_;
  358. };
  359. /**
  360. * Sets the opacity of the background mask.
  361. * @param {number} opacity Background mask opacity.
  362. */
  363. goog.ui.Dialog.prototype.setBackgroundElementOpacity = function(opacity) {
  364. this.backgroundElementOpacity_ = opacity;
  365. if (this.getElement()) {
  366. var bgEl = this.getBackgroundElement();
  367. if (bgEl) {
  368. goog.style.setOpacity(bgEl, this.backgroundElementOpacity_);
  369. }
  370. }
  371. };
  372. /**
  373. * Sets the modal property of the dialog. In case the dialog is already
  374. * inDocument, renders the modal background elements according to the specified
  375. * modal parameter.
  376. *
  377. * Note that non-modal dialogs cannot use an iframe mask.
  378. *
  379. * @param {boolean} modal Whether the dialog is modal.
  380. */
  381. goog.ui.Dialog.prototype.setModal = function(modal) {
  382. if (modal != this.modal_) {
  383. this.setModalInternal_(modal);
  384. }
  385. };
  386. /**
  387. * Sets the modal property of the dialog.
  388. * @param {boolean} modal Whether the dialog is modal.
  389. * @private
  390. */
  391. goog.ui.Dialog.prototype.setModalInternal_ = function(modal) {
  392. this.modal_ = modal;
  393. if (this.isInDocument()) {
  394. var dom = this.getDomHelper();
  395. var bg = this.getBackgroundElement();
  396. var bgIframe = this.getBackgroundIframe();
  397. if (modal) {
  398. if (bgIframe) {
  399. dom.insertSiblingBefore(bgIframe, this.getElement());
  400. }
  401. dom.insertSiblingBefore(bg, this.getElement());
  402. } else {
  403. dom.removeNode(bgIframe);
  404. dom.removeNode(bg);
  405. }
  406. }
  407. if (this.isVisible()) {
  408. this.setA11YDetectBackground(modal);
  409. }
  410. };
  411. /**
  412. * @return {boolean} modal Whether the dialog is modal.
  413. */
  414. goog.ui.Dialog.prototype.getModal = function() {
  415. return this.modal_;
  416. };
  417. /**
  418. * @return {string} The CSS class name for the dialog element.
  419. */
  420. goog.ui.Dialog.prototype.getClass = function() {
  421. return this.getCssClass();
  422. };
  423. /**
  424. * Sets whether the dialog can be dragged.
  425. * @param {boolean} draggable Whether the dialog can be dragged.
  426. */
  427. goog.ui.Dialog.prototype.setDraggable = function(draggable) {
  428. this.draggable_ = draggable;
  429. this.setDraggingEnabled_(draggable && this.isInDocument());
  430. };
  431. /**
  432. * Returns a dragger for moving the dialog and adds a class for the move cursor.
  433. * Defaults to allow dragging of the title only, but can be overridden if
  434. * different drag targets or dragging behavior is desired.
  435. * @return {!goog.fx.Dragger} The created dragger instance.
  436. * @protected
  437. */
  438. goog.ui.Dialog.prototype.createDragger = function() {
  439. return new goog.fx.Dragger(this.getElement(), this.titleEl_);
  440. };
  441. /**
  442. * @return {boolean} Whether the dialog is draggable.
  443. */
  444. goog.ui.Dialog.prototype.getDraggable = function() {
  445. return this.draggable_;
  446. };
  447. /**
  448. * Enables or disables dragging.
  449. * @param {boolean} enabled Whether to enable it.
  450. * @private
  451. */
  452. goog.ui.Dialog.prototype.setDraggingEnabled_ = function(enabled) {
  453. // This isn't ideal, but the quickest and easiest way to append
  454. // title-draggable to the last class in the class_ string, then trim and
  455. // split the string into an array (in case the dialog was set up with
  456. // multiple, space-separated class names).
  457. var classNames =
  458. goog.string.trim(goog.getCssName(this.class_, 'title-draggable'))
  459. .split(' ');
  460. if (this.getElement()) {
  461. if (enabled) {
  462. goog.dom.classlist.addAll(goog.asserts.assert(this.titleEl_), classNames);
  463. } else {
  464. goog.dom.classlist.removeAll(
  465. goog.asserts.assert(this.titleEl_), classNames);
  466. }
  467. }
  468. if (enabled && !this.dragger_) {
  469. this.dragger_ = this.createDragger();
  470. goog.dom.classlist.addAll(goog.asserts.assert(this.titleEl_), classNames);
  471. goog.events.listen(
  472. this.dragger_, goog.fx.Dragger.EventType.START, this.setDraggerLimits_,
  473. false, this);
  474. } else if (!enabled && this.dragger_) {
  475. this.dragger_.dispose();
  476. this.dragger_ = null;
  477. }
  478. };
  479. /** @override */
  480. goog.ui.Dialog.prototype.createDom = function() {
  481. goog.ui.Dialog.base(this, 'createDom');
  482. var element = this.getElement();
  483. goog.asserts.assert(element, 'getElement() returns null');
  484. var dom = this.getDomHelper();
  485. this.titleEl_ = dom.createDom(
  486. goog.dom.TagName.DIV, goog.getCssName(this.class_, 'title'),
  487. this.titleTextEl_ = dom.createDom(
  488. goog.dom.TagName.SPAN, {
  489. 'className': goog.getCssName(this.class_, 'title-text'),
  490. 'id': this.getId()
  491. },
  492. this.title_),
  493. this.titleCloseEl_ = dom.createDom(
  494. goog.dom.TagName.SPAN, goog.getCssName(this.class_, 'title-close'))),
  495. goog.dom.append(
  496. element, this.titleEl_,
  497. this.contentEl_ = dom.createDom(
  498. goog.dom.TagName.DIV, goog.getCssName(this.class_, 'content')),
  499. this.buttonEl_ = dom.createDom(
  500. goog.dom.TagName.DIV, goog.getCssName(this.class_, 'buttons')));
  501. // Make the title and close button behave correctly with screen readers.
  502. // Note: this is only being added if the dialog is not decorated. Decorators
  503. // are expected to add aria label, role, and tab indexing in their templates.
  504. goog.a11y.aria.setRole(this.titleTextEl_, goog.a11y.aria.Role.HEADING);
  505. goog.a11y.aria.setRole(this.titleCloseEl_, goog.a11y.aria.Role.BUTTON);
  506. goog.dom.setFocusableTabIndex(this.titleCloseEl_, true);
  507. goog.a11y.aria.setLabel(
  508. this.titleCloseEl_, goog.ui.Dialog.MSG_GOOG_UI_DIALOG_CLOSE_);
  509. this.titleTextId_ = this.titleTextEl_.id;
  510. goog.a11y.aria.setRole(element, this.getPreferredAriaRole());
  511. goog.a11y.aria.setState(
  512. element, goog.a11y.aria.State.LABELLEDBY, this.titleTextId_ || '');
  513. // If setContent() was called before createDom(), make sure the inner HTML of
  514. // the content element is initialized.
  515. if (this.content_) {
  516. goog.dom.safe.setInnerHtml(this.contentEl_, this.content_);
  517. }
  518. goog.style.setElementShown(this.titleCloseEl_, this.hasTitleCloseButton_);
  519. // Render the buttons.
  520. if (this.buttons_) {
  521. this.buttons_.attachToElement(this.buttonEl_);
  522. }
  523. goog.style.setElementShown(this.buttonEl_, !!this.buttons_);
  524. this.setBackgroundElementOpacity(this.backgroundElementOpacity_);
  525. };
  526. /** @override */
  527. goog.ui.Dialog.prototype.decorateInternal = function(element) {
  528. goog.ui.Dialog.base(this, 'decorateInternal', element);
  529. var dialogElement = this.getElement();
  530. goog.asserts.assert(
  531. dialogElement, 'The DOM element for dialog cannot be null.');
  532. // Decorate or create the content element.
  533. var contentClass = goog.getCssName(this.class_, 'content');
  534. this.contentEl_ = goog.dom.getElementsByTagNameAndClass(
  535. null, contentClass, dialogElement)[0];
  536. if (!this.contentEl_) {
  537. this.contentEl_ =
  538. this.getDomHelper().createDom(goog.dom.TagName.DIV, contentClass);
  539. if (this.content_) {
  540. goog.dom.safe.setInnerHtml(this.contentEl_, this.content_);
  541. }
  542. dialogElement.appendChild(this.contentEl_);
  543. }
  544. // Decorate or create the title bar element.
  545. var titleClass = goog.getCssName(this.class_, 'title');
  546. var titleTextClass = goog.getCssName(this.class_, 'title-text');
  547. var titleCloseClass = goog.getCssName(this.class_, 'title-close');
  548. this.titleEl_ =
  549. goog.dom.getElementsByTagNameAndClass(null, titleClass, dialogElement)[0];
  550. if (this.titleEl_) {
  551. // Only look for title text & title close elements if a title bar element
  552. // was found. Otherwise assume that the entire title bar has to be
  553. // created from scratch.
  554. this.titleTextEl_ = goog.dom.getElementsByTagNameAndClass(
  555. null, titleTextClass, this.titleEl_)[0];
  556. this.titleCloseEl_ = goog.dom.getElementsByTagNameAndClass(
  557. null, titleCloseClass, this.titleEl_)[0];
  558. } else {
  559. // Create the title bar element and insert it before the content area.
  560. // This is useful if the element to decorate only includes a content area.
  561. this.titleEl_ =
  562. this.getDomHelper().createDom(goog.dom.TagName.DIV, titleClass);
  563. dialogElement.insertBefore(this.titleEl_, this.contentEl_);
  564. }
  565. // Decorate or create the title text element.
  566. if (this.titleTextEl_) {
  567. this.title_ = goog.dom.getTextContent(this.titleTextEl_);
  568. // Give the title text element an id if it doesn't already have one.
  569. if (!this.titleTextEl_.id) {
  570. this.titleTextEl_.id = this.getId();
  571. }
  572. } else {
  573. this.titleTextEl_ = goog.dom.createDom(
  574. goog.dom.TagName.SPAN,
  575. {'className': titleTextClass, 'id': this.getId()});
  576. this.titleEl_.appendChild(this.titleTextEl_);
  577. }
  578. this.titleTextId_ = this.titleTextEl_.id;
  579. goog.a11y.aria.setState(
  580. dialogElement, goog.a11y.aria.State.LABELLEDBY, this.titleTextId_ || '');
  581. // Decorate or create the title close element.
  582. if (!this.titleCloseEl_) {
  583. this.titleCloseEl_ =
  584. this.getDomHelper().createDom(goog.dom.TagName.SPAN, titleCloseClass);
  585. this.titleEl_.appendChild(this.titleCloseEl_);
  586. }
  587. goog.style.setElementShown(this.titleCloseEl_, this.hasTitleCloseButton_);
  588. // Decorate or create the button container element.
  589. var buttonsClass = goog.getCssName(this.class_, 'buttons');
  590. this.buttonEl_ = goog.dom.getElementsByTagNameAndClass(
  591. null, buttonsClass, dialogElement)[0];
  592. if (this.buttonEl_) {
  593. // Button container element found. Create empty button set and use it to
  594. // decorate the button container.
  595. this.buttons_ = new goog.ui.Dialog.ButtonSet(this.getDomHelper());
  596. this.buttons_.decorate(this.buttonEl_);
  597. } else {
  598. // Create new button container element, and render a button set into it.
  599. this.buttonEl_ =
  600. this.getDomHelper().createDom(goog.dom.TagName.DIV, buttonsClass);
  601. dialogElement.appendChild(this.buttonEl_);
  602. if (this.buttons_) {
  603. this.buttons_.attachToElement(this.buttonEl_);
  604. }
  605. goog.style.setElementShown(this.buttonEl_, !!this.buttons_);
  606. }
  607. this.setBackgroundElementOpacity(this.backgroundElementOpacity_);
  608. };
  609. /** @override */
  610. goog.ui.Dialog.prototype.enterDocument = function() {
  611. goog.ui.Dialog.base(this, 'enterDocument');
  612. // Listen for keyboard events while the dialog is visible.
  613. this.getHandler()
  614. .listen(this.getElement(), goog.events.EventType.KEYDOWN, this.onKey_)
  615. .listen(this.getElement(), goog.events.EventType.KEYPRESS, this.onKey_);
  616. // NOTE: see bug 1163154 for an example of an edge case where making the
  617. // dialog visible in response to a KEYDOWN will result in a CLICK event
  618. // firing on the default button (immediately closing the dialog) if the key
  619. // that fired the KEYDOWN is also normally used to activate controls
  620. // (i.e. SPACE/ENTER).
  621. //
  622. // This could be worked around by attaching the onButtonClick_ handler in a
  623. // setTimeout, but that was deemed undesirable.
  624. this.getHandler().listen(
  625. this.buttonEl_, goog.events.EventType.CLICK, this.onButtonClick_);
  626. // Add drag support.
  627. this.setDraggingEnabled_(this.draggable_);
  628. // Add event listeners to the close box and the button container.
  629. this.getHandler().listen(
  630. this.titleCloseEl_, goog.events.EventType.CLICK, this.onTitleCloseClick_);
  631. var element = this.getElement();
  632. goog.asserts.assert(element, 'The DOM element for dialog cannot be null');
  633. goog.a11y.aria.setRole(element, this.getPreferredAriaRole());
  634. if (this.titleTextEl_.id !== '') {
  635. goog.a11y.aria.setState(
  636. element, goog.a11y.aria.State.LABELLEDBY, this.titleTextEl_.id);
  637. }
  638. if (!this.modal_) {
  639. this.setModalInternal_(false);
  640. }
  641. };
  642. /** @override */
  643. goog.ui.Dialog.prototype.exitDocument = function() {
  644. if (this.isVisible()) {
  645. this.setVisible(false);
  646. }
  647. // Remove drag support.
  648. this.setDraggingEnabled_(false);
  649. goog.ui.Dialog.base(this, 'exitDocument');
  650. };
  651. /**
  652. * Sets the visibility of the dialog box and moves focus to the
  653. * default button. Lazily renders the component if needed. After this
  654. * method returns, isVisible() will always return the new state, even
  655. * if there is a transition.
  656. * @param {boolean} visible Whether the dialog should be visible.
  657. * @override
  658. */
  659. goog.ui.Dialog.prototype.setVisible = function(visible) {
  660. if (visible == this.isVisible()) {
  661. return;
  662. }
  663. // If the dialog hasn't been rendered yet, render it now.
  664. if (!this.isInDocument()) {
  665. this.render();
  666. }
  667. goog.ui.Dialog.base(this, 'setVisible', visible);
  668. };
  669. /**
  670. * @override
  671. * @suppress {deprecated} AFTER_SHOW is deprecated earlier in this file.
  672. */
  673. goog.ui.Dialog.prototype.onShow = function() {
  674. goog.ui.Dialog.base(this, 'onShow');
  675. this.dispatchEvent(goog.ui.Dialog.EventType.AFTER_SHOW);
  676. };
  677. /**
  678. * @override
  679. * @suppress {deprecated} AFTER_HIDE is deprecated earlier in this file.
  680. */
  681. goog.ui.Dialog.prototype.onHide = function() {
  682. goog.ui.Dialog.base(this, 'onHide');
  683. this.dispatchEvent(goog.ui.Dialog.EventType.AFTER_HIDE);
  684. if (this.disposeOnHide_) {
  685. this.dispose();
  686. }
  687. };
  688. /**
  689. * Sets dragger limits when dragging is started.
  690. * @param {!goog.events.Event} e goog.fx.Dragger.EventType.START event.
  691. * @private
  692. */
  693. goog.ui.Dialog.prototype.setDraggerLimits_ = function(e) {
  694. var doc = this.getDomHelper().getDocument();
  695. var win = goog.dom.getWindow(doc) || window;
  696. // Take the max of scroll height and view height for cases in which document
  697. // does not fill screen.
  698. var viewSize = goog.dom.getViewportSize(win);
  699. var w = Math.max(doc.body.scrollWidth, viewSize.width);
  700. var h = Math.max(doc.body.scrollHeight, viewSize.height);
  701. var dialogSize = goog.style.getSize(this.getElement());
  702. if (goog.style.getComputedPosition(this.getElement()) == 'fixed') {
  703. // Ensure position:fixed dialogs can't be dragged beyond the viewport.
  704. this.dragger_.setLimits(
  705. new goog.math.Rect(
  706. 0, 0, Math.max(0, viewSize.width - dialogSize.width),
  707. Math.max(0, viewSize.height - dialogSize.height)));
  708. } else {
  709. this.dragger_.setLimits(
  710. new goog.math.Rect(0, 0, w - dialogSize.width, h - dialogSize.height));
  711. }
  712. };
  713. /**
  714. * Handles a click on the title close area.
  715. * @param {goog.events.BrowserEvent} e Browser's event object.
  716. * @private
  717. */
  718. goog.ui.Dialog.prototype.onTitleCloseClick_ = function(e) {
  719. this.handleTitleClose_();
  720. };
  721. /**
  722. * Performs the action of closing the dialog in response to the title close
  723. * button being interacted with. General purpose method to be called by click
  724. * and button event handlers.
  725. * @private
  726. */
  727. goog.ui.Dialog.prototype.handleTitleClose_ = function() {
  728. if (!this.hasTitleCloseButton_) {
  729. return;
  730. }
  731. var bs = this.getButtonSet();
  732. var key = bs && bs.getCancel();
  733. // Only if there is a valid cancel button is an event dispatched.
  734. if (key) {
  735. var caption = /** @type {Element|string} */ (bs.get(key));
  736. if (this.dispatchEvent(new goog.ui.Dialog.Event(key, caption))) {
  737. this.setVisible(false);
  738. }
  739. } else {
  740. this.setVisible(false);
  741. }
  742. };
  743. /**
  744. * @return {boolean} Whether this dialog has a title close button.
  745. */
  746. goog.ui.Dialog.prototype.getHasTitleCloseButton = function() {
  747. return this.hasTitleCloseButton_;
  748. };
  749. /**
  750. * Sets whether the dialog should have a close button in the title bar. There
  751. * will always be an element for the title close button, but setting this
  752. * parameter to false will cause it to be hidden and have no active listener.
  753. * @param {boolean} b Whether this dialog should have a title close button.
  754. */
  755. goog.ui.Dialog.prototype.setHasTitleCloseButton = function(b) {
  756. this.hasTitleCloseButton_ = b;
  757. if (this.titleCloseEl_) {
  758. goog.style.setElementShown(this.titleCloseEl_, this.hasTitleCloseButton_);
  759. }
  760. };
  761. /**
  762. * @return {boolean} Whether the escape key should close this dialog.
  763. */
  764. goog.ui.Dialog.prototype.isEscapeToCancel = function() {
  765. return this.escapeToCancel_;
  766. };
  767. /**
  768. * @param {boolean} b Whether the escape key should close this dialog.
  769. */
  770. goog.ui.Dialog.prototype.setEscapeToCancel = function(b) {
  771. this.escapeToCancel_ = b;
  772. };
  773. /**
  774. * Sets whether the dialog should be disposed when it is hidden. By default
  775. * dialogs are not disposed when they are hidden.
  776. * @param {boolean} b Whether the dialog should get disposed when it gets
  777. * hidden.
  778. */
  779. goog.ui.Dialog.prototype.setDisposeOnHide = function(b) {
  780. this.disposeOnHide_ = b;
  781. };
  782. /**
  783. * @return {boolean} Whether the dialog should be disposed when it is hidden.
  784. */
  785. goog.ui.Dialog.prototype.getDisposeOnHide = function() {
  786. return this.disposeOnHide_;
  787. };
  788. /** @override */
  789. goog.ui.Dialog.prototype.disposeInternal = function() {
  790. this.titleCloseEl_ = null;
  791. this.buttonEl_ = null;
  792. goog.ui.Dialog.base(this, 'disposeInternal');
  793. };
  794. /**
  795. * Sets the button set to use.
  796. * Note: Passing in null will cause no button set to be rendered.
  797. * @param {goog.ui.Dialog.ButtonSet?} buttons The button set to use.
  798. */
  799. goog.ui.Dialog.prototype.setButtonSet = function(buttons) {
  800. this.buttons_ = buttons;
  801. if (this.buttonEl_) {
  802. if (this.buttons_) {
  803. this.buttons_.attachToElement(this.buttonEl_);
  804. } else {
  805. goog.dom.safe.setInnerHtml(this.buttonEl_, goog.html.SafeHtml.EMPTY);
  806. }
  807. goog.style.setElementShown(this.buttonEl_, !!this.buttons_);
  808. }
  809. };
  810. /**
  811. * Returns the button set being used.
  812. * @return {goog.ui.Dialog.ButtonSet?} The button set being used.
  813. */
  814. goog.ui.Dialog.prototype.getButtonSet = function() {
  815. return this.buttons_;
  816. };
  817. /**
  818. * Handles a click on the button container.
  819. * @param {goog.events.BrowserEvent} e Browser's event object.
  820. * @private
  821. */
  822. goog.ui.Dialog.prototype.onButtonClick_ = function(e) {
  823. var button = this.findParentButton_(/** @type {Element} */ (e.target));
  824. if (button && !button.disabled) {
  825. var key = button.name;
  826. var caption = /** @type {Element|string} */ (this.getButtonSet().get(key));
  827. if (this.dispatchEvent(new goog.ui.Dialog.Event(key, caption))) {
  828. this.setVisible(false);
  829. }
  830. }
  831. };
  832. /**
  833. * Finds the parent button of an element (or null if there was no button
  834. * parent).
  835. * @param {Element} element The element that was clicked on.
  836. * @return {Element} Returns the parent button or null if not found.
  837. * @private
  838. */
  839. goog.ui.Dialog.prototype.findParentButton_ = function(element) {
  840. var el = element;
  841. while (el != null && el != this.buttonEl_) {
  842. if (el.tagName == goog.dom.TagName.BUTTON) {
  843. return /** @type {Element} */ (el);
  844. }
  845. el = el.parentNode;
  846. }
  847. return null;
  848. };
  849. /**
  850. * Handles keydown and keypress events, and dismisses the popup if cancel is
  851. * pressed. If there is a cancel action in the ButtonSet, than that will be
  852. * fired. Also prevents tabbing out of the dialog.
  853. * @param {goog.events.BrowserEvent} e Browser's event object.
  854. * @private
  855. */
  856. goog.ui.Dialog.prototype.onKey_ = function(e) {
  857. var close = false;
  858. var hasHandler = false;
  859. var buttonSet = this.getButtonSet();
  860. var target = e.target;
  861. if (e.type == goog.events.EventType.KEYDOWN) {
  862. // Escape and tab can only properly be handled in keydown handlers.
  863. if (this.escapeToCancel_ && e.keyCode == goog.events.KeyCodes.ESC) {
  864. // Only if there is a valid cancel button is an event dispatched.
  865. var cancel = buttonSet && buttonSet.getCancel();
  866. // Users may expect to hit escape on a SELECT element.
  867. var isSpecialFormElement =
  868. target.tagName == goog.dom.TagName.SELECT && !target.disabled;
  869. if (cancel && !isSpecialFormElement) {
  870. hasHandler = true;
  871. var caption = buttonSet.get(cancel);
  872. close = this.dispatchEvent(
  873. new goog.ui.Dialog.Event(
  874. cancel,
  875. /** @type {Element|null|string} */ (caption)));
  876. } else if (!isSpecialFormElement) {
  877. close = true;
  878. }
  879. } else if (
  880. e.keyCode == goog.events.KeyCodes.TAB && e.shiftKey &&
  881. target == this.getElement()) {
  882. // Prevent the user from shift-tabbing backwards out of the dialog box.
  883. // Instead, set up a wrap in focus backward to the end of the dialog.
  884. this.setupBackwardTabWrap();
  885. }
  886. } else if (e.keyCode == goog.events.KeyCodes.ENTER) {
  887. // Only handle ENTER in keypress events, in case the action opens a
  888. // popup window.
  889. var key;
  890. if (target.tagName == goog.dom.TagName.BUTTON && !target.disabled) {
  891. // If the target is a button and it's enabled, we can fire that button's
  892. // handler.
  893. key = target.name;
  894. } else if (target == this.titleCloseEl_) {
  895. // if the title 'close' button is in focus, close the dialog
  896. this.handleTitleClose_();
  897. } else if (buttonSet) {
  898. // Try to fire the default button's handler (if one exists), but only if
  899. // the button is enabled.
  900. var defaultKey = buttonSet.getDefault();
  901. var defaultButton = defaultKey && buttonSet.getButton(defaultKey);
  902. // Users may expect to hit enter on a TEXTAREA, SELECT or an A element.
  903. var isSpecialFormElement = (target.tagName == goog.dom.TagName.TEXTAREA ||
  904. target.tagName == goog.dom.TagName.SELECT ||
  905. target.tagName == goog.dom.TagName.A) &&
  906. !target.disabled;
  907. if (defaultButton && !defaultButton.disabled && !isSpecialFormElement) {
  908. key = defaultKey;
  909. }
  910. }
  911. if (key && buttonSet) {
  912. hasHandler = true;
  913. close = this.dispatchEvent(
  914. new goog.ui.Dialog.Event(key, String(buttonSet.get(key))));
  915. }
  916. } else if (
  917. target == this.titleCloseEl_ && e.keyCode == goog.events.KeyCodes.SPACE) {
  918. // if the title 'close' button is in focus on 'SPACE,' close the dialog
  919. this.handleTitleClose_();
  920. }
  921. if (close || hasHandler) {
  922. e.stopPropagation();
  923. e.preventDefault();
  924. }
  925. if (close) {
  926. this.setVisible(false);
  927. }
  928. };
  929. /**
  930. * Dialog event class.
  931. * @param {string} key Key identifier for the button.
  932. * @param {string|Element} caption Caption on the button (might be i18nlized).
  933. * @constructor
  934. * @extends {goog.events.Event}
  935. */
  936. goog.ui.Dialog.Event = function(key, caption) {
  937. this.type = goog.ui.Dialog.EventType.SELECT;
  938. this.key = key;
  939. this.caption = caption;
  940. };
  941. goog.inherits(goog.ui.Dialog.Event, goog.events.Event);
  942. /**
  943. * Event type constant for dialog events.
  944. * TODO(attila): Change this to goog.ui.Dialog.EventType.SELECT.
  945. * @type {string}
  946. * @deprecated Use goog.ui.Dialog.EventType.SELECT.
  947. */
  948. goog.ui.Dialog.SELECT_EVENT = 'dialogselect';
  949. /**
  950. * Events dispatched by dialogs.
  951. * @enum {string}
  952. */
  953. goog.ui.Dialog.EventType = {
  954. /**
  955. * Dispatched when the user closes the dialog.
  956. * The dispatched event will always be of type {@link goog.ui.Dialog.Event}.
  957. * Canceling the event will prevent the dialog from closing.
  958. */
  959. SELECT: 'dialogselect',
  960. /**
  961. * Dispatched after the dialog is closed. Not cancelable.
  962. * @deprecated Use goog.ui.PopupBase.EventType.HIDE.
  963. */
  964. AFTER_HIDE: 'afterhide',
  965. /**
  966. * Dispatched after the dialog is shown. Not cancelable.
  967. * @deprecated Use goog.ui.PopupBase.EventType.SHOW.
  968. */
  969. AFTER_SHOW: 'aftershow'
  970. };
  971. /**
  972. * A button set defines the behaviour of a set of buttons that the dialog can
  973. * show. Uses the {@link goog.structs.Map} interface.
  974. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper; see {@link
  975. * goog.ui.Component} for semantics.
  976. * @constructor
  977. * @extends {goog.structs.Map}
  978. */
  979. goog.ui.Dialog.ButtonSet = function(opt_domHelper) {
  980. // TODO(attila): Refactor ButtonSet to extend goog.ui.Component?
  981. this.dom_ = opt_domHelper || goog.dom.getDomHelper();
  982. goog.structs.Map.call(this);
  983. };
  984. goog.inherits(goog.ui.Dialog.ButtonSet, goog.structs.Map);
  985. goog.tagUnsealableClass(goog.ui.Dialog.ButtonSet);
  986. /**
  987. * A CSS className for this component.
  988. * @type {string}
  989. * @private
  990. */
  991. goog.ui.Dialog.ButtonSet.prototype.class_ = goog.getCssName('goog-buttonset');
  992. /**
  993. * The button that has default focus (references key in buttons_ map).
  994. * @type {?string}
  995. * @private
  996. */
  997. goog.ui.Dialog.ButtonSet.prototype.defaultButton_ = null;
  998. /**
  999. * Optional container the button set should be rendered into.
  1000. * @type {Element}
  1001. * @private
  1002. */
  1003. goog.ui.Dialog.ButtonSet.prototype.element_ = null;
  1004. /**
  1005. * The button whose action is associated with the escape key and the X button
  1006. * on the dialog.
  1007. * @type {?string}
  1008. * @private
  1009. */
  1010. goog.ui.Dialog.ButtonSet.prototype.cancelButton_ = null;
  1011. /** @override */
  1012. goog.ui.Dialog.ButtonSet.prototype.clear = function() {
  1013. goog.structs.Map.prototype.clear.call(this);
  1014. this.defaultButton_ = this.cancelButton_ = null;
  1015. };
  1016. /**
  1017. * Adds a button to the button set. Buttons will be displayed in the order they
  1018. * are added.
  1019. *
  1020. * @param {*} key Key used to identify the button in events.
  1021. * @param {*} caption A string caption or a DOM node that can be
  1022. * appended to a button element.
  1023. * @param {boolean=} opt_isDefault Whether this button is the default button,
  1024. * Dialog will dispatch for this button if enter is pressed.
  1025. * @param {boolean=} opt_isCancel Whether this button has the same behaviour as
  1026. * cancel. If escape is pressed this button will fire.
  1027. * @return {!goog.ui.Dialog.ButtonSet} The button set, to make it easy to chain
  1028. * "set" calls and build new ButtonSets.
  1029. * @override
  1030. */
  1031. goog.ui.Dialog.ButtonSet.prototype.set = function(
  1032. key, caption, opt_isDefault, opt_isCancel) {
  1033. goog.structs.Map.prototype.set.call(this, key, caption);
  1034. if (opt_isDefault) {
  1035. this.defaultButton_ = /** @type {?string} */ (key);
  1036. }
  1037. if (opt_isCancel) {
  1038. this.cancelButton_ = /** @type {?string} */ (key);
  1039. }
  1040. return this;
  1041. };
  1042. /**
  1043. * Adds a button (an object with a key and caption) to this button set. Buttons
  1044. * will be displayed in the order they are added.
  1045. * @see goog.ui.Dialog.DefaultButtons
  1046. * @param {{key: string, caption: string}} button The button key and caption.
  1047. * @param {boolean=} opt_isDefault Whether this button is the default button.
  1048. * Dialog will dispatch for this button if enter is pressed.
  1049. * @param {boolean=} opt_isCancel Whether this button has the same behavior as
  1050. * cancel. If escape is pressed this button will fire.
  1051. * @return {!goog.ui.Dialog.ButtonSet} The button set, to make it easy to chain
  1052. * "addButton" calls and build new ButtonSets.
  1053. */
  1054. goog.ui.Dialog.ButtonSet.prototype.addButton = function(
  1055. button, opt_isDefault, opt_isCancel) {
  1056. return this.set(button.key, button.caption, opt_isDefault, opt_isCancel);
  1057. };
  1058. /**
  1059. * Attaches the button set to an element, rendering it inside.
  1060. * @param {Element} el Container.
  1061. */
  1062. goog.ui.Dialog.ButtonSet.prototype.attachToElement = function(el) {
  1063. this.element_ = el;
  1064. this.render();
  1065. };
  1066. /**
  1067. * Renders the button set inside its container element.
  1068. */
  1069. goog.ui.Dialog.ButtonSet.prototype.render = function() {
  1070. if (this.element_) {
  1071. goog.dom.safe.setInnerHtml(this.element_, goog.html.SafeHtml.EMPTY);
  1072. var domHelper = goog.dom.getDomHelper(this.element_);
  1073. this.forEach(function(caption, key) {
  1074. var button =
  1075. domHelper.createDom(goog.dom.TagName.BUTTON, {'name': key}, caption);
  1076. if (key == this.defaultButton_) {
  1077. button.className = goog.getCssName(this.class_, 'default');
  1078. }
  1079. this.element_.appendChild(button);
  1080. }, this);
  1081. }
  1082. };
  1083. /**
  1084. * Decorates the given element by adding any {@code button} elements found
  1085. * among its descendants to the button set. The first button found is assumed
  1086. * to be the default and will receive focus when the button set is rendered.
  1087. * If a button with a name of {@link goog.ui.Dialog.DefaultButtonKeys.CANCEL}
  1088. * is found, it is assumed to have "Cancel" semantics.
  1089. * TODO(attila): ButtonSet should be a goog.ui.Component. Really.
  1090. * @param {Element} element The element to decorate; should contain buttons.
  1091. */
  1092. goog.ui.Dialog.ButtonSet.prototype.decorate = function(element) {
  1093. if (!element || element.nodeType != goog.dom.NodeType.ELEMENT) {
  1094. return;
  1095. }
  1096. this.element_ = element;
  1097. var buttons =
  1098. goog.dom.getElementsByTagName(goog.dom.TagName.BUTTON, this.element_);
  1099. for (var i = 0, button, key, caption; button = buttons[i]; i++) {
  1100. // Buttons should have a "name" attribute and have their caption defined by
  1101. // their innerHTML, but not everyone knows this, and we should play nice.
  1102. key = button.name || button.id;
  1103. caption = goog.dom.getTextContent(button) || button.value;
  1104. if (key) {
  1105. var isDefault = i == 0;
  1106. var isCancel = button.name == goog.ui.Dialog.DefaultButtonKeys.CANCEL;
  1107. this.set(key, caption, isDefault, isCancel);
  1108. if (isDefault) {
  1109. goog.dom.classlist.add(button, goog.getCssName(this.class_, 'default'));
  1110. }
  1111. }
  1112. }
  1113. };
  1114. /**
  1115. * Gets the component's element.
  1116. * @return {Element} The element for the component.
  1117. * TODO(user): Remove after refactoring to goog.ui.Component.
  1118. */
  1119. goog.ui.Dialog.ButtonSet.prototype.getElement = function() {
  1120. return this.element_;
  1121. };
  1122. /**
  1123. * Returns the dom helper that is being used on this component.
  1124. * @return {!goog.dom.DomHelper} The dom helper used on this component.
  1125. * TODO(user): Remove after refactoring to goog.ui.Component.
  1126. */
  1127. goog.ui.Dialog.ButtonSet.prototype.getDomHelper = function() {
  1128. return this.dom_;
  1129. };
  1130. /**
  1131. * Sets the default button.
  1132. * @param {?string} key The default button.
  1133. */
  1134. goog.ui.Dialog.ButtonSet.prototype.setDefault = function(key) {
  1135. this.defaultButton_ = key;
  1136. };
  1137. /**
  1138. * Returns the default button.
  1139. * @return {?string} The default button.
  1140. */
  1141. goog.ui.Dialog.ButtonSet.prototype.getDefault = function() {
  1142. return this.defaultButton_;
  1143. };
  1144. /**
  1145. * Sets the cancel button.
  1146. * @param {?string} key The cancel button.
  1147. */
  1148. goog.ui.Dialog.ButtonSet.prototype.setCancel = function(key) {
  1149. this.cancelButton_ = key;
  1150. };
  1151. /**
  1152. * Returns the cancel button.
  1153. * @return {?string} The cancel button.
  1154. */
  1155. goog.ui.Dialog.ButtonSet.prototype.getCancel = function() {
  1156. return this.cancelButton_;
  1157. };
  1158. /**
  1159. * Returns the HTML Button element.
  1160. * @param {string} key The button to return.
  1161. * @return {Element} The button, if found else null.
  1162. */
  1163. goog.ui.Dialog.ButtonSet.prototype.getButton = function(key) {
  1164. var buttons = this.getAllButtons();
  1165. for (var i = 0, nextButton; nextButton = buttons[i]; i++) {
  1166. if (nextButton.name == key || nextButton.id == key) {
  1167. return nextButton;
  1168. }
  1169. }
  1170. return null;
  1171. };
  1172. /**
  1173. * Returns all the HTML Button elements in the button set container.
  1174. * @return {!IArrayLike<!Element>} A live NodeList of the buttons.
  1175. */
  1176. goog.ui.Dialog.ButtonSet.prototype.getAllButtons = function() {
  1177. return goog.dom.getElementsByTagName(
  1178. goog.dom.TagName.BUTTON, goog.asserts.assert(this.element_));
  1179. };
  1180. /**
  1181. * Enables or disables a button in this set by key. If the button is not found,
  1182. * does nothing.
  1183. * @param {string} key The button to enable or disable.
  1184. * @param {boolean} enabled True to enable; false to disable.
  1185. */
  1186. goog.ui.Dialog.ButtonSet.prototype.setButtonEnabled = function(key, enabled) {
  1187. var button = this.getButton(key);
  1188. if (button) {
  1189. button.disabled = !enabled;
  1190. }
  1191. };
  1192. /**
  1193. * Enables or disables all of the buttons in this set.
  1194. * @param {boolean} enabled True to enable; false to disable.
  1195. */
  1196. goog.ui.Dialog.ButtonSet.prototype.setAllButtonsEnabled = function(enabled) {
  1197. var allButtons = this.getAllButtons();
  1198. for (var i = 0, button; button = allButtons[i]; i++) {
  1199. button.disabled = !enabled;
  1200. }
  1201. };
  1202. /**
  1203. * The keys used to identify standard buttons in events.
  1204. * @enum {string}
  1205. */
  1206. goog.ui.Dialog.DefaultButtonKeys = {
  1207. OK: 'ok',
  1208. CANCEL: 'cancel',
  1209. YES: 'yes',
  1210. NO: 'no',
  1211. SAVE: 'save',
  1212. CONTINUE: 'continue'
  1213. };
  1214. /**
  1215. * @desc Standard caption for the dialog 'OK' button.
  1216. * @private
  1217. */
  1218. goog.ui.Dialog.MSG_DIALOG_OK_ = goog.getMsg('OK');
  1219. /**
  1220. * @desc Standard caption for the dialog 'Cancel' button.
  1221. * @private
  1222. */
  1223. goog.ui.Dialog.MSG_DIALOG_CANCEL_ = goog.getMsg('Cancel');
  1224. /**
  1225. * @desc Standard caption for the dialog 'Yes' button.
  1226. * @private
  1227. */
  1228. goog.ui.Dialog.MSG_DIALOG_YES_ = goog.getMsg('Yes');
  1229. /**
  1230. * @desc Standard caption for the dialog 'No' button.
  1231. * @private
  1232. */
  1233. goog.ui.Dialog.MSG_DIALOG_NO_ = goog.getMsg('No');
  1234. /**
  1235. * @desc Standard caption for the dialog 'Save' button.
  1236. * @private
  1237. */
  1238. goog.ui.Dialog.MSG_DIALOG_SAVE_ = goog.getMsg('Save');
  1239. /**
  1240. * @desc Standard caption for the dialog 'Continue' button.
  1241. * @private
  1242. */
  1243. goog.ui.Dialog.MSG_DIALOG_CONTINUE_ = goog.getMsg('Continue');
  1244. /**
  1245. * @desc Standard label for the dialog 'X' (close) button.
  1246. * @private
  1247. */
  1248. goog.ui.Dialog.MSG_GOOG_UI_DIALOG_CLOSE_ = goog.getMsg('Close');
  1249. /**
  1250. * The default captions for the default buttons.
  1251. * @enum {string}
  1252. */
  1253. goog.ui.Dialog.DefaultButtonCaptions = {
  1254. OK: goog.ui.Dialog.MSG_DIALOG_OK_,
  1255. CANCEL: goog.ui.Dialog.MSG_DIALOG_CANCEL_,
  1256. YES: goog.ui.Dialog.MSG_DIALOG_YES_,
  1257. NO: goog.ui.Dialog.MSG_DIALOG_NO_,
  1258. SAVE: goog.ui.Dialog.MSG_DIALOG_SAVE_,
  1259. CONTINUE: goog.ui.Dialog.MSG_DIALOG_CONTINUE_
  1260. };
  1261. /**
  1262. * The standard buttons (keys associated with captions).
  1263. * @enum {{key: string, caption: string}}
  1264. */
  1265. goog.ui.Dialog.ButtonSet.DefaultButtons = {
  1266. OK: {
  1267. key: goog.ui.Dialog.DefaultButtonKeys.OK,
  1268. caption: goog.ui.Dialog.DefaultButtonCaptions.OK
  1269. },
  1270. CANCEL: {
  1271. key: goog.ui.Dialog.DefaultButtonKeys.CANCEL,
  1272. caption: goog.ui.Dialog.DefaultButtonCaptions.CANCEL
  1273. },
  1274. YES: {
  1275. key: goog.ui.Dialog.DefaultButtonKeys.YES,
  1276. caption: goog.ui.Dialog.DefaultButtonCaptions.YES
  1277. },
  1278. NO: {
  1279. key: goog.ui.Dialog.DefaultButtonKeys.NO,
  1280. caption: goog.ui.Dialog.DefaultButtonCaptions.NO
  1281. },
  1282. SAVE: {
  1283. key: goog.ui.Dialog.DefaultButtonKeys.SAVE,
  1284. caption: goog.ui.Dialog.DefaultButtonCaptions.SAVE
  1285. },
  1286. CONTINUE: {
  1287. key: goog.ui.Dialog.DefaultButtonKeys.CONTINUE,
  1288. caption: goog.ui.Dialog.DefaultButtonCaptions.CONTINUE
  1289. }
  1290. };
  1291. /**
  1292. * Creates a new ButtonSet with a single 'OK' button, which is also set with
  1293. * cancel button semantics so that pressing escape will close the dialog.
  1294. * @return {!goog.ui.Dialog.ButtonSet} The created ButtonSet.
  1295. */
  1296. goog.ui.Dialog.ButtonSet.createOk = function() {
  1297. return new goog.ui.Dialog.ButtonSet().addButton(
  1298. goog.ui.Dialog.ButtonSet.DefaultButtons.OK, true, true);
  1299. };
  1300. /**
  1301. * Creates a new ButtonSet with 'OK' (default) and 'Cancel' buttons.
  1302. * @return {!goog.ui.Dialog.ButtonSet} The created ButtonSet.
  1303. */
  1304. goog.ui.Dialog.ButtonSet.createOkCancel = function() {
  1305. return new goog.ui.Dialog.ButtonSet()
  1306. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.OK, true)
  1307. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.CANCEL, false, true);
  1308. };
  1309. /**
  1310. * Creates a new ButtonSet with 'Yes' (default) and 'No' buttons.
  1311. * @return {!goog.ui.Dialog.ButtonSet} The created ButtonSet.
  1312. */
  1313. goog.ui.Dialog.ButtonSet.createYesNo = function() {
  1314. return new goog.ui.Dialog.ButtonSet()
  1315. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.YES, true)
  1316. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.NO, false, true);
  1317. };
  1318. /**
  1319. * Creates a new ButtonSet with 'Yes', 'No' (default), and 'Cancel' buttons.
  1320. * @return {!goog.ui.Dialog.ButtonSet} The created ButtonSet.
  1321. */
  1322. goog.ui.Dialog.ButtonSet.createYesNoCancel = function() {
  1323. return new goog.ui.Dialog.ButtonSet()
  1324. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.YES)
  1325. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.NO, true)
  1326. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.CANCEL, false, true);
  1327. };
  1328. /**
  1329. * Creates a new ButtonSet with 'Continue', 'Save', and 'Cancel' (default)
  1330. * buttons.
  1331. * @return {!goog.ui.Dialog.ButtonSet} The created ButtonSet.
  1332. */
  1333. goog.ui.Dialog.ButtonSet.createContinueSaveCancel = function() {
  1334. return new goog.ui.Dialog.ButtonSet()
  1335. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.CONTINUE)
  1336. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.SAVE)
  1337. .addButton(goog.ui.Dialog.ButtonSet.DefaultButtons.CANCEL, true, true);
  1338. };
  1339. // TODO(user): These shared instances should be phased out.
  1340. (function() {
  1341. if (typeof document != 'undefined') {
  1342. /** @deprecated Use goog.ui.Dialog.ButtonSet#createOk. */
  1343. goog.ui.Dialog.ButtonSet.OK = goog.ui.Dialog.ButtonSet.createOk();
  1344. /** @deprecated Use goog.ui.Dialog.ButtonSet#createOkCancel. */
  1345. goog.ui.Dialog.ButtonSet.OK_CANCEL =
  1346. goog.ui.Dialog.ButtonSet.createOkCancel();
  1347. /** @deprecated Use goog.ui.Dialog.ButtonSet#createYesNo. */
  1348. goog.ui.Dialog.ButtonSet.YES_NO = goog.ui.Dialog.ButtonSet.createYesNo();
  1349. /** @deprecated Use goog.ui.Dialog.ButtonSet#createYesNoCancel. */
  1350. goog.ui.Dialog.ButtonSet.YES_NO_CANCEL =
  1351. goog.ui.Dialog.ButtonSet.createYesNoCancel();
  1352. /** @deprecated Use goog.ui.Dialog.ButtonSet#createContinueSaveCancel. */
  1353. goog.ui.Dialog.ButtonSet.CONTINUE_SAVE_CANCEL =
  1354. goog.ui.Dialog.ButtonSet.createContinueSaveCancel();
  1355. }
  1356. })();