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
storeenvoieid=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
datagridse 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'
}
}
});
Php Code Sniffer : changer l’indentation
PHPCodeSniffer est un super outil de vérification de qualité de code.
Le seul souci c’est qu’il vérifie en ayant une indentation de 4.
Tous mes sources sont basés sur une indentation de 2.
La solution :
- chercher où se trouve le fichier :
CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php - éditer à la main le fichier et changer la valeur :
public $indent = 4;
en :
public $indent = 2;
Pour information, mon fichier se trouvait ici (Ubutunu 10.04) :
/usr/share/php/PHP/CodeSniffer/Standards/Generic/Sniffs/WhiteSpace/ScopeIndentSniff.php
La seconde modification c’est pour les variables passées à l’intérieur des fonctions : de la même façon le code est censé avoir une indentation de 4.
C’est dans le fichier :
CodeSniffer/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php
Qu’il vous faudra modifier :
$expectedIndent = ($functionIndent + 4);
par :
$expectedIndent = ($functionIndent + 2);
Pour information, mon fichier se trouvait ici (Ubutunu 10.04) :
/usr/share/php/PHP/CodeSniffer/Standards/PEAR/Sniffs/Functions/FunctionCallSignatureSniff.php
Linux, MySQL, utf8 : requêtes et queries : comment éviter le décalage du résultat
Peut être avez-vous déjà rencontré ce problème : vous faites toute une base de données en utf-8.
En suivant les recommandations ici, c’est facile. Le seul problème, et ils ne donnent pas de solution, c’est que lorsqu’on fait les requêtes, les résultats sont bien renvoyés en utf-8 mais il y a un décalage s’il y a des accents :
![]()
Comment faire pour éviter ce décalage ? Facile. La solution ici.
En fait dans votre fichier de configuration, il faut aussi configurer le client, pas uniquement le serveur.
Il faut ajouter la directive « default-character-set = utf8 » dans le fichier de configuration :
sudo vim /etc/mysql/my.cnf [client] port = 3306 socket = /var/run/mysqld/mysqld.sock default-character-set = utf8
Et puis relancer le service :
sudo service mysql restart
Et voici le résultat : tout est rentré dans l’ordre :
![]()
Résultats de tests de papdevis
Voici un outil qui teste la qualité d’un site. Il fonctionne bien :
Les tests ont été assez bons, pour mon premier site développé en tant que freelance :
ExtJS et grids : double click, comment faire, howto ?
Cela fait une bonne heure que je cherche comment avoir le double click sur une grid générée dynamiquement.
C’est très simple :
Ce code ne fonctionnait pas, donc si vous êtes dans le même cas que moi, n’ayez pas peur :
this.gridAttributs = Ext.create('Ext.grid.Panel', {
border: 0,
store: this.store,
columns: [
{ text: "id", dataIndex: 'id', sortable: true },
{ ... },
{ ... }
],
celldblclick: function(evt, elem, opts ) {
console.log('dblclick');
}
});
Voici le code qui fonctionne :
this.gridAttributs = Ext.create('Ext.grid.Panel', {
border: 0,
store: this.store,
columns: [
{ text: "id", dataIndex: 'id', sortable: true },
{ ... },
{ ... }
]
});
this.gridAttributs.on('cellDblClick', function(evt, elem, opts ) {
console.log('dblclick');
});
J’espère vous avoir évité de perdre l’heure que moi même j’ai perdu ! 😉
MySQL : solution à « Can’t find any matching row in the user table »
Voilà quel était mon problème, et j’espère vous aider en vous apportant une solution si vous avez ce problème :
j’ai fait un dump complet de toutes les bases de données mysql dans un gros fichier, via l’ordre mysqldump :
mysqldump -u root -pmonmotdepasse --all-databases > a_integrer.sql
Ensuite j’ai copié le fichier sur l’ordinateur destination, et j’ai ré-injecté le sql dans la base de données, ce qui a tout ré-intégré de manière transparente :
mysqldump -u root -pmonmotdepasse < a_integrer.sql
Le seul problème, c’est que, sur l’ordinateur destination, impossible de se connecter sur la base de données, alors que l’utilisateur avait bien été intégré dans la base. En me connectant à la base, j’ai voulu modifier le mot de passe, mais impossible. L’erreur était la suivante :
Can't find any matching row in the user table
Donc impossible de changer le mot de passe alors que l’utilisateur existe bien.
La solution qui a fonctionné est la suivante : supprimer puis recréer l’utilisateur avec les droits d’accès adéquats.
Faites la même chose :
- un
mysqldumptotal ; - ré-injection sur l’ordinateur destination ;
- si problème avec les mots de passe : suppression à la main du compte
mysqlconcerné, puis re-création avec les bons droits.
Kamini : petite piqure de rappel : parce qu’on est…
J’aimerais faire un lien vers une vidéo de Kamini, et j’espère que si vous ne la connaissez pas, vous fera un peu sourire :