/build/alertify.js

https://github.com/alihalabyah/AlertifyJS · JavaScript · 2769 lines · 1651 code · 209 blank · 909 comment · 247 complexity · e326dcdfa9255ae037b3488a86d91c19 MD5 · raw file

Large files are truncated click here to view the full file

  1. /**
  2. * AlertifyJS
  3. * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
  4. *
  5. * @author Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
  6. * @copyright 2014
  7. * @license MIT <http://opensource.org/licenses/mit-license.php>
  8. * @link http://alertifyjs.com
  9. * @module AlertifyJS
  10. * @version 0.0.0
  11. */
  12. ( function ( window ) {
  13. 'use strict';
  14. /**
  15. * Keys enum
  16. * @type {Object}
  17. */
  18. var keys = {
  19. ENTER: 13,
  20. ESC: 27
  21. };
  22. /**
  23. * Default options
  24. * @type {Object}
  25. */
  26. var defaults = {
  27. modal:true,
  28. movable:true,
  29. resizable:true,
  30. closable:true,
  31. maximizable:true,
  32. pinnable:true,
  33. pinned:true,
  34. transition:'pulse',
  35. padding: true,
  36. overflow:true,
  37. notifier:{
  38. delay:5,
  39. position:'bottom-right'
  40. },
  41. glossary:{
  42. title:'AlertifyJS',
  43. ok: 'OK',
  44. cancel: 'Cancel',
  45. acccpt: 'Accept',
  46. deny: 'Deny',
  47. confirm: 'Confirm',
  48. decline: 'Decline',
  49. close: 'Close',
  50. maximize: 'Maximize',
  51. restore: 'Restore',
  52. },
  53. theme:{
  54. input:'ajs-input',
  55. ok:'ajs-ok',
  56. cancel:'ajs-cancel',
  57. }
  58. };
  59. /**
  60. * [Helper] Adds the specified class(es) to the element.
  61. *
  62. * @element {node} The element
  63. * @className {string} One or more space-separated classes to be added to the class attribute of the element.
  64. *
  65. * @return {undefined}
  66. */
  67. function addClass(element,classNames){
  68. element.className += ' ' + classNames;
  69. }
  70. /**
  71. * [Helper] Removes the specified class(es) from the element.
  72. *
  73. * @element {node} The element
  74. * @className {string} One or more space-separated classes to be removed from the class attribute of the element.
  75. *
  76. * @return {undefined}
  77. */
  78. function removeClass(element,classNames){
  79. var classes = classNames.split(' ');
  80. for(var x=0;x<classes.length;x+=1){
  81. element.className = element.className.replace(' ' + classes[x], '');
  82. }
  83. }
  84. /**
  85. * [Helper] Checks if the document is RTL
  86. *
  87. * @return {Boolean} True if the document is RTL, false otherwise.
  88. */
  89. function isRightToLeft(){
  90. return window.getComputedStyle(document.body).direction === 'rtl';
  91. }
  92. /**
  93. * [Helper] Get the document current scrollTop
  94. *
  95. * @return {Number} current document scrollTop value
  96. */
  97. function getScrollTop(){
  98. return ((document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop);
  99. }
  100. /**
  101. * [Helper] Get the document current scrollLeft
  102. *
  103. * @return {Number} current document scrollLeft value
  104. */
  105. function getScrollLeft(){
  106. return ((document.documentElement && document.documentElement.scrollLeft) || document.body.scrollLeft);
  107. }
  108. /**
  109. * Use a closure to return proper event listener method. Try to use
  110. * `addEventListener` by default but fallback to `attachEvent` for
  111. * unsupported browser. The closure simply ensures that the test doesn't
  112. * happen every time the method is called.
  113. *
  114. * @param {Node} el Node element
  115. * @param {String} event Event type
  116. * @param {Function} fn Callback of event
  117. * @return {Function}
  118. */
  119. var on = ( function () {
  120. if ( document.addEventListener ) {
  121. return function ( el, event, fn, useCapture ) {
  122. el.addEventListener( event, fn, useCapture === true );
  123. };
  124. } else if ( document.attachEvent ) {
  125. return function ( el, event, fn ) {
  126. el.attachEvent( 'on' + event, fn );
  127. };
  128. }
  129. } () );
  130. /**
  131. * Use a closure to return proper event listener method. Try to use
  132. * `removeEventListener` by default but fallback to `detachEvent` for
  133. * unsupported browser. The closure simply ensures that the test doesn't
  134. * happen every time the method is called.
  135. *
  136. * @param {Node} el Node element
  137. * @param {String} event Event type
  138. * @param {Function} fn Callback of event
  139. * @return {Function}
  140. */
  141. var off = ( function () {
  142. if ( document.removeEventListener ) {
  143. return function ( el, event, fn, useCapture ) {
  144. el.removeEventListener( event, fn, useCapture === true );
  145. };
  146. } else if ( document.detachEvent ) {
  147. return function ( el, event, fn ) {
  148. el.detachEvent( 'on' + event, fn );
  149. };
  150. }
  151. } () );
  152. /**
  153. * Prevent default event from firing
  154. *
  155. * @param {Event} event Event object
  156. * @return {undefined}
  157. function prevent ( event ) {
  158. if ( event ) {
  159. if ( event.preventDefault ) {
  160. event.preventDefault();
  161. } else {
  162. event.returnValue = false;
  163. }
  164. }
  165. }
  166. */
  167. var transition = ( function () {
  168. var t, type;
  169. var supported = false;
  170. var el = document.createElement( 'fakeelement' );
  171. var transitions = {
  172. 'WebkitTransition': 'webkitTransitionEnd',
  173. 'MozTransition': 'transitionend',
  174. 'OTransition': 'otransitionend',
  175. 'transition': 'transitionend'
  176. };
  177. for ( t in transitions ) {
  178. if ( el.style[ t ] !== undefined ) {
  179. type = transitions[ t ];
  180. supported = true;
  181. break;
  182. }
  183. }
  184. return {
  185. type: type,
  186. supported: supported
  187. };
  188. } () );
  189. /**
  190. * Creates event handler delegate that sends the instance as last argument.
  191. *
  192. * @return {Function} a function wrapper which sends the instance as last argument.
  193. */
  194. function delegate(context,method){
  195. return function(){
  196. if ( arguments.length > 0 ) {
  197. var args = [];
  198. for(var x=0; x<arguments.length;x+=1){
  199. args.push(arguments[x]);
  200. }
  201. args.push(context);
  202. return method.apply(context, args);
  203. }
  204. return method.apply(context, [null,context]);
  205. };
  206. }
  207. /**
  208. * Helper for creating a dialog close event.
  209. *
  210. * @return {object}
  211. */
  212. function createCloseEvent(index, button){
  213. return {
  214. index: index,
  215. button: button,
  216. cancel: false
  217. };
  218. }
  219. /**
  220. * Super class for all dialogs
  221. *
  222. * @return {Object} base dialog prototype
  223. */
  224. var dialog = (function () {
  225. //holds open dialogs instances
  226. var openInstances = [],
  227. //dummy variable, used to trigger dom reflow.
  228. reflow = null,
  229. //dialog building blocks
  230. templates = {
  231. dimmer:'<div class="ajs-dimmer"></div>',
  232. /*tab index required to fire click event before body focus*/
  233. modal: '<div class="ajs-modal" tabindex="0"></div>',
  234. dialog: '<div class="ajs-dialog" tabindex="0"></div>',
  235. reset: '<a class="ajs-reset" href="#"></a>',
  236. commands: '<div class="ajs-commands"><button class="ajs-pin"></button><button class="ajs-maximize"></button><button class="ajs-close"></button></div>',
  237. header: '<div class="ajs-header"></div>',
  238. body: '<div class="ajs-body"></div>',
  239. content: '<div class="ajs-content"></div>',
  240. footer: '<div class="ajs-footer"></div>',
  241. buttons: { primary: '<div class="ajs-primary ajs-buttons"></div>', auxiliary: '<div class="ajs-auxiliary ajs-buttons"></div>' },
  242. button: '<button class="ajs-button"></button>',
  243. resizeHandle: '<div class="ajs-handle"></div>',
  244. },
  245. //common class names
  246. classes = {
  247. base: 'alertify',
  248. prefix: 'ajs-',
  249. hidden: 'ajs-hidden',
  250. noSelection: 'ajs-no-selection',
  251. noOverflow: 'ajs-no-overflow',
  252. noPadding:'ajs-no-padding',
  253. modeless: 'ajs-modeless',
  254. movable: 'ajs-movable',
  255. resizable: 'ajs-resizable',
  256. fixed: 'ajs-fixed',
  257. closable:'ajs-closable',
  258. maximizable:'ajs-maximizable',
  259. maximize: 'ajs-maximize',
  260. restore: 'ajs-restore',
  261. pinnable:'ajs-pinnable',
  262. unpinned:'ajs-unpinned',
  263. pin:'ajs-pin',
  264. maximized: 'ajs-maximized',
  265. animationIn: 'ajs-in',
  266. animationOut: 'ajs-out',
  267. shake:'ajs-shake'
  268. };
  269. /**
  270. * Helper: initializes the dialog instance
  271. *
  272. * @return {Number} The total count of currently open modals.
  273. */
  274. function initialize(instance){
  275. if(!instance.__internal){
  276. //no need to expose init after this.
  277. delete instance.__init;
  278. //in case the script was included before body.
  279. //after first dialog gets initialized, it won't be null anymore!
  280. if(null === reflow){
  281. // set tabindex attribute on body element this allows script to give it
  282. // focus after the dialog is closed
  283. document.body.setAttribute( 'tabindex', '0' );
  284. }
  285. //get dialog buttons/focus setup
  286. var setup;
  287. if(typeof instance.setup === 'function'){
  288. setup = instance.setup();
  289. setup.options = setup.options || {};
  290. setup.focus = setup.focus || {};
  291. }else{
  292. setup = {
  293. buttons:[],
  294. focus:{
  295. element:null,
  296. select:false
  297. },
  298. options:{
  299. }
  300. };
  301. }
  302. var internal = instance.__internal = {
  303. /**
  304. * Flag holding the open state of the dialog
  305. *
  306. * @type {Boolean}
  307. */
  308. isOpen:false,
  309. /**
  310. * Active element is the element that will receive focus after
  311. * closing the dialog. It defaults as the body tag, but gets updated
  312. * to the last focused element before the dialog was opened.
  313. *
  314. * @type {Node}
  315. */
  316. activeElement:document.body,
  317. buttons: setup.buttons,
  318. focus: setup.focus,
  319. options: {
  320. title: undefined,
  321. modal: undefined,
  322. pinned: undefined,
  323. movable: undefined,
  324. resizable: undefined,
  325. closable: undefined,
  326. maximizable: undefined,
  327. pinnable: undefined,
  328. transition: undefined,
  329. padding:undefined,
  330. overflow:undefined,
  331. onshow:undefined,
  332. onclose:undefined
  333. },
  334. resetHandler:undefined,
  335. beginMoveHandler:undefined,
  336. beginResizeHandler:undefined,
  337. bringToFrontHandler:undefined,
  338. modalClickHandler:undefined,
  339. buttonsClickHandler:undefined,
  340. commandsClickHandler:undefined,
  341. transitionInHandler:undefined,
  342. transitionOutHandler:undefined
  343. };
  344. var elements = {};
  345. //root node
  346. elements.root = document.createElement('div');
  347. elements.root.className = classes.base + ' ' + classes.hidden + ' ';
  348. elements.root.innerHTML = templates.dimmer + templates.modal;
  349. //dimmer
  350. elements.dimmer = elements.root.firstChild;
  351. //dialog
  352. elements.modal = elements.root.lastChild;
  353. elements.modal.innerHTML = templates.dialog;
  354. elements.dialog = elements.modal.firstChild;
  355. elements.dialog.innerHTML = templates.reset + templates.commands + templates.header + templates.body + templates.footer + templates.reset;
  356. //reset links
  357. elements.reset = [];
  358. elements.reset.push(elements.dialog.firstChild);
  359. elements.reset.push(elements.dialog.lastChild);
  360. //commands
  361. elements.commands = {};
  362. elements.commands.container = elements.reset[0].nextSibling;
  363. elements.commands.pin = elements.commands.container.firstChild;
  364. elements.commands.maximize = elements.commands.pin.nextSibling;
  365. elements.commands.close = elements.commands.maximize.nextSibling;
  366. //header
  367. elements.header = elements.commands.container.nextSibling;
  368. //body
  369. elements.body = elements.header.nextSibling;
  370. elements.body.innerHTML = templates.content;
  371. elements.content = elements.body.firstChild;
  372. //footer
  373. elements.footer = elements.body.nextSibling;
  374. elements.footer.innerHTML = templates.buttons.auxiliary + templates.buttons.primary + templates.resizeHandle;
  375. elements.resizeHandle = elements.footer.lastChild;
  376. //buttons
  377. elements.buttons = {};
  378. elements.buttons.auxiliary = elements.footer.firstChild;
  379. elements.buttons.primary = elements.buttons.auxiliary.nextSibling;
  380. elements.buttons.primary.innerHTML = templates.button;
  381. elements.buttonTemplate = elements.buttons.primary.firstChild;
  382. //remove button template
  383. elements.buttons.primary.removeChild(elements.buttonTemplate);
  384. for(var x=0; x < instance.__internal.buttons.length; x+=1) {
  385. var button = instance.__internal.buttons[x];
  386. button.element = elements.buttonTemplate.cloneNode();
  387. button.element.innerHTML = button.text;
  388. if(typeof button.className === 'string' && button.className !== ''){
  389. addClass(button.element, button.className);
  390. }
  391. for(var key in button.attrs){
  392. if(key !== 'className' && button.attrs.hasOwnProperty(key)){
  393. button.element.setAttribute(key, button.attrs[key]);
  394. }
  395. }
  396. if(button.scope === 'auxiliary'){
  397. elements.buttons.auxiliary.appendChild(button.element);
  398. }else{
  399. elements.buttons.primary.appendChild(button.element);
  400. }
  401. }
  402. //make elements pubic
  403. instance.elements = elements;
  404. //save event handlers delegates
  405. internal.resetHandler = delegate(instance, onReset);
  406. internal.beginMoveHandler = delegate(instance, beginMove);
  407. internal.beginResizeHandler = delegate(instance, beginResize);
  408. internal.bringToFrontHandler = delegate(instance, bringToFront);
  409. internal.modalClickHandler = delegate(instance, modalClickHandler);
  410. internal.buttonsClickHandler = delegate(instance, buttonsClickHandler);
  411. internal.commandsClickHandler = delegate(instance, commandsClickHandler);
  412. internal.transitionInHandler = delegate(instance, handleTransitionInEvent);
  413. internal.transitionOutHandler = delegate(instance, handleTransitionOutEvent);
  414. //settings
  415. instance.setting('title', setup.options.title === undefined ? alertify.defaults.glossary.title : setup.options.title);
  416. instance.setting('modal', setup.options.modal === undefined ? alertify.defaults.modal : setup.options.modal);
  417. instance.setting('movable', setup.options.movable === undefined ? alertify.defaults.movable : setup.options.movable);
  418. instance.setting('resizable', setup.options.resizable === undefined ? alertify.defaults.resizable : setup.options.resizable);
  419. instance.setting('closable', setup.options.closable === undefined ? alertify.defaults.closable : setup.options.closable);
  420. instance.setting('maximizable', setup.options.maximizable === undefined ? alertify.defaults.maximizable : setup.options.maximizable);
  421. instance.setting('pinnable', setup.options.pinnable === undefined ? alertify.defaults.pinnable : setup.options.pinnable);
  422. instance.setting('pinned', setup.options.pinned === undefined ? alertify.defaults.pinned : setup.options.pinned);
  423. instance.setting('transition', setup.options.transition === undefined ? alertify.defaults.transition : setup.options.transition);
  424. instance.setting('padding', setup.options.padding === undefined ? alertify.defaults.padding : setup.options.padding);
  425. instance.setting('overflow', setup.options.overflow === undefined ? alertify.defaults.overflow : setup.options.overflow);
  426. // allow dom customization
  427. if(typeof instance.build === 'function'){
  428. instance.build();
  429. }
  430. }
  431. //add to DOM tree.
  432. document.body.appendChild(instance.elements.root);
  433. }
  434. /**
  435. * Helper: adds/removes no-overflow class from body
  436. *
  437. */
  438. function ensureNoOverflow(){
  439. var requiresNoOverflow = 0;
  440. for(var x=0;x<openInstances.length;x+=1){
  441. var instance = openInstances[x];
  442. if(instance.isModal() || instance.isMaximized()){
  443. requiresNoOverflow+=1;
  444. }
  445. }
  446. if(requiresNoOverflow === 0){
  447. //last open modal or last maximized one
  448. removeClass(document.body, classes.noOverflow);
  449. }else if(requiresNoOverflow > 0 && document.body.className.indexOf(classes.noOverflow) < 0){
  450. //first open modal or first maximized one
  451. addClass(document.body, classes.noOverflow);
  452. }
  453. }
  454. /**
  455. * Sets the name of the transition used to show/hide the dialog
  456. *
  457. * @param {Object} instance The dilog instance.
  458. *
  459. */
  460. function updateTransition(instance, value, oldValue){
  461. if(typeof oldValue === 'string'){
  462. removeClass(instance.elements.root,classes.prefix + oldValue);
  463. }
  464. addClass(instance.elements.root, classes.prefix + value);
  465. reflow = instance.elements.root.offsetWidth;
  466. }
  467. /**
  468. * Toggles the dialog display mode
  469. *
  470. * @param {Object} instance The dilog instance.
  471. * @param {Boolean} on True to make it modal, false otherwise.
  472. *
  473. * @return {undefined}
  474. */
  475. function updateDisplayMode(instance){
  476. if(instance.setting('modal')){
  477. //make modal
  478. removeClass(instance.elements.root, classes.modeless);
  479. //only if open
  480. if(instance.isOpen()){
  481. unbindModelessEvents(instance);
  482. //in case a pinned modless dialog was made modal while open.
  483. updateAbsPositionFix(instance);
  484. ensureNoOverflow();
  485. }
  486. }else{
  487. //make modelss
  488. addClass(instance.elements.root, classes.modeless);
  489. //only if open
  490. if(instance.isOpen()){
  491. bindModelessEvents(instance);
  492. //in case pin/unpin was called while a modal is open
  493. updateAbsPositionFix(instance);
  494. ensureNoOverflow();
  495. }
  496. }
  497. }
  498. /**
  499. * Helper: Brings the modeless dialog to front, attached to modeless dialogs.
  500. *
  501. * @param {Event} event Focus event
  502. * @param {Object} instance The dilog instance.
  503. *
  504. * @return {undefined}
  505. */
  506. function bringToFront(event, instance){
  507. // Do not bring to front if preceeded by an open modal
  508. var index = openInstances.indexOf(instance);
  509. for(var x=index+1;x<openInstances.length;x+=1){
  510. if(openInstances[x].isModal()){
  511. return;
  512. }
  513. }
  514. // Bring to front by making it the last child.
  515. if(document.body.lastChild !== instance.elements.root){
  516. document.body.appendChild(instance.elements.root);
  517. setFocus(instance);
  518. }
  519. return false;
  520. }
  521. /**
  522. * Helper: reflects dialogs options updates
  523. *
  524. * @param {Object} instance The dilog instance.
  525. * @param {String} option The updated option name.
  526. *
  527. * @return {undefined}
  528. */
  529. function optionUpdated(instance, option, oldValue, newValue){
  530. switch(option){
  531. case 'title':
  532. instance.setHeader(newValue);
  533. break;
  534. case 'modal':
  535. updateDisplayMode(instance);
  536. break;
  537. case 'pinned':
  538. updatePinned(instance);
  539. break;
  540. case 'closable':
  541. updateClosable(instance);
  542. break;
  543. case 'maximizable':
  544. updateMaximizable(instance);
  545. break;
  546. case 'pinnable':
  547. updatePinnable(instance);
  548. break;
  549. case 'movable':
  550. updateMovable(instance);
  551. break;
  552. case 'resizable':
  553. updateResizable(instance);
  554. break;
  555. case 'transition':
  556. updateTransition(instance,newValue, oldValue);
  557. break;
  558. case 'padding':
  559. if(newValue){
  560. removeClass(instance.elements.root, classes.noPadding);
  561. }else if(instance.elements.root.className.indexOf(classes.noPadding) < 0){
  562. addClass(instance.elements.root, classes.noPadding);
  563. }
  564. break;
  565. case 'overflow':
  566. if(newValue){
  567. removeClass(instance.elements.root, classes.noOverflow);
  568. }else if(instance.elements.root.className.indexOf(classes.noOverflow) < 0){
  569. addClass(instance.elements.root, classes.noOverflow);
  570. }
  571. break;
  572. case 'transition':
  573. updateTransition(instance,newValue, oldValue);
  574. break;
  575. }
  576. }
  577. /**
  578. * Helper: reflects dialogs options updates
  579. *
  580. * @param {Object} instance The dilog instance.
  581. * @param {Object} obj The object to set/get a value on/from.
  582. * @param {Function} callback The callback function to call if the key was found.
  583. * @param {String|Object} key A string specifying a propery name or a collection of key value pairs.
  584. * @param {Object} value Optional, the value associated with the key (in case it was a string).
  585. * @param {String} option The updated option name.
  586. *
  587. * @return {Object} result object
  588. * The result objects has an 'op' property, indicating of this is a SET or GET operation.
  589. * GET:
  590. * - found: a flag indicating if the key was found or not.
  591. * - value: the property value.
  592. * SET:
  593. * - items: a list of key value pairs of the properties being set.
  594. * each contains:
  595. * - found: a flag indicating if the key was found or not.
  596. * - key: the property key.
  597. * - value: the property value.
  598. */
  599. function update(instance, obj, callback, key, value){
  600. var result = {op:undefined, items: [] };
  601. if(typeof value === 'undefined' && typeof key === 'string') {
  602. //get
  603. result.op = 'get';
  604. if(obj.hasOwnProperty(key)){
  605. result.found = true;
  606. result.value = obj[key];
  607. }else{
  608. result.found = false;
  609. result.value = undefined;
  610. }
  611. }
  612. else
  613. {
  614. var old;
  615. //set
  616. result.op = 'set';
  617. if(typeof key === 'object'){
  618. //set multiple
  619. var args = key;
  620. for (var prop in args) {
  621. if (obj.hasOwnProperty(prop)) {
  622. if(obj[prop] !== args[prop]){
  623. old = obj[prop];
  624. obj[prop] = args[prop];
  625. callback.call(instance,prop, old, args[prop]);
  626. }
  627. result.items.push({ 'key': prop, 'value': args[prop], 'found':true});
  628. }else{
  629. result.items.push({ 'key': prop, 'value': args[prop], 'found':false});
  630. }
  631. }
  632. } else if (typeof key === 'string'){
  633. //set single
  634. if (obj.hasOwnProperty(key)) {
  635. if(obj[key] !== value){
  636. old = obj[key];
  637. obj[key] = value;
  638. callback.call(instance,key, old, value);
  639. }
  640. result.items.push({'key': key, 'value': value , 'found':true});
  641. }else{
  642. result.items.push({'key': key, 'value': value , 'found':false});
  643. }
  644. } else {
  645. //invalid params
  646. throw new Error('args must be a string or object');
  647. }
  648. }
  649. return result;
  650. }
  651. /**
  652. * Triggers a close event.
  653. *
  654. * @param {Object} instance The dilog instance.
  655. *
  656. * @return {undefined}
  657. */
  658. function triggerClose(instance){
  659. var found;
  660. triggerCallback(instance, function(button){
  661. return found = (button.invokeOnClose === true);
  662. });
  663. //none of the buttons registered as onclose callback
  664. //close the dialog
  665. if(!found && instance.isOpen()){
  666. instance.close();
  667. }
  668. }
  669. /**
  670. * Dialogs commands event handler, attached to the dialog commands element.
  671. *
  672. * @param {Event} event DOM event object.
  673. * @param {Object} instance The dilog instance.
  674. *
  675. * @return {undefined}
  676. */
  677. function commandsClickHandler(event,instance){
  678. var target = event.srcElement || event.target;
  679. switch(target){
  680. case instance.elements.commands.pin:
  681. if(!instance.isPinned()){
  682. pin(instance);
  683. } else {
  684. unpin(instance);
  685. }
  686. break;
  687. case instance.elements.commands.maximize:
  688. if(!instance.isMaximized()){
  689. maximize(instance);
  690. } else {
  691. restore(instance);
  692. }
  693. break;
  694. case instance.elements.commands.close:
  695. triggerClose(instance);
  696. break;
  697. }
  698. return false;
  699. }
  700. /**
  701. * Helper: pins the modeless dialog.
  702. *
  703. * @param {Object} instance The dialog instance.
  704. *
  705. * @return {undefined}
  706. */
  707. function pin(instance){
  708. //pin the dialog
  709. instance.setting('pinned', true);
  710. }
  711. /**
  712. * Helper: unpins the modeless dialog.
  713. *
  714. * @param {Object} instance The dilog instance.
  715. *
  716. * @return {undefined}
  717. */
  718. function unpin(instance){
  719. //unpin the dialog
  720. instance.setting('pinned', false);
  721. }
  722. /**
  723. * Helper: enlarges the dialog to fill the entire screen.
  724. *
  725. * @param {Object} instance The dilog instance.
  726. *
  727. * @return {undefined}
  728. */
  729. function maximize(instance){
  730. //maximize the dialog
  731. addClass(instance.elements.root, classes.maximized);
  732. if(instance.isOpen()){
  733. ensureNoOverflow();
  734. }
  735. }
  736. /**
  737. * Helper: returns the dialog to its former size.
  738. *
  739. * @param {Object} instance The dilog instance.
  740. *
  741. * @return {undefined}
  742. */
  743. function restore(instance){
  744. //maximize the dialog
  745. removeClass(instance.elements.root, classes.maximized);
  746. if(instance.isOpen()){
  747. ensureNoOverflow();
  748. }
  749. }
  750. /**
  751. * Show or hide the maximize box.
  752. *
  753. * @param {Object} instance The dilog instance.
  754. * @param {Boolean} on True to add the behavior, removes it otherwise.
  755. *
  756. * @return {undefined}
  757. */
  758. function updatePinnable(instance){
  759. if(instance.setting('pinnable')){
  760. // add class
  761. addClass(instance.elements.root, classes.pinnable);
  762. }else{
  763. // remove class
  764. removeClass(instance.elements.root, classes.pinnable);
  765. }
  766. }
  767. /**
  768. * Helper: Fixes the absolutly positioned modal div position.
  769. *
  770. * @param {Object} instance The dialog instance.
  771. *
  772. * @return {undefined}
  773. */
  774. function addAbsPositionFix(instance){
  775. var scrollLeft = getScrollLeft();
  776. instance.elements.modal.style.marginTop = getScrollTop() + 'px';
  777. instance.elements.modal.style.marginLeft = scrollLeft + 'px';
  778. instance.elements.modal.style.marginRight = (-scrollLeft) + 'px';
  779. }
  780. /**
  781. * Helper: Removes the absolutly positioned modal div position fix.
  782. *
  783. * @param {Object} instance The dialog instance.
  784. *
  785. * @return {undefined}
  786. */
  787. function removeAbsPositionFix(instance){
  788. var marginTop = parseInt(instance.elements.modal.style.marginTop,10);
  789. var marginLeft = parseInt(instance.elements.modal.style.marginLeft,10);
  790. instance.elements.modal.style.marginTop = '';
  791. instance.elements.modal.style.marginLeft = '';
  792. instance.elements.modal.style.marginRight = '';
  793. if (instance.isOpen()) {
  794. var top = 0,
  795. left = 0
  796. ;
  797. if(instance.elements.dialog.style.top !== ''){
  798. top = parseInt(instance.elements.dialog.style.top, 10);
  799. }
  800. instance.elements.dialog.style.top = (top + (marginTop - getScrollTop())) + 'px';
  801. if(instance.elements.dialog.style.left !== ''){
  802. left = parseInt(instance.elements.dialog.style.left, 10);
  803. }
  804. instance.elements.dialog.style.left = (left + (marginLeft - getScrollLeft())) + 'px';
  805. }
  806. }
  807. /**
  808. * Helper: Adds/Removes the absolutly positioned modal div position fix based on its pinned setting.
  809. *
  810. * @param {Object} instance The dialog instance.
  811. *
  812. * @return {undefined}
  813. */
  814. function updateAbsPositionFix(instance){
  815. // if modeless and unpinned add fix
  816. if(!instance.setting('modal') && !instance.setting('pinned')){
  817. addAbsPositionFix(instance);
  818. }else{
  819. removeAbsPositionFix(instance);
  820. }
  821. }
  822. /**
  823. * Toggles the dialog position lock | modeless only.
  824. *
  825. * @param {Object} instance The dilog instance.
  826. * @param {Boolean} on True to make it modal, false otherwise.
  827. *
  828. * @return {undefined}
  829. */
  830. function updatePinned(instance){
  831. if(instance.setting('pinned')){
  832. removeClass(instance.elements.root, classes.unpinned);
  833. if(instance.isOpen()){
  834. removeAbsPositionFix(instance);
  835. }
  836. }else{
  837. addClass(instance.elements.root, classes.unpinned);
  838. if(instance.isOpen() && !instance.isModal()){
  839. addAbsPositionFix(instance);
  840. }
  841. }
  842. }
  843. /**
  844. * Show or hide the maximize box.
  845. *
  846. * @param {Object} instance The dilog instance.
  847. * @param {Boolean} on True to add the behavior, removes it otherwise.
  848. *
  849. * @return {undefined}
  850. */
  851. function updateMaximizable(instance){
  852. if(instance.setting('maximizable')){
  853. // add class
  854. addClass(instance.elements.root, classes.maximizable);
  855. }else{
  856. // remove class
  857. removeClass(instance.elements.root, classes.maximizable);
  858. }
  859. }
  860. /**
  861. * Show or hide the close box.
  862. *
  863. * @param {Object} instance The dilog instance.
  864. * @param {Boolean} on True to add the behavior, removes it otherwise.
  865. *
  866. * @return {undefined}
  867. */
  868. function updateClosable(instance){
  869. if(instance.setting('closable')){
  870. // add class
  871. addClass(instance.elements.root, classes.closable);
  872. bindClosableEvents(instance);
  873. }else{
  874. // remove class
  875. removeClass(instance.elements.root, classes.closable);
  876. unbindClosableEvents(instance);
  877. }
  878. }
  879. // flag to cancel click event if already handled by end resize event (the mousedown, mousemove, mouseup sequence fires a click event.).
  880. var cancelClick = false;
  881. /**
  882. * Helper: closes the modal dialog when clicking the modal
  883. *
  884. * @param {Event} event DOM event object.
  885. * @param {Object} instance The dilog instance.
  886. *
  887. * @return {undefined}
  888. */
  889. function modalClickHandler(event, instance){
  890. var target = event.srcElement || event.target;
  891. if(!cancelClick && target === instance.elements.modal){
  892. triggerClose(instance);
  893. }
  894. cancelClick = false;
  895. return false;
  896. }
  897. // flag to cancel keyup event if already handled by click event (pressing Enter on a focusted button).
  898. var cancelKeyup = false;
  899. /**
  900. * Helper: triggers a button callback
  901. *
  902. * @param {Object} The dilog instance.
  903. * @param {Function} Callback to check which button triggered the event.
  904. *
  905. * @return {undefined}
  906. */
  907. function triggerCallback(instance, check){
  908. for(var idx=0; idx < instance.__internal.buttons.length; idx+=1){
  909. var button = instance.__internal.buttons[idx];
  910. if(!button.element.disabled && check(button)){
  911. var closeEvent = createCloseEvent(idx, button);
  912. if( typeof instance.callback === 'function'){
  913. instance.callback.apply(instance, [closeEvent]);
  914. }
  915. //close the dialog only if not canceled.
  916. if (closeEvent.cancel === false){
  917. instance.close();
  918. }
  919. break;
  920. }
  921. }
  922. }
  923. /**
  924. * Clicks event handler, attached to the dialog footer.
  925. *
  926. * @param {Event} DOM event object.
  927. * @param {Object} The dilog instance.
  928. *
  929. * @return {undefined}
  930. */
  931. function buttonsClickHandler(event,instance){
  932. var target = event.srcElement || event.target;
  933. triggerCallback(instance, function(button){
  934. // if this button caused the click, cancel keyup event
  935. return button.element === target && (cancelKeyup = true);
  936. });
  937. }
  938. /**
  939. * Keyup event handler, attached to the document.body
  940. *
  941. * @param {Event} DOM event object.
  942. * @param {Object} The dilog instance.
  943. *
  944. * @return {undefined}
  945. */
  946. function keyupHandler(event){
  947. //hitting enter while button has focus will trigger keyup too.
  948. //ignore if handled by clickHandler
  949. if(cancelKeyup){
  950. cancelKeyup = false;
  951. return;
  952. }
  953. var instance = openInstances[openInstances.length-1];
  954. var keyCode = event.keyCode;
  955. if (keyCode === keys.ENTER || keyCode === keys.ESC) {
  956. triggerCallback(instance, function(button){
  957. return button.key === keyCode;
  958. });
  959. return false;
  960. }
  961. }
  962. /**
  963. * Sets focus to proper dialog element
  964. *
  965. * @param {Object} instance The dilog instance.
  966. * @param {Node} [resetTarget=undefined] DOM element to reset focus to.
  967. *
  968. * @return {undefined}
  969. */
  970. function setFocus ( instance, resetTarget ) {
  971. // reset target has already been determined.
  972. if (resetTarget) {
  973. resetTarget.focus();
  974. } else {
  975. // current instance focus settings
  976. var focus = instance.__internal.focus;
  977. // the focus element.
  978. var element = focus.element;
  979. // a number means a button index
  980. if (typeof focus.element === 'number') {
  981. element = instance.__internal.buttons[focus.element].element;
  982. }
  983. // focus
  984. if (element && element.focus) {
  985. element.focus();
  986. // if selectable
  987. if (focus.select && element.select) {
  988. element.select();
  989. }
  990. }
  991. }
  992. }
  993. /**
  994. * Focus event handler, attached to document.body and dialogs own reset links.
  995. * handles the focus for modal dialogs only.
  996. *
  997. * @param {Event} event DOM focus event object.
  998. * @param {Object} instance The dilog instance.
  999. *
  1000. * @return {undefined}
  1001. */
  1002. function onReset ( event, instance ) {
  1003. // should work on last modal if triggered from document.body
  1004. if(!instance){
  1005. for(var x=openInstances.length-1;x>-1;x-=1){
  1006. if(openInstances[x].isModal()){
  1007. instance = openInstances[x];
  1008. break;
  1009. }
  1010. }
  1011. }
  1012. // if modal
  1013. if(instance && instance.isModal()){
  1014. // determine reset target to enable forward/backward tab cycle.
  1015. var resetTarget, target = event.srcElement || event.target;
  1016. var lastResetLink = target === instance.elements.reset[1];
  1017. // if last reset link, then go to maximize or close
  1018. if(lastResetLink){
  1019. if(instance.setting('maximizable')){
  1020. resetTarget = instance.elements.commands.maximize;
  1021. }else if(instance.setting('closable')){
  1022. resetTarget = instance.elements.commands.close;
  1023. }
  1024. }
  1025. // if no reset target found, try finding the best button
  1026. if(resetTarget === undefined){
  1027. if(typeof instance.__internal.focus.element === 'number'){
  1028. // button focus element, go to first available button
  1029. if(target === instance.elements.reset[0]){
  1030. resetTarget = instance.elements.buttons.auxiliary.firstChild || instance.elements.buttons.primary.firstChild;
  1031. }else if(lastResetLink){
  1032. //restart the cycle by going to first reset link
  1033. resetTarget = instance.elements.reset[0];
  1034. }
  1035. }else{
  1036. // will reach here when tapping backwards, so go to last child
  1037. // The focus element SHOULD NOT be a button (logically!).
  1038. if(target === instance.elements.reset[0]){
  1039. resetTarget = instance.elements.buttons.primary.lastChild || instance.elements.buttons.auxiliary.lastChild;
  1040. }
  1041. }
  1042. }
  1043. // focus
  1044. setFocus(instance, resetTarget);
  1045. }
  1046. }
  1047. //animation timers
  1048. var transitionInTimeout,transitionOutTimeout;
  1049. /**
  1050. * Transition in transitionend event handler.
  1051. *
  1052. * @param {Event} TransitionEnd event object.
  1053. * @param {Object} The dilog instance.
  1054. *
  1055. * @return {undefined}
  1056. */
  1057. function handleTransitionInEvent ( event, instance) {
  1058. // clear the timer
  1059. clearTimeout( transitionInTimeout );
  1060. // once transition is complete, set focus
  1061. setFocus(instance);
  1062. // allow handling key up after transition ended.
  1063. cancelKeyup = false;
  1064. // allow custom `onfocus` method
  1065. if ( typeof instance.onfocus === 'function' ) {
  1066. instance.onfocus();
  1067. }
  1068. // unbind the event
  1069. off( instance.elements.dialog, transition.type, instance.__internal.transitionInHandler);
  1070. }
  1071. /**
  1072. * Transition out transitionend event handler.
  1073. *
  1074. * @param {Event} TransitionEnd event object.
  1075. * @param {Object} The dilog instance.
  1076. *
  1077. * @return {undefined}
  1078. */
  1079. function handleTransitionOutEvent ( event, instance) {
  1080. // clear the timer
  1081. clearTimeout( transitionOutTimeout );
  1082. // unbind the event
  1083. off( instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
  1084. // reset move updates
  1085. resetMove(instance);
  1086. // reset resize updates
  1087. resetResize(instance);
  1088. // restore if maximized
  1089. if(instance.isMaximized()){
  1090. restore(instance);
  1091. }
  1092. // return focus to the last active element
  1093. instance.__internal.activeElement.focus();
  1094. }
  1095. /* Controls moving a dialog around */
  1096. //holde the current moving instance
  1097. var movable = null,
  1098. //holds the current X offset when move starts
  1099. offsetX = 0,
  1100. //holds the current Y offset when move starts
  1101. offsetY = 0
  1102. ;
  1103. /**
  1104. * Helper: sets the element top/left coordinates
  1105. *
  1106. * @param {Event} event DOM event object.
  1107. * @param {Node} element The element being moved.
  1108. *
  1109. * @return {undefined}
  1110. */
  1111. function moveElement(event, element){
  1112. element.style.left = (event.pageX - offsetX) + 'px';
  1113. element.style.top = (event.pageY - offsetY) + 'px';
  1114. }
  1115. /**
  1116. * Triggers the start of a move event, attached to the header element mouse down event.
  1117. * Adds no-selection class to the body, disabling selection while moving.
  1118. *
  1119. * @param {Event} event DOM event object.
  1120. * @param {Object} instance The dilog instance.
  1121. *
  1122. * @return {Boolean} false
  1123. */
  1124. function beginMove(event, instance){
  1125. if(event.button === 0 && !instance.isMaximized() && instance.setting('movable')){
  1126. movable = instance;
  1127. offsetX = event.pageX;
  1128. offsetY = event.pageY;
  1129. var element = instance.elements.dialog;
  1130. if(element.style.left){
  1131. offsetX -= parseInt(element.style.left,10);
  1132. }
  1133. if(element.style.top){
  1134. offsetY -= parseInt(element.style.top,10);
  1135. }
  1136. moveElement(event, element);
  1137. addClass(document.body, classes.noSelection);
  1138. return false;
  1139. }
  1140. }
  1141. /**
  1142. * The actual move handler, attached to document.body mousemove event.
  1143. *
  1144. * @param {Event} event DOM event object.
  1145. *
  1146. * @return {undefined}
  1147. */
  1148. function move(event){
  1149. if(movable){
  1150. moveElement(event, movable.elements.dialog);
  1151. }
  1152. }
  1153. /**
  1154. * Triggers the end of a move event, attached to document.body mouseup event.
  1155. * Removes no-selection class from document.body, allowing selection.
  1156. *
  1157. * @return {undefined}
  1158. */
  1159. function endMove(){
  1160. if(movable){
  1161. movable = null;
  1162. removeClass(document.body, classes.noSelection);
  1163. }
  1164. }
  1165. /**
  1166. * Resets any changes made by moving the element to its original state,
  1167. *
  1168. * @param {Object} instance The dilog instance.
  1169. *
  1170. * @return {undefined}
  1171. */
  1172. function resetMove(instance){
  1173. var element = instance.elements.dialog;
  1174. element.style.left = element.style.top = '';
  1175. }
  1176. /**
  1177. * Updates the dialog move behavior.
  1178. *
  1179. * @param {Object} instance The dilog instance.
  1180. * @param {Boolean} on True to add the behavior, removes it otherwise.
  1181. *
  1182. * @return {undefined}
  1183. */
  1184. function updateMovable(instance){
  1185. if(instance.setting('movable')){
  1186. // add class
  1187. addClass(instance.elements.root, classes.movable);
  1188. if(instance.isOpen()){
  1189. bindMovableEvents(instance);
  1190. }
  1191. }else{
  1192. //reset
  1193. resetMove(instance);
  1194. // remove class
  1195. removeClass(instance.elements.root, classes.movable);
  1196. if(instance.isOpen()){
  1197. unbindMovableEvents(instance);
  1198. }
  1199. }
  1200. }
  1201. /* Controls moving a dialog around */
  1202. //holde the current instance being resized
  1203. var resizable = null,
  1204. //holds the staring left offset when resize starts.
  1205. startingLeft = Number.Nan,
  1206. //holds the staring width when resize starts.
  1207. startingWidth = 0,
  1208. //holds the initial width when resized for the first time.
  1209. minWidth = 0,
  1210. //holds the offset of the resize handle.
  1211. handleOffset = 0
  1212. ;
  1213. /**
  1214. * Helper: sets the element width/height and updates left coordinate if neccessary.
  1215. *
  1216. * @param {Event} event DOM mousemove event object.
  1217. * @param {Node} element The element being moved.
  1218. * @param {Boolean} pinned A flag indicating if the element being resized is pinned to the screen.
  1219. *
  1220. * @return {undefined}
  1221. */
  1222. function resizeElement(event, element, pageRelative){
  1223. //calculate offsets from 0,0
  1224. var current = element;
  1225. var offsetLeft = 0;
  1226. var offsetTop = 0;
  1227. do
  1228. {
  1229. offsetLeft += current.offsetLeft;
  1230. offsetTop += current.offsetTop;
  1231. }while(current = current.offsetParent);
  1232. // determine X,Y coordinates.
  1233. var X,Y;
  1234. if(pageRelative === true){
  1235. X = event.pageX;
  1236. Y = event.pageY;
  1237. }else{
  1238. X = event.clientX;
  1239. Y = event.clientY;
  1240. }
  1241. // rtl handling
  1242. var isRTL = isRightToLeft();
  1243. if(isRTL){
  1244. // reverse X
  1245. X = document.body.offsetWidth - X;
  1246. // if has a starting left, calculate offsetRight
  1247. if(!isNaN(startingLeft)){
  1248. offsetLeft = document.body.offsetWidth - offsetLeft - element.offsetWidth;
  1249. }
  1250. }
  1251. // set width/height
  1252. element.style.height = (Y - offsetTop + handleOffset) + 'px';
  1253. element.style.width = (X - offsetLeft + handleOffset) + 'px';
  1254. // if the element being resized has a starting left, maintain it.
  1255. // the dialog is centered, divide by half the offset to maintain the margins.
  1256. if(!isNaN(startingLeft)){
  1257. var diff = Math.abs(element.offsetWidth - startingWidth) * 0.5;
  1258. if(isRTL){
  1259. //negate the diff, why?
  1260. //when growing it should decrease left
  1261. //when shrinking it should increase left
  1262. diff *= -1;
  1263. }
  1264. if(element.offsetWidth > startingWidth){
  1265. //growing
  1266. element.style.left = (startingLeft + diff) + 'px';
  1267. }else if(element.offsetWidth >= minWidth){
  1268. //shrinking
  1269. element.style.left = (startingLeft - diff) + 'px';
  1270. }
  1271. }
  1272. }
  1273. /**
  1274. * Triggers the start of a resize event, attached to the resize handle element mouse down event.
  1275. * Adds no-selection class to the body, disabling selection while moving.
  1276. *
  1277. * @param {Event} event DOM event object.
  1278. * @param {Object} instance The dilog instance.
  1279. *
  1280. * @return {Boolean} false
  1281. */
  1282. function beginResize(event, instance){
  1283. if(event.button === 0 && !instance.isMaximized()){
  1284. resizable = instance;
  1285. handleOffset = instance.elements.resizeHandle.offsetHeight/2;
  1286. var element = instance.elements.dialog;
  1287. startingLeft = parseInt(element.style.left,10);
  1288. element.style.height = element.offsetHeight + 'px';
  1289. element.style.minHeight = instance.elements.header.offsetHeight + instance.elements.footer.offsetHeight + 'px';
  1290. element.style.width = (startingWidth = element.offsetWidth) + 'px';
  1291. if(element.style.maxWidth !== 'none'){
  1292. element.style.minWidth = (minWidth = element.offsetWidth) + 'px';
  1293. }
  1294. element.style.maxWidth = 'none';
  1295. addClass(document.body, classes.noSelection);
  1296. return false;
  1297. }
  1298. }
  1299. /**
  1300. * The actual resize handler, attached to document.body mousemove event.
  1301. *
  1302. * @param {Event} event DOM event object.
  1303. *
  1304. * @return {undefined}
  1305. */
  1306. function resize(event){
  1307. if(resizable){
  1308. resizeElement(event, resizable.elements.dialog, !resizable.setting('modal') && !resizable.setting('pinned'));
  1309. }
  1310. }
  1311. /**
  1312. * Triggers the end of a resize event, attached to document.body mouseup event.
  1313. * Removes no-selection class from document.body, allowing selection.
  1314. *
  1315. * @return {undefined}
  1316. */
  1317. function endResize(){
  1318. if(resizable){
  1319. resizable = null;
  1320. removeClass(document.body, classes.noSelection);
  1321. cancelClick = true;
  1322. }
  1323. }
  1324. /**
  1325. * Resets any changes made by resizing the element to its original state.
  1326. *
  1327. * @param {Object} instance The dilog instance.
  1328. *
  1329. * @return {undefined}
  1330. */
  1331. function resetResize(instance){
  1332. var element = instance.elements.dialog;
  1333. if(element.style.maxWidth === 'none'){
  1334. //clear inline styles.
  1335. element.style.maxWidth = element.style.minWidth = element.style.width = element.style.height = element.style.minHeight = element.style.left = '';
  1336. //reset variables.
  1337. startingLeft = Number.Nan;
  1338. startingWidth = minWidth = handleOffset = 0;
  1339. }
  1340. }
  1341. /**
  1342. * Updates the dialog move behavior.
  1343. *
  1344. * @param {Object} instance The dilog instance.
  1345. * @param {Boolean} on True to add the behavior, removes it otherwise.
  1346. *
  1347. * @return {undefined}
  1348. */
  1349. function updateResizable(instance){
  1350. if(instance.setting('resizable')){
  1351. // add class
  1352. addClass(instance.elements.root, classes.resizable);
  1353. if(instance.isOpen()){
  1354. bindResizableEvents(instance);
  1355. }
  1356. }else{
  1357. //reset
  1358. resetResize(instance);
  1359. // remove class
  1360. removeClass(instance.elements.root, classes.resizable);
  1361. if(instance.isOpen()){
  1362. unbindResizableEvents(instance);
  1363. }
  1364. }
  1365. }
  1366. /**
  1367. * Reset move/resize on window resize.
  1368. *
  1369. * @param {Event} event window resize event object.
  1370. *
  1371. * @return {undefined}
  1372. */
  1373. function windowResize(/*event*/){
  1374. for(var x=0;x<openInstances.length;x+=1){
  1375. var instance = openInstances[x];
  1376. resetMove(instance);
  1377. resetResize(instance);
  1378. }
  1379. }
  1380. /**
  1381. * Bind dialogs events
  1382. *
  1383. * @param {Object} instance The dilog instance.
  1384. *
  1385. * @return {undefined}
  1386. */
  1387. function bindEvents(instance){
  1388. // if first dialog, hook body handlers
  1389. if(openInstances.length === 1){
  1390. //global
  1391. on( window, 'resize', windowResize);
  1392. on( document.body, 'keyup', keyupHandler);
  1393. on( document.body, 'focus', onReset);
  1394. //move
  1395. on( document.body, 'mousemove', move);
  1396. on( document.body, 'mouseup', endMove);
  1397. //resize
  1398. on( document.body, 'mousemove', resize);
  1399. on( document.body, 'mouseup', endResize);
  1400. }
  1401. // common events
  1402. on( instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler);
  1403. on( instance.elements.footer, 'click', instance.__internal.buttonsClickHandler);
  1404. on( instance.elements.reset[0], 'focus', instance.__internal.resetHandler);
  1405. on( instance.elements.reset[1], 'focus', instance.__internal.resetHandler);
  1406. //prevent handling key u…