Skip to content
Snippets Groups Projects
Forked from an inaccessible project.
  • Thomas Fritsch's avatar
    90203c2b
    maj readme · 90203c2b
    Thomas Fritsch authored
    suppression plugin-proposal-class-properties inutile désormais
    wording
    mise en forme
    synchro cours react
    90203c2b
    History
    maj readme
    Thomas Fritsch authored
    suppression plugin-proposal-class-properties inutile désormais
    wording
    mise en forme
    synchro cours react

D. POO avancée

Sommaire

D.1. Composition : La classe PizzaThumbnail

L'objectif de cet exercice est d'utiliser une classe à l'intérieur d'une autre. On va se servir de la classe Img développée précédemment à l'intérieur d'un nouveau composant : PizzaThumbnail.

  1. Modifiez le code de la méthode render() de la classe Component pour lui permettre de recevoir dans le paramètre children :

    • soit une chaîne de caractères (comme c'est déjà le cas actuellement)
    • soit un tableau de chaînes de caractères.
      Par exemple : si tagName vaut "div" et que children vaut [ "youpi", "ça", "marche" ] alors render() retournera la chaîne "<div>youpiçamarche</div>".

    NB : Pour ne pas alourdir trop le code de la méthode render() et pour avoir un code plus lisible, passez le code de rendu des enfants, dans une méthode renderChildren().

    NB2 : Pour tester si children est un tableau (classe Array), vous pouvez utiliser l'opérateur instanceof cf. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Op%C3%A9rateurs/instanceof

    Testez votre classe avec le code suivant :

    const title = new Component( 'h1', null, ['La', ' ', 'carte'] );
    document.querySelector('.pageTitle').innerHTML = title.render();
  2. Adaptez la méthode render() pour permettre de passer dans children non seulement des chaînes de caractères comme c'est déjà le cas mais aussi d'autres Component, comme ceci :

    const c = new Component(
    	'article',
    	{name:'class', value:'pizzaThumbnail'},
    	[
    		new Img('https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300'),
    		'Regina'
    	]
    );
    document.querySelector( '.pageContent' ).innerHTML = c.render();

    Pour cela, la méthode renderChildren() devra maintenant tester le type de chaque enfant :

    • si cet enfant est lui-même une instance de Component, on fait alors appel à la méthode render() du Component enfant (petit indice : ça ressemble quand même beaucoup au concept de "récursivité"...)
    • si l'enfant est une chaîne de caractères, alors la chaîne est ajoutée telle qu'elle, comme auparavant

    NB : Pour tester si un enfant est de la classe Component, vous pouvez là aussi utiliser l'opérateur instanceof

    Si votre code fonctionne correctement, vous devez avoir le rendu suivant :

    NB : Si jamais le fond blanc derrière "Regina" n'apparaît pas, vérifiez que votre classe Component ajoute bien la classe CSS pizzaThumbnail sur la balise <article>...

  3. Grâce aux modifications que l'on vient de faire sur la classe Component, on peut maintenant créer la classe PizzaThumbnail (dans src/components/PizzaThumbnail.js).

    PizzaThumbnail est en fait un Component auquel on peut passer un objet "pizza" (une cellule du tableau data) et qui est capable de générer le même code HTML que ce que l'on avait fait dans le TP1.

    Je vous fourni son code car l'intérêt ici, est simplement de s'assurer que votre classe Component gère bien les différents cas limites :

    export default class PizzaThumbnail extends Component {
    	constructor({ name, image, price_small, price_large }) {
    		super('article', { name: 'class', value: 'pizzaThumbnail' }, [
    			new Component('a', { name: 'href', value: image }, [
    				new Img(image),
    				new Component('section', null, [
    					new Component('h4', null, name),
    					new Component('ul', null, [
    						new Component('li', null, `Prix petit format : ${price_small.toFixed(2)} €`),
    						new Component('li', null, `Prix grand format : ${price_large.toFixed(2)} €` ),
    					]),
    				]),
    			]),
    		]);
    	}
    }

    Comme vous le voyez, on utilise ici le même principe que pour la classe Img : on surcharge juste le constructeur en invoquant celui de Component (super(...)) et en lui passant les bons tagName, attribute et children.

    Attention : Je pense que vous l'aurez remarqué, le code que je vous ai fourni pour cette classe PizzaThumbnail n'est pas hyper pratique ni lisible : elle a une utilité dans notre exercice mais ce n'est pas un fonctionnement que je recommande dans la vraie vie ou dans vos futurs projets !

    Plutôt que d'imbriquer des Component dans des Component comme on le fait ici, on aurait pu par exemple overrider simplement la méthode renderChildren() pour lui faire retourner une template string contenant tout le code HTML.

    Dans le src/main.js, testez votre classe avec le code suivant :

    const pizza = data[0];
    const pizzaThumbnail = new PizzaThumbnail(pizza);
    document.querySelector( '.pageContent' ).innerHTML = pizzaThumbnail.render();

    Si votre classe Component est correctement codée (c'est à dire qu'elle prend en compte tous les cas limites) alors le code HTML généré sera le suivant :

    <article class="pizzaThumbnail">
    	<a href="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300">
    		<img src="https://images.unsplash.com/photo-1532246420286-127bcd803104?fit=crop&w=500&h=300" />
    		<section>
    			<h4>Regina</h4>
    			<ul>
    				<li>Prix petit format : 6.50 €</li>
    				<li>Prix grand format : 9.95 €</li>
    			</ul>
    		</section>
    	</a>
    </article>

    ... et le rendu obtenu dans la page sera celui-ci :

D.2. Composition : La classe PizzaList

Après un exemple simple de composition, attaquons nous à un cas plus complexe : celui de la PizzaList.

Cette classe va nous permettre d'afficher plusieurs vignettes côte à côte grâce au composant PizzaThumbnail créé précédemment.

Créez la classe PizzaList dans le fichier src/pages/PizzaList.js :

  • cette classe hérite de Component
  • instanciez-la dans src/main.js comme ceci :
     // `data` est le tableau défini dans `src/data.js`
     const pizzaList = new PizzaList(data);
     document.querySelector( '.pageContent' ).innerHTML = pizzaList.render();
  • pour chaque cellule du tableau data, le composant PizzaList créera un composant PizzaThumbnail associé
  • le code HTML retourné par la méthode pizzaList.render() sera une balise <section class="pizzaList"> dans laquelle sera injectée la combinaison du render() de chaque PizzaThumbnail

NB : en théorie, un simple override du constructor et l'utilisation de la méthode Array.map doivent suffire !

Le résultat attendu est le suivant :

D.3. Propriétés et méthodes statiques : La classe Router

Dans cet exercice, je vous propose de développer une classe Router qui, à l'aide des propriétés et méthodes statiques, va gérer l'affichage à la fois du titre de la page, et de son contenu.

C'est une classe qui nous servira dans les prochains TP et qui nous permettra de naviguer d'une page à l'autre sans rechargement de page ! (principe de base des SPA)

D.3.1 Rappels de syntaxe

Pour rappel les propriétés et méthodes statiques peuvent se déclarer à l'aide du mot clé static. Ces propriétés/méthodes n'existent qu'au niveau de la classe (et pas de l'instance) et s'utilisent comme ceci :

