1/*
2 * respond.js - A small and fast polyfill for min/max-width CSS3 Media Queries
3 * Copyright 2011, Scott Jehl, scottjehl.com
4 * Dual licensed under the MIT or GPL Version 2 licenses.
5 * Usage: Check out the readme file or github.com/scottjehl/respond
6*/
7(function( win, mqSupported ){
8	//exposed namespace
9	win.respond		= {};
10
11	//define update even in native-mq-supporting browsers, to avoid errors
12	respond.update	= function(){};
13
14	//expose media query support flag for external use
15	respond.mediaQueriesSupported	= mqSupported;
16
17	//if media queries are supported, exit here
18	if( mqSupported ){ return; }
19
20	//define vars
21	var doc 			= win.document,
22		docElem 		= doc.documentElement,
23		mediastyles		= [],
24		rules			= [],
25		appendedEls 	= [],
26		parsedSheets 	= {},
27		resizeThrottle	= 30,
28		head 			= doc.getElementsByTagName( "head" )[0] || docElem,
29		links			= head.getElementsByTagName( "link" ),
30		requestQueue	= [],
31
32		//loop stylesheets, send text content to translate
33		ripCSS			= function(){
34			var sheets 	= links,
35				sl 		= sheets.length;
36
37			for( var i = 0; i < sl; i++ ){
38				var sheet		= sheets[ i ],
39					href		= sheet.href,
40					media		= sheet.media,
41					isCSS		= sheet.rel && sheet.rel.toLowerCase() === "stylesheet";
42
43				//only links plz and prevent re-parsing
44				if( !!href && isCSS && !parsedSheets[ href ] ){
45					if( !/^([a-zA-Z]+?:(\/\/)?(www\.)?)/.test( href )
46						|| href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){
47						requestQueue.push( {
48							href: href,
49							media: media
50						} );
51					}
52					else{
53						parsedSheets[ href ] = true;
54					}
55				}
56			}
57			makeRequests();
58
59		},
60
61		//recurse through request queue, get css text
62		makeRequests	= function(){
63			if( requestQueue.length ){
64				var thisRequest = requestQueue.shift();
65
66				ajax( thisRequest.href, function( styles ){
67					translate( styles, thisRequest.href, thisRequest.media );
68					parsedSheets[ thisRequest.href ] = true;
69					makeRequests();
70				} );
71			}
72		},
73
74		//find media blocks in css text, convert to style blocks
75		translate		= function( styles, href, media ){
76			var qs		= styles.match( /@media ([^\{]+)\{((?!@media)[\s\S])*(?=\}[\s]*\/\*\/mediaquery\*\/)/gmi ),
77				ql		= qs && qs.length || 0,
78				//try to get CSS path
79				href	= href.substring( 0, href.lastIndexOf( "/" )),
80				repUrls = function( css ){
81					return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" );
82				},
83				useMedia = !ql && media;
84
85			//if path exists, tack on trailing slash
86			if( href.length ){ href += "/"; }
87
88			//if no internal queries exist, but media attr does, use that
89			//note: this currently lacks support for situations where a media attr is specified on a link AND
90				//its associated stylesheet has internal CSS media queries.
91				//In those cases, the media attribute will currently be ignored.
92			if( useMedia ){
93				ql = 1;
94			}
95
96
97			for( var i = 0; i < ql; i++ ){
98				var fullq;
99
100				//media attr
101				if( useMedia ){
102					fullq = media;
103					rules.push( repUrls( styles ) );
104				}
105				//parse for styles
106				else{
107					fullq	= qs[ i ].match( /@media ([^\{]+)\{([\S\s]+?)$/ ) && RegExp.$1;
108					rules.push( RegExp.$2 && repUrls( RegExp.$2 ) );
109				}
110
111				var eachq	= fullq.split( "," ),
112					eql		= eachq.length;
113
114				for( var j = 0; j < eql; j++ ){
115					var thisq	= eachq[ j ];
116					mediastyles.push( {
117						media	: thisq.match( /(only\s+)?([a-zA-Z]+)(\sand)?/ ) && RegExp.$2,
118						rules	: rules.length - 1,
119						minw	: thisq.match( /\(min\-width:[\s]*([\s]*[0-9]+)px[\s]*\)/ ) && parseFloat( RegExp.$1 ),
120						maxw	: thisq.match( /\(max\-width:[\s]*([\s]*[0-9]+)px[\s]*\)/ ) && parseFloat( RegExp.$1 )
121					} );
122				}
123			}
124
125			applyMedia();
126		},
127
128		lastCall,
129
130		resizeDefer,
131
132		//enable/disable styles
133		applyMedia			= function( fromResize ){
134			var name		= "clientWidth",
135				docElemProp	= docElem[ name ],
136				currWidth 	= doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp,
137				styleBlocks	= {},
138				dFrag		= doc.createDocumentFragment(),
139				lastLink	= links[ links.length-1 ],
140				now 		= (new Date()).getTime();
141
142			//throttle resize calls
143			if( fromResize && lastCall && now - lastCall < resizeThrottle ){
144				clearTimeout( resizeDefer );
145				resizeDefer = setTimeout( applyMedia, resizeThrottle );
146				return;
147			}
148			else {
149				lastCall	= now;
150			}
151
152			for( var i in mediastyles ){
153				var thisstyle = mediastyles[ i ];
154				if( !thisstyle.minw && !thisstyle.maxw ||
155					( !thisstyle.minw || thisstyle.minw && currWidth >= thisstyle.minw ) &&
156					(!thisstyle.maxw || thisstyle.maxw && currWidth <= thisstyle.maxw ) ){
157						if( !styleBlocks[ thisstyle.media ] ){
158							styleBlocks[ thisstyle.media ] = [];
159						}
160						styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] );
161				}
162			}
163
164			//remove any existing respond style element(s)
165			for( var i in appendedEls ){
166				if( appendedEls[ i ] && appendedEls[ i ].parentNode === head ){
167					head.removeChild( appendedEls[ i ] );
168				}
169			}
170
171			//inject active styles, grouped by media type
172			for( var i in styleBlocks ){
173				var ss		= doc.createElement( "style" ),
174					css		= styleBlocks[ i ].join( "\n" );
175
176				ss.type = "text/css";
177				ss.media	= i;
178
179				if ( ss.styleSheet ){
180		        	ss.styleSheet.cssText = css;
181		        }
182		        else {
183					ss.appendChild( doc.createTextNode( css ) );
184		        }
185		        dFrag.appendChild( ss );
186				appendedEls.push( ss );
187			}
188
189			//append to DOM at once
190			head.insertBefore( dFrag, lastLink.nextSibling );
191		},
192		//tweaked Ajax functions from Quirksmode
193		ajax = function( url, callback ) {
194			var req = xmlHttp();
195			if (!req){
196				return;
197			}
198			req.open( "GET", url, true );
199			req.onreadystatechange = function () {
200				if ( req.readyState != 4 || req.status != 200 && req.status != 304 ){
201					return;
202				}
203				callback( req.responseText );
204			}
205			if ( req.readyState == 4 ){
206				return;
207			}
208			req.send();
209		},
210		//define ajax obj
211		xmlHttp = (function() {
212			var xmlhttpmethod = false,
213				attempts = [
214					function(){ return new ActiveXObject("Microsoft.XMLHTTP") },
215					function(){ return new ActiveXObject("Msxml3.XMLHTTP") },
216					function(){ return new ActiveXObject("Msxml2.XMLHTTP") },
217					function(){ return new XMLHttpRequest() }
218				],
219				al = attempts.length;
220
221			while( al-- ){
222				try {
223					xmlhttpmethod = attempts[ al ]();
224				}
225				catch(e) {
226					continue;
227				}
228				break;
229			}
230			return function(){
231				return xmlhttpmethod;
232			};
233		})();
234
235	//translate CSS
236	ripCSS();
237
238	//expose update for re-running respond later on
239	respond.update = ripCSS;
240
241	//adjust on resize
242	function callMedia(){
243		applyMedia( true );
244	}
245	if( win.addEventListener ){
246		win.addEventListener( "resize", callMedia, false );
247	}
248	else if( win.attachEvent ){
249		win.attachEvent( "onresize", callMedia );
250	}
251})(
252	this,
253	(function( win ){
254
255		//for speed, flag browsers with window.matchMedia support and IE 9 as supported
256		if( win.matchMedia ){ return true; }
257
258		var bool,
259			doc			= document,
260			docElem		= doc.documentElement,
261			refNode		= docElem.firstElementChild || docElem.firstChild,
262			// fakeBody required for <FF4 when executed in <head>
263			fakeUsed	= !doc.body,
264			fakeBody	= doc.body || doc.createElement( "body" ),
265			div			= doc.createElement( "div" ),
266			q			= "only all";
267
268		div.id = "mq-test-1";
269		div.style.cssText = "position:absolute;top:-99em";
270		fakeBody.appendChild( div );
271
272		div.innerHTML = '_<style media="'+q+'"> #mq-test-1 { width: 9px; }</style>';
273		if( fakeUsed ){
274			docElem.insertBefore( fakeBody, refNode );
275		}
276		div.removeChild( div.firstChild );
277		bool = div.offsetWidth == 9;
278		if( fakeUsed ){
279			docElem.removeChild( fakeBody );
280		}
281		else{
282			fakeBody.removeChild( div );
283		}
284		return bool;
285	})( this )
286);