butor.js.butor.js Maven / Gradle / Ivy
The 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')||'en';
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 = $('