dialog.js (20614B)
1 /*! 2 * Dialogs Manager v4.8.1 3 * https://github.com/kobizz/dialogs-manager 4 * 5 * Copyright Kobi Zaltzberg 6 * Released under the MIT license 7 * https://github.com/kobizz/dialogs-manager/blob/master/LICENSE.txt 8 */ 9 10 (function($, global) { 11 'use strict'; 12 13 /* 14 * Dialog Manager 15 */ 16 var DialogsManager = { 17 widgetsTypes: {}, 18 createWidgetType: function(typeName, properties, Parent) { 19 20 if (!Parent) { 21 Parent = this.Widget; 22 } 23 24 var WidgetType = function() { 25 26 Parent.apply(this, arguments); 27 }; 28 29 var prototype = WidgetType.prototype = new Parent(typeName); 30 31 prototype.types = prototype.types.concat([typeName]); 32 33 $.extend(prototype, properties); 34 35 prototype.constructor = WidgetType; 36 37 WidgetType.extend = function(typeName, properties) { 38 39 return DialogsManager.createWidgetType(typeName, properties, WidgetType); 40 }; 41 42 return WidgetType; 43 }, 44 addWidgetType: function(typeName, properties, Parent) { 45 46 if (properties && properties.prototype instanceof this.Widget) { 47 return this.widgetsTypes[typeName] = properties; 48 } 49 50 return this.widgetsTypes[typeName] = this.createWidgetType(typeName, properties, Parent); 51 }, 52 getWidgetType: function(widgetType) { 53 54 return this.widgetsTypes[widgetType]; 55 } 56 }; 57 58 /* 59 * Dialog Manager instances constructor 60 */ 61 DialogsManager.Instance = function() { 62 63 var self = this, 64 elements = {}, 65 settings = {}; 66 67 var initElements = function() { 68 69 elements.body = $('body'); 70 }; 71 72 var initSettings = function(options) { 73 74 var defaultSettings = { 75 classPrefix: 'dialog', 76 effects: { 77 show: 'fadeIn', 78 hide: 'fadeOut' 79 } 80 }; 81 82 $.extend(settings, defaultSettings, options); 83 }; 84 85 this.createWidget = function(widgetType, properties) { 86 87 var WidgetTypeConstructor = DialogsManager.getWidgetType(widgetType), 88 widget = new WidgetTypeConstructor(widgetType); 89 90 properties = properties || {}; 91 92 widget.init(self, properties); 93 94 return widget; 95 }; 96 97 this.getSettings = function(property) { 98 99 if (property) { 100 return settings[property]; 101 } 102 103 return Object.create(settings); 104 }; 105 106 this.init = function(settings) { 107 108 initSettings(settings); 109 110 initElements(); 111 112 return self; 113 }; 114 115 self.init(); 116 }; 117 118 /* 119 * Widget types constructor 120 */ 121 DialogsManager.Widget = function(widgetName) { 122 123 var self = this, 124 settings = {}, 125 events = {}, 126 elements = {}, 127 hideTimeOut = 0, 128 baseClosureMethods = ['refreshPosition']; 129 130 var bindEvents = function() { 131 132 var windows = [elements.window]; 133 134 if (elements.iframe) { 135 windows.push(jQuery(elements.iframe[0].contentWindow)); 136 } 137 138 windows.forEach(function(window) { 139 if (settings.hide.onEscKeyPress) { 140 window.on('keyup', onWindowKeyUp); 141 } 142 143 if (settings.hide.onOutsideClick) { 144 window[0].addEventListener('click', hideOnOutsideClick, true); 145 } 146 147 if (settings.hide.onOutsideContextMenu) { 148 window[0].addEventListener('contextmenu', hideOnOutsideClick, true); 149 } 150 151 if (settings.position.autoRefresh) { 152 window.on('resize', self.refreshPosition); 153 } 154 }); 155 156 if (settings.hide.onClick || settings.hide.onBackgroundClick) { 157 elements.widget.on('click', hideOnClick); 158 } 159 }; 160 161 var callEffect = function(intent, params) { 162 163 var effect = settings.effects[intent], 164 $widget = elements.widget; 165 166 if ($.isFunction(effect)) { 167 effect.apply($widget, params); 168 } else { 169 170 if ($widget[effect]) { 171 $widget[effect].apply($widget, params); 172 } else { 173 throw 'Reference Error: The effect ' + effect + ' not found'; 174 } 175 } 176 }; 177 178 var ensureClosureMethods = function() { 179 180 var closureMethodsNames = baseClosureMethods.concat(self.getClosureMethods()); 181 182 $.each(closureMethodsNames, function() { 183 184 var methodName = this, 185 oldMethod = self[methodName]; 186 187 self[methodName] = function() { 188 189 oldMethod.apply(self, arguments); 190 }; 191 }); 192 }; 193 194 var fixIframePosition = function(position) { 195 if (! position.my) { 196 return; 197 } 198 199 var horizontalOffsetRegex = /left|right/, 200 extraOffsetRegex = /([+-]\d+)?$/, 201 iframeOffset = elements.iframe.offset(), 202 iframeWindow = elements.iframe[0].contentWindow, 203 myParts = position.my.split(' '), 204 fixedParts = []; 205 206 if (myParts.length === 1) { 207 if (horizontalOffsetRegex.test(myParts[0])) { 208 myParts.push('center'); 209 } else { 210 myParts.unshift('center'); 211 } 212 } 213 214 myParts.forEach(function(part, index) { 215 var fixedPart = part.replace(extraOffsetRegex, function(partOffset) { 216 partOffset = +partOffset || 0; 217 218 if (! index) { 219 partOffset += iframeOffset.left - iframeWindow.scrollX; 220 } else { 221 partOffset += iframeOffset.top - iframeWindow.scrollY; 222 } 223 224 if (partOffset >= 0) { 225 partOffset = '+' + partOffset; 226 } 227 228 return partOffset; 229 }); 230 231 fixedParts.push(fixedPart); 232 }); 233 234 position.my = fixedParts.join(' '); 235 }; 236 237 var hideOnClick = function(event) { 238 239 if (isContextMenuClickEvent(event)) { 240 return; 241 } 242 243 if (settings.hide.onClick) { 244 245 if ($(event.target).closest(settings.selectors.preventClose).length) { 246 return; 247 } 248 } else if (event.target !== this) { 249 return; 250 } 251 252 self.hide(); 253 }; 254 255 var isIgnoredTarget = function(event) { 256 257 if (! settings.hide.ignore) { 258 return false; 259 } 260 261 return !! $(event.target).closest(settings.hide.ignore).length; 262 }; 263 264 var hideOnOutsideClick = function(event) { 265 266 if (isContextMenuClickEvent(event) || $(event.target).closest(elements.widget).length || isIgnoredTarget(event)) { 267 return; 268 } 269 270 self.hide(); 271 }; 272 273 var initElements = function() { 274 275 self.addElement('widget'); 276 277 self.addElement('header'); 278 279 self.addElement('message'); 280 281 self.addElement('window', window); 282 283 self.addElement('body', document.body); 284 285 self.addElement('container', settings.container); 286 287 if (settings.iframe) { 288 self.addElement('iframe', settings.iframe); 289 } 290 291 if (settings.closeButton) { 292 if ( settings.closeButtonClass ) { 293 // Backwards compatibility 294 settings.closeButtonOptions.iconClass = settings.closeButtonClass; 295 } 296 297 const $button = $('<div>', settings.closeButtonOptions.attributes), 298 $buttonIcon = $('<i>', {class: settings.closeButtonOptions.iconClass}); 299 300 $button.append($buttonIcon); 301 302 self.addElement('closeButton', $button); 303 } 304 305 var id = self.getSettings('id'); 306 307 if (id) { 308 self.setID(id); 309 } 310 311 var classes = []; 312 313 $.each(self.types, function() { 314 classes.push(settings.classes.globalPrefix + '-type-' + this); 315 }); 316 317 classes.push(self.getSettings('className')); 318 319 elements.widget.addClass(classes.join(' ')); 320 }; 321 322 var initSettings = function(parent, userSettings) { 323 324 var parentSettings = $.extend(true, {}, parent.getSettings()); 325 326 settings = { 327 headerMessage: '', 328 message: '', 329 effects: parentSettings.effects, 330 classes: { 331 globalPrefix: parentSettings.classPrefix, 332 prefix: parentSettings.classPrefix + '-' + widgetName, 333 preventScroll: parentSettings.classPrefix + '-prevent-scroll' 334 }, 335 selectors: { 336 preventClose: '.' + parentSettings.classPrefix + '-prevent-close' 337 }, 338 container: 'body', 339 preventScroll: false, 340 iframe: null, 341 closeButton: false, 342 closeButtonOptions: { 343 iconClass: parentSettings.classPrefix + '-close-button-icon', 344 attributes: {}, 345 }, 346 position: { 347 element: 'widget', 348 my: 'center', 349 at: 'center', 350 enable: true, 351 autoRefresh: false 352 }, 353 hide: { 354 auto: false, 355 autoDelay: 5000, 356 onClick: false, 357 onOutsideClick: true, 358 onOutsideContextMenu: false, 359 onBackgroundClick: true, 360 onEscKeyPress: true, 361 ignore: '' 362 } 363 }; 364 365 $.extend(true, settings, self.getDefaultSettings(), userSettings); 366 367 initSettingsEvents(); 368 }; 369 370 var initSettingsEvents = function() { 371 372 $.each(settings, function(settingKey) { 373 374 var eventName = settingKey.match(/^on([A-Z].*)/); 375 376 if (!eventName) { 377 return; 378 } 379 380 eventName = eventName[1].charAt(0).toLowerCase() + eventName[1].slice(1); 381 382 self.on(eventName, this); 383 }); 384 }; 385 386 var isContextMenuClickEvent = function(event) { 387 // Firefox fires `click` event on every `contextmenu` event. 388 return event.type === 'click' && event.button === 2; 389 }; 390 391 var normalizeClassName = function(name) { 392 393 return name.replace(/([a-z])([A-Z])/g, function() { 394 395 return arguments[1] + '-' + arguments[2].toLowerCase(); 396 }); 397 }; 398 399 var onWindowKeyUp = function(event) { 400 var ESC_KEY = 27, 401 keyCode = event.which; 402 403 if (ESC_KEY === keyCode) { 404 self.hide(); 405 } 406 }; 407 408 var unbindEvents = function() { 409 410 var windows = [elements.window]; 411 412 if (elements.iframe) { 413 windows.push(jQuery(elements.iframe[0].contentWindow)); 414 } 415 416 windows.forEach(function(window) { 417 if (settings.hide.onEscKeyPress) { 418 window.off('keyup', onWindowKeyUp); 419 } 420 421 if (settings.hide.onOutsideClick) { 422 window[0].removeEventListener('click', hideOnOutsideClick, true); 423 } 424 425 if (settings.hide.onOutsideContextMenu) { 426 window[0].removeEventListener('contextmenu', hideOnOutsideClick, true); 427 } 428 429 if (settings.position.autoRefresh) { 430 window.off('resize', self.refreshPosition); 431 } 432 }); 433 434 if (settings.hide.onClick || settings.hide.onBackgroundClick) { 435 elements.widget.off('click', hideOnClick); 436 } 437 }; 438 439 this.addElement = function(name, element, classes) { 440 441 var $newElement = elements[name] = $(element || '<div>'), 442 normalizedName = normalizeClassName(name); 443 444 classes = classes ? classes + ' ' : ''; 445 446 classes += settings.classes.globalPrefix + '-' + normalizedName; 447 448 classes += ' ' + settings.classes.prefix + '-' + normalizedName; 449 450 $newElement.addClass(classes); 451 452 return $newElement; 453 }; 454 455 this.destroy = function() { 456 457 unbindEvents(); 458 459 elements.widget.remove(); 460 461 self.trigger('destroy'); 462 463 return self; 464 }; 465 466 this.getElements = function(item) { 467 468 return item ? elements[item] : elements; 469 }; 470 471 this.getSettings = function(setting) { 472 473 var copy = Object.create(settings); 474 475 if (setting) { 476 return copy[setting]; 477 } 478 479 return copy; 480 }; 481 482 this.hide = function() { 483 484 if (! self.isVisible()) { 485 return; 486 } 487 488 clearTimeout(hideTimeOut); 489 490 callEffect('hide', arguments); 491 492 unbindEvents(); 493 494 if (settings.preventScroll) { 495 self.getElements('body').removeClass(settings.classes.preventScroll); 496 } 497 498 self.trigger('hide'); 499 500 return self; 501 }; 502 503 this.init = function(parent, properties) { 504 505 if (!(parent instanceof DialogsManager.Instance)) { 506 throw 'The ' + self.widgetName + ' must to be initialized from an instance of DialogsManager.Instance'; 507 } 508 509 ensureClosureMethods(); 510 511 self.trigger('init', properties); 512 513 initSettings(parent, properties); 514 515 initElements(); 516 517 self.buildWidget(); 518 519 self.attachEvents(); 520 521 self.trigger('ready'); 522 523 return self; 524 }; 525 526 this.isVisible = function() { 527 528 return elements.widget.is(':visible'); 529 }; 530 531 this.on = function(eventName, callback) { 532 533 if ('object' === typeof eventName) { 534 $.each(eventName, function(singleEventName) { 535 self.on(singleEventName, this); 536 }); 537 538 return self; 539 } 540 541 var eventNames = eventName.split(' '); 542 543 eventNames.forEach(function(singleEventName) { 544 if (!events[singleEventName]) { 545 events[singleEventName] = []; 546 } 547 548 events[singleEventName].push(callback); 549 }); 550 551 return self; 552 }; 553 554 this.off = function(eventName, callback) { 555 556 if (! events[ eventName ]) { 557 return self; 558 } 559 560 if (! callback) { 561 delete events[eventName]; 562 563 return self; 564 } 565 566 var callbackIndex = events[eventName].indexOf(callback); 567 568 if (-1 !== callbackIndex) { 569 events[eventName].splice(callbackIndex, 1); 570 } 571 572 return self; 573 }; 574 575 this.refreshPosition = function() { 576 577 if (! settings.position.enable) { 578 return; 579 } 580 581 var position = $.extend({}, settings.position); 582 583 if (elements[position.of]) { 584 position.of = elements[position.of]; 585 } 586 587 if (! position.of) { 588 position.of = window; 589 } 590 591 if (settings.iframe) { 592 fixIframePosition(position); 593 } 594 595 elements[position.element].position(position); 596 }; 597 598 this.setID = function(id) { 599 600 elements.widget.attr('id', id); 601 602 return self; 603 }; 604 605 this.setHeaderMessage = function(message) { 606 607 self.getElements('header').html(message); 608 609 return self; 610 }; 611 612 this.setMessage = function(message) { 613 614 elements.message.html(message); 615 616 return self; 617 }; 618 619 this.setSettings = function(key, value) { 620 621 if (jQuery.isPlainObject(value)) { 622 $.extend(true, settings[key], value); 623 } else { 624 settings[key] = value; 625 } 626 627 return self; 628 }; 629 630 this.show = function() { 631 632 clearTimeout(hideTimeOut); 633 634 elements.widget.appendTo(elements.container).hide(); 635 636 callEffect('show', arguments); 637 638 self.refreshPosition(); 639 640 if (settings.hide.auto) { 641 hideTimeOut = setTimeout(self.hide, settings.hide.autoDelay); 642 } 643 644 bindEvents(); 645 646 if (settings.preventScroll) { 647 self.getElements('body').addClass(settings.classes.preventScroll); 648 } 649 650 self.trigger('show'); 651 652 return self; 653 }; 654 655 this.trigger = function(eventName, params) { 656 657 var methodName = 'on' + eventName[0].toUpperCase() + eventName.slice(1); 658 659 if (self[methodName]) { 660 self[methodName](params); 661 } 662 663 var callbacks = events[eventName]; 664 665 if (!callbacks) { 666 return; 667 } 668 669 $.each(callbacks, function(index, callback) { 670 671 callback.call(self, params); 672 }); 673 674 return self; 675 }; 676 }; 677 678 DialogsManager.Widget.prototype.types = []; 679 680 // Inheritable widget methods 681 DialogsManager.Widget.prototype.buildWidget = function() { 682 683 var elements = this.getElements(), 684 settings = this.getSettings(); 685 686 elements.widget.append(elements.header, elements.message); 687 688 this.setHeaderMessage(settings.headerMessage); 689 690 this.setMessage(settings.message); 691 692 if (this.getSettings('closeButton')) { 693 elements.widget.prepend(elements.closeButton); 694 } 695 }; 696 697 DialogsManager.Widget.prototype.attachEvents = function() { 698 699 var self = this; 700 701 if (self.getSettings('closeButton')) { 702 self.getElements('closeButton').on('click', function() { 703 self.hide(); 704 }); 705 } 706 }; 707 708 DialogsManager.Widget.prototype.getDefaultSettings = function() { 709 710 return {}; 711 }; 712 713 DialogsManager.Widget.prototype.getClosureMethods = function() { 714 715 return []; 716 }; 717 718 DialogsManager.Widget.prototype.onHide = function() { 719 }; 720 721 DialogsManager.Widget.prototype.onShow = function() { 722 }; 723 724 DialogsManager.Widget.prototype.onInit = function() { 725 }; 726 727 DialogsManager.Widget.prototype.onReady = function() { 728 }; 729 730 DialogsManager.widgetsTypes.simple = DialogsManager.Widget; 731 732 DialogsManager.addWidgetType('buttons', { 733 activeKeyUp: function(event) { 734 735 var TAB_KEY = 9; 736 737 if (event.which === TAB_KEY) { 738 event.preventDefault(); 739 } 740 741 if (this.hotKeys[event.which]) { 742 this.hotKeys[event.which](this); 743 } 744 }, 745 activeKeyDown: function(event) { 746 747 if (!this.focusedButton) { 748 return; 749 } 750 751 var TAB_KEY = 9; 752 753 if (event.which === TAB_KEY) { 754 event.preventDefault(); 755 756 var currentButtonIndex = this.focusedButton.index(), 757 nextButtonIndex; 758 759 if (event.shiftKey) { 760 761 nextButtonIndex = currentButtonIndex - 1; 762 763 if (nextButtonIndex < 0) { 764 nextButtonIndex = this.buttons.length - 1; 765 } 766 } else { 767 768 nextButtonIndex = currentButtonIndex + 1; 769 770 if (nextButtonIndex >= this.buttons.length) { 771 nextButtonIndex = 0; 772 } 773 } 774 775 this.focusedButton = this.buttons[nextButtonIndex].focus(); 776 } 777 }, 778 addButton: function(options) { 779 780 var self = this, 781 settings = self.getSettings(), 782 buttonSettings = jQuery.extend(settings.button, options); 783 784 var classes = options.classes ? options.classes + ' ' : ''; 785 786 classes += settings.classes.globalPrefix + '-button'; 787 788 var $button = self.addElement(options.name, $('<' + buttonSettings.tag + '>').html(options.text), classes); 789 790 self.buttons.push($button); 791 792 var buttonFn = function() { 793 794 if (settings.hide.onButtonClick) { 795 self.hide(); 796 } 797 798 if ($.isFunction(options.callback)) { 799 options.callback.call(this, self); 800 } 801 }; 802 803 $button.on('click', buttonFn); 804 805 if (options.hotKey) { 806 this.hotKeys[options.hotKey] = buttonFn; 807 } 808 809 this.getElements('buttonsWrapper').append($button); 810 811 if (options.focus) { 812 this.focusedButton = $button; 813 } 814 815 return self; 816 }, 817 bindHotKeys: function() { 818 819 this.getElements('window').on({ 820 keyup: this.activeKeyUp, 821 keydown: this.activeKeyDown 822 }); 823 }, 824 buildWidget: function() { 825 826 DialogsManager.Widget.prototype.buildWidget.apply(this, arguments); 827 828 var $buttonsWrapper = this.addElement('buttonsWrapper'); 829 830 this.getElements('widget').append($buttonsWrapper); 831 }, 832 getClosureMethods: function() { 833 834 return [ 835 'activeKeyUp', 836 'activeKeyDown' 837 ]; 838 }, 839 getDefaultSettings: function() { 840 841 return { 842 hide: { 843 onButtonClick: true 844 }, 845 button: { 846 tag: 'button' 847 } 848 }; 849 }, 850 onHide: function() { 851 852 this.unbindHotKeys(); 853 }, 854 onInit: function() { 855 856 this.buttons = []; 857 858 this.hotKeys = {}; 859 860 this.focusedButton = null; 861 }, 862 onShow: function() { 863 864 this.bindHotKeys(); 865 866 if (!this.focusedButton) { 867 this.focusedButton = this.buttons[0]; 868 } 869 870 if (this.focusedButton) { 871 this.focusedButton.focus(); 872 } 873 }, 874 unbindHotKeys: function() { 875 876 this.getElements('window').off({ 877 keyup: this.activeKeyUp, 878 keydown: this.activeKeyDown 879 }); 880 } 881 }); 882 883 DialogsManager.addWidgetType('lightbox', DialogsManager.getWidgetType('buttons').extend('lightbox', { 884 getDefaultSettings: function() { 885 886 var settings = DialogsManager.getWidgetType('buttons').prototype.getDefaultSettings.apply(this, arguments); 887 888 return $.extend(true, settings, { 889 contentWidth: 'auto', 890 contentHeight: 'auto', 891 position: { 892 element: 'widgetContent', 893 of: 'widget', 894 autoRefresh: true 895 } 896 }); 897 }, 898 buildWidget: function() { 899 900 DialogsManager.getWidgetType('buttons').prototype.buildWidget.apply(this, arguments); 901 902 var $widgetContent = this.addElement('widgetContent'), 903 elements = this.getElements(); 904 905 $widgetContent.append(elements.header, elements.message, elements.buttonsWrapper); 906 907 elements.widget.html($widgetContent); 908 909 if (elements.closeButton) { 910 $widgetContent.prepend(elements.closeButton); 911 } 912 }, 913 onReady: function() { 914 915 var elements = this.getElements(), 916 settings = this.getSettings(); 917 918 if ('auto' !== settings.contentWidth) { 919 elements.message.width(settings.contentWidth); 920 } 921 922 if ('auto' !== settings.contentHeight) { 923 elements.message.height(settings.contentHeight); 924 } 925 } 926 })); 927 928 DialogsManager.addWidgetType('confirm', DialogsManager.getWidgetType('lightbox').extend('confirm', { 929 onReady: function() { 930 931 DialogsManager.getWidgetType('lightbox').prototype.onReady.apply(this, arguments); 932 933 var strings = this.getSettings('strings'), 934 isDefaultCancel = this.getSettings('defaultOption') === 'cancel'; 935 936 this.addButton({ 937 name: 'cancel', 938 text: strings.cancel, 939 callback: function(widget) { 940 941 widget.trigger('cancel'); 942 }, 943 focus: isDefaultCancel 944 }); 945 946 this.addButton({ 947 name: 'ok', 948 text: strings.confirm, 949 callback: function(widget) { 950 951 widget.trigger('confirm'); 952 }, 953 focus: !isDefaultCancel 954 }); 955 }, 956 getDefaultSettings: function() { 957 958 var settings = DialogsManager.getWidgetType('lightbox').prototype.getDefaultSettings.apply(this, arguments); 959 960 settings.strings = { 961 confirm: 'OK', 962 cancel: 'Cancel' 963 }; 964 965 settings.defaultOption = 'cancel'; 966 967 return settings; 968 } 969 })); 970 971 DialogsManager.addWidgetType('alert', DialogsManager.getWidgetType('lightbox').extend('alert', { 972 onReady: function() { 973 974 DialogsManager.getWidgetType('lightbox').prototype.onReady.apply(this, arguments); 975 976 var strings = this.getSettings('strings'); 977 978 this.addButton({ 979 name: 'ok', 980 text: strings.confirm, 981 callback: function(widget) { 982 983 widget.trigger('confirm'); 984 } 985 }); 986 }, 987 getDefaultSettings: function() { 988 989 var settings = DialogsManager.getWidgetType('lightbox').prototype.getDefaultSettings.apply(this, arguments); 990 991 settings.strings = { 992 confirm: 'OK' 993 }; 994 995 return settings; 996 } 997 })); 998 999 // Exporting the DialogsManager variable to global 1000 global.DialogsManager = DialogsManager; 1001 })( 1002 typeof jQuery !== 'undefined' ? jQuery : typeof require === 'function' && require('jquery'), 1003 typeof module !== 'undefined' ? module.exports : window 1004 );