toloframework
Version:
Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.
1,456 lines (1,355 loc) • 43.6 kB
JavaScript
// Utilisée pour la compatibilité avec Trail-Passion 3.
// Mais vouée à disparaitre.
/**
* ToloFrameWork v2.2
*-------------------------------------------------------------------------------
* L'instanciation étant ce que l'on fait le plus souvent avec
* Toloframework, l'écriture en est simplifiée au maximum. Voici un
* exemple d'utilisation :
* var a = $$("sys.widget.Button", {caption: "Click Me!"});
*
* Si la classe n'existe pas, on le trace dans le log, et on retourne
* null. Attention ! si la classe existe mais possède une erreur de
* syntaxe, on retourne aussi null.
*
* @param className Nom complet de la classe.
* @param attribs [Optionel] Dictionnaire des attributs et de leurs valeurs initiales.
*/
window.$$ = function(className, attribs) {
var single,
cls,
obj,
k,
path,
names,
signals,
cur,
i,
f,
sn,
n,
s;
// S'agit-t-il d'un singleton déjà instancié ?
single = $$._.singletons[className];
if (single && typeof single === "object") {
return single;
}
if (attribs) $$.trace(attribs, 9);
cls = $$.loadClass(className);
obj = new cls();
obj.$id = $$.newId();
// Mise à jour des attributs.
if (typeof attribs === "object") {
for (k in attribs) {
if (!k) continue;
obj["_" + k] = attribs[k];
}
}
// La mécanique suivante sert à récupérer la hiérarchie des
// classes afin d'appeler les constructeurs les uns après les
// autres. Sans cette astuce, les attributs de construction
// ne sont pas renseignés quand la super classe exécute son
// constructeur.
path = [];
names = [];
signals = {};
cur = cls.prototype;
while (cur) {
if (cur.$signals) {
for (i in cur.$signals) {
signals[cur.$signals[i]] = 1;
}
}
f = cur.$init;
/**
* Attention !
* Quand on ne définit pas de méthode init() et que l'on
* hérite d'une classe en définissant une, f pointera non
* pas sur null, mais sur la méthode init() du parent.
*/
if (f) {
if (f != path[path.length - 1]) {
path.push(f);
names.push(cur.$classname);
}
}
cur = cur.$parent;
}
// Création des signals.
for(sn in signals) {
// Signal Name.
if (sn) {
s = new $$.Signal(obj, sn);
obj["$_" + sn] = s;
}
}
while (path.length > 0) {
f = path.pop();
n = names.pop();
if (f) {
f.call(obj);
}
}
if (obj.$singleton) {
// C'est un singleton, alors on stoque son unique instance
// en mémoire pour réutilisation.
$$._.singletons[className] = obj;
}
return obj;
};
/**
* Charger la classe className.
* @param className Nom de la classe à charger en mémoire.
* @param def Si cet argument est renseigné, on ne télécharge pas la classe,
* mais on prend cette valeur comme définition.
*/
$$.loadClass = function(className, def) {
var cls = $$._.classes[className],
superclass,
s,
k,
i,
// Signal name.
sn,
// Function name.
name;
if (!cls) {
// La classe n'a pas encore été téléchargée.
if (def) {
// Si on spécifie la défintion de la classe en argument,
// on écrase l'ancienne définition potentielle.
$$._.classes[className] = null;
} else {
def = window["TFW::" + className];
}
if (!def) {
console.error("[$$.loadClass] This class does not exist: " + className);
return;
}
// Création de la classe à partir de sa définition.
cls = function () {};
superclass = $$.Object;
if (def.superclass) {
superclass = $$.loadClass(def.superclass);
}
$$.trace("This class extends " + superclass.prototype.$classname, 9);
// Héritage
cls.prototype = new superclass();
// Eviter que le constructeur de cette classe soit celui de la super classe.
cls.prototype.constructor = cls;
// Définition de l'attribut $parent pour accéder aux méthodes parentes.
cls.prototype.$parent = superclass.prototype;
// Multilangues.
cls.prototype.$lang = def.lang || {};
if (def.lang && typeof def.lang === "object") {
for (k in def.lang) {
cls.prototype.$defLang = k;
break;
}
cls.prototype.$ = $$.dico;
}
// Conserver le nom de la super classe.
cls.prototype.$superclass = superclass.prototype.$classname;
// Conserver le nom de la classe.
cls.prototype.$classname = className;
// Les noms des signals.
cls.prototype.$signals = def.signals;
// Est-ce un singleton ?
cls.prototype.$singleton = def.singleton;
// Espace réservé aux membres statiques de la classe.
s = {className: className};
$$._.statics[className] = s;
cls.prototype.$static = $$._.statics;
// Valeurs par défaut des attributs.
if (def.attributes) {
for (k in def.attributes) {
cls.prototype["_" + k] = def.attributes[k];
}
}
// Déclaration du constructeur.
cls.prototype.$init = def.init;
// Définition des méthodes liées aux signaux.
if (def.signals) {
for (i in def.signals) {
sn = def.signals[i];
$$._declareSignal(cls, sn);
}
}
// Détacher tous les signaux : entrants et sortans.
cls.prototype.unbind = function() {
console.log("TODO: unbind!");
};
// Assigner toutes les méthodes.
for (name in def.functions) {
cls.prototype[name] = def.functions[name];
}
// Mise en cache de la classe.
$$._.classes[className] = cls;
if (def.css) {
// Chargement d'un CSS.
$$.loadCss(className, "all", "style/" + className + "/style.css");
}
// Appel d'un éventuel constructeur de classe.
if (typeof def.classInit === "function") {
def.classInit($$._.statics[className]);
}
}
return cls;
};
$$._declareSignal = function(cls, sn) {
cls.prototype["fire" + sn] = function(a) {
this["$_" + sn].emit(a);
};
cls.prototype[sn] = function(a,b,c) {
this["$_" + sn].connect(a,b,c);
return this;
};
cls.prototype["unbind" + sn] = function(a,b,c) {
this["$_" + sn].disconnect(a,b,c);
return this;
};
};
$$.addScript = function(code, id) {
var s = document.createElement("script");
if (id !== undefined) {
s.setAttribute("id", id);
}
s.innerHTML = "//<!-- \n" + code + "\n// -->";
document.querySelector("head").appendChild(s);
return s;
};
$$.stopPropagation = function(e) {
var evt = e ? e : window.event;
if (evt.stopPropagation) {
evt.stopPropagation();
evt.preventDefault();
}
if (evt.cancelBubble) {
evt.cancelBubble = true;
evt.returnValue = false;
}
};
$$.addEvent = function(element, type, handler) {
var handlers;
if (type === "mouseenter") {
return $$.addEvent(element, "mouseover", $$._.mouseEnter(handler));
}
if (type === "mouseleave") {
return $$.addEvent(element, "mouseout", $$._.mouseEnter(handler));
}
if (!handler.$$guid) handler.$$guid = $$.addEvent.guid++;
if (!element.$events) element.$events = {};
handlers = element.$events[type];
if (!handlers) {
handlers = element.$events[type] = {};
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
handlers[handler.$$guid] = handler;
if (type == "DOMMouseScroll") {
// Firefox gère un événement particulier pour le MouseWheel.
element.addEventListener(type, handler, false);
} else {
element["on" + type] = $$._.handleEvent;
}
return null;
};
$$.addEvent.guid = 1;
$$.removeEvent = function(element, type, handler) {
if (element.$events && element.$events[type]) {
if (handler === undefined) {
delete element.$events[type];
} else {
delete element.$events[type][handler.$$guid];
}
}
};
/**
* Retourne true si "child" est un enfant de "parent".
*/
$$.isChildElement = function(parent, child) {
if (parent === child) {
return false;
}
while (child && child !== parent) {
child = child.parentNode;
}
return child === parent;
};
$$.App = {};
$$.Object = function() {};
/**
* Cette méthod sert à récupérer le prototype d'un ancètre particulier.
* Vous pouvez ainsi appeler une méthode d'un parent.
* Il est aussi possible d'utiliser cette méthode pour savoir si un objet
* appartient à une classe donnée. En effet, la méthode retourne null si
* la classe n'existe pas dansla chaîne d'héritage.
* @code
* var a = $$("B");
* a.foo(3.14);
* a.$super("A").foo.call(a, 3.14);
* @code
*/
$$.Object.prototype.$super = function(classname) {
var p = window[this.$classname];
while (p) {
if (p.prototype.$classname == classname) {
return p.prototype;
}
p = p.prototype.$parent;
if (!p) break;
p = window[p.$classname];
}
return null;
};
// URL d'une resource.
$$.Object.prototype.$url = function(file) {
return $$.url(file, this.$classname);
};
$$.Object.prototype.$classname = null;
$$.Object.prototype.$parent = null;
$$.Signal = function(sender, name) {
this._name = name;
this._sender = sender;
this._slots = {};
};
$$.Signal.prototype.connect = function(obj, slot, data) {
var k = this._key(obj, slot),
s = this._slots[k];
if (s === undefined) {
s = [obj, slot, []];
this._slots[k] = s;
}
s[2].push(data);
return k;
};
$$.Signal.prototype.disconnect = function(obj, slot) {
var k = (slot ? this._key(obj, slot) : obj);
delete this._slots[k];
};
$$.Signal.prototype.emit = function(arg) {
var i, s, obj, f, j, d, msg;
try {
for (i in this._slots) {
s = this._slots[i];
if (s) {
obj = s[0];
// Slot function.
f = s[1];
if (f) {
f = $$.slot(obj, f);
for (j in s[2]) {
d = s[2][j];
try {
f.call(obj, arg, d, this._sender);
} catch (e) {
throw Error("[$$.Signal.emit] Exception occured in slot "
+ obj.$classname + "." + s[1] + "\n" + e);
}
}
}
else {
f = s[0];
f(arg, s[2], this._sender);
}
}
}
} catch (e) {
msg = "[$$.Signal.emit] Exception occured in signal " + this._sender.$classname
+ "." + this._name + "\n" + e;
$$.trace(msg);
throw Error(msg);
}
};
$$.Signal.prototype._key = function(obj, slot) {
if (!slot) {
return "#" + $$.newId();
}
return obj.$id + slot;
};
/**
* Tous les paramètres de toloframework se trouvent ici.
*/
$$.params = {
// Graine utilisée pour empêcher la mise en cache des fichiers lus
// par AJAX. En effet, cette valeur sera ajoutée à la fin de l'URL
// pour éviter d'aller la chercher dans le cache.
seed: 0,
// Emplacement des fichiers de classes.
root: "tfw",
// Extension des fichiers de classes.
// Certains proxies rejettent l'extension ".js" :
// il peut alors être utile d'utiliser ".htm".
jsExt: "js.htm",
debug: 0,
compression: true,
lang: "fr"
};
/**
* Les variables internes sont stockées ici.
* Il ne faut surtout pas les modifier depuis l'extérieur.
*/
$$._ = {
// Identifiant unique pour les objets.
id: 0,
// Liste des classes téléchargées.
classes: {},
// Liste des codes dources pour les classes téléchargées.
classesSrc: {},
// Liste les classes qui sont des singletons.
// La clef est le nom complet de la classe et la valeur est
// soit une chaîne vide pour indiquer que la classe est un
// singleton non instancié, soit l'objet singleton lui-même.
singletons: {},
// Liste des CSS téléchargés.
styles: {},
// Liste des variables statiques de chaque classe.
statics: {},
// Indentation pour la trace.
traceIndent: "",
/**
* Voir http://blog.stchur.com/2007/03/15/mouseenter-and-mouseleave-events-for-firefox-and-other-non-ie-browsers/
*/
mouseEnter: function(f) {
return function(e) {
var child = e.relatedTarget;
if (this === child || $$.isChildElement(this, child)) {
return;
}
f.call(this, e);
};
},
handleEvent: function(evt) {
var handlers, i;
evt = evt || window.event;
if (this.$events) {
if (!evt.stopPropagation) {
// IE.
evt.stopPropagation = function() {
window.event.cancelBubble = true;
};
}
if (!evt.preventDefault) {
// IE.
evt.preventDefault = function() {
window.event.returnValue = false;
};
}
handlers = this.$events[evt.type];
for (i in handlers) {
this.$$handleEvent = handlers[i];
this.$$handleEvent(evt);
}
}
},
/**
* Constructeur commun à tous les objets.
* Pour créer un nouvel objet, on lui passe un dictionnaire
* d'attributs. Les noms finaux de ces attributs seront préfixés
* par un underscore.
*
* @param obj Objet à construire.
* @param attribs Dictionnaire des attributs par défaut.
*/
init: function (obj, attribs) {
var k;
if (typeof attribs == "object") {
for (k in attribs) {
if (k == null) continue;
obj["_" + k] = attribs[k];
}
}
else {
obj["_init"] = attribs;
}
}
};
/**
* Certaines classes peuvent avoir besoin de fichier annexes
* (images, fontes, css, ...). La convention est de les stoquer
* dans un répertoire portant le nom du package de la classe.
* Par exemple, la classe "sys.widget.Widget" utilise des fichiers
* provenant du sous-répertoire "sys.widget/".
* Pour connaître l'URL du fichier dont on a besoin, il suffit de
* faire appel à cette méthode.
*
* Pour simplifier les appels, tous les objets ont une méthode $url().
* @param filename Fichier resource dont on veut l'URL.
* @param classname Nom de la classe.
*/
$$.url = function(filename, classname) {
var prefix = "", items;
if (classname) {
items = classname.split('.');
items.pop();
prefix = items.join('.') + "/";
}
return $$.params.root + "/src/" + prefix + filename;
};
/**
* Lit un fichier au format JSON stoqué dans le répertoire "pub".
*
* @param filename Nom du fichier à accéder.
* @param obj Objet à notifier quand le fichier est téléchargé.
* @param slotSuccess Méthode à appeler en cas de succés.
* Elle doit accepter deux arguments :
* la donnée téléchargée et le nom du fichier.
* @param slotError Méthode à appeler en cas d'échec.
* Elle doit accepter deux arguments :
* le message d'erreur et le nom du fichier.
*/
$$.getData = function(filename, obj, slotSuccess, slotError) {
return $$.loadJSON($$.params.root + "/pub/" + filename);
};
$$.loadJSON = function(url, obj, slotSuccess, slotError) {
if (!url) {
throw new Error("[$$.loadJSON(url, obj, success, error)] Missing url!");
}
if (!obj) {
throw new Error("[$$.loadJSON(url, obj, success, error)] Missing obj!");
}
var local_filename = url,
local_obj = obj,
local_success = $$.slot(obj, slotSuccess),
local_error = (slotError ? $$.slot(obj, slotError) : null);
try {
$$.ajax(
{
url: url,
cache: false,
dataType: "json",
error: function(xhr, status, msg) {
if (local_error) {
local_error.call(local_obj, msg, local_filename);
} else {
alert("Fatal error in $$.loadJSON(" + local_filename + ", ...):\n"
+ "url = " + url + "\n"
+ msg);
}
},
success: function(data) {
// Si tout va bien, on appelle la callback.
try {
if (local_success) {
local_success.call(local_obj, data, local_filename);
}
} catch (e) {
throw Error("[$$.loadJSON] Unexpected error in success function "
+ local_obj.$classname + "." + local_success + "()\n" + e);
}
}
}
);
} catch (err) {
throw Error("[Error in $$.loadJSON] url = " + url + "\n" + err);
}
};
$$.loadText = function(url, obj, slotSuccess, slotError) {
if (!url) {
throw Error("[$$.loadText(url, obj, success, error)] Missing url!");
}
if (!obj) {
throw Error("[$$.loadText(url, obj, success, error)] Missing obj!");
}
var local_filename = url,
local_obj = obj,
local_success = $$.slot(obj, slotSuccess),
local_error = (slotError ? $$.slot(obj, slotError) : null);
try {
$$.ajax(
{
url: url,
cache: false,
dataType: "text",
error: function(xhr, status, msg) {
if (local_error) {
local_error.call(local_obj, msg, local_filename);
} else {
alert("Fatal error in $$.loadText(" + local_filename + ", ...):\n"
+ "url = " + url + "\n"
+ msg);
}
},
success: function(data) {
// Si tout va bien, on appelle la callback.
try {
if (local_success) {
local_success.call(local_obj, data, local_filename);
}
} catch (e) {
throw Error("[$$.loadText] Unexpected error in success function "
+ local_obj.$classname + "." + local_success + "()\n" + e);
}
}
}
);
} catch (err) {
throw Error("[Error in $$.loadText] url = " + url + "\n" + err);
}
};
/**
* Charge de façon asynchrone une feuille de style CSS.
* Si elle a déjà été chargée, rien ne se passe.
*/
$$.loadCss = function(name, media, path) {
var key, url, head, css;
if (media === undefined) media = "all";
if (path === undefined) path = $$.params.root + "/src/" + name;
key = name + "." + media;
if ($$._.styles[key] == null) {
url = path + "?seed=" + $$.params.seed + Math.random();
head = document.getElementsByTagName("head")[0];
css = document.createElement('link');
css.type = 'text/css';
css.rel = 'stylesheet';
css.href = url;
css.media = media;
head.appendChild(css);
$$._.styles[key] = true;
}
};
/**
* Ajoute à l'objet original tous les attributs de l'objet overrider.
* S'il y a des attributs en communs, les valeurs seront écrasées par
* overrider.
*
* @param original Objet initial.
* @param overrider Objet qui va surcharger l'original.
* @param onlyNewValues Si true, on ajoute uniquement les clefs inexistantes.
* Par défaut, il est à false.
* @return Objet original étendu par overrider.
*/
$$.extend = function(original, overrider, onlyNewValues) {
var k;
if (original === undefined) original = {};
if (overrider) {
for (k in overrider) {
if (k === undefined) continue;
if (onlyNewValues && original[k] === undefined) continue;
original[k] = overrider[k];
}
}
return original;
};
/**
* Copie un objet de type JSON, c'est-à-dire
* qui ne contient pas de fonctions ni d'objets natifs.
*/
$$.clone = function(obj) {
var r, k, i;
if ($$.isArray(obj)) {
r = [];
for (i in obj) {
r.push($$.clone(obj[i]));
}
return r;
}
if (typeof obj == "object") {
r = {};
for (k in obj) {
r[k] = $$.clone(obj[k]);
}
return r;
}
return obj;
};
/**
* Retourne un identifiant unique.
*/
$$.newId = function() {
$$._.id++;
if ($$._.id > 2000000000) {
$$._.id = 0;
}
return $$._.id;
};
/**
* Retourne un slot sur un objet donné.
* Par exemple, si l'objet toto a une méthode toString(),
* on peut l'appeler comme ceci :
* $$.slot(toto, "toString").call(toto)
*
* @param obj Objet sur lequel on veut appeler une méthode.
* @param functionName Chaîne contenant le nom de la méthode à appeler.
*/
$$.slot = function(obj, functionName) {
var cls, f;
if (!obj.$classname) {
$$.trace("============================================================");
$$.trace("ERROR: [$$.slot] Not a Toloramework object!");
$$.trace("obj=...",5);$$.trace(obj,5);
$$.trace("functionName=...",5);$$.trace(functionName,5);
$$.trace("------------------------------------------------------------");
throw Error("[$$.slot(" + functionName + ")] Not a Toloframework object!");
}
cls = $$._.classes[obj.$classname];
if (!cls) {
throw Error("[$$.slot(" + functionName + ")] Class not defined: " + obj.$classname);
}
f = cls.prototype[functionName];
if (!f) {
throw Error("[$$.slot()] Slot not found: " + obj.$classname + "." + functionName);
}
return f;
};
/**
* Appel d'une méthode au bout d'un certain nombre de millisecondes.
* Le premier argument peut être omis. Dans ce cas, il vaudra 1 milliseconde.
*
* @param delay Nombre de millisecondes avant exécution de la méthode.
* @param obj Objet sur lequel on veut appeler une méthode.
* @param slot Chaîne contenant le nom de la méthode à appeler.
* @param data [Optionel] Argument à passer à la méthode.
* @return Identifiant servant à annuler l'appel avec la fonction window.clearTimeout(id).
*/
$$.invokeLater = function(delay, obj, slot, data) {
if (typeof delay == 'object') {
data = slot;
slot = obj;
obj = delay;
delay = 1;
}
var local_obj = obj,
local_data = data,
process = $$.slot(obj, slot);
return window.setTimeout(
function() {
if (typeof local_obj == 'function') {
try {
local_obj(slot);
} catch (x) {
$$.trace("Exception in the following function:");
$$.trace(local_obj);
throw Error("[$$.invokeLater]\n" + x);
}
} else {
try {
process.call(local_obj, local_data);
} catch (x) {
throw Error("[$$.invokeLater] "
+ local_obj.$classname + "." + slot + ":\n" + x);
}
}
},
delay );
};
$$.init = function(options) {
$$.params = $$.extend($$.params, options, true);
};
if (String.prototype.trim === undefined) {
$$._trimLeft = new RegExp("^\\s+");
$$._trimRight = new RegExp("\\s+$");
String.prototype.trim = function() {
return this.toString()
.replace( $$._trimLeft, "" )
.replace( $$._trimRight, "" );
};
}
if ( window.JSON && window.JSON.parse ) {
$$.parseJSON = window.JSON.parse;
}
else {
$$.parseJSON = function(json) {
try {
if ( typeof json !== "string" || !json ) {
return null;
}
json = $$.trim(json);
return (new Function("return " + json))();
} catch (x) {
throw Error("Invalid JSON!\n" + x + "\n" + json);
}
};
}
/**
* Cette fonction provient du site http://code.google.com/p/jquery-json/
* ---------------------------------------------------------------------
* Convertit un objet en chaîne de caractères au format JSON.
*/
$$.toJSON = function(o){
var type = typeof(o),
month, day, year, hours, minutes, seconds, milli,
ret, i,
pairs, k, name,
val;
if (o === null || type == "undefined") return "null";
if (type == "number" || type == "boolean") return o + "";
if (type == "string") return $$.quoteString(o);
if (type == 'object') {
if (typeof o.toJSON == "function") return $$.toJSON( o.toJSON() );
if (o.constructor === Date) {
month = o.getUTCMonth() + 1;
if (month < 10) month = '0' + month;
day = o.getUTCDate();
if (day < 10) day = '0' + day;
year = o.getUTCFullYear();
hours = o.getUTCHours();
if (hours < 10) hours = '0' + hours;
minutes = o.getUTCMinutes();
if (minutes < 10) minutes = '0' + minutes;
seconds = o.getUTCSeconds();
if (seconds < 10) seconds = '0' + seconds;
milli = o.getUTCMilliseconds();
if (milli < 100) milli = '0' + milli;
if (milli < 10) milli = '0' + milli;
return '"' + year + '-' + month + '-' + day + 'T' +
hours + ':' + minutes + ':' + seconds +
'.' + milli + 'Z"';
}
if (o.constructor === Array) {
ret = [];
for (i = 0; i < o.length; i++) ret.push( $$.toJSON(o[i]) || "null" );
return "[" + ret.join(",") + "]";
}
pairs = [];
for (k in o) {
type = typeof k;
if (type == "number") {
name = '"' + k + '"';
}
else if (type == "string") {
name = $$.quoteString(k);
}
else {
continue; //skip non-string or number keys
}
if (typeof o[k] == "function") {
continue; //skip pairs where the value is a function.
}
val = $$.toJSON(o[k]);
pairs.push(name + ":" + val);
}
return "{" + pairs.join(", ") + "}";
}
return "null";
};
/**
* Cette fonction provient du site http://code.google.com/p/jquery-json/
* ---------------------------------------------------------------------
* Returns a string-repr of a string, escaping quotes intelligently.
* Mostly a support function for toJSON.
*
* Examples:
* >>> $$.quoteString("apple")
* "apple"
*
* >>> $$.quoteString('"Where are we going?", she asked.')
* "\"Where are we going?\", she asked."
*/
$$.quoteString = function(string)
{
var _escapeable = /["\\\x00-\x1f\x7f-\x9f]/g,
_meta = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
};
if (string.match(_escapeable))
{
return '"' + string.replace(
_escapeable, function (a)
{
var c = _meta[a];
if (typeof c === 'string') return c;
c = a.charCodeAt();
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
}) + '"';
}
return '"' + string + '"';
};
$$.xhr = function() {
var xhr=null;
try {
xhr = new XMLHttpRequest();
}
catch(e) {
try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
catch (e2) {
try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
catch (e) {
throw Error("Unable to create XMLHttpRequest needed for Ajax!");
}
}
}
return xhr;
};
$$.xhrResponse = function(response, dataType) {
if (dataType == "xml") {
return response.responseXML;
}
var text = response.responseText;
if (dataType == "json") {
return $$.parseJSON(text);
}
return text;
};
$$.ajax = function(options) {
options = $$.extend(
{
async: true,
dataType: "text",
type: "GET",
data: null,
cache: true,
success: function(data) {
$$.trace("[$$.ajax] Success!", 3);
$$.trace(data, 4);
},
error: function(data) {
$$.trace("[$$.ajax] Error!", 3);
$$.trace(data, 4);
}
},
options
);
// Astuce pour contrôler la mise en cache.
if (options.url.indexOf("?") > -1) {
options.url += "&seed=" + $$.params.seed + Math.random();
}
else {
options.url += "?" + $$.params.seed + Math.random();
}
var xhr = $$.xhr(),
params,
key, val;
if (options.async) {
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
options.success($$.xhrResponse(xhr, options.dataType), xhr.statusText, xhr);
}
else {
options.error(xhr, xhr.statusText);
}
}
};
}
xhr.open(options.type, options.url, options.async);
params = null;
if (options.type == "POST" && options.data) {
params = "";
if (typeof options.data == "object") {
for (key in options.data) {
val = options.data[key];
if (params.length > 0) {
params += "&";
}
params += key + "=" + encodeURIComponent(val);
}
}
//xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
xhr.setRequestHeader(
"Content-type",
"application/x-www-form-urlencoded");
xhr.setRequestHeader("Content-length", params.length);
xhr.setRequestHeader("Connection", "close");
}
try {
xhr.send(params);
}
catch (e) {
if (options.error) {
options.error(e.message);
}
else {
throw Error("[$$.ajax] " + e.message + "\n" + options.url);
}
}
if (!options.async) {
return $$.xhrResponse(xhr, options.dataType);
}
return null;
};
$$.service = function(p_service, p_input, p_obj, p_slot) {
var callback = null,
obj, slot, f, arg;
if (!p_obj) {
throw Error("[$$.service] Missing argument #3: object!");
}
if (!p_slot) {
callback = p_obj;
}
obj = p_obj;
slot = p_slot;
f = slot ? $$.slot(obj, slot): null;
arg = {
s: p_service,
i: $$.toJSON(p_input)
};
$$.ajax(
{
url: $$.params.root + "/server.php",
data: arg,
type: "POST",
cache: false,
dataType: "json",
success: function(data) {
// Si tout va bien, on appelle la callback.
try {
if (callback) {
callback(data);
} else {
f.call(obj, data);
}
} catch (e) {
throw Error("[$$.service] Unexpected error in callback function "
+ obj.$classname + "." + slot + "()\n" + e);
}
}
}
);
};
// Local storage
if (window["localStorage"]) {
/**
* Ce navigateur supporte le localStorage.
*/
$$.localLoad = function(key, defVal) {
var val = window.localStorage.getItem( key );
if (val === null) {
return defVal;
}
try {
val = $$.parseJSON(val);
}
catch(e) {
val = defVal;
}
return val;
};
$$.localSave = function(key, val) {
window.localStorage.setItem(key, $$.toJSON(val));
};
$$.sessionLoad = function(key, defVal) {
var val = window.sessionStorage.getItem( key );
if (val === null) {
return defVal;
}
return $$.parseJSON( val );
};
$$.sessionSave = function(key, val) {
window.sessionStorage.setItem( key, $$.toJSON( val ) );
};
} else {
/**
* Ce navigateur n supporte pas le localStorage,
* alors nous allons l'émuler avec des cookies.
*/
$$.localLoad = function(key) {
var theCookie = "" + document.cookie,
ind = theCookie.indexOf(key),
ind1;
if (ind == -1 || key == "") return null;
ind1 = theCookie.indexOf(';', ind);
if (ind1 == -1) ind1 = theCookie.length;
return $$.parseJSON(decodeURIComponent(theCookie.substring(ind + key.length + 1, ind1)));
};
$$.localSave = function(key, val) {
var nDays = 365,
today = new Date(),
expire = new Date();
expire.setTime(today.getTime() + 3600000*24*nDays);
document.cookie = key + "=" + encodeURIComponent($$.toJSON(val))
+ ";expires=" + expire.toGMTString();
};
}
/**
* Il peut-être utile d'activer les traces en cas de problème.
* On peut donc trufer le code d'appels à cette fonction $$.trace()
* et régler leur affichage au runtime à l'aide de la variable $$.params.debug.
* Cette dernière est nue valeur numérique aui indique le niveau le plus élévé
* de a trace à afficher. Ainsi, s'il vaut 2, l'appel suivant produira une trace :
* $$.trace("Coucou", 2);
* mais pas celui-ci :
* $$.trace("Plus de détails", 3);
*/
$$.trace = function(msg, level) {
if (!$$.params.debug) return;
if (!level) level = 1;
if ($$.params.debug >= level) {
var begin = "";
if (typeof msg === 'string') {
begin = msg.substring(0, 4);
if (begin == '<<< ') {
if ($$._.traceIndent.length > 2) {
$$._.traceIndent = $$._.traceIndent.substring(0, $$._.traceIndent.length - 2);
}
}
}
console.log($$._.traceIndent, msg);
if (begin == '>>> ') {
$$._.traceIndent += " ";
}
}
};
// Prévenir les erreurs sur les browsers qui ne supportent pas l'objet "console".
if (window.console === undefined) {
window.console = {};
}
var consoleMethods = ["log", "info", "warn", "error"],
i;
for (i = 0 ; i < consoleMethods.length ; i++) {
if (window.console[consoleMethods[i]] === undefined) {
window.console[consoleMethods[i]] = function(){};
}
}
/**
* Retourne un dictionnaire contenant les arguments passés dans l'URL.
* Ceci fonctionne pour des arguments du style "a=toto&b=titi".
* Si le "=" est omis, la valeur est renvoyée dans le champ "" (chaîne vide) du dictionnaire.
*/
$$.urlArgs = function() {
var f = {},
t = location.search,
i, x;
if (t.length < 2) return f;
t = t.substring(1).split('&');
for (i=0; i<t.length; i++){
x = t[i].split('=');
if (x.length == 1) {
f[""] = decodeURIComponent(x[0]);
}
else {
f[x[0]] = decodeURIComponent(x[1]);
}
}
return f;
};
$$.browser = function() {
var s = navigator.userAgent,
j, i = s.indexOf("MSIE");
if (i > -1) {
j = s.indexOf(".", i + 5);
return ["msie", parseInt(s.substring(i + 4, j))];
}
i = s.indexOf("Firefox/");
if (i > -1) {
j = s.indexOf(".", i + 8);
return ["firefox", parseInt(s.substring(i + 8, j))];
}
return [s, 0];
};
$$.isArray = function (x) {
return (typeof x == 'object' && x instanceof Array);
};
$$.htmlentities = function(t) {
var entities = {
'"': '"', // 34 22
'&': '&', // 38 26
'\'': ''', // 39 27
'<': '<', // 60 3C
'>': '>', // 62 3E
'\^': 'ˆ', // 94 5E
'‘': '‘', // 145 91
'’': '’', // 146 92
'“': '“', // 147 93
'”': '”', // 148 94
'•': '•', // 149 95
'–': '–', // 150 96
'—': '—', // 151 97
'˜': '˜', // 152 98
'™': '™', // 153 99
'š': 'š', // 154 9A
'›': '›', // 155 9B
'œ': 'œ', // 156 9C
'': 'ť', // 157 9D
'ž': 'ž', // 158 9E
'Ÿ': 'Ÿ', // 159 9F
' ': ' ', // 160 A0
'¡': '¡', // 161 A1
'¢': '¢', // 162 A2
'£': '£', // 163 A3
' ': '¤', // 164 A4
'¥': '¥', // 165 A5
'¦': '¦', // 166 A6
'§': '§', // 167 A7
'¨': '¨', // 168 A8
'©': '©', // 169 A9
'ª': 'ª', // 170 AA
'«': '«', // 171 AB
'¬': '¬', // 172 AC
'': '­', // 173 AD
'®': '®', // 174 AE
'¯': '¯', // 175 AF
'°': '°', // 176 B0
'±': '±', // 177 B1
'²': '²', // 178 B2
'³': '³', // 179 B3
'´': '´', // 180 B4
'µ': 'µ', // 181 B5
'¶': '¶', // 182 B6
'·': '·', // 183 B7
'¸': '¸', // 184 B8
'¹': '¹', // 185 B9
'º': 'º', // 186 BA
'»': '»', // 187 BB
'¼': '¼', // 188 BC
'½': '½', // 189 BD
'¾': '¾', // 190 BE
'¿': '¿', // 191 BF
'À': 'À', // 192 C0
'Á': 'Á', // 193 C1
'Â': 'Â', // 194 C2
'Ã': 'Ã', // 195 C3
'Ä': 'Ä', // 196 C4
'Å': 'Å', // 197 C5
'Æ': 'Æ', // 198 C6
'Ç': 'Ç', // 199 C7
'È': 'È', // 200 C8
'É': 'É', // 201 C9
'Ê': 'Ê', // 202 CA
'Ë': 'Ë', // 203 CB
'Ì': 'Ì', // 204 CC
'Í': 'Í', // 205 CD
'Î': 'Î', // 206 CE
'Ï': 'Ï', // 207 CF
'Ð': 'Ð', // 208 D0
'Ñ': 'Ñ', // 209 D1
'Ò': 'Ò', // 210 D2
'Ó': 'Ó', // 211 D3
'Ô': 'Ô', // 212 D4
'Õ': 'Õ', // 213 D5
'Ö': 'Ö', // 214 D6
'×': '×', // 215 D7
'Ø': 'Ø', // 216 D8
'Ù': 'Ù', // 217 D9
'Ú': 'Ú', // 218 DA
'Û': 'Û', // 219 DB
'Ü': 'Ü', // 220 DC
'Ý': 'Ý', // 221 DD
'Þ': 'Þ', // 222 DE
'ß': 'ß', // 223 DF
'à': 'à', // 224 E0
'á': 'á', // 225 E1
'â': 'â', // 226 E2
'ã': 'ã', // 227 E3
'ä': 'ä', // 228 E4
'å': 'å', // 229 E5
'æ': 'æ', // 230 E6
'ç': 'ç', // 231 E7
'è': 'è', // 232 E8
'é': 'é', // 233 E9
'ê': 'ê', // 234 EA
'ë': 'ë', // 235 EB
'ì': 'ì', // 236 EC
'í': 'í', // 237 ED
'î': 'î', // 238 EE
'ï': 'ï', // 239 EF
'ð': 'ð', // 240 F0
'ñ': 'ñ', // 241 F1
'ò': 'ò', // 242 F2
'ó': 'ó', // 243 F3
'ô': 'ô', // 244 F4
'õ': 'õ', // 245 F5
'ö': 'ö', // 246 F6
'÷': '÷', // 247 F7
'ø': 'ø', // 248 F8
'ù': 'ù', // 249 F9
'ú': 'ú', // 250 FA
'û': 'û', // 251 FB
'ü': 'ü', // 252 FC
'ý': 'ý', // 253 FD
'þ': 'þ', // 254 FE
'ÿ': 'ÿ' // 255 FF
},
newText = "",
idx, lastIdx = 0,
r;
for (idx = 0 ; idx < t.length ; idx++) {
r = entities[t.charAt(idx)];
if (r !== undefined) {
newText += t.substr(lastIdx, idx) + r;
lastIdx = idx + 1;
}
}
newText += t.substr(lastIdx);
return newText;
};
/**
* Retourne la taille d'un dictionnaire.
*/
$$.size = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
/**
* Retourner la valeur numérique entière de x.
* Si min et/ou max sont définis, ce sont des contraintes à appliquer à x.
*/
$$.intVal = function(x, min, max) {
x = parseInt(x);
if (isNaN(x)) return 0;
if (min !== undefined && x < min) x = min;
if (max !== undefined && x > max) x = max;
return x;
};
/**
* Retourner la valeur numérique flottante de x.
* Si min et/ou max sont définis, ce sont des contraintes à appliquer à x.
*/
$$.floatVal = function(x, min, max) {
x = parseFloat(x);
if (isNaN(x)) return 0;
if (min !== undefined && x < min) x = min;
if (max !== undefined && x > max) x = max;
return x;
};
/**
* Multilangue.
*/
$$.lang = function(lang) {
if (lang === undefined) {
return $$.localLoad("tfw.Language", $$.params.lang)
|| navigator.language || navigator.browserLanguage || "fr";
}
$$.params.lang = lang;
$$.localSave("tfw.Language", lang);
};
$$.dico = function(k) {
var dic = this.$lang[$$.lang()],
txt, newTxt, i, c, lastIdx, pos;
if (!dic) {
dic = this.$lang[this.$defLang];
}
if (!dic) {
return "[[" + k + "]]";
}
txt = dic[k];
if (!txt) {
return "[[" + k + "]]";
}
if (arguments.length > 1) {
newTxt = "";
lastIdx = 0;
for (i = 0 ; i < txt.length ; i++) {
c = txt.charAt(i);
if (c === '$') {
newTxt += txt.substring(lastIdx, i);
i++;
pos = txt.charCodeAt(i) - 48;
if (pos < 1 || pos >= arguments.length) {
newTxt += "$" + txt.charAt(i);
} else {
newTxt += arguments[pos];
}
lastIdx = i + 1;
} else if (c === '\\') {
newTxt += txt.substring(lastIdx, i);
i++;
newTxt += txt.charAt(i);
lastIdx = i + 1;
}
}
newTxt += txt.substr(lastIdx);
txt = newTxt;
}
return txt;
};