From a3005eccde834f01f432a8d0eba7d3ecec33a498 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 8 Jan 2017 11:53:52 +0100 Subject: [PATCH] Updated page-manager not to bug when #tag in URL + allows 'http[s]://host/page/bla' (without ending /) --- public_html/js/lib/min/page-manager.js | 334 ++++++++++++++++++++++++- public_html/js/lib/page-manager.js | 38 ++- 2 files changed, 354 insertions(+), 18 deletions(-) diff --git a/public_html/js/lib/min/page-manager.js b/public_html/js/lib/min/page-manager.js index 8968861..b1080fe 100755 --- a/public_html/js/lib/min/page-manager.js +++ b/public_html/js/lib/min/page-manager.js @@ -1,9 +1,325 @@ -function pageManagerClass(){}var ptrPageManagerClass; -pageManagerClass.prototype={loaded:null,depJS:null,depCSS:null,xhr:[],activeXHR:null,page:null,vars:[],root:"",path:"",jsPath:"js",cssPath:"css",pagelist:null,container:null,ajax:function(b,c,d,a){var e;e=this.xhr.push(window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHttpRequest"))-1;this.activeXHR=this.xhr[e];this.xhr[e].addEventListener("readystatechange",function(a){if(void 0!=this.xhr[a]&&4===this.xhr[a].readyState){-1<[0,200].indexOf(this.xhr[a].status)?c(this.xhr[a].responseText): -c();this.xhr[a]=null;var b=!0;for(a=0;a le lien à charger + - pHandler une fonction qui s'éxécutera avec la réponse de la requête passée en paramètre (voir exemples dessous pour pHandler) + - pMethod type de méthode, vaut 'POST' ou 'GET' et vaut 'POST' par défaut ou s'il n'est pas renseigné + - pForm formulaire de type FormData() contenant les données à envoyer (uniquement en POST), si pForm vaut GET les données doivent être passées dans l'URL + ========================================================================== */ + ajax: function(pLink, pHandler, pMethod, pForm){ + // on efface les requêtes qui sont terminées et on push une nouvelle + // for( var i = 0 ; i < this.xhr.length ; i++ ){ + // this.xhr = this.xhr.splice(i, 1); // suppression entrée + // } + + var index; + + // IE7+, Firefox, Chrome, Opera, Safari + // IE5, IE6 + index = this.xhr.push( window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHttpRequest') ) -1; + + // On definit un pointeur sur l'instance XHR active (ajax) + this.activeXHR = this.xhr[index]; + + this.xhr[index].addEventListener('readystatechange', function(i){ + + // si la requete a ete supprimee, on quitte + if( this.xhr[i] == undefined ) + return; + + // Si la requête est terminée + if( this.xhr[i].readyState === 4 ){ // si la requête est terminée + + if( [0,200].indexOf(this.xhr[i].status) > -1 ) // si fichier existe et reçu + pHandler(this.xhr[i].responseText); + else // si code d'erreur retourne null + pHandler(); + + + // On supprime la requête + this.xhr[i] = null; + + // S'il n'y a plus de requête active, on supprime tout + var empty = true; + for( var i = 0 ; i < this.xhr.length ; i++ ) + if( this.xhr[i] !== null ){ + empty = false; + break; + } + + // Si tout est vide, on efface + empty && ( this.xhr = [] ); + } + + }.bind(this, index), false); + + // gestion de la méthode + var method = ( typeof pMethod == 'string' && /^POST|GET$/i.test(pMethod) ) ? pMethod.toUpperCase() : 'POST'; + + // gestion du formulaire si la méthode est POST + var form = ( method == 'POST' && typeof pForm == 'object' && pForm instanceof FormData ) ? pForm : null; + + this.xhr[index].open( method, pLink, true ); + this.xhr[index].send( form ); + + return this; + }, + /***************************************************** [APPLICATION] Ajax() ******************************************************/ + // EXEMPLES DE FONCTIONS POUR pHandler // + // 1. var a = function(param){ alert(param); } // les deux notations 1 et 2 sont équivalents + // 2. function a(param){ alert(param); } // les deux notations 1 et 2 sont équivalents + + // ajax( 'index.php', a ); // utilisation d'une fonction définie + + // ajax( 'index.php', alert ); // utilisation d'une fonction prédéfinie + // ajax( 'index.php', alert, 'GET' ); // utilisation de méthode + + // var fd = new FormData(); // création d'un formulaire + // fd.append('var', 100); // ajout de la variable VAR qui vaut 100 + + // ajax( 'index.php', alert, null, fd ); // saut de paramètre avec null + envoi formulaire + // ajax( 'index.php?var=10', alert, 'GET' ); // envoi formulaire en GET (dans l'url) + // ajax( 'index.php?var=10', alert, 'POST', fd ); // envoi formulaire en GET (dans l'url) + en POST via le formulaire FD + + + /* ======================================================================= + Cette fonction effectue une décomposition de l'URL sur le shéma spécifié dessous + Renvoie pour http://www.exemple.com/dirA/dirB/#/NOMPAGE/VARPAGE + - null si la page n'est pas référencée dans le tableau PAGELIST + - null si le lien ne contient pas /#/NOMPAGE à la fin + - null si NOMPAGE ne contient pas uniquement : lettres, chiffres, underscore + - null si VARPAGE ne contient pas uniquement : lettres, chiffres, underscore + - un objet contenant {page: valeur, var: valeur} + ========================================================================== */ + explodeURL: function(url_data){ + url_data = (arguments.length >= 1) ? url_data : document.URL; + // si pageList est correct et que l'URL correspond à un schéma de page => continue [sinon] return null + if( this.pagelist != null && /^(?:(?:https?:\/\/)?[^\/]+)\/([a-z0-9_]+)(?:\/|((?:\/\w+)+)\/?)?(#.*)?$/i.test(url_data) ){ + // si la page récupérée dans l'url est dans la liste => renvoi de l'objet [sinon] null + var vars = RegExp.$2.split('/'); + // on supprime la première entrée vide + vars = vars.slice(1); + + return ( this.pagelist.indexOf(RegExp.$1) > -1 ) ? {page: RegExp.$1, var: vars} : null; + }else + return null; + }, + /* ======================================================================= + Cette fonction ajoute des dépendances (un js et un css) situés dans le répertoire des pages. + pageDir/ + _JS/ + page1.js + page2.js + _CSS/ + page1.css + page2.css + ========================================================================== */ + loadDependencies: function(){ + // si depCSS est un élément du DOM c'est à dire qu'il contient le fichier de la page précédente et qu'il est enfant de , on le détruit + if( typeof this.depCSS == 'object' && this.depCSS instanceof Element && this.depCSS.parentNode == document.head ) + document.head.removeChild( this.depCSS ); + + // si depJS est un élément du DOM c'est à dire qu'il contient le fichier de la page précédente, on le détruit + if( typeof this.depJS == 'object' && this.depJS instanceof Element && this.depJS.parentNode == document.head ) + document.head.removeChild( this.depJS ); + + // si le fichier css existe + this.ajax(this.root+this.path+'/'+this.cssPath+'/'+this.page+'.css', function(e){ + if( e != null ){ // on charge la dépendance CSS si le fichier existe + this.depCSS = document.createElement('link'); + this.depCSS.rel = 'stylesheet'; + this.depCSS.type = 'text/css'; + this.depCSS.href = this.root+this.path+'/'+this.cssPath+'/'+this.page+'.css'; + document.head.appendChild(this.depCSS); + }else + console.warn('[loadDependencies_Error] - ('+this.root+this.path+'/'+this.cssPath+'/'+this.page+'.css)'); + }.bind(this)); + + // si le fichier js existe + this.ajax(this.root+this.path+'/'+this.jsPath+'/'+this.page+'.js', function(e){ + if( e != null ){ // on charge la dépendance JS si le fichier existe + this.depJS = document.createElement('script'); + this.depJS.type = 'text/javascript'; + this.depJS.src = this.root+this.path+'/'+this.jsPath+'/'+this.page+'.js'; + document.head.appendChild(this.depJS); + }else + console.warn('[loadDependencies_Error] - ('+this.root+this.path+'/'+this.jsPath+'/'+this.page+'.js)'); + }.bind(this)); + }, + + /* ======================================================================= + Met à jour l'URL de la page en fonction de la page chargée et des + variables associées (ne recharge aucune ressource) + ======================================================================= */ + updateURL: function(){ + if( this.vars.length > 0 ) // si il y a des variables + window.history.pushState(this.page, this.page, this.root+'/'+this.page+'/'+this.vars.join('/')+'/'); + else // s'il n'y en a pas + window.history.pushState(this.page, this.page, this.root+'/'+this.page+'/'); + + // on peut récupérer le nom de la page (quand on fait retour en arrière de l'historique) + // dans la variable : window.history.state + }, + + /* ======================================================================= + Cette fonction est celle qui gère les 2 autres et celle que l'utilisateur utilisera + PARAMETRES: + - pName le nom de la page à charger (lettres, chiffres, underscore) (*) + - pPath chemin (relatif ou absolu) du dossier contenant les pages de même nom de fichier que le nom (extension .php) + - pContainer l'élément du DOM qui contiendra la page chargée (**) + - pPageList> tableau contenant la liste des pages sous forme de chaînes de caractères (**) (***) + * Le chemin du dossier sans le '/' final si c'est le dossier actuel le chemin est une chaîne vide + Si le dossier est 'page' et que l'on cherche la page 'accUe1l', la requête sera vers 'page/accUe1l.php' + le nom de la page est sensible à la casse + ** 1. pPageList et pContainer doivent être mis en paramètres uniquement à la première utilisation + et la première utilisation doit se faire au chargement de la page car elle permetra + de mettre l'URL à jour et/ou charger la page de l'URL + *** la première page du tableau est la page par défaut (qui est chargée si l'URL ne contient + pas la page ou si la page de l'URL ne correspond à aucune page de la liste) + ========================================================================== */ + setPage: function(pName, pPath, pContainer, pPageList, pRoot){ + + // liste de pages si c'est un tableau + var pageList = ( typeof pPageList == 'object' && pPageList instanceof Array ) ? pPageList : null; // si this.pagelist n'est pas overwrite il vaut null + + if( pageList != null ){ // si c'est un tableau + for( var i = 0 ; i < pageList.length ; i++ ){ // on parcourt tout les éléments pour vérifier que chaque élément ne contient que : lettres, chiffres, underscore [non]> pageList = null + pageList = ( typeof pageList[i] == 'string' && /^[a-z0-9_]+$/i.test(pageList[i]) ) ? pageList : null; + if( pageList == null ) break; // si le tableau est null stoppe la boucle + } + } + /* on attribue la variable temporaire pageList à l'attribut de l'objet si la variable pageList temporaire n'est pas nulle */ + this.pagelist = ( pageList != null ) ? pageList : this.pagelist; + // affecte à l'attribut page la page par défaut (premier élément de pagelist) + // this.page = this.pagelist[0]; + // Gestion de pRoot si racine autre que racine du vhost + this.root = ( typeof pRoot == 'string' ) ? pRoot : this.root; + // affecte pPath à l'attribut path s'il est renseigné + this.path = ( typeof pPath == 'string' ) ? pPath : this.path; + /* on attribue le paramètre pContainer à l'attribut si il est spécifié */ + this.container = ( typeof pContainer == 'object' && pContainer instanceof Element ) ? pContainer : this.container; + + // si this.pagelist && this.container ne sont pas null + if( this.pagelist && this.container ){ + + // Si on veut just `refresh` la page + if( pName === true ){ + + this.ajax(this.root+this.path+'/'+this.page+'.php', function(e){ + this.container.innerHTML = e; + this.loadDependencies(); + this.refresher.apply(this); + }.bind(this), 'POST', fd); + + return this; + + // si le pName est renseigné et qu'il est dans pagelist + }else if( typeof pName == 'string' && this.pagelist.indexOf(pName) > -1 ){ + // affecte pName à l'attribut page + this.page = pName; + + // formulaire POST + var fd = new FormData(); + for( var i = 0 ; i < this.vars.length ; i++ ) + fd.append(this.vars[i], null); + + this.ajax(this.root+this.path+'/'+this.page+'.php', function(e){ + this.container.innerHTML = e; + this.loadDependencies(); + }.bind(this), 'POST', fd); + + // change l'URL en conséquences(stateObj, titre, url) + this.updateURL(); + + }else{ // si la page n'est pas spécifiée ou qu'elle n'est pas dans la liste des pages + var urlGet = this.explodeURL(); + // si on a récupéré le numéro de la page dans l'URL et qu'elle fait partie de la liste des pages + if( urlGet != null ){ + + // formulaire POST + var fd = new FormData(); + this.vars.length = 0; + + for( var i = 0 ; i < urlGet.var.length ; i++ ){ // replacing object variables with explodeURL variables + this.vars[i] = urlGet.var[i]; + fd.append(this.vars[i], null); + } + + + // si différent de la page actuelle, on charge la page + if( urlGet.page == this.page ){ + return this; + } + + this.page = urlGet.page; + + this.ajax(this.root+this.path+'/'+this.page+'.php', function(e){ + this.container.innerHTML = e; + this.loadDependencies(); + }.bind(this), 'POST', fd); + + // change l'URL en conséquences(stateObj, titre, url) + this.updateURL(); + + }else // si l'url ne contient rien, on charge la page par défaut + this.setPage(this.pagelist[0]); + } + }else + console.warn('pagelist et container manquant'); + + return this; + }, + + + + /* Rechargement de la page courante + * + * @action Recharge la page en cours en conservant les variables + * + */ + refresh: function(refresher){ + if( refresher instanceof Function ){ + + this.refresher = refresher; + return; + + }else{ + + return this.setPage(true); + + } + + } + +}; diff --git a/public_html/js/lib/page-manager.js b/public_html/js/lib/page-manager.js index 659e32f..7450141 100755 --- a/public_html/js/lib/page-manager.js +++ b/public_html/js/lib/page-manager.js @@ -28,6 +28,7 @@ pageManagerClass.prototype = { cssPath: 'css', // le chemin du dossier contenant les feuilles de style (.css) pagelist: null, // la liste des pages pouvant être chargées container: null, // élément DOM qui contiendra le contenu des pages à charger + refresher: function(){}, // function appelée après refresh /* ======================================================================= Cette fonction effectue une requête Ajax (compatible à partir de IE5) PARAMETRES: @@ -124,11 +125,11 @@ pageManagerClass.prototype = { explodeURL: function(url_data){ url_data = (arguments.length >= 1) ? url_data : document.URL; // si pageList est correct et que l'URL correspond à un schéma de page => continue [sinon] return null - if( this.pagelist != null && /^(?:(?:https?:\/\/)?[^\/]+)\/([a-z0-9_]+)\/?(?:\/((?:.+\/)+)\/?)?$/i.test(url_data) ){ + if( this.pagelist != null && /^(?:(?:https?:\/\/)?[^\/]+)\/([a-z0-9_]+)(?:\/|((?:\/\w+)+)\/?)?(#.*)?$/i.test(url_data) ){ // si la page récupérée dans l'url est dans la liste => renvoi de l'objet [sinon] null - var vars = RegExp.$2.split('/'); - while( vars[vars.length-1] == '' ) // on supprime les dernières entrées vides - vars.pop(); + // on supprime la première entrée vide + var vars = RegExp.$2.split('/').slice(1); + console.log(vars); return ( this.pagelist.indexOf(RegExp.$1) > -1 ) ? {page: RegExp.$1, var: vars} : null; }else @@ -230,10 +231,21 @@ pageManagerClass.prototype = { this.container = ( typeof pContainer == 'object' && pContainer instanceof Element ) ? pContainer : this.container; // si this.pagelist && this.container ne sont pas null - if( this.pagelist != null && this.container != null ){ + if( this.pagelist && this.container ){ + + // Si on veut just `refresh` la page + if( pName === true ){ + + this.ajax(this.root+this.path+'/'+this.page+'.php', function(e){ + this.container.innerHTML = e; + this.loadDependencies(); + this.refresher.apply(this); + }.bind(this), 'POST', fd); + + return this; // si le pName est renseigné et qu'il est dans pagelist - if( typeof pName == 'string' && this.pagelist.indexOf(pName) > -1 ){ + }else if( typeof pName == 'string' && this.pagelist.indexOf(pName) > -1 ){ // affecte pName à l'attribut page this.page = pName; @@ -296,10 +308,18 @@ pageManagerClass.prototype = { * @action Recharge la page en cours en conservant les variables * */ - refresh: function(){ - this.setPage(this.page); + refresh: function(refresher){ + if( refresher instanceof Function ){ + + this.refresher = refresher; + return; + + }else{ + + return this.setPage(true); + + } - return this; } };