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