All Downloads are FREE. Search and download functionalities are using the official Maven repository.

butor.js.butor.js Maven / Gradle / Ivy

There is a newer version: 1.0.22
Show newest version
/*
 * Copyright 2013-2018 butor.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
//"use strict";

/**
 * @file Base description of the butor front-end framework.
 * @copyright butor.com
 * @license Apache-2.0
 **/

/**
 * @namespace butor
 */
butor = {};

/**
 * @class Class
 * @singleton
 * @memberof butor
 * @description Factory class which provides a prototype class definition.
 * It allows to create a base class or to inherit from a parent class.
 *
 * @see Simple JavaScript Inheritance by John Resig {@link http://ejohn.org/}
 */
butor.Class = new function() {

	/*
	 * Enrich a class with
	 *	1. Properties and their getters/setters
	 *	2. Default behaviour for logging
	 *	3. Event (bind, fire) capabilities
	 *  4. Default behaviour for message display
	 */
	var _enrich = function(prop, base) {
		if (prop.properties && Object.prototype.toString.call(prop.properties) === '[object Object]') {
			for (var name in prop.properties) {
				var pDef = prop.properties[name];
				var field = "_" +name;
				prop[field] = pDef.init;
				var applyOn = pDef.apply;
				var mName = name.charAt(0).toUpperCase() + name.slice(1);
				var sName = "set" +mName;
				prop[sName] = (function(field, applyOn) {
					return function(value) {
						this[field] = value;
						if (applyOn) {
							this[applyOn](value);
						}
					};
				})(field, applyOn);
				var sName = (pDef.type && pDef.type.toLowerCase() == "boolean" ? "is" : "get") +mName;
				prop[sName] = (function(field) {
					return function() {
						return this[field];
					};
				})(field);
			}
			delete prop.properties;
		}
		if (!prop.logDebug && !base.logDebug) {
			prop.logDebug = function() {
				LOGGER.debug.apply(LOGGER, arguments);
			};
		}
		if (!prop.logInfo && !base.logInfo) {
			prop.logInfo = function() {
				LOGGER.info.apply(LOGGER, arguments);
			};
		}
		if (!prop.logError && !base.logError) {
			prop.logError = function() {
				LOGGER.error.apply(LOGGER, arguments);
			};
		}
		if (!prop.logWarn && !base.logWarn) {
			prop.logWarn = function() {
				LOGGER.warn.apply(LOGGER, arguments);
			};
		}

		if (!prop.msgInfo && !base.msgInfo) {
			prop.msgInfo = function() {
				App.info.apply(App, arguments);
			};
		}
		if (!prop.msgWarning && !base.msgWarning) {
			prop.msgWarning = function() {
				App.warning.apply(App, arguments);
			};
		}
		if (!prop.msgError && !base.msgError) {
			prop.msgError = function() {
				App.error.apply(App, arguments);
			};
		}

		if (!prop.tr && !base.tr) {
			prop.tr = function() {
				return App.tr.apply(App, arguments);
			};
		}

		// add events support;
		//prop._eventMgr = null;
		if (!base._eventMgr) {
			prop.bind = function(e, h) {
				if (this._eventMgr == null) {
					this._eventMgr = new butor.EventMgr();
				}
				this._eventMgr.bind(e, h);
			};
			prop.unbind = function(e, h) {
				if (this._eventMgr != null) {
					this._eventMgr.unbind(e, h);
				}
			};
			prop.unbindAll = function(e) {
				if (this._eventMgr != null) {
					this._eventMgr.unbindAll(e);
				}
			};
			prop.fire = function(e, d) {
				if (this._eventMgr != null) {
					this._eventMgr.fire(e, d);
				}
			};
		}
	};

	var initializing = false;

	// check that function decompilation works properly.
	// If it does, the function will be converted to string and the test will return true
	// so the resulting regex will be /\bbase\b/
	//TODO not required
	//var fnTest = /seeme/.test(function() {
	//	seeme;
	//}) ? /\bbase\b/ : /.*/;

	// The base class implementation (does nothing).
	var BaseCls = function() {};

	/**
	 * @typedef ClassDefinition
	 * @memberof butor.Class#
	 * @see butor.Class#define
	 * @description The specification object to create a class:
	 *
	 * @property {Map} [statics] - **RESERVED KEYWORD** A map of static methods to be defined in a class. The entry key is the method name, the entry value is the method definition. Those methods could be accessed without creating an instance of a class.
	 * @property {Map} [properties] - **RESERVED KEYWORD** A map of properties defined in a class. The entry key is the property (class field) name, the entry value is a map with type=['boolean', 'string', etc] attribute . Those methods will be provided a get/set/is accessor.
	 * @property {Function} [construct] - **RESERVED KEYWORD** A function representing the constructor of the class. The method will be invoked when an instance is created.
	 * @property {Object} fieldName - A field of the class. As many fields as required can be added.
	 * @property {Function} methodName - A method of the class. As many methods as required can be added.
	 *
	 * @example
	 *{
	 *	statics : {
	 *		createInstance: function(args) {
	 *			//TODO
	 *		}
	 *	},
	 *	properties: {
	 *		name: {type:'string'},
	 *		age: {type:'Number'},
	 *		eligible: {type:'boolean'}
	 *	},
	 *	_privateField: null,
	 *	publicField: 123,
	 *	construct: function(name, age) {
	 *		this._name = name;
	 *		this._age = age;
	 *		//TODO
	 *		this._init();
	 *	},
	 *	printDebugInfo: function() {
	 *		LOGGER.info("name=" +this.getName();
	 *		LOGGER.info("age=" +this._age; // _age exists through properties specs
	 *		LOGGER.info("eligible=" +this.isEligible();
	 *	}
	 *}
	 */

	/**
	 * @method define
	 * @description Creates (define) a new class based on the definition argument.
	 * If this method is invoked on a defined class, the new class will be a derived
	 * class.
	 *
	 * Arguments order could be :
	 * 1. name, definition[, enrich] or
	 * 2. definition[, enrich]
	 *
	 * @access public
	 * @memberof butor.Class#
	 *
	 * @param {string} [name] - The class name.
	 * @param {butor.Class#ClassDefinition} definition - The class definition.
	 * @param {boolean} [enrich=true] - Enrich the new class with:
	 *	1. Properties and their getters/setters
	 *	2. Default behaviour for logging
	 *	3. Event (bind, fire) capabilities
	 *	4. Default behaviour for message display
	 *
	 * @return {Class} The class definition (with prototype).
	 *
	 * @example
	 * my.namspace.FirstClass = butor.Class.define("my.namspace.FirstClass", {
	 *   properties : {'name':{'type':'string'}},
	 *   construct : function(name) {
	 *     this._name = name;
	 *   }
	 * });
	 * my.namspace.SecondClass = my.namspace.FirstClass.define("my.namspace.SecondClass", {
	 *   _age : null,
	 *   statics : {
	 *     createInstance : function(name, age, eligible) {
	 *       var inst = new my.namspace.SecondClass(name, age);
	 *       inst.setEligible(eligible);
	 *       return inst;
	 *     }
	 *   },
	 *   properties : {'age':{'type':'Number'},
	 *     'eligible':{'type':'boolean'}},
	 *   construct : function(name, age) {
	 *     this.base(name);
	 *     this._age = age;
	 *   },
	 *   doIt : function() {
	 *     //TODO
	 *     this.fire('done-it', this._name);
	 *   }
	 * });
	 *
	 * var fc = new my.namspace.FirstClass('John Smith');
	 * LOGGER.info("name=" +fc.getName());
	 * fc.setName('Joe Smith');
	 * LOGGER.info("name=" +fc.getName());
	 *
	 * var sc = new my.namspace.SecondClass.createInstance('John Smith', 30);
	 * sc.bind('done-it', function(e) {
	 *   LOGGER.info('got event "done-it" with data=' +e.data);
	 * };
	 * LOGGER.info("name=" +sc.getName());
	 * LOGGER.info("age=" +sc.getAge());
	 * sc.setEligible(true);
	 * LOGGER.info("eligible=" +sc.isEligible());
	 * sc.doIt();
	 */
	BaseCls.define = function(arg1, arg2, arg3) {
		var clsName = 'Class';
		var prop = null;
		var rich = true;
		var base = this.prototype;

		var ft = Object.prototype.toString.call(arg1);
		if (ft == "[object String]") {
			clsName = arg1;
			prop = arg2;
			rich = arg3;
		} else {
			prop = arg1;
			rich = arg2;
		}
		// statics members ?
		var statics = prop.statics;
		if (statics != null) {
			delete prop.statics;
		}

		// rich class?
		if (rich == null || rich == undefined || rich == true) {
			_enrich(prop, base);
		}

		// Instantiate a base class (but only create the instance,
		// don't run the construct constructor)
		initializing = true;
		var prototype = new this();
		initializing = false;

		prototype['base'] = function() {
			//nothing to do in higher class
		};
		var fields = prototype['__butor_cls_fields__'] = {};
		// Copy the properties over onto the new prototype
		for (var name in prop) {
			// Check if we're overwriting an existing function
			var et = typeof prop[name];
			if (et == "function" && typeof base[name] == "function") {
				//&& fnTest.test(prop[name])) {
				prototype[name] = (function(name, fn) {
					return function() {
						var tmp = this.base;

						// Add a new .base() method that is the same method
						// but on the super-class
						this.base = base[name];
						// The method only need to be bound temporarily, so we
						// remove it when we're done executing
						var ret = fn.apply(this, arguments);
						this.base = tmp;

						return ret;
					};
				})(name, prop[name]);

			} else if (et == "function") {
				prototype[name] = prop[name];

			} else {
				// class fields.
				// To be defined in each class instance
				// just before calling constructor
				fields[name] = prop[name];
			}
		}

		var Class = function() {
			// The constructor
			// Create a copy of the class fields
			// so each instance had a copy
			var fields = this['__butor_cls_fields__'];
			if (fields) {
				for (var f in fields) {
					var ft = Object.prototype.toString.call( fields[f] );
					if (!butor.Utils.isEmpty(fields[f]) && ft == "[object Object]") {
						this[f] = $.extend(true, {}, fields[f]);
					} else {
						this[f] = fields[f];
					}
				}
			}
			// All construction is actually done in the construct method
			if (!initializing && this.construct)
				this.construct.apply(this, arguments);
		}

		// Populate our constructed prototype object
		Class.prototype = prototype;

		// Enforce the constructor to be what we expect
		Class.prototype.constructor = Class;

		// And make this class extendable
		Class.define = arguments.callee;
		Class.extend = arguments.callee;

		if (statics != null) {
			for (var name in statics) {
				Class[name] = statics[name];
			}
		}
		Class.prototype['__butor_cls_name__'] = clsName;
		Class.prototype.getClassName = function() {
			return this['__butor_cls_name__'];
		};
		return Class;
	};

	BaseCls.extend = function(prop, rich) {
		LOGGER.warn('extend() is deprecated. Please use define().');
		return BaseCls.define(prop, rich);
	};

	return BaseCls;
}();

