Je suis développeur web freelance et propose des formations à Symfony2 ! Contactez-moi pour en discuter.

Quelques astuces testées sur le terrain pour débugguer une application angularJS sans quitter le navigateur.

Dans chrome, on ouvre la console avec F12 ou ctrl+maj+j. Couplé au fait que l’on peut modifier le code et le faire rejouer directement depuis les outils pour développeurs, cela permet de débugger/corriger rapidement son code sans avoir à recharger la page systématiquement, ce qui est parfois très pratique.

Récupérer un service

Des fois un service en cours de développement ne marche pas comme il faut et c’est pas toujours marrant de devoir recharger la page à chaque modification pour déclencher à nouveau l’appel en question et le débugger. Par contre, on peut récupérer le service avec la méthode ci-dessous, modifier en direct la méthode depuis l’onglet « sources » de chrome dev tools, et lancer l’exécution à la main de la méthode réticente.

1
2
var service = angular.element('body').injector().get('myService');
service.maMethodeARelancer("plop");

A mettre entre toutes les mains. Depuis que j’ai découvert ça, je ne peux plus m’en passer.

Récupérer un scope local

Des fois on a besoin de récupérer le scope sous un contrôleur, que ce soit pour voir le contenu d’une variable à un instant particulier. Pour ce cas d’usage, la plupart du temps le débuggueur est plus pratique.
Par contre, quand on veut appeler une action de controlleur présente dans le scope, c’est très pratique.

Il faut connaitre le sélecteur DOM de l’élément du DOM dans lequel se trouve le scope auquel ou souhaite accéder.

1
var scope = angular.element('[ui-view=panel-communication]').scope()

Ensuite, on peut accéder aux propriétés accessibles dans ce scope, et appeler les méthodes que l’on souhaite.

1
2
scope.unMethodeARelancer("pouet")
scope.UnAttributQueJePeuxRegarderPlusFacilementDansLeDebuggueur

Débugger les directives

Il est parfois d’accéder au scope local à la directive, qui ont un don assez fou pour ne pas contenir les valeurs que l’on croit, les rendant particulièrement pénibles à débugger.

1
var localScope = angular.element('[ui-view=panel-communication]').localScope()

Certains directives ont un contrôleur, auquel on peut accéder, ce qui est un luxe qui peut vous faire gagner pas mal de temps et vous économiser nombre de dolipranes.

1
var controller = angular.element('[ui-view=panel-communication]').controller()

Bon courage !

Lorsque l’on souhaite écrire du code concis, l’héritage permet de gagner du temps en factorisant le code dans une classe dont plusieurs vont hériter, et se partager les fonctionnalités.

Dans une application AngularJS, il est donc régulièrement nécessaire de faire hériter dans un contrôleur les fonctionnalités d’un contrôleur de base. Je ne parle pas ici d’imbrication, c’est-à-dire d’un « gros » contrôleurs dont une sous partie du DOM est gérée par un ou plusieurs autres contrôleurs, en charge de fonctionnalités restreintes localement. Je vais parler d’héritage au sens objet du terme, bien que cette notion soit un peu floue en JavaScript (j’entends déjà les puristes du fond qui hurlent, mais ça n’est pas l’objet de cet article).

L’héritage de contrôleurs est par exemple utile pour une application de type CRUD, où les divers contrôleurs vont vouloir réaliser des opérations similaires sur différents modèles, avec des particularités pour chacun d’entre elles. On va donc vouloir mettre l’essentiel des fonctionnalités partagées dans un contrôleur de base, et fournir le comportement spécifique dans les fils.

Voici comment je fais pour faire hériter des propriétés, des méthodes, le scope et ce qu’il surveille d’un contrôleur de base vers des contrôleurs fils. Tout part d’un contrôleur de base.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var BaseController = function($scope, DataStorage) {
    this.scope = $scope;
    this.DataStorage = DataStorage;
    this.scope.pageNb = 1;

    var me = this;
    this.scope.$watch ('pageNb', function() {
        me.getList();
        me.scope.isAllselected = false;
    });

    this.scope.displayCreationPopup = _.bind (this.displayCreationPopup, this);
    this.scope.hideCreationPopup    = _.bind (this.hideCreationPopup, this);

    this.scope.showCreationPopup    = false;
}

