1/** 2 * Responsive elements utility. 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.Responsiveelement#init 12 * 13 * @classdesc Takes a casual element and makes it responsive. This way one can 14 * set different behaviour and styling to the element per breakpoint 15 * just as they do in a responsive web page as a whole. 16 * 17 * @param {Object} options The initialisation options of the module. 18 * 19 * @property {Object} options The options that define the object 20 * behaviour. 21 * @property {boolean} options.debug Whether to print debug messages in 22 * the browser console. 23 * @property {boolean} options.slug A special codename for the instance 24 * of the element, to be used as a 25 * class on its container and as a key 26 * in arrays where it is grouped with 27 * other elements of its kind. 28 * @property {string} options.breakpoints List of name and width pairs that 29 * define the responsive breakpoints of 30 * this element. 31 * @property {string} options.element The element to be enhanced with 32 * responsive behaviour. 33 * @property {string} options.enter Comma separated list of breakpoints 34 * in which the module enters, which 35 * means it is enabled. 36 * @property {string} options.leave Comma separated list of breakpoints 37 * in which the module leaves, which 38 * means it is disabled. 39 */ 40Responsiville.Responsiveelement = function( options ) { 41 42 // Responsiville must be initialised. 43 if( ! Responsiville.Main.getInstance() ) { 44 throw new Error( 'Responsiville instantiation error: the framework has not been initialised, call new Responsiville::Main() and then Responsiville::init().' ); 45 } 46 47 48 49 // General settings setup. 50 this.enabled = false; 51 this.options = jQuery.extend( this.options, Responsiville.Responsiveelement.defaults, options ); 52 this.codeName = 'responsiville.responsiveelement'; 53 this.responsiville = Responsiville.Main.getInstance(); 54 55 56 57 // Cache important DOM elements. 58 this.$body = this.responsiville.$body; 59 this.$document = this.responsiville.$document; 60 this.$element = jQuery( this.options.element ); 61 62 63 64 // The previous and current breakpoint of the element. 65 this.previousBreakpoint = null; 66 this.currentBreakpoint = null; 67 68 69 70 // If no element found raise an error. 71 if( this.$element.length === 0 ) { 72 this.log( 'Responsiville.Responsiveelement instantiation error: no element found (' + this.options.element + ').' ); 73 return; 74 } 75 76 if( this.$element.length > 1 ) { 77 this.log( 'Responsiville.Responsiveelement instantiation error: more than one elements found (' + this.options.element + ').' ); 78 return; 79 } 80 81 82 83 // Check for developer settings inside the element's data attributes. 84 for( var key in this.options ) { 85 86 var value = this.$element.data( 'responsiville-responsiveelement-' + key ); 87 if( typeof value !== 'undefined' ) { 88 this.options[key] = value; 89 } 90 91 } 92 93 94 95 // Uniqueue element slug. 96 if( this.options.slug == '' ) { 97 Responsiville.Responsiveelement.elementsCounter = Responsiville.Responsiveelement.elementsCounter !== undefined ? ++Responsiville.Responsiveelement.elementsCounter : 0; 98 this.options.slug = this.codeName.replace( '.', '-' ) + '-' + Responsiville.Responsiveelement.elementsCounter; 99 } 100 101 102 103 // Sort the breakpoints by ascending width 104 this.options.breakpoints.sort( function( breakpointA, breakpointB ) { 105 return breakpointA.width - breakpointB.width; 106 }); 107 108 109 110 // Take special care for enter/leave breakpoints possibly given as HTML data attributes. 111 var htmlBreakpoints = Responsiville.determineEnableDisableBreakpoints({ 112 enter: this.$element.data( 'responsiville-responsiveelement-enter' ), 113 leave: this.$element.data( 'responsiville-responsiveelement-leave' ) 114 }); 115 116 if( htmlBreakpoints !== null ) { 117 this.options.enter = htmlBreakpoints.enter; 118 this.options.leave = htmlBreakpoints.leave; 119 } 120 121 122 123 // Add this object as the main element's data attribute for API usage. 124 this.$element.data( 'responsiville-api', this ); 125 this.$element.data( 'responsiville-responsiveelement-api', this ); 126 127 // Uniquely identify this element. 128 this.$element.addClass( this.options.slug ); 129 130 // Images inside the responsive element.
131 this.$images = this.$element.find( 'img' ); 132 133 // Initialises the responsive element. 134 this.setupEvents(); 135 136 // Enable right away if necessary. 137 if( this.responsiville.is( this.options.enter ) ) { 138 this.enable(); 139 } 140 141 this.log( 'responsivelements initialised' ); 142 143 /** 144 * Called after the module has been initialised. 145 * @event Responsiville.Responsiveelement#init 146 */ 147 this.fireEvent( 'init' ); 148 149}; 150 151 152 153// Extend with logging functions. 154Responsiville.extend( Responsiville.Responsiveelement, Responsiville.Debug.Extensions ); 155 156// Extend with event handling mechanism. 157Responsiville.extend( Responsiville.Responsiveelement, Responsiville.Events ); 158 159 160 161/** 162 * @property {boolean} AUTO_RUN Controls whether this module should run by 163 * default, without the developer calling it, using 164 * its default settings. Defaults to true. 165 */ 166Responsiville.Responsiveelement.AUTO_RUN = typeof RESPONSIVILLE_AUTO_INIT !== 'undefined' ? RESPONSIVILLE_AUTO_INIT : true; 167 168 169 170/** 171 * @property {Object} defaults Default values for this module settings. 172 */ 173Responsiville.Responsiveelement.defaults = { 174 debug : false, 175 slug : '', 176 breakpoints : [{ breakpoint1: 1024 }, { breakpoint2: 2048 }, { breakpoint3: 9999 }], 177 element : '.responsiville-responsiveelement', 178 enter : 'small, mobile, tablet, laptop, desktop, large, xlarge', 179 leave : '' 180}; 181 182 183 184/** 185 * Runs through the page and searches for elements that apply to the current 186 * module in order to apply it to them automatically. Useful for automatically 187 * creating elements with this module's behaviour just by setting up the 188 * predefined classes and data attributes in HTML elements of the page. 189 * 190 * @function 191 * @static 192 * 193 * @return {void} 194 */ 195Responsiville.Responsiveelement.autoRun = function() { 196 197 jQuery( Responsiville.Responsiveelement.defaults.element ).each( function() { 198 199 new Responsiville.Responsiveelement({ 200 debug : Responsiville.Main.getInstance().options.debug, 201 element : this 202 }); 203 204 }); 205 206}; 207 208 209 210/** 211 * Sets up event handlers necessary for the object to function properly. 212 * 213 * @return {void} 214 */ 215Responsiville.Responsiveelement.prototype.setupEvents = function() { 216 217 var k, length; 218 219 // Register to be enabled on the required breakpoints. 220 var breakpointsEnter = Responsiville.splitAndTrim( this.options.enter ); 221 222 for( k=0, length=breakpointsEnter.length; k<length; k++ ) { 223 this.responsiville.on( 'enter.' + breakpointsEnter[k], this.getBoundFunction( this.enable ) ); 224 } 225 226 // Register to be disabled on the required breakpoints. 227 var breakpointsLeave = Responsiville.splitAndTrim( this.options.leave ); 228 229 for( k=0, length=breakpointsLeave.length; k<length; k++ ) { 230 this.responsiville.on( 'enter.' + breakpointsLeave[k], this.getBoundFunction( this.disable ) ); 231 } 232 233 234 235 // Check which images have loaded and wait for the rest of them. 236 for( k=0, length=this.$images.length; k < length; k++ ) { 237 238 var image = this.$images.eq( k ).get( 0 ); 239 240 if( Responsiville.hasImageLoaded( image ) ) { 241 this.imagesLoaded++; 242 } else { 243 image.onload = this.getBoundFunction( this.imageLoaded ); 244 image.onerror = this.getBoundFunction( this.imageError ); 245 } 246 247 } 248 249}; 250 251 252 253/** 254 * Enables the responsive element. 255 * 256 * @fires Responsiville.Responsiveelement#enabled 257 * 258 * @return {void} 259 */ 260Responsiville.Responsiveelement.prototype.enable = function() { 261 262 if( this.enabled ) { 263 return; 264 } 265 this.enabled = true; 266 this.log( 'enable responsiveelement' ); 267 268 // Process element on resize. 269 this.responsiville.on( 'resize', this.getBoundFunction( this.processElement ) ); 270 this.processElement(); 271 272 /** 273 * Called after the module has been enabled. 274 * @event Responsiville.Responsiveelement#enabled 275 */ 276 this.fireEvent( 'enabled' ); 277 278}; 279 280 281 282/** 283 * Disables the responsive element. 284 * 285 * @fires Responsiville.Responsiveelement#disabled 286 * 287 * @return {void} 288 */ 289Responsiville.Responsiveelement.prototype.disable = function() { 290 291 if( ! this.enabled ) { 292 return; 293 } 294 this.enabled = false;
295 this.log( 'disable responsiveelement' ); 296 297 298 // Process element on resize. 299 this.responsiville.off( 'resize', this.getBoundFunction( this.processElement ) ); 300 301 var removeClasses = ''; 302 var breakpoint; 303 304 for( var k = 0; k < this.options.breakpoints.length; k++ ) { 305 306 breakpoint = this.options.breakpoints[k]; 307 removeClasses += breakpoint.name + ' '; 308 309 } 310 311 this.$element.removeClass( removeClasses ); 312 313 /** 314 * Called after the module has been disabled. 315 * @event Responsiville.Responsiveelement#disabled 316 */ 317 this.fireEvent( 'disabled' ); 318 319}; 320 321 322 323/** 324 * Callback that runs when an image inside the responsive element 325 * has successfully loaded. 326 * 327 * @param {Event} event The image loaded event that originally fired. 328 * 329 * @fires Responsiville.Responsiveelement#image.loaded 330 * 331 * @return {void} 332 */ 333Responsiville.Responsiveelement.prototype.imageLoaded = function( event ) { 334 335 this.imagesLoaded++; 336 337 if( this.enabled && this.imagesLoaded == this.$images.length ) { 338 this.processElement(); 339 } 340 341 /** 342 * Called after an image inside the responsive element has loaded. 343 * @event Responsiville.Responsiveelement#image.loaded 344 */ 345 this.fireEvent( 'image.loaded', [ event ] ); 346 347}; 348 349 350 351/** 352 * Runs when an image inside the responsive element has aborted loading due to 353 * any error. 354 * 355 * @param {Event} event The image error event that originally fired. 356 * 357 * @fires Responsiville.Responsiveelement#image.error 358 * 359 * @return {void} 360 */ 361Responsiville.Responsiveelement.prototype.imageError = function( event ) { 362 363 this.imagesLoaded++; 364 365 if( this.enabled && this.imagesLoaded == this.$images.length ) { 366 this.processElement(); 367 } 368 369 /** 370 * Called after an image inside the responsive element was unable to load 371 * due to an error. 372 * @event Responsiville.Responsiveelement#image.error 373 */ 374 this.fireEvent( 'image.error', [ event ] ); 375 376}; 377 378 379 380/** 381 * Runs all the callback functions registered on the responsive element's 382 * breakpoint changes. 383 * 384 * @return {void} 385 */ 386Responsiville.Responsiveelement.prototype.runBreakpointChangeCallbacks = function() { 387 388 // Run all callbacks set on leaving the previous breakpoint. 389 this.fireEvent( 'leave.' + this.previousBreakpoint.name, this.$element ); 390 391 // Run all callbacks set on entering the current breakpoint. 392 this.fireEvent( 'enter.' + this.currentBreakpoint.name, this.$element ); 393 394}; 395 396 397 398/** 399 * Does whatever necessary on the element in order to satisfy its responsive 400 * behaviour. Runs on window resize events and on Responsiville resize and 401 * breakpoint change events. 402 * 403 * @fires Responsiville.Responsiveelement#image.processing 404 * @fires Responsiville.Responsiveelement#image.change 405 * @fires Responsiville.Responsiveelement#image.processed 406 407 * @return {void} 408 */ 409Responsiville.Responsiveelement.prototype.processElement = function() { 410 411 /** 412 * Called before processing a responsive element. 413 * @event Responsiville.Responsiveelement#processing 414 */ 415 this.fireEvent( 'processing' ); 416 417 418 419 var k; 420 var breakpoint; 421 var newBreakpoint; 422 var elementWidth; 423 424 elementWidth = this.$element.outerWidth(); 425 426 // If an element has zero width, or is display:none, attempt a best guess by traversing its parents. 427 if( elementWidth === 0 ) { 428 429 this.log( 'non-displayed or zero-width element. Checking the element parents.' ); 430 431 var $parents = jQuery( this.$element.parents() ); 432 433 for( k = 0; k < $parents.length; k++ ) { 434 435 elementWidth = $parents.eq( k ).width(); 436 if( $parents.eq( k ).css('display') !== 'none' && elementWidth !== 0 ) { 437 this.log( 'found displayed parent with non-zero width ' + elementWidth + 'px' ); 438 break; 439 } 440 441 } 442 443 } 444 445 this.log( 'processing element with width ' + elementWidth + 'px' ); 446 447 448 449 // Calculate the classes to remove 450 var removeClasses = ''; 451 452 for( k = 0; k < this.options.breakpoints.length; k++ ) { 453 454 breakpoint = this.options.breakpoints[k]; 455 removeClasses += breakpoint.name + ' '; 456 457 } 458 459 // Calculate the classes to add 460 var addClasses = ''; 461 462 for( k = 0, length = this.options.breakpoints.length; k < length; k++ ) { 463 464 breakpoint = this.options.breakpoints[k]; 465 if( elementWidth > breakpoint.width ) { 466 addClasses += breakpoint.name + ' '; 467 newBreakpoint = breakpoint; 468 } else { 469 break; 470 } 471 472 } 473 474 if( elementWidth <= breakpoint.width ) { 475 addClasses += breakpoint.name + ' '; 476 newBreakpoint = breakpoint; 477 } 478 479 this.log( 'removing classes: ' + addClasses ); 480 this.log( 'adding classes: ' + addClasses ); 481 482 this.$element.removeClass( removeClasses ); 483 this.$element.addClass( addClasses ); 484 485 486 487 // Sets up current breakpoint the first time the module runs. 488 var firstRun = false;
489 490 if( ! this.currentBreakpoint ) { 491 firstRun = true; 492 this.currentBreakpoint = newBreakpoint; 493 } 494 495 var oldBreakpoint = this.currentBreakpoint; 496 var breakpointChanged = firstRun || oldBreakpoint.name != newBreakpoint.name; 497 498 if( breakpointChanged ) { 499 500 this.log( 'breakpoint change detected from ' + this.currentBreakpoint.name + ' to ' + newBreakpoint.name ); 501 502 this.previousBreakpoint = this.currentBreakpoint; 503 this.currentBreakpoint = newBreakpoint; 504 505 this.runBreakpointChangeCallbacks(); 506 507 /** 508 * Called after a breakpoint change has occured. 509 * @event Responsiville.Responsiveelement#change 510 */ 511 this.fireEvent( 'change', this.$element ); 512 513 } 514 515 516 517 this.log( 'processed element' ); 518 519 /** 520 * Called after processing a responsive element. 521 * @event Responsiville.Responsiveelement#processed.element 522 */ 523 this.fireEvent( 'processed' ); 524 525};
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.