/****************************************************/ /* _ _ _____ ___ _ ___ _______ __ */ /* | | | |_ _|_ _| | |_ _|_ _\ \ / / */ /* | | | | | | | || | | | | | \ V / */ /* | |_| | | | | || |___ | | | | | | */ /* \___/ |_| |___|_____|___| |_| |_| */ /****************************************************/ /* [1] REFERENCES DE VALEURS =========================================================*/ // Construction d'un référence var ref = function(ref_table, variable){ /* (1) On cherche une nouvelle clé */ var key = null; while( key == null || ref_table.hasOwnProperty(key) ) key = '$'+ ( 0x10000000+Math.floor( Math.random()*0xefffffff) ).toString(16)+'$'; /* (2) On attribue la valeur */ ref_table[key] = variable; /* (3) On retourne la clé */ return key; }; /************************************************************************************/ /* _____ ___ ____ __ __ ____ _ _ ___ _ ____ _____ ____ */ /* | ___/ _ \| _ \| \/ | | __ )| | | |_ _| | | _ \| ____| _ \ */ /* | |_ | | | | |_) | |\/| |_____| _ \| | | || || | | | | | _| | |_) | */ /* | _|| |_| | _ <| | | |_____| |_) | |_| || || |___| |_| | |___| _ < */ /* |_| \___/|_| \_\_| |_| |____/ \___/|___|_____|____/|_____|_| \_\ */ /************************************************************************************/ /* CONSTRUCTEUR -> INITIALISE UNE L'INSTANCE * * @form_object Objet définissant le formulaire * */ var FormBuilder = function(form_object){ /* [1] Enregistrement du formulaire =========================================================*/ /* On définit le formulaire (sous forme de description formelle) */ this.form_object = form_object; /* [2] On initialise notre Observer =========================================================*/ this.observer = new MutationObserver( FormBuilder.DOMUpdates ); }; /************************************************************************/ /* _ _____ _____ ____ ___ ____ _ _ _____ _____ ____ */ /* / \|_ _|_ _| _ \|_ _| __ )| | | |_ _| ____/ ___| */ /* / _ \ | | | | | |_) || || _ \| | | | | | | _| \___ \ */ /* / ___ \| | | | | _ < | || |_) | |_| | | | | |___ ___) | */ /* /_/ \_\_| |_| |_| \_\___|____/ \___/ |_| |_____|____/ */ /************************************************************************/ /* 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: null, // element qui contiendra le formulaire built_form: null, // Object correspondant au formulaire construit root_element: null, // Element correspondant à l'objet construit ref_table: {'$00000000$': null}, // Tableau contenant la liste des références ref_assoc: {'NULL': '$00000000$'}, // Association entre table de référence et scope d'entrée observer: this.observer, // Observeur du sous-DOM qui sera créé scope: {} // Scope pour les variables (getters + setters) }; /* 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 fun_out_val: /^\{([a-z_]+)\(\)\}$/, // Regex associée à une function incluse dans une clé ref_pri: /^\$[a-f0-9]{8}\$$/ // Clé de référence }; 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', 'listeners', 'repeat', 'browse', 'funcs' ]; FormBuilder.spec_attr = [ // Liste des attributs à dupliquer jusque dans la definition primaire '_value', '_selected' ]; // Configuration des observers FormBuilder.observe = { attributes: { attributes: true, attributeOldValue: true, childList: false, characterData: false, subtree: false, characterDataOldValue: false }, characterData: { attributes: false, attributeOldValue: false, childList: false, characterData: true, subtree: false, characterDataOldValue: true } }; // DEBUG des performances FormBuilder.debug_time = false; FormBuilder.debug_time_details = false; /* DEMARRAGE DU DEBUG */ FormBuilder.debugStart = function(group, type){ if( type == 1 && FormBuilder.debug_time ){} else if( type == 2 && FormBuilder.debug_time_details ){} else return {type: 0}; var time_r = '['+ parseInt( Math.random()*0xeffffffff ).toString(16) +']'; console.time(time_r); console.group(group); return { type: type, time_r: time_r, group: group }; }; /* MESSAGE DE DEBUG */ FormBuilder.debug = function(message, type){ if( type == 1 && FormBuilder.debug_time ){} else if( type == 2 && FormBuilder.debug_time_details ){} else return {type: 0}; console.debug(message); }; /* AFFICHAGE DU DEBUG */ FormBuilder.debugStop = function(obj){ if( obj.type == 1 && FormBuilder.debug_time ){} else if( obj.type == 2 && FormBuilder.debug_time_details ){} else return null; console.timeEnd(obj.time_r); console.groupEnd(obj.group); }; // FormBuilder.debugStart = function(){ console.time(); }; // FormBuilder.debugStop = function(s){ console.timeEnd(); }; /************************************************************/ /* __ __ _____ _____ _ _ ___ ____ ____ */ /* | \/ | ____|_ _| | | |/ _ \| _ \/ ___| */ /* | |\/| | _| | | | |_| | | | | | | \___ \ */ /* | | | | |___ | | | _ | |_| | |_| |___) | */ /* |_| |_|_____| |_| |_| |_|\___/|____/|____/ */ /************************************************************/ /* AJOUTE UNE DEFINITION * * @def_object Objet de définition * */ FormBuilder.prototype.add_definition = function(def_object){ var dbg = FormBuilder.debugStart('FormBuilder.add_definition', 1); /* [1] On ajoute la définition ==================================================================*/ for( var key in def_object ) this.defs_object[key] = def_object[key]; FormBuilder.debugStop(dbg); }; /* ON CONSTRUIT L'OBJET * * @scope [OPT] Objet contenant les variables à passer * */ FormBuilder.prototype.build = function(scope){ var dbg = FormBuilder.debugStart('FormBuilder.build', 1); /* [0] Gestion du paramètres ==================================================================*/ /* (1) On initialise le scope vide s'il n'est pas donné */ scope = (scope == null) ? {} : scope; /* (2) On transforme toutes les variables en références */ for( var key in scope ){ // On enregistre la référence scope[key] = ref(this.ref_table, scope[key]); // On enregistre l'association, pour modifier plus tard this.ref_assoc[key] = scope[key]; // On ajoute une entrée dans le @scope // this.scope['$'+key] = this.ref_assoc[key]; this.scope['$'+key] = this.ref_assoc[key]; var fset = function(o, k, v){ o.ref_table[ o.scope['$'+k] ] = v; }; /* (1) On définit le getter */ this.__defineGetter__('$'+key, (function(o, k){ return function(){ return o.ref_table[ o.scope['$'+k] ]; }; })(this, key) ); /* (2) On définit le setter */ this.__defineSetter__('$'+key, (function(o, k){ return function(v){ o.ref_table[ o.scope['$'+k] ] = v; o.attach(); }; })(this, key) ); } /* [1] On clone l'object =========================================================*/ this.built_form = JSON.parse(JSON.stringify(this.form_object)); /* [2] On remplace les valeurs ==================================================================*/ this.built_form = FormBuilder.replaceStatements(this.built_form, scope, this.defs_object, this.ref_table, this.ref_assoc); FormBuilder.debugStop(dbg); }; /* ON MODIFIE L'OBJET (scope uniquement) * * @scope [OPT] Objet contenant les variables à modifier * */ FormBuilder.prototype.update = function(scope){ var dbg = FormBuilder.debugStart('FormBuilder.update', 1); /* [0] Gestion du paramètres ==================================================================*/ /* (1) On initialise le scope vide s'il n'est pas donné */ scope = (scope == null) ? {} : scope; /* (2) On récupère toutes les références des variables */ for( var key in scope ){ // si on trouve l'association if( this.ref_assoc.hasOwnProperty(key) ){ // On modifie la valeur this.ref_table[ this.ref_assoc[key] ] = scope[key]; } } FormBuilder.debugStop(dbg); }; /* CONSTRUIT LES DOM ASSOCIE A L'OBJET CONSTRUIT * * @parent Element auquel rattacher le formulaire * */ FormBuilder.prototype.attach = function(parent){ var dbg = FormBuilder.debugStart('FormBuilder.attach', 1); /* [0] Initialisation =========================================================*/ /* (1) Gestion du paramètre @parent */ if( !(parent instanceof Element) && this.parent_element === null ) return false; this.parent_element = (parent instanceof Element) ? parent : this.parent_element; var d, i, j; /* [1] Si on avait déja construit, on retire le dom =========================================================*/ if( this.built_form.hasOwnProperty('dom') ){ /* (1) On déconnecte l'observer */ this.observer.disconnect(); /* (2) On détache les éléments */ FormBuilder.detachElements(this.parent_element, this.built_form.dom); } /* [1] On construit le DOM =========================================================*/ /* (1) On récupère les éléments */ var dom = FormBuilder.createElements(this.built_form, this.ref_table, this.ref_assoc); /* (2) On attache les éléments */ FormBuilder.attachElements(this.parent_element, this.built_form.dom); /* (3) On lance l'observer */ this.observer.observe(this.parent_element, FormBuilder.observe.attributes); FormBuilder.debugStop(dbg); }; /************************************************/ /* ____ _____ _ _____ ___ ____ */ /* / ___|_ _|/ \|_ _|_ _/ ___| */ /* \___ \ | | / _ \ | | | | | */ /* ___) || |/ ___ \| | | | |___ */ /* |____/ |_/_/ \_\_| |___\____| */ /************************************************/ /* 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){ var dbg = FormBuilder.debugStart('FormBuilder::fetchNodeDefinition', 2); FormBuilder.debug('node = "'+node+'"', 2); /* [0] Initialisation ==================================================================*/ var m = null, key, i, regex; r = FormBuilder.regex.reg_in_key; /* [1] Si la définition existe, on la retourne ==================================================================*/ if( typeof defs != 'undefined' && defs.hasOwnProperty(node) ){ FormBuilder.debugStop(dbg); return { def: defs[node] }; /* [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]; FormBuilder.debugStop(dbg); /* (3) On renvoie le lien + le scope */ return { def: defs[key], scope: matches }; } } } } FormBuilder.debugStop(dbg); // Si on a rien trouvé, on retourne NULL return {}; }; /* REMPLACE RECURSIVEMENT LES VALEURS DE @OBJECT AVEC LE @SCOPE -> RECURSIF * * @object Objet dans lequel remplacer les valeurs * @scope Ensemble des variables permettant le remplacement * @definitions Définitions des éléments * @ref_table Table des références * @ref_assoc Table des associations de références * * @return replaced Objet avec les remplacements effectués * */ FormBuilder.replaceStatements = function(object, scope, definitions, ref_table, ref_assoc){ var dbg = FormBuilder.debugStart('FormBuilder::replaceStatements', 2); /* [0] Initialisation ==================================================================*/ /* (1) Paramètres */ object = (object instanceof Object) ? object : {}; scope = (scope instanceof Object) ? JSON.parse(JSON.stringify(scope)) : {}; /* (2) Variables */ var key, r, tmp, m, found, lasti, s, parts; var m_fun, m_arr, m_reg, m_pri, m_aval; /* (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 ) scope[key] = object.scope[key]; /* [1] On lie les définitions de l'attribut 'node' -> 'node_link' ==================================================================*/ if( object.hasOwnProperty('node') && typeof object.node == 'string' ){ /* On cherche une définition */ found = FormBuilder.fetchNodeDefinition(object.node, definitions); // Si on trouve if( found.hasOwnProperty('def') ){ // 1. On clone la définition dans l'attribut 'node_link' object.node_link = JSON.parse(JSON.stringify(found.def)); // 2. On ajoute les matches dans l'attribut 'scope' if( found.hasOwnProperty('scope') ) for( key in found.scope ) scope[key] = found.scope[key]; } } /* [2] Gestion de l'attribut 'browse' =========================================================*/ if( object.hasOwnProperty('browse') ){ // Si le champ est correct, on définit l'ID avec le nom du tableau if( object.browse.hasOwnProperty('array') && FormBuilder.regex.arr_out_set.test(object.browse.array) ) object.browse.id = RegExp.$1; // Sinon, on supprime le 'browse' else delete object.browse; } /* [3] 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' ){ // On transforme en tableau object[key] = [object[key]]; /* (2.1) On cherche toutes les FONCTIONS à remplacer ---------------------------------------------------------*/ /* (1) On récupère le remplacement */ m_fun = FormBuilder.replaceStatementsFunction(object[key][0], scope, ref_table); /* (2) Si on un remplacement, on remplace, et on passe à la clé suivante */ if( m_fun !== false ){ object[key] = m_fun; continue; } /* (2.2) On cherche tous les TABLEAUX à remplacer ---------------------------------------------------------*/ /* (1) On récupère le remplacement */ m_arr = FormBuilder.replaceStatementsArray(object[key][0], scope, ref_table); /* (2) Si on un remplacement, on remplace, et on passe à la clé suivante */ if( m_arr !== false ){ object[key] = m_arr; continue; } /* (2.3) On cherche toutes les match de REGEX à remplacer ---------------------------------------------------------*/ /* (1) On récupère les remplacements */ object[key] = FormBuilder.replaceStatementsRegex(object[key], scope); /* (2.4) On cherche toutes les variables PRIMITIVES à remplacer ---------------------------------------------------------*/ /* (1) On récupère les remplacements */ object[key] = FormBuilder.replaceStatementsPrimary(object[key], scope); /* (2.5) On cherche toutes les VALEURS DE REPEAT à remplacer ---------------------------------------------------------*/ /* (1) On récupère les remplacements */ object[key] = FormBuilder.replaceStatementsArrayValue(object[key], scope, ref_table, ref_assoc); /* [1.2] Si c'est un type primitif, on le référencie =========================================================*/ }else if( typeof object[key] === 'number' ) object[key] = [ ref(ref_table, object[key]) ]; } /* [4] 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) ) scope[ key.substring(1) ] = ref( ref_table, object[key] ); /* (2) Ajout des tableaux de type '$$nomArr' ---------------------------------------------------------*/ else if( FormBuilder.regex.arr_in_key.test(key) ) scope[ key.substring(2) ] = ref( ref_table, object[key] ); } /* [5] On lance récursivement ==================================================================*/ /* on clone le scope */ scope = JSON.parse(JSON.stringify(scope)); for( key in object ){ /* S'il ne s'agit d'un attribut interdit */ if( FormBuilder.spread_attr.indexOf(key) > -1 ){ /* (1) Si c'est un tableau, on lance récursivement pour chaque item */ if( object[key] instanceof Array ){ for( var i in object[key] ){ // on lance récursivement FormBuilder.debug('**'+key+'['+i+']', 2); FormBuilder.replaceStatements(object[key][i], scope, definitions, ref_table, ref_assoc); } /* (2) Si c'est un objet, on lance récursivement */ }else if( object[key] instanceof Object ){ // on lance récursivement FormBuilder.debug('**'+key, 2); FormBuilder.replaceStatements(object[key], scope, definitions, ref_table, ref_assoc); } } } /* [5] On retourne l'object courant ==================================================================*/ FormBuilder.debugStop(dbg); return object; }; /* REMPLACE UNE FUNCTION SOUS LA FORME "{funcName}()" par sa référence * * @statement String contenant la chaine * @scope Objet contenant le scope * @ref_table Objet contenant les références * * @return newVal Retourne la nouvelle (ref) valeur ou FALSE si rien n'a été fais * */ FormBuilder.replaceStatementsFunction = function(statement, scope, ref_table){ /* (1) On initialise les variables */ var match = null; var regex = FormBuilder.regex.fun_out_val; /* (2) On exécute la regex */ match = regex.exec(statement); /* (3) Si ça match pas, on retourne FALSE */ if( match === null ) return false; /* (4) Sinon, si la fonction n'est pas dans le scope, on l'initialise */ if( !scope.hasOwnProperty(match[1]) ) scope[match[1]] = ref( ref_table, function(){} ); // on met une fonction vide /* (5) On remplace le 'statement' par la fonction */ return scope[match[1]]; }; /* REMPLACE UN TABLEAU SOUS LA FORME "{{arrayName}}" par sa référence * * @statement String contenant la chaine * @scope Objet contenant le scope * @ref_table Objet contenant les références * * @return newVal Retourne la nouvelle (ref) valeur ou FALSE si rien n'a été fais * */ FormBuilder.replaceStatementsArray = function(statement, scope, ref_table){ /* (1) On initialise les variables */ var match = null; var regex = FormBuilder.regex.arr_out_set; /* (2) On exécute la regex */ match = regex.exec(statement); /* (3) Si ça match pas, on retourne FALSE */ if( match === null ) return false; /* (4) Sinon, si le tableau n'est pas dans le scope, on l'initialise */ if( !scope.hasOwnProperty(match[1]) ) scope[match[1]] = ref( ref_table, []); // on met un tableau vide /* (5) On remplace le 'statement' par le tableau */ return scope[match[1]]; }; /* REMPLACE LES VALEURS DE MATCH DE REGEX SOUS LA FORME "{$matchNumber}" par leur référence * * @statements Tableau contenant les parties de la chaine * @scope Objet contenant le scope * * @return splitVal Tableau contenant les parties de la chaine (références + chaine) * */ FormBuilder.replaceStatementsRegex = function(statements, scope){ /* [1] Initialisation =========================================================*/ var regex = FormBuilder.regex.reg_out_val; var match = null; var matches = []; var lasti, parts, p, i, key; /* [2] Pour chaque partie de la chaine =========================================================*/ for( p = 0 ; p < statements.length ; p++ ){ /* (1) Initialisation */ m = null; matches = []; lasti = -1; parts = []; /* (2) Tant que ça match, on récupère les infos du match */ while( (match=regex.exec(statements[p])) !== null ){ // si on boucle, on sort if( lasti >= regex.lastIndex ) break; lasti = regex.lastIndex; matches.push( match ); } lasti = 0; /* [3] Pour chaque match =========================================================*/ for( i = 0 ; i < matches.length ; i++ ){ key = matches[i][1]; /* (1) On met la chaine d'avant le match (si existe) */ if( lasti > 0 || matches[i].index > 0 ) parts.push( statements[p].substr(lasti, matches[i].index-lasti) ); /* (2) Si la var n'est pas dans le scope, on l'initialise vide */ if( !scope.hasOwnProperty(key) ) scope[key] = ''; // on met une chaine vide /* (3) On insère la valeur du scope */ parts.push( scope[key] ); /* (4) On met à jour l'index de fin pour la suite */ lasti = matches[i].index + matches[i][0].length; } /* (5) On ajoute la partie après le match */ if( lasti < statements[p].length ) parts.push( statements[p].substr(lasti, statements[p].length) ); /* [4] on remplace statements[p] par sa décomposition =========================================================*/ statements = statements.slice(0, p).concat(parts).concat(statements.slice(p+1)); } /* [5] On retourne la chaine modifiée (ou non) =========================================================*/ return statements; }; /* REMPLACE LES VARIABLE SOUS LA FORME "{varName}" par leur référence * * @statements Tableau contenant les parties de la chaine * @scope Objet contenant le scope * * @return splitVal Tableau contenant les parties de la chaine (références + chaine) * */ FormBuilder.replaceStatementsPrimary = function(statements, scope){ /* [1] Initialisation =========================================================*/ var regex = FormBuilder.regex.pri_out_val; var match = null; var matches = []; var lasti, parts, p, i, key; /* [2] Pour chaque partie de la chaine =========================================================*/ for( p = 0 ; p < statements.length ; p++ ){ /* (1) Initialisation */ m = null; matches = []; lasti = 0; parts = []; /* (2) Tant que ça match, on récupère les infos du match */ while( (match=regex.exec(statements[p])) !== null ) matches.push( match ); /* [3] Pour chaque match =========================================================*/ for( i = 0 ; i < matches.length ; i++ ){ key = matches[i][1]; /* (1) On met la chaine d'avant le match (si existe) */ if( lasti > 0 || matches[i].index > 0 ) parts.push( statements[p].substr(lasti, matches[i].index-lasti) ); /* (2) Si la var n'est pas dans le scope, on l'initialise vide */ if( !scope.hasOwnProperty(key) ) scope[key] = ''; // on met une chaine vide /* (3) On insère la valeur du scope */ parts.push( scope[key] ); /* (4) On met à jour l'index de fin pour la suite */ lasti = matches[i].index + matches[i][0].length; } /* (5) On ajoute la partie après le match */ if( lasti < statements[p].length ) parts.push( statements[p].substr(lasti, statements[p].length) ); /* [4] on remplace statements[p] par sa décomposition =========================================================*/ statements = statements.slice(0, p).concat(parts).concat(statements.slice(p+1)); } /* [5] On retourne la chaine modifiée (ou non) =========================================================*/ return statements; }; /* REMPLACE LES VARIABLE SOUS LA FORME "{arrName.itemValName}" par leur référence * * @statements Tableau contenant les parties de la chaine * @scope Objet contenant le scope * @ref_table Objet contenant les références * @ref_assoc Table des associations de références * * @return splitVal Tableau contenant les parties de la chaine (références + chaine) * */ FormBuilder.replaceStatementsArrayValue = function(statements, scope, ref_table, ref_assoc){ /* [1] Initialisation =========================================================*/ var regex = FormBuilder.regex.arr_out_val; var match = null; var matches = []; var lasti, parts, p, i, key; /* [2] Pour chaque partie de la chaine =========================================================*/ for( p = 0 ; p < statements.length ; p++ ){ /* (1) Initialisation */ m = null; matches = []; lasti = 0; parts = []; /* (2) Tant que ça match, on récupère les infos du match */ while( (match=regex.exec(statements[p])) !== null ) matches.push( match ); /* [3] Pour chaque match =========================================================*/ for( i = 0 ; i < matches.length ; i++ ){ key = matches[i][1] + matches[i][2] + matches[i][3]; /* (1) On met la chaine d'avant le match (si existe) */ if( lasti > 0 || matches[i].index > 0 ) parts.push( statements[p].substr(lasti, matches[i].index-lasti) ); /* (2) On initialise la référence pour la remplir (si n'existe pas)*/ if( !ref_assoc.hasOwnProperty(key) ) ref_assoc[key] = ref(ref_table); /* (3) On insère la valeur du scope */ parts.push( ref_assoc[key] ); /* (4) On met à jour l'index de fin pour la suite */ lasti = matches[i].index + matches[i][0].length; } /* (5) On ajoute la partie après le match */ if( lasti < statements[p].length ) parts.push( statements[p].substr(lasti, statements[p].length) ); /* [4] on remplace statements[p] par sa décomposition =========================================================*/ statements = statements.slice(0, p).concat(parts).concat(statements.slice(p+1)); } /* [5] On retourne la chaine modifiée (ou non) =========================================================*/ return statements; }; /* CONSTRUIT UNE CHAINE A PARTIR DE SES PARTIES (VARIABLES OU TEXTE BRUT) * * @ref_table Objet contenant les références * @parts Tableau contenant les parties * * @return built Chaine recomposée * */ FormBuilder.readRef = function(ref_table, parts){ /* [0] Initialisation =========================================================*/ var i, built = ""; /* [1] Si c'est un tableau ou une fonction, on le retourne =========================================================*/ if( typeof parts == 'string' && FormBuilder.regex.ref_pri.test(parts) ){ // tant qu'on a une référence while( typeof parts == 'string' && FormBuilder.regex.ref_pri.test(parts) ){ parts = ref_table[parts]; if( parts instanceof Array && parts.length == 1) parts = parts[0]; } return parts; } /* [2] Sinon, on remplace par les valeurs =========================================================*/ for( i in parts ){ /* (1) Si référence, on ajoute la valeur (si elle existe) */ if( FormBuilder.regex.ref_pri.test(parts[i]) && ref_table.hasOwnProperty(parts[i]) ){ // si le résultat est un tableau, on lance récursivement if( ref_table[parts[i]] instanceof Array ) built += FormBuilder.readRef(ref_table, ref_table[parts[i]]); // sinon else built += ref_table[parts[i]]; /* (2) Sinon, on ajoute simplement */ }else built += parts[i].toString(); } /* [3] On retourne le résultat =========================================================*/ return built; }; /* CONSTRUIT UN ELEMENT A PARTIR D'UNE DEFINITION * * @definition Objet de définition de l'élément * @ref_table Objet contenant les références * @ref_assoc Table des associations de références * * @return built Retourne les éléments construits * */ FormBuilder.createElements = function(definition, ref_table, ref_assoc){ var dbg = FormBuilder.debugStart('FormBuilder::createElements', 2); /* [0] Initialisation ===========================================================*/ var built = []; var i, j, k, n, tmp, b, repeated = ['NULL'], scope, funcs; /* [1] Gestion de l'attribut 'repeat' =========================================================*/ if( definition.hasOwnProperty('repeat') ){ tmp = FormBuilder.readRef( ref_table, definition.repeat.n ); // Si c'est bien un nombre if( !isNaN(tmp) ){ /* (1) On initialise le tableau */ repeated = []; /* (2) Vérification des variables */ if( !ref_assoc.hasOwnProperty(definition.repeat.id+':i') ) ref_assoc[definition.repeat.id+':i'] = ref(0); if( !ref_assoc.hasOwnProperty(definition.repeat.id+':n') ) ref_assoc[definition.repeat.id+':n'] = ref(tmp); /* (2) On construit le scope pour chaque valeur */ for( i = 0 ; i < tmp ; i++ ){ repeated[i] = {}; repeated[i][definition.repeat.id+':i'] = i; repeated[i][definition.repeat.id+':n'] = tmp; } } } /* [2] Gestion de l'attribut 'browse' =========================================================*/ if( definition.hasOwnProperty('browse') ){ tmp = FormBuilder.readRef( ref_table, definition.browse.array ); // Si c'est bien un tableau if( tmp instanceof Array ){ /* (1) On initialise le tableau et le scope */ repeated = []; scope = []; funcs = {}; /* (2) On cherche les variables */ k = new RegExp( definition.browse.id+'.(.+)' ); // Si on trouve une valeur du type 'monTableauId.xxxxx' for( i in ref_assoc ) if( k.test(i) ) scope.push( RegExp.$1 ); /* (2) On récupère les fonctions custom */ if( definition.browse.hasOwnProperty('funcs') ){ // Pour chaque fonction for( i in definition.browse.funcs ){ // Si elle a le bon nom if( k.test(i) ){ // on ajoute la fonction à la liste de fonctions funcs[RegExp.$1] = FormBuilder.readRef( ref_table, definition.browse.funcs[i] ); } } } /* (3) Vérification des variables */ if( !ref_assoc.hasOwnProperty(definition.browse.id+':i') ) ref_assoc[definition.browse.id+':i'] = ref(0); if( !ref_assoc.hasOwnProperty(definition.browse.id+':n') ) ref_assoc[definition.browse.id+':n'] = ref(tmp); /* (4) On construit le scope pour chaque valeur */ for( i = 0 ; i < tmp.length ; i++ ){ repeated[i] = {}; // Pour chaque champ, on récupère/calcule la valeur for( j in scope ){ // {1} Si c'est une fonction custom // if( funcs.hasOwnProperty(scope[j]) ) repeated[i][definition.browse.id+'.'+scope[j]] = funcs[ scope[j] ]( tmp[i] ); // {2} Si la valeur est un attribut // else if( tmp[i].hasOwnProperty(scope[j]) ) repeated[i][definition.browse.id+'.'+scope[j]] = tmp[i][scope[j]]; } repeated[i][definition.browse.id+':i'] = i; repeated[i][definition.browse.id+':n'] = tmp.length; } } } /* [xxxx] Pour chaque instance (répétition) =========================================================*/ for( b = 0 ; b < repeated.length ; b++ ){ /* (1) On initialise l'élément courant */ built[b] = { prev: [], node: [], next: [] }; /* (2) Gestion des variables de 'repeat' et 'browse' */ if( repeated[b] instanceof Object ){ for( i in repeated[b] ) ref_table[ ref_assoc[i] ] = repeated[b][i]; } /* [3] On construit les éléments @prev_nodes, s'ils existent ===========================================================*/ if( definition.hasOwnProperty('prev_nodes') ) for( i in definition.prev_nodes ) built[b].prev = FormBuilder.createElements(definition.prev_nodes[i], ref_table, ref_assoc); /* [4] On construit les éléments @next_nodes s'ils existent ===========================================================*/ if( definition.hasOwnProperty('next_nodes') ) for( i in definition.next_nodes ) built[b].next = FormBuilder.createElements(definition.next_nodes[i], ref_table, ref_assoc); /* [5] On construit l'objet actuel ===========================================================*/ /* (1) Si c'est un sub-node, on récupère la définition récursivement */ if( definition.hasOwnProperty('node_link') ){ tmp = FormBuilder.createElements(definition.node_link, ref_table, ref_assoc); for( i in tmp ){ /* On ajoute les éléments à l'instance actuelle */ built[b].prev = built[b].prev.concat( tmp[i].prev ); built[b].node = built[b].node.concat( tmp[i].node ); built[b].next = tmp[i].next.concat( built[b].next ); } } /* (2) Si on a le type de hmtl */ else if( definition.hasOwnProperty('node_type') ){ // {1} Création de l'élément // built[b].node.push( document.createElement( FormBuilder.readRef(ref_table, definition.node_type) ) ) - 1; } /* (3) On ajoute les attributs ---------------------------------------------------------*/ if( definition.hasOwnProperty('attributes') ){ // Pour chaque attribut, on définit for( i in definition.attributes ) // Pour chaque node for( n in built[b].node ) built[b].node[n].setAttribute( i, FormBuilder.readRef(ref_table, definition.attributes[i]) ); } /* (4) On ajoute le CSS ---------------------------------------------------------*/ if( definition.hasOwnProperty('css') ){ // On applique pour chaque noeud for( n in built[b].node ) FormBuilder.applyCSS(built[b].node[n], definition.css); } /* (5) On ajoute le contenu HTML ---------------------------------------------------------*/ if( definition.hasOwnProperty('text') ){ // Note: Override les enfants for( n in built[b].node ) built[b].node[n].innerHTML = FormBuilder.readRef(ref_table, definition.text); } /* (6) On ajoute les listeners ---------------------------------------------------------*/ if( definition.hasOwnProperty('listeners') ){ // Pour chaque listener for( i in definition.listeners ) // Pour chaque node for( n in built[b].node ) built[b].node[n].addEventListener( i, FormBuilder.readRef(ref_table, definition.listeners[i]), false ); } /* (7) On ajoute les enfants ---------------------------------------------------------*/ if( definition.hasOwnProperty('children') ){ /* (1) Pour chaque enfant */ for( i in definition.children ){ tmp = FormBuilder.createElements(definition.children[i], ref_table, ref_assoc); // Pour chaque node for( n in built[b].node ) FormBuilder.attachElements(built[b].node[n], tmp); } } /* (8) Gestion de la récupération dynamique */ for( n in built[b].node ){ // {2} Determination du type // var input = FormBuilder.fetchNodeType(definition) == 'input' && definition.hasOwnProperty('_value'); var select = FormBuilder.fetchNodeType(definition) == 'select' && definition.hasOwnProperty('_selected'); // {3} Si de type texte // if( input ){ // On met à jour la valeur built[b].node[n].addEventListener('input', function(e){ // Uniquement si tableau de taille 1 avec une reference if( !(definition._value instanceof Array) ) return; if( definition._value.length > 1 ) return; if( !FormBuilder.regex.ref_pri.test(definition._value[0]) ) return; // on recupere cette reference var value_ref = definition._value[0]; // On recupere la derniere reference var last = value_ref, curr = value_ref; // On var jusqu'a la valeur while( FormBuilder.regex.ref_pri.test(curr) ){ last = curr; curr = ref_table[ curr ]; if( curr instanceof Array ) curr = curr[0]; } // On attribut la valeur modifiee ref_table[ last ] = this.value; }, false); } // TODO: Gestion de la transmission de 'value' / 'selected' lors de la construction } } var identifier = definition.hasOwnProperty('node') ? definition.node : definition.node_type FormBuilder.debug('<'+identifier+'>', 2); FormBuilder.debugStop(dbg); /* [8] On ajoute l'élément à la définition et retourne =========================================================*/ definition.dom = built; return built; }; /* ATTACHE UN JEU D'ELEMENTS A UN PARENT * * @parent Element parent * @set Set d'enfants à attacher * */ FormBuilder.attachElements = function(parent, set){ var dbg = FormBuilder.debugStart('FormBuilder::attachElements', 2); var c, a; /* [1] Pour chaque contexte (élément + prev + next) =========================================================*/ for( c in set ){ /* (1) On attache les éléments précédents ---------------------------------------------------------*/ for( a in set[c].prev ){ /* (1) Si c'est un Element */ if( set[c].prev[a] instanceof Element ) parent.appendChild( set[c].prev[a] ); /* (2) On lance récursivement */ if( set[c].prev[a] instanceof Object ) FormBuilder.attachElements(parent, [ set[c].prev[a] ]); } /* (2) On ajoute l'élément actuel ---------------------------------------------------------*/ for( a in set[c].node ){ /* (1) Si c'est un Element */ if( set[c].node[a] instanceof Element ) parent.appendChild( set[c].node[a] ); /* (2) On lance récursivement */ if( set[c].node[a] instanceof Object ) FormBuilder.attachElements(parent, [ set[c].node[a] ]); } /* (3) On attache les éléments suivants ---------------------------------------------------------*/ for( a in set[c].next ){ /* (1) Si c'est un Element */ if( set[c].next[a] instanceof Element ) parent.appendChild( set[c].next[a] ); /* (2) On lance récursivement */ if( set[c].next[a] instanceof Object ) FormBuilder.attachElements(parent, [ set[c].next[a] ]); } } FormBuilder.debugStop(dbg); }; /* DETACHE UN JEU D'ELEMENTS D'UN PARENT * * @parent Element parent * @set Set d'enfants à attacher * */ FormBuilder.detachElements = function(parent, set){ var c, a; /* [1] Pour chaque contexte (élément + prev + next) =========================================================*/ for( c in set ){ /* (1) On attache les éléments précédents ---------------------------------------------------------*/ for( a in set[c].prev ){ /* (1) Si c'est un Element */ if( set[c].prev[a] instanceof Element ) parent.removeChild( set[c].prev[a] ); /* (2) On lance récursivement */ if( set[c].prev[a] instanceof Object ) FormBuilder.detachElements(parent, [ set[c].prev[a] ]); } /* (2) On ajoute l'élément actuel ---------------------------------------------------------*/ for( a in set[c].node ){ /* (1) Si c'est un Element */ if( set[c].node[a] instanceof Element ) parent.removeChild( set[c].node[a] ); /* (2) On lance récursivement */ if( set[c].node[a] instanceof Object ) FormBuilder.detachElements(parent, [ set[c].node[a] ]); } /* (3) On attache les éléments suivants ---------------------------------------------------------*/ for( a in set[c].next ){ /* (1) Si c'est un Element */ if( set[c].next[a] instanceof Element ) parent.removeChild( set[c].next[a] ); /* (2) On lance récursivement */ if( set[c].next[a] instanceof Object ) FormBuilder.detachElements(parent, [ set[c].next[a] ]); } } }; /* APPLIQUE UN STYLE CSS-LIKE A UN ELEMENT * * @element Element auquel on veut appliquer le css * @css Objet contenant les règles css * */ FormBuilder.applyCSS = function(element, css){ /* On applique chaque règle */ for( var r in css ) element.style[r] = css[r]; }; /* RETOURNE LE NODE_TYPE EN PARCOURANT LES DEFINITION RECURSIVEMENT * * @definition Definition contenant node_link ou node_type * * @return node_type Node_type final en suivant les définition * */ FormBuilder.fetchNodeType = function(definition){ /* (1) Si c'est la définition finale */ if( definition.hasOwnProperty('node_type') ) return definition.node_type; /* (2) Si on trouve un lien vers définition, on lance récursivement */ else if( definition.hasOwnProperty('node_link') ) return FormBuilder.fetchNodeType(definition.node_link); }; FormBuilder.DOMUpdates = function(events){ var e; /* [1] On parcourt chaque évènement =========================================================*/ for( e in events){ console.log( events[e] ); console.log( events[e].target ); console.log( events[e].type ); } };