BaseController.prototype.displayCreationPopup = function (){
    this.scope.showCreationPopup = true;
}

BaseController.prototype.hideCreationPopup = function (){
    this.scope.showCreationPopup = false;
}

Plusieurs choses sont à noter :
– Je définis les propriété (objets et fonctions) à faire hériter dans le prototype. Le prototype définit toutes les propriété communes à toutes les instances de BaseController. C’est donc très bien pour nous dans le cas présent, mais il faut faire attention à ce que l’on met dedans, ce n’est pas toujours ce que l’on va vouloir.
– Bien qu’il s’agisse d’un contrôlleur, on ne l’enregistre pas via angular.module(…).controller. En effet, c’est notre classe abstraite, elle ne contient pas le code métier que l’on souhaite exécuter tel quel, et ne contient pas à proprement parler de code complet.
– D’ailleurs, dans $scope.$watch, on fait appel à la méthode getList() qui n’est pas définie ! Je la définis plus tard, dans les contrôlleurs fils. Mais afin d’éviter de la duplication de code, comme tous les controlleurs font appel à getList lors d’un changement de numéro de page, le code métier correspondant est écrit ici.
– Je fournis le $scope en paramètres du contrôlleur de base, et un service de stockage. Comme le code n’est pas exécuté tel quel, DataStorage n’a pas besoin d’être un vrai service (en fait, il n’a même pas besoin d’être défini). Tous ce dont on a besoin, c’est d’avoir une variable dans laquelle appeler des méthodes. En java ou autre, on aura un type plus précis sur cette variablee, afin d’éviter de pouvoir y mettre n’importe quoi. Dans les divers classes filles, nous injecterons un vrai service, dépendant du contrôlleur dont nous avons besoin afin d’exécuter la logique métier.

Et maintenant, le code métier d’un exemple de classe fille.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// On hérite le contrôleur de celui de base.
var NewsController = function($scope, NewsStorage, $injector) {
    $injector.invoke(BaseController, this, {$scope: $scope, DataStorage:NewsStorage});
   
    this.scope.getList    = _.bind (this.getList, this);
    this.getList();
}
NewsController.prototype = Object.create (BaseController.prototype);

// Une fonction quelconque de récupération de liste de news
// Exemple de ce qu'on pourrait avoir avec de l'asynchrone type requête Ajax :
NewsController.prototype.getList = function (){
    var me = this;
    this.scope.displayLoader = true;
    this.NewsStorage.getList (this.scope.pageNb, function(result){
            me.scope.news = result.data.news;
            me.scope.nbPages = result.data.nbPages;
            me.scope.displayLoader = false;
        }
    );
}

// On injecte le contrôleur dans l'application
var module = angular.module ('myApp.controllers', ['common.controllers', 'common.stores', 'myApp.stores']);
module.controller('NewsController', [
    '$scope',
    'NewsStorage',
    '$injector',
    NewsController
]);

Les 2 lignes importantes d’un point de vue de l’héritage sont
$injector.invoke(BaseController, this, {$scope: $scope, DataStorage:NewsStorage});
et
NewsController.prototype = Object.create (BaseController.prototype);

La première injecte les données présentes dans le $scope du père (en lui fournissant au passage les bons services). Elle fournit aussi tout ce qui s’y rattache : on conserve le $scope.$watch par l’héritage.
La seconde ligne étend le prototype, c’est à dire qu’elle duplique les méthodes dans le contrôlleur fils. Oui, c’est du javascript, pas du C++ ou du Java hein, l’héritage reste ici une notion un peu abstraite (haha).

Cette fois ci, le contrôleur News est bien injecté dans l’application. On le configure, et on lui donne les bons services, c’est eux qui sont fournis au père.
On implémente la méthode getList(), que l’on peut imaginer faire un appel ajax ou dans un cache quelconque (applicatif ou localstorage, soyons fous) pour récupérer les données nécessaires.

Vu que les méthodes sont héritées, depuis la vue, on peut donc appeler showCreationPopup() et hideCreationPopup(). On peut également utiliser les propriété du scope, et rajouter les nôtres.

