toloframework
Version:
Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.
939 lines (869 loc) • 27.4 kB
JavaScript
'use strict';
/**
* Widgets inherit this class.
*/
function Widget(options) {
this.__data = {};
try {
var e;
if (typeof options === 'undefined') options = {};
if (typeof options.innerHTML !== 'undefined' && typeof options.childNodes !== 'undefined') {
// On passe directement un élément.
options = {element: options};
}
if (typeof options.tag === 'undefined') options.tag = "div";
if (options.element) {
this.element(options.element);
} else if (typeof options.id !== 'undefined') {
e = window.document.getElementById(options.id);
if (!e) {
throw Error("Can't find element with id: \"" + options.id + "\"!");
}
this.element(e);
} else {
this.element(window.document.createElement(options.tag));
this.addClass("wdg", "custom");
}
}
catch (ex) {
console.error("[widget] ", ex);
console.error("[Widget] ", JSON.stringify(options));
throw Error(ex);
}
}
Widget.prototype = {
/**
* Accessor for attribute element
* @return element
*/
element: function(v) {
if (v === undefined) return this._element;
if (typeof v === 'string') {
v = window.document.querySelector(v);
}
this._element = v;
return this;
},
data: function(k, v) {
if (typeof v === 'undefined') {
return this.__data[k];
}
this.__data[k] = v;
return this;
},
/**
* Remove this element from its parent.
* @memberof wdg
*/
detach: function() {
var e = this._element;
if (e) {
var p = e.parentNode;
if (p) {
p.removeChild(e);
}
}
return this;
},
/**
* @param name
* @memberof wdg
*/
addEvent: function(name, slot, sender) {
if (typeof slot === 'string') {
var that = this, key = slot;
if (typeof sender === 'undefined') sender = this;
slot = function(x) {
var f = sender[key];
if (typeof f === 'function') {
f.call(sender, x);
} else {
throw Error("\"" + slot + "\" is not a function of: " + sender);
}
};
}
var e = this.element();
if (name == 'tap') {
e.addEventListener(
"mousedown",
function(evt) {
evt.preventDefault();
evt.stopPropagation();
},
false
);
e.addEventListener(
"mouseup",
function(evt) {
evt.preventDefault();
evt.stopPropagation();
slot(evt);
},
false
);
e.addEventListener("touchend", slot, false);
} else {
e.addEventListener(name, slot, false);
}
return this;
},
/**
* Retire un attribut à l'élément sous-jacent.
* @example
* this.removeAttr("selected");
* @memberof wdg
*/
removeAttr: function() {
if (this._element) {
var i, arg;
for (i = 0 ; i < arguments.length ; i++) {
arg = arguments[i];
this._element.removeAttribute(arg);
}
}
return this;
},
/**
* Teste l'existence d'un attribut dans l'élément sous-jacent.
* @memberof wdg
*/
hasAttribute: function(key) {
if (!this._element) return false;
return this._element.hasAttribute(key);
},
/**
* Lit ou affecte la valeur d'un attribut de l'élément sous-jacent.
* @example
* // Le pseudo attribut '$' sert à insérer du texte.
* var div = new Widget();
* div.attr({$: 'Hello world!', title: 'Typical first sentence...'});
* @param key Nom de l'attribut ou dictionnaire des attributs.
* @param val [Facultatif] Valeur de l'attribut. Si elle est omise, la méthode retourne la valeur actuelle.
* @param ns [Facultatif] Namespace éventuel.
* @memberof wdg
*/
attr: function(key, val, ns) {
var k;
if (!this._element || !this._element.getAttribute) return null;
if (typeof key == "string") {
if (val !== undefined) {
if (key == "class") {
this._element.className = val;
return this;
}
if (ns && this._element.setAttributeNS) {
this._element.setAttributeNS(ns, key, val);
}
else {
this._element.setAttribute(key, val);
}
return this;
}
return this._element.getAttribute(key);
}
if (typeof key == "object") {
for (k in key) {
if (k == "class") {
this._element.className = key[k];
} else if (k == "$") {
this.text(key[k]);
} else {
this._element.setAttribute(k, key[k]);
}
}
}
return this;
},
/**
* Permet de changer le style de l'élément sous-jacent.
* @example
* this.css("color", "red");
* alert(this.css("display"));
* this.css(
* {
* margin: "0px",
* padding: "3px",
* border: "1px solid black"
* }
* );
* @param key Nom du champ de style ou dictionnaire clefs/valeurs.
* @param val Valeur du champ key. S'il est omis et que "key"
* est de type string, alors la méthode retourne la valeur
* actuelle.
* @memberof wdg
*/
css: function(key, val) {
var k, e = this._element;
if (!e) return null;
if (typeof e.style !== 'object') {
console.error("[wdg.css] This element does not support styles!", e);
e.style = {};
}
if (typeof key == 'string') {
if (val) {
e.style[key] = val;
return this;
}
return e.style[key];
}
if (typeof key == 'object') {
for (k in key) {
try {
e.style[k] = key[k];
} catch (x) {
console.error("[wdg.css] Bad CSS attribute " + k + ": " + key[k]);
}
}
}
return this;
},
insertAfter: function(target) {
if (typeof target.element === 'function') {
target = target.element();
}
target.parentNode.insertBefore(this.element(), target.nextSibling);
return this;
},
insertBefore: function(target) {
if (typeof target.element === 'function') {
target = target.element();
}
target.parentNode.insertBefore(this.element(), target);
return this;
},
/**
* Append children to this widget.
*/
append: function() {
var i, arg;
for (i = 0 ; i < arguments.length ; i++) {
arg = arguments[i];
if (typeof arg === 'number') arg = "" + arg;
if (typeof arg === 'undefined' || typeof arg === 'null'
|| (typeof arg !== 'object' && typeof arg !== 'string')) {
console.error("[Widget.append] Argument #" + i + " is invalid!", arguments);
console.error("[Widget.append] Is type is: " + (typeof arg));
continue;
};
if (typeof arg === 'string') {
if (arg.length < 1) arg = " ";
arg = window.document.createTextNode(arg);
if (!arg) {
console.error(
"[Widget.append] Unable to create a text node with this text: ", arg
);
console.error("[wdg] arguments=...", arguments);
throw Error(
"[Widget.append] Unable to create a text node with this text: "
+ JSON.stringify(arg)
);
}
}
if (Array.isArray(arg)) {
arg.forEach(
function(item) {
this.append(item);
}, this
);
} else {
var e = arg;
if( typeof e.element === 'function' ) e = e.element();
else if( e.element ) e = e.element;
this._element.appendChild(e);
}
}
return this;
},
/**
* Append this widget to a parent.
* @param parent
* @memberof wdg
*/
appendTo: function(parent) {
if (!parent) return this;
if (typeof parent.append === 'function') {
parent.append(this);
} else if (typeof parent.appendChild === 'function') {
parent.appendChild(this._element);
this.onAppend();
}
return this;
},
/**
* Append this widget to BODY.
* @memberof wdg
*/
appendToBody: function() {
window.document.body.appendChild(this._element);
return this;
},
hasClass: function() {
var e = this._element.classList;
var i, arg;
for (i = 0 ; i < arguments.length ; i++) {
arg = arguments[i];
if (!e.contains(arg)) return false;
}
return true;
},
/**
* Add CSS classe(s) to this widget.
* @memberof wdg
*/
addClass: function() {
var e = this._element.classList;
var i, arg;
for (i = 0 ; i < arguments.length ; i++) {
arg = arguments[i];
if (typeof arg === 'string') {
arg = arg.trim();
if (arg.length > 0) e.add(arg);
} else {
console.error("[wdg.addClass] Arguments with bad type!", arguments);
}
}
return this;
},
/**
* Remove CSS classe(s) to this widget.
* @memberof wdg
*/
removeClass: function() {
var e = this._element.classList;
var i, arg;
for (i = 0 ; i < arguments.length ; i++) {
arg = arguments[i];
e.remove(arg);
}
return this;
},
/**
* @description
* Toggle CSS classe(s) to this widget.
* @memberof wdg
*/
toggleClass: function() {
var e = this._element.classList;
var i, arg;
for (i = 0 ; i < arguments.length ; i++) {
arg = arguments[i];
e.toggle(arg);
}
return this;
},
/**
* Remove all children of this widget.
* Any argument passed will be appended to this widget.
* @memberof wdg
*/
clear: function() {
// (!) On préfère retirer les éléments un par un du DOM plutôt que d'utiliser simplement
// this.html("").
// En effet, le code simplifié a des conséquences inattendues dans IE9 et IE10 au moins.
// Le bug des markers qui disparaissaients sur les cartes de Trail-Passion 4 a été corrigé
// avec cette modification.
var e = this.element();
while(e.firstChild){
e.removeChild(e.firstChild);
}
var i, arg;
for (i = 0 ; i < arguments.length ; i++) {
arg = arguments[i];
this.append(arg);
}
return this;
},
/**
* 1) Sans argument, cette fonction retourne le texte contenu
* dans ce widget.
* 2) Avec un texte en argument, cette fonction retire tous
* les enfants de ce widget et les remplace par un noeud
* texte.
* 3) Avec un élément du DOM en argument, cette fonction
* retourne le texte contenu par ce dernier.
* @memberof tfw.Widget
*/
text: function(arg) {
var e, text, i, child;
if (typeof arg == 'string' || typeof arg == 'number') {
arg = '' + arg;
if (arg.substr(0, 6) == '<html>') return this.html( arg.substr( 6 ) );
this.clear();
this._element.appendChild(window.document.createTextNode(arg));
return this;
} else {
// On retourne le contenu textuel de ce widget.
e = this._element;
if (arg !== undefined) {
// On peut passer un élément du DOM pour en extraire son contenu textuel.
e = arg;
}
text = "";
if (e.childNodes) {
for (i=0 ; i<e.childNodes.length ; i++) {
child = e.childNodes[i];
if (child.nodeType == 3) {
if (child.nodeValue) {
text += child.nodeValue;
}
} else {
text += this.text( child );
}
}
}
return text;
}
},
/**
*
* @param html
* @memberof wdg
*/
html: function(html) {
if (typeof html === 'undefined') return this._element.innerHTML;
if (this._element) this._element.innerHTML = html;
return this;
},
/**
* If applicable, give focus to this element.
* @memberof wdg
*/
focus: function() {
var e = this._element;
if (!e) return this;
if (typeof e.focus === 'function') {
e.focus();
}
return this;
},
/**
* Returns the bounds of the underlying element.
* @memberof wdg
*/
rect: function() {
var e = this._element;
if (!e) return null;
if (typeof e.getBoundingClientRect !== 'function') {
console.error("[wdg.rect] This element has non `getBoundingClientRect` function:", e);
}
var r = e.getBoundingClientRect();
if( typeof r.width === 'undefined' ) r.width = r.right - r.left;
if( typeof r.height === 'undefined' ) r.height = r.bottom - r.top;
return r;
},
/**
*
* @param
* @memberof wdg
*/
Tap: function(slot, sender) {
if (typeof slot === 'undefined') return this._Tap;
var that = this;
if (typeof sender === 'undefined') sender = that;
if (typeof slot === 'string') slot = sender[slot];
if (!this._Tap) {
this.activatePointerEvents();
}
this._Tap = [slot, sender];
return this;
}
};
/**
* @return void
*/
Widget.prototype.activatePointerEvents = function() {
if (this._pointerEvents) return this;
this._pointerEvents = {start: 0};
/*
interact(this.element()).on("tap", function(evt) {
var slot = that._Tap;
if (slot) {
slot[0].call(slot[1], {x: evt.x, y: evt.y});
}
});
return this;
*/
var pe = this._pointerEvents;
var that = this;
this.addEvent(
"touchstart",
function(evt) {
evt.preventDefault();
evt.stopPropagation();
pe.touch = 1;
pe.start = Date.now();
}
);
this.addEvent(
"touchend",
function(evt) {
evt.preventDefault();
evt.stopPropagation();
var tap = that._Tap;
if (!tap) return;
pe.touch = 0;
var delta = Date.now() - pe.start;
if (delta < 900) {
tap[0].call(tap[1], evt);
}
}
);
this.addEvent(
"mousedown",
function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (pe.touch) return;
pe.start = Date.now();
}
);
this.addEvent(
"mouseup",
function(evt) {
evt.preventDefault();
evt.stopPropagation();
var tap = that._Tap;
if (!tap) return;
var delta = Date.now() - pe.start;
if (delta < 900) {
tap[0].call(tap[1], evt);
}
}
);
return this;
};
/**
* @return void
*/
Widget.prototype.div = function() {
var div = new Widget();
for (var i = 0 ; i < arguments.length ; i++) {
div.addClass(arguments[i]);
}
return div;
};
Widget.prototype.tag = function(tag) {
if (typeof tag === 'undefined') tag = 'div';
var div = new Widget({tag: tag});
for (var i = 1 ; i < arguments.length ; i++) {
div.addClass(arguments[i]);
}
return div;
};
/**
* @return void
*/
Widget.prototype.isInDOM = function() {
return Widget.isInDOM(this.element());
};
/**
* Fonction à surcharger si on veut réagir lors de l'insertion dans le
* DOM.
*/
Widget.prototype.onAppend = function() {};
Widget.create = function(args) {
return new Widget(args);
};
Widget.find = function(query) {
return new Widget({element: window.document.querySelector(query)});
};
/**
* Create a SVG élément with attributes.
*/
Widget.svg = function(tag, attribs) {
var namespace = "http://www.w3.org/2000/svg";
if (typeof tag === 'object') {
attribs = tag;
tag = "svg";
}
if (typeof tag !== 'string') tag = 'svg';
var e = window.document.createElementNS(namespace, tag);
var w = new Widget({element: e});
if (typeof attribs === 'undefined') attribs = {};
if (tag == 'svg') {
if (typeof attribs.version === 'undefined') attribs.version = "1.1";
if (typeof attribs['xmlns:svg'] === 'undefined') {
attribs['xmlns:svg'] = 'http://www.w3.org/2000/svg';
}
if (typeof attribs['xmlns'] === 'undefined') {
attribs['xmlns'] = 'http://www.w3.org/2000/svg';
}
if (typeof attribs['xmlns:xlink'] === 'undefined') {
attribs['xmlns:xlink'] = 'http://www.w3.org/1999/xlink';
}
if (typeof attribs.viewBox === 'undefined'
&& typeof attribs.width === 'number'
&& typeof attribs.height === 'number')
{
attribs.viewBox = "0 0 " + attribs.width + " " + attribs.height;
}
}
if (typeof attribs === 'object') {
w.attr(attribs);
}
return w;
};
/**
* Tester si le widget ou élément est actuellement attaché au DOM.
*/
Widget.isInDOM = function(e) {
if (!e) return false;
if (typeof e.element === 'function') {
e = e.element();
}
if (e === window.document) return true;
return Widget.isInDOM(e.parentNode);
};
/**
* Create a `span` with a text or an HTML content.
* If `txt` starts with `<html>`, we set an HTML content.
*/
Widget.fromTextOrHtml = function(txt) {
var e = Widget.span();
if (txt.substr(0, 6) == '<html>') {
e.html(txt.substr(6));
} else {
e.text(txt);
}
return e;
};
/**
* Create a DIV and apply all arguments as classes to it.
*/
Widget.div = function() {
var div = new Widget({tag: "div"});
for (var i = 0 ; i < arguments.length ; i++) {
div.addClass(arguments[i]);
}
return div;
};
/**
* Create a SPAN and apply all arguments as classes to it.
*/
Widget.span = function() {
var div = new Widget({tag: "span"});
for (var i = 0 ; i < arguments.length ; i++) {
div.addClass(arguments[i]);
}
return div;
};
Widget.tag = function(tag) {
if (typeof tag === 'undefined') tag = 'div';
var div = new Widget({tag: tag});
for (var i = 1 ; i < arguments.length ; i++) {
div.addClass(arguments[i]);
}
return div;
};
Widget.id = function(id) {
return new Widget({element: window.document.getElementById(id)});
};
/**
* Widget defining the `document.body` element.
*/
Widget.body = new Widget(window.document.body);
module.exports = Widget;
/*
* classList.js: Cross-browser full element.classList implementation.
* 2014-07-23
*
* http://purl.eligrey.com/github/classList.js/blob/master/classList.js
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
// Full polyfill for browsers with no classList support
if (!("classList" in window.document.createElement("_"))) {
(function () {
"use strict";
if (!('Element' in window)) return;
var classListProp = "classList";
var protoProp = "prototype";
var elemCtrProto = window.Element[protoProp];
var objCtr = Object;
var strTrim = String.prototype.trim || function () {
var rx = new RegExp("^\\s+|\\s+$", "g");
return this.replace(rx, '');
};
var arrIndexOf = Array[protoProp].indexOf || function (item) {
var
i = 0
, len = this.length
;
for (; i < len; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
}
// Vendors: please allow content code to instantiate DOMExceptions
, DOMEx = function (type, message) {
this.name = type;
this.code = DOMException[type];
this.message = message;
}
, checkTokenAndGetIndex = function (classList, token) {
if (token === "") {
throw new DOMEx(
"SYNTAX_ERR"
, "An invalid or illegal string was specified"
);
}
if ((new RegExp("\\s")).test(token)) {
throw new DOMEx(
"INVALID_CHARACTER_ERR"
, "String contains an invalid character"
);
}
return arrIndexOf.call(classList, token);
}
, ClassList = function (elem) {
var
trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
, classes = trimmedClasses ? trimmedClasses.split(new RegExp("\\s+")) : []
, i = 0
, len = classes.length
;
for (; i < len; i++) {
this.push(classes[i]);
}
this._updateClassName = function () {
elem.setAttribute("class", this.toString());
};
}
, classListProto = ClassList[protoProp] = []
, classListGetter = function () {
return new ClassList(this);
}
;
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function (i) {
return this[i] || null;
};
classListProto.contains = function (token) {
token += "";
return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function () {
var
tokens = arguments
, i = 0
, l = tokens.length
, token
, updated = false
;
do {
token = tokens[i] + "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token);
updated = true;
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.remove = function () {
var
tokens = arguments
, i = 0
, l = tokens.length
, token
, updated = false
, index
;
do {
token = tokens[i] + "";
index = checkTokenAndGetIndex(this, token);
while (index !== -1) {
this.splice(index, 1);
updated = true;
index = checkTokenAndGetIndex(this, token);
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.toggle = function (token, force) {
token += "";
var
result = this.contains(token)
, method = result ?
force !== true && "remove"
:
force !== false && "add"
;
if (method) {
this[method](token);
}
if (force === true || force === false) {
return force;
} else {
return !result;
}
};
classListProto.toString = function () {
return this.join(" ");
};
if (objCtr.defineProperty) {
var classListPropDesc = {
get: classListGetter, enumerable: true, configurable: true
};
try {
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
} catch (ex) { // IE 8 doesn't support enumerable:true
if (ex.number === -0x7FF5EC54) {
classListPropDesc.enumerable = false;
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
}
}
} else if (objCtr[protoProp].__defineGetter__) {
elemCtrProto.__defineGetter__(classListProp, classListGetter);
}
}());
} else {
// There is full or partial native classList support, so just check if we need
// to normalize the add/remove and toggle APIs.
(function () {
"use strict";
var testElement = window.document.createElement("_");
testElement.classList.add("c1", "c2");
// Polyfill for IE 10/11 and Firefox <26, where classList.add and
// classList.remove exist but support only one argument at a time.
if (!testElement.classList.contains("c2")) {
var createMethod = function(method) {
var original = DOMTokenList.prototype[method];
DOMTokenList.prototype[method] = function(token) {
var i, len = arguments.length;
for (i = 0; i < len; i++) {
token = arguments[i];
original.call(this, token);
}
};
};
createMethod('add');
createMethod('remove');
}
testElement.classList.toggle("c3", false);
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
// support the second argument.
if (testElement.classList.contains("c3")) {
var _toggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function(token, force) {
if (1 in arguments && !this.contains(token) === !force) {
return force;
} else {
return _toggle.call(this, token);
}
};
}
testElement = null;
}());
}