NxTIC/js/lib/form-builder.js

453 lines
14 KiB
JavaScript
Raw Normal View History

/* CONSTRUCTEUR -> INITIALISE UNE L'INSTANCE
*
* @parent_element<Element> Element qui contiendra le formulaire
* @form_object<Object> Objet définissant le formulaire
*
*/
var FormBuilder = function(parent_element, form_object){
/* (1) On définit le parent */
this.parent_element = parent_element;
/* (2) On définit le formulaire (sous forme de description formelle) */
this.form_object = form_object;
};
/************************************************************************/
/* _ _____ _____ ____ ___ ____ _ _ _____ _____ ____ */
/* / \|_ _|_ _| _ \|_ _| __ )| | | |_ _| ____/ ___| */
/* / _ \ | | | | | |_) || || _ \| | | | | | | _| \___ \ */
/* / ___ \| | | | | _ < | || |_) | |_| | | | | |___ ___) | */
/* /_/ \_\_| |_| |_| \_\___|____/ \___/ |_| |_____|____/ */
/************************************************************************/
/* DEFINITION DES ATTRIBUTS
*
*/
FormBuilder.prototype = {
form_object: this.form_object, // objet permettant la construction du formulaire
defs_object: {}, // objet des définitions des éléments
parent_element: this.parent_element, // element qui contiendra le formulaire
built_form: null // Element correspondant au formulaire construit
};
/* DEFINITION DES ATTRIBUTS STATIQUES
*
*/
FormBuilder.regex = {
regex_input_key: /^\/\^(.+)\$\/$/, // Regex associée à une "regex" incluse dans une clé
regex_ouput_value: /\{(\$[1-9])\}/, // Regex associée à la valeur du dernier match de "regex"
primitive_ouput_value: /\{([a-z-]+)\}/g, // Regex associée à une variable primitif à remplacer
primitive_input_key: /^\$([a-z-]+)$/, // Regex associée à la clé d'une variable primitive
array_output_set: /^\{\{([a-z-]+)\}\}$/, // Regex associée à un tableau à remplacer
array_output_value: /\{([a-z-]+)\.([a-z-]+)\}/g, // Regex associée à une valeur de tableau à remplacer (primitif)
array_input_key: /^\$\$([a-z-]+)$/ // Regex associée à la clé d'un tableau
};
2016-09-17 14:04:03 +00:00
FormBuilder.spread_attr = [ // Liste des attributs diffusant le scope
'children', // diffuse aux éléments
'next_nodes', // diffuse aux éléments
'prev_nodes', // diffuse aux éléments
'attributes' // aux valeurs d'attributs
];
FormBuilder.allowed_attr = [ // Liste des attributs fixes autorisés
'node', /* nom du modèle à utiliser (dans @defs_object) */
'node_type', /* nom réel de l'élément */
'next_nodes', /* tableau des enfants à ajouter à la suite de l'élément */
'prev_nodes', /* tableau des enfants à ajouter à avant l'élément */
'attributes', /* tableau des attributs à passer au niveau inférieur */
'children', /* tableau des enfants à passer au niveau inférieur */
'text', /* texte à insérer (innerHTML), REMPLACE 'children' */
'repeat' /* nombre/tableau déterminant qu'il faut répéter @n fois ou en fonction de la taille du tableau */
];
FormBuilder.ignore_rec = [ // Attributs à ne pas lancer récursivement (car inutile)
'parent', /* ne pas lancer récursivement sur parent car sinon récursion infinie */
'scope' /* ne pas lancer sur scope, car fait partie des méta-données */
],
/************************************************************/
/* __ __ _____ _____ _ _ ___ ____ ____ */
/* | \/ | ____|_ _| | | |/ _ \| _ \/ ___| */
/* | |\/| | _| | | | |_| | | | | | | \___ \ */
/* | | | | |___ | | | _ | |_| | |_| |___) | */
/* |_| |_|_____| |_| |_| |_|\___/|____/|____/ */
/************************************************************/
/* AJOUTE UNE DEFINITION
*
* @def_object<Object> Objet de définition
*
*/
FormBuilder.prototype.add_definition = function(def_object){
/* [1] On ajoute la définition
=========================================================*/
for( var key in def_object )
this.defs_object[key] = def_object[key];
};
/* ON CONSTRUIT L'OBJET (avant la construction)
*
* @scope<Object> for( key in object ){
[OPT] Objet contenant les variables à passer
*
*/
FormBuilder.prototype.build = function(scope){
/* [0] Gestion du paramètres
=========================================================*/
scope = (scope == null) ? {} : scope;
/* [1] On formatte l'objet
=========================================================*/
// - Ajout des références children[n].parent vers élément parent
// - Ajout des références element.node_link vers définition (en fonction de element.node)
this.built_form = FormBuilder.formatFormObject(this.form_object, this.defs_object);
/* [2] On remplace les valeurs
=========================================================*/
this.built_form = FormBuilder.replaceStatements(this.form_object, scope);
};
/************************************************/
/* ____ _____ _ _____ ___ ____ */
/* / ___|_ _|/ \|_ _|_ _/ ___| */
/* \___ \ | | / _ \ | | | | | */
/* ___) || |/ ___ \| | | | |___ */
/* |____/ |_/_/ \_\_| |___\____| */
/************************************************/
/* FORMATTE L'OBJET DE DESCRIPTION DU FORMULAIRE
*
* @object<Object> Objet dans lequel remplacer les valeurs
* @defs<Object> Objet de définition des nodes
*
*
* @desc
* 1. Ajoute l'attribut PARENT aux enfants pour qu'ils puisse accéder à l'élément parent
* 2. Ajout l'attribut NODE_LINK aux éléments afin de lier leur définition en fonction de la valeur de 'node'
*
*/
FormBuilder.formatFormObject = function(object, defs){
/* [1] Si a l'attribut 'node' <string>
=========================================================*/
if( object.hasOwnProperty('node') && typeof object.node == 'string' ){
/* On cherche une définition */
var found_def = FormBuilder.fetchNodeDefinition(object.node, defs);
// Si on trouve
if( found_def !== null ){
// 1. On inclus la définition dans l'attribut 'node_link'
object.node_link = found_def.def;
// 2. On ajoute les matches dans l'attribut 'scope'
if( found_def.hasOwnProperty('scope') )
object.scope = found_def.scope;
}
}
/* [2] Pour chaque enfant, s'il y a
=========================================================*/
if( object.hasOwnProperty('children') ){
for( var child in object.children ){
/* On ajoute le parent + on lance récursivement */
object.children[child].parent = object;
FormBuilder.formatFormObject(object.children[child], defs);
}
}
return object;
};
/* CHERCHE UNE DEFINITION CORRESPONDANT A L'ATTRIBUT 'node'
*
* @node<String> Nom du node pour lequel trouver la définition
* @defs<Object> Objet de définition
*
* @return node_definition<Object> Retourne la définition pour le 'node' en question
* ou NULL si on ne trouve rien
*
*/
FormBuilder.fetchNodeDefinition = function(node, defs){
/* [0] Initialisation
=========================================================*/
var m = null, key, i, regex;
r = FormBuilder.regex.regex_input_key;
/* [1] Si la définition existe, on la retourne
=========================================================*/
if( defs != null && defs.hasOwnProperty(node) ){
return { def: defs.node, scope: null };
/* [2] Sinon, on cherche une REGEX
=========================================================*/
}else{
// Pour chaque définition
for( key in defs ){
/* (2.1) On regarde s'il n'y a pas de REGEX dans la clé
---------------------------------------------------------*/
if( r.test(key) ){
// On construit la regex
regex = new RegExp( key.slice(1, -1) );
/* (1) Si la regex match */
if( (m=regex.test(node)) ){
/* (2) On récupère les 'match' */
matches = {};
for( i = 1 ; i < RegExp.length && i < 10 ; i++ )
matches['$'+i] = RegExp['$'+i];
/* (3) On renvoie le lien + le scope */
return { def: defs[key], scope: matches };
}
}
}
}
// Si on a rien trouvé, on retourne NULL
return null;
};
/* REMPLACE RECURSIVEMENT LES VALEURS DE @OBJECT AVEC LE @SCOPE -> RECURSIF
*
* @object<Object> Objet dans lequel remplacer les valeurs
* @scope<Object> Ensemble des variables permettant le remplacement
*
* @return replaced<Object> Objet avec les remplacements effectués
*
*/
FormBuilder.replaceStatements = function(object, scope){
/* [0] Initialisation
=========================================================*/
/* (1) Paramètres */
object = (object instanceof Object) ? object : {};
scope = (scope instanceof Object) ? scope : {};
/* (2) Variables */
var key, r, tmpr, m;
var next_scope = scope;
2016-09-17 14:04:03 +00:00
/* (3) On récupère le scope s'il est dans l'attribut 'scope' */
if( object.hasOwnProperty('scope') && object.scope instanceof Object )
for( key in object.scope )
next_scope[key] = object.scope[key];
/* [1] On remplace les valeurs
=========================================================*/
for( key in object ){
/* [1.1] Si c'est une string, on regarde s'il faut remplacer
=========================================================*/
if( FormBuilder.ignore_rec.indexOf(key) < 0 && typeof object[key] == 'string' ){
/* (2.1) On cherche tous les TABLEAUX à remplacer
---------------------------------------------------------*/
/* (1.1) On récupère les remplacements de TABLEAUX */
m = null, r = FormBuilder.regex.array_output_set;
var m_arr = [];
/* (1.2) Si ça match */
if( m = r.exec(object[key]) )
m_arr.push( m[1] );
/* (1.3) Pour chaque match */
for( m in m_arr ){
// {1} Si la var n'est pas dans le scope //
if( !scope.hasOwnProperty(m_arr[m]) )
scope[m_arr[m]] = []; // on met un tableau vide
// {2} on attribue le tableau //
object[key] = scope[m_arr[m]];
2016-09-17 14:04:03 +00:00
console.log('arr', m_arr[m], scope[m_arr[m]]);
}
/* (1.4) Si on a trouvé qqch, on passe à la clé suivante */
if( m_arr.length > 0 )
continue;
/* (2.2) On cherche toutes les variables PRIMITIVES à remplacer
---------------------------------------------------------*/
/* (1.1) On récupère les remplacements PRIMITIFS */
m = null, r = FormBuilder.regex.primitive_ouput_value;
var m_pri = [];
/* (1.2) Tant que ça match */
while( (m=r.exec(object[key])) !== null )
m_pri.push( m[1] );
/* (1.3) Pour chaque match */
for( m in m_pri ){
// {1} Si la var n'est pas dans le scope //
if( !scope.hasOwnProperty(m_pri[m]) )
scope[m_pri[m]] = ''; // on met une chaine vide
// {2} on remplace toutes les occurences par la valeur //
tmpr = new RegExp( "\{"+m_pri[m]+"\}", 'g' );
object[key] = object[key].replace(tmpr, scope[m_pri[m]]);
2016-09-17 06:29:59 +00:00
2016-09-17 14:04:03 +00:00
console.log('pri', m_pri[m], scope[m_pri[m]]);
}
2016-09-17 06:29:59 +00:00
/* (1.4) Si on a trouvé qqch, on passe à la clé suivante */
if( m_pri.length > 0 )
continue;
/* (2.3) On cherche toutes les variables REGEX à remplacer
---------------------------------------------------------*/
/* (1.1) On récupère les remplacements REGEX */
m = null, r = FormBuilder.regex.regex_ouput_value,
lasti = -1;
var m_reg = [];
/* (1.2) Tant que ça match */
var max = 10;
while( (m=r.exec(object[key])) !== null && max > 0 ){
// si on boucle, on sort
if( lasti >= r.lastIndex ) break;
lasti = r.lastIndex;
m_reg.push( m[1] );
}
/* (1.3) Pour chaque match */
for( m in m_reg ){
// {1} Si la var n'est pas dans le scope //
if( !scope.hasOwnProperty(m_reg[m]) )
scope[m_reg[m]] = ''; // on met une chaine vide
// {2} on remplace toutes les occurences par la valeur //
tmpr = new RegExp( "\{\\$"+m_reg[m][1]+"\}", 'g' );
object[key] = object[key].replace(tmpr, scope[m_reg[m]]);
2016-09-17 14:04:03 +00:00
console.log('reg', m_reg[m], scope);
}
/* (1.4) Si on a trouvé qqch, on passe à la clé suivante */
if( m_reg.length > 0 )
continue;
/* (2.4) On cherche toutes les valeurs de TABLEAUX à remplacer
---------------------------------------------------------*/
/* (1.1) On récupère les remplacements de valeurs de TABLEAUX */
m = null, r = FormBuilder.regex.array_output_value;
var m_aval = [];
/* (1.2) Tant que ça match */
while( (m=r.exec(object[key])) !== null )
m_aval.push( m[1] );
/* (1.3) Pour chaque match */
for( m in m_aval ){ //TODO: implémenter l'ajout des items d'un tableau au scope lors d'un "repeat"
// {1} Si la var n'est pas dans le scope //
if( !scope.hasOwnProperty(m_aval[m]) )
scope[m_aval[m]] = ''; // on met une chaine vide
// {2} on remplace toutes les occurences par la valeur //
tmpr = new RegExp( "/\{"+m_aval[m]+"\}/", 'g' );
object[key].replace(tmpr, scope[m_aval[m]]);
}
2016-09-17 06:29:59 +00:00
}
}
/* [2] On ajoute les variables '$var' et '$$arr' au scope suivant
=========================================================*/
for( key in object ){
/* (1) Ajout des variables de type '$nomVar'
---------------------------------------------------------*/
if( FormBuilder.regex.primitive_input_key.test(key) )
next_scope[ key.substring(1) ] = object[key];
/* (2) Ajout des tableaux de type '$$nomArr'
---------------------------------------------------------*/
else if( FormBuilder.regex.array_input_key.test(key) )
next_scope[ key.substring(2) ] = object[key];
}
/* [3] On lance récursivement
=========================================================*/
for( key in object ){
/* S'il ne s'agit d'un attribut qu'on peut parcourir */
if( FormBuilder.ignore_rec.indexOf(key) < 0 ){
2016-09-17 14:04:03 +00:00
/* (1) Si la clé est un 'diffuseur', envoie aussi le scope actuel (le tout cloné) */
if( FormBuilder.spread_attr.indexOf(key) > -1 )
next_scope = JSON.parse( JSON.stringify(scope).slice(0,-1) + ',' + JSON.stringify(next_scope).slice(1) );
2016-09-17 14:04:03 +00:00
/* (2) Si c'est un objet, on lance récursivement */
if( object[key] instanceof Object )
object[key] = FormBuilder.replaceStatements(object[key], next_scope);
2016-09-17 14:04:03 +00:00
/* (3) Si c'est un tableau, on lance récursivement pour chaque item */
else if( object[key] instanceof Array )
for( var i in object[key] )
object[key][i] = FormBuilder.replaceStatements(object[key][i], next_scope);
}
}
return object;
};