/* CONSTRUCTEUR -> INITIALISE UNE L'INSTANCE * * @parent_element Element qui contiendra le formulaire * @form_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 = { reg_in_key: /^\/\^(.+)\$\/$/, // Regex associée à une "regex" incluse dans une clé reg_out_val: /\{(\$[1-9])\}/, // Regex associée à la valeur du dernier match de "regex" pri_out_val: /\{([a-z-]+)\}/g, // Regex associée à une variable primitif à remplacer pri_in_key: /^\$([a-z-]+)$/, // Regex associée à la clé d'une variable primitive arr_out_set: /^\{\{([a-z-]+)\}\}$/, // Regex associée à un tableau à remplacer arr_out_val: /\{([a-z-]+)\.([a-z-]+)\}/g, // Regex associée à une valeur de tableau à remplacer (primitif) arr_in_key: /^\$\$([a-z-]+)$/ // Regex associée à la clé d'un tableau }; 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', // diffuse aux attributs 'node_link' ]; 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.no_recursion = [ // 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 */ 'received' ]; /************************************************************/ /* __ __ _____ _____ _ _ ___ ____ ____ */ /* | \/ | ____|_ _| | | |/ _ \| _ \/ ___| */ /* | |\/| | _| | | | |_| | | | | | | \___ \ */ /* | | | | |___ | | | _ | |_| | |_| |___) | */ /* |_| |_|_____| |_| |_| |_|\___/|____/|____/ */ /************************************************************/ /* AJOUTE UNE DEFINITION * * @def_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 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 Objet dans lequel remplacer les valeurs * @defs 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' =========================================================*/ 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 Nom du node pour lequel trouver la définition * @defs Objet de définition * * @return node_definition 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.reg_in_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 Objet dans lequel remplacer les valeurs * @scope Ensemble des variables permettant le remplacement * * @return replaced 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 = {}; /* (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( 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.arr_out_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]]; // 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.pri_out_val; 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]]); // console.log('pri', m_pri[m], scope[m_pri[m]]); } /* (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.reg_out_val, 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]]); // 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.arr_out_val; 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]]); } } } /* [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.pri_in_key.test(key) ) next_scope[ key.substring(1) ] = object[key]; /* (2) Ajout des tableaux de type '$$nomArr' ---------------------------------------------------------*/ else if( FormBuilder.regex.arr_in_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 interdit */ if( FormBuilder.spread_attr.indexOf(key) > -1 ){ console.log('spreads to', key); /* (1) on envoie aussi le scope actuel (le tout cloné) */ var tmp = [ JSON.stringify(scope).slice(0,-1), JSON.stringify(next_scope).slice(1) ]; // si au moins une valeur @scope ET @next_scope if( tmp[0].length > 2 && tmp[1].length > 2 ) tmp[1] = ',' + tmp[1]; next_scope = JSON.parse( tmp[0] + tmp[1] ); /* (3) Si c'est un tableau, on lance récursivement pour chaque item */ if( object[key] instanceof Array ) for( var i in object[key] ){ console.log(object, key+'['+i+']', scope); FormBuilder.replaceStatements(object[key][i], next_scope); } /* (2) Si c'est un objet, on lance récursivement */ else if( object[key] instanceof Object ){ console.log(object, key, scope); FormBuilder.replaceStatements(object[key], next_scope); } } } return object; };