github.com/fanux/shipyard@v0.0.0-20161009071005-6515ce223235/controller/static/semantic/src/definitions/modules/modal.js (about) 1 /*! 2 * # Semantic UI - Modal 3 * http://github.com/semantic-org/semantic-ui/ 4 * 5 * 6 * Copyright 2014 Contributors 7 * Released under the MIT license 8 * http://opensource.org/licenses/MIT 9 * 10 */ 11 12 ;(function ( $, window, document, undefined ) { 13 14 "use strict"; 15 16 $.fn.modal = function(parameters) { 17 var 18 $allModules = $(this), 19 $window = $(window), 20 $document = $(document), 21 $body = $('body'), 22 23 moduleSelector = $allModules.selector || '', 24 25 time = new Date().getTime(), 26 performance = [], 27 28 query = arguments[0], 29 methodInvoked = (typeof query == 'string'), 30 queryArguments = [].slice.call(arguments, 1), 31 32 requestAnimationFrame = window.requestAnimationFrame 33 || window.mozRequestAnimationFrame 34 || window.webkitRequestAnimationFrame 35 || window.msRequestAnimationFrame 36 || function(callback) { setTimeout(callback, 0); }, 37 38 returnedValue 39 ; 40 41 $allModules 42 .each(function() { 43 var 44 settings = ( $.isPlainObject(parameters) ) 45 ? $.extend(true, {}, $.fn.modal.settings, parameters) 46 : $.extend({}, $.fn.modal.settings), 47 48 selector = settings.selector, 49 className = settings.className, 50 namespace = settings.namespace, 51 error = settings.error, 52 53 eventNamespace = '.' + namespace, 54 moduleNamespace = 'module-' + namespace, 55 56 $module = $(this), 57 $context = $(settings.context), 58 $close = $module.find(selector.close), 59 60 $allModals, 61 $otherModals, 62 $focusedElement, 63 $dimmable, 64 $dimmer, 65 66 element = this, 67 instance = $module.data(moduleNamespace), 68 69 elementNamespace, 70 id, 71 observer, 72 module 73 ; 74 module = { 75 76 initialize: function() { 77 module.verbose('Initializing dimmer', $context); 78 79 module.create.id(); 80 module.create.dimmer(); 81 module.refreshModals(); 82 83 module.verbose('Attaching close events', $close); 84 module.bind.events(); 85 module.observeChanges(); 86 module.instantiate(); 87 }, 88 89 instantiate: function() { 90 module.verbose('Storing instance of modal'); 91 instance = module; 92 $module 93 .data(moduleNamespace, instance) 94 ; 95 }, 96 97 create: { 98 dimmer: function() { 99 var 100 defaultSettings = { 101 debug : settings.debug, 102 dimmerName : 'modals', 103 duration : { 104 show : settings.duration, 105 hide : settings.duration 106 } 107 }, 108 dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) 109 ; 110 if($.fn.dimmer === undefined) { 111 module.error(error.dimmer); 112 return; 113 } 114 module.debug('Creating dimmer with settings', dimmerSettings); 115 $dimmable = $context.dimmer(dimmerSettings); 116 if(settings.detachable) { 117 module.verbose('Modal is detachable, moving content into dimmer'); 118 $dimmable.dimmer('add content', $module); 119 } 120 $dimmer = $dimmable.dimmer('get dimmer'); 121 }, 122 id: function() { 123 id = (Math.random().toString(16) + '000000000').substr(2,8); 124 elementNamespace = '.' + id; 125 module.verbose('Creating unique id for element', id); 126 } 127 }, 128 129 destroy: function() { 130 module.verbose('Destroying previous modal'); 131 $module 132 .removeData(moduleNamespace) 133 .off(eventNamespace) 134 ; 135 $window.off(elementNamespace); 136 $close.off(eventNamespace); 137 $context.dimmer('destroy'); 138 }, 139 140 observeChanges: function() { 141 if('MutationObserver' in window) { 142 observer = new MutationObserver(function(mutations) { 143 module.debug('DOM tree modified, refreshing'); 144 module.refresh(); 145 }); 146 observer.observe(element, { 147 childList : true, 148 subtree : true 149 }); 150 module.debug('Setting up mutation observer', observer); 151 } 152 }, 153 154 refresh: function() { 155 module.remove.scrolling(); 156 module.cacheSizes(); 157 module.set.screenHeight(); 158 module.set.type(); 159 module.set.position(); 160 }, 161 162 refreshModals: function() { 163 $otherModals = $module.siblings(selector.modal); 164 $allModals = $otherModals.add($module); 165 }, 166 167 attachEvents: function(selector, event) { 168 var 169 $toggle = $(selector) 170 ; 171 event = $.isFunction(module[event]) 172 ? module[event] 173 : module.toggle 174 ; 175 if($toggle.length > 0) { 176 module.debug('Attaching modal events to element', selector, event); 177 $toggle 178 .off(eventNamespace) 179 .on('click' + eventNamespace, event) 180 ; 181 } 182 else { 183 module.error(error.notFound, selector); 184 } 185 }, 186 187 bind: { 188 events: function() { 189 $close.on('click' + eventNamespace, module.event.close); 190 $window.on('resize' + elementNamespace, module.event.resize); 191 } 192 }, 193 194 get: { 195 id: function() { 196 return (Math.random().toString(16) + '000000000').substr(2,8); 197 } 198 }, 199 200 event: { 201 close: function() { 202 module.verbose('Closing element pressed'); 203 if( $(this).is(selector.approve) ) { 204 if(settings.onApprove.call(element) !== false) { 205 module.hide(); 206 } 207 else { 208 module.verbose('Approve callback returned false cancelling hide'); 209 } 210 } 211 else if( $(this).is(selector.deny) ) { 212 if(settings.onDeny.call(element) !== false) { 213 module.hide(); 214 } 215 else { 216 module.verbose('Deny callback returned false cancelling hide'); 217 } 218 } 219 else { 220 module.hide(); 221 } 222 }, 223 click: function(event) { 224 if( $(event.target).closest($module).length === 0 ) { 225 module.debug('Dimmer clicked, hiding all modals'); 226 if( module.is.active() ) { 227 module.remove.clickaway(); 228 if(settings.allowMultiple) { 229 module.hide(); 230 } 231 else { 232 module.hideAll(); 233 } 234 } 235 } 236 }, 237 debounce: function(method, delay) { 238 clearTimeout(module.timer); 239 module.timer = setTimeout(method, delay); 240 }, 241 keyboard: function(event) { 242 var 243 keyCode = event.which, 244 escapeKey = 27 245 ; 246 if(keyCode == escapeKey) { 247 if(settings.closable) { 248 module.debug('Escape key pressed hiding modal'); 249 module.hide(); 250 } 251 else { 252 module.debug('Escape key pressed, but closable is set to false'); 253 } 254 event.preventDefault(); 255 } 256 }, 257 resize: function() { 258 if( $dimmable.dimmer('is active') ) { 259 requestAnimationFrame(module.refresh); 260 } 261 } 262 }, 263 264 toggle: function() { 265 if( module.is.active() || module.is.animating() ) { 266 module.hide(); 267 } 268 else { 269 module.show(); 270 } 271 }, 272 273 show: function(callback) { 274 callback = $.isFunction(callback) 275 ? callback 276 : function(){} 277 ; 278 module.refreshModals(); 279 module.showModal(callback); 280 }, 281 282 hide: function(callback) { 283 callback = $.isFunction(callback) 284 ? callback 285 : function(){} 286 ; 287 module.refreshModals(); 288 module.hideModal(callback); 289 }, 290 291 showModal: function(callback) { 292 callback = $.isFunction(callback) 293 ? callback 294 : function(){} 295 ; 296 if( module.is.animating() || !module.is.active() ) { 297 298 module.showDimmer(); 299 module.cacheSizes(); 300 module.set.position(); 301 module.set.screenHeight(); 302 module.set.type(); 303 module.set.clickaway(); 304 305 if( !settings.allowMultiple && $otherModals.filter('.' + className.active).length > 0) { 306 module.debug('Other modals visible, queueing show animation'); 307 module.hideOthers(module.showModal); 308 } 309 else { 310 settings.onShow.call(element); 311 if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { 312 module.debug('Showing modal with css animations'); 313 $module 314 .transition({ 315 debug : settings.debug, 316 animation : settings.transition + ' in', 317 queue : settings.queue, 318 duration : settings.duration, 319 useFailSafe : true, 320 onComplete : function() { 321 settings.onVisible.apply(element); 322 module.add.keyboardShortcuts(); 323 module.save.focus(); 324 module.set.active(); 325 module.set.autofocus(); 326 callback(); 327 } 328 }) 329 ; 330 } 331 else { 332 module.debug('Showing modal with javascript'); 333 $module 334 .fadeIn(settings.duration, settings.easing, function() { 335 settings.onVisible.apply(element); 336 module.add.keyboardShortcuts(); 337 module.save.focus(); 338 module.set.active(); 339 callback(); 340 }) 341 ; 342 } 343 } 344 } 345 else { 346 module.debug('Modal is already visible'); 347 } 348 }, 349 350 hideModal: function(callback, keepDimmed) { 351 callback = $.isFunction(callback) 352 ? callback 353 : function(){} 354 ; 355 module.debug('Hiding modal'); 356 settings.onHide.call(element); 357 358 if( module.is.animating() || module.is.active() ) { 359 if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { 360 module.remove.active(); 361 $module 362 .transition({ 363 debug : settings.debug, 364 animation : settings.transition + ' out', 365 queue : settings.queue, 366 duration : settings.duration, 367 useFailSafe : true, 368 onStart : function() { 369 if(!module.othersActive() && !keepDimmed) { 370 module.hideDimmer(); 371 } 372 module.remove.keyboardShortcuts(); 373 }, 374 onComplete : function() { 375 settings.onHidden.call(element); 376 module.restore.focus(); 377 callback(); 378 } 379 }) 380 ; 381 } 382 else { 383 module.remove.active(); 384 if( !module.othersActive() ) { 385 module.hideDimmer(); 386 } 387 module.remove.keyboardShortcuts(); 388 $module 389 .fadeOut(settings.duration, settings.easing, function() { 390 settings.onHidden.call(element); 391 module.restore.focus(); 392 callback(); 393 }) 394 ; 395 } 396 } 397 }, 398 399 showDimmer: function() { 400 if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) { 401 module.debug('Showing dimmer'); 402 $dimmable.dimmer('show'); 403 } 404 else { 405 module.debug('Dimmer already visible'); 406 } 407 }, 408 409 hideDimmer: function() { 410 if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) { 411 $dimmable.dimmer('hide', function() { 412 if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { 413 module.remove.clickaway(); 414 module.remove.screenHeight(); 415 } 416 }); 417 } 418 else { 419 module.debug('Dimmer is not visible cannot hide'); 420 return; 421 } 422 }, 423 424 hideAll: function(callback) { 425 var 426 $visibleModals = $allModals.filter(':visible') 427 ; 428 callback = $.isFunction(callback) 429 ? callback 430 : function(){} 431 ; 432 if( $visibleModals.length > 0 ) { 433 module.debug('Hiding all visible modals'); 434 module.hideDimmer(); 435 $visibleModals 436 .modal('hide modal', callback) 437 ; 438 } 439 }, 440 441 hideOthers: function(callback) { 442 var 443 $visibleModals = $otherModals.filter(':visible') 444 ; 445 callback = $.isFunction(callback) 446 ? callback 447 : function(){} 448 ; 449 if( $visibleModals.length > 0 ) { 450 module.debug('Hiding other modals', $otherModals); 451 $visibleModals 452 .modal('hide modal', callback, true) 453 ; 454 } 455 }, 456 457 othersActive: function() { 458 return ($otherModals.filter('.' + className.active).length > 0); 459 }, 460 461 add: { 462 keyboardShortcuts: function() { 463 module.verbose('Adding keyboard shortcuts'); 464 $document 465 .on('keyup' + eventNamespace, module.event.keyboard) 466 ; 467 } 468 }, 469 470 save: { 471 focus: function() { 472 $focusedElement = $(document.activeElement).blur(); 473 } 474 }, 475 476 restore: { 477 focus: function() { 478 if($focusedElement && $focusedElement.length > 0) { 479 $focusedElement.focus(); 480 } 481 } 482 }, 483 484 remove: { 485 active: function() { 486 $module.removeClass(className.active); 487 }, 488 clickaway: function() { 489 if(settings.closable) { 490 $dimmer 491 .off('click' + elementNamespace) 492 ; 493 } 494 }, 495 screenHeight: function() { 496 if(module.cache.height > module.cache.pageHeight) { 497 module.debug('Removing page height'); 498 $body 499 .css('height', '') 500 ; 501 } 502 }, 503 keyboardShortcuts: function() { 504 module.verbose('Removing keyboard shortcuts'); 505 $document 506 .off('keyup' + eventNamespace) 507 ; 508 }, 509 scrolling: function() { 510 $dimmable.removeClass(className.scrolling); 511 $module.removeClass(className.scrolling); 512 } 513 }, 514 515 cacheSizes: function() { 516 var 517 modalHeight = $module.outerHeight() 518 ; 519 if(module.cache === undefined || modalHeight !== 0) { 520 module.cache = { 521 pageHeight : $(document).outerHeight(), 522 height : modalHeight + settings.offset, 523 contextHeight : (settings.context == 'body') 524 ? $(window).height() 525 : $dimmable.height() 526 }; 527 } 528 module.debug('Caching modal and container sizes', module.cache); 529 }, 530 531 can: { 532 fit: function() { 533 return ( ( module.cache.height + (settings.padding * 2) ) < module.cache.contextHeight); 534 } 535 }, 536 537 is: { 538 active: function() { 539 return $module.hasClass(className.active); 540 }, 541 animating: function() { 542 return $module.transition('is supported') 543 ? $module.transition('is animating') 544 : $module.is(':visible') 545 ; 546 }, 547 scrolling: function() { 548 return $dimmable.hasClass(className.scrolling); 549 }, 550 modernBrowser: function() { 551 // appName for IE11 reports 'Netscape' can no longer use 552 return !(window.ActiveXObject || "ActiveXObject" in window); 553 } 554 }, 555 556 set: { 557 autofocus: function() { 558 if(settings.autofocus) { 559 var 560 $inputs = $module.find(':input:visible'), 561 $autofocus = $inputs.filter('[autofocus]'), 562 $input = ($autofocus.length > 0) 563 ? $autofocus 564 : $inputs 565 ; 566 $input.first().focus(); 567 } 568 }, 569 clickaway: function() { 570 if(settings.closable) { 571 $dimmer 572 .on('click' + elementNamespace, module.event.click) 573 ; 574 } 575 }, 576 screenHeight: function() { 577 if( module.can.fit() ) { 578 $body.css('height', ''); 579 } 580 else { 581 module.debug('Modal is taller than page content, resizing page height'); 582 $body 583 .css('height', module.cache.height + (settings.padding / 2) ) 584 ; 585 } 586 }, 587 active: function() { 588 $module.addClass(className.active); 589 }, 590 scrolling: function() { 591 $dimmable.addClass(className.scrolling); 592 $module.addClass(className.scrolling); 593 }, 594 type: function() { 595 if(module.can.fit()) { 596 module.verbose('Modal fits on screen'); 597 if(!module.othersActive) { 598 module.remove.scrolling(); 599 } 600 } 601 else { 602 module.verbose('Modal cannot fit on screen setting to scrolling'); 603 module.set.scrolling(); 604 } 605 }, 606 position: function() { 607 module.verbose('Centering modal on page', module.cache); 608 if(module.can.fit()) { 609 $module 610 .css({ 611 top: '', 612 marginTop: -(module.cache.height / 2) 613 }) 614 ; 615 } 616 else { 617 $module 618 .css({ 619 marginTop : '', 620 top : $document.scrollTop() 621 }) 622 ; 623 } 624 } 625 }, 626 627 setting: function(name, value) { 628 module.debug('Changing setting', name, value); 629 if( $.isPlainObject(name) ) { 630 $.extend(true, settings, name); 631 } 632 else if(value !== undefined) { 633 settings[name] = value; 634 } 635 else { 636 return settings[name]; 637 } 638 }, 639 internal: function(name, value) { 640 if( $.isPlainObject(name) ) { 641 $.extend(true, module, name); 642 } 643 else if(value !== undefined) { 644 module[name] = value; 645 } 646 else { 647 return module[name]; 648 } 649 }, 650 debug: function() { 651 if(settings.debug) { 652 if(settings.performance) { 653 module.performance.log(arguments); 654 } 655 else { 656 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 657 module.debug.apply(console, arguments); 658 } 659 } 660 }, 661 verbose: function() { 662 if(settings.verbose && settings.debug) { 663 if(settings.performance) { 664 module.performance.log(arguments); 665 } 666 else { 667 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 668 module.verbose.apply(console, arguments); 669 } 670 } 671 }, 672 error: function() { 673 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 674 module.error.apply(console, arguments); 675 }, 676 performance: { 677 log: function(message) { 678 var 679 currentTime, 680 executionTime, 681 previousTime 682 ; 683 if(settings.performance) { 684 currentTime = new Date().getTime(); 685 previousTime = time || currentTime; 686 executionTime = currentTime - previousTime; 687 time = currentTime; 688 performance.push({ 689 'Name' : message[0], 690 'Arguments' : [].slice.call(message, 1) || '', 691 'Element' : element, 692 'Execution Time' : executionTime 693 }); 694 } 695 clearTimeout(module.performance.timer); 696 module.performance.timer = setTimeout(module.performance.display, 100); 697 }, 698 display: function() { 699 var 700 title = settings.name + ':', 701 totalTime = 0 702 ; 703 time = false; 704 clearTimeout(module.performance.timer); 705 $.each(performance, function(index, data) { 706 totalTime += data['Execution Time']; 707 }); 708 title += ' ' + totalTime + 'ms'; 709 if(moduleSelector) { 710 title += ' \'' + moduleSelector + '\''; 711 } 712 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 713 console.groupCollapsed(title); 714 if(console.table) { 715 console.table(performance); 716 } 717 else { 718 $.each(performance, function(index, data) { 719 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 720 }); 721 } 722 console.groupEnd(); 723 } 724 performance = []; 725 } 726 }, 727 invoke: function(query, passedArguments, context) { 728 var 729 object = instance, 730 maxDepth, 731 found, 732 response 733 ; 734 passedArguments = passedArguments || queryArguments; 735 context = element || context; 736 if(typeof query == 'string' && object !== undefined) { 737 query = query.split(/[\. ]/); 738 maxDepth = query.length - 1; 739 $.each(query, function(depth, value) { 740 var camelCaseValue = (depth != maxDepth) 741 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 742 : query 743 ; 744 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 745 object = object[camelCaseValue]; 746 } 747 else if( object[camelCaseValue] !== undefined ) { 748 found = object[camelCaseValue]; 749 return false; 750 } 751 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 752 object = object[value]; 753 } 754 else if( object[value] !== undefined ) { 755 found = object[value]; 756 return false; 757 } 758 else { 759 return false; 760 } 761 }); 762 } 763 if ( $.isFunction( found ) ) { 764 response = found.apply(context, passedArguments); 765 } 766 else if(found !== undefined) { 767 response = found; 768 } 769 if($.isArray(returnedValue)) { 770 returnedValue.push(response); 771 } 772 else if(returnedValue !== undefined) { 773 returnedValue = [returnedValue, response]; 774 } 775 else if(response !== undefined) { 776 returnedValue = response; 777 } 778 return found; 779 } 780 }; 781 782 if(methodInvoked) { 783 if(instance === undefined) { 784 module.initialize(); 785 } 786 module.invoke(query); 787 } 788 else { 789 if(instance !== undefined) { 790 instance.invoke('destroy'); 791 } 792 module.initialize(); 793 } 794 }) 795 ; 796 797 return (returnedValue !== undefined) 798 ? returnedValue 799 : this 800 ; 801 }; 802 803 $.fn.modal.settings = { 804 805 name : 'Modal', 806 namespace : 'modal', 807 808 debug : false, 809 verbose : true, 810 performance : true, 811 812 allowMultiple : false, 813 detachable : true, 814 closable : true, 815 autofocus : true, 816 817 dimmerSettings : { 818 closable : false, 819 useCSS : true 820 }, 821 822 context : 'body', 823 824 queue : false, 825 duration : 500, 826 easing : 'easeOutExpo', 827 offset : 0, 828 transition : 'scale', 829 830 padding : 50, 831 832 onShow : function(){}, 833 onHide : function(){}, 834 835 onVisible : function(){}, 836 onHidden : function(){}, 837 838 onApprove : function(){ return true; }, 839 onDeny : function(){ return true; }, 840 841 selector : { 842 close : '.close, .actions .button', 843 approve : '.actions .positive, .actions .approve, .actions .ok', 844 deny : '.actions .negative, .actions .deny, .actions .cancel', 845 modal : '.ui.modal' 846 }, 847 error : { 848 dimmer : 'UI Dimmer, a required component is not included in this page', 849 method : 'The method you called is not defined.', 850 notFound : 'The element you specified could not be found' 851 }, 852 className : { 853 active : 'active', 854 animating : 'animating', 855 scrolling : 'scrolling' 856 } 857 }; 858 859 860 })( jQuery, window , document );