bootbox.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. /**
  2. * bootbox.js v3.3.0
  3. *
  4. * http://bootboxjs.com/license.txt
  5. */
  6. var bootbox = window.bootbox || (function(document, $) {
  7. /*jshint scripturl:true sub:true */
  8. var _locale = 'en',
  9. _defaultLocale = 'en',
  10. _animate = true,
  11. _backdrop = 'static',
  12. _defaultHref = 'javascript:;',
  13. _classes = '',
  14. _btnClasses = {},
  15. _icons = {},
  16. /* last var should always be the public object we'll return */
  17. that = {};
  18. /**
  19. * public API
  20. */
  21. that.setLocale = function(locale) {
  22. for (var i in _locales) {
  23. if (i == locale) {
  24. _locale = locale;
  25. return;
  26. }
  27. }
  28. throw new Error('Invalid locale: '+locale);
  29. };
  30. that.addLocale = function(locale, translations) {
  31. if (typeof _locales[locale] === 'undefined') {
  32. _locales[locale] = {};
  33. }
  34. for (var str in translations) {
  35. _locales[locale][str] = translations[str];
  36. }
  37. };
  38. that.setIcons = function(icons) {
  39. _icons = icons;
  40. if (typeof _icons !== 'object' || _icons === null) {
  41. _icons = {};
  42. }
  43. };
  44. that.setBtnClasses = function(btnClasses) {
  45. _btnClasses = btnClasses;
  46. if (typeof _btnClasses !== 'object' || _btnClasses === null) {
  47. _btnClasses = {};
  48. }
  49. };
  50. that.alert = function(/*str, label, cb*/) {
  51. var str = "",
  52. label = _translate('OK'),
  53. cb = null;
  54. switch (arguments.length) {
  55. case 1:
  56. // no callback, default button label
  57. str = arguments[0];
  58. break;
  59. case 2:
  60. // callback *or* custom button label dependent on type
  61. str = arguments[0];
  62. if (typeof arguments[1] == 'function') {
  63. cb = arguments[1];
  64. } else {
  65. label = arguments[1];
  66. }
  67. break;
  68. case 3:
  69. // callback and custom button label
  70. str = arguments[0];
  71. label = arguments[1];
  72. cb = arguments[2];
  73. break;
  74. default:
  75. throw new Error("Incorrect number of arguments: expected 1-3");
  76. }
  77. return that.dialog(str, {
  78. // only button (ok)
  79. "label" : label,
  80. "icon" : _icons.OK,
  81. "class" : _btnClasses.OK,
  82. "callback": cb
  83. }, {
  84. // ensure that the escape key works; either invoking the user's
  85. // callback or true to just close the dialog
  86. "onEscape": cb || true
  87. });
  88. };
  89. that.confirm = function(/*str, labelCancel, labelOk, cb*/) {
  90. var str = "",
  91. labelCancel = _translate('CANCEL'),
  92. labelOk = _translate('CONFIRM'),
  93. cb = null;
  94. switch (arguments.length) {
  95. case 1:
  96. str = arguments[0];
  97. break;
  98. case 2:
  99. str = arguments[0];
  100. if (typeof arguments[1] == 'function') {
  101. cb = arguments[1];
  102. } else {
  103. labelCancel = arguments[1];
  104. }
  105. break;
  106. case 3:
  107. str = arguments[0];
  108. labelCancel = arguments[1];
  109. if (typeof arguments[2] == 'function') {
  110. cb = arguments[2];
  111. } else {
  112. labelOk = arguments[2];
  113. }
  114. break;
  115. case 4:
  116. str = arguments[0];
  117. labelCancel = arguments[1];
  118. labelOk = arguments[2];
  119. cb = arguments[3];
  120. break;
  121. default:
  122. throw new Error("Incorrect number of arguments: expected 1-4");
  123. }
  124. var cancelCallback = function() {
  125. if (typeof cb === 'function') {
  126. return cb(false);
  127. }
  128. };
  129. var confirmCallback = function() {
  130. if (typeof cb === 'function') {
  131. return cb(true);
  132. }
  133. };
  134. return that.dialog(str, [{
  135. // first button (cancel)
  136. "label" : labelCancel,
  137. "icon" : _icons.CANCEL,
  138. "class" : _btnClasses.CANCEL,
  139. "callback": cancelCallback
  140. }, {
  141. // second button (confirm)
  142. "label" : labelOk,
  143. "icon" : _icons.CONFIRM,
  144. "class" : _btnClasses.CONFIRM,
  145. "callback": confirmCallback
  146. }], {
  147. // escape key bindings
  148. "onEscape": cancelCallback
  149. });
  150. };
  151. that.prompt = function(/*str, labelCancel, labelOk, cb, defaultVal*/) {
  152. var str = "",
  153. labelCancel = _translate('CANCEL'),
  154. labelOk = _translate('CONFIRM'),
  155. cb = null,
  156. defaultVal = "";
  157. switch (arguments.length) {
  158. case 1:
  159. str = arguments[0];
  160. break;
  161. case 2:
  162. str = arguments[0];
  163. if (typeof arguments[1] == 'function') {
  164. cb = arguments[1];
  165. } else {
  166. labelCancel = arguments[1];
  167. }
  168. break;
  169. case 3:
  170. str = arguments[0];
  171. labelCancel = arguments[1];
  172. if (typeof arguments[2] == 'function') {
  173. cb = arguments[2];
  174. } else {
  175. labelOk = arguments[2];
  176. }
  177. break;
  178. case 4:
  179. str = arguments[0];
  180. labelCancel = arguments[1];
  181. labelOk = arguments[2];
  182. cb = arguments[3];
  183. break;
  184. case 5:
  185. str = arguments[0];
  186. labelCancel = arguments[1];
  187. labelOk = arguments[2];
  188. cb = arguments[3];
  189. defaultVal = arguments[4];
  190. break;
  191. default:
  192. throw new Error("Incorrect number of arguments: expected 1-5");
  193. }
  194. var header = str;
  195. // let's keep a reference to the form object for later
  196. var form = $("<form></form>");
  197. form.append("<input class='input-block-level' autocomplete=off type=text value='" + defaultVal + "' />");
  198. var cancelCallback = function() {
  199. if (typeof cb === 'function') {
  200. // yep, native prompts dismiss with null, whereas native
  201. // confirms dismiss with false...
  202. return cb(null);
  203. }
  204. };
  205. var confirmCallback = function() {
  206. if (typeof cb === 'function') {
  207. return cb(form.find("input[type=text]").val());
  208. }
  209. };
  210. var div = that.dialog(form, [{
  211. // first button (cancel)
  212. "label" : labelCancel,
  213. "icon" : _icons.CANCEL,
  214. "class" : _btnClasses.CANCEL,
  215. "callback": cancelCallback
  216. }, {
  217. // second button (confirm)
  218. "label" : labelOk,
  219. "icon" : _icons.CONFIRM,
  220. "class" : _btnClasses.CONFIRM,
  221. "callback": confirmCallback
  222. }], {
  223. // prompts need a few extra options
  224. "header" : header,
  225. // explicitly tell dialog NOT to show the dialog...
  226. "show" : false,
  227. "onEscape": cancelCallback
  228. });
  229. // ... the reason the prompt needs to be hidden is because we need
  230. // to bind our own "shown" handler, after creating the modal but
  231. // before any show(n) events are triggered
  232. // @see https://github.com/makeusabrew/bootbox/issues/69
  233. div.on("shown", function() {
  234. form.find("input[type=text]").focus();
  235. // ensure that submitting the form (e.g. with the enter key)
  236. // replicates the behaviour of a normal prompt()
  237. form.on("submit", function(e) {
  238. e.preventDefault();
  239. div.find(".btn-primary").click();
  240. });
  241. });
  242. div.modal("show");
  243. return div;
  244. };
  245. that.dialog = function(str, handlers, options) {
  246. var buttons = "",
  247. callbacks = [];
  248. if (!options) {
  249. options = {};
  250. }
  251. // check for single object and convert to array if necessary
  252. if (typeof handlers === 'undefined') {
  253. handlers = [];
  254. } else if (typeof handlers.length == 'undefined') {
  255. handlers = [handlers];
  256. }
  257. var i = handlers.length;
  258. while (i--) {
  259. var label = null,
  260. href = null,
  261. _class = null,
  262. icon = '',
  263. callback = null;
  264. if (typeof handlers[i]['label'] == 'undefined' &&
  265. typeof handlers[i]['class'] == 'undefined' &&
  266. typeof handlers[i]['callback'] == 'undefined') {
  267. // if we've got nothing we expect, check for condensed format
  268. var propCount = 0, // condensed will only match if this == 1
  269. property = null; // save the last property we found
  270. // be nicer to count the properties without this, but don't think it's possible...
  271. for (var j in handlers[i]) {
  272. property = j;
  273. if (++propCount > 1) {
  274. // forget it, too many properties
  275. break;
  276. }
  277. }
  278. if (propCount == 1 && typeof handlers[i][j] == 'function') {
  279. // matches condensed format of label -> function
  280. handlers[i]['label'] = property;
  281. handlers[i]['callback'] = handlers[i][j];
  282. }
  283. }
  284. if (typeof handlers[i]['callback']== 'function') {
  285. callback = handlers[i]['callback'];
  286. }
  287. if (handlers[i]['class']) {
  288. _class = handlers[i]['class'];
  289. } else if (i == handlers.length -1 && handlers.length <= 2) {
  290. // always add a primary to the main option in a two-button dialog
  291. _class = 'btn-primary';
  292. }
  293. if (handlers[i]['link'] !== true) {
  294. _class = 'btn ' + _class;
  295. }
  296. if (handlers[i]['label']) {
  297. label = handlers[i]['label'];
  298. } else {
  299. label = "Option "+(i+1);
  300. }
  301. if (handlers[i]['icon']) {
  302. icon = "<i class='"+handlers[i]['icon']+"'></i> ";
  303. }
  304. if (handlers[i]['href']) {
  305. href = handlers[i]['href'];
  306. }
  307. else {
  308. href = _defaultHref;
  309. }
  310. buttons = "<a data-handler='"+i+"' class='"+_class+"' href='" + href + "'>"+icon+""+label+"</a>" + buttons;
  311. callbacks[i] = callback;
  312. }
  313. // @see https://github.com/makeusabrew/bootbox/issues/46#issuecomment-8235302
  314. // and https://github.com/twitter/bootstrap/issues/4474
  315. // for an explanation of the inline overflow: hidden
  316. // @see https://github.com/twitter/bootstrap/issues/4854
  317. // for an explanation of tabIndex=-1
  318. var parts = ["<div class='bootbox modal' tabindex='-1' style='overflow:hidden;'>"];
  319. if (options['header']) {
  320. var closeButton = '';
  321. if (typeof options['headerCloseButton'] == 'undefined' || options['headerCloseButton']) {
  322. closeButton = "<a href='"+_defaultHref+"' class='close'>&times;</a>";
  323. }
  324. parts.push("<div class='modal-header'>"+closeButton+"<h3>"+options['header']+"</h3></div>");
  325. }
  326. // push an empty body into which we'll inject the proper content later
  327. parts.push("<div class='modal-body'></div>");
  328. if (buttons) {
  329. parts.push("<div class='modal-footer'>"+buttons+"</div>");
  330. }
  331. parts.push("</div>");
  332. var div = $(parts.join("\n"));
  333. // check whether we should fade in/out
  334. var shouldFade = (typeof options.animate === 'undefined') ? _animate : options.animate;
  335. if (shouldFade) {
  336. div.addClass("fade");
  337. }
  338. var optionalClasses = (typeof options.classes === 'undefined') ? _classes : options.classes;
  339. if (optionalClasses) {
  340. div.addClass(optionalClasses);
  341. }
  342. // now we've built up the div properly we can inject the content whether it was a string or a jQuery object
  343. div.find(".modal-body").html(str);
  344. function onCancel(source) {
  345. // for now source is unused, but it will be in future
  346. var hideModal = null;
  347. if (typeof options.onEscape === 'function') {
  348. // @see https://github.com/makeusabrew/bootbox/issues/91
  349. hideModal = options.onEscape();
  350. }
  351. if (hideModal !== false) {
  352. div.modal('hide');
  353. }
  354. }
  355. // hook into the modal's keyup trigger to check for the escape key
  356. div.on('keyup.dismiss.modal', function(e) {
  357. // any truthy value passed to onEscape will dismiss the dialog
  358. // as long as the onEscape function (if defined) doesn't prevent it
  359. if (e.which === 27 && options.onEscape) {
  360. onCancel('escape');
  361. }
  362. });
  363. // handle close buttons too
  364. div.on('click', 'a.close', function(e) {
  365. e.preventDefault();
  366. onCancel('close');
  367. });
  368. // well, *if* we have a primary - give the first dom element focus
  369. div.on('shown', function() {
  370. div.find("a.btn-primary:first").focus();
  371. });
  372. div.on('hidden', function(e) {
  373. // @see https://github.com/makeusabrew/bootbox/issues/115
  374. // allow for the fact hidden events can propagate up from
  375. // child elements like tooltips
  376. if (e.target === this) {
  377. div.remove();
  378. }
  379. });
  380. // wire up button handlers
  381. div.on('click', '.modal-footer a', function(e) {
  382. var handler = $(this).data("handler"),
  383. cb = callbacks[handler],
  384. hideModal = null;
  385. // sort of @see https://github.com/makeusabrew/bootbox/pull/68 - heavily adapted
  386. // if we've got a custom href attribute, all bets are off
  387. if (typeof handler !== 'undefined' &&
  388. typeof handlers[handler]['href'] !== 'undefined') {
  389. return;
  390. }
  391. e.preventDefault();
  392. if (typeof cb === 'function') {
  393. hideModal = cb(e);
  394. }
  395. // the only way hideModal *will* be false is if a callback exists and
  396. // returns it as a value. in those situations, don't hide the dialog
  397. // @see https://github.com/makeusabrew/bootbox/pull/25
  398. if (hideModal !== false) {
  399. div.modal("hide");
  400. }
  401. });
  402. // stick the modal right at the bottom of the main body out of the way
  403. $("body").append(div);
  404. div.modal({
  405. // unless explicitly overridden take whatever our default backdrop value is
  406. backdrop : (typeof options.backdrop === 'undefined') ? _backdrop : options.backdrop,
  407. // ignore bootstrap's keyboard options; we'll handle this ourselves (more fine-grained control)
  408. keyboard : false,
  409. // @ see https://github.com/makeusabrew/bootbox/issues/69
  410. // we *never* want the modal to be shown before we can bind stuff to it
  411. // this method can also take a 'show' option, but we'll only use that
  412. // later if we need to
  413. show : false
  414. });
  415. // @see https://github.com/makeusabrew/bootbox/issues/64
  416. // @see https://github.com/makeusabrew/bootbox/issues/60
  417. // ...caused by...
  418. // @see https://github.com/twitter/bootstrap/issues/4781
  419. div.on("show", function(e) {
  420. $(document).off("focusin.modal");
  421. });
  422. if (typeof options.show === 'undefined' || options.show === true) {
  423. div.modal("show");
  424. }
  425. return div;
  426. };
  427. /**
  428. * #modal is deprecated in v3; it can still be used but no guarantees are
  429. * made - have never been truly convinced of its merit but perhaps just
  430. * needs a tidyup and some TLC
  431. */
  432. that.modal = function(/*str, label, options*/) {
  433. var str;
  434. var label;
  435. var options;
  436. var defaultOptions = {
  437. "onEscape": null,
  438. "keyboard": true,
  439. "backdrop": _backdrop
  440. };
  441. switch (arguments.length) {
  442. case 1:
  443. str = arguments[0];
  444. break;
  445. case 2:
  446. str = arguments[0];
  447. if (typeof arguments[1] == 'object') {
  448. options = arguments[1];
  449. } else {
  450. label = arguments[1];
  451. }
  452. break;
  453. case 3:
  454. str = arguments[0];
  455. label = arguments[1];
  456. options = arguments[2];
  457. break;
  458. default:
  459. throw new Error("Incorrect number of arguments: expected 1-3");
  460. }
  461. defaultOptions['header'] = label;
  462. if (typeof options == 'object') {
  463. options = $.extend(defaultOptions, options);
  464. } else {
  465. options = defaultOptions;
  466. }
  467. return that.dialog(str, [], options);
  468. };
  469. that.hideAll = function() {
  470. $(".bootbox").modal("hide");
  471. };
  472. that.animate = function(animate) {
  473. _animate = animate;
  474. };
  475. that.backdrop = function(backdrop) {
  476. _backdrop = backdrop;
  477. };
  478. that.classes = function(classes) {
  479. _classes = classes;
  480. };
  481. /**
  482. * private API
  483. */
  484. /**
  485. * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
  486. * unlikely to be required. If this gets too large it can be split out into separate JS files.
  487. */
  488. var _locales = {
  489. 'br' : {
  490. OK : 'OK',
  491. CANCEL : 'Cancelar',
  492. CONFIRM : 'Sim'
  493. },
  494. 'da' : {
  495. OK : 'OK',
  496. CANCEL : 'Annuller',
  497. CONFIRM : 'Accepter'
  498. },
  499. 'de' : {
  500. OK : 'OK',
  501. CANCEL : 'Abbrechen',
  502. CONFIRM : 'Akzeptieren'
  503. },
  504. 'en' : {
  505. OK : 'OK',
  506. CANCEL : 'Cancel',
  507. CONFIRM : 'OK'
  508. },
  509. 'es' : {
  510. OK : 'OK',
  511. CANCEL : 'Cancelar',
  512. CONFIRM : 'Aceptar'
  513. },
  514. 'fr' : {
  515. OK : 'OK',
  516. CANCEL : 'Annuler',
  517. CONFIRM : 'D\'accord'
  518. },
  519. 'it' : {
  520. OK : 'OK',
  521. CANCEL : 'Annulla',
  522. CONFIRM : 'Conferma'
  523. },
  524. 'nl' : {
  525. OK : 'OK',
  526. CANCEL : 'Annuleren',
  527. CONFIRM : 'Accepteren'
  528. },
  529. 'pl' : {
  530. OK : 'OK',
  531. CANCEL : 'Anuluj',
  532. CONFIRM : 'Potwierdź'
  533. },
  534. 'ru' : {
  535. OK : 'OK',
  536. CANCEL : 'Отмена',
  537. CONFIRM : 'Применить'
  538. },
  539. 'zh_CN' : {
  540. OK : 'OK',
  541. CANCEL : '取消',
  542. CONFIRM : '确认'
  543. },
  544. 'zh_TW' : {
  545. OK : 'OK',
  546. CANCEL : '取消',
  547. CONFIRM : '確認'
  548. }
  549. };
  550. function _translate(str, locale) {
  551. // we assume if no target locale is probided then we should take it from current setting
  552. if (typeof locale === 'undefined') {
  553. locale = _locale;
  554. }
  555. if (typeof _locales[locale][str] === 'string') {
  556. return _locales[locale][str];
  557. }
  558. // if we couldn't find a lookup then try and fallback to a default translation
  559. if (locale != _defaultLocale) {
  560. return _translate(str, _defaultLocale);
  561. }
  562. // if we can't do anything then bail out with whatever string was passed in - last resort
  563. return str;
  564. }
  565. return that;
  566. }(document, window.jQuery));
  567. // @see https://github.com/makeusabrew/bootbox/issues/71
  568. window.bootbox = bootbox;