1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	/**
  6 	 * Only grows, never shrinks.
  7 	 *
  8 	 * @private
  9 	 * @type {number}
 10 	 */
 11 	var uniqueIdCounter = 0;
 12 
 13 	/**
 14 	 * Generates a unique id with an optional prefix.
 15 	 *
 16 	 * The returned value is only unique among other returned values,
 17 	 * not globally.
 18 	 *
 19 	 * @public
 20 	 * @param {string}
 21 	 *        Optional prefix for the id to be generated.
 22 	 * @return {string}
 23 	 *        Never the same string more than once.
 24 	 */
 25 	function uniqueId(prefix) {
 26 		return (prefix || '') + (++uniqueIdCounter);
 27 	}
 28 
 29 	/**
 30 	 * Escapes the given property name by prefixing dots with backslashes.
 31 	 *
 32 	 * @param {string} name The property name to escape.
 33 	 * @return {string} Escaed string.
 34 	 */
 35 	function escapePropertyName(name) {
 36 		return name.replace(/\./g, '\\.');
 37 	}
 38 
 39 	/**
 40 	 * A regular expression to identify executable script tags.
 41 	 *
 42 	 * @type {RegExp}
 43 	 */
 44 	var rgxpScriptType = /\/(java|ecma)script/i;
 45 
 46 	/**
 47 	 * A regular expression to find opening script tags.
 48 	 *
 49 	 * @type {RegExp}
 50 	 */
 51 	var rgxpScriptTag = new RegExp('<script(\\s[^>]*?)?>', 'ig');
 52 
 53 	/**
 54 	 * A regular expression to find script types.
 55 	 *
 56 	 * @type {RegExp}
 57 	 */
 58 	var rgxpType = new RegExp(
 59 		' type\\s*=\\s*'
 60 			+ '[\\"\\\']'
 61 			+ '([^\\"\\\']*[^\\s][^\\"\\\']*)'
 62 			+ '[\\"\\\']',
 63 		'i'
 64 	);
 65 
 66 	var rand = Math.random().toString().replace('.', '');
 67 
 68 	/**
 69 	 * Places sentinal strings in front of every executable script tag.
 70 	 *
 71 	 * @param {string} html HTML markup
 72 	 * @return {string} HTML string with marked <script> tags.
 73 	 */
 74 	function markScriptTagLocations(html) {
 75 		var i = 0;
 76 		return html.replace(rgxpScriptTag, function (str, substr, offset) {
 77 			var type = substr && substr.match(rgxpType);
 78 			if (!type || rgxpScriptType.test(type)) {
 79 				return rand + (i++) + str;
 80 			}
 81 			return str;
 82 		});
 83 	}
 84 
 85 	/**
 86 	 * Masks a <script> tag's `type' attribute by replacing it with a random
 87 	 * string.
 88 	 *
 89 	 * Masking script tags is done to prevent them from being handled specially
 90 	 * by jQuery, which removes javascript/ecmascript tags when appending DOM
 91 	 * elements into the document, but executes them.
 92 	 *
 93 	 * unmakeScriptType() reverses this.
 94 	 *
 95 	 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the
 96 	 *                                       <script> tag that is to have its
 97 	 *                                       type attribute masked.
 98 	 */
 99 	function maskScriptType($script) {
100 		var type = $script.attr('type');
101 		$script.attr('type', rand).attr('data-origtype', type);
102 	}
103 
104 	/**
105 	 * Restores a <script> tag's original type attribute value if it had been
106 	 * masked using maskScriptType().
107 	 *
108 	 * Essentially the reverse of maskScriptType().
109 	 *
110 	 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the
111 	 *                                       <script> tag that is to have its
112 	 *                                       type attribute unmasked.
113 	 */
114 	function unmaskScriptType($script) {
115 		var orig = $script.attr('data-origtype');
116 		if (typeof orig === 'string') {
117 			$script.attr('type', orig).removeAttr('data-origtype');
118 		} else {
119 			$script.removeAttr('type');
120 		}
121 	}
122 
123 	/**
124 	 * Replaces the type attribute of <script> tags with a value that will
125 	 * protect them from being specially handled by jQuery.
126 	 *
127 	 * @param {string} html Markup
128 	 * @return {string} Markup with <script> tags protected from jQuery.
129 	 */
130 	function protectScriptTags(html, $scripts) {
131 		var i;
132 		var type;
133 		var $script;
134 		for (i = 0; i < $scripts.length; i++) {
135 			$script = $scripts.eq(i);
136 			type = $script.attr('type');
137 			if (!type || rgxpScriptType.test(type)) {
138 				maskScriptType($script);
139 				html = html.replace(rand + i, $script[0].outerHTML);
140 				unmaskScriptType($script);
141 			}
142 		}
143 		return html;
144 	}
145 
146 	/**
147 	 * Restores the type attribute for <script> tags that have been processed
148 	 * via protectScriptTags().
149 	 *
150 	 * @param {jQuery.<HTMLElement>} $element Root HTMLElement in which to
151 	 *                                        restore <script> tags.
152 	 */
153 	function restoreScriptTagTypes($element) {
154 		var $scripts = $element.find('script[type="' + rand + '"]');
155 		var $script;
156 		var i;
157 		for (i = 0; i < $scripts.length; i++) {
158 			$script = $scripts.eq(i);
159 			$script.removeClass(rand + i);
160 			unmaskScriptType($script);
161 		}
162 	}
163 
164 	/**
165 	 * Joins the innerHTML of multiple elements.
166 	 *
167 	 * @param {jQuery.<HTMLElement>} $elements
168 	 * @return {string} A concatenated string of the contents of the given set
169 	 *                  of elements.
170 	 */
171 	function joinContents($elements) {
172 		var contents = '';
173 		$elements.each(function () {
174 			contents += jQuery(this).html();
175 		});
176 		return contents;
177 	}
178 
179 	/**
180 	 * Inserts the inner HTML of the given HTML markup while preserving
181 	 * <script> tags, and still allowing jQuery to execute them.
182 	 *
183 	 * @param {jQuery.<HTMLElement>} $element
184 	 * @param {string} html
185 	 */
186 	function insertInnerHTMLWithScriptTags($element, html) {
187 		if (!rgxpScriptTag.test(html)) {
188 			$element.html(jQuery(html).contents());
189 			return;
190 		}
191 		var marked = markScriptTagLocations(html);
192 		var $scripts = jQuery(html).filter('script');
193 		var $html = jQuery(marked);
194 		var contents = joinContents($html.filter(':not(script)'));
195 		contents = protectScriptTags(contents, $scripts);
196 		$element.html(contents);
197 
198 		// Trap script errors originating from rendered tags and log it on the
199 		// console.
200 		try {
201 			$element.append($scripts);
202 		} catch (ex) {
203 			var _console = 'console'; // Because jslint is paranoid.
204 			if (typeof window[_console] === 'function') {
205 				window[_console].error(ex);
206 			}
207 		}
208 
209 		restoreScriptTagTypes($element);
210 	}
211 
212 	/**
213 	 * Merge class names from one element into another.
214 	 *
215 	 * The merge result will be a unqiue set of space-seperated class names.
216 	 *
217 	 * @param {jQuery.<HTMLElement>} $first jQuery unit set containing the DOM
218 	 *                                      whose class names are to be merged.
219 	 * @param {jQuery.<HTMLElement>} $second jQuery unit set containing the DOM
220 	 *                                       whose class names are to be merged.
221 	 * @return {string} The merge result of the merge: a unqiue set of
222 	 *                  space-seperated class names.
223 	 */
224 	function mergeClassNames($first, $second) {
225 		var first = ($first.attr('class') || '').split(' ');
226 		var second = ($second.attr('class') || '').split(' ');
227 		var names = first.concat(second).sort();
228 		var i;
229 		for (i = 1; i < names.length; i++) {
230 			if (names[i] === names[i - 1]) {
231 				names.splice(i--, 1);
232 			}
233 		}
234 		return names.join(' ');
235 	}
236 
237 	/**
238 	 * Creates a map of attributes merged from their value in $from with their
239 	 * value in $to.
240 	 *
241 	 * Class names--unlike other attributes, which are simply copied from $from
242 	 * into $to--are treaded specially to produce a unique set of
243 	 * space-seperated class names.
244 	 *
245 	 * @param {jQuery.<HTMLElement>} $to jQuery unit set containing the DOM
246 	 *                                   element which should receive the
247 	 *                                   merged attributes.
248 	 * @param {jQuery.<HTMLElement>} $from jQuery unit set containing the DOM
249 	 *                                     element whose attributes will be
250 	 *                                     merged into the other.
251 	 * @param {object<string, string>} A associate array of attributes.
252 	 */
253 	function mergeAttributes($to, $from) {
254 		var from = $from[0].attributes;
255 		var to = $to[0].attributes;
256 		var i;
257 		var attr = {};
258 		for (i = 0; i < from.length; i++) {
259 			attr[from[i].name] = ('class' === from[i].name)
260 			                   ? mergeClassNames($to, $from)
261 			                   : $from.attr(from[i].name);
262 		}
263 		return attr;
264 	}
265 
266 	/**
267 	 * Renders the given HTML string onto (not into) an DOM element.
268 	 * Does nearly the equivelent of $.replaceWith() or changing the element's
269 	 * outerHTML.
270 	 *
271 	 *             http://bugs.jquery.com/ticket/8142#comment:6
272 	 *             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
273 	 * 'All of jQuery's insertion methods use a domManip function internally  to
274 	 * clean/process elements before and after they are inserted into  the  DOM.
275 	 * One of the things the domManip function  does  is  pull  out  any  script
276 	 * elements about to  be  inserted  and  run  them  through  an  "evalScript
277 	 * routine" rather than inject them with the rest of the  DOM  fragment.  It
278 	 * inserts the scripts separately, evaluates them,  and  then  removes  them
279 	 * from the DOM.
280 	 *
281 	 * 'I believe that  one  of  the  reasons  jQuery  does  this  is  to  avoid
282 	 * "Permission Denied" errors that  can  occur  in  Internet  Explorer  when
283 	 * inserting scripts under certain circumstances. It also avoids  repeatedly
284 	 * inserting/evaluating the  same  script  (which  could  potentially  cause
285 	 * problems) if it is within a containing element that you are inserting and
286 	 * then moving around the DOM.'
287 	 *
288 	 * @param {jQuery.<HTMLElement>} $element jQuery unit set containing the
289 	 *                                        DOM element we wish to render the
290 	 *                                        given html content onto.
291 	 * @param {string} html HTML content which will become give to the element.
292 	 */
293 	function renderOnto($element, html) {
294 		insertInnerHTMLWithScriptTags($element, html);
295 		var attr = mergeAttributes($element, jQuery(html));
296 		var name;
297 		for (name in attr) {
298 			if (attr.hasOwnProperty(name)) {
299 				$element.attr(name, attr[name]);
300 			}
301 		}
302 	}
303 
304 	/**
305 	 * Removes the given chainback instance from its cached location.
306 	 *
307 	 * @param {Chainback} chainback Instance to remove from cache.
308 	 */
309 	function decache(chainback) {
310 		if (chainback._constructor.__gcncache__[chainback.__gcnhash__]) {
311 			delete chainback._constructor.__gcncache__[chainback.__gcnhash__];
312 		}
313 	}
314 
315 	GCN.uniqueId = uniqueId;
316 	GCN.escapePropertyName = escapePropertyName;
317 	GCN.renderOnto = renderOnto;
318 	GCN.decache = decache;
319 
320 }(GCN));
321