/* file: Templates.js 

WARNING: Templates in separate files can be sensitive to race conditions on when the different 
template files are loaded. Templates in one file MUST therefore NEVER refer to templates from 
another file. 

## Mods to Underscore

We use underscore.js templates, but with a couple of tweaks:

 - The input value (e.g. a SoDash data object) is always available as the 
 variable "context". Context is always set (even if there is no input value), 
 and always has a circular reference context.context = context.	
This (a) lets us to handle missing fields without undefined errors, 
and (b) lets us pass the item on to nested template calls.
 
 - Embedded javascript is via <script>...</script>, instead of <% ... %>
 (interpolation is unchanged as <%= %>).
 Important: We're using a simple regex which only matches exactly on "<script>"
 This enables syntax highlighting in the editor.
 Side effects: be careful if using templates to insert "static" 
 script blocks (but that's probably a bad idea anyway).

## Usage

Define a template in an html file like this:
<template id='MyWidgetName'>
	<div class='MyWidgetName'>etc</div>
</template>

Load it via loadTemplates(url)

Use it via: templates.MyWidgetName(item)


## Cruft
Templates will hook up with old css (which could use a clean-up).

*/

// Use script tags for embedded javascript
//console.log("Original Template Settings", _.templateSettings, _.templateSettings.interpolate.source, _.templateSettings.evaluate.source);
_.templateSettings = {
  interpolate: /<%=([\s\S]+?)%>/g,
  evaluate: /<script>([\s\S]+?)<\/script>/g
};

/** Compile the templates and store them in templates namespace */
var templates = {};

/**
 * Poke a SoDash item so it can serve as an underscore template context.
Lets context.property be used for potentially undefined properties.
This is called automatically for templates loaded via loadTemplates().
 * @param item
 * @returns item (modified)
 * It is harmless to call this multiple times
 */
var _templateContext = function(item) {
	if (!item) item = {};
	item.context = item;
	// Set (or reset) a property (Warning: modifies obj!)
	// obj can be null, in which case a new object is created.
	// Typical use case: <%= templates.subTemplate(set(context.child, key, value)); %>
	// ?? promote this to a Utils function?
	item.set = function(obj, key, value) {
		if ( ! obj) obj = {};
		obj[key] = value;
		return obj;
	};
	//item.pass = context;
	return item;
}

/**
 * Compile & store the template. Does not apply it.
 * @param tmpl String containing <template>s 
 */
function loadTemplates2(tmpl) {
	try {
		var name = tmpl.replace(/.+?id='(.+?)'[\s\S]+/, "$1");
		var guts = tmpl.replace(/<template.+?>([\s\S]+?)<\/template>/, "$1");
		
		assert(name, tmpl);
		assert(name.indexOf(" ") == -1, name);
		
		var tFn = _.template(guts);
		
		// Make sure _templateContext is always applied.
		// Add in guaranteed error reporting (otherwise Firefox is silent).
		templates[name] = function(data){
			try {
				return tFn(_templateContext(data));
			} catch (err) {
				console.log("template", name, err);			
				console.trace();
				throw err;
			}
		}.bind({'name': name});

		// templates remember their name
		templates[name].Name = name; //.name breaks in WebKit
		console.log("templates","Loaded "+name);
		return name;
	} catch(e) {
		console.log("ERROR: failed to load template from: "+tmpl, e);
	}
};


function ajaxifyFromTemplate(template, tName) {
	assert(template, tName);
	console.log("templates","Ajaxify "+tName);
	// Get elements using this template which are not ajaxed
	// This is where the "FromTemplate" divs get expanded
	var elems = $('[template="' + tName + '"]').not(".ajaxed");
	console.log("templates", tName, "Found "+elems.length);
	// 				
	elems.each(function() {
		var elem = $(this);
		console.log("templates","...Applying "+tName, elem);
		var cntxt = {};				
		var tContext = elem.attr('context');
		if (tContext) {			
			cntxt = $.parseJSON(tContext);
		}

		var h = template(cntxt);

		elem.html(h);

		elem.addClass('ajaxed');
	});

	$(document).trigger("FromTemplates:done");
}
/** Check each template for un-ajaxed divs.
	Is this thread/race safe w.r.t. template loading??
	But needed to handle ajax loading of FromTemplate divs */
function ajaxifyFromTemplates() {	
	for(var tName in templates) {
		var template = templates[tName];
		ajaxifyFromTemplate(template, tName);
	}
}


/**
 * Use this to load-then-process templates
 * @param url
 * @param callback
 */
function loadTemplates(url, callback) {
	console.log("templates","Loading from "+url+"...");
	$.ajax({
		url: url,
		dataType:'html',
		success: function(result) {		
			console.log("templates","...Loaded from "+url);
			var regex = /<template\s+id='.+?'\s*>[\s\S]+?<\/template>/gm;
			var tmpls = result.match(regex); //regex.exec(result);

			if(!tmpls) {
				console.log("No templates found in " + url);
				return;
			}

			var templateNames = new Array();

			for(var i=0; i<tmpls.length; i++) {
				var name = loadTemplates2(tmpls[i]);
				assert(name, tmpls[i]);
				templateNames.push(name);
			}				

			// Now that all the templates are loaded, try applying them
			for(var i=0; i<templateNames.length; i++) {
				var name = templateNames[i];
				ajaxifyFromTemplate(templates[name], name);
			}

			// Fire this event when templates are loaded.
			if(callback !== undefined) {
				// Wrap in a JQuery document-ready delay  
				$(callback);
			}
		}		
	});
};

console.log("registering ajaxify templates...");
ajaxifyFunctions.push(ajaxifyFromTemplates);
$(ajaxifyFromTemplates());


