( function( $ ) { /** * Helper for simulating media queries without resizing * the viewport. * * Parts based on Respond.js by Scott Jehl (https://github.com/scottjehl/Respond) * * @since 1.10 * @class SimulateMediaQuery */ var SimulateMediaQuery = { /** * Strings to look for in stylesheet URLs that are * going to be parsed. If a string matches, that * stylesheet won't be parsed. * * @since 1.10 * @property {Array} ignored */ ignored: [], /** * Strings to look for in stylesheet URLs. If a * string matches, that stylesheet will be reparsed * on each updated. * * @since 1.10 * @property {Array} reparsed */ reparsed: [], /** * The current viewport width to simulate. * * @since 1.10 * @property {Number} width */ width: null, /** * A callback to run when an update completes. * * @since 1.10 * @property {Function} callback */ callback: null, /** * Cache of original stylesheets. * * @since 1.10 * @property {Object} sheets */ sheets: {}, /** * Style tags used for rendering simulated * media query styles. * * @since 1.10 * @property {Array} styles */ styles: [], /** * AJAX queue for retrieving rules from a sheet. * * @since 1.10 * @property {Array} queue */ queue: [], /** * The value of 1em in pixels. * * @since 1.10 * @access private * @property {Number} emPxValue */ emPxValue: null, /** * Regex for parsing styles. * * @since 1.10 * @property {Object} _regex */ regex: { media: /@media[^{]*{([\s\S]+?})\s*}/ig, empty: /@media[^{]*{([^{}]*?)}/ig, keyframes: /@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi, comments: /\/\*[^*]*\*+([^/][^*]*\*+)*\//gi, urls: /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, findStyles: /@media *([^\{]+)\{([\S\s]+?)\}$/, only: /(only\s+)?([a-zA-Z]+)\s?/, minw: /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, maxw: /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, minmaxwh: /\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi, other: /\([^\)]*\)/g }, /** * Adds strings to look for in stylesheet URLs * that are going to be parsed. If a string matches, * that stylesheet won't be parsed. * * @since 1.10 * @method ignore * @param {Array} strings */ ignore: function( strings ) { Array.prototype.push.apply( this.ignored, strings ); }, /** * Adds strings to look for in stylesheet URLs. If a * string matches, that stylesheet will be reparsed. * * @since 1.10 * @method reparse * @param {Array} strings */ reparse: function( strings ) { Array.prototype.push.apply( this.reparsed, strings ); }, /** * Updates all simulated media query rules. * * @since 1.10 * @method update * @param {Number} width The viewport width to simulate. * @param {Function} callback */ update: function( width, callback ) { this.width = undefined === width ? null : width; this.callback = undefined === callback ? null : callback; ForceJQueryValues.update(); if ( this.queueSheets() ) { this.runQueue(); } else { this.applyStyles(); } }, /** * Adds all sheets that aren't already cached * to the AJAX queue for fetching sheets. * * @since 1.10 * @method queueSheets * @return {Boolean} */ queueSheets: function() { var elements = $( 'link, style' ), sheet = null, href = null, id = null, tagName = null, rel = null, media = null, key = null, isCSS = null, ignore = false, i = 0, k = 0; for ( ; i < elements.length; i++ ) { element = elements[ i ]; href = element.href; id = element.id; tagName = element.tagName.toLowerCase(); rel = element.rel; media = element.media; key = !! href ? href : !! id ? id : 'style-' + i; isCSS = true; ignore = false; if ( 'style' === tagName || ( !! href && rel && rel.toLowerCase() === 'stylesheet' ) ) { for ( k = 0; k < this.ignored.length; k++ ) { if ( key.indexOf( this.ignored[ k ] ) > -1 ) { ignore = true; break; } } if ( ignore ) { continue; } for ( k = 0; k < this.reparsed.length; k++ ) { if ( key.indexOf( this.reparsed[ k ] ) > -1 ) { this.sheets[ key ] = null; break; } } if ( undefined === this.sheets[ key ] || ! this.sheets[ key ] ) { this.queue.push( { element : elements.eq( i ), key : key, tagName : tagName, href : href, id : id, media : media } ); } } } return this.queue.length; }, /** * Send AJAX requests to get styles from all * stylesheets in the queue. * * @since 1.10 * @method runQueue */ runQueue: function() { var item; if ( this.queue.length ) { item = this.queue.shift(); if ( 'style' === item.tagName ) { this.parse( item.element.html(), item ); this.runQueue(); } else { $.get( item.href, $.proxy( function( response ) { this.parse( response, item ); this.runQueue(); }, this ) ); } } else { this.applyStyles(); } }, /** * Parse a stylesheet that has been returned * from an AJAX request. * * @since 1.10 * @method parse * @param {String} styles * @param {Array} item */ parse: function( styles, item ) { var re = this.regex, cleaned = this.cleanStyles( styles ), allQueries = cleaned.match( re.media ), length = allQueries && allQueries.length || 0, useMedia = ! length && item.media, query = null, queries = null, media = null, all = '', i = 0, k = 0; if ( allQueries ) { all = cleaned.replace( re.media, '' ); } else if ( useMedia && 'all' != item.media ) { length = 1; } else { all = cleaned; } this.sheets[ item.key ] = { element : item.element, key : item.key, tagName : item.tagName, href : item.href, id : item.id, all : all, queries : [] }; for ( i = 0; i < length; i++ ) { if ( useMedia ) { query = item.media; cleaned = this.convertURLs( cleaned, item.href ); } else{ query = allQueries[ i ].match( re.findStyles ) && RegExp.$1; cleaned = RegExp.$2 && this.convertURLs( RegExp.$2, item.href ); } queries = query.split( ',' ); for ( k = 0; k < queries.length; k++ ) { query = queries[ k ]; media = query.split( '(' )[ 0 ].match( re.only ) && RegExp.$2; if ( 'print' == media ) { continue; } if ( query.replace( re.minmaxwh, '' ).match( re.other ) ) { continue; } this.sheets[ item.key ].queries.push( { minw : query.match( re.minw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || '' ), maxw : query.match( re.maxw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || '' ), styles : cleaned } ); } } }, /** * Applies simulated media queries to the page. * * @since 1.10 * @method applyStyles */ applyStyles: function() { var head = $( 'head' ), styles = null, style = null, sheet = null, key = null, query = null, i = null, min = null, max = null, added = false; this.clearStyles(); for ( key in this.sheets ) { styles = ''; style = $( '' ); sheet = this.sheets[ key ]; if ( ! sheet.queries.length || ! this.width ) { continue; } styles += sheet.all; for ( i = 0; i < sheet.queries.length; i++ ) { query = sheet.queries[ i ]; min = query.minw; max = query.maxw; added = false; if ( min ) { min = parseFloat( min ) * ( min.indexOf( 'em' ) > -1 ? this.getEmPxValue() : 1 ); if ( this.width >= min ) { styles += query.styles; added = true; } } if ( max && ! added ) { max = parseFloat( max ) * ( max.indexOf( 'em' ) > -1 ? this.getEmPxValue() : 1 ); if ( this.width <= max ) { styles += query.styles; } } } this.styles.push( style ); head.append( style ); style.html( styles ); sheet.element.remove(); if ( this.callback ) { this.callback(); this.callback = null; } } }, /** * Clears all style tags used to render * simulated queries. * * @since 1.10 * @method applyStyles */ clearStyles: function() { var head = $( 'head' ), key = null, styles = this.styles.slice( 0 ); this.styles = []; for ( key in this.sheets ) { if ( ! this.sheets[ key ].element.parent().length ) { head.append( this.sheets[ key ].element ); } } setTimeout( function() { for ( var i = 0; i < styles.length; i++ ) { styles[ i ].empty(); styles[ i ].remove(); } }, 50 ); }, /** * Removes comments, keyframes and empty media * queries from a CSS style string. * * @since 2.0.6 * @method styles */ cleanStyles: function( styles ) { var re = this.regex; return styles.replace( re.comments, '' ).replace( re.keyframes, '' ).replace( re.empty, '' ); }, /** * Converts relative URLs to absolute URLs since the * styles will be added to a