modal-dialog.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. 'use strict';
  2. exports.__esModule = true;
  3. var _dom = require('./utils/dom');
  4. var Dom = _interopRequireWildcard(_dom);
  5. var _fn = require('./utils/fn');
  6. var Fn = _interopRequireWildcard(_fn);
  7. var _component = require('./component');
  8. var _component2 = _interopRequireDefault(_component);
  9. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  10. function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
  11. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  12. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  13. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
  14. * @file modal-dialog.js
  15. */
  16. var MODAL_CLASS_NAME = 'vjs-modal-dialog';
  17. var ESC = 27;
  18. /**
  19. * The `ModalDialog` displays over the video and its controls, which blocks
  20. * interaction with the player until it is closed.
  21. *
  22. * Modal dialogs include a "Close" button and will close when that button
  23. * is activated - or when ESC is pressed anywhere.
  24. *
  25. * @extends Component
  26. */
  27. var ModalDialog = function (_Component) {
  28. _inherits(ModalDialog, _Component);
  29. /**
  30. * Create an instance of this class.
  31. *
  32. * @param {Player} player
  33. * The `Player` that this class should be attached to.
  34. *
  35. * @param {Object} [options]
  36. * The key/value store of player options.
  37. *
  38. * @param {Mixed} [options.content=undefined]
  39. * Provide customized content for this modal.
  40. *
  41. * @param {string} [options.description]
  42. * A text description for the modal, primarily for accessibility.
  43. *
  44. * @param {boolean} [options.fillAlways=false]
  45. * Normally, modals are automatically filled only the first time
  46. * they open. This tells the modal to refresh its content
  47. * every time it opens.
  48. *
  49. * @param {string} [options.label]
  50. * A text label for the modal, primarily for accessibility.
  51. *
  52. * @param {boolean} [options.temporary=true]
  53. * If `true`, the modal can only be opened once; it will be
  54. * disposed as soon as it's closed.
  55. *
  56. * @param {boolean} [options.uncloseable=false]
  57. * If `true`, the user will not be able to close the modal
  58. * through the UI in the normal ways. Programmatic closing is
  59. * still possible.
  60. */
  61. function ModalDialog(player, options) {
  62. _classCallCheck(this, ModalDialog);
  63. var _this = _possibleConstructorReturn(this, _Component.call(this, player, options));
  64. _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
  65. _this.closeable(!_this.options_.uncloseable);
  66. _this.content(_this.options_.content);
  67. // Make sure the contentEl is defined AFTER any children are initialized
  68. // because we only want the contents of the modal in the contentEl
  69. // (not the UI elements like the close button).
  70. _this.contentEl_ = Dom.createEl('div', {
  71. className: MODAL_CLASS_NAME + '-content'
  72. }, {
  73. role: 'document'
  74. });
  75. _this.descEl_ = Dom.createEl('p', {
  76. className: MODAL_CLASS_NAME + '-description vjs-offscreen',
  77. id: _this.el().getAttribute('aria-describedby')
  78. });
  79. Dom.textContent(_this.descEl_, _this.description());
  80. _this.el_.appendChild(_this.descEl_);
  81. _this.el_.appendChild(_this.contentEl_);
  82. return _this;
  83. }
  84. /**
  85. * Create the `ModalDialog`'s DOM element
  86. *
  87. * @return {Element}
  88. * The DOM element that gets created.
  89. */
  90. ModalDialog.prototype.createEl = function createEl() {
  91. return _Component.prototype.createEl.call(this, 'div', {
  92. className: this.buildCSSClass(),
  93. tabIndex: -1
  94. }, {
  95. 'aria-describedby': this.id() + '_description',
  96. 'aria-hidden': 'true',
  97. 'aria-label': this.label(),
  98. 'role': 'dialog'
  99. });
  100. };
  101. /**
  102. * Builds the default DOM `className`.
  103. *
  104. * @return {string}
  105. * The DOM `className` for this object.
  106. */
  107. ModalDialog.prototype.buildCSSClass = function buildCSSClass() {
  108. return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this);
  109. };
  110. /**
  111. * Handles `keydown` events on the document, looking for ESC, which closes
  112. * the modal.
  113. *
  114. * @param {EventTarget~Event} e
  115. * The keypress that triggered this event.
  116. *
  117. * @listens keydown
  118. */
  119. ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) {
  120. if (e.which === ESC && this.closeable()) {
  121. this.close();
  122. }
  123. };
  124. /**
  125. * Returns the label string for this modal. Primarily used for accessibility.
  126. *
  127. * @return {string}
  128. * the localized or raw label of this modal.
  129. */
  130. ModalDialog.prototype.label = function label() {
  131. return this.options_.label || this.localize('Modal Window');
  132. };
  133. /**
  134. * Returns the description string for this modal. Primarily used for
  135. * accessibility.
  136. *
  137. * @return {string}
  138. * The localized or raw description of this modal.
  139. */
  140. ModalDialog.prototype.description = function description() {
  141. var desc = this.options_.description || this.localize('This is a modal window.');
  142. // Append a universal closeability message if the modal is closeable.
  143. if (this.closeable()) {
  144. desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
  145. }
  146. return desc;
  147. };
  148. /**
  149. * Opens the modal.
  150. *
  151. * @fires ModalDialog#beforemodalopen
  152. * @fires ModalDialog#modalopen
  153. *
  154. * @return {ModalDialog}
  155. * Returns itself; method can be chained.
  156. */
  157. ModalDialog.prototype.open = function open() {
  158. if (!this.opened_) {
  159. var player = this.player();
  160. /**
  161. * Fired just before a `ModalDialog` is opened.
  162. *
  163. * @event ModalDialog#beforemodalopen
  164. * @type {EventTarget~Event}
  165. */
  166. this.trigger('beforemodalopen');
  167. this.opened_ = true;
  168. // Fill content if the modal has never opened before and
  169. // never been filled.
  170. if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
  171. this.fill();
  172. }
  173. // If the player was playing, pause it and take note of its previously
  174. // playing state.
  175. this.wasPlaying_ = !player.paused();
  176. if (this.options_.pauseOnOpen && this.wasPlaying_) {
  177. player.pause();
  178. }
  179. if (this.closeable()) {
  180. this.on(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
  181. }
  182. player.controls(false);
  183. this.show();
  184. this.el().setAttribute('aria-hidden', 'false');
  185. /**
  186. * Fired just after a `ModalDialog` is opened.
  187. *
  188. * @event ModalDialog#modalopen
  189. * @type {EventTarget~Event}
  190. */
  191. this.trigger('modalopen');
  192. this.hasBeenOpened_ = true;
  193. }
  194. return this;
  195. };
  196. /**
  197. * If the `ModalDialog` is currently open or closed.
  198. *
  199. * @param {boolean} [value]
  200. * If given, it will open (`true`) or close (`false`) the modal.
  201. *
  202. * @return {boolean}
  203. * the current open state of the modaldialog
  204. */
  205. ModalDialog.prototype.opened = function opened(value) {
  206. if (typeof value === 'boolean') {
  207. this[value ? 'open' : 'close']();
  208. }
  209. return this.opened_;
  210. };
  211. /**
  212. * Closes the modal, does nothing if the `ModalDialog` is
  213. * not open.
  214. *
  215. * @fires ModalDialog#beforemodalclose
  216. * @fires ModalDialog#modalclose
  217. *
  218. * @return {ModalDialog}
  219. * Returns itself; method can be chained.
  220. */
  221. ModalDialog.prototype.close = function close() {
  222. if (this.opened_) {
  223. var player = this.player();
  224. /**
  225. * Fired just before a `ModalDialog` is closed.
  226. *
  227. * @event ModalDialog#beforemodalclose
  228. * @type {EventTarget~Event}
  229. */
  230. this.trigger('beforemodalclose');
  231. this.opened_ = false;
  232. if (this.wasPlaying_ && this.options_.pauseOnOpen) {
  233. player.play();
  234. }
  235. if (this.closeable()) {
  236. this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
  237. }
  238. player.controls(true);
  239. this.hide();
  240. this.el().setAttribute('aria-hidden', 'true');
  241. /**
  242. * Fired just after a `ModalDialog` is closed.
  243. *
  244. * @event ModalDialog#modalclose
  245. * @type {EventTarget~Event}
  246. */
  247. this.trigger('modalclose');
  248. if (this.options_.temporary) {
  249. this.dispose();
  250. }
  251. }
  252. return this;
  253. };
  254. /**
  255. * Check to see if the `ModalDialog` is closeable via the UI.
  256. *
  257. * @param {boolean} [value]
  258. * If given as a boolean, it will set the `closeable` option.
  259. *
  260. * @return {boolean}
  261. * Returns the final value of the closable option.
  262. */
  263. ModalDialog.prototype.closeable = function closeable(value) {
  264. if (typeof value === 'boolean') {
  265. var closeable = this.closeable_ = !!value;
  266. var close = this.getChild('closeButton');
  267. // If this is being made closeable and has no close button, add one.
  268. if (closeable && !close) {
  269. // The close button should be a child of the modal - not its
  270. // content element, so temporarily change the content element.
  271. var temp = this.contentEl_;
  272. this.contentEl_ = this.el_;
  273. close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' });
  274. this.contentEl_ = temp;
  275. this.on(close, 'close', this.close);
  276. }
  277. // If this is being made uncloseable and has a close button, remove it.
  278. if (!closeable && close) {
  279. this.off(close, 'close', this.close);
  280. this.removeChild(close);
  281. close.dispose();
  282. }
  283. }
  284. return this.closeable_;
  285. };
  286. /**
  287. * Fill the modal's content element with the modal's "content" option.
  288. * The content element will be emptied before this change takes place.
  289. *
  290. * @return {ModalDialog}
  291. * Returns itself; method can be chained.
  292. */
  293. ModalDialog.prototype.fill = function fill() {
  294. return this.fillWith(this.content());
  295. };
  296. /**
  297. * Fill the modal's content element with arbitrary content.
  298. * The content element will be emptied before this change takes place.
  299. *
  300. * @fires ModalDialog#beforemodalfill
  301. * @fires ModalDialog#modalfill
  302. *
  303. * @param {Mixed} [content]
  304. * The same rules apply to this as apply to the `content` option.
  305. *
  306. * @return {ModalDialog}
  307. * Returns itself; method can be chained.
  308. */
  309. ModalDialog.prototype.fillWith = function fillWith(content) {
  310. var contentEl = this.contentEl();
  311. var parentEl = contentEl.parentNode;
  312. var nextSiblingEl = contentEl.nextSibling;
  313. /**
  314. * Fired just before a `ModalDialog` is filled with content.
  315. *
  316. * @event ModalDialog#beforemodalfill
  317. * @type {EventTarget~Event}
  318. */
  319. this.trigger('beforemodalfill');
  320. this.hasBeenFilled_ = true;
  321. // Detach the content element from the DOM before performing
  322. // manipulation to avoid modifying the live DOM multiple times.
  323. parentEl.removeChild(contentEl);
  324. this.empty();
  325. Dom.insertContent(contentEl, content);
  326. /**
  327. * Fired just after a `ModalDialog` is filled with content.
  328. *
  329. * @event ModalDialog#modalfill
  330. * @type {EventTarget~Event}
  331. */
  332. this.trigger('modalfill');
  333. // Re-inject the re-filled content element.
  334. if (nextSiblingEl) {
  335. parentEl.insertBefore(contentEl, nextSiblingEl);
  336. } else {
  337. parentEl.appendChild(contentEl);
  338. }
  339. return this;
  340. };
  341. /**
  342. * Empties the content element. This happens anytime the modal is filled.
  343. *
  344. * @fires ModalDialog#beforemodalempty
  345. * @fires ModalDialog#modalempty
  346. *
  347. * @return {ModalDialog}
  348. * Returns itself; method can be chained.
  349. */
  350. ModalDialog.prototype.empty = function empty() {
  351. /**
  352. * Fired just before a `ModalDialog` is emptied.
  353. *
  354. * @event ModalDialog#beforemodalempty
  355. * @type {EventTarget~Event}
  356. */
  357. this.trigger('beforemodalempty');
  358. Dom.emptyEl(this.contentEl());
  359. /**
  360. * Fired just after a `ModalDialog` is emptied.
  361. *
  362. * @event ModalDialog#modalempty
  363. * @type {EventTarget~Event}
  364. */
  365. this.trigger('modalempty');
  366. return this;
  367. };
  368. /**
  369. * Gets or sets the modal content, which gets normalized before being
  370. * rendered into the DOM.
  371. *
  372. * This does not update the DOM or fill the modal, but it is called during
  373. * that process.
  374. *
  375. * @param {Mixed} [value]
  376. * If defined, sets the internal content value to be used on the
  377. * next call(s) to `fill`. This value is normalized before being
  378. * inserted. To "clear" the internal content value, pass `null`.
  379. *
  380. * @return {Mixed}
  381. * The current content of the modal dialog
  382. */
  383. ModalDialog.prototype.content = function content(value) {
  384. if (typeof value !== 'undefined') {
  385. this.content_ = value;
  386. }
  387. return this.content_;
  388. };
  389. return ModalDialog;
  390. }(_component2['default']);
  391. /**
  392. * Default options for `ModalDialog` default options.
  393. *
  394. * @type {Object}
  395. * @private
  396. */
  397. ModalDialog.prototype.options_ = {
  398. pauseOnOpen: true,
  399. temporary: true
  400. };
  401. _component2['default'].registerComponent('ModalDialog', ModalDialog);
  402. exports['default'] = ModalDialog;