bootbox.all.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444
  1. /*! @preserve
  2. * bootbox.js
  3. * version: 5.5.2
  4. * author: Nick Payne <nick@kurai.co.uk>
  5. * license: MIT
  6. * http://bootboxjs.com/
  7. */
  8. (function (root, factory) {
  9. 'use strict';
  10. if (typeof define === 'function' && define.amd) {
  11. // AMD
  12. define(['jquery'], factory);
  13. } else if (typeof exports === 'object') {
  14. // Node, CommonJS-like
  15. module.exports = factory(require('jquery'));
  16. } else {
  17. // Browser globals (root is window)
  18. root.bootbox = factory(root.jQuery);
  19. }
  20. }(this, function init($, undefined) {
  21. 'use strict';
  22. // Polyfills Object.keys, if necessary.
  23. // @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
  24. if (!Object.keys) {
  25. Object.keys = (function () {
  26. var hasOwnProperty = Object.prototype.hasOwnProperty,
  27. hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
  28. dontEnums = [
  29. 'toString',
  30. 'toLocaleString',
  31. 'valueOf',
  32. 'hasOwnProperty',
  33. 'isPrototypeOf',
  34. 'propertyIsEnumerable',
  35. 'constructor'
  36. ],
  37. dontEnumsLength = dontEnums.length;
  38. return function (obj) {
  39. if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
  40. throw new TypeError('Object.keys called on non-object');
  41. }
  42. var result = [], prop, i;
  43. for (prop in obj) {
  44. if (hasOwnProperty.call(obj, prop)) {
  45. result.push(prop);
  46. }
  47. }
  48. if (hasDontEnumBug) {
  49. for (i = 0; i < dontEnumsLength; i++) {
  50. if (hasOwnProperty.call(obj, dontEnums[i])) {
  51. result.push(dontEnums[i]);
  52. }
  53. }
  54. }
  55. return result;
  56. };
  57. }());
  58. }
  59. var exports = {};
  60. var VERSION = '5.5.2';
  61. exports.VERSION = VERSION;
  62. var locales = {
  63. ar : {
  64. OK : 'موافق',
  65. CANCEL : 'الغاء',
  66. CONFIRM : 'تأكيد'
  67. },
  68. bg_BG : {
  69. OK : 'Ок',
  70. CANCEL : 'Отказ',
  71. CONFIRM : 'Потвърждавам'
  72. },
  73. br : {
  74. OK : 'OK',
  75. CANCEL : 'Cancelar',
  76. CONFIRM : 'Sim'
  77. },
  78. cs : {
  79. OK : 'OK',
  80. CANCEL : 'Zrušit',
  81. CONFIRM : 'Potvrdit'
  82. },
  83. da : {
  84. OK : 'OK',
  85. CANCEL : 'Annuller',
  86. CONFIRM : 'Accepter'
  87. },
  88. de : {
  89. OK : 'OK',
  90. CANCEL : 'Abbrechen',
  91. CONFIRM : 'Akzeptieren'
  92. },
  93. el : {
  94. OK : 'Εντάξει',
  95. CANCEL : 'Ακύρωση',
  96. CONFIRM : 'Επιβεβαίωση'
  97. },
  98. en : {
  99. OK : 'OK',
  100. CANCEL : 'Cancel',
  101. CONFIRM : 'OK'
  102. },
  103. es : {
  104. OK : 'OK',
  105. CANCEL : 'Cancelar',
  106. CONFIRM : 'Aceptar'
  107. },
  108. eu : {
  109. OK : 'OK',
  110. CANCEL : 'Ezeztatu',
  111. CONFIRM : 'Onartu'
  112. },
  113. et : {
  114. OK : 'OK',
  115. CANCEL : 'Katkesta',
  116. CONFIRM : 'OK'
  117. },
  118. fa : {
  119. OK : 'قبول',
  120. CANCEL : 'لغو',
  121. CONFIRM : 'تایید'
  122. },
  123. fi : {
  124. OK : 'OK',
  125. CANCEL : 'Peruuta',
  126. CONFIRM : 'OK'
  127. },
  128. fr : {
  129. OK : 'OK',
  130. CANCEL : 'Annuler',
  131. CONFIRM : 'Confirmer'
  132. },
  133. he : {
  134. OK : 'אישור',
  135. CANCEL : 'ביטול',
  136. CONFIRM : 'אישור'
  137. },
  138. hu : {
  139. OK : 'OK',
  140. CANCEL : 'Mégsem',
  141. CONFIRM : 'Megerősít'
  142. },
  143. hr : {
  144. OK : 'OK',
  145. CANCEL : 'Odustani',
  146. CONFIRM : 'Potvrdi'
  147. },
  148. id : {
  149. OK : 'OK',
  150. CANCEL : 'Batal',
  151. CONFIRM : 'OK'
  152. },
  153. it : {
  154. OK : 'OK',
  155. CANCEL : 'Annulla',
  156. CONFIRM : 'Conferma'
  157. },
  158. ja : {
  159. OK : 'OK',
  160. CANCEL : 'キャンセル',
  161. CONFIRM : '確認'
  162. },
  163. ka : {
  164. OK: 'OK',
  165. CANCEL: 'გაუქმება',
  166. CONFIRM: 'დადასტურება'
  167. },
  168. ko : {
  169. OK: 'OK',
  170. CANCEL: '취소',
  171. CONFIRM: '확인'
  172. },
  173. lt : {
  174. OK : 'Gerai',
  175. CANCEL : 'Atšaukti',
  176. CONFIRM : 'Patvirtinti'
  177. },
  178. lv : {
  179. OK : 'Labi',
  180. CANCEL : 'Atcelt',
  181. CONFIRM : 'Apstiprināt'
  182. },
  183. nl : {
  184. OK : 'OK',
  185. CANCEL : 'Annuleren',
  186. CONFIRM : 'Accepteren'
  187. },
  188. no : {
  189. OK : 'OK',
  190. CANCEL : 'Avbryt',
  191. CONFIRM : 'OK'
  192. },
  193. pl : {
  194. OK : 'OK',
  195. CANCEL : 'Anuluj',
  196. CONFIRM : 'Potwierdź'
  197. },
  198. pt : {
  199. OK : 'OK',
  200. CANCEL : 'Cancelar',
  201. CONFIRM : 'Confirmar'
  202. },
  203. ru : {
  204. OK : 'OK',
  205. CANCEL : 'Отмена',
  206. CONFIRM : 'Применить'
  207. },
  208. sk : {
  209. OK : 'OK',
  210. CANCEL : 'Zrušiť',
  211. CONFIRM : 'Potvrdiť'
  212. },
  213. sl : {
  214. OK : 'OK',
  215. CANCEL : 'Prekliči',
  216. CONFIRM : 'Potrdi'
  217. },
  218. sq : {
  219. OK : 'OK',
  220. CANCEL : 'Anulo',
  221. CONFIRM : 'Prano'
  222. },
  223. sv : {
  224. OK : 'OK',
  225. CANCEL : 'Avbryt',
  226. CONFIRM : 'OK'
  227. },
  228. sw: {
  229. OK : 'Sawa',
  230. CANCEL : 'Ghairi',
  231. CONFIRM: 'Thibitisha'
  232. },
  233. ta:{
  234. OK : 'சரி',
  235. CANCEL : 'ரத்து செய்',
  236. CONFIRM : 'உறுதி செய்'
  237. },
  238. th : {
  239. OK : 'ตกลง',
  240. CANCEL : 'ยกเลิก',
  241. CONFIRM : 'ยืนยัน'
  242. },
  243. tr : {
  244. OK : 'Tamam',
  245. CANCEL : 'İptal',
  246. CONFIRM : 'Onayla'
  247. },
  248. uk : {
  249. OK : 'OK',
  250. CANCEL : 'Відміна',
  251. CONFIRM : 'Прийняти'
  252. },
  253. vi : {
  254. OK : 'OK',
  255. CANCEL : 'Hủy bỏ',
  256. CONFIRM : 'Xác nhận'
  257. },
  258. zh_CN : {
  259. OK : 'OK',
  260. CANCEL : '取消',
  261. CONFIRM : '确认'
  262. },
  263. zh_TW : {
  264. OK : 'OK',
  265. CANCEL : '取消',
  266. CONFIRM : '確認'
  267. }
  268. };
  269. var templates = {
  270. dialog:
  271. '<div class="bootbox modal" tabindex="-1" role="dialog" aria-hidden="true">' +
  272. '<div class="modal-dialog">' +
  273. '<div class="modal-content">' +
  274. '<div class="modal-body"><div class="bootbox-body"></div></div>' +
  275. '</div>' +
  276. '</div>' +
  277. '</div>',
  278. header:
  279. '<div class="modal-header">' +
  280. '<h5 class="modal-title"></h5>' +
  281. '</div>',
  282. footer:
  283. '<div class="modal-footer"></div>',
  284. closeButton:
  285. '<button type="button" class="bootbox-close-button close" aria-hidden="true">&times;</button>',
  286. form:
  287. '<form class="bootbox-form"></form>',
  288. button:
  289. '<button type="button" class="btn"></button>',
  290. option:
  291. '<option></option>',
  292. promptMessage:
  293. '<div class="bootbox-prompt-message"></div>',
  294. inputs: {
  295. text:
  296. '<input class="bootbox-input bootbox-input-text form-control" autocomplete="off" type="text" />',
  297. textarea:
  298. '<textarea class="bootbox-input bootbox-input-textarea form-control"></textarea>',
  299. email:
  300. '<input class="bootbox-input bootbox-input-email form-control" autocomplete="off" type="email" />',
  301. select:
  302. '<select class="bootbox-input bootbox-input-select form-control"></select>',
  303. checkbox:
  304. '<div class="form-check checkbox"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-checkbox" type="checkbox" /></label></div>',
  305. radio:
  306. '<div class="form-check radio"><label class="form-check-label"><input class="form-check-input bootbox-input bootbox-input-radio" type="radio" name="bootbox-radio" /></label></div>',
  307. date:
  308. '<input class="bootbox-input bootbox-input-date form-control" autocomplete="off" type="date" />',
  309. time:
  310. '<input class="bootbox-input bootbox-input-time form-control" autocomplete="off" type="time" />',
  311. number:
  312. '<input class="bootbox-input bootbox-input-number form-control" autocomplete="off" type="number" />',
  313. password:
  314. '<input class="bootbox-input bootbox-input-password form-control" autocomplete="off" type="password" />',
  315. range:
  316. '<input class="bootbox-input bootbox-input-range form-control-range" autocomplete="off" type="range" />'
  317. }
  318. };
  319. var defaults = {
  320. // default language
  321. locale: 'en',
  322. // show backdrop or not. Default to static so user has to interact with dialog
  323. backdrop: 'static',
  324. // animate the modal in/out
  325. animate: true,
  326. // additional class string applied to the top level dialog
  327. className: null,
  328. // whether or not to include a close button
  329. closeButton: true,
  330. // show the dialog immediately by default
  331. show: true,
  332. // dialog container
  333. container: 'body',
  334. // default value (used by the prompt helper)
  335. value: '',
  336. // default input type (used by the prompt helper)
  337. inputType: 'text',
  338. // switch button order from cancel/confirm (default) to confirm/cancel
  339. swapButtonOrder: false,
  340. // center modal vertically in page
  341. centerVertical: false,
  342. // Append "multiple" property to the select when using the "prompt" helper
  343. multiple: false,
  344. // Automatically scroll modal content when height exceeds viewport height
  345. scrollable: false,
  346. // whether or not to destroy the modal on hide
  347. reusable: false
  348. };
  349. // PUBLIC FUNCTIONS
  350. // *************************************************************************************************************
  351. // Return all currently registered locales, or a specific locale if "name" is defined
  352. exports.locales = function (name) {
  353. return name ? locales[name] : locales;
  354. };
  355. // Register localized strings for the OK, CONFIRM, and CANCEL buttons
  356. exports.addLocale = function (name, values) {
  357. $.each(['OK', 'CANCEL', 'CONFIRM'], function (_, v) {
  358. if (!values[v]) {
  359. throw new Error('Please supply a translation for "' + v + '"');
  360. }
  361. });
  362. locales[name] = {
  363. OK: values.OK,
  364. CANCEL: values.CANCEL,
  365. CONFIRM: values.CONFIRM
  366. };
  367. return exports;
  368. };
  369. // Remove a previously-registered locale
  370. exports.removeLocale = function (name) {
  371. if (name !== 'en') {
  372. delete locales[name];
  373. }
  374. else {
  375. throw new Error('"en" is used as the default and fallback locale and cannot be removed.');
  376. }
  377. return exports;
  378. };
  379. // Set the default locale
  380. exports.setLocale = function (name) {
  381. return exports.setDefaults('locale', name);
  382. };
  383. // Override default value(s) of Bootbox.
  384. exports.setDefaults = function () {
  385. var values = {};
  386. if (arguments.length === 2) {
  387. // allow passing of single key/value...
  388. values[arguments[0]] = arguments[1];
  389. } else {
  390. // ... and as an object too
  391. values = arguments[0];
  392. }
  393. $.extend(defaults, values);
  394. return exports;
  395. };
  396. // Hides all currently active Bootbox modals
  397. exports.hideAll = function () {
  398. $('.bootbox').modal('hide');
  399. return exports;
  400. };
  401. // Allows the base init() function to be overridden
  402. exports.init = function (_$) {
  403. return init(_$ || $);
  404. };
  405. // CORE HELPER FUNCTIONS
  406. // *************************************************************************************************************
  407. // Core dialog function
  408. exports.dialog = function (options) {
  409. if ($.fn.modal === undefined) {
  410. throw new Error(
  411. '"$.fn.modal" is not defined; please double check you have included ' +
  412. 'the Bootstrap JavaScript library. See https://getbootstrap.com/docs/4.4/getting-started/javascript/ ' +
  413. 'for more details.'
  414. );
  415. }
  416. options = sanitize(options);
  417. if ($.fn.modal.Constructor.VERSION) {
  418. options.fullBootstrapVersion = $.fn.modal.Constructor.VERSION;
  419. var i = options.fullBootstrapVersion.indexOf('.');
  420. options.bootstrap = options.fullBootstrapVersion.substring(0, i);
  421. }
  422. else {
  423. // Assuming version 2.3.2, as that was the last "supported" 2.x version
  424. options.bootstrap = '2';
  425. options.fullBootstrapVersion = '2.3.2';
  426. console.warn('Bootbox will *mostly* work with Bootstrap 2, but we do not officially support it. Please upgrade, if possible.');
  427. }
  428. var dialog = $(templates.dialog);
  429. var innerDialog = dialog.find('.modal-dialog');
  430. var body = dialog.find('.modal-body');
  431. var header = $(templates.header);
  432. var footer = $(templates.footer);
  433. var buttons = options.buttons;
  434. var callbacks = {
  435. onEscape: options.onEscape
  436. };
  437. body.find('.bootbox-body').html(options.message);
  438. // Only attempt to create buttons if at least one has
  439. // been defined in the options object
  440. if (getKeyLength(options.buttons) > 0) {
  441. each(buttons, function (key, b) {
  442. var button = $(templates.button);
  443. button.data('bb-handler', key);
  444. button.addClass(b.className);
  445. switch (key) {
  446. case 'ok':
  447. case 'confirm':
  448. button.addClass('bootbox-accept');
  449. break;
  450. case 'cancel':
  451. button.addClass('bootbox-cancel');
  452. break;
  453. }
  454. button.html(b.label);
  455. footer.append(button);
  456. callbacks[key] = b.callback;
  457. });
  458. body.after(footer);
  459. }
  460. if (options.animate === true) {
  461. dialog.addClass('fade');
  462. }
  463. if (options.className) {
  464. dialog.addClass(options.className);
  465. }
  466. if (options.size) {
  467. // Requires Bootstrap 3.1.0 or higher
  468. if (options.fullBootstrapVersion.substring(0, 3) < '3.1') {
  469. console.warn('"size" requires Bootstrap 3.1.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
  470. }
  471. switch (options.size) {
  472. case 'small':
  473. case 'sm':
  474. innerDialog.addClass('modal-sm');
  475. break;
  476. case 'large':
  477. case 'lg':
  478. innerDialog.addClass('modal-lg');
  479. break;
  480. case 'extra-large':
  481. case 'xl':
  482. innerDialog.addClass('modal-xl');
  483. // Requires Bootstrap 4.2.0 or higher
  484. if (options.fullBootstrapVersion.substring(0, 3) < '4.2') {
  485. console.warn('Using size "xl"/"extra-large" requires Bootstrap 4.2.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
  486. }
  487. break;
  488. }
  489. }
  490. if (options.scrollable) {
  491. innerDialog.addClass('modal-dialog-scrollable');
  492. // Requires Bootstrap 4.3.0 or higher
  493. if (options.fullBootstrapVersion.substring(0, 3) < '4.3') {
  494. console.warn('Using "scrollable" requires Bootstrap 4.3.0 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
  495. }
  496. }
  497. if (options.title) {
  498. body.before(header);
  499. dialog.find('.modal-title').html(options.title);
  500. }
  501. if (options.closeButton) {
  502. var closeButton = $(templates.closeButton);
  503. if (options.title) {
  504. if (options.bootstrap > 3) {
  505. dialog.find('.modal-header').append(closeButton);
  506. }
  507. else {
  508. dialog.find('.modal-header').prepend(closeButton);
  509. }
  510. } else {
  511. closeButton.prependTo(body);
  512. }
  513. }
  514. if (options.centerVertical) {
  515. innerDialog.addClass('modal-dialog-centered');
  516. // Requires Bootstrap 4.0.0-beta.3 or higher
  517. if (options.fullBootstrapVersion < '4.0.0') {
  518. console.warn('"centerVertical" requires Bootstrap 4.0.0-beta.3 or higher. You appear to be using ' + options.fullBootstrapVersion + '. Please upgrade to use this option.');
  519. }
  520. }
  521. // Bootstrap event listeners; these handle extra
  522. // setup & teardown required after the underlying
  523. // modal has performed certain actions.
  524. if(!options.reusable) {
  525. // make sure we unbind any listeners once the dialog has definitively been dismissed
  526. dialog.one('hide.bs.modal', { dialog: dialog }, unbindModal);
  527. }
  528. if (options.onHide) {
  529. if ($.isFunction(options.onHide)) {
  530. dialog.on('hide.bs.modal', options.onHide);
  531. }
  532. else {
  533. throw new Error('Argument supplied to "onHide" must be a function');
  534. }
  535. }
  536. if(!options.reusable) {
  537. dialog.one('hidden.bs.modal', { dialog: dialog }, destroyModal);
  538. }
  539. if (options.onHidden) {
  540. if ($.isFunction(options.onHidden)) {
  541. dialog.on('hidden.bs.modal', options.onHidden);
  542. }
  543. else {
  544. throw new Error('Argument supplied to "onHidden" must be a function');
  545. }
  546. }
  547. if (options.onShow) {
  548. if ($.isFunction(options.onShow)) {
  549. dialog.on('show.bs.modal', options.onShow);
  550. }
  551. else {
  552. throw new Error('Argument supplied to "onShow" must be a function');
  553. }
  554. }
  555. dialog.one('shown.bs.modal', { dialog: dialog }, focusPrimaryButton);
  556. if (options.onShown) {
  557. if ($.isFunction(options.onShown)) {
  558. dialog.on('shown.bs.modal', options.onShown);
  559. }
  560. else {
  561. throw new Error('Argument supplied to "onShown" must be a function');
  562. }
  563. }
  564. // Bootbox event listeners; used to decouple some
  565. // behaviours from their respective triggers
  566. if (options.backdrop === true) {
  567. // A boolean true/false according to the Bootstrap docs
  568. // should show a dialog the user can dismiss by clicking on
  569. // the background.
  570. // We always only ever pass static/false to the actual
  571. // $.modal function because with "true" we can't trap
  572. // this event (the .modal-backdrop swallows it)
  573. // However, we still want to sort-of respect true
  574. // and invoke the escape mechanism instead
  575. dialog.on('click.dismiss.bs.modal', function (e) {
  576. // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
  577. // moved *inside* the outer dialog rather than *alongside* it
  578. if (dialog.children('.modal-backdrop').length) {
  579. e.currentTarget = dialog.children('.modal-backdrop').get(0);
  580. }
  581. if (e.target !== e.currentTarget) {
  582. return;
  583. }
  584. dialog.trigger('escape.close.bb');
  585. });
  586. }
  587. dialog.on('escape.close.bb', function (e) {
  588. // the if statement looks redundant but it isn't; without it
  589. // if we *didn't* have an onEscape handler then processCallback
  590. // would automatically dismiss the dialog
  591. if (callbacks.onEscape) {
  592. processCallback(e, dialog, callbacks.onEscape);
  593. }
  594. });
  595. dialog.on('click', '.modal-footer button:not(.disabled)', function (e) {
  596. var callbackKey = $(this).data('bb-handler');
  597. if (callbackKey !== undefined) {
  598. // Only process callbacks for buttons we recognize:
  599. processCallback(e, dialog, callbacks[callbackKey]);
  600. }
  601. });
  602. dialog.on('click', '.bootbox-close-button', function (e) {
  603. // onEscape might be falsy but that's fine; the fact is
  604. // if the user has managed to click the close button we
  605. // have to close the dialog, callback or not
  606. processCallback(e, dialog, callbacks.onEscape);
  607. });
  608. dialog.on('keyup', function (e) {
  609. if (e.which === 27) {
  610. dialog.trigger('escape.close.bb');
  611. }
  612. });
  613. // the remainder of this method simply deals with adding our
  614. // dialog element to the DOM, augmenting it with Bootstrap's modal
  615. // functionality and then giving the resulting object back
  616. // to our caller
  617. $(options.container).append(dialog);
  618. dialog.modal({
  619. backdrop: options.backdrop,
  620. keyboard: false,
  621. show: false
  622. });
  623. if (options.show) {
  624. dialog.modal('show');
  625. }
  626. return dialog;
  627. };
  628. // Helper function to simulate the native alert() behavior. **NOTE**: This is non-blocking, so any
  629. // code that must happen after the alert is dismissed should be placed within the callback function
  630. // for this alert.
  631. exports.alert = function () {
  632. var options;
  633. options = mergeDialogOptions('alert', ['ok'], ['message', 'callback'], arguments);
  634. // @TODO: can this move inside exports.dialog when we're iterating over each
  635. // button and checking its button.callback value instead?
  636. if (options.callback && !$.isFunction(options.callback)) {
  637. throw new Error('alert requires the "callback" property to be a function when provided');
  638. }
  639. // override the ok and escape callback to make sure they just invoke
  640. // the single user-supplied one (if provided)
  641. options.buttons.ok.callback = options.onEscape = function () {
  642. if ($.isFunction(options.callback)) {
  643. return options.callback.call(this);
  644. }
  645. return true;
  646. };
  647. return exports.dialog(options);
  648. };
  649. // Helper function to simulate the native confirm() behavior. **NOTE**: This is non-blocking, so any
  650. // code that must happen after the confirm is dismissed should be placed within the callback function
  651. // for this confirm.
  652. exports.confirm = function () {
  653. var options;
  654. options = mergeDialogOptions('confirm', ['cancel', 'confirm'], ['message', 'callback'], arguments);
  655. // confirm specific validation; they don't make sense without a callback so make
  656. // sure it's present
  657. if (!$.isFunction(options.callback)) {
  658. throw new Error('confirm requires a callback');
  659. }
  660. // overrides; undo anything the user tried to set they shouldn't have
  661. options.buttons.cancel.callback = options.onEscape = function () {
  662. return options.callback.call(this, false);
  663. };
  664. options.buttons.confirm.callback = function () {
  665. return options.callback.call(this, true);
  666. };
  667. return exports.dialog(options);
  668. };
  669. // Helper function to simulate the native prompt() behavior. **NOTE**: This is non-blocking, so any
  670. // code that must happen after the prompt is dismissed should be placed within the callback function
  671. // for this prompt.
  672. exports.prompt = function () {
  673. var options;
  674. var promptDialog;
  675. var form;
  676. var input;
  677. var shouldShow;
  678. var inputOptions;
  679. // we have to create our form first otherwise
  680. // its value is undefined when gearing up our options
  681. // @TODO this could be solved by allowing message to
  682. // be a function instead...
  683. form = $(templates.form);
  684. // prompt defaults are more complex than others in that
  685. // users can override more defaults
  686. options = mergeDialogOptions('prompt', ['cancel', 'confirm'], ['title', 'callback'], arguments);
  687. if (!options.value) {
  688. options.value = defaults.value;
  689. }
  690. if (!options.inputType) {
  691. options.inputType = defaults.inputType;
  692. }
  693. // capture the user's show value; we always set this to false before
  694. // spawning the dialog to give us a chance to attach some handlers to
  695. // it, but we need to make sure we respect a preference not to show it
  696. shouldShow = (options.show === undefined) ? defaults.show : options.show;
  697. // This is required prior to calling the dialog builder below - we need to
  698. // add an event handler just before the prompt is shown
  699. options.show = false;
  700. // Handles the 'cancel' action
  701. options.buttons.cancel.callback = options.onEscape = function () {
  702. return options.callback.call(this, null);
  703. };
  704. // Prompt submitted - extract the prompt value. This requires a bit of work,
  705. // given the different input types available.
  706. options.buttons.confirm.callback = function () {
  707. var value;
  708. if (options.inputType === 'checkbox') {
  709. value = input.find('input:checked').map(function () {
  710. return $(this).val();
  711. }).get();
  712. } else if (options.inputType === 'radio') {
  713. value = input.find('input:checked').val();
  714. }
  715. else {
  716. if (input[0].checkValidity && !input[0].checkValidity()) {
  717. // prevents button callback from being called
  718. return false;
  719. } else {
  720. if (options.inputType === 'select' && options.multiple === true) {
  721. value = input.find('option:selected').map(function () {
  722. return $(this).val();
  723. }).get();
  724. }
  725. else {
  726. value = input.val();
  727. }
  728. }
  729. }
  730. return options.callback.call(this, value);
  731. };
  732. // prompt-specific validation
  733. if (!options.title) {
  734. throw new Error('prompt requires a title');
  735. }
  736. if (!$.isFunction(options.callback)) {
  737. throw new Error('prompt requires a callback');
  738. }
  739. if (!templates.inputs[options.inputType]) {
  740. throw new Error('Invalid prompt type');
  741. }
  742. // create the input based on the supplied type
  743. input = $(templates.inputs[options.inputType]);
  744. switch (options.inputType) {
  745. case 'text':
  746. case 'textarea':
  747. case 'email':
  748. case 'password':
  749. input.val(options.value);
  750. if (options.placeholder) {
  751. input.attr('placeholder', options.placeholder);
  752. }
  753. if (options.pattern) {
  754. input.attr('pattern', options.pattern);
  755. }
  756. if (options.maxlength) {
  757. input.attr('maxlength', options.maxlength);
  758. }
  759. if (options.required) {
  760. input.prop({ 'required': true });
  761. }
  762. if (options.rows && !isNaN(parseInt(options.rows))) {
  763. if (options.inputType === 'textarea') {
  764. input.attr({ 'rows': options.rows });
  765. }
  766. }
  767. break;
  768. case 'date':
  769. case 'time':
  770. case 'number':
  771. case 'range':
  772. input.val(options.value);
  773. if (options.placeholder) {
  774. input.attr('placeholder', options.placeholder);
  775. }
  776. if (options.pattern) {
  777. input.attr('pattern', options.pattern);
  778. }
  779. if (options.required) {
  780. input.prop({ 'required': true });
  781. }
  782. // These input types have extra attributes which affect their input validation.
  783. // Warning: For most browsers, date inputs are buggy in their implementation of 'step', so
  784. // this attribute will have no effect. Therefore, we don't set the attribute for date inputs.
  785. // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#Setting_maximum_and_minimum_dates
  786. if (options.inputType !== 'date') {
  787. if (options.step) {
  788. if (options.step === 'any' || (!isNaN(options.step) && parseFloat(options.step) > 0)) {
  789. input.attr('step', options.step);
  790. }
  791. else {
  792. throw new Error('"step" must be a valid positive number or the value "any". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-step for more information.');
  793. }
  794. }
  795. }
  796. if (minAndMaxAreValid(options.inputType, options.min, options.max)) {
  797. if (options.min !== undefined) {
  798. input.attr('min', options.min);
  799. }
  800. if (options.max !== undefined) {
  801. input.attr('max', options.max);
  802. }
  803. }
  804. break;
  805. case 'select':
  806. var groups = {};
  807. inputOptions = options.inputOptions || [];
  808. if (!$.isArray(inputOptions)) {
  809. throw new Error('Please pass an array of input options');
  810. }
  811. if (!inputOptions.length) {
  812. throw new Error('prompt with "inputType" set to "select" requires at least one option');
  813. }
  814. // placeholder is not actually a valid attribute for select,
  815. // but we'll allow it, assuming it might be used for a plugin
  816. if (options.placeholder) {
  817. input.attr('placeholder', options.placeholder);
  818. }
  819. if (options.required) {
  820. input.prop({ 'required': true });
  821. }
  822. if (options.multiple) {
  823. input.prop({ 'multiple': true });
  824. }
  825. each(inputOptions, function (_, option) {
  826. // assume the element to attach to is the input...
  827. var elem = input;
  828. if (option.value === undefined || option.text === undefined) {
  829. throw new Error('each option needs a "value" property and a "text" property');
  830. }
  831. // ... but override that element if this option sits in a group
  832. if (option.group) {
  833. // initialise group if necessary
  834. if (!groups[option.group]) {
  835. groups[option.group] = $('<optgroup />').attr('label', option.group);
  836. }
  837. elem = groups[option.group];
  838. }
  839. var o = $(templates.option);
  840. o.attr('value', option.value).text(option.text);
  841. elem.append(o);
  842. });
  843. each(groups, function (_, group) {
  844. input.append(group);
  845. });
  846. // safe to set a select's value as per a normal input
  847. input.val(options.value);
  848. break;
  849. case 'checkbox':
  850. var checkboxValues = $.isArray(options.value) ? options.value : [options.value];
  851. inputOptions = options.inputOptions || [];
  852. if (!inputOptions.length) {
  853. throw new Error('prompt with "inputType" set to "checkbox" requires at least one option');
  854. }
  855. // checkboxes have to nest within a containing element, so
  856. // they break the rules a bit and we end up re-assigning
  857. // our 'input' element to this container instead
  858. input = $('<div class="bootbox-checkbox-list"></div>');
  859. each(inputOptions, function (_, option) {
  860. if (option.value === undefined || option.text === undefined) {
  861. throw new Error('each option needs a "value" property and a "text" property');
  862. }
  863. var checkbox = $(templates.inputs[options.inputType]);
  864. checkbox.find('input').attr('value', option.value);
  865. checkbox.find('label').append('\n' + option.text);
  866. // we've ensured values is an array so we can always iterate over it
  867. each(checkboxValues, function (_, value) {
  868. if (value === option.value) {
  869. checkbox.find('input').prop('checked', true);
  870. }
  871. });
  872. input.append(checkbox);
  873. });
  874. break;
  875. case 'radio':
  876. // Make sure that value is not an array (only a single radio can ever be checked)
  877. if (options.value !== undefined && $.isArray(options.value)) {
  878. throw new Error('prompt with "inputType" set to "radio" requires a single, non-array value for "value"');
  879. }
  880. inputOptions = options.inputOptions || [];
  881. if (!inputOptions.length) {
  882. throw new Error('prompt with "inputType" set to "radio" requires at least one option');
  883. }
  884. // Radiobuttons have to nest within a containing element, so
  885. // they break the rules a bit and we end up re-assigning
  886. // our 'input' element to this container instead
  887. input = $('<div class="bootbox-radiobutton-list"></div>');
  888. // Radiobuttons should always have an initial checked input checked in a "group".
  889. // If value is undefined or doesn't match an input option, select the first radiobutton
  890. var checkFirstRadio = true;
  891. each(inputOptions, function (_, option) {
  892. if (option.value === undefined || option.text === undefined) {
  893. throw new Error('each option needs a "value" property and a "text" property');
  894. }
  895. var radio = $(templates.inputs[options.inputType]);
  896. radio.find('input').attr('value', option.value);
  897. radio.find('label').append('\n' + option.text);
  898. if (options.value !== undefined) {
  899. if (option.value === options.value) {
  900. radio.find('input').prop('checked', true);
  901. checkFirstRadio = false;
  902. }
  903. }
  904. input.append(radio);
  905. });
  906. if (checkFirstRadio) {
  907. input.find('input[type="radio"]').first().prop('checked', true);
  908. }
  909. break;
  910. }
  911. // now place it in our form
  912. form.append(input);
  913. form.on('submit', function (e) {
  914. e.preventDefault();
  915. // Fix for SammyJS (or similar JS routing library) hijacking the form post.
  916. e.stopPropagation();
  917. // @TODO can we actually click *the* button object instead?
  918. // e.g. buttons.confirm.click() or similar
  919. promptDialog.find('.bootbox-accept').trigger('click');
  920. });
  921. if ($.trim(options.message) !== '') {
  922. // Add the form to whatever content the user may have added.
  923. var message = $(templates.promptMessage).html(options.message);
  924. form.prepend(message);
  925. options.message = form;
  926. }
  927. else {
  928. options.message = form;
  929. }
  930. // Generate the dialog
  931. promptDialog = exports.dialog(options);
  932. // clear the existing handler focusing the submit button...
  933. promptDialog.off('shown.bs.modal', focusPrimaryButton);
  934. // ...and replace it with one focusing our input, if possible
  935. promptDialog.on('shown.bs.modal', function () {
  936. // need the closure here since input isn't
  937. // an object otherwise
  938. input.focus();
  939. });
  940. if (shouldShow === true) {
  941. promptDialog.modal('show');
  942. }
  943. return promptDialog;
  944. };
  945. // INTERNAL FUNCTIONS
  946. // *************************************************************************************************************
  947. // Map a flexible set of arguments into a single returned object
  948. // If args.length is already one just return it, otherwise
  949. // use the properties argument to map the unnamed args to
  950. // object properties.
  951. // So in the latter case:
  952. // mapArguments(["foo", $.noop], ["message", "callback"])
  953. // -> { message: "foo", callback: $.noop }
  954. function mapArguments(args, properties) {
  955. var argn = args.length;
  956. var options = {};
  957. if (argn < 1 || argn > 2) {
  958. throw new Error('Invalid argument length');
  959. }
  960. if (argn === 2 || typeof args[0] === 'string') {
  961. options[properties[0]] = args[0];
  962. options[properties[1]] = args[1];
  963. } else {
  964. options = args[0];
  965. }
  966. return options;
  967. }
  968. // Merge a set of default dialog options with user supplied arguments
  969. function mergeArguments(defaults, args, properties) {
  970. return $.extend(
  971. // deep merge
  972. true,
  973. // ensure the target is an empty, unreferenced object
  974. {},
  975. // the base options object for this type of dialog (often just buttons)
  976. defaults,
  977. // args could be an object or array; if it's an array properties will
  978. // map it to a proper options object
  979. mapArguments(
  980. args,
  981. properties
  982. )
  983. );
  984. }
  985. // This entry-level method makes heavy use of composition to take a simple
  986. // range of inputs and return valid options suitable for passing to bootbox.dialog
  987. function mergeDialogOptions(className, labels, properties, args) {
  988. var locale;
  989. if (args && args[0]) {
  990. locale = args[0].locale || defaults.locale;
  991. var swapButtons = args[0].swapButtonOrder || defaults.swapButtonOrder;
  992. if (swapButtons) {
  993. labels = labels.reverse();
  994. }
  995. }
  996. // build up a base set of dialog properties
  997. var baseOptions = {
  998. className: 'bootbox-' + className,
  999. buttons: createLabels(labels, locale)
  1000. };
  1001. // Ensure the buttons properties generated, *after* merging
  1002. // with user args are still valid against the supplied labels
  1003. return validateButtons(
  1004. // merge the generated base properties with user supplied arguments
  1005. mergeArguments(
  1006. baseOptions,
  1007. args,
  1008. // if args.length > 1, properties specify how each arg maps to an object key
  1009. properties
  1010. ),
  1011. labels
  1012. );
  1013. }
  1014. // Checks each button object to see if key is valid.
  1015. // This function will only be called by the alert, confirm, and prompt helpers.
  1016. function validateButtons(options, buttons) {
  1017. var allowedButtons = {};
  1018. each(buttons, function (key, value) {
  1019. allowedButtons[value] = true;
  1020. });
  1021. each(options.buttons, function (key) {
  1022. if (allowedButtons[key] === undefined) {
  1023. throw new Error('button key "' + key + '" is not allowed (options are ' + buttons.join(' ') + ')');
  1024. }
  1025. });
  1026. return options;
  1027. }
  1028. // From a given list of arguments, return a suitable object of button labels.
  1029. // All this does is normalise the given labels and translate them where possible.
  1030. // e.g. "ok", "confirm" -> { ok: "OK", cancel: "Annuleren" }
  1031. function createLabels(labels, locale) {
  1032. var buttons = {};
  1033. for (var i = 0, j = labels.length; i < j; i++) {
  1034. var argument = labels[i];
  1035. var key = argument.toLowerCase();
  1036. var value = argument.toUpperCase();
  1037. buttons[key] = {
  1038. label: getText(value, locale)
  1039. };
  1040. }
  1041. return buttons;
  1042. }
  1043. // Get localized text from a locale. Defaults to 'en' locale if no locale
  1044. // provided or a non-registered locale is requested
  1045. function getText(key, locale) {
  1046. var labels = locales[locale];
  1047. return labels ? labels[key] : locales.en[key];
  1048. }
  1049. // Filter and tidy up any user supplied parameters to this dialog.
  1050. // Also looks for any shorthands used and ensures that the options
  1051. // which are returned are all normalized properly
  1052. function sanitize(options) {
  1053. var buttons;
  1054. var total;
  1055. if (typeof options !== 'object') {
  1056. throw new Error('Please supply an object of options');
  1057. }
  1058. if (!options.message) {
  1059. throw new Error('"message" option must not be null or an empty string.');
  1060. }
  1061. // make sure any supplied options take precedence over defaults
  1062. options = $.extend({}, defaults, options);
  1063. //make sure backdrop is either true, false, or 'static'
  1064. if (!options.backdrop) {
  1065. options.backdrop = (options.backdrop === false || options.backdrop === 0) ? false : 'static';
  1066. } else {
  1067. options.backdrop = typeof options.backdrop === 'string' && options.backdrop.toLowerCase() === 'static' ? 'static' : true;
  1068. }
  1069. // no buttons is still a valid dialog but it's cleaner to always have
  1070. // a buttons object to iterate over, even if it's empty
  1071. if (!options.buttons) {
  1072. options.buttons = {};
  1073. }
  1074. buttons = options.buttons;
  1075. total = getKeyLength(buttons);
  1076. each(buttons, function (key, button, index) {
  1077. if ($.isFunction(button)) {
  1078. // short form, assume value is our callback. Since button
  1079. // isn't an object it isn't a reference either so re-assign it
  1080. button = buttons[key] = {
  1081. callback: button
  1082. };
  1083. }
  1084. // before any further checks make sure by now button is the correct type
  1085. if ($.type(button) !== 'object') {
  1086. throw new Error('button with key "' + key + '" must be an object');
  1087. }
  1088. if (!button.label) {
  1089. // the lack of an explicit label means we'll assume the key is good enough
  1090. button.label = key;
  1091. }
  1092. if (!button.className) {
  1093. var isPrimary = false;
  1094. if (options.swapButtonOrder) {
  1095. isPrimary = index === 0;
  1096. }
  1097. else {
  1098. isPrimary = index === total - 1;
  1099. }
  1100. if (total <= 2 && isPrimary) {
  1101. // always add a primary to the main option in a one or two-button dialog
  1102. button.className = 'btn-primary';
  1103. } else {
  1104. // adding both classes allows us to target both BS3 and BS4 without needing to check the version
  1105. button.className = 'btn-secondary btn-default';
  1106. }
  1107. }
  1108. });
  1109. return options;
  1110. }
  1111. // Returns a count of the properties defined on the object
  1112. function getKeyLength(obj) {
  1113. return Object.keys(obj).length;
  1114. }
  1115. // Tiny wrapper function around jQuery.each; just adds index as the third parameter
  1116. function each(collection, iterator) {
  1117. var index = 0;
  1118. $.each(collection, function (key, value) {
  1119. iterator(key, value, index++);
  1120. });
  1121. }
  1122. function focusPrimaryButton(e) {
  1123. e.data.dialog.find('.bootbox-accept').first().trigger('focus');
  1124. }
  1125. function destroyModal(e) {
  1126. // ensure we don't accidentally intercept hidden events triggered
  1127. // by children of the current dialog. We shouldn't need to handle this anymore,
  1128. // now that Bootstrap namespaces its events, but still worth doing.
  1129. if (e.target === e.data.dialog[0]) {
  1130. e.data.dialog.remove();
  1131. }
  1132. }
  1133. function unbindModal(e) {
  1134. if (e.target === e.data.dialog[0]) {
  1135. e.data.dialog.off('escape.close.bb');
  1136. e.data.dialog.off('click');
  1137. }
  1138. }
  1139. // Handle the invoked dialog callback
  1140. function processCallback(e, dialog, callback) {
  1141. e.stopPropagation();
  1142. e.preventDefault();
  1143. // by default we assume a callback will get rid of the dialog,
  1144. // although it is given the opportunity to override this
  1145. // so, if the callback can be invoked and it *explicitly returns false*
  1146. // then we'll set a flag to keep the dialog active...
  1147. var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
  1148. // ... otherwise we'll bin it
  1149. if (!preserveDialog) {
  1150. dialog.modal('hide');
  1151. }
  1152. }
  1153. // Validate `min` and `max` values based on the current `inputType` value
  1154. function minAndMaxAreValid(type, min, max) {
  1155. var result = false;
  1156. var minValid = true;
  1157. var maxValid = true;
  1158. if (type === 'date') {
  1159. if (min !== undefined && !(minValid = dateIsValid(min))) {
  1160. console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your min value may not be enforced by this browser.');
  1161. }
  1162. else if (max !== undefined && !(maxValid = dateIsValid(max))) {
  1163. console.warn('Browsers which natively support the "date" input type expect date values to be of the form "YYYY-MM-DD" (see ISO-8601 https://www.iso.org/iso-8601-date-and-time-format.html). Bootbox does not enforce this rule, but your max value may not be enforced by this browser.');
  1164. }
  1165. }
  1166. else if (type === 'time') {
  1167. if (min !== undefined && !(minValid = timeIsValid(min))) {
  1168. throw new Error('"min" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');
  1169. }
  1170. else if (max !== undefined && !(maxValid = timeIsValid(max))) {
  1171. throw new Error('"max" is not a valid time. See https://www.w3.org/TR/2012/WD-html-markup-20120315/datatypes.html#form.data.time for more information.');
  1172. }
  1173. }
  1174. else {
  1175. if (min !== undefined && isNaN(min)) {
  1176. minValid = false;
  1177. throw new Error('"min" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-min for more information.');
  1178. }
  1179. if (max !== undefined && isNaN(max)) {
  1180. maxValid = false;
  1181. throw new Error('"max" must be a valid number. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');
  1182. }
  1183. }
  1184. if (minValid && maxValid) {
  1185. if (max <= min) {
  1186. throw new Error('"max" must be greater than "min". See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-max for more information.');
  1187. }
  1188. else {
  1189. result = true;
  1190. }
  1191. }
  1192. return result;
  1193. }
  1194. function timeIsValid(value) {
  1195. return /([01][0-9]|2[0-3]):[0-5][0-9]?:[0-5][0-9]/.test(value);
  1196. }
  1197. function dateIsValid(value) {
  1198. return /(\d{4})-(\d{2})-(\d{2})/.test(value);
  1199. }
  1200. // The Bootbox object
  1201. return exports;
  1202. }));