/***** end of Javascript class definition and inheritance *****/
Butor = butor.Class;

/**
 * @typedef Event
 * @memberof butor.EventMgr#
 * @description Structures an event used in the framework.
 *
 * These events can be defined directly when they are throwed by the method {@link butor.EventMgr#fire fire},
 * only the objects binded on this event with the method {@link butor.EventMgr#bind bind}
 * will know how the data is structured.
 *
 * @property {string} name - Name of the event.
 * @property {Map} data - Data propagated by the event.
 */

/**
 * @class EventMgr
 * @description Manager of events with the bind/fire pattern.
 *
 * Each butor object is built with the {@link butor.Class Class} definition owns an {@link butor.EventMgr EventMgr}. Thus all butor objects can be binded to an event and a handler,
 * but only the same instance of an object can be binded to the {@link butor.EventMgr#Event Event} it fires.
 *
 * You can bind an object on many events or many objects on one event, but you need to apply their
 * bindings carrefully: if you call a method which binds an handler to an event, the handler will be binded
 * as many times as the method is called. It can cause some lack of performances.
 *
 * @memberof butor
 * @example
 * 
 * 

* @example * var eventMgr = new butor.EventMgr(); * * var hello = function(e) { * $('.event-example').text(e.data.text); * eventMgr.unbind('example', this); * } * * eventMgr.bind('example', hello); * * @example * ExampleMgr = butor.Class.define({ * _ownEvent: { * 'name': 'exampleEvent', * 'handler': function(e) { * console.log(e['data']['msg']); * } * }, * construct: function() { * this.bind(this._ownEvent['name'], this._ownEvent['handler']); * }, * throwEvent: function(msg) { * this.fire('exampleEvent', {'msg': msg}); * } * }); * * var exampleMgr = new ExampleMgr(); * exampleMgr.throwEvent('This event is thrown by myself.'); * exampleMgr.fire('exampleEvent', { * 'msg': 'This event is thrown by some which owns an instance of me.' * }); * * @example * exampleMgr = function() { * butor.example.ExampleMgr = butor.example.ExampleMgr || butor.Class.define({ * _ownEvent: { * 'name': 'exampleEvent', * 'handler': function(e) { * console.log(e['data']['msg']); * } * }, * construct: function() { * this.bind(this._ownEvent['name'], this._ownEvent['handler']); * }, * throwEvent: function(msg) { * this.fire('exampleEvent', {'msg': msg}); * } * }); * * var exampleMgr = new butor.example.ExampleMgr(); * exampleMgr.throwEvent('This event is thrown by myself.'); * exampleMgr.fire('exampleEvent', { * 'msg': 'This event is thrown by some which owns an instance of me.' * }); * * @example * // You can treat a lot event with the same 'fire' call. * butor.example.EventMaster = butor.example.EventMaster || butor.Class.define({ * construct: function() { * var eventName = 'speakEvent'; * this.bind(eventName, this._speakEnglish); * this.bind(eventName, this._speakFrench); * this.bind(eventName, this._speakSpanish); * this.bind(eventName, this._speakGerman); * this.bind(eventName, this._speakRussian); * }, * _speakEnglish: function() { * console.log('hello !'); * }, * _speakFrench: function() { * console.log('salut !'); * }, * _speakSpanish: function() { * console.log('hola !'); * }, * _speakGerman: function() { * console.log('hallo !'); * }, * _speakRussian: function() { * console.log('sorry...'); * } * }); * * var eventMaster = new butor.example.EventMaster(); * eventMaster.fire('speakEvent'); // Will speak in all languages. * * @example * // **Be careful**: the 'bind' method masks 'this' of your object, you need the * 'jQuery.proxy(fn, ctx)' or 'fn.bind(ctx)' to use the appropriate context of your object. * butor.example.UnsafeMgr = butor.example.UnsafeMgr || butor.Class.define({ * _boo: 'BOO !', * construct: function() { * this.bind('unsafeEvent', this._peekaboo); // INCORRECT CONTEXT (will send an undefined * result) * this.bind('jqueryEvent', $.proxy(this._peekaboo, this)); // jQuery * this.bind('vanillaEvent', this._peekaboo.bind(this)); // Vanilla * }, * _peekaboo: function() { * console.log(this._boo); * } * }); * * @example * var unsafeMgr = new butor.example.UnsafeMgr(); * unsafeMgr.fire('unsafeEvent'); // Display an undefined result. * unsafeMgr.fire('jqueryEvent'); * unsafeMgr.fire('vanillaEvent'); * * // You can use the event manager in order to treat some data one by one. * butor.example.ChainMgr = butor.example.ChainMgr || butor.Class.define({ * _data: null, * _dataHandler: null, * _total: 0, * _index: 0, * treatData: function(arrayOfData, handler) { * this._data = arrayOfData; * this._handler = handler; * this._index = 0; * this._total = arrayOfData.length; * * this.bind('dataLoaded', $.proxy(function() { * this._treatNextData(); * }, this)); * * this._treatNextData(); * }, * _treatNextData: function() { * if (this._index === this._total) { * this.unbind('dataLoaded'); * return; * } * * this._handler(this._data[this._index]); * this._index += 1; * this.fire('dataLoaded'); * } * }); * * var chainMsg = new butor.example.ChainMgr(); * var accumulator = 0; * var data = [100, 200, 300, 400]; * var handler = function(value) { * accumulator += value; * }; * * chainMsg.treatData(data, handler); * console.log(accumulator); */ butor.EventMgr = butor.Class.define('butor.EventMgr', { construct: function() { this.base(); this._events = {}; }, /** * @method bind * @description Binds a handler to an event. * @memberof butor.EventMgr# * @param {string} eventName - Name of the event to bind to the handler. * @param {Function} handler - The handler which will catch the event. It receives the event as an argument. * @example * EventMgr.bind('event', function(e) { * console.log("the fired event is: " + e); * }); */ bind: function(event, handler) { var handlers = this._events[event]; if (!handlers) { handlers = []; this._events[event] = handlers; } handlers.push(handler); window.LOGGER && LOGGER.info('bind handler to event:{}', event); }, /** * @method unbind * @description Unbinds a handler from an event. * @memberof butor.EventMgr# * @param {string} eventName - Name of the event to unbind from. * @param {Object} handler - A reference to the same handler instance passed to {@link butor.EventMgr#bind bind}. */ unbind: function(event, handler) { var handlers = this._events[event]; if (!handlers) return; for (var ii = 0; ii < handlers.length; ii += 1) { if (handlers[ii] === handler) { handlers.splice(ii, 1); break; } } window.LOGGER && LOGGER.info('unbind handler to event:{}', event); }, /** * @method unbindAll * @description Unbinds all handlers from an event. * @memberof butor.EventMgr# * @param {string} event - The event's name to unbind from. */ unbindAll: function(event) { var handlers = this._events[event]; if (!handlers) return; for (var ii = handlers.length - 1; ii >= 0; ii -= 1) { handlers.splice(ii, 1); } window.LOGGER && LOGGER.info('unbind all handler to event:{}', event); }, /** * @method fire * @description Fires an event. All the handlers binded on this event will be executed. * @memberof butor.EventMgr# * @param {string} eventName - The event's name to throw. * @param {Object} data - The data attached to the throwed event. */ fire: function(event, data) { var handlers = this._events[event]; if (!handlers) return; handlers = $.extend([], handlers); window.LOGGER && LOGGER.info('fire event:{}', event); for (var ii = 0; ii < handlers.length; ii += 1) { try { var hn = handlers[ii]; hn({ event: event, data: data }); } catch (err) { window.LOGGER && LOGGER.error(err); } } } }, false); /** * @class Timer * @description Fixed interval timer. * * The timer is used in order to keep triggering a **Function** again and again * during an interval of time. * * @memberof butor * @param {Func} fnc - Function called by the timer. * @param {Number} delay - Delay of the Timer. * @param {boolean} [start=false] - Launch the function when the timer is created. * * @example * var total = 100; * var count = 0; * * var progress_panel = new butor.panel.ProgressPanel(); * $('body').append(progress_panel.getPanel()); * * var doAProgress = function() { * count++; * if (count < total) { * progress_panel.progress(count, total, 'Progress loading: ' + count + '%'); * } else { * progress_panel.progress(count, total, 'Finished!'); * } * } * * var timer = new butor.Timer(doAProgress, 100); * * $('#stopBtn').click(function() { * $(this).hide(); * $('#playBtn').show(); * timer.stop(); * }); * * $('#playBtn').click(function() { * $(this).hide(); * $('#stopBtn').show(); * timer.start(); * }); * * $('#stopBtn').hide(); */ butor.Timer = butor.Class.define({ construct: function(fnc, delay, start) { this.base(); this._timer = null; this._fnc = fnc; this._delay = delay; if (start) { this._fnc(); this.start(); } }, /** * @method stop * @description Stops the timer. * @memberof butor.Timer# */ stop: function() { if (this._timer) { window.clearInterval(this._timer); this._timer = null; } }, /** * @method start * @description Starts the timer. * @memberof butor.Timer# */ start: function() { if (this._timer == null) { this._timer = window.setInterval(this._fnc, this._delay); } }, /** * @method restart * @description Restarts the timer. * @memberof butor.Timer# */ restart: function() { this.stop(); this.start(); } }); // temporary App singleton App = function() { return { getLang: function() { var lang = $.cookie("lang"); if (butor.Utils.isEmpty(lang)) { lang = 'en'; } return lang; }, translate: function() {}, translateElem: function() {}, info: function(msg) {}, error: function(msg) {}, mask: function(msg) { if ($.mask) { $('body').mask(msg); } }, unmask: function() { if ($.unmask) { $('body').unmask(); } } } }(); //==================== /** * @class Logger * @singleton * @memberof butor * @description Provider of simple logging output. * * Each level is used as filter on the logging output: in order to see all the messages, * you need to switch to the upper logging level. * By default, the level {@link butor.Logger.ERROR ERROR} is selected but the method * {@link butor.Logger#setLevel setLevel} should be used to change the logging level. * * Every logged messages is prefixed by the name of the logging level, * in addition every javascript **Error** are parsed by the Logger: the message "Error :" * prefixed the error message. More of one messages can be added as an array to the logging methods * {@link butor.Logger#info info}, {@link butor.Logger#error error}, {@link butor.Logger#warn warn}, * {@link butor.Logger#debug debug} and {@link butor.Logger#trace trace} as an array of strings. * This array is *formatted* following the first string element of the array. * * LOGGER.setLevel(butor.Logger.INFO); * LOGGER.info('{} {} \n', 'hello', 'world', new Error('i am an error')); * // logging output: * // INFO hello world * // Error: @debugger eval code:1:43 * * The formatted code replace each '{}' characters by extra strings in the first element. * If there isn't enough '{}' in the first string, the other messages are added to the end of it. * * @example * var logger = new butor.Logger(); * * var testLogger = function() { * logger.error(logger._level + ' ERROR'); * logger.warn(logger._level + ' WARNING'); * logger.info(logger._level + ' INFO'); * logger.debug(logger._level + ' DEBUG'); * logger.trace(logger._level + ' TRACE'); * } * * var changeLevel = function(i) { * console.log('---- LEVEL: ' + i); * logger.setLevel(i); * testLogger(); * } * * changeLevel(); // it will take i = undefined * // output : ERROR, WARNING, INFO (default level: 3) * * for (var i = 0; i < 6; i++) { * changeLevel(i); * } */ butor.Logger = butor.Class.define({ statics: { /** * @var {Number} OFF * @description Off level. * @constant * @default 0 * @memberof butor.Logger */ OFF: 0, /** * @var {Number} ERROR * @description Error level. * @constant * @default 1 * @memberof butor.Logger */ ERROR: 1, /** * @var {Number} WARN * @description Warning level. * @constant * @default 2 * @memberof butor.Logger */ WARN: 2, /** * @var {Number} INFO * @description Info level. * @constant * @default 3 * @memberof butor.Logger */ INFO: 3, /** * @var {Number} DEBUG * @description Debug level. * @constant * @default 4 * @memberof butor.Logger */ DEBUG: 4, /** * @var {Number} TRACE * @description Trace level. * @constant * @default 5 * @memberof butor.Logger */ TRACE: 5 }, _level: 1, construct: function() {}, /** * @method setLevel * @description Sets the level of the logger. * @memberof butor.Logger# * @param {Number} [level=butor.Logger.INFO] - Logging level. */ setLevel: function(level) { this._level = butor.Logger.INFO; if (butor.Utils.isInteger(level) && level >= 0 && level <= 5) { this._level = level; } }, /** * @method infoEnabled * @description Checks if the info level is enable. * @memberof butor.Logger# * @return {boolean} Return true whether the info level is enable. */ infoEnabled: function() { return this._level != butor.Logger.OFF && this._level >= butor.Logger.INFO; }, /** * @method warnEnabled * @description Checks if the warning level is enable. * @memberof butor.Logger# * @return {boolean} Return true whether the warning level is enable. */ warnEnabled: function() { return this._level != butor.Logger.OFF && this._level >= butor.Logger.WARN; }, /** * @method errorEnabled * @description Checks if the error level is enable. * @memberof butor.Logger# * @return {boolean} Return true whether the error level is enable. */ errorEnabled: function() { return this._level != butor.Logger.OFF && this._level >= butor.Logger.ERROR; }, /** * @method debugEnabled * @description Checks if the debug level is enable. * @memberof butor.Logger# * @return {boolean} Return true whether the debug level is enable. */ debugEnabled: function() { return this._level != butor.Logger.OFF && this._level >= butor.Logger.DEBUG; }, /** * @method traceEnabled * @description Checks if the trace level is enable. * @memberof butor.Logger# * @return {boolean} Return true whether the trace level is enable. */ traceEnabled: function() { return this._level != butor.Logger.OFF && this._level >= butor.Logger.TRACE; }, /** * @method _replaceArgs * @access private * @description Replaces the log arguments used for log messages. * @memberof butor.Logger# * @param {string|Error|string[]|Error[]} args - Arguments to replace. */ _replaceArgs: function(args) { if (!args || args.length === 0) return args; var cnt = args.length; if (cnt === 1) { var arg = args[0]; if (arg instanceof Error) return "Error: " + arg.stack; return arg + ''; } var txt = args[0]; for (var ii = 1; ii < cnt; ii += 1) { var arg = args[ii]; if (arg instanceof Error) arg = "Error: " + arg.stack; if (txt.indexOf('{}') > -1) txt = txt.replace('{}', arg); else txt += arg; } return txt; }, /** * @method info * @description Writes an info-level message in logging output. * @memberof butor.Logger# * @param {string|Error|string[]|Error[]} msg - Message to log, or an Error or an array of message and Error. */ info: function(args) { if (!this.infoEnabled()) return; var msg = 'INFO ' + this._replaceArgs(arguments); try { if (window.console.info) { window.console.info(msg); } else { window.console.log(msg); } } catch (err) { //not ready } }, /** * @method warn * @description Writes a warning-level message in logging output. * @memberof butor.Logger# * @param {string|Error|string[]|Error[]} msg - Message to log, or an Error or an array of message and Error. */ warn: function(msg) { if (!this.warnEnabled()) return; var msg = 'WARN ' + this._replaceArgs(arguments); try { if (window.console.warn) { window.console.warn(msg); } else { window.console.log(msg); } } catch (err) { //not ready } }, /** * @method error * @description Writes an error-level message in logging output. * @memberof butor.Logger# * @param {string|Error|string[]|Error[]} msg - Message to log, or an Error or an array of message and Error. */ error: function(msg) { if (!this.errorEnabled()) return; var msg = 'ERROR ' + this._replaceArgs(arguments); try { if (window.console.error) { window.console.error(msg); } else { window.console.log(msg); } } catch (err) { //not ready } }, /** * @method debug * @description Writes a debug-level message in logging output. * @memberof butor.Logger# * @param {string|Error|string[]|Error[]} msg - Message to log, or an Error or an array of message and Error. */ debug: function(msg) { if (!this.debugEnabled()) return; var msg = 'DEBUG ' + this._replaceArgs(arguments); try { if (window.console.debug) { window.console.debug(msg); } else { window.console.log(msg); } } catch (err) { //not ready } }, /** * @method trace * @description Writes a trace-level message in logging output. * @memberof butor.Logger# * @param {string|Error|string[]|Error[]} msg - Message to log, or an Error or an array of message and Error. */ trace: function(msg) { if (!this.traceEnabled()) return; var msg = 'TRACE ' + this._replaceArgs(arguments); try { if (window.console.trace) { window.console.trace(msg); } else { window.console.log(msg); } } catch (err) { //not ready } } }); /** * @class Loader * @memberof butor * @description Loader of scripts and styles by their URL. * * Every style can be loaded with the *static* method {@link butor.Loader.loadCSS loadCSS}. * Otherwise, all the script are loaded by pushing their url into an array and * by passing this array to the function {@link butor.Loader#loadScripts loadScripts}. * Many scripts may be loaded and any error of loading don't stop the loading of the other scripts, * the generated error is displayed as an error-level message in the logging output. * * @example * *

*

*
*
* * @example * //example.css: styles to load * p.loader { * border: 1px solid; * color: red; * } * * .loading { * border: 1px solid; * color: blue; * } * * .loaded { * border: 1px solid; * color: green; * } * * @example * // example1.js : script to load * $('.loader-example1').text('Hello world!'); * * @example * // example2.js : script to load * $('.loader-example2').text('Bonjour tout le monde!'); * * @example * butor.example.FeedbackLoader = butor.example.FeedbackLoader || butor.Loader.define({ * _progress_panel: null; * construct: function() { * this.base(); * this._progress_panel = new butor.panel.ProgressPanel(); * }, * loadScripts: function(scripts, loadName) { * this._scripts = scripts; * this._loadName = loadName; * this._loadingIndex = 0; * this._total = scripts.length; * * this.bind('scriptLoaded', $.proxy(function() { * this._progress_panel.progress(this._loadingIndex, this._total, * this._scripts[this._loadingIndex] + " loading..."); * this._loadNextScript(); * }, this)); * * this._progress_panel.progress(this._loadingIndex, this._total, * this._scripts[this._loadingIndex] + " loading..."); * this._loadNextScript(); * } * }); * * var scripts = ['path/to/example1.js', 'path/to/example2.js']; * var loader = new butor.example.FeedbackLoader(); * loader.loadScripts(scripts, 'loadingExamples'); */ butor.Loader = butor.Class.define({ statics: { /** * @method loadCSS * @description Loads a CSS file from its URL by including it in the HTML header. * @memberof butor.Loader */ loadCSS: function(url) { var ref = document.createElement("link"); ref.rel = "stylesheet"; ref.type = "text/css"; ref.href = url + (url.indexOf("?") == -1 ? "?_" : "&_") + Math.random(); document.getElementsByTagName("head")[0].appendChild(ref); } }, _allSuccess: true, _scripts: [], _loadName: null, _loadingIndex: 0, _total: 0, construct: function() { this._loadingIndex = 0; this._total = 0; }, /** * @method _loadScript * @access private * @description Loads a script from its URL. * @memberof butor.Loader# * @param {string} url - URL of the script. * @param {string} index - Index of the script. * @param {string} total - Total of scripts to load. * @fires butor.Loader#loadingScript * @fires butor.Loader#scriptLoaded */ _loadScript: function(url, index, total) { var self = this; var mn = self._loadName + ': ' + url; LOGGER.info('Loading module ' + mn + ' ...'); this.fire('loadingScript', { 'url': url, 'index': index, 'total': total, 'loadName': self._loadName }); $.getScript(url) .done(function(script, textStatus) { LOGGER.info('Done loading module ' + mn); self.fire('scriptLoaded', { 'url': url, 'index': index, 'total': total, 'success': true, 'loadName': self._loadName }); }) .fail(function(jqxhr, settings, exception) { self._allSuccess = false; LOGGER.error('Failed to load module ' + mn); LOGGER.error(jqxhr); LOGGER.error(exception); self.fire('scriptLoaded', { 'url': url, 'index': index, 'total': total, 'success': false, 'loadName': self._loadName }); }); }, /** * @method _loadNextScript * @access private * @description Loads the next script. * @memberof butor.Loader# * @fires butor.Loader#done */ _loadNextScript: function() { if (this._loadingIndex === this._total) { this.fire('done', { 'success': this._allSuccess, 'total': this._total, 'loadName': this._loadName }); return; } this._loadingIndex++; this._loadScript(this._scripts[this._loadingIndex - 1], this._loadingIndex, this._total); }, /** * @method loadScripts * @description Loads a list of script JavaScript files one by one. * Each loaded script throws the events `loadingScript` and `scriptLoaded` respectivly before and after the loading. * @memberof butor.Loader# * @param {Array} scripts - Array of script URLs. * @param {string} loadName - Name of loading. * @fires butor.Loader#loadingScript * @fires butor.Loader#scriptLoaded */ loadScripts: function(scripts, loadName) { this._scripts = scripts; this._loadName = loadName; this._loadingIndex = 0; this._total = scripts.length; this.bind('scriptLoaded', $.proxy(function(e_) { this._loadNextScript(); }, this)); this._loadNextScript(); } }); /** * @class ga * @memberof butor * @description Google Analytics (GA) Manager * * This manager uses the **legacy library** *ga.js*. It activates Google Analytics tracking * by managing the configuration. In order to have access to the GA functionnalities your need to provide * your *account* (your *web property ID*) and the *domain name* of your website. * * @return {Object} Google Analytics feedback. * @see {@link https://developers.google.com/analytics/devguides/collection/gajs/ Analytics for Web (ga.js) - Developer Guide} */ //TODO Augment descriptions //TODO Link to GA docs/API? butor.ga = butor.ga || function() { var _gaq = null; var _account = null; var _domainName = null; /** * @method activate * @memberof butor.ga# * @description Activates the Google Analytics engine. * @param {string} account - GA account. * @param {string} domainName - Domain name of the web site. */ var activate = function(account, domainName) { if (_gaq) { LOGGER && LOGGER.warn('Google Analytics API already set!'); return; } if (butor.Utils.isEmpty(account)) { LOGGER && LOGGER.warn('Cannot activate Google Analytics! Missing Account'); return; } if (butor.Utils.isEmpty(domainName)) { LOGGER && LOGGER.warn('Missing Google Analytics! Missing domainName'); return; } _account = account; _domainName = domainName; _gaq = _gaq || []; _gaq.push(['_setAccount', account]); _gaq.push(['_setDomainName', domainName]); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); LOGGER && LOGGER.info('Activated Google Analytics.'); enable(); }; /** * @method push * @memberof butor.ga# * @description Pushes a page on the Google Analytics engine. * @param {Object} page - A page to push on GA. */ var push = function(page) { if (!_gaq || !page) { LOGGER && LOGGER.warn( 'Google Analytics is not activated! call butor.ga.activate(id, domain).' ); return; } var pos = page.indexOf("?"); if (pos > -1) { page = page.substring(0, pos); } _gaq.push(['_trackPageview', page + ' (' + App.getLang() + ')']); }; /** * @method enable * @memberof butor.ga# * @description Enables Google Analytics engine. */ var enable = function() { window['ga-disable-' + _account] = false; LOGGER && LOGGER.info('Enabled Google Analytics.'); }; /** * @method disable * @memberof butor.ga# * @description Disables Google Analytics engine. */ var disable = function() { window['ga-disable-' + _account] = true; LOGGER && LOGGER.info('Disabled Google Analytics.'); }; return { 'activate': activate, 'push': push, 'enable': enable, 'disable': disable } }(); /** * @class PopoverTooltip * @memberof butor * @description A wrapper of Bootstrap Popover Tooltip. * * This wrapper is used in order to build a popover quickly only with the constructor parameters. * It contains only the 'hover' feature of a basic popover but it can be enriched by using * the {@link http://getbootstrap.com/javascript/#popovers Bootstrap Popover documentation}. * * @see {@link http://getbootstrap.com/javascript/#popovers Bootstrap Popover documentation} * @param {jQueryElement} jqe - jQuery element. * * @param {object} [options] - The popover tooltip options. * @param {string} [options.title] - Title of the Popover. * @param {string} [options.target] - Target element of the Popover. * Useful to show the popover on different elements where the mouse hovers on. * @param {string|function} [options.placement=right] - Sets the position of the popover. **Allowed options**: top, bottom, left, right, auto. * @param {boolean} [options.html=false] - Insert HTML into the popover. * If false, jQuery's text method will be used to insert content into the DOM. * Use text if you're worried about XSS attacks. * @param {boolean} [options.animation=true] - Apply a CSS fade transition to the popover. * @param {string|function} [options.content=""] - Default content value if data-content attribute isn't present. * If a function is given, it will be called with its this reference set to the element that the popover is attached to. * * @example * Hello * * @example * var options = { * 'title': 'ExamplePopoverTooltip', * 'placement': 'bottom', * 'html': true, * 'animation': false, * 'content': 'World!' * }; * * var popover = new butor.PopoverTooltip($('#popover'), options); */ butor.PopoverTooltip = butor.Class.define({ _options: null, _jqe: null, _hoverP: null, construct: function(jqe, options) { options = $.extend({ 'html': true, 'animation': true, 'placement': 'bottom' }, options); jqe = $(jqe); if (options.target) { options.target = $(options.target); } else { options.target = jqe; } this._options = options; this._jqe = jqe; this._hoverP = $.proxy(this._hoverInOut, this); jqe.bind('hover', this._hoverP); }, _hoverInOut: function(e) { var options = this._options; if (e.type == 'mouseenter') { options.target.popover({ 'title': '' + options.title + '', 'placement': options.placement, 'html': options.html, 'trigger': 'manual', 'animation': options.animation, 'content': '' + options.content + '' }).popover('show'); } else { options.target.popover('destroy'); } }, /** * @method remove * @memberof butor.PopoverTooltip# * @description Removes this popover tooltip. */ remove: function() { this._jqe.unbind('hover', this._hoverP); this._options.target.popover('destroy'); } }); /** * @class Utils * @singleton * @memberof butor * @description Utilities toolbox for the butor plateform. * * This singleton contains a lot of useful methods in order to make easier some basic manipulation on strings and on numbers. * It helps you to format a Date or a Currency from a string or a number. * Finally, it provides a set of methods for DOM manipulation. */ butor.Utils = function() { var _handlersBak = {}; /** * @method _setEnable * @memberof butor.Utils# * @access private * @description Enables a selector on the plateform. * @param {jQueryElement} selector - jQuery Selector. * @param {boolean} enabled - Flag which enables data to be displayed. */ var _setEnable = function(selector_, enabled_) { var elm = $(selector_); if (!elm) return; if (enabled_) { elm.removeAttr("disabled"); } else { elm.attr("disabled", "disabled"); } }; /** * @method leftPad * @memberof butor.Utils# * @description Puts zeros on the left of a value. * @param {Number} value - Value to pad. * @param {Number} length - Length of the result value. * @return {string} New value with zeros on the left. * * @example * // Get the leftPad function from the Utils singleton * var leftPad = butor.Utils.leftPad; * leftPad('5', 2); //'05' * leftPad('40', 4); //'0040' * leftPad('ooo', 6); //'000ooo' */ var leftPad = function(v_, l_) { var str = '000000000000000' + v_; return str.substring(str.length - l_); }; var _eshm = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }; return { leftPad: leftPad, /** * @method escapeStr * @memberof butor.Utils# * @description HTML escaper. * @param {Object} String - String to escape. * @return {string} The escaped string. */ escapeStr : function(str) { if (!str) { return str; } return String(str).replace(/[&<>"'`=\/]/g, function (s) { return _eshm[s]; }); }, escape : function(obj, fields) { if (!obj) { return obj; } var self = this; if (typeof(obj) == "string") { return this.escapeStr(obj); } else if (obj instanceof Array) { $.each(obj, function(index, value) { obj[index] = self.escapeStr(value); }); } else if (fields && fields instanceof Array && obj instanceof Object) { $.each(fields, function(index, str) { obj[str] = self.escapeStr(obj[str]); }); } }, /** * @method formatDateTime * @memberof butor.Utils# * @description {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date Date} object formater. * The date is format as the {@link butor.Utils#formatDateTime formatDateTime} method. Both seconds and milliseconds are set to output format. * The Date is formated as **"year/month/day hour:minutes:seconds"**. * @see butor.Utils#formatDateTime * @param {Object} date - The date time to format. * @return {string} The formated DateTime. */ formatDateTime: function(date) { return butor.Utils.formatDate(date, true, true); }, /** * @method formatDate * @memberof butor.Utils# * @description Date object formater. * @param {string|Date} date - The date to format can be: * - A string which will be parsed with the {@link butor.Utils#parseDate parseDate(str)} method. * - A Date object discribed in the {@link http://mzl.la/1NKHxCz MDN JavaScript documentation}. * @param {boolean} [showTime=false] - Flag to show time. * @param {boolean} [showSecs=false] - Flag to show seconds. * @param {boolean} [showMillisecs=false] - Flag to show milliseconds. * @return {string} The formated Date. * * @example * // Get the formatDate function from the Utils singleton * var formatDate = butor.Utils.formatDate; * * // Example with a Date object * formatDate(new Date()); * * // Example with a string * parseTime('1992/07/17 14:30:56'); // output: '14:30' * parseTime('1992/07/17 14:30:56', true); // output: '14:30:56' */ formatDate: function(date_, showTime_, showSecs_, showMillisecs_) { if (butor.Utils.isEmpty(date_)) { return ""; } if (typeof(date_) === "string") { try { date_ = butor.Utils.parseDate(date_); //new Date(Date.parse(date_)); } catch (err) { // OK then return what we got! return date_; } } var str = date_.getFullYear() + "-" + leftPad(date_.getMonth() + 1, 2) + "-" + leftPad(date_.getDate(), 2); if (showTime_) { str += " " + leftPad(date_.getHours(), 2) + ":" + leftPad(date_.getMinutes(), 2); if (showSecs_) { str += ":" + leftPad(date_.getSeconds(), 2); if (showMillisecs_) { str += "." + leftPad(date_.getMilliseconds(), 3); } } } return str; }, /** * @method formatTime * @memberof butor.Utils# * @description Time object formater. * @param {string|Date} time - The time to format can be: * - A string which will be parsed with the {@link butor.Utils#parseDate parseDate(str)} method. * - A Date object discribed in the {@link http://mzl.la/1NKHxCz MDN JavaScript documentation}. * @param {boolean} [showSecs=false] - Flag to show seconds. * @return {string} The formated Time as **hour:minutes[:seconds]**. * * @example * // Get the formatTime function from the Utils singleton * var formatTime = butor.Utils.formatTime; * * // Example with a Date object * var date = new Date('December 25, 1995 23:15:30'); * formatTime(date); // output: '23:15' * * // Example with a string * formatTime('1992/07/17 14:30:56'); // output: '14:30' * formatTime('1992/07/17 14:30:56', true); // output: '14:30:56' */ formatTime: function(date_, showSecs_) { if (butor.Utils.isEmpty(date_)) { return ""; } if (typeof(date_) === "string") { try { date_ = butor.Utils.parseDate(date_); //new Date(Date.parse(date_)); } catch (err) { // OK then return what we got! return date_; } } var str = leftPad(date_.getHours(), 2) + ":" + leftPad(date_.getMinutes(), 2); if (showSecs_) { str += ":" + leftPad(date_.getSeconds(), 2); } return str; }, /** * @typedef {string} DateString * @memberof butor.Utils# * @see butor.Utils#parseDate * @description * More complexe date written as **"year/month/day hour:minutes:seconds"** * (*the blank space is important*). * * The accepted separators are '/' and '-'. * @property {string} year - Number value representing a year on 4 digits. * Values from 0000 to 9999. * @property {string} month - Number value representing a month on 2 digits. * Values from 01 for January to 12 fo December. * @property {string} day - Number value representing a day of the month on 2 digits. * Values from 00 to 31. * @property {string} [hour] - Number value representing an hour of the day on 2 digits (for 24 hours). * Values from 00 to 23. * @property {string} [minutes] - Number value representing a minute on 2 digits. * Values from 00 to 59. * @property {string} [seconds] - Number value representing a second on 2 digits. * Values from 00 to 59. */ /** * @method parseDate * @memberof butor.Utils# * @description * Parse a date in string format into a JavaScript Date object. * * The parser takes date written as a {@link butor.Utils#DateString DateString}. * * @see {@link http://mzl.la/1NKHxCz MDN JavaScript documentation for Date.} * @param {string} date - The date to parse. * @return {Date} A Date object discribed * in {@link http://mzl.la/1NKHxCz MDN JavaScript documentation for Date}. * * @example * // Get the parseDate function from the Utils singleton * var parseDate = butor.Utils.parseDate; * * // Example tests: * parseDate("1992/07/17 14:30:56"); //Good! * parseDate("1992-07-17"); //Good too! * parseDate("1992/07-17"); //Also good, but strange. * parseDate("1992/07/17,14:30:56"); //Evil. * parseDate("December 17, 1995 03:24:00"); //Will definitively fail. * //for each, output: [object Date] { ... } */ parseDate: function(date_) { if (butor.Utils.isEmpty(date_)) { return null; } if (typeof(date_) !== "string") { return date_; } if (window.moment) { pd = moment(date_); if (pd) { pd = pd.toDate(); return pd; } } try { var re = /^(\d{4})[-\/](\d{2})[-\/](\d{2})$/; var d = date_.match(re); var pd; if (d) { pd = new Date(d[1], d[2] - 1, d[3]); if (pd) { return pd; } } re = /^(\d{4})[-\/](\d{2})[-\/](\d{2}) (\d\d):(\d\d):(\d\d).*/; //ignore millis d = date_.match(re); if (d) { pd = new Date(d[1], d[2] - 1, d[3], d[4], d[5], d[6]); if (pd) { return pd; } } pd = new Date(Date.parse(date_)); return pd; } catch (err) { return null; } }, /** * @method getUUID * @memberof butor.Utils# * @description Get an universally unique identifier conform to RFC-4122, section 4.4 on 32 digits. * @return {string} An UUID (hex char). * @see {@link http://www.ietf.org/rfc/rfc4122.txt RFC-4122} * * @example * butor.Utils.getUUID(); * // output: '1E9EBC2A-8421-4F6A-989D-D6A3DE72FF6D' */ getUUID: function() { var s = [], itoh = '0123456789ABCDEF'; // Make array of random hex digits. The UUID only has 32 digits in // it, but we // allocate an extra items to make room for the '-'s we'll be // inserting. for (var i = 0; i < 36; i++) s[i] = Math.floor(Math.random() * 0x10); // Conform to RFC-4122, section 4.4 s[14] = 4; // Set 4 high bits of time_high field to version s[19] = (s[19] & 0x3) | 0x8; // Specify 2 high bits of clock // sequence // Convert to hex chars for (var i = 0; i < 36; i++) s[i] = itoh[s[i]]; // Insert '-'s s[8] = s[13] = s[18] = s[23] = '-'; return s.join(''); }, /** * @method isEmpty * @memberof butor.Utils# * @description Check if a string is empty, undefined or null. * @param {string} str - The string to check. * @return {boolean} Return **true** if the string is empty, undefined or null. * * @example * butor.Utils.isEmpty(); // output: true * butor.Utils.isEmpty('AmIEmpty?'); // output: false */ isEmpty: function(str) { return str === null || str === undefined || String(str).trim() === ''; }, /** * @method nullToEmpty * @memberof butor.Utils# * @description Transform a null or undefined string into an empty string. * @param {string|null|undefined} str - The string to check. * @return {boolean} An empty string (equal to ''). * * @example * butor.Utils.nullToEmpty(); // output: '' */ nullToEmpty: function(str) { return str === null || str === undefined ? '' : str; }, /** * @method isInteger * @memberof butor.Utils# * @description Check if a value is an Integer. * @param {Number} val - The value to check. * @return {boolean} True if the value is an Integer. * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt MDN Documentation for parseInt method} * * @example * // Get the isInteger function from the Utils singleton * isInteger = butor.Utils.isInteger; * * isInteger('Integer'); // output: false * isInteger('2'); // output: true * isInteger(2); // output: true * isInteger(2.0); // output: true * isInteger(2.87654); // output: false */ isInteger: function(val) { return !butor.Utils.isEmpty(val) && parseInt(val) == val; }, /** * @method isFloat * @memberof butor.Utils# * @description Check if a value is a Float. * @param {Number} val - The value to check. * @return {boolean} True if the value is an Integer. * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat MDN Documentation for parseFloat method} * * @example * // Get the isInteger function from the Utils singleton * isFloat = butor.Utils.isFloat; * * isFloat('Float'); // output: false * isFloat('2'); // output: true * isFloat(2); // output: true * isFloat(2.0); // output: true * isFloat(2.87654); // output: true */ isFloat: function(val) { return !butor.Utils.isEmpty(val) && parseFloat(val) == val; }, /** * @method enable * @memberof butor.Utils# * @description Enable the HTML element of a given jQuery Selector. * @param {jQueryElement} selector - jQuery Selector. * @param {boolean} [enabled=true] - Flag which enable the selector in the App. * @see butor.Utils#disable * * @example * butor.Utils.disable($("div.bar")); * // output: "
​" * * butor.Utils.enable($("div.bar")); * // output: "
​" * * for(var i=0; i<10; i++) { * butor.Utils.enable($("div.bar"), (i>5)?true:false); * console.log($("div.bar").attr("disabled")); * } */ enable: function(selector_, enabled) { if (enabled == null) { enabled = true; } _setEnable(selector_, enabled); }, /** * @method disable * @memberof butor.Utils# * @description Disable the HTML element of a given jQuery Selector. * @param {jQuerySelector} selector - jQuery Selector. * @see butor.Utils#enable */ disable: function(selector_) { _setEnable(selector_, false); }, /** * @method selectNavItem * @memberof butor.Utils# * @description Select an item in the nav bar and unselect the previous selected item. * @param {jQuerySelector} navbar - The navbar tag. * @param {jQuerySelector} item - The item to select. * * @example * * * * @example * // Example tests * butor.Utils.selectNavItem("nav","#menu1"); * // output: * // * * butor.Utils.selectNavItem("nav","#menu2"); * // output: * // */ selectNavItem: function(navbar, itemId_) { $(navbar).find("li.active").removeClass("active"); $(navbar).find(itemId_).addClass("active"); }, //TODO Example really used in a butor application (tutorial) /** * @method fillSelect * @memberof butor.Utils# * @description Fill a select element. * @param {jQueryElement} jqSelect - jQuery select object. * * @param {object[]} list - The list of items which fill the HTML select. * @param {string} list.id - The item value. * * **Be careful**, 'id' or 'k1' are default keys, but others keys can be set in order to identify the item value. * * @param {string} list.desc - The item description. * * **Be careful**, 'value', 'description' or 'desc' are default keys. * * @param {object} [opts] - The list of options. * @param {boolean} [opts.showEmpty=false] - The first value of the select is empty if `showEmpty` is true. * @param {boolean} [opts.emptyLabel=false] - The first value of the select has a label with an empty code (only if `showEmpty` is true). * @param {string} [opts.idFN='id'] - An unique id. * @param {string} [opts.descFN='desc'] - A description field name. * The default option depend of the keys in the list of item. * @param {boolean} [opts.sorted=false] - If true, the list of items will be sorted by their descriptions. * @param {string} [emptyLabel=''] - If showEmpty is true, an specific label is shown with the specified value. * * @example * * * * @example * // Example tests * var beatles = [ * {id: 'b1', firstName: 'John', lastName: 'Lennon', role: 'Vocals'}, * {id: 'b2', firstName: 'Paul', lastName: 'McCartney', role: 'Vocals'}, * {id: 'b3',firstName: 'Georges', lastName: 'Harrison' ,role: 'Guitar'}, * {id: 'b4',firstName: 'Ringo', lastName: 'Starr', role: 'Drums'} * ]; * * var opts = { * showEmpty: true, * idFN: 'id', * descFN: 'firstName', * sorted: true * }; * * butor.Utils.fillSelect($('select'), beatles, opts); * * @example * * */ fillSelect: function(jqSelect, list, opts) { jqSelect.empty(); opts = opts || {}; if (opts['showEmpty']) { var ei = $(''); var emptyLabel = opts['emptyLabel']; if (emptyLabel == null) { emptyLabel = App.tr('--- select ---'); } ei.html(emptyLabel); ei.appendTo(jqSelect); } if (!list || list.length === 0) { return; } var item = list[0]; if (opts.idFN === undefined) { if (item['k1'] !== undefined) { //it is a codeset opts.idFN = 'k1'; } else { opts.idFN = 'id'; } } if (opts.descFN === undefined) { if (item['value'] !== undefined) { //it is a codeset opts.descFN = 'value'; } else { if (item.desc) { opts.descFN = 'desc'; } else if (item.description) { opts.descFN = 'description'; } else if (item.value) { opts.descFN = 'value'; } } } var idFN = opts.idFN; var descFN = opts.descFN; if (opts.sorted == null || opts.sorted == true) { list = list.sort(function(a, b) { var d1 = a[descFN]; var d2 = b[descFN]; return d1.localeCompare(d2); }) } for (var ii = 0; ii < list.length; ii++) { var item = list[ii]; $('') .appendTo(jqSelect); } }, /** * @method formatCurrency * @memberof butor.Utils# * @description Formats a value into currency. * @param {Number} number - The value to format in currency. * @return {string} The formated string. * * @example * butor.Utils.formatCurrency(2); // output: '2.00' * butor.Utils.formatCurrency('2042.2042'); // output: '2,042.20' */ formatCurrency: function(N) { return butor.Utils.formatNumber(N, 2); }, /** * @method formatInt * @memberof butor.Utils# * @description Formats a value into Integer. * @param {Number} number - The value to format in Integer. * @return {string} The formated string. * * @example * butor.Utils.formatInt(2.048); // output: '2' * butor.Utils.formatInt('2.048'); // output: '2' */ formatInt: function(N) { return butor.Utils.formatNumber(N, 0); }, /** * @method formatYield * @memberof butor.Utils# * @description Formats a value into a number with 2 decimals and yielded. * @param {Number} number - Value to format into yielded number. * @return {string} The formated string. * * @example * butor.Utils.formatYield(2.045555555); // output: '2.05' * butor.Utils.formatYield(2.044999999); // output: '2.04' */ formatYield: function(N) { return butor.Utils.formatNumber(N, 2); }, /** * @method formatPrice * @memberof butor.Utils# * @description Formats a value into a number with 3 decimals and yielded as a price. * @param {Number} number - Value to format into price number. * @return {string} The formated price string. * * @example * butor.Utils.formatPrice(2.045555555); // output: '2.046' * butor.Utils.formatPrice(2.044999999); // output: '2.045' */ formatPrice: function(N) { return butor.Utils.formatNumber(N, 3); }, /** * @method formatNumber * @memberof butor.Utils# * @description Formats a number into string. * @param {string} number - Value to format. * @param {Number} [decimals=0] - Number of decimals for formating. * @param {string} [separator=','] - Separator for formating. * @return {string} The formated number string. * * @example * butor.Utils.formatNumber('287,467',4,'-'); // output: '287-467.0000' * butor.Utils.formatNumber('287,467',1,'.'); // output: '287.467.0' * butor.Utils.formatNumber('287.467'); // output: '287' */ formatNumber: function(N, dec, sep) { if (dec === "undefined") dec = 2; if (!sep) sep = ','; if (typeof(N) === "string") { N = N.replace(/,/g, ''); N = parseFloat(N); } if (isNaN(N)) N = 0; N = N.toFixed(dec); var cpl = N.split("."); N = cpl[0].replace(/.(?=(?:.{3})+$)/g, '$&' + sep) + (dec > 0 ? '.' + cpl[ 1] : ''); if (N.indexOf('-,') == 0) { N = N.replace('-,', '-'); } return N; }, // direction: 1:asc, 0:desc /** * @method multiColSort * @memberof butor.Utils# * @description Sorts a list of object on multiple fields. * @param {Object[]} list - List of Object to sort. * @param {string[]} fields - Array of fields to sort on. * @param {Number} [direction=1] - Direction of sorting (acsendant:1 and descendant:-1). * @return {Object[]} The sorted list with the priority order for the fields. * * @example * // Initialisation * var beatles = [ * {id: 1, firstName: 'John', lastName: 'Lennon', role: 'Vocals'}, * {id: 2, firstName: 'Paul', lastName: 'McCartney', role: 'Vocals'}, * {id: 3, firstName: 'Georges', lastName: 'Harrison', role: 'Guitar'}, * {id: 4, firstName: 'Ringo', lastName: 'Starr', role: 'Drums'} * ]; * * $.each(beatles, function(index, item) { * item.label = [item.id, item.firstName, item.lastName, item.role].join(' '); * }); * * var opts = { * showEmpty: true, * idFN: 'id', * descFN: 'label', * sorted: false * }; * * // Fields to sort on * var fields1 = ['firstName', 'lastName']; * var fields2 = ['role', 'id']; * * // Acsendant sort by firstName * var r1 = butor.Utils.multiColSort(beatles, fields1, 1); * butor.Utils.fillSelect($('#select1'), r1, opts); * * // Descendant sort by firstName * var r2 = butor.Utils.multiColSort(beatles, fields1, -1); * butor.Utils.fillSelect($('#select2'), r2, opts); * * // Acsendant sort by Role (then by ID) * var r3 = butor.Utils.multiColSort(beatles, fields2); * butor.Utils.fillSelect($('#select3'), r3, opts); * * @example * * * * */ multiColSort: function(list, fields, direction) { var keySort = function(a, b, d) { d = (d != -1 && d != 1 ? 1 : d); if (a == b) { return 0; } return a > b ? 1 * d : -1 * d; }; var sList = list.sort(function(a, b) { var sorted = 0; var count = fields.length; var done = 0; while (sorted == 0 && done < count) { var field = fields[done++]; sorted = keySort(a[field], b[field], direction); } return sorted; }); return sList; } }; }(); /** * @class DatePicker * @memberof butor * @description A wrapper of Bootstrap DatePicker. * * This wrapper is used in order to build a date picker quickly only with the constructor parameters. * It adds the automatic translation when the language plateform is switched, a fired event when the date picker is {@link butor.DatePicker#event:change changed} and a validation system in order to check if the date input is correct. * * @param {jQueryElement} jqe - jQuery element. * * @param {Map} options - Map of options used to configure the build of the date picker when it is instancied. * @param {MsgPanel} [options.msgPanel=null] - A message panel which display the date picker notifications. * @param {string} [options.errorMsg=null] - Error message to display. * @param {string} [options.format='yyyy/mm/dd'] - Format of the date. * @param {boolean} [options.forceParse=true] - Flag to force the parsing of input date. * @param {boolean} [options.autoclose=true] - Flag to close automatically the date picker. * * @example *
* *
* * @example * var nowTemp = new Date(); * var now = new Date(nowTemp.getFullYear(), nowTemp.getMonth(), nowTemp.getDate(), 0, * 0, 0, 0); * * var args = { * 'msgPanel': null, * 'errorMsg': 'erreur', * 'format': "yyyy/mm/dd", * 'forceParse': true, * 'autoclose': true, * 'orientation': 'bottom', * 'language': 'en', * }; * * args['msgPanel'] = new butor.panel.MsgPanel($('body'), '400px', '100px'); * * var datePicker = new butor.DatePicker($('#datepicker'), args); * datePicker.setDate(now); * * datePicker.bind('change', function(e){ * var date = e.data; * args['msgPanel'].info('Date has changed to ' + date + '.'); * }); */ butor.DatePicker = butor.Class.define({ _jqe : null, _date : null, _msgPanel : null, _isValid : false, _errorMsg : null, _prevValue : null, _options : { 'msgPanel':null, 'errorMsg':null, 'format': "yyyy/mm/dd", 'forceParse': true, 'autoclose': true, 'todayHighlight': true, 'todayBtn':true, 'weekStart':1 }, construct : function(jqe, options) { if (!jqe) { return; } this._options['language'] = $.cookie('lang'); this._jqe = jqe; this._date = jqe.find('input.date'); this._options= $.extend(this._options, options); this._msgPanel = this._options.msgPanel; this._errorMsg = this._options.errorMsg || App.getMsgPanel(); this._date.datepicker(this._options).change($.proxy(this._dateChanged, this)); }, /** * @method isValid * @memberof butor.DatePicker# * @description Checks the inner date of the DatePicker. */ isValid: function() { return butor.Utils.isEmpty(this._date.val()) || this._isValid; }, /** * @method getDate * @memberof butor.DatePicker# * @description Gets the date value of the datePicker. * @return {string} The date value. */ getDate: function() { if (!this.isValid()) { return null; } return this._date.datepicker('getDate'); }, /** * @method setDate * @memberof butor.DatePicker# * @description Sets the date value. * @param {string} date - Date value. */ setDate: function(date) { this._prevValue = this.getDate(); this._date.datepicker('setDate', date); this.validate(); }, /** * @method setErrorMsg * @memberof butor.DatePicker# * @description Sets the error message. * @param {string} errorMsg - Error message. */ setErrorMsg: function(errorMsg) { this._errorMsg = errorMsg; }, /** * @method _dateChanged * @access private * @memberof butor.DatePicker# * @description Notices a date changement. * @fires butor.DatePicker#change */ _dateChanged: function() { this._msgPanel && this._msgPanel.hideMsg(); this.validate(); if (!this._isValid && this._errorMsg && this._msgPanel) { this._msgPanel && this._msgPanel.error(this._errorMsg); } var d = this.getDate(); var diff = true; try { diff = (this._prevValue != null ? this._prevValue.getTime() : 0) != (d != null ? d.getTime() : 0); } catch (err) { //nevermind; } if (diff) { this.fire("change", d); } this._prevValue = d; }, /** * @method validate * @memberof butor.DatePicker# * @description Validates the date. * @return {boolean} True if the date is valid. */ validate: function() { // bootstrap datepicker validates through forceParse. Enforces backward compatibility this._isValid = true; return this._isValid; } }); /** * @class AttrSet * @memberof butor * @description Provider of helping methods in order to manipulate sets of attributes. * * This provider gets resources from the `codeset.ajax` service. * This service is used to manipulates static values stores in the plateform database, there are specific to a system or are redundant values in the plateform. Thus, it provides an helper to get some specific codeset with an unique ajax call and another helper for the codeset manipulation. * * @param {string} [lang=App.getLang()] - Language of the required code set. * @param {string} sysId - ID of the requested system. * @param {string} context - Name of the requested system. * */ butor.AttrSet = butor.Class.define({ /** * @method getCodeSet * @memberof butor.AttrSet# * @description Gets a code set from the `codeset.ajax` module. * * The AttrSet manager provides a method to get some data stored in a specific database * called `attrSet`. This database contains reusable values in all the plateform and this function * is to make easier the AJAX request for this service. It also provides some helper in order to * manipulate these sets of values. * @param {Map} args - Set of arguments for the codeset request. * @param {string} [args.lang=App.getLang()] - Language of the required code set. * @param {string} args.sysId - ID of the requested system. * @param {string} args.context - Name of the requested system. * * @param {Object} handler - Attached handler. * @return {Number} ID of the AJAX request. * * @example * * * @example * var attr = new butor.AttrSet(); * * var args = [ * { 'context': 'exampleSystem', 'lang': 'en' }, * { 'lang': 'fr' }, * { 'lang': 'es' } * ]; * * * var handler = function() { * butor.Utils.fillSelect($('.codeSet'), result.data[0]); * } * * attr.getCodeSet(args[0], handler); * attr.getCodeSets(args, handler); */ //TODO unsafe description getCodeSet: function(sArgs_, handler_) { if (sArgs_ instanceof Array) { return this.getCodeSets(sArgs_, handler_); } // lang could be '' (no specific lang) but not undefined if (sArgs_.lang === undefined) { sArgs_.lang = App.getLang(); } var args = { streaming: false, service: 'getCodeSet' }; var context = sArgs_.context ? sArgs_.context : sArgs_.sysId; var reqId = AJAX.call((context ? '/' + context : '') + '/codeset.ajax', args, [sArgs_], handler_); return reqId; }, /** * @method getCodeSets * @memberof butor.AttrSet# * @description Gets a code sets from the `codeset.ajax` module. * @param {Map[]} sArgs - Array of sets described as {@link butor.AttrSet#CodeSetRequestArguments CodeSetRequestArguments} * @param {Object} handler - Handler attached. * @return {Number} ID of the AJAX request. * @see butor.AttrSet#getCodeSet */ getCodeSets: function(sArgs_, handler_) { if (!(sArgs_ instanceof Array)) { sArgs_ = [sArgs_]; } for (var ii in sArgs_) { // lang could be '' (no specific lang) but not undefined if (sArgs_[ii].lang === undefined) { sArgs_[ii].lang = App.getLang(); } } var args = { streaming: false, service: 'getCodeSets' }; var context = sArgs_[0].context ? sArgs_[0].context : sArgs_[0].sysId; var reqId = AJAX.call((context ? '/' + context : '') + '/codeset.ajax', args, [sArgs_], handler_); return reqId; }, /** * @member {Object} Helper * @memberof butor.AttrSet# * @access private * @description Functions providers for manipulations of data sets. */ /** * @method createHelper * @memberof butor.AttrSet * @description * Creates a helper in order to manipulate map. * It provides some functions: * - **get(id)**: return the id value. * - **list()**: return a copy of the built map. * - **size()**: return the map size. * - **add(id, value)**: add a value linked to the id. * - **remove(id)**: remove the value linked to the id. * * @param {Object} attrSet - Set of attributes. * @return {AttrSetHelper} A Helper object. * * @example *
* @example * var makeDataList = function(selector, helper) { * selector.empty(); * selector.append($('

').text('DATA:')); * * var ul = $('
    '); * * for (var i = 0; i < helper.size(); i++) { * var id = helper.list()[i].id; * ul.append($('
  • ').text(helper.get(id))); * } * selector.append(ul); * } * * var attr = new butor.AttrSet(); * * var list = [ * {'id': 'b0', 'value': 'Unknown'}, * {'id': 'b1', 'value': 'Lennon'}, * {'id': 'b2', 'value': 'McCartney'}, * {'id': 'b3', 'value': 'Harrison'}, * ]; * * var helper = attr.createHelper(list); * helper.add('b4', 'Starr'); * helper.remove('b0'); * makeDataList($('.attrSet'), helper); */ createHelper: function(attrSet_) { return new function() { var _list = attrSet_; var _map = {}; if (attrSet_) { for (var ii = 0; ii < attrSet_.length; ii++) { var item = attrSet_[ii]; var id = item.k1 || item.id; _map[id] = item.value; } } return { /** * @method get * @description Gets the required value. * @memberof butor.AttrSet#Helper * @param {string} id - ID of the value. * @return {string} - The value. */ get: function(id_) { return _map[id_]; }, /** * @method list * @description Gets a copy of the original set. * @memberof butor.AttrSet#Helper * @return {Map[]} - A copy of the original set. */ list: function() { return _list.slice(0); // a copy }, /** * @method size * @description Gets the size of the set. * @memberof butor.AttrSet#Helper * @return {Number} - The size of the set. */ size: function() { return _list ? _list.length : 0; }, /** * @method add * @description Adds a key/value element in the set. * @memberof butor.AttrSet#Helper * @param {string} - The key of the element. * @param {string} - The value of the element. */ add: function(id, value) { if (!_map[id]) { _list.push({ 'id': id, 'value': value }); } _map[id] = value; }, /** * @method remove * @description Removes a key/value element in the set. * @memberof butor.AttrSet#Helper * @param {string} - The key of the element. */ remove: function(id) { if (!_map[id]) { return; } for (var ii = 0; ii < _list.length; ii++) { var item = _list[ii]; if (item.id == id) { // MODIFICATION //delete _list[ii]; _list.splice(ii, 1); break; } } delete _map[id]; } }; }(); } }); /** * @class Bundle * @memberof butor * @description Bundle for internationalization of the platform. * * This class manages all the sets of texts in different languages as JSON objects. * It provides the methods {@link butor.Bundle#add add}, {@link butor.Bundle#override override} and * {@link butor.Bundle#remove remove} in order to simply manage all your bundles of texts. * The methods {@link butor.Bundle#getLang getLang} or {@link butor.Bundle#setLang setLang} are used * to change the default language of the application and {@link butor.Bundle#get get} or {@link butor.Bundle#getEntry getEntry} are used to get a value from a specific bundle (each bundle is characterized by its ID), a key and the current language of the application. * * @example * // Bundle example * var signBundle = { * 'Sign out': {'fr': 'Déconnecter'}, * 'Sign in': {'fr': 'Se connecter'}, * } * * var signBundle2 = { * 'Sign out': {'fr': 'Déconnexion'}, * 'Sign in': {'fr': 'Connexion'}, * } * * @example * var bundle = new butor.Bundle(); * bundle.add('sign', signBundle); * * bundle.getLang(); * // output: 'en' (default language) * * bundle.get('Sign out', 'sign'); * // output: "Sign out" * * bundle.getEntry('Sign out', 'sign'); * // output: Object { fr="Déconnecter"} * * bundle.get('Sign out', 'sign'); * // output: "Sign out" * * bundle.setLang('fr'); * bundle.get('Sign out', 'sign'); * // output: "Déconnecter" * * bundle.setLang('es'); * bundle.get('Sign out', 'sign'); * // output: "Sign out" ('en language is undefined') * * bundle.override('sign', signBundle2); * bundle.get('Sign out', 'sign'); * // output: "Déconnexion" * * bundle.remove('sign'); * bundle.get('Sign out', 'sign'); * // output: WARN Failed to get bundle with appId:"sign", key:"Sign out" */ butor.Bundle = butor.Class.define({ construct: function() { this._bundles = {}; this._overrides = {}; this._curLang = $.cookie("lang"); if (butor.Utils.isEmpty(this._curLang)) { this._curLang = 'en'; } this._defaultBundleId = 'common'; }, /** * @method getLang * @memberof butor.Bundle# * @description Gets the current language. * @return {string} The current language. */ getLang: function() { return this._curLang; }, /** * @method setLang * @memberof butor.Bundle# * @description Sets the current language. * @param {string} lang - The current language. */ setLang: function(lang) { if (butor.Utils.isEmpty(lang)) { return; } this._curLang = lang; $.cookie("lang", this._curLang, { 'path': "/", 'expires': 1000 }); }, /** * @method add * @memberof butor.Bundle# * @description Adds a new language in the platform. * @param {string} bundleId - ID of the new bundle. * @param {Map} bundle - The new bundle. */ add: function(bundleId, bundle) { if (!this._bundles[bundleId]) { this._bundles[bundleId] = bundle; } else { $.extend(this._bundles[bundleId], bundle); } if (this._overrides[bundleId]) { $.extend(this._bundles[bundleId], this._overrides[bundleId]); } }, /** * @method override * @memberof butor.Bundle# * @description Overrides a bundle with a new one. * @param {string} bundleId - ID of the new bundle. * @param {Map} bundle - New Bundle. */ override: function(bundleId, bundle) { this._overrides[bundleId] = bundle; this.add(bundleId, {}); }, /** * @method remove * @memberof butor.Bundle# * @description Removes a bundle. * @param {string} bundleId - ID of the new bundle. */ remove: function(bundleId) { this._bundles[bundleId] = null; }, /** * @method get * @memberof butor.Bundle# * @description Gets the value from a key. * @param {string} key - Key of the entry. * @param {string} bundleId - ID of the bundle. * @return {string} The bundle value. */ get: function(key_, bundleId_) { var val = key_; var entry = this.getEntry(key_, bundleId_); if (entry) { val = entry[this._curLang]; if (butor.Utils.isEmpty(val)) { val = key_; } } if (val == '_tr_') { // to be translated? // some times keys are not the real english text, so grap the english test // and mark it to be translated val = key_; if (entry && this._curLang != 'en') { val = entry['en'] || key_; } val = '' + this._curLang + ' ' + val; } return val; }, /** * @method getEntry * @memberof butor.Bundle# * @description Gets a bundle entry. * @param {string} key - Key of the entry. * @param {string} bundleId - ID of the bundle. */ getEntry: function(key_, bundleId_) { var bundleId = bundleId_ || this._defaultBundleId; var entry; try { entry = this._bundles[bundleId][key_]; } catch (err) {} if (entry === undefined) { if (bundleId === this._defaultBundleId) { LOGGER.warn( 'Failed to get bundle with appId:"{}", key:"{}"', bundleId_, key_); } else { return this.getEntry(key_, this._defaultBundleId, bundleId); } } return entry; } }); LOGGER = new butor.Logger(); /** * @class AJAX * @memberof butor * @description Manages all AJAX requests in order to call the Back-End services. * * This manager provides a main method {@link butor.AJAX#call call} and some helpers as * {@link butor.AJAX#isSuccess isSuccess}, {@link butor.AJAX#isSuccessWithData isSuccessWithData}, * {@link butor.AJAX#maskFieldsInRequestLog maskFieldsInRequestLog} and {@link butor.AJAX#fakeResponse fakeResponse}. * The {@link butor.AJAX#call call} needs a lot of provider and it is useful to encapsulate it * into a closure in order to keep all its statics parameter unchanged with a lot of use of this method. * The methods {@link butor.AJAX#isSuccess isSuccess} and {@link butor.AJAX#isSuccessWithData isSuccessWithData} * are mainly used into the {@link butor.AJAX#call call} handler in order to check if the request is succeed. * * @singleton * * @example * // It's an example class of an AJAX caller: it helps to call 2 services `exampleService1` and `exampleService2`. * butor.example.ExampleAjax = butor.example.ExampleAjax || function() { * return { * exampleDo1: function(args, handler) { * return AJAX.call('/example/exampleServices.ajax', * {streaming: false, service: 'exampleService1'}, * [args], handler * ); * }, * exampleDo2: function(exampleId, handler) { * return AJAX.call('/example/exampleService.ajax', * {streaming: true, service: 'exampleService2'}, * [exampleId], handler * ); * }, * } * }(); * * var request1, request2; * * var exampleId = 118218; * var args = { * 'exampleId': exampleId, * 'isExample': true * } * * var ajaxCallHandler = { * 'msgHandler': App.getMsgPanel(), * 'scope': this, * 'callback': function(response) { * if (!AJAX.isSuccessWithData(response, this._reqExample)) { * return; * } * * console.log(response['data']); * } * } * var dlg = new butor.dlg.Dialog(); * * request1 = butor.example.ExampleAjax.exampleDo1(args, ajaxCallHandler); * request2 = butor.example.ExampleAjax.exampleDo2(exampleId, { * 'msgPanel': App.getBottomPanel(), * 'callback': function(resp) { * console.log(this); * } * }); */ butor.AJAX = function() { var _app; var _lastNotifTime; var _streamingReqMap = {}; // do not log fields of json request that contains (case i) these words var _fieldsToMaskInRequestLogRE = null; /** * @method parseMessages * @memberof butor.AJAX# * @description Parses messages for an AJAX request * @access private * @param {Array} messages - Array of messages to parse. * @param {Func} tr - Translator of messages. */ var parseMessages = function(messages, tr) { if (!messages || messages.length === 0) return null; tr = tr || $.proxy(getApp().tr, getApp()); var msgs = ""; var hasError = false; var hasWarning = false; for (var ii = 0; ii < messages.length; ii++) { var msg = messages[ii]; if (msg['type'] === 'DATA') continue; if (msg['message']) { var txt = tr(msg['id'], msg['sysId']); // bundle id? placeholders? if (txt.indexOf("{") > -1) { var toks = msg['message'].split(","); for (var pp = 0; pp < toks.length; pp++) { var ptr = tr(toks[pp], msg['sysId']); txt = txt.replace("{" + (pp + 1) + "}", ptr); } msgs += txt + "\n"; } else { msgs += txt + " " + msg['message'] + "\n"; } } else { msgs += tr(msg['id'], msg['sysId']) + "\n"; } if (msg['type'] === 'ERROR') { if (msg['id'] === 'SESSION_TIMEDOUT') { getApp().sessionTimedOut(); } hasError = true; } else if (msg['type'] === 'WARNING') { hasWarning = true; } } return { 'isError': hasError, 'isWarning': hasWarning, 'msg': msgs }; }; /** * @method cleanStreamingReq * @memberof butor.AJAX# * @access private * @description Cleans the AJAX requests for streaming * @param {Number} reqId - ID of the AJAX request. */ function cleanStreamingReq(reqId) { setTimeout(function() { $('#sr_' + reqId).remove(); }, 1000); }; /** * @method detCallback * @access private * @memberof butor.AJAX# * @description Gets a callback function for the request. * @param {Object|Function} handler - Handler of the linked request. * @return {Object} A callback for the request. */ function detCallback(handler_) { var callBack; if (handler_.callBack) { handler_.callback = handler_.callBack; } if (typeof handler_ == 'function') { callBack = handler_; } else if (handler_ && handler_.callback && handler_.scope) { callBack = $.proxy(handler_.callback, handler_.scope); } else if (handler_ && handler_.callback) { callBack = handler_.callback; } return callBack; }; /** * @method getApp * @access private * @memberof butor.AJAX# * @description Gets the application for the request. * @return {Object} An application object. */ function getApp() { return _app != null ? _app : App; } return { /** * @method setApp * @memberof butor.AJAX# * @description Sets the application for the request: it defines itself a lot of mandatory options for the AJAX request as the language or the session ID. It needs to be called before a `call(url, opts, handler)`. * @param {butor.App} app - Application object. */ setApp: function(app) { _app = app; }, parseMessages: parseMessages, /** * @method call * @memberof butor.AJAX# * @description Calls a Back-End service by making an AJAX request. * @param {string} url - URL to call. * * @param {Map} opts - AJAX call options. * @param {boolean} [opts.download=false] - Switch for download procedure. * @param {boolean} [opts.streaming=false] - Switch for streaming procedure. * @param {string} opts.target - Target of the download. * @param {string} opts.services - Service name linked to the request. * * @param {Map} serviceArgs - Parameters of the service. * * @param {Object} handler - Handler of the request. * @param {object} [handler.scope=callback] - Scope of the callback. * * By default the value **this** of the callback specifies its own scope, * but it can be overriden by any specified active instance. * @param {MsgPanel | BottomPanel} [handler.msgHandler={@link butor.App#getMsgPanel getMsgPanel}] - Default message panel. * * This is the message panel used to display the message throws by the AJAX request. * For example if the back-end services has an error, it will be display in this message panel. * @param {function} handler.callback - Callback of the request. * * It is called whenever the AJAX call is ended. * This callback accepted the AJAX response as a parameter which contains all information linked to the AJAX request. * * @return {string|null} ID of the request (or null if the request fails). * * __Advice:__ _The `reqId` is usefull for debug and is always referenced in * logging in order to keep a trace._ * * @example * butor.example.ExampleAjax = function() { * return { * doAJAX : function(args, handler) { * return AJAX.call( * '/example/example.do.ajax', * {streaming: false, service: 'exampleAjax'}, * args, * handler * ); * } * }; * }; * * var request = butor.example.ExampleAJAX.doAJAX({}, { * 'scope': this, * 'callback': function(result) { * console.log("An AJAX request has been sent."); * } * } */ call: function(url, args, serviceArgs, handler) { var callBack = detCallback(handler); if (!callBack) { LOGGER.warn('No ajax handler provided for url=' + url); } handler.cb = callBack; var msgHandler = handler && handler.msgHandler ? handler.msgHandler : getApp(); try { if (!args) { msgHandler.error({ 'msg': 'Missing service call info!' }); return; } if (butor.Utils.isEmpty(args['streaming'])) args['streaming'] = false; args['lang'] = getApp().getLang(); args['reqId'] = 'ajax-' + butor.Utils.getUUID(); args['sessionId'] = getApp().getSessionId(); if (LOGGER.infoEnabled()) { args['args'] = JSON.stringify(serviceArgs); var str = JSON.stringify(args); if (_fieldsToMaskInRequestLogRE) { for (var r = 0; r < _fieldsToMaskInRequestLogRE.length; r += 1) { var d = str.match(_fieldsToMaskInRequestLogRE[r]); if (d) { for (var i = 0; i < d.length; i += 1) { var repl = d[i].split(':')[0] + ':"*"'; str = str.replace(d[i], repl); } } } } LOGGER.info('====> REQUEST url:' + url + ', args:' + str); } args['args'] = encodeURIComponent(JSON.stringify(serviceArgs)); if (args['download']) { var form = $(''); form.appendTo($('body')); if (args['target']) { form.prop('target', args['target']); } for (var key in args) { if (key == 'download' || key == 'streaming') { continue; } var val = args[key]; var elm = $(''); form.append(elm); } form.submit().remove(); (function() { var reqId = args['reqId']; var checkReady = function() { // download start when a cookie // named as the request Id appear. // the download service set that cookie just // before is starts streaming var reqC = $.cookie(reqId); LOGGER.info('cookie(' + reqId + ')=' + reqC); if (reqC) { $.removeCookie(reqId); callBack && callBack({ 'reqId': reqId, 'data': 'started' }); } else { setTimeout(checkReady, 1000); } }; setTimeout(checkReady, 1000); })(); return args['reqId']; } if (args['streaming']) { _streamingReqMap[args['reqId']] = handler; AJAX.streamingReq(args['reqId'], url, $.param(args)); return args['reqId']; } $.ajax({ 'type': 'POST', 'url': url, 'data': args, 'success': function(json_) { if (args['streaming']) return; try { if (handler !== null) { if (butor.Utils.isEmpty(json_)) { return; } var result; if (typeof(json_) === "object") { if (LOGGER.infoEnabled()) { LOGGER.info("-----> RESPONSE: " + JSON.stringify(json_)); } result = json_; } else { if (LOGGER.infoEnabled()) { LOGGER.info("-----> RESPONSE: " + json_); } result = jQuery.parseJSON(json_); } if (!result) { return; } var msg = parseMessages(result['messages']); if (msg && msg.msg && msg.msg.length > 0) { result['hasError'] = msg['isError']; msgHandler.showMsg(msg); } callBack(result); } } catch (err) { msgHandler.error(err); LOGGER.error(err); if (callBack !== null) { callBack(null); } } }, 'error': function(jqXHR, textStatus, errorThrown) { if (textStatus == 'timeout') { msgHandler.error(msgHandler.tr( "Service call timed out! Please retry in few moments")); LOGGER.error(errorThrown); } else if (jqXHR.status == 0) { msgHandler.error(msgHandler.tr( "Service unreachable! Please retry in few moments")); LOGGER.error("Network problem: " + errorThrown); } else { msgHandler.error(errorThrown); LOGGER.error(errorThrown); } if (callBack !== null) { callBack(null); } } }); return args['reqId']; } catch (err) { msgHandler.error(err); LOGGER.error(err); if (callBack !== null) { callBack(null); } return null; } }, /** * @method streamingReq * @memberof butor.AJAX# * @access private * @description Makes a streamed AJAX request. * @param {Number} reqId - ID of the request. * @param {string} url - URL to call. * @param {Object} args - Arguments of the AJAX request. */ streamingReq: function(reqId_, url, args) { var sr = $('