jsbeans

jsbeans  1.0.0

jsbeans > jsbeans > mvc.js (source view)
Search:
 
Filters
/*!
 * Copyright (c) 2009 Francesco Mele jsbeans@francescomele.com
 *
 * This Software is licenced under the LGPL Licence (GNU Lesser General 
 * Public License).
 * In addition to the LGPL Licence the Software is subject to the 
 * following conditions:
 * 
 * 	i	every modification must be public and comunicated to the Author
 * 	ii	every "jsbean" added to this library must be self consistent 
 * 		except for the dependence from jsbeans-x.x.x.js
 * 	iii	copyright notice and this permission notice shall be included 
 * 		in all copies or substantial portions of the Software
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * <code>jsbeans.mvc</code> is a small MVC (Model-View-Controller) Framework.<br/>
 * Here are APIs, see <a href="../util/script_other/mvc/docs/index.html">reference</a> for usage or take a look at a <a href="../jsbeans.mvc.html">sample app</a>.
 * @namespace jsbeans
 * @class mvc
 * @static
 * */
jsbeans.mvc = {
	/**
	 * Main responsable for rendering a content.<br/>
	 * It provides utility methods to match Model against a corresponding View.<br/>
	 * Should be the last call in a custom controller function/method, after creating or fetching a Model.
	 * @namespace jsbeans.mvc
	 * @class controller
	 * @static
	 */
	controller: {
		/**
		 * A local cache of generic template Functions. See <a href="https://github.com/olado/doT">doU.js</a> documentation for a complete reference.
		 * @static
		 * */
		cache: [],
		/**
		 * It matches {@param} <code class="param">model</code> against {@param} <code class="param">view</code> and then invokes {@param} <code class="param">callback</code>.<br/>
		 * Internally it used <code>view.load</code> and <code><a href="https://github.com/olado/doT">doU.js</a></code> Javascript Template Engine, version 0.1.2. There's no need to include in your libraries as it has been directly included in source (doU license permits it).
		 * @method execute
		 * @param model {Object | JSON} data in Object or JSON format. May be local or remote, via AJAX request.
		 * @param view {String} the name of used View remotely loaded in <code>data-templatePath</code> custom attribute of <code>view</code> script tag (<code>&lt;script type="text/javascript" src="jsbeans/mvc.js" data-templatePath="views">&lt;/script></code>) or inline into document if <code>inline="true"</code> has been set (<code>&lt;script type="text/javascript" src="jsbeans/mvc.js" inline="true">&lt;/script></code>).
		 * @param callback {Function} a Function fired after View load and its match against Model. It takes the result of the matching as argument.<br/>
		 * 		Usually <code>callback(html)</code> simply does a <code>document.getElementById('anID').innerHTML = html;</code>
		 * @static
		 * */
		execute: function(model, v, clbk) {
			jsbeans.mvc.view.load(v, function(tmpl) {
				if (jsbeans.mvc.controller.cache[v] == null || typeof jsbeans.mvc.controller.cache[v] == "undefined") {
					// source from doU.js version 0.1.2
					var str = '', tb = "{{", te = "}}", m, l; 
					var arr = tmpl.replace(/\s*<!\[CDATA\[\s*|\s*\]\]>\s*|[\r\n\t]|(\/\*[\s\S]*?\*\/)/g, '').split(tb).join(te +'\x1b').split(te);
					
					for (m=0,l=arr.length; m < l; m++) {
						str += arr[m].charAt(0) !== '\x1b' ?
								"out+='" + arr[m].replace(/(\\|["'])/g, '\\$1') + "'" : (arr[m].charAt(1) === '=' ?
										';out+=(' + arr[m].substr(2) + ');' : (arr[m].charAt(1) === '!' ?
												';out+=(' + arr[m].substr(2) + ").toString().replace(/&(?!\\w+;)/g, '&#38;').split('<').join('&#60;').split('>').join('&#62;').split('" + '"' + "').join('&#34;').split(" + '"' + "'" + '"' + ").join('&#39;').split('/').join('&#x2F;');" : ';' + arr[m].substr(1)));
					}
					
					str = ('var out="";'+str+';return out;').split("out+='';").join('').split('var out="";out+=').join('var out=');
					
					try {
						jsbeans.mvc.controller.cache[v] = new Function("data", str);
					} 
					catch (e) {
						// hide errors as usual
						//alert("Error:\n" + e.message);
					}
				}
				clbk(jsbeans.mvc.controller.cache[v](model));
			});
		}
	},
	/**
	 * Simple small collection of utility methods to manage Object.
	 * @namespace jsbeans.mvc
	 * @class model
	 * @static
	 */
	model: {
		/**
		 * Tries to create a new instance of {@param} <code class="param">model</code> evaluating <code>new &lt;model>()</code>
		 * @method create
		 * @param model {String} a name of a class
		 * @return {Object} a new instance of <code>&lt;model></code>. <code>null</code> otherwise
		 * @static
		 * */
		create: function(m) {
			try {
				eval("var res = new " + m + "();");
				return res;
			} 
			catch (e) {
				return null;
			}
		},
		/**
		 * Cicles through {@param} <code class="param">json</code> keys and stores corresponding values into {@param} <code class="param">object</code>.<br/>
		 * Note that {@param} <code class="param">object</code> will have new values only if it already has same properties, or a superset, of {@param} <code class="param">json</code>. It does <strong>not</strong> add new property on {@param} <code class="param">object</code>.
		 * @param json {JSON} a set of key/value pairs in JSON format
		 * @param object {Object} an instance of an Object.
		 * @static
		 * */
		populate: function(json, obj) {
			for (var p in json) {
				if (obj.hasOwnProperty(p)) {
					obj[p] = json[p];
				}
			}
		},
		/**
		 * Merges objects passed as arguments. In other words the returning object will be a JSON resulting as union of all objects passed, with no duplicates.<br/>
		 * If first argument is an <code>Array</code> no other argument is taken into account and the merge process will cycle through that Array.<br/>
		 * Any subsequent key will override previously key/value pair.
		 * @param first {JSON | Object | Array} first (and unique in case of type Array) object to cycle through
		 * @param [object] {arguments[1..n]} optional arguments of objects to merge. Ignored when <code class="param">first</code> is of type <code>Array</code>
		 * @return {JSON} merged object. An empty JSON if <code class="param">first</code> is <code>null</code> or <code>undefined</code>
		 * @static
		 * */
		merge: function(o) {
			if (o != null && typeof(o) != "undefined") {
				var objs = arguments;
				if (o.constructor == Array) {
					objs = o;
				}
				var res = {};
				for (var i = 0, obj; obj = objs[i]; i++) {
					for (var p in obj) {
						res[p] = obj[p];
					}
				}
				return res;
			}
			return {};
		}
	},
	/**
	 * Loads and store views.<br/>
	 * It contains useful methods to achieve performance and a simple XmlHttpRequest Object to decouple the framework from any external library.
	 * @namespace jsbeans.mvc
	 * @class view
	 * @static
	 */
	view: {
		/**
		 * A local cache of loaded views. Turns to be optimal for performance reasons.
		 * @property cache
		 * @type Array
		 * @default []
		 * @static
		 * */
		cache: [],
		/**
		 * Stores value of the path where templates (views) are found
		 * @property templatePath
		 * @type String
		 * @default null
		 * @static
		 * */
		templatePath: null,
		/**
		 * Tells if a view is in an inline script tag.
		 * @property inline
		 * @type Boolean
		 * @default false
		 * @static
		 * */
		inline: false,
		/**
		 * Loads templates in two different ways:
		 * <ol>
		 * <li>remotely from server (via a GET)</li>
		 * <li>locally in page (via a document.getElementById)</li>
		 * </ol>
		 * and stores them locally.<br/>
		 * In the first case templates must be located as defined in <code>data-templatePath</code> custom attribute of view script tag,
		 * in the second one you <strong>must</strong> use the custom attribute <code>inline</code> set to <code>true</code>.<br/>
		 * You cannot use both in a mixed way. In other words choose your storage: remote XOR local. Local takes priority over remote if both defined.<br/>
		 * To prevent errors remote calls are enqueued in a FIFO. If a call is in progress waits 50ms before retrying.
		 * @method load
		 * @param view {String} the name of the view to be loaded. For remote views the file must have <code>html</code> extension.
		 * @param callback {Function} a function fired after template is loaded
		 * @static
		 * */
		load: function(v, callback) {
			if (jsbeans.mvc.view.cache[v] == null || typeof jsbeans.mvc.view.cache[v] == "undefined") {
				if (jsbeans.mvc.view.inline) {
					var tmpl = document.getElementById(v);
					if (tmpl != null && typeof(tmpl) != "undefined") {
						jsbeans.mvc.view.cache[v] = tmpl.innerHTML;
						callback(jsbeans.mvc.view.cache[v]);
					}
				}
				else {
					var q = {view: v, callback: callback};
					if (!jsbeans.mvc.view.queue.has(q)) {
						jsbeans.mvc.view.queue.objects.push(q);
					}
					if (jsbeans.mvc.view.queue.processing) {
							setTimeout(function() {
								jsbeans.mvc.view.load(v, callback);
							}, 50);
						return;
					}
					jsbeans.mvc.view.queue.doProcess();
				}
			}
			else {
				callback(jsbeans.mvc.view.cache[v]);
			}
		},
		/**
		 * A preloader for templates.<br/>
		 * May accept a single name or an <code>Array</code> of names.
		 * As {@method} <code class="methd">load</code> it uses queue for remote templates.
		 * @method fetch
		 * @param view {String | Array} the view, or views in case of Array, to load
		 * @static
		 * */
		fetch: function(v) {
			var views = v.constructor == Array ? v : [v];
			if (jsbeans.mvc.view.inline) {
				for (var i = 0, view; view = views[i]; i++) {
					var tmpl = document.getElementById(view);
					if (tmpl != null && typeof(tmpl) != "undefined") {
						jsbeans.mvc.view.cache[view] = tmpl.innerHTML;
					}
				}
			}
			else {
				var xhr = jsbeans.mvc.view.xhr();
				for (var i = 0, view; view = views[i]; i++) {
					// empty function as we just need to fecth templates, not to use them
					var callback = function() {};
					var q = {view: v, callback: callback};
					if (!jsbeans.mvc.view.queue.has(q)) {
						jsbeans.mvc.view.queue.objects.push(q);
					}
					if (jsbeans.mvc.view.queue.processing) {
						setTimeout(function() {
							jsbeans.mvc.view.load(view, callback);
						}, 50);
						return;
					}
					jsbeans.mvc.view.queue.doProcess();
				}
			}
		},
		/**
		 * XmlHttpRequest Object stored locally after first use.
		 * @static
		 * @private
		 * */
		_xhr: null,
		/**
		 * Create, then stores, and returns a XmlHttpRequest Object.
		 * @method xhr
		 * @return a simple implementation of XmlHttpRequest Object
		 * @static
		 * */
		xhr: function() {
			if (jsbeans.mvc.view._xhr == null) {
				var res = null;
				try	{
					res = new XMLHttpRequest();
				} 
				catch (e) {
					try	{ 
						res = new ActiveXObject("Msxml2.XMLHTTP");
					} 
					catch (e) {
						try { 
							res = new ActiveXObject("Microsoft.XMLHTTP");
						} 
						catch (e) {
							alert("Your browser does not support AJAX!");
							return null;
						}
					}
				}
				jsbeans.mvc.view._xhr = res;
			}
			return jsbeans.mvc.view._xhr;
		},
		/**
		 * Queue repository and manager for remote templates.<br/>
		 * Plaese note: <strong>this class is not intended to be used directly</strong>. 
		 * @namespace jsbeans.mvc.view
		 * @class queue
		 * @static
		 * */
		queue: {
			/**
			 * Tells if a request is in progress or not
			 * @property processing
			 * @type Boolean
			 * @default false
			 * @static
			 * */
			processing: false,
			/**
			 * JSON objects in the queue waiting to be procedeed or in progress
			 * @property objects
			 * @type Array
			 * @default []
			 * @static
			 * */
			objects: [],
			/**
			 * Real loader for templates.<br/>
			 * It is responsable to make AJAX requests and set <code class="param">jsbeans.mvc.queue.processing</code> accordingly to each call.<br/>
			 * Use it if you want to force queue to load next template.
			 * @method doProcess
			 * @static
			 * */
			doProcess: function() {
				if (jsbeans.mvc.view.queue.objects.length > 0) {
					jsbeans.mvc.view.queue.processing = true;
					var cur = jsbeans.mvc.view.queue.objects.shift();
					var xhr = jsbeans.mvc.view.xhr();
					xhr.onreadystatechange = function() {
						if (xhr.readyState == 4) {
//							setTimeout(function() {
								jsbeans.mvc.view.cache[cur.view] = xhr.responseText;
								cur.callback(jsbeans.mvc.view.cache[cur.view]);
								jsbeans.mvc.view.queue.processing = false;
//							}, 0);
						}
					};
					xhr.open("GET", jsbeans.mvc.view.templatePath + "/" + cur.view + ".html", true);
					xhr.send(null);
				}
				else {
					jsbeans.mvc.view.queue.processing = false;
				}
			},
			/**
			 * Checks if a template has been pushed in the queue.<br/>
			 * By now it simply checks using view name.
			 * @method has
			 * @return {boolean} Returns <code>true</code> if view is already in the list ready to be processed
			 * @static
			 * */
			has: function(check) {
				var c = check.view;
				for (var i = 0, obj; obj = jsbeans.mvc.view.queue.objects[i]; i++) {
					if (c == obj.view) {
						return true;
					}
				}
				return false;
			}
		}
	}
};
(function() {
	var scripts = document.getElementsByTagName("script");
	for (var i = 0, scr; scr = scripts[i]; i++) {
		var src = scr.getAttribute("src");
		if (src != null && typeof(src) != "undefined" && src.indexOf("mvc.js") != -1) {
			var path = scr.getAttribute("data-templatePath");
			if (path != null && typeof path != "undefined") {
				jsbeans.mvc.view.templatePath = path;
			}
			var inline = scr.getAttribute("inline");
			if (inline != null && typeof inline != "undefined") {
				jsbeans.mvc.view.inline = inline;
			}
			break;
		} 
	}
	// still null, fallback to empty
	if (jsbeans.mvc.view.templatePath == null) {
		jsbeans.mvc.view.templatePath = "";
	}
})();

Copyright © 2016 Francesco Mele. All rights reserved.