1/** 2 * Creates and initialises the main Responsiville object. 3 * 4 * @class 5 * @extends Responsiville.Debug 6 * @extends Responsiville.Events 7 * 8 * @author Nevma, [email protected] 9 * @license Nevma Copyright (c) http://www.nevma.gr 10 * 11 * @fires Responsiville.Main#init 12 * 13 * @classdesc This is the main object of the Responsiville framework. The whole 14 * Responsiville framework depends on this object to function and so 15 * do all the plugins that belong to the framework as well. It 16 * contains core functions that handle the breakpoints and the 17 * responsiveness of the web page in development. 18 * 19 * @param {Object} options The initialisation options of the module. 20 * 21 * @property {Object} options The options that define the object 22 * behaviour. 23 * @property {boolean} options.debug Whether to print debug messages in 24 * the browser console. 25 * @property {boolean} options.debugUI Whether to show the debug panel at 26 * the edge of the screen. 27 * @property {string} options.debugPos Where to position the debug panel (br 28 * means bottom-right, tl means top-left 29 * and so on). 30 * @property {int} options.throttle Time delay to throttle expensive 31 * function calls as a result of 32 * repeated operations like the browser 33 * resize event. 34 * @property {Array} options.breakpoints Array of extra breakpoints to include 35 * to the framework. Each breakpoint is 36 * an associative array like { name: 37 * 'small', width: 320, icon: 'uE800' }. 38 * One might need to add breakpoints to 39 * this array but they probably should 40 * not remove any beacuse this might 41 * break behaviour of other code that 42 * depends on them. 43 */ 44 45Responsiville.Main = function ( options ) { 46 47 // Responsiville is a singleton. 48 49 if ( ! Responsiville.Main.instance ) { 50 51 Responsiville.Main.instance = this; 52 53 } else { 54 55 throw new Error( 'Responsiville instantiation error: the framework has already been initialised, new Responsiville.Main is a singleton.' ); 56 57 } 58 59 60 61 // General settings setup. 62 63 this.options = {}; 64 this.options = jQuery.extend( this.options, Responsiville.Main.defaults, options ); 65 this.codeName = 'responsiville.main'; 66 67 68 69 // Cache important DOM elements (note that it might be too early for the body here). 70 71 this.$window = jQuery( window ); 72 this.$html = jQuery( 'html' ); 73 74 75 76 // Flag which checks whether the resize callbacks have been called. 77 78 this.adjustCallbacksCalled = false; 79 80 // The previous and current breakpoint of the page. 81 82 this.previousBreakpoint = null; 83 this.currentBreakpoint = null; 84 85 86 87 // Log some crap. 88 89 var toLog = 90 '\n' + 91 ' ____ _ _ ____\n' + 92 ' / __ \\___ _________ ____ ____ _____(_) __(_) / /__\n' + 93 ' / /_/ / _ \\/ ___/ __ \\/ __ \\/ __ \\/ ___/ / | / / / / / _ \\\n' + 94 ' / _, _/ __(__ ) /_/ / /_/ / / / (__ ) /| |/ / / / / __/\n' + 95 '/_/ |_|\\___/____/ .___/\\____/_/ /_/____/_/ |___/_/_/_/\\___/\n' + 96 ' /_/\n' + 97 ' version ' + Responsiville.VERSION + '\n\n'; 98 99 toLog += 'breakpoints:\n'; 100 101 for ( var k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 102 103 var breakpoint = this.options.breakpoints[k]; 104 toLog += breakpoint.name + '\t' + breakpoint.width + 'px' + ( k < length-1 ? '\n' : '' ); 105 106 } 107 108 this.log( toLog ); 109 110 111 112 this.$debugControls = null; 113 this.$debugPerformance = null; 114 this.$debugToggle = null; 115 116 117 118 // Adjust to the current viewport and device characteristics for the first time. 119 120 this.adjust(); 121 122}; 123 124 125 126// Extend with logging functions. 127 128Responsiville.extend( Responsiville.Main, Responsiville.Debug.Extensions ); 129 130// Extend with event handling mechanism. 131 132Responsiville.extend( Responsiville.Main, Responsiville.Events ); 133 134 135 136/** 137 * @property {boolean} AUTO_RUN Controls whether this module should run by 138 * default, without the developer calling it, using 139 * its default settings. Defaults to true. 140 */ 141 142Responsiville.Main.AUTO_RUN = typeof RESPONSIVILLE_AUTO_INIT !== 'undefined' ? RESPONSIVILLE_AUTO_INIT : true; 143 144 145 146/** 147 * @property {Object} defaults Default values for this module settings. 148 */ 149 150Responsiville.Main.defaults = { 151 debug : false, 152 debugUI : false, 153 debugPos : 'br', 154 throttle : 250, 155 breakpoints : [ 156 { name: 'small', width: 320, icon: '\uE800' }, // Screens 0 - 320 157 { name: 'mobile', width: 599, icon: '\uE801' }, // Screens 321 - 599 158 { name: 'tablet', width: 1023, icon: '\uE802' }
158, // Screens 600 - 1023 159 { name: 'laptop', width: 1279, icon: '\uE803' }, // Screens 1024 - 1279 160 { name: 'desktop', width: 1439, icon: '\uE804' }, // Screens 1280 - 1439 161 { name: 'large', width: 1679, icon: '\uE804' }, // Screens 1440 - 1679 162 { name: 'xlarge', width: 99999, icon: '\uE804' } // Screens 1601 - infinity 163 ] 164}; 165 166 167 168/** 169 * Automatically initialises the Responsiville framework. It is dependendent on 170 * the fact that the jQuery and the main Responsiville scripts (not the modules) 171 * have loaded. It is activated via the global RESPONSIVILLE_AUTO_INIT and it 172 * uses the global RESPONSIVILLE_DEBUG. 173 * 174 * @function 175 * @static 176 * 177 * @return {void} 178 */ 179 180Responsiville.Main.autoRun = function () { 181 182 // Create, but not yet initialise the Responsiville framework main singleton instance. 183 184 var responsiville = new Responsiville.Main({ 185 debug : RESPONSIVILLE_DEBUG, 186 debugUI : RESPONSIVILLE_DEBUG 187 }); 188 189 190 191 // On DOMContentLoaded fully initialise the instance. 192 193 jQuery( (function () { 194 195 this.init(); 196 197 }).bind( responsiville ) ); 198 199}; 200 201 202 203/** 204 * Get the singleton responsiville instance. Oh, did we mention? Responsiville 205 * is a singleton, because in a browser you only get one responsive web page at 206 * a time. 207 * 208 * @function 209 * @static 210 * 211 * @return {Responsiville.Main} The singleton Responsiville object instance 212 * which is responsible for the current page. 213 */ 214 215Responsiville.Main.getInstance = function () { 216 217 return Responsiville.Main.instance; 218 219}; 220 221 222 223/** 224 * Initilalises the responsive magic. Sets up the necessary event handlers on 225 * browser window resize that assist in controlling the desired responsive 226 * behaviour. Runs exactly once. 227 * 228 * @return {void} 229 */ 230 231Responsiville.Main.prototype.init = function () { 232 233 // In the case the framework was initialised automatically in the HEAD before the BODY was created. 234 235 var bodyElementNotCreated = typeof this.$body === 'undefined' || this.$body.length === 0; 236 237 if ( bodyElementNotCreated ) { 238 this.$body = jQuery( 'body' ); 239 } 240 241 242 243 // Setup the debugging tools of the framework. 244 245 Responsiville.Debug.setupDebug(); 246 247 248 249 // General debounced resize handler so that the rest of the code hooks to it instead of directly on window resize. 250 251 this.$window.on( 'resize', Responsiville.debounce( this.windowResize, this.options.throttle, this ) ); 252 253 254 255 256 // Run responsive adjustments once immediately. 257 258 this.adjust(); 259 260 261 262 /** 263 * Called after the module has been initialised. 264 * 265 * @event Responsiville.Main#init 266 */ 267 268 this.fireEvent( 'init' ); 269 270 271 272 // A de facto breakpoint has occured after init, so run the breakpoint change callbacks. 273 274 this.runBreakpointChangeCallbacks(); 275 276 277 278 this.log( 'framework Responsiville has initialised' ); 279 280}; 281 282 283 284/** 285 * Registers a new, non-default, breakpoint in the system. 286 * 287 * @param {string} breakpointName The code name of the new breakpoint. 288 * @param {int} breakpointWidth The window width of the new breakpoint. 289 * 290 * @return {void} 291 */ 292 293Responsiville.Main.prototype.registerBreakpoint = function ( breakpointName, breakpointWidth ) { 294 295 var index = 0; 296 297 for ( var k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 298
299 // Breakpoints have to be sorted by ascending screen width. 300 301 var breakpoint = this.options.breakpoints[k]; 302 303 if ( breakpointWidth < breakpoint.width ) { 304 305 index = k; 306 break; 307 308 } 309 310 } 311 312 this.options.breakpoints.splice( k, 0, { name: breakpointName, width: breakpointWidth } ); 313 314 this.dir( 'breakpoint registered:', { name: breakpointName, width: breakpointWidth }, 'breakpoints:', this.options.breakpoints ); 315 316}; 317 318 319 320/** 321 * Deregisters an existing breakpoint. 322 * 323 * @param {string} breakpointName The code name of the breakpoint to deregister. 324 * 325 * @return {void} 326 */ 327 328Responsiville.Main.prototype.deregisterBreakpoint = function ( breakpointName ) { 329 330 for ( var k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 331 332 var breakpoint = this.options.breakpoints[k]; 333 334 if ( breakpointName == breakpoint.name ) { 335 336 this.options.breakpoints.splice( k+1, 1 ); 337 break; 338 339 } 340 341 } 342 343 this.dir( 'breakpoint deregistered: ' + breakpointName, 'new breakpoints: ', this.options.breakpoints ); 344 345}; 346 347 348 349/** 350 * Window resize event handler which takes care of manipulating the breakpoints, 351 * setting and unsetting them accordingly. 352 * 353 * @return {void} 354 */ 355 356Responsiville.Main.prototype.adjust = function () { 357 358 this.log( 'adjusting to viewport' ); 359 360 361 362 // Calculate the classes to remove from the HTML element based on the current breakpoint. 363 364 var removeClasses = ''; 365 366 var k; 367 var breakpoint; 368 369 for ( k = this.options.breakpoints.length - 1; k >= 0; k-- ) { 370 371 breakpoint = this.options.breakpoints[k]; 372 removeClasses += breakpoint.name + ' '; 373 374 } 375 376 377 378 // Calculate the classes to add to the HTML element based on the current breakpoint. 379 380 var addClasses = ''; 381 382 for ( k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 383 384 breakpoint = this.options.breakpoints[k]; 385 386 if ( window.innerWidth > breakpoint.width ) { 387 addClasses += breakpoint.name + ' '; 388 } else { 389 break; 390 } 391 392 } 393 394 if ( window.innerWidth <= breakpoint.width ) { 395 addClasses += breakpoint.name + ' '; 396 } 397 398 399 400 // Add a class that indicates whether we are on a touch device or not. 401 402 if ( this.isDevice() ) { 403 addClasses += 'device '; 404 } else { 405 removeClasses += 'device '; 406 } 407 408 409 410 // Adds a class that indicates the current browser orientation. 411 412 if ( window.innerWidth >= window.innerHeight ) { 413 addClasses += 'landscape '; 414 removeClasses += 'portrait '; 415 } else { 416 addClasses += 'portrait '; 417 removeClasses += 'landscape '; 418 } 419 420 421 422 // Adds a class that indicates whether the current device is a PC or phone/tablet. 423 424 if ( this.isDevice() ) { 425 addClasses += 'device '; 426 removeClasses += 'pc '; 427 } else { 428 addClasses += 'pc '; 429 removeClasses += 'device '; 430 } 431 432 this.$html.removeClass( removeClasses ); 433 this.$html.addClass( addClasses ); 434 435 436 437 // Sets up current breakpoint the first time the framework runs. 438 439 var firstRun = false; 440 441 if ( ! this.currentBreakpoint ) { 442 443 firstRun = true; 444 this.currentBreakpoint = this.calculateCurrentBreakpoint(); 445 446 } 447 448 449 450 // Checks if and what kind of breakpoint change has occured. 451 452 var newBreakpoint = this.calculateCurrentBreakpoint(); 453 var oldBreakpoint = this.currentBreakpoint; 454 455 var breakpointChanged = 456 firstRun || 457 oldBreakpoint.name != newBreakpoint.name || 458 ! this.adjustCallbacksCalled; 459 460 if ( breakpointChanged ) { 461 462 this.previousBreakpoint = this.currentBreakpoint; 463 this.currentBreakpoint = newBreakpoint; 464 465 } 466 467 468 469 // Calls event handlers that run on breakpoint changes. 470 471 if ( breakpointChanged ) { 472 473 // Run the breakpoint change callbacks. 474 475 this.runBreakpointChangeCallbacks(); 476 477 478 479 /** 480 * Called after a breakpoint change has occured. 481 * 482 * @event Responsiville.Main#change 483 */ 484 485 this.fireEvent( 'change' ); 486 487 this.adjustCallbacksCalled = true; 488 489 } 490 491 492 493 this.log( 494 'window resized', 'window width: ' + window.innerWidth, 495 ( breakpointChanged ? 'breakpoint changed from: ' + this.previousBreakpoint.name + '' : 'no breakpoint change' ), 496 'current breakpoint: ' + this.currentBreakpoint.name 497 ); 498 499}; 500 501 502 503/** 504 * Runs all callbacks registered on breakpoint change events (either entering or 505 * leaving a breakpoint). 506 * 507 * @return {void} 508 */ 509 510Responsiville.Main.prototype.runBreakpointChangeCallbacks = function () { 511 512 // Run all callbacks set on leaving the previous breakpoint. 513 514 this.fireEvent( 'leave.' + this.previousBreakpoint.name ); 515 516 // Run all callbacks set on entering the current breakpoint. 517 518 this.fireEvent( 'enter.' + this.currentBreakpoint.name ); 519 520}; 521 522 523 524/** 525 * Runs all the function callbacks which are attached to the Responsiville Main 526 * object's resize event. The call to this function has been registered as 527 * throttled to begin with, so that the browser is protected from repeated calls 528 * of the same callbacks. 529 * 530 * @fires Responsiville.Main#resize 531 * 532 * @return void 533 */ 534 535Responsiville.Main.prototype.windowResize = function () { 536 537 this.adjust(); 538 539 /** 540 * Called after a breakpoint change has occured. 541 * 542 * @event Responsiville.Main#resize 543 */ 544 545 this.fireEvent( 'resize' ); 546 547}; 548 549 550 551/** 552 * Checks whether the current screen has a high devide pixel ratio, that is 553 * greater than 1 (HiDPI, retina, etc). 554 * 555 * @return {boolean} Whether the current screen has a high device pixel ratio. 556 */ 557 558Responsiville.Main.prototype.isHiDPI = function () { 559 560 if ( window.devicePixelRatio > 1 ) { 561 562 return true; 563 564 } 565 566 // High resolution screen check regular expression. 567 568 var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)'; 569 570 if ( window.matchMedia && window.matchMedia( mediaQuery ).matches ) { 571 572 return true; 573 574 } 575 576 return false;
577 578}; 579 580 581 582/** 583 * Checks whether the current device is a mobile and not a desktop one based on 584 * the user agent of the browser. 585 * 586 * @return {boolean} Whether the current device is a mobile one. 587 */ 588 589Responsiville.Main.prototype.isDevice = function () { 590 591 var deviceAgentQuery = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; 592 593 return deviceAgentQuery.test( navigator.userAgent ); 594 595}; 596 597 598 599/** 600 * Checks whether the current browser window size is currently at the given 601 * breakpoint(s). Useful in order to check if we are inside the range of a 602 * specific breakpoint. 603 * 604 * @param {Array<string>|string} breakpointNames The breakpoint name(s) against 605 * which to check. Can be just a 606 * single breakpoint name or a 607 * comma separated list of 608 * breakpoint names or an array of 609 * breakpoint names. 610 * 611 * @return {boolean} Whether the browser window is at the given breakpoint(s). 612 */ 613 614Responsiville.Main.prototype.is = function ( breakpointNames ) { 615 616 var k; 617 var testBreakpointName; 618 var breakpointNamesArray = Responsiville.splitAndTrim( breakpointNames ); 619 620 for ( k in breakpointNamesArray ) { 621 622 testBreakpointName = breakpointNamesArray[k]; 623 624 if ( this.currentBreakpoint && this.currentBreakpoint.name == testBreakpointName ) { 625 return true; 626 } 627 628 } 629 630 return false; 631 632}; 633 634 635 636/** 637 * Checks whether the current window is at a size smaller than the given 638 * breakpoint. Remember that we are checking mobile first. 639 * 640 * @param {string} testBreakpointName The breakpoint name against which to 641 * check. 642 * 643 * @return {boolean} Whether the browser window is at a smaller breakpoint. 644 */ 645 646Responsiville.Main.prototype.isSmallerThan = function ( testBreakpointName ) { 647 648 for ( var k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 649 650 var breakpoint = this.options.breakpoints[k]; 651 652 if ( breakpoint.name == testBreakpointName ) { 653 return window.innerWidth < breakpoint.width; 654 } 655 656 } 657 658}; 659 660 661 662/** 663 * Checks whether the current window is at a size equal or smaller than the 664 * given breakpoint. 665 * 666 * @param {string} testBreakpointName The breakpoint name against which to 667 * check. 668 * 669 * @return {boolean} Whether the browser window is at an equal or smaller 670 * breakpoint. 671 */ 672 673Responsiville.Main.prototype.isEqualOrSmallerThan = function ( testBreakpointName ) { 674 675 return this.is( testBreakpointName ) || this.isSmallerThan( testBreakpointName ); 676 677}; 678 679 680 681/** 682 * Checks whether the current window is at a size bigger than the given 683 * breakpoint. 684 * 685 * @param {string} testBreakpointName The breakpoint name against which to 686 * check. 687 * 688 * @return {boolean} Whether the browser window is at bigger breakpoint. 689 */ 690 691Responsiville.Main.prototype.isBiggerThan = function ( testBreakpointName ) { 692 693 for ( var k = this.options.breakpoints.length-1; k >= 0; k-- ) { 694 695 var breakpoint = this.options.breakpoints[k]; 696 697 if ( breakpoint.name == testBreakpointName ) { 698 return window.innerWidth > breakpoint.width; 699 } 700 701 } 702 703}; 704 705 706 707/** 708 * Checks whether the current window is at a size equal or bigger than the given 709 * breakpoint. 710 * 711 * @param {string} testBreakpointName The breakpoint name against which to 712 * check. 713 * 714 * @return {boolean} Whether the browser window is at an equal or bigger 715 * breakpoint. 716 */ 717 718Responsiville.Main.prototype.isEqualOrBiggerThan = function ( testBreakpointName ) { 719 720 return this.is( testBreakpointName ) || this.isBiggerThan( testBreakpointName ); 721 722}; 723 724 725 726/** 727 * Checks whether the last breakpoint change was towards a bigger screen. 728 * 729 * @return {boolean} Whether the last breakpoint change was towards a bigger 730 * screen. 731 */ 732 733Responsiville.Main.prototype.hasGrown = function () { 734 735 if ( this.currentBreakpoint.name == this.previousBreakpoint.name ) { 736 return false;
737 } 738 739 740 741 // Calculate the current and previous breakpoint widths. 742 743 var currentBreakpointWidth = 0; 744 var previousBreakpointWidth = 0; 745 746 for ( var k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 747 748 var breakpoint = this.options.breakpoints[k]; 749 750 if ( this.currentBreakpoint.name == breakpoint.name ) { 751 currentBreakpointWidth = breakpoint.width; 752 } else if ( this.previousBreakpoint.name == breakpoint.name ) { 753 previousBreakpointWidth = breakpoint.width; 754 } 755 756 } 757 758 759 760 // In case of the first load of the page. 761 762 if ( previousBreakpointWidth === 0 ) { 763 return true; 764 } 765 766 767 // All other usual cases. 768 769 return currentBreakpointWidth > previousBreakpointWidth; 770 771}; 772 773 774 775/** 776 * Calculate the current breakpoint according to the current browser window 777 * size. 778 * 779 * @return {Object} The current breakpoint. 780 */ 781 782Responsiville.Main.prototype.calculateCurrentBreakpoint = function () { 783 784 var tempBreakpoint; 785 786 for ( var k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 787 788 var breakpoint = this.options.breakpoints[k]; 789 790 // The current breakpoint is the one closest but bigger than the window width. 791 792 if ( window.innerWidth <= breakpoint.width ) { 793 794 tempBreakpoint = breakpoint; 795 break; 796 797 } 798 799 } 800 801 return tempBreakpoint; 802 803}; 804 805 806 807/** 808 * Gets a property from the browser local storage. 809 * 810 * @param {string} name The key name of the property to retrieve from the 811 * browser local storage. 812 * 813 * @return {Object} The value that corresponds to the given property from the 814 * browser local storage. 815 */ 816 817Responsiville.Main.prototype.getProperty = function ( name ) { 818 819 var localStorage = window.localStorage; 820 821 var settings = localStorage.getItem( this.codeName ); 822 823 if ( settings ) { 824 825 settings = JSON.parse( settings ); 826 return settings[name]; 827 828 } 829 830}; 831 832 833 834/** 835 * Sets a property in the browser local storage. 836 * 837 * @param {string} name The key name of the property to set in the browser 838 * local storage. 839 * @param {Object} value The value that is to be set for the given property code 840 * name in the browser local storage. 841 * 842 * @return {void} 843 */ 844 845Responsiville.Main.prototype.setProperty = function ( name, value ) { 846 847 var localStorage = window.localStorage; 848 849 var settings = localStorage.getItem( this.codeName ); 850 851 852 853 // Makes sure the settings object is created. 854 855 if ( ! settings ) { 856 settings = {}; 857 } else { 858 settings = JSON.parse( settings ); 859 } 860 861 settings[name] = value; 862 863 864 865 // Stores one big settings object in local storage. 866 867 localStorage.setItem( this.codeName, JSON.stringify( settings ) ); 868 869}; 870 871 872 873/** 874 * Prints the current registered breakpoints in the browser console. 875 * 876 * @return {void} 877 */ 878 879Responsiville.Main.prototype.printBreakpoints = function () { 880 881 var debug = this.options.debug; 882 883 this.options.debug = true; 884 885 var toLog = [ 'Breakpoints' ]; 886 887 for ( var k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 888 889 var breakpoint = this.options.breakpoints[k]; 890 toLog.push( breakpoint.name + ' - ' + breakpoint.width ); 891 892 } 893 894 this.log.apply( this, toLog ); 895 896 this.options.debug = debug; 897 898};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.