PhpDocumentor 2 : howto et résultats exceptionnels
Après plusieurs échanges avec le développeur principal de PhpDocumentor 2, celui-ci a corrigé plusieurs bogues et maintenant la version alpha tourne, et j’ai pu la lancer sur mon framework.
Résultats tout simplement exceptionnels (pas pour mon framework, mais pour PhpDocumentor).
Ce qui m’a le plus bluffé c’est le schéma de diagramme de classes : non seulement il est beau et pratique, mais il est clair.
Donc une vue globale de mon diagramme de classes :
Et quand on clique pour zoomer, le rendu est tout aussi exceptionnel :
C’est pratique, grâce à ça, je vois tout ce qui manque. Par exemple, les ListeXXX ne sont pas dans une classe générique, alors que les ItemXXX oui, des petites évolutions à faire pour rendre un peu tout ça plus propre, mais c’est un bon début !
Ensuite, viennent les classes et la documentation générée : de la même façon, si tout est correctement organisé, le rendu est vraiment très bon et la documentation est enfin utilisable !
jQuery Mobile : comment forcer le rechargement d’une page ?
Si vous faites des boutons de navigation, votre code jQuery Mobile va ressembler à ceci :
<a href="/">Home</a>
Le seul hic, c’est qu’en cliquant dessus, le navigateur ne va pas recharger réellement la page, il va se baser sur son cache, en supposant que la page n’a pas changé. Ce qui peut poser problème. Par exemple, sur mon site mobile, lorsqu’un commande est validée, je vide le panier côté serveur et je fais un lien vers la page racine, le « home ». Avec le code précédent, le navigateur ne recharge pas réellement la page, et garde le panier toujours « rempli » car il garde les informations et variables JavaScript en mémoire. Dans ce cas, il faut forcer au rechargement de la page. Comment faire ?
Grâce à l’astuce de ce site, voici la solution : il suffit d’ajouter cette balise : rel="external"
.
Et la page sera rechargée.
<a href="/" rel="external">Home</a>
En espérant que cela aide du monde !
JavaScript, jQuery et closure: petit exercice à faire
Ci suit un petit exercice JavaScript: copiez collez les deux morceaux de code qui suivent dans une page jsfiddle.net
Si vous examinez le code JavaScript, il est très simple : l’objectif de l’exercice semble simple : il faut juste faire « flasher » les libellés les uns après les autres (c’est là que la difficulté se trouve).
Seul problème, avec le code qui suit et semble correct : ça ne fonctionne pas. L’exercice est… de trouver la solution en JavaScript.
Ne regardez pas la fin, car il y a la solution !.
Code HTML :
<table id="s-inscrire-infos">
<tbody>
<tr>
<td id="label-nomprenom"> <span><label for="label-nomprenom"><b>Nom et prénom : </b></label></span> </td>
<td> <span> <input type="text" name="nomprenom" id="nomprenom" class="form-nom-prenom" size="50" maxlength="50" /> </span> </td>
</tr>
<tr class="erreur" id="erreur-nomprenom" style="display:none">
<td colspan="2">* Il faut renseigner votre nom et prénom</td>
</tr>
<tr>
<td id="label-adresse1"> <span> <label for="label-adresse"> <b>Adresse ligne 1 : </b> <br /> <font size="-2">(ou nom de la société) </font> </label> </span> </td>
<td> <span><input type="text" name="adresse1" id="adresse1" class="form-adresse" size="50" maxlength="60" /></span> <br /> <span class="tiny">Rue, voie, boîte postale, nom de société</span> </td>
</tr>
<tr class="erreur" id="erreur-adresse1" style="display:none">
<td colspan="2">* Il faut au moins renseigner une ligne</td>
</tr>
<tr>
<td id="label-adresse2"> <span><label for="label-adresse"><b>Adresse ligne 2 : </b> <br /> <font size="-2">(facultatif) </font> </label></span> </td>
<td> <span><input type="text" name="adresse2" id="adresse2" class="form-adresse" size="50" maxlength="60" /></span> <br /> <span class="tiny">Appartement, bâtiment, étage, digicode, cedex, etc.</span> </td>
</tr>
<tr>
<td id="label-cp"> <span><label for="label-cp"><b>Code postal : </b></label></span> </td>
<td> <span> <input type="text" name="cp" id="cp" class="form-cp" size="20" maxlength="20" /> </span> </td>
</tr>
<tr class="erreur" id="erreur-cp" style="display:none">
<td colspan="2">* Il faut renseigner votre code postal</td>
</tr>
<tr>
<td id="label-ville"> <span><label for="label-ville"><b>Ville : </b></label></span> </td>
<td> <span> <input type="text" name="ville" id="ville" class="form-ville" size="25" maxlength="50" /> </span> </td>
</tr>
<tr class="erreur" id="erreur-ville" style="display:none">
<td colspan="2">* Il faut renseigner votre ville</td>
</tr>
<tr>
<td id="label-tel"> <span> <label for="tel"><b>Numéro de téléphone : </b></label> </span> </td>
<td> <span> <input type="text" name="tel" id="tel" class="form-tel" size="15" maxlength="20" /> </span> </td>
</tr>
<tr class="erreur" id="erreur-tel" style="display:none">
<td colspan="2">* Il faut renseigner votre téléphone</td>
</tr>
<tr>
<td colspan="2" class="form-submit"> <input type="submit" value="Valider" /> </td>
</tr>
</tbody>
</table>
Code JavaScript :
jQuery.fn.getBg = function() {
return $(this).parents().filter(function() {
var color = $(this).css('background-color');
return color != 'transparent' && color != 'rgba(0, 0, 0, 0)';
}).eq(0).css('background-color');
};
function flash(id, font_color, bg_color, nb) {
var bc = $(id).getBg();
var cl = $(id).css('color');
var mx = parseInt(nb);
if (mx <= 0) {
mx = 1;
}
for (var i = 0; i < mx; i++) {
$(id).animate({
backgroundColor: bg_color,
color: font_color
}, 200).animate({
backgroundColor: bc,
color: cl
});
};
}
dataMessage = new Array("#erreur-nomprenom", "#erreur-adresse1", "#erreur-cp", "#erreur-ville", "#erreur-tel");
var theQueue = $({});
for (key in dataMessage) {
var m = dataMessage[key];
if (m.indexOf('#erreur') == 0) {
console.log('should add:'+m);
theQueue.queue('flash', function(next) {
console.log('i will flash:'+m);
$(m).fadeIn('slow', function() {
flash('#label-' + this.id.substr(7), "#ffffff", "#aa0000", 3);
next();
});
});
}
}
theQueue.dequeue('flash');
Solution :
var theQueue = $({});
for (key in dataMessage) {
var m = dataMessage[key];
if (m.indexOf('#erreur') == 0) {
console.log('should add:' + m);
var toFlash = (function(m) {
return function(next) {
console.log('i will flash:' + m);
$(m).fadeIn('slow', function() {
flash('#label-' + this.id.substr(7), "#ffffff", "#aa0000", 3);
next();
});
}
})(m);
theQueue.queue('flash', toFlash);
}
}
theQueue.dequeue('flash');
Eh oui pas si évident que ça à trouver !
Merci à Julien pour son aide !
Si vous avez d’autres solutions n’hésitez pas à les mettre dans un commentaire en bas !
Erreurs 404 originales
Voici quelques liens vers des erreurs 404 que j’ai trouvé très originales.
Si vous en avez d’autres, n’hésitez pas à les ajouter dans les commentaires !
Php : day of week – jour de la semaine. Astuce
Oui, on peut facilement imaginer plusieurs choses :
Les jours de la semaine sont numérotés de 1 à 7. Faux.
Ils sont numérotés de 0 à 6.
Enfin pour l’astuce qui pourrait certainement vous faire gagner du temps :
Les jours de la semaine commencent à lundi. Faux.
Si on lit la documentation : 0 (pour dimanche) à 6 (pour samedi).
Bon à savoir !
Diablo III : vraiment multilingue !
Sencha / ExtJS : comment garder une colonne triée avec une grid ?
Quand on fait une grille de données (datagrid
) et qu’on la lie avec un magasin (store
) c’est facile.
On peut autoriser à trier par colonnes dans la datagrid
.
Seul problème : si le store
est un stocké sur le serveur, il fait une seule fois l’appel et ensuite c’est la datagrid
qui gère les tris.
Quand on modifie un enregistrement, il est envoyé au serveur, le serveur l’enregistre, et renvoie le résultat de ce qu’il a enregistré. Généralement, il renvoie exactement ce qu’il a reçu. Le seul problème, c’est qu’au retour, la grille ne rafraichit pas l’ordre de tri selon les colonnes qu’on a choisies.
Exemple concret : vous avez une grille avec plein de noms. Vous cliquez sur la colonne « nom », pour la trier par ordre alphabétique. Vous changez le nom « Albert » par « Zoé ». Voici ce qu’il se passe :
- Le
store
envoieid=54, nom="Zoé"
au serveur ; - Le serveur fait la modification en base, et renvoie
id=54, nom="Zoé"
en retour ; - Le store reçoit
id=54, nom="Zoé"
, fait son changement en interne et le transmet à ladatagrid
; - La
datagrid
se rafraichit mais ne change pas le tri et laisse"Zoé"
à la même place.
La solution : dans le store, lors de l’événement qui signale que le résultat de l’écriture a été intégré (« write
« ) il faut forcer l’appel à sort();
qui sera répercuté sur la datagrid
automatiquement.
Voici mon code (raccourci à l’extrême sur ma classe de store qui gère les exceptions et plein d’autres choses) :
Ext.define('Ext.data.StoreHandleErrors', { extend: 'Ext.data.Store', alias: 'data.storehandleerrors', constructor: function(config) { this.callParent([config]); this.on( 'write', function(me, opts) { this.sort(); }, this ); } });
PDO, MySQL et erreurs détaillées : comment faire
Voici mon ancien code d’exécution des requêtes SQL :
$stmt = self::$_pdo->prepare($sql); if ($stmt===false) { } foreach ($tab as $key=>$valeur) { $stmt->bindValue($key, $valeur); } $stmt->execute(); if ($stmt===false) { throw new Exception( "Erreur execution de la requete :\n\"".$sql."\"\n". "Paramètres de la requete :\n\"".var_export($tab, true)."\"\n". "Details de l'erreur : \n".var_export(self::$_pdo->errorInfo(), true) ); }
Le seul (gros) problème, c’est sur erreur d’exécution, il n’y avait aucune explication claire (détail = erreur 0x00). J’ai trouvé la solution : il faut dire de lever une exception si erreur :
self::$_pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
Et maintenant, tout problème d’exécution de query lève une exception qui contient une erreur vraiment détaillée et utile de la requête.
ExtJs : dériver un Store « générique » et s’en servir
Ça fait plus d’une journée que je cherche comment faire un Store
générique, c’est à dire que j’ai plusieurs Store qui sont tous basés sur le même modèle avec le même type de proxy, etc.
Donc au lieu de faire un copier coller pour chaque Store
, j’ai cherché comment en faire un « générique » auquel je pourrai appliquer une configuration « par défaut ».
Voilà le code complet résultat, avec les fonctions qui gère les erreurs possibles renvoyées par Php (session expirée, problème d’écriture en base de données, etc).
Ce qui m’a pris le plus de temps à trouver c’est que pour « surcharger » le constructeur, ça n’est pas la fonction classique « initComponent: function(){ }
» mais la fonction de base "constructor: function(config) { }"
.
Il ne faut, de plus, surtout pas oublier d’appeler le parent, non pas via this.callParent();
mais via this.callParent([config]);
.
Ci suit du code, le code de plus d’une journée de travail, en deux parties (j’espère qu’il sauvera du temps à des personnes, ou qu’il les mettra sur la bonne voie !) :
- première partie = la surcharge
- seconde partie = exemple d’utilisation de cette surcharge
Première partie : code de la classe
Ext.define('Ext.data.StoreHandleErrors', { extend: 'Ext.data.Store', alias: 'data.storehandleerrors', constructor: function(config) { /* (!!) Réécriture par dessus certaines propriétés * du proxy : si jamais elles existent déjà, * elles vont être réécrites. */ config.autoLoad= true; config.autoSync= true; config.proxy.type= 'ajax'; config.proxy.reader= { type: 'json', successProperty: 'success', root: 'data', messageProperty: 'message' }; config.proxy.writer= { type: 'json', writeAllFields: true, root: 'data' }; config.proxy.listeners= { exception: function(proxy, response, operation) { var error=operation.getError(), title='Erreur du serveur'; if (error instanceof Array) { error=error.join("
"); } switch(response.status) { case 200: if (response.responseText!='') { var b = Ext.JSON.decode(response.responseText); if (b.title) { title=b.title; } if (b.success==false) { if (b.timeout==true) { windowLoginPanel.show(); } } } break; case -1: var error= 'Le serveur met trop de temps à répondre'+ '
'+ 'On ne peut rien faire, essayez '+ 'd\'actualiser la page.'; break; case 500: var error= 'Le serveur a une erreur interne.'+ '
'+ 'On ne peut rien faire, essayez '+ 'd\'actualiser la page.'; break; default: var error= 'Erreur renvoyée par le serveur non gérée.
'+ 'Détails :
'+ response.statusText+ '
'+ 'On ne peut rien faire, essayez '+ 'd\'actualiser la page.'; break; } Ext.MessageBox.show({ title: title, msg: error, icon: Ext.MessageBox.ERROR, buttons: Ext.Msg.OK }); } }; this.callParent([config]); this.on( 'write', function(proxy, operation) { if ( (operation.action == 'create') || (operation.action == 'update') ) { var m = this.getById( parseInt( operation.resultSet.records[0].internalId ) ); } else if (operation.action == 'destroy') { var m = this.getAt(0); } if (m) { this.panelGridEtEdit.gsGrid.getSelectionModel().select(m); } else { this.panelGridEtEdit.gsGrid.getSelectionModel().deselectAll(); } Ext.example.msg( Ext.String.capitalize(operation.action), operation.resultSet.message ); }, this ); } });
Seconde partie : utilisation de la classe
var storeAdresses = Ext.create('Ext.data.StoreHandleErrors', { model: 'Intranet.Adresse', proxy: { api: { read: '/json/intranet/liste/adresses/', create: '/json/intranet/item/adresse/?mode=create', update: '/json/intranet/item/adresse/?mode=update', destroy: '/json/intranet/item/adresse/?mode=destroy' } } });