Source: scripts/server/webjs-server.js

/**
 * WebJS server module
 * @module webjs-server
 */

/** 
 * JQuery extensions for templating and returning the result
 * @class jQuery
 */
jQuery.fn.extend(
/** @lends jQuery */
{
	contextPath: function(attr, prefix) {
		return this.attr(attr, function() { return request.contextPath + this[attr].replace(prefix, ""); });
	},
	print: function(doctype) {
		doctype = doctype || "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
		return doctype
		 + "\n"
		 + this[0].innerHTML;
	},
	
	/**
	 * Loop template addition to jQuery.<br/>
	 * For example, if you want to copy a div.blog-entry using an array of entries :<br/>
	 * <pre>$(".blog-entry").template(entries, function(entry, index) {
	 *	 this.appendTo(".blog");
	 *	 this.attr("id", "blog-entry-"+entry.id);
	 *	 this.attr("data-pk", entry.id);
	 *	 this.find(".blog-entry-title").text(entry.title);
	 *	 this.find(".blog-entry-author").text(entry.author);
	 *	 this.find(".blog-entry-text").text(entry.text);
	 * });</pre>
	 * @param {Array} list
	 * @param {function} handler
	 */
	template: function(list, handler) {
		var template = this.remove();
		list.forEach(function() {
			handler.apply(template.clone(), arguments);
		});
	},
	
	list: function(list, separator, handler) {
		var result = [];
		list.forEach(function(entry, index) {
			result.push( handler(entry, index) );
		});
		return this.html( result.join(separator) );
	},
	validate: function() {
	
	}
});

// String formatting
String.format = function(source, params) {
	if ( arguments.length == 1 ) 
		return function() {
			var args = jQuery.makeArray(arguments);
			args.unshift(source);
			return String.format.apply( this, args );
		};
	if ( arguments.length > 2 && params.constructor != Array  ) {
		params = jQuery.makeArray(arguments).slice(1);
	}
	if ( params.constructor != Array ) {
		params = [ params ];
	}
	jQuery.each(params, function(i, n) {
		source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
	});
	return source;
};

/** 
 * Prints given arguments to standard output
 * @param {Array} arguments Strings to be displayed
 */
function print() {
	java.lang.System.out.println($.makeArray(arguments).join(", "));
}

/**
 * Loads and executes a list of JavaScripts files
 * @function module:webjs-server~load
 * @param {Array} scripts List of scripts to be loaded
 * @see Defined in Java code fr.dz.webjs.scripts.WebJSScriptLibrary
 */

/**
 * Reads a file and returns its content
 * @function module:webjs-server~readFile
 * @param {String} filename The file name
 * @return {String} The file content
 * @see Defined in Java code fr.dz.webjs.scripts.WebJSScriptLibrary
 */

/**
 * Gets a message from the resource bundles using client locale
 * @function module:webjs-server~getMessage
 * @param {String} key The message key
 * @param {Array} parameters The message parameters
 * @return {String} The message
 * @see Defined in Java code fr.dz.webjs.scripts.WebJSScriptLibrary
 */

/**
 * Gets a Log4J logger from JS. A JS exception is needed to get script name and script line.
 * @function module:webjs-server~internalGetLogger
 * @param {String} exception The exception
 * @see Defined in Java code fr.dz.webjs.scripts.WebJSScriptLibrary
 * @private
 * @deprecated Use {@link module:messages-server~Messages} instead
 */

/**
 * Gets a Log4J logger from JS.
 * @function module:webjs-server~getLogger
 * @return {Object} A Log4J logger reflecting javascript.<script_file_name>:<line_number>
 * @deprecated Use {@link module:messages-server~Messages} instead
 */
function getLogger() {
	try {
		throw new Packages.java.lang.Exception("JS logger exception");
	} catch(e) {
		return internalGetLogger(e);
	}
}

/**
 * WebJS server utility
 * @namespace
 */
var WebJS = {
	
	/**
     * Default WebJS client scripts added to generated web pages
     * @type {Array}
     * @constant
     */
	CLIENT_SCRIPTS: ["webjs-static/js/moment.js", "webjs-static/js/jquery.js", "webjs-static/js/jquery-ui.js", 
	                 "webjs-generated/js/configuration-client.js", "webjs-static/js/webjs-client.js", 
	                 "webjs-static/js/messages-client.js" ],
	
	/**
     * Default WebJS CSSs added to generated web pages
     * @type {Array}
     * @constant
     */
	CSS: ["webjs-static/css/webjs.css", "webjs-static/css/messages.css" ],
	
	/**
	 * WebJS initializations :
	 * <ul><li>loads the given template, if exists</li>
	 * <li>adds default WebJS client scripts</li>
	 * <li>relocate links to match request</li>
	 * <li>feeds messages</li></ul>
	 * @private
	 */
	init: function() {
		this.loadTemplate();
		this.addHeaders();
		this.relocateLinks();
	},
	
	/** 
	 * Loads the associated template
	 * @private
	 */
	loadTemplate: function() {
		var template = $("html").attr("webjs:template");
		if ( template !== undefined ) {
			var templateDocument = $(readFile(template));
			
			// Fills headers
			var templateHead = templateDocument.find("head");
			$("head").children().each(function(){
				templateHead.append($(this).clone());
			});
			
			// Fills include tags
			templateDocument.find(WebJS.escape("webjs:include")).each(function(){
				var include = $(this);
				var source = $(WebJS.escapeId(include.attr("id")));
				include.replaceWith(source.clone());
			});
			
			// Replaces document by the new one
			$(document).find("head").replaceWith(templateDocument.find("head"));
			$(document).find("body").replaceWith(templateDocument.find("body"));
			
			// Deletes template attribute
			$("html").removeAttr("webjs:template");
		}
	},
	
	/**
	 * If links are relative, adds the request base URI before
	 * @private
	 */
	relocateLinks: function() {
		$("link, a").each(function() {
			var jq = $(this);
			var href = jq.attr("href");
			if ( href && href != '' && href.indexOf(":") == -1 && href.indexOf("#") != 0 ) {
				jq.attr("href", request.contextRoot + "/" + href );
			}
		});
		$("script, img").each(function() {
			var jq = $(this);
			var src = jq.attr("src");
			if ( src && src != '' && src.indexOf(":") == -1 && src.indexOf("#") != 0 ) {
				jq.attr("src", request.contextRoot + "/" + src );
			}
		});
	},
	
	/**
	 * Add WebJS client scripts and CSS to HTML headers
	 * @private
	 */ 
	addHeaders: function() {
		$.makeArray(this.CLIENT_SCRIPTS).reverse().forEach(function(clientScript, index) {
			var script = $("<script/>")
				.attr("src", clientScript)
				.attr("type", "text/javascript");
			$("head").prepend(script);
		});
		$.makeArray(this.CSS).reverse().forEach(function(css, index) {
			var link = $("<link/>")
				.attr("href", css)
				.attr("rel", "stylesheet");
			$("head").prepend(link);
		});
		
		// Theme CSS
		var link = $("<link/>")
			.attr("href", "webjs-static/themes/"+this.getTheme()+"/jquery-ui.css")
			.attr("rel", "stylesheet");
		$("head").prepend(link);
	},
	
	/**
	 * Feeds localized messages using resources
	 * @param jqRoot jQuery root
	 * @private
	 */ 
	feedLocalizedMessages: function(jqRoot) {
		jqRoot.find(WebJS.escape("webjs:message")).each(function(){
			var messageNode = $(this);
			var id = messageNode.attr("id");
			var parameters = [];
			messageNode.find(WebJS.escape("webjs:parameter")).each(function(){
				parameters.push($(this).attr("value"));
			});
			var message = getMessage(id, parameters);
			if ( message ) {
				messageNode.replaceWith(message);
			} else {
				messageNode.replaceWith("/!\\ "+id+" /!\\");
			}
		});
	},
	
	/** 
	 * Returns the page result :
	 * <ul><li>feeds messages</li>
	 * <li>fixes textareas</li></ul>
	 * @private
	 */
	returnResult: function() {
		
		// The whole page need to be rendered (no ajax)
		if ( this.isWholePageRendered() ) {
			
			// Feed localization messages
			this.feedLocalizedMessages($("html"));
			
			// Send messages to the client
			var append = $("body").append("<messages/>");
			for ( var i = 0; i < request.messages.size(); i++ ) {
				append.append($("<message><level>"+request.messages.get(i).level+"</level><text>"+request.messages.get(i).message+"</text></message>"));
			}
			
			// Fix some HTML tags
			return this.fixHTMLTags($(document).print());
		}
		// Else, it's an Ajax result, so we need to generate a result
		else {
			var result = $("<html><webpart><update></update><messages></messages></webpart></html>");
			
			// HTML updates
			var append = result.find("update");
			request.renderSelector.split(",").forEach(function(selector,index) {
				$(selector).each(function(){
					append.append($(this).clone());
				});
			});
			
			// Feed localization messages
			this.feedLocalizedMessages(result);
			
			// Sends messages to the client
			append = result.find("messages");
			for ( var i = 0; i < request.messages.size(); i++ ) {
				append.append($("<message><level>"+request.messages.get(i).level+"</level><text>"+request.messages.get(i).message+"</text></message>"));
			}
			
			// Fix some HTML tags
			return this.fixHTMLTags(result.html());
		}
	},
	
	/**
	 * Replaces <textarea /> to <textarea></textarea> because browsers can't manage it...
	 * Idem for <div />
	 * @private
	 */ 
	fixHTMLTags: function(html) {
		["textarea", "div"].forEach(function(tag, index){
			html = html.replace(new RegExp("<"+tag+"([^>]*)\\/>","g"), "<"+tag+"$1></"+tag+">");
		});
		return html;
	},
	
	/**
	 * Returns true if the whole page is rendered
	 */
	isWholePageRendered: function() {
		return request.renderSelector == null || request.renderSelector == '' || request.renderSelector == request.DEFAULT_RENDER_SELECTOR;
	},
	
	/**
	 * Tests the existence of bind variables
	 * @param {Array} bindNames List of bind variables names
	 * @param {function} callbackExists Function executed if the bindings exist
	 * @param {function} callbackNotExists Function executed if the bindings not exist
	 */
	checkBindings: function(bindNames, callbackExists, callbackNotExists) {
		var allExist = true;
		$.makeArray(bindNames).forEach(function(bindName, index) {
			allExist = allExist && eval("typeof("+bindName+")") != "undefined";
		});
		if ( allExist && callbackExists && typeof(callbackExists) == "function" ) {
			callbackExists();
		}
		if ( ! allExist && callbackNotExists && typeof(callbackNotExists) == "function" ) {
			callbackNotExists();
		}
	},
	
	/**
	 * Appends a static HTML file into a given node
	 * @param {Object} node jQuery Node to be appended
	 * @param {String} htmlFile HTML filename
	 */
	appendHtmlFile: function(node, htmlFile) {
		var fileContent = $(readFile(htmlFile)).find("body");
		node.append(fileContent);
	},
	
	/**
	 * Escapes an HTML id
	 * @param {String} id The id to be escaped
	 * @return {String} The escaped id
	 */ 
	escapeId: function(id) {
		return "#" + this.escape(id);
	},
	
	/**
	 * Escapes an HTML class
	 * @param {String} clazz The class to be escaped
	 * @return {String} The escaped class
	 */
	escapeClass: function(clazz) {
		return "." + this.escape(clazz);
	},
	
	/**
	 * Escapes a selector
	 * @param {String} selector The selector to be escaped
	 * @return {String} The escaped selector
	 * @todo To be completed
	 */ 
	escape: function(selector) {
		return selector.replace(":", "\\:");
	},
	
	/**
	 * Return the used theme
	 * @return {String} The theme name
	 * @todo Parameterize theme for the user
	 */
	getTheme: function() {
		return request.webJS.configuration.xmlConfiguration.clientBehavior.defaultTheme;
	},
};

// WebJS initialization
WebJS.init();