Vous pouvez au passage remarquer que je bind régulièrement mes fonctions via la librairie underscore sur l’objet this. Cela permet de ne pas perdre l’objet contrôlleur lorsque les appels se font depuis angular, qui utilise comme objet d’appel le $scope local et non le contrôlleur. Dans cet exemple, tous les bindings ne sont pas nécessaires, mais quand je fais un hideCreationPopup() depuis une vue, c’est le binding dont je viens de parler qui fait qu’on peut changer la valeur de showCreationPopup, et que ce changement se répercute sur la vue.

Cela permet d’isoler le code récurrent dans une classe de base afin d’en faciliter sa réutilisation, ce qui permet, pour les contrôleurs fils, de n’implémenter que la logique spécifique.

Cet article fait partie d’une série d’articles d’introduction à Backbone.js, largement inspirée du tutoriel (en anglais) Hello Backbone.js. Les bouts de code du tutoriel original ont été légèrement refactorés.

Dans cette dernière partie, nous allons ajouter des actions à la vue de chaque item, sans modifier l’application, afin de pouvoir supprimer des éléments ou inverser les valeurs de part1 et part2. Pour cela, nous allons tirer partie du refactoring de la partie précédente, afin d’ajouter la code là où cela a du sens.

Modification de l’ItemView

L’avantage d’avoir une vue dédiée pour l’affichage des item (ItemView) comme nous l’avons vu dans la partie précédente, c’est que l’on peut maintenant lui faire gérer la logique spécifique à notre vue. En l’occurence, nous allons ajouter la gestion d’opérations qui ont lieu au niveau des éléments la liste : la suppression d’un élément, ou la modification de l’un d’entre eux.
Lire la suite »

Cet article fait partie d’une série d’articles d’introduction à Backbone.js, largement inspirée du tutoriel (en anglais) Hello Backbone.js. Les bouts de code du tutoriel original ont été légèrement refactorés.

On va reprendre l’exemple précédent, et le refactorer, afin de déléguer le rendu de notre modèle dans une vue dédiée. Cela va nous permettre de gagner en flexibilité pour manipuler les données au niveau atomique (au niveau des éléments du modèle), ce que nous ferons par la suite. Du point de vue fonctionnalité par contre, rien ne va changer : nous allons simplement améliorer la qualité du code.
Lire la suite »

Cet article fait partie d’une série d’articles d’introduction à Backbone.js, largement inspirée du tutoriel (en anglais) Hello Backbone.js. Les bouts de code du tutoriel original ont été légèrement refactorés.

Dans cette partie, nous allons voir comment créer une collection de modèles pour stocker les données, et les associer à une vue.

Le modèle

Les choses sérieuses commencent. Nous allons créer notre premier modèle. Un modèle, c’est dont une structure de donnée qui représente les informations que l’on va manipuler. Dans la partie précédente, nous n’avons pas créé de modèle matériellement : lors d’un clic, nous avons ajouté un élément au DOM, mais nous n’avons pas vraiment de trace de ce qui est fait. Si nous souhaitons par la suite manipuler les données, les choses peuvent devenir un peu compliquées.
Afin de garder un trace de ce que nous manipulons, nous allons créer un modèle qui représente les éléments de la liste que nous avions affiché précédemment. Afin de rester simple, nous allons faire que chaque élément contienne 2 propriétés, part1 et part2, qui stockeront chacunes un mot.

Accrochez vous bien, ça va aller très vite :

1
2
var Item = Backbone.Model.extend({
  });

Lire la suite »

Cet article fait partie d’une série d’articles d’introduction à Backbone.js, largement inspirée du tutoriel (en anglais) Hello Backbone.js. Les bouts de code du tutoriel original ont été légèrement refactorés.

Dans cet exemple, nous allons voir comment associer des évènements DOM à une vue.

Les évènements

Il est possible de faire écouter à nos vues les différents évènements du DOM, et leur faire exécuter des actions en conséquence. Pour celà, il suffit d’enregister les évènements dans la propriété events de la vue, en précisant un sélecteur, ainsi que quelle méthode appeler. Exemple très court où l’on ajoute un bouton, et lors du clic sur ce bouton on appelle la méthode btnClick, qui ouvre une boite de message. J’ai enlevé presque tout le code, pour ne garder que l’essentiel :
Lire la suite »

Cet article fait partie d’une série d’articles d’introduction à Backbone.js, largement inspirée du tutoriel (en anglais) Hello Backbone.js. Les bouts de code du tutoriel original ont été légèrement refactorés.

Backbone.js est une librairie très intéressante, mais un peu difficile à prendre en main. Des questions récurrentes sur le sujet amené à écrire cette petite série d’articles. Mon but, c’est de vous présenter cette librairie de manière plus simple que l’exemple « officiel » Backbone todos.js, qui est, disons, un peu raide.

Dans cette série donc, on va y aller de manière progressive, comme l’on pourrait procéder lors de la conception d’une application. Je ne toucherais pas au CSS et mettrais un minimum d’HTML, afin de nous concentrer sur Backbone. On va partir de quelque chose de très simple, et le faire évoluer, toujours de manière simple, vers une petite application, en essayant surtout de mettre en place des bonnes pratiques.

Lire la suite »

Cet article est la traduction d’un article original de Fabien Potencier, à l’origine de Symfony2, disponible ici.

Si vous deviez utiliser notre framework dès maintenant, vous voudriez probablement permettre de personnaliser les pages d’erreur. Actuellement, nous gérons les erreurs 404 et les erreurs 500, mais les réponses sont codées en dur dans le framework lui-même. Les rendre personnalisables n’est pas très difficile : on déclenche un nouvel évènement, et quelqu’un l’écoute. Pour faire ça bien, il faut que l’écouteur appelle ensuite un contrôleur. Et si le contrôleur d’erreur lève une exception ? Boucle infinie. Il doit y avoir un moyen plus facile, hein ?

Pensez à la classe HttpKernel ! Au lieu de résoudre les mêmes problèmes encore et encore, et réinventer la roue à chaque fois, la classe HttpKernel est une implémentation générique, extensible et flexible de HttpKernelInterface.
Lire la suite »

Par défaut, les champs de formulaires de type « Date » de Symfony2 sont représentés par 3 listes déroulantes. C’est très pratique pour du prototypage, mais ce n’est pas forcément ce que l’on veut proposer à l’utilisateur. On peut avoir envie de plutôt utiliser un sélecteur de date de type calendrier, comme on peut le trouver sur n’importe quel site de réservation d’hôtel ou de billet d’avion par exemple.

Nous allons donc voir comment implémenter cette fonctionnalité dans Symfony2 avec jQuery UI. jQuery est une librairie JavaScript qu’on ne présente plus, jQuery UI en est une extension qui propose de nombreuses fonctionnalités pour créer des interfaces dynamiques très web 2.0.

Lire la suite »

Dans cet article, je vais parler de la génération de formulaires dynamiques avec Symfony2. Le but: pouvoir créer des formulaires dans lesquels on rajoute des champs sans recharger la page, et qui sont associés à des entités que l’on peut ensuite persister dans la base de données. C’est un sujet relativement mal documenté. J’ai utilisé en partie le travail de Khepin, qui s’était essayé au même exercice il y a quelque temps.

Pour réaliser cela, nous allons devoir mixer du PHP (via Symfony2) avec du JavaScript (avec jQuery). Nous allons faire au plus simple et utiliser seulement ces 2 librairies afin de faire marcher cette fonctionnalité. C’est loin des standards de développement modernes (et très loin des miens) où le but n’est pas simplement d’avoir un code qui fonctionne, mais qui soit maintenable. Si le sujet vous intéresse, sachez que côté JavaScript, des librairies comme Backbone.js ont de plus en plus la côte pour faire du code fonctionnel et maintenable côté client. Je reviendrais sur ce thème dans un autre article sous peu.

Pour vous éviter de souffrir, le code du pseudo bundle décrit dans cet article est disponible sur GitHub, avec les informations nécessaires pour le lancer : https://github.com/Keirua/KeiruaProdCustomerDemoBundle
Lire la suite »