class Counter {
    static counter = 0;
    static getCounter() {
        return this.counter++;
    }
}

console.log(
    Counter.getCounter(),   // 0
    Counter.counter,        // 1
    Counter.getCounter(),   // 1
    Counter.counter,        // 2
);

D.3.2. La classe Router

  1. Créez une classe Router (dans un module src/Router.js) avec :

    • une propriété statique titleElement
    • une propriété statique contentElement
    • une propriété statique routes
  2. Dans le src/main.js, renseignez les valeurs de titleElement, contentElement et routes comme ceci :

    Router.titleElement = document.querySelector('.pageTitle');
    Router.contentElement = document.querySelector('.pageContent');
    Router.routes = [
    	{path: '/', page: pizzaList, title: 'La carte'}
    ];
  3. Enfin développez une méthode statique Router.navigate(path) qui permette d'afficher la PizzaList et son titre comme ceci :

    Router.navigate('/'); // affiche 'La carte' dans .pageTitle
    	// et la pizzaList dans .pageContent

D.4. Private, Setter & Getter : La propriété pizzaList.pizzas

Dans cet exercice je vous propose d'utiliser plusieurs syntaxes supplémentaires: les propriétés et méthodes privées ainsi que les getters/setters.

D.4.1. Rappels propriétés privées

Pour déclarer et utiliser des propriétés ou méthodes privées il suffit de les préfixer du caractère '#' comme ceci :

class Character {
	firstName;
	#canCook = false; // propriété privée (#)
	constructor(firstName) {
		this.firstName = firstName;
		this.#canCook = (firstName === 'Walter');
	}
}

Comme les propriétés publiques, le support des propriétés et méthodes privées n'est pas encore dans la spec officielle mais devrait être intégré à ES2022 (ES13). Néanmoins il est possible de les utiliser grâce au preset-env qui inclue ces syntaxes.

NB : Si vous vous demandez pourquoi on écrit #propriete et pas private propriete comme dans d'autres langages, la réponse se trouve ici : https://github.com/tc39/proposal-class-fields/blob/master/PRIVATE_SYNTAX_FAQ.md#why-arent-declarations-private-x

D.4.2. Rappels getters/setters

Vous pouvez déclarer des getter et des setters de la manière suivante :

class Character {
	#firstName;
	set firstName(value) {
		console.log(value);
		this.#firstName = value.toLowerCase();
	}
}

Ce sont en fait des méthodes qui se "déguisent" en propriétés. Pour utiliser le setter écrit au dessus, on peut faire simplement :

heisenberg.firstName = 'Walter';

On a l'impression d'utiliser une propriété, mais en réalité c'est une méthode qui est déclenchée, et donc le console.log(value) va s'exécuter.

D.4.3. Mise en oeuvre

A l'aide des propriétés privées, des getters et des setters, faire en sorte que le code suivant permette d'afficher la liste des pizzas :

const pizzaList = new PizzaList([]);

Router.titleElement = document.querySelector('.pageTitle');
Router.contentElement = document.querySelector('.pageContent');
Router.routes = [{ path: '/', page: pizzaList, title: 'La carte' }];

Router.navigate('/'); // affiche une page vide
pizzaList.pizzas = data;
Router.navigate('/'); // affiche la liste des pizzas

C'est la fin de ce TP, rendez-vous très vite pour le prochain chapitre !