Série Construire un E-Commerce avec JavaScript

Image de la série de tutoriels Construire une site E-Commerce avec JavaScript
Image de la série de tutoriels Construire une site E-Commerce avec JavaScript

Préambule

Aujourd’hui sur Activateur Web, je vous propose une nouvelle série qui sera consacrée cette fois à la création d’un site e-commerce en JavaScript. 

Alors, je vous rassure tout de suite, une bonne partie du travail est déjà fait, et ce que nous réaliserons dans ce projet, c’est l’intégration des éléments fournis par L’API dans les différentes pages web du site.

C’est un projet que j’ai réalisé pendant ma formation de développeur web chez OpenClassRooms, mais au moment où je l’avais réalisé, tout le frontend était à construire. 

Le créateur du code original est Luc Bourrat, et j’ai modifié quelques éléments uniquement.

N’hésitez pas à aller voir les formations qu’ils proposent. La pédagogie par projets, pour moi, est une des meilleures qui existe. 

Dans ce projet, l’API et le frontend sont déjà construits, et nous allons donc écrire du code en JavaScript uniquement. 

Comme toujours sur Activateur Web, vous aurez la possibilité de suivre cette série de tutoriels, en vidéos que je mettrai à chaque début de chapitre, ou en lecture avec un guide pas à pas. 

Codes couleurs pour plus de compréhension

Pour la compréhension de cette série, pour ceux qui suivront le format à lire, j’ai indiqué le nom d’un dossier est de couleur verte, le nom d’un fichier est de couleur orange, celui d’une fonction ou d’une méthode est en mauve.

Table des matières - Sommaire

Introduction

Prérequis

Bien que j’essaierai d’être le plus clair possible dans mes explications, il est bien de connaître déjà les bases du JavaScript, et notamment ce qui concerne la modification du DOM.  

Outils nécessaires : 

  • Un éditeur de code : Pour ma part, j’utilise VS Code et c’est cet éditeur que j’utiliserai tout au long de cette série de tutoriels. Vous pouvez bien entendu, en utiliser un autre.

Si vous n’avez pas encore d’éditeur de code, n’hésitez pas à suivre l’article de blog que j’ai réalisé sur l’installation de Visual Studio Code.

  • L’extension Live Server est installée sur VSCode. (pour lancer le frontend).
  • Node.js et son gestionnaire de paquets Npm. Bien évidemment, pour construire une API avec Node.js, il faut que Node.js et Npm soient installés sur votre système.

Si vous n’avez pas encore installé Node.js et Npm, j’ai là encore écrit un article de blog sur le sujet. N’hésitez pas à le suivre.

Pitch du projet :

Briefing avec l’équipe qui a déjà travaillé sur le projet.

Voici les informations pour que tu puisses démarrer l’implémentation du site de manière dynamique. 

Voici les différentes tâches que tu vas devoir mener à bien :

  • Unifier les travaux déjà réalisés par l’équipe en intégrant dynamiquement les éléments de l’API dans les différentes pages web avec JavaScript. Le code du front-end et de l’API est disponible sur ce repository GitHub.

4 pages ont été mises en place : page d’accueil, page Produit, page Panier et la page Confirmation. Sur l’ensemble des pages, toutes les parties statiques sont en place, elles sont donc prêtes à recevoir le contenu dynamique.

Aussi, sur chaque page, un exemple de la partie dynamique est systématiquement donné et des “id” ont été attribués aux éléments. De cette façon, tu n’as pas à t’occuper de la mise en place de la structure HTML ni du style CSS, tout est déjà fait. Tu n’as plus qu’à t’occuper d’intégrer ces éléments dynamiquement grâce à JS et l’API.

Spécifications techniques et fonctionnelles

Pour plus de précisions, voici les spécifications techniques et fonctionnelles du projet. Tu pourras y trouver tous les détails de celui-ci, les attentes pour chaque page du site web et les détails de l’API. 

Diapositive précédente
Diapositive suivante

Mise en place du projet - Backend et frontend

La première chose à faire va être de cloner le projet depuis GitHub. 

Pour cela, je commence par créer un dossier sur mon bureau, que je nomme par exemple “E-commerce JavaScript”.

J’ouvre ensuite ce dossier dans mon éditeur de code, et à partir de là, il y a 2 méthodes possibles : pour ceux qui ont Git installé sur leur système et une autre pour ceux qui ne l’ont pas.  Je tiens à rappeler qu’il est toujours crucial de versionner son code, et que par conséquent, un compte GitHub ou similaire ainsi que Git sont plus utiles. 

1ère méthode : Pour ceux qui ont Git installé sur leur système :

Pour cloner l’API et le frontend du projet, ouvrez un terminal depuis le dossier E-commerce JavaScript, et taper ensuite en ligne de commande : 

				
					git clone https://github.com/FabRiviere/AW-ShoppingCart_JS.git

				
			

Après avoir taper sur la touche Entrée, un dossier AW-ShoppingCart_JS comprenant tout le code du site doit être présent dans votre dossier E-commerce JavaScript. Depuis la ligne de commande, entrez dans votre nouveau dossier AW-ShoppingCart_JS en tapant :

				
					cd AW-ShoppingCart_JS

				
			

Votre ligne de commande doit maintenant se finir par …./E-commerce-Javascript/AW-ShoppingCart_JS/

Entrez ensuite dans le dossier du backend en tapant :

				
					cd back

				
			

Installer maintenant le serveur backend en tapant : 

				
					npm install

				
			

L’installation va prendre quelques temps, en fonction de votre système, de votre connexion, etc. 

Une fois l’installation terminée, lancer le serveur backend de l’application en tapant : 

				
					nodemon server

				
			

Le serveur backend est lancé. Maintenant nous pouvons lancer le frontend en utilisant l’extension de VSCode ”LiveServer” en appuyant sur l’icône Go Live, située en bas à droite de VSCode. Ou sinon en faisant un clic droit sur le fichier index.html et ensuite Open with Live Server.

L’application frontend doit s’afficher correctement. Pour le moment, nous n’aurons que les éléments d’en-tête et pied de page, car c’est justement notre mission de devoir peupler ces pages dynamiquement. 

Image du frontend du projet initial
Page d'accueil du frontend du projet initial

2ème méthode : Pour ceux qui n’ont pas Git sur leur système

Sachez tout d’abord qu’un développeur se doit de “versionner” son code, et donc de disposer d’un service tel que Git et GitHub. Je me répète, mais imaginez que votre projet est presque fini et que votre ordinateur vous lâche, et bien si vous ne l’avez pas versionné et hébergé quelque part, vous devrez tout recommencer.. 

Ceci étant dit, pour ceux qui n’ont pas encore Git sur leur système, vous pouvez tout de même utiliser le projet. 

Pour cela, commencer par vous rendre à cette adresse : 

https://github.com/FabRiviere/AW-ShoppingCart_JS

Téléchargement du repository en fichier .zip

Ensuite, vous allez cliquer sur le bouton vert indiquant “<> Code”, en haut à droite. 

Dans la liste de ce bouton, vous avez un sous-menu appelé “Download ZIP”. Cliquez sur ce choix. Une fenêtre d’explorateur s’ouvrira alors pour vous demander où enregistrer ce dossier ZIP. Enregistrez le, où bon vous semble. Ensuite il faudra extraire ce dossier ZIP, en faisant un clic droit dessus, et en choisissant “Extraire vers”.

Ouvrez ensuite ce dossier extrait et copiez tous les dossiers et fichiers qu’il contient. 

Ouvrez ensuite le dossier AW-E-commerce-JS, depuis votre explorateur de fichiers. A l’intérieur, créez un nouveau dossier que vous nommez AW-ShoppingCart_JS, et collez y les fichiers et dossiers copiés précédemment. 

Ouvrez un terminal depuis le dossier AW-E-commerce-JS, et depuis la ligne de commande, entrez dans votre nouveau dossier AW-ShoppingCart_JS en tapant : 

				
					cd AW-ShoppingCart_JS

				
			

Votre ligne de commande doit maintenant se finir par …./E-commerce-Javascript/AW-ShoppingCart_JS/

Entrez ensuite dans le dossier du backend en tapant :

				
					cd back

				
			

Installer maintenant le serveur backend en tapant : 

				
					npm install

				
			

L’installation va prendre quelques temps, en fonction de votre système, de votre connexion, etc. 

Une fois l’installation terminée, lancer le serveur backend de l’application en tapant : 

				
					nodemon server

				
			

Le serveur backend est lancé. Maintenant nous pouvons lancer le frontend en utilisant l’extension de VSCode ”LiveServer” en appuyant sur l’icône Go Live, située en bas à droite de VSCode. Ou sinon en faisant un clic droit sur le fichier index.html et ensuite Open with Live Server.

L’application frontend doit s’afficher correctement. Pour le moment, nous n’aurons que les éléments d’en-tête et pied de page, car c’est justement notre mission de devoir peupler ces pages dynamiquement. 

Image du frontend du projet initial
Page d'accueil du frontend du projet initial

Avant de se lancer dans notre code

Avant de nous lancer dans le code, je veux juste indiquer que le travail qui nous est demandé dans ce projet, est d’implémenter les éléments dynamiquement. 

La notion de dynamique signifie que nous allons devoir implémenter des fonctions pour injecter de nouveaux éléments, tels que des balises, du texte, des images, etc. dans les balises HTML déjà existantes. En d’autres termes, nous allons devoir modifier le DOM.

Qu’est ce que le DOM ?

Le DOM pour Document Object Model ou en français, le modèle objet de document.

Il représente la structure d’un document sous forme d’un arbre, et définit la façon dont cette structure peut être manipulée par les programmes en termes de style et de contenu.

Il faut voir le DOM comme une représentation de notre page Web en Objet.

Le modèle objet de document proposé par le DOM fourni une autre manière de représenter, stocker et manipuler ce même document. Le DOM est une représentation entièrement orientée objet de la page Web, et peut être manipulé à l’aide d’un langage de script comme JavaScript.

À l’origine, JavaScript et le DOM étaient fortement liés, mais ils ont fini par évoluer en deux entités distinctes.

Le DOM n'est pas un langage de programmation

Le DOM n’est pas un langage de programmation, mais sans lui le langage JavaScript n’aurait aucun modèle ni aucune notion des pages Web, des documents XML, et des éléments pour lesquels il est généralement utilisé. Chaque élément d’un document, que ce soit le document lui-même, ses en-têtes, les tableaux internes au document, les en-têtes de colonnes et le texte contenu dans les cellules de ces tableaux, fait partie du modèle objet de document (DOM) de ce document. Tous ces éléments sont donc accessibles et peuvent être manipulés à l’aide du DOM et d’un langage de script comme JavaScript.

Je ne vais pas m’étendre plus sur le DOM, et nous verrons les méthodes que nous avons à notre disposition en codant le projet.
Si vous voulez en savoir plus, n’hésitez pas à aller voir la bible MDN sur le DOM.

Prendre en main les maquettes HTML / CSS

Afin d’implémenter nos fonctions qui modifieront le DOM, il est primordial de regarder la structure de nos pages HTML. De cette structure va dépendre les éléments à mettre en place et à ajouter au DOM.

Fichier index.html

Dans le fichier index.html, nous pouvons voir que nous devons implémenter un lien contenant un article constitué d’une image du produit, d’un titre H3 qui sera le nom du produit et d’un paragraphe pour la description du produit.

Nous allons donc récupérer une liste de produits, et chaque produit sera présenté avec la même structure

Nous pouvons voir également que cet élément sera à implémenter dans une section qui a l’id “ items”.

Fichier product.html

 Sur cette page, nous avons un article qui est constitué de plusieurs div, dans lesquelles, nous devrons ajouter l’image du produit, le nom du produit, la description, et également un select où nous devrons placer les options de couleurs. et nous avons aussi un input pour la quantité et un bouton pour ajouter le produit au panier

Nous allons devoir également nous conformer aux spécifications techniques et fonctionnelles concernant les options et quantités de produits que nous mettrons dans le panier. Nous reviendrons sur ce point en codant. 

Fichier cart.html

Ici, nous avons une section avec l’id “cart__items”, qui devra contenir tous les produits que nous aurons mis dans le panier. Comme pour ma page d’accueil (index.html), tous les produits auront la même présentation. A savoir, un article contenant l’image, le nom, la couleur, la quantité du produit, ainsi qu’un élément pour supprimer le produit (ici un paragraphe). 

A la suite de cette section, nous avons une div qui contiendra la quantité de produits dans le panier ainsi que le coût total du panier.

Et enfin, nous avons un formulaire afin que le client puisse passer commande de son panier. Nous avons vu dans les spécifications techniques que nous devons récupérer le contenu de chacun de ces champs afin de le transmettre à l’API, en même temps que les id de tous les produits contenus dans le panier. Ceci afin de recevoir un n° de commande.

Fichier confirmation.html

Cette page devra implémenter le n° de commande reçu de l’API. Et c’est la seule chose qui nous est demandée ici. Nous le verrons peut-être à l’occasion d’un bonus à la fin de ce tutoriel que nous pourrions faire plus..

Mais pour le moment, nous avons déjà beaucoup de travail à effectuer. 

Manipulation de l’API

Afin de savoir comment est conçue l’API, et ce que nous devons recevoir de sa part lors des requêtes que nous lui ferons, nous pouvons nous servir soit du navigateur ou alors d’un outil comme Postman. 

Nous avons vu dans les spécifications que nous avons 3 routes que nous devons appeler lors d’une requête spécifique. 

Il est indiqué également que l’url de l’API est http://localhost:3000/api/products.

C’est à dire que ce sera toujours par ceci que commencera l’adresse de requête de l’API

Recevoir tous les produits

La première route afin de recevoir la liste des produits de notre e-commerce est comme écrit dans les spécifications, un /. C’est-à-dire à la racine de l’url.

 L’adresse de l’api pour cette requête est donc :

http://localhost:3000/api/products

Tapons cette adresse dans le navigateur et voyons ce que l’API renvoie. 

Bien entendu, veillez à ce que le serveur backend soit bien lancé.

image de la liste de produits renvoyée par l'API
Liste de produits renvoyée par l'API au format JSON

Nous recevons bien la liste des produits dans un tableau au format JSON.

Sachez que vous pouvez avoir un autre aperçu de ce tableau que moi. En effet, dans Google Chrome, j’utilise l’extension JSON View afin d’afficher le JSON de cette manière. Vous pouvez aussi l’installer si vous le souhaitez, mais l’important est de bien recevoir les données de la part de l’API. Peu importe comment le format JSON s’affiche.

Recevoir un produit spécifique selon son id

La route pour recevoir un produit spécifique est comme écrit dans les spécifications, un /{product-id}. C’est-à-dire que nous devons remplacer {product-id}, par l’id d’un produit présent dans la liste. Je copie donc un id d’un produit présent dans la liste, et je le colle après avoir mis un / derrière products

 L’adresse de l’api pour cette requête est donc par exemple pour le 1er produit :

http://localhost:3000/api/products/10fc43gt5464asr87try964r4nbg65d4

Après avoir saisi cette adresse dans le navigateur : 

Image de réception d'un produit avec son id en paramètre de requête
Réception d'un produit avec son id en paramètre de requête

Nous recevons bien le produit ayant l’id correspondant à l’id demandé dans la requête. 

Recevoir le n° de commande

La dernière route que nous aurons à interroger est celle qui permettra de recevoir le n° de commande

Les spécifications nous indique que l’adresse sera : 

http://localhost:3000/api/products/order 

Saisissons cette adresse dans le navigateur : 

image de la réponse de l'API lors de la requête GET sur order
Réponse de l'API lors de la requête GET sur order

Ici, nous recevons un objet JSON mais vide

C’est normal, car rappelez vous qu’ici, la requête attendue n’est pas une requête de type GET mais une requête de type “POST” dans laquelle nous devrons envoyer dans le corps de la requête un objet contact, et un tableau products contenant tous les id des produits. Cette requête de type GET n’est donc pas appropriée.

Ceci étant fait, il est temps de passer au code, je pense.

Insérer les produits dynamiquement sur la page d’accueil

Pour implémenter les fonctions, nous utiliserons le langage et des fichiers JavaScript. Nous allons donc créer un nouveau dossier dans le dossier “front”que nous nommons “js”. A l’intérieur de ce dossier, créons un nouveau fichier que nous nommons “index.js”, car ce script est déjà indiqué dans notre fichier index.html (tout en bas).

Nous l’avons vu, la page d’accueil recevra la liste des produits de notre e-commerce. 

Mais comment aller chercher ces produits dans notre API ? 

Aller chercher en anglais se dit fetch ! Et ça tombe bien car il existe une  API nommée fetch qui fournit une interface JavaScript pour l’accès et la manipulation des parties du pipeline HTTP, comme les requêtes et les réponses. Cela fournit aussi une méthode globale fetch() qui procure un moyen facile et logique de récupérer des ressources à travers le réseau de manière asynchrone. 

L’utilisation la plus simple de fetch() prend un argument — le chemin de la ressource que nous souhaitons récupérer — et retourne une promesse (promise) contenant, en réponse, un objet (de type Response).

Nous allons donc pouvoir avec cette méthode, faire appel à l’adresse de l’API et récupérer les données au format JSON par retour des promesses.

Place au fetch..

Nous pouvons donc commencer à déclarer une constante url correspondant au endpoint (adresse de la route) de notre requête GET

				
					// URL de l'API
const url = 'http://localhost:3000/api/products/';

				
			

Ensuite codons notre méthode fetch()

				
					fetch(url)
    .then((response) => response.json())
    .then((data) => {
        addCards(data);
    })
    .catch((error) => {
        alert(
            "Attention votre serveur Node n'est pas lancé !"
        );
    });

				
			

Ci-dessus, je passe donc le chemin de la ressource en argument de la méthode :

				
					fetch(url)
				
			

Ensuite par retour de promesse, nous recevrons une réponse de type http. Je demande donc à l’aide de la méthode json() d’extraire cet objet Response au format JSON

				
					.then((response) => response.json())
				
			

Et nous allons ensuite récupérer les données de notre objet JSON, et nous allons appeler une fonction que nous nommons addCards(), par exemple, à laquelle nous passerons les données en attribut. Bien entendu, nous devons coder cette fonction addCards(data) par la suite.

				
					.then((data) => {
        addCards(data);
    })

				
			

Nous mettons enfin un .catch qui renverra une erreur au cas où le serveur de l’API ne soit pas lancé, ou que fetch() ne fonctionne pas. Et nous utilisons la méthode alert(), afin d’avoir une fenêtre modale qui s’ouvre pour nous indiquer l’erreur que nous avons mis en paramètre. 

				
					.catch((error) => {
        alert(
            "Attention votre serveur Node n'est pas lancé !"
        );
    });

				
			

Il nous reste maintenant à implémenter notre fonction addCards(data) déclarée dans la promesse. 

Fonction addCards() - Affichage de tous les produits

Je commence donc par déclarer ma fonction, en lui passant les données récupérées en attribut (que nous avons nommé data) : 

				
					// Affichage des articles en page d'accueil
function addCards(data) {

}

				
			

Boucle for

Nous avons vu précédemment les données que nous allons recevoir sera la totalité de nos produits. Hors ici, nous allons coder la structure d’un produit et répéter cette structure pour chaque produit. Nous avons donc besoin d’une boucle : 

				
					function addCards(data) {
    for (product of data) {

    }
}

				
			

Je créai donc une boucle for que l’on peut traduire par : pour chaque produit (product) contenu dans ma liste de produits(data)

getElementById()

Je dois ensuite regarder où je dois implémenter mes produits dans ma page HTML. Là aussi, nous l’avons vu, dans ma section portant l’id  “items”

Je vais donc déclarer une variable correspondant à cette section, et comme cette section ne changera jamais, plutôt qu’une variable, je déclare une constante

Je peux récupérer cette section en utilisant ce que nous appelons une méthode d’instance sur le DOM. Il existe beaucoup de méthodes d’instance mais comme ici je veux cibler un élément (la section) par son id, j’utiliserai donc la méthode getElementById(). Je déclare donc ma constante ainsi : 

				
					// Affichage des articles en page d'accueil
function addCards(data) {
    for (product of data) {
        const card = document.getElementById("items");

    }
}

				
			

.innerHTML

Nous allons ensuite utiliser une propriété d’instance du DOM qui est innerHTML

Cette propriété innerHTML définit la syntaxe HTML décrivant les descendants de l’élément. Nous allons donc écrire que les descendants de notre section ayant l’id “items” correspondent à, comme ceci :

				
					// Affichage des articles en page d'accueil
function addCards(data) {
    for (product of data) {
        const card = document.getElementById("items");

        card.innerHTML += 
    }
}

				
			

Notez que nous avons mis les signes += pour dire que nous ajoutons directement le code à suivre à notre élément. Nous verrons, cet opérateur += , plus en détails dans la suite de cette série.

Variables pour les propriétés du produit

Ensuite nous devons ajouter les balises qui ont été mises en commentaires dans le fichier de la page d’accueil(index.html), par le développeur frontend. Mais afin de pouvoir ajouter pour chaque produit son nom, son image, sa description et son id, nous utiliserons des variables. Une variable correspond à une propriété de notre objet comme par exemple product.name ou product.imageUrl, etc.

Backticks

Pour pouvoir utiliser des variables à l’intérieur de code HTML, nous devons ouvrir en premier lieu les backticks. Le backtick correspond à un accent grave, et autrement dit en appuyant simultanément sur les touches AltGr + 7 d’un clavier AZERTY, suivi par l’appui sur la touche espace. Et c’est donc à l’intérieur de ces backticks que nous allons coder notre HTML intégrant nos variables. Les variables pour qu’elles soient lues à l’intérieur de nos backticks s’écriront sous cette forme : ${product.name} par exemple.

				
					function addCards(data) {
    for (product of data) {
        const card = document.getElementById("items");

        card.innerHTML += `
                <a href="./product.html?_id=${product._id}">
                  <article>
                    <img decoding="async" src="${product.imageUrl}">
                    <h3 class="productName">${product.name}</h3>
                    <p class="productDescription">${product.description}</p>
                  </article>
                  </a>
            `;
    }
}

				
			

Vous pouvez voir également que pour faire le lien de chaque article, j’ai utilisé l’attribut href auquel correspond la page product.html suivi de son _id que je passe en paramètre de la requête. Ceci va nous être très utile pour la suite du projet.

Notre fonction addCards() est maintenant terminée, et si tout fonctionne comme il faut, on doit maintenant voir la liste de nos produits affichée sur la page d’accueil

Image de la page d'accueil du projet avec affichage des produits dynamiquement avec JavaScript
Page d'accueil du projet, avec affichage de la liste des produits, dynamiquement avec JavaScript

Faire le lien entre un produit de la page d’accueil et la page Produit

Comme je vous l’ai dit, j’ai anticipé cette étape en ajoutant le href dans la balise <a> de mes produits. Cela signifie que ce lien et donc le href du produit correspond à ceci :

				
					<a href="./product.html?_id=${product._id}">

				
			

href=

  • ./product.html => nous indiquons que le lien enverra vers la page product.html. Et comme cette page est dans le même dossier que ma page index.html ou je suis actuellement, je note donc ./ devant. Si la page product.html serait dans un autre dossier, il faudrait ici indiquer le chemin exact depuis notre page actuelle.  
  • ?_id=${product._id} => ici nous ajoutons un paramètre dans l’url de la requête qui correspond à l’_id auquel nous donnons comme valeur l’id du produit spécifique que nous ciblons

En codant ce lien ainsi, cela nous permettra d’utiliser l’interface URLSearchParams

L’ URLSearchParams interface définit des méthodes utilitaires pour travailler avec la chaîne de requête d’une URL.

Récupérer l’id du produit à afficher

Comme nous allons travailler sur un produit spécifique, cela se passe sur la page (fichier) produit.html. Nous devons donc créer un nouveau fichier JavaScript que l’on nommera “product.js”, dont le script est déjà implémenté dans le fichier HTML. 

L ‘URLSearchParams va donc nous permettre de travailler avec l’url de la requête.

Nous utiliserons donc le constructeur de cette interface pour récupérer une nouvelle instance de l’objet.

Et nous devons également utiliser l’interface location suivie de sa propriété search.

La location.search est une chaîne de recherche, également appelée chaîne de requête. C’est-à-dire une chaîne contenant un ‘?‘ suivi des paramètres de l’URL.

Et pour nous en servir plus facilement nous allons utiliser une constante qui correspondra à une instance de cette interface. pour cela nous codons :

				
					// Constante nécessaire à la récupération de la chaîne de requête et paramètres de l'URL
const searchParams = new URLSearchParams(location.search);

				
			

Maintenant que nous avons cette nouvelle instance de la chaîne de requête, nous devons lui indiquer quel paramètre nous souhaitons récupérer.

searchParams.get()

Pour cela, nous utilisons la méthode get() de URLSearchParams qui renverra la première valeur associée au paramètre de recherche donné.

Nous pouvons donc coder pour récupérer l’id du produit :

				
					// On récupère l'id du produit
const newId = searchParams.get('_id');


				
			

Maintenant que nous avons récupéré l’id de l’article que nous souhaitons afficher, nous pouvons donc dire que cette nouvelle url correspondra à une nouvelle constante :

				
					// Appel de l'API suivant l'id de l'article
const newUrl = `http://localhost:3000/api/products/${newId}`;
				
			

Pensez bien à ouvrir et fermer les backticks qui entourent l’url. 

Et bien ceci étant fait, il nous reste à aller chercher le produit défini par son ID.

Insérer un produit et ses détails dans la page Produit

Aller chercher ? ça vous dit quelque chose ? En anglais : Fetch

En avant pour le fetch()

				
					// Fetch pour récupérer le produit
fetch(newUrl)
    .then((response) => response.json())
    .then((data) => {
        const product = data;
        addCard(product);
   
    });

				
			

Ci-dessus rien de plus, pour l’instant, que le premier fetch(). Nous lui passons notre constante newUrl en attribut, et nous indiquons que nos données seront nommées product. Ensuite nous passons cette constante product en attribut d’une fonction que nous appelons addCard()et que nous allons devoir coder.

Fonction addCard() - Ajout d’un produit

Pour ajouter un produit dynamiquement au DOM, nous allons comme nous l’avons fait pour la liste de produits, cibler les balises déjà présentes dans notre fichier product.html

Ensuite nous utiliserons la propriété d’instance du DOM innerHTML et définir chaque propriété de l’objet (le produit) avec des variables. Voici la fonction :

				
					// Fetch pour récupérer le produit
fetch(newUrl)
    .then((response) => response.json())
    .then((data) => {
        const product = data;
        addCard(product);
   
    // Affichage du produit spécifique sur la page produit (product.html)
    function addCard(product) {
        const productImage = document.querySelector('.item__img');
        productImage.innerHTML += `<img decoding="async" src="${product.imageUrl}" alt="${product.altTxt}">`;

        const productName = document.getElementById('title');
        productName.innerHTML += `${product.name}`;

        const productPrice = document.getElementById('price');
        productPrice.innerHTML += `${product.price}`;

        const productDescription = document.getElementById('description');
        productDescription.innerHTML += `${product.description}`;

        addColors(product);
     };
   });

				
			

Ci-dessus, nous ciblons chaque champ de notre page product.html. Nous déclarons une constante dans laquelle nous récupérons l’élément ciblé grâce à la méthode d’instance getElementById()

Notez que pour l’image, nous récupérons l’élément ciblé qui a la classe .item__img avec la méthode querySelector(‘.item__img’). Nous aurions pû être tenté de cibler l’élément également avec la méthode getElementsByClassName(‘item__img).Mais c’est une fausse bonne idée ! 😉 Je vous explique brièvement pourquoi, tout de suite après. 

Enfin la dernière ligne de notre code fait appel à une nouvelle fonction que j’ai nommée addColors(). En effet, pour injecter les options de couleurs nous aurons besoin de cette fonction.

getElementById(), getElementsByClassName(), querySelector()

Petite parenthèse pour savoir comment utiliser au mieux ces méthodes d’instance : 

  • getElementById : La méthode renvoie un objet Élément représentant l’élément dont la propriété id correspond à la chaîne de caractères spécifiée.
  • getElementsByClassName :  il retournera uniquement les éléments qui sont les descendants de l’élément racine spécifié avec les noms de classes donnés.
  • querySelector : Retourne le premier Élément dans le document correspondant au sélecteur – ou groupe de sélecteurs – spécifié(s), ou null si aucune correspondance n’est trouvée. 

Attention lorsque vous utilisez querySelector a bien lui indiquer s’ il s’agit d’une classe ou d’un id. Avec un . ou un # pour indiquer si c’est une classe ou un id

Ciblage de sélecteurs avec querySelector

Mais la plus grande différence est qu’avec querySelector , nous pouvons cibler des sélecteurs CSS, ce qui n’est pas le cas avec les 2 autres. Et dans le code de notre product.html, l’élément ayant la class “item__img” est bien un sélecteur CSS. Il est utilisé, au passage, avec la méthodologie BEM.  

Essayez de mettre un getElementsByClassName à la place, et vous verrez que l’image ne s’affiche pas plus. 

Bon à savoir également, querySelector renverra le 1er élément du document ayant cette classe. Si nous devons cibler plusieurs éléments ayant la même classe, nous utiliserons alors querySelectorAll, pour recevoir la liste des éléments.

Comme toujours, n’hésitez pas à aller voir la bible MDN pour voir et comprendre toutes les différences entre getElementById, getElementsByClassName, et querySelector. 

Ceci étant, je ferme la parenthèse, et continuons le code. 

Fonction addColors() - ajout des options de couleurs

Dans notre fichier product.html, nous voyons que le choix des options de couleurs se fait dans un select qui à l’idcolors”. Nous avons vu également lorsque nous faisons appel à l’API que les couleurs d’un produit sont renvoyées dans un tableau nommé “colors”. 

Et ce que nous avons besoin de faire ici, et d’indiquer que nous voulons que pour chaque couleur faisant partie du tableau “colors” du produit, une option de ce select doit lui être attribuée.Nous devons donc itérer sur chaque couleur, et pour faire cela, nous avons donc besoin d’une boucle. Enfin chaque couleur itérée sera ajoutée à une option du select. Nous codons donc ainsi : 

				
					// Fonction pour choix des couleurs
function addColors(product) {
	// on cible le select
    let options = document.getElementById('colors');
	// pour chaque couleur du tableau colors du produit
    for (let colors of product.colors) {
        // on ajoute une option ayant la valeur de la couleur
        options.innerHTML += `<option value="${colors}">${colors}</option>`;
    }
};

				
			

A ce stade, le produit sur lequel nous cliquons depuis la page d’accueil s’affiche correctement sur notre page product.html.

Nous pouvons choisir sa quantité et une option de couleur parmi celles proposées.

Il nous reste sur cette page à coder la fonctionnalité pour ajouter ce produit au panier.

Image de l'affichage d'un produit sur la page produit
Affichage d'un produit sur la page produit

Ajouter des produits dans le panier

Attention, car nous avons vu dans les spécifications que nous devons prendre en compte le fait que le produit peut être mis au panier plusieurs fois, et selon l’option de couleur, il apparaitra sur une ou plusieurs lignes.

Nous aurons besoin de créer des instances de l’objet produit afin de récupérer les bonnes données. Pour créer des instances nous allons avoir besoin d’une représentation de notre objet produit. Nous allons donc commencer par créer une classe Product définissant les propriétés de notre objet. 

Création de la class Product

La création d’une classe permettra d’utiliser une syntaxe plus simple pour créer des objets et manipuler l’héritage.

En réalité, les classes sont juste des fonctions spéciales. Ainsi, les classes sont définies de la même façon que les fonctions : par déclaration, ou par expression.

Par contre, il faut savoir que si les fonctions peuvent remonter dans le code, ce n’est pas le cas d’une classe. C’est-à-dire que nous devons instancier une classe avant de faire appel à son instance. C’est pourquoi, je vais la mettre en haut de mon code, avant la constante searchParams.

Généralement, je mets mes classes dans un autre fichier, mais ici étant donné que nous devons tout coder dans le fichier product.js qui est le seul déclaré en script sur la page product.html. Nous verrons cela dans le bonus. 

Tout le code que nous créons dans ce fichier sera donc intégré à la promesse de notre fetch. 

Pour créer et initialiser les objets créés avec une classe, nous devons utiliser une méthode spéciale appelée constructor().

Nous codons donc la class ainsi : 

				
					//  Création de la classe produit
class Product {   
    constructor(id,name,description,price,colors,imageUrl,altTxt,quantity)  {
            this.id = id;
            this.name = name;
            this.description = description;
            this.price = +price;
            this.colors = colors;
            this.imageUrl = imageUrl;
            this.altTxt = altTxt;
            this.quantity = +quantity;
    }
}

				
			

Rien de sorcier ici, nous déclarons notre classe avec chacune de ces propriétés que nous mettons dans la méthode constructor et indiquons ensuite à quoi correspond ces propriétés. 

Noter que devant “price” et “quantity”, nous mettons le signe +, ceci afin d’indiquer qu’il s’agit d’une propriété de  type number.

Ceci étant fait, nous allons maintenant pouvoir nous occuper des actions à faire pour ajouter un produit au panier.

Ecoute du bouton Ajouter au panier

Nous allons donc devoir définir ce qui se passe lorsque nous cliquons sur le bouton “Ajouter au panier”. 

La première chose à faire est de cibler ce bouton dans notre fichier product.html et de lui allouer une constante.

				
					 let btnAddToCart = document.getElementById('addToCart');
				
			

EventTarget()

Ensuite, nous devons en quelque sorte écouter ce qui se passe lorsque nous cliquons sur ce bouton. En effet, lorsque nous cliquons sur ce bouton, nous devons envoyer le produit dans le panier? C’est ce que nous appelons un événement. En JavaScript, un événement se gère avec l’interface EventTarget . Une des méthodes de EventTarget est la méthode addEventListener()

addEventListener()

La méthode addEventListener() attache une fonction à appeler chaque fois que l’événement spécifié est envoyé à la cible

Autrement dit, pour notre cas, nous créons une fonction qui enverra le produit dans le panier à chaque fois que nous ferons un clic sur le bouton “Ajouter au panier”. 

Nous déclarons donc l’écoute de l’événement sur notre bouton ainsi : 

				
					btnAddToCart.addEventListener("click", () => {

});

				
			

Nous commençons par écrire notre constante “btnAddToCart” , auquelle on ajoute la méthode addEventListener(). On lui précise ce que l’on va écouter, et ici, c’est le “click” sur le bouton. Ensuite on met en 2ème attribut,  une fonction fléchée où nous indiquerons les actions à réaliser. 

Fonction à l’intérieur de l’écoute

Nous devons récupérer la valeur des inputs contenant l’option de couleur et la quantité. Nous ciblons donc ces éléments depuis notre product.html avec :

				
					let colors = document.getElementById('colors');
let quantity = document.getElementById('quantity');

				
			

Maintenant lorsque nous allons cliquer sur le bouton d’ajout au panier, nous devons récupérer le produit spécifié. Nous créons donc une nouvelle instance de notre objet produit (notre classe) : 

				
					let objectProduct = new Product(
    newId,
    product.name,
    product.description,
    product.price,
    colors.value,
    product.imageUrl,
    product.altTxt,
    quantity.value,
);

				
			

L’instance de notre produit est maintenant créée. 

Nous arrivons maintenant au besoin de gérer les 2 cas possibles pour les produits identiques qui sont ajoutés au panier : 

  • Si le produit a la même couleur, on doit incrémenter sa quantité uniquement sans créer un nouveau produit.
  • Si le produit n’a pas la même couleur, alors on ajoute ce produit au panier

Afin de pouvoir comparer nos produits, et aussi pour anticiper le fait que nous aurons besoin de récupérer les produits ajoutés au panier, nous devons utiliser le localStorage

localStorage est une propriété d’instance de l’API Web Storage. Cette API fournit des mécanismes par lesquels les navigateurs peuvent stocker des paires clé / valeur, d’une manière beaucoup plus intuitive que l’utilisation de cookies.

Accès au localStorage

Petite parenthèse afin de comprendre que nous pouvons accéder au localStorage des pages Web en ouvrant les outils de navigateur. Pour ouvrir les outils de votre navigateur, vous pouvez soit appuyer sur la touche F12 de votre clavier, lorsque vous êtes sur la page web, ou alors faire un clic droit, et cliquer sur “Inspecter”

Le stockage local se situe ensuite dans l’onglet Appli de ces outils.

Image de l'emplacement du localStorage dans les outils de navigateur
Emplacement du localStorage dans les outils de navigateur

Ci-dessus, nous sommes sur Google Chrome, mais ces outils existent sur tous les navigateurs. 

Je ferme cette parenthèse et continuons notre code. 

Déclarer le panier

Notre panier par définition sera un array puisqu’il pourra contenir plusieurs produits.

Nous pouvons déclarer un constante indiquant ceci et comme nous nous en servirons par la suite, nous allons la déclarer tout en haut de notre fichier product.js

				
					const cart = [];
				
			

Pour l’instant, il s’agit donc d’un tableau(array) vide. 

Ensuite, voici ce que nous allons coder :

				
					let isInCart = false;
let indexModification;
for (products of cart) {
   switch(products.colors) {
     case objectProduct.colors:
       isInCart = true;
       indexModification = cart.indexOf(products);
    }
}

				
			

Ci-dessus, on commence par déclarer une variable “isInCart” avec comme valeur false. Puis une variable “indexModification”. 

Boucle for et switch

Ensuite, nous créons une boucle for afin de parcourir chaque produit présent dans le panier. Nous utilisons ensuite dans cette boucle une instruction switch.

L’instruction switch évalue une expression et, selon le résultat obtenu et le cas associé, exécute les instructions correspondantes.

Nous donnons donc “products.colors” comme propriété à évaluer dans le switch, et nous associons le cas ou “objectProduct.colors”, qui représente la couleur de l’instance de notre produit, est présent. Dans ce cas, nous passons la valeur de la variable “isInCart” à true, et “indexModification” correspondra à cart.indexOf(products).Ce dernier correspondant à l’emplacement du produit que nous cherchons dans le panier. Pour cela nous avons fait appel à la méthode indexOf().

indexOf()

La méthode indexOf() renvoie le premier indice pour lequel on trouve un élément donné dans un tableau.

indexOf() compare l’élément recherché aux éléments contenus dans le tableau en utilisant une égalité stricte (la même méthode utilisée par l’opérateur ===).

Conditions d'affichage d'un produit identique selon les options de couleurs

Conditions if/else

Une fois donné ces instructions à notre switch, nous devons maintenant créer des conditions afin de décider de ce que nous faisons en fonction de si le produit identique avec la même couleur est présent dans le panier ou non .

				
					// Si déjà présent, on incrémente la quantité
 if (isInCart) {
  cart[indexModification].quantity = +cart[indexModification].quantity + +objectProduct.quantity;
   localStorage.setItem("products", JSON.stringify(cart));
                   
  // Sinon on ajoute le produit au localStorage
 } else {
    cart.push(objectProduct);
    localStorage.setItem("products", JSON.stringify(cart));
                       
 }

				
			

Ci dessus, nous disons donc que : 

  •  si présent dans le panier : if (isInCart)
    • alors, la quantité du produit défini par sa position dans le tableau du panier sera modifiée, pour correspondre à cette même quantité, plus la quantité du produit actuel que l’on ajoute. Et on met ensuite à jour,  ce produit avec sa nouvelle quantité dans le stockage local.
  • Sinon (sous entendu non présent dans le panier) :
    • alors on envoie le produit dans le panier = cart.push(objectProduct).
    • Et on l’enregistre dans le localStorage. 

Vous remarquez que nous avons utilisé la méthode setItem() de localStorage afin d’enregistrer les produits dans le panier. Nous lui passons en 1er paramètre une string (chaîne de caractères) qui est le nom que l’on souhaite donner à l’objet stocké, et en 2ème paramètre on lui passe la valeur de cet objet. Ici, nous faisons également appelle à la fonction JSON.stringify(cart) afin de convertir  les valeurs JavaScript en chaîne au format JSON

Nous avons pratiquement terminé, et à ce stade, notre code fonctionne déjà correctement. 

Tests de l’ajout au panier

Si nous sélectionnons une couleur et une quantité à notre produit et que nous appuyons sur le bouton Ajouter au panier, nous pouvons voir que pour le moment rien ne se produit.

Pourtant si nous nous rendons dans le localStorage de nos outils de navigateur, nous pouvons voir que nous avons bien stocker notre produit dans le panier.

Si nous ajoutons de nouveau ce produit avec la même couleur, alors nous ne créons pas un nouveau produit mais la quantité du produit a augmentée

Si nous ajoutons de nouveau le produit mais avec une couleur différente, alors nous avons bien un nouveau produit dans les produits de notre panier

Tout fonctionne correctement.

Améliorations

Comme nous allons avoir besoin de notre constante cart, plus tard dans le développement de notre projet, nous devons modifier cette constante en la déclarant ainsi :

				
					// cart qui est un tableau : l'item products du LS ou un tableau vide
const cart = JSON.parse(localStorage.getItem("products")) || [];

				
			

Ici, contrairement à la méthode JSON.stringify(), nous utilisons JSON.parse() pour transformer la valeur de la chaîne JSON en JavaScript. Ensuite, nous faisons appelle à la méthode getItem() du localStorage, pour récupérer les objets présents dedans.

Obligation de renseigner les champs quantité et couleur

Une autre amélioration, que nous pouvons faire, est peut être de gérer l’impossibilité de mettre un produit n’ayant pas de valeurs pour la quantité ou la couleur. Vous pouvez essayer de votre côté, car à l’heure actuelle, rien ne nous empêche d’ajouter un produit sans avoir sélectionné une couleur ou une quantité. Ce qui n’est pas logique. Je vais donc gérer cela avec des conditions, comme ceci : 

				
					if(colors.value == "") {
   alert("vous devez sélectionner une couleur");
}else if(quantity.value == 0) {
   alert("Vous devez indiquer une quantité")
}else {
  let isInCart = false;
  let indexModification;
  for (products of cart) {
    switch(products.colors) {
      case objectProduct.colors:
       isInCart = true;
       indexModification = cart.indexOf(products);
     }
   }
   
  // Si déjà présent, on incrémente la quantité
  if (isInCart) {
  cart[indexModification].quantity = +cart[indexModification].quantity + +objectProduct.quantity;
  localStorage.setItem("products", JSON.stringify(cart));
                   
  // Sinon on ajoute le produit au localStorage
   } else {
      cart.push(objectProduct);
      localStorage.setItem("products", JSON.stringify(cart));       
   }
}

				
			

Rien d’extraordinaire ici. Nous déclenchons des alertes si un des 2 champs de quantité ou de couleur est vide ou null, sinon on exécute le code normalement.

Avertissement de l’ajout d’un produit au panier

Pour moi, il serait bien également d’être averti lorsqu’un produit est bien ajouté au panier. Ajoutons simplement une alerte pour cela : 

				
					if(colors.value == "") {
   alert("vous devez sélectionner une couleur");
}else if(quantity.value == 0) {
   alert("Vous devez indiquer une quantité")
}else {
  let isInCart = false;
  let indexModification;
  for (products of cart) {
    switch(products.colors) {
      case objectProduct.colors:
       isInCart = true;
       indexModification = cart.indexOf(products);
     }
   }
   
  // Si déjà présent, on incrémente la quantité
  if (isInCart) {
  cart[indexModification].quantity = +cart[indexModification].quantity + +objectProduct.quantity;
  localStorage.setItem("products", JSON.stringify(cart));
                   
  // Sinon on ajoute le produit au localStorage
   } else {
      cart.push(objectProduct);
      localStorage.setItem("products", JSON.stringify(cart));       
   }
alert("Votre produit a bien été ajouté au panier");
}

				
			

Page produit fonctionnelle

Notre page produit est maintenant fonctionnelle. 

Nous avons vu comment récupérer un produit spécifique par rapport à son ID. Puis comment ajouter ce produit dynamiquement sur notre page, et comment pouvoir sélectionner les différentes options de couleurs

Enfin nous avons fait en sorte de créer un événement lors du clic sur le bouton “Ajouter au panier”, en y intégrant des conditions lors de la création d’une instance de notre objet produit. 

Nos produits s’ajoutent maintenant correctement dans le panier. Mais pour le moment, cela est invisible pour nos clients.  Nous allons donc pouvoir gérer le code nécessaire à notre page panier.

Afficher un tableau récapitulatif des achats dans la page Panier

La première chose à faire pour interagir avec notre page cart.html, nous devons créer un nouveau fichier dans notre dossier “js”. Nous le nommons donc “cart.js”. 

Pour afficher un récapitulatif des produits présents dans le panier, nous devons donc récupérer la liste des produits disponible dans le tableau products du localStorage.

				
					cart = JSON.parse(localStorage.getItem("products")) || [];
				
			

Nous allons ensuite parcourir ce tableau pour afficher chacun de ces éléments. 

Nous pouvons récupérer chacun des produits en faisant une boucle for :

				
					if (cart.length > 0) {
    for (product of cart) {
        displayCart(product)
    }
}

				
			

Ici, j’ai juste rajouter une condition if, pour indiquer que nous voulons afficher le panier seulement si celui-ci possède un nombre de produits supérieur à 0

Ensuite la boucle for pour parcourir le tableau, et nous passons comme paramètre “product of cart” pour récupérer la valeur des éléments. A ne pas confondre avec “products in Cart” où nous récupérons l’index des éléments

Nous faisons ensuite appel à la fonction displayCart(product) que nous devons coder à la suite :

Fonction displayCart(product)

				
					// Fonction pour afficher les produits du panier
function displayCart(product) {
    const indexProduct = cart.indexOf(product);  
    const productList = document.getElementById('cart__items');
 
    productList.innerHTML += `
                            <article class="cart__item" data-id="${product.id}">
                                <div class="cart__item__img">
                                <img decoding="async" src="${product.imageUrl}" alt="${product.altTxt}">
                                </div>
                                <div class="cart__item__content">
                                    <div class="cart__item__content__titlePrice">
                                        <h2>${product.name}</h2>
                                        <p>${product.price * product.quantity}</p>
                                    </div>
                                    <div class="cart__item__content__settings">
                                        <div><p>Color : ${product.colors}</p></br></div>
                                        <div class="cart__item__content__settings__quantity">
                                            <p>Qté : </p>
                                            <input type="number" class="itemQuantity" name="itemQuantity" min="1" max="100" value="${product.quantity}" data-index=${indexProduct} >
                                        </div>
                                        <div class="cart__item__content__settings__delete">
                                            <p class="deleteItem">Supprimer</p>
                                        </div>
                                    </div>
                                </div>
                            </article>
                                `;
}

				
			

Dans cette fonction comme d’habitude, nous commençons par cibler nos éléments HTML et nous définissons également une constante pour récupérer l’emplacement du produit spécifié dans le tableau, avec le méthode indexOf()

Nous ajoutons également à notre input quantity, l’attribut data-index auquel nous donnons comme valeur la constante  “indexProduct”.

Cela va nous servir pour la suite. 

Notre page panier est maintenant peuplée des produits présents dans le panier.

Image de l'affichage du récapitulatif des produits présents dans le panier sur la page panier
Affichage du récapitulatif des produits présents dans le panier sur la page panier

Nos produits sont bien présents, mais nous devons aussi afficher le nombre d’articles dans le panier ainsi que le prix total du panier. 

Fonction productsInCart() - Affichage du nombre d’articles dans le panier

				
					// Fonction pour affichage du nombre d'articles dans le panier
function productsInCart() {
    let productNumbers = document.getElementById('totalQuantity');
   
    if(cart.length > 0) {
        let productInCart = 0;
        for ( product of cart) {
            productInCart += product.quantity;
        }
        if (productNumbers != null) {
            productNumbers.innerHTML += `${productInCart}`;
        }
    }
}

				
			

Nous ciblons l’élément HTML à cibler pour ajouter le total de produits dynamiquement.

Ensuite, je mets une condition pour dire que j’exécute le code, uniquement s’ il y a des produits dans le panier. 

On définit ensuite une variable “productInCart” à laquelle nous donnons une valeur de 0 par défaut

Ensuite, nous parcourons notre tableau de produits, et nous disons que pour chaque produit, nous ajouterons la quantité du produit à la valeur de “productInCart”. On utilise pour cela l’opérateur affectation après addition. (voir explications ci-dessous).

Enfin, on s’assure que notre élément html n’est pas null, et donc qu’il existe bien. S’il existe alors nous utilisons la méthode innerHTML pour insérer la valeur de “productInCart”. Cette valeur correspond à la quantité totale de produits.

+= Opérateur d’affectation après addition

L’opérateur d’addition et d’affectation (+=) calcule la somme ou la concaténation (suivant le type) de ses deux opérandes puis affecte le résultat à la variable représentée par l’opérande gauche

Dans notre cas, il fera donc la somme car la quantité et “productInCart” sont de type number

Attention de ne pas voir cela comme représentant la quantité + 0. Ce sera le cas pour la première itération mais plus à partir de la seconde. 

En fait, lorsqu’on écrit productInCart += product.quantity, c’est comme si on écrivait productInCart = productInCart + product.quantity .

Pour bien comprendre, imaginons que nous avons 3 produits avec une quantité de 1  par produit. 

Au premier tour de boucle : 

productInCart = 0 + 1 = 1

Au second tour de boucle : 

productInCart = 1 + 1 = 2

Au troisième tour de boucle

productInCart  = 2 + 1 = 3

Appel de la fonction productsInCart()

Pour que le nombre de produits dans le panier apparaisse, nous devons appeler cette fonction, sinon rien ne sera affiché ! 

Nous pouvons faire cela dans la condition que nous avons déclarée  au début de notre fichier, mais en dehors de la boucle for. Comme ceci :

				
					if (cart.length > 0) {
    for (product of cart) {
        displayCart(product)
    }
    productsInCart();
}

				
			

Maintenant le nombre de produits dans le panier s’affiche.

Occupons nous maintenant d’afficher le prix total du panier.

Calcul et affichage du montant total du panier

Appel de la fonction productsInCart()

Avant d’afficher le prix total du panier, nous devons tout d’abord le calculer : 

				
					// calcul du total panier
function updateTotalCost () {
    let totalCart = 0;
    cart.forEach((product) => {
        totalCart = totalCart + (product.quantity * product.price);
    });
    return totalCart;
}

				
			

Dans cette fonction, nous déclarons une variable “totalCart” avec 0 comme valeur par défaut. Puis dans une boucle foreach(), on utilise une fonction fléchée pour qu’à chaque produit contenu dans le panier, on calcule “totalCart” qui sera égal à totalCart + la quantité multiplié par le prix.  On aurait pu là aussi utiliser l’opérateur +=. Mais pour plus de clarté, je vous ai détaillé le calcul.

Enfin on retourne le résultat et la valeur de “totalCart”. 

Notre calcul est maintenant fait. Il nous reste à nous occuper de l’affichage.

Fonction displayTotalCart() - Affichage du prix total

Pour afficher le prix total du panier, nous créons une nouvelle fonction :

				
					// Affichage du Total panier
function displayTotalCart() {
    const totalContent = document.getElementById('totalPrice');
    if (totalContent != null) {
        totalContent.innerHTML += updateTotalCost();
    }
}
				
			

Nous ciblons comme toujours notre élément HTML auquel nous faisons correspondre une constante. 

Ensuite je fais une condition afin de m’assurer que cet élément n’est pas null

Nous pourrions nous en passer, mais je vous expliquerai par la suite pourquoi nous devons le faire. 

Enfin dans cette condition nous ajoutons cet élément au DOM, avec innerHTML et nous lui donnons comme valeur ce que nous retourne la fonction updateTotalCost()

Et nous appelons ainsi la fonction de calcul dans la fonction d’affichage

Pour le moment, rien ne s’affiche et c’est bien normal puisque nous devons maintenant appeler la fonction displayTotalCart(). Et comme nous l’avons fait pour l’affichage de la quantité de produits, nous pouvons appeler cette fonction dans la condition déclarée en début de notre fichier. Toujours en dehors de la boucle for.

				
					if (cart.length > 0) {
    for (product of cart) {
        displayCart(product)
    }
    productsInCart();
    displayTotalCart();
}

				
			

Cette fois, le prix total du panier s’affiche bien. 

Nous pouvons ajouter un produit au panier, et nous verrons que la quantité totale de produits et le prix total du panier seront mis à jour automatiquement.

Gérer la modification et la suppression de produits dans la page Panier

Nous devons continuer notre développement de projet et nous charger de gérer la modification et la suppression d’un produit dans le panier. 

En effet, pour l’instant si nous modifions la quantité d’un produit , rien ne bouge, ou si nous cliquons sur le texte “Supprimer”, rien ne se passe mis à part une erreur dans la console. 

Gestion des quantités

Intéressons nous à la gestion des quantités. 

Je vous avoue que pour gérer les quantités concernant la modification des inputs de type number, j’ai pas mal galéré ! Cela aurait été beaucoup plus facile avec 2 boutons distincts. C’est-à-dire un bouton pour augmenter la quantité et un autre pour la réduire. Mais ici, nous avons un input de type number et il faut donc faire avec. 

Je vous donne l’événement d’écoute que j’ai créer et vous explique ensuite :

				
					// Fonction pour modification de la quantité de produits dans le panier
const inputList = document.querySelectorAll(".itemQuantity");
for (input of inputList) {
    let ind = input.getAttribute('data-index');
     //: Ecoute du clic sur les boutons de l'input type number
    input.addEventListener("click", e => {      
        const newValue = e.target.value;
        for (products of cart) {
            let index = cart.indexOf(products);
            qty = cart[index].quantity;
                if (index == ind && newValue > qty) {
                cart[index].quantity++;
                localStorage.setItem("products", JSON.stringify(cart));
                location.reload();
                } else if (index == ind && newValue < qty) {
                cart[index].quantity--;
                localStorage.setItem("products", JSON.stringify(cart));
                location.reload();
                } else {
                newValue == qty;
                }  
        }
    });  
}

				
			

Je commence donc par cibler les inputs pour gérer les quantités. Noter ici que j’utilise querySelectorAll() car nous devons cibler une liste d’éléments portant tous la même classe “.itemQuantity”. 

  • Ensuite j’utilise une boucle for, pour indiquer que ce que je ferai comme action sera pour chaque input de la liste des inputs. 
  • Je déclare une variable qui va récupérer la valeur de l’attribut data-index. C’est pour cela que j’avais déclaré cet attribut dans la balise input de l’HTML que nous avons injecté dynamiquement. Et nous lui avons donné comme valeur l’emplacement que le produit occupe dans le panier.
  • Je vais ensuite déclarer mon écoute du clic sur l’input avec la méthode addEventListener(). Je lui dit que j’écoute le clic, et lui passe en paramètre de fonction fléchée le “e” de event. J’aurai pu écrire event d’ailleurs. 
  • Je déclare ensuite une constante “newValue” ayant comme valeur la valeur de l’événement cible, ou autrement dit la nouvelle valeur de mon input
  • Ensuite je créai une boucle for pour parcourir chacun des produits.
  • Je  déclare que index est équivalent à l’emplacement du produit dans la liste de produits en me servant de nouveau de la méthode indexOf()
  • Je créai une variable qty correspondant à la quantité du produit ciblé que j’indique avec cart[index].quantity.

Conditions dans l’écouteur

  • Je finis ensuite en créant les conditions suivantes : 
    • Je vérifie si l’index de mon input est égal à l’index de mon produit et si la nouvelle valeur de mon input est supérieure à la quantité :
      • Si c’est le cas, alors j’incrémente la quantité du produit.
      • Je mets à jour la quantité du produit dans le localStorage.
      • J’actualise la page avec la méthode location.reload()
    • Sinon je vérifie si l’index de mon input est égal à l’index de mon produit et si la nouvelle valeur de mon input est inférieure à la quantité :
      • Si c’est le cas, alors je décrémente la quantité du produit.
      • Je mets à jour la quantité du produit dans le localStorage.
      • J’actualise la page avec la méthode location.reload()
    • Enfin, si je ne suis pas dans une des conditions précédentes, je dis que la nouvelle valeur est égale à la quantité actuelle. 

J’avoue qu’il y a peut être plus simple comme solution, mais l’important, comme toujours, est le résultat. Cela fonctionne. Maintenant lorsque j’augmente ou diminue la quantité d’un produit, le prix se met à jour automatiquement, la quantité et le prix total également. Et la quantité est mise à jour dans le localStorage

Gestion de la suppression d’un produit

Afin, de gérer plus simplement la suppression d’un produit de la liste de produit de notre panier, nous allons commencer par ajouter une fonction comme attribut à notre élément HTML. J’ajoute donc ceci à la balise p ayant la class “deleteItem” dans ma fonction displayCart()

				
					<p class="deleteItem" onclick="remove(${indexProduct})">Supprimer</p>

				
			

Nous ajoutons donc l’attribut onclick auquel nous passons comme valeur la fonction remove() ayant en paramètre l’emplacement du produit ciblé.

Et maintenant je peux coder la fonction remove() ainsi :

				
					// Fonction pour supprimer les produits du panier
function remove(index) {
    cart.splice(index, 1);
    localStorage.setItem("products", JSON.stringify(cart));
    location.reload();
    displayCart();
}

				
			

Nous utilisons pour supprimer le produit la méthode splice(). Cette méthode peut être utilisée pour supprimer ou pour ajouter des éléments à un tableau

En 1er attribut, nous lui donnons quel index ciblé dans le tableau, et en 2ème attribut, le nombre d’éléments à supprimer. Nous mettons ensuite à jour notre localStorage, et actualisons la page, puis nous relançons l’affichage de notre liste de produits dans le panier. Etant donné que nous avons passé le “indexProduct” comme paramètre de la fonction remove() dans le html, nous supprimerons le produit concerné par cet emplacement.

Maintenant si nous appuyons sur le texte “Supprimer”, le produit ciblé est bien supprimé de la liste et également du localStorage

Félicitations ! Nous avons maintenant une page produit fonctionnelle, acceptant la modification des quantités et la suppression d’un ou plusieurs produits. 

Il est temps de donner la possibilité à nos clients de passer commande ! 

Formulaire de commande

Une fois les produits validés dans le panier, le client doit, pour passer commande, remplir le formulaire mis à sa disposition dans la partie basse du panier. 

Nous avons vu dans les spécifications techniques et fonctionnelles que nous devons vérifier les données saisies par l’utilisateur avant l’envoi du formulaire.

Concernant l’envoi de ce formulaire, on nous a spécifié que la requête faite devra être de type “POST” et qu’elle devra en JSON contenir un objet contact et un tableau de produits constitué de chaînes de caractères représentant l’ID des produits

Alors essayons de voir cela maintenant.

Regex de validations

Pour valider les données transmises par l’utilisateur, nous utiliserons des expressions régulières, autrement appelées Regex ou RegExp, ou encore expressions rationnelles. Ces expressions sont des motifs utilisés pour correspondre à certaines combinaisons de caractères au sein de chaînes de caractères.

Je ne vais pas rentrer ici, dans le détails de construction d’une regex, et si vous le souhaitez vous pouvez en apprendre plus sur MDN ou encore sur ce cours

Je vais donc déclarer mes regex ainsi : 

				
					const regexName = /^(([a-zA-ZÀ-ÿ]+[\s\-]{1}[a-zA-ZÀ-ÿ]+)|([a-zA-ZÀ-ÿ]+))$/;
const regexCity = /^(([a-zA-ZÀ-ÿ]+[\s\-]{1}[a-zA-ZÀ-ÿ]+)|([a-zA-ZÀ-ÿ]+)){1,10}$/;
const regexMail = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]{2,}\.[a-z]{2,4}$/;
const regexAddress = /^(([a-zA-ZÀ-ÿ0-9]+[\s\-]{1}[a-zA-ZÀ-ÿ0-9]+)){1,10}$/;


				
			

Ecriture des regex

Tout d’abord, nous écrivons toujours la regex entre 2 slashs : / /

Ensuite nous écrivons, par exemple : 

  • ^ pour indiquer le début de la chaîne de caractères
  • [a-zA-ZÀ-ÿ]+ indique que nous acceptons les lettres de A à Z aussi bien en minuscule qu’en majuscule, et avec ou sans accent. Et que ces lettres peuvent être répétées(signe +).  
  • [\s\-]{1} signifie que nous acceptons ensuite un seul espace blanc ou un tiret .
  • [a-zA-ZÀ-ÿ] + signifie qu’ à la suite de l’espace blanc, plusieurs de ces lettres minuscules, majuscules, avec accents peuvent être dans la chaine. 
  • | [a-zA-ZÀ-ÿ] + correspond à une alternative. c’est-à-dire que cela peut être aussi une chaîne de caractères en minuscules, majuscules, avec accents mais sans espace. C’est pour cela que la regex précédente et cette alternative sont placées entre parenthèses. 
  • $ pour terminer la chaîne de caractères. 

Voilà un petit peu, pour quelques explications, mais encore une fois, ce tutoriel n’étant pas consacré aux regex, je ne vais pas m’étendre plus. 

Assurez- vous tout de même de bien écrire ces regex, car la moindre faute, espace ou oubli, ne permettra pas de valider les données saisies par l’utilisateur. 

Objet Contact

Nous allons avant tout devoir écrire le reste de notre code dans un événement

En effet, c’est lors du clic sur le bouton “Commander” que nous devrons transmettre les actions à réaliser et les données à envoyer. 

Nous devons donc tout de suite cibler notre bouton dans le HTML. Vous en avez maintenant l’habitude : 

				
					const order = document.querySelector('#order');
				
			

Nous pouvons ensuite déclencher l’écoute de ce bouton avec la méthode addEventListener()

				
					order.addEventListener("click", (event) => {
   
});

				
			

Nous l’avons dit, nous devrons transmettre un objet contact. Et comme indiqué dans les spécifications, celui-ci doit contenir les champs lastName, firstName, address, city et email.  Attention à bien écrire le code qui va suivre dans l’événement.

Nous déclarons donc ces champs, et nous leur donnons comme valeur, les champs du formulaire correspondants dans notre HTML, afin de récupérer ces données : 

				
					//; On rassemble les informations du formulaire de contact
let contact = {
    firstName: document.getElementById("firstName").value,
    lastName: document.getElementById("lastName").value,
    address: document.getElementById("address").value,
    city: document.getElementById("city").value,
    email: document.getElementById("email").value
}

				
			

Notez qu’ici, nous demandons à récupérer la valeur de chaque champ ( .value). 

Pour transmettre ces données, nous devons avant tout les valider. C’est à dire que nous devons créer une condition if/else. Cette condition doit être : 

  • Si (if) les données des champs transmises sont valides, on les transmet.
  • Sinon (else) on renvoie une erreur pour chaque champ. 

Pour valider les données par rapport aux expressions régulières que nous avons codé, nous pouvons utiliser la méthode test()

Méthode test de RegExp

La méthode test() vérifie s’il y a une correspondance entre un texte et une expression rationnelle. Elle retourne true en cas de succès et false dans le cas contraire.

Attention à bien écrire le code qui va suivre toujours dans l’événement.

Nous devons donc déclarer notre condition if ainsi :

				
					//; Contrôle de la validation du formulaire
    if (
        (regexName.test(contact.firstName) == true) &
        (regexName.test(contact.lastName) == true) &
        (regexAddress.test(contact.address) == true) &
        (regexCity.test(contact.city) == true) &
        (regexMail.test(contact.email) == true)
    ) {
       
    }

				
			

Ci-dessus nous disons donc que pour les champs firstName et lastName nous testerons que la correspondance avec la regexName vaut bien true, et nous faisons pareil pour les autres champs auxquels on associe la regex qui correspond

Notez bien que nous mettons le signe & après chaque vérification de correspondance car il faut bien que toutes les données de ces champs soient validées, et pas seulement une d’entre elles. 

Poursuivons notre condition avec le else qui renverra les erreurs :

				
					else {
    errorMsgFirstName = document.getElementById("firstNameErrorMsg");
    if (regexName.test(contact.firstName) == false) {
       errorMsgFirstName.innerHTML += "Merci de renseigner ce champs  correctement";
     }
       
    errorMsgLastName = document.getElementById("lastNameErrorMsg");
    if (regexName.test(contact.lastName) == false){
     errorMsgLastName.innerHTML = "Merci de renseigner ce champs correctement";
    }
       
    errorMsgAdress = document.getElementById("addressErrorMsg");
    if (regexAdress.test(contact.address) == false) {
     errorMsgAdress.innerHTML += "Merci de renseigner ce champs correctement";
     }
       
    errorMsgCity = document.getElementById("cityErrorMsg");
    if (regexCity.test(contact.city) == false) {
     errorMsgCity.innerHTML += "Merci de renseigner ce champs correctement";
    }
       
    errorMsgEmail = document.getElementById("emailErrorMsg");
    if (regexMail.test(contact.email) == false) {
     errorMsgEmail.innerHTML += "Merci de renseigner ce champs correctement";
     }
}

				
			

Pour chaque erreur que nous devons envoyer, nous commençons par cibler l’élément HTML dans laquelle elle sera renvoyée. Puis nous disons que si la correspondance entre la donnée du champ et la regex vaut false, alors nous affichons un message d’erreur dynamiquement avec le innerHTML.

Maintenant que nos conditions sont posées, il va falloir indiquer ce que nous faisons si les données sont valides, et donc dans le if. Nous avons dit que, si elles sont valides, nous devons transmettre l’objet contact et également un tableau nommé products contenant la liste des ID des produits.

Array products

Créons donc cet array products dans notre condition if

				
					//; Articles dans le panier
 let products = [];
 for(listId of cart) {
    products.push(listId.id);
 }

				
			

Je commence par déclarer une variable products et lui passe comme valeur par défaut, un tableau vide

Je me sers ensuite de ma constante “cart”, pour faire une boucle à l’intérieur de celle-ci qui va me récupérer la liste des id des produits présents dans le panier.

J’ai nommé ici listId, mais nous pourrions l’appeler autrement, cela n’a aucune incidence. Le principal est d’ensuite envoyer à chaque itération de ma boucle cet element.id dans le tableau products. Nous faisons cela grâce à la méthode push()

Objet "contact" et tableau "products" à disposition

Nous avons maintenant à notre disposition, notre objet contact et notre tableau “products”  contenant les id des produits

Il est donc temps d’aller chercher ces éléments pour les envoyer,  non ? 

Aller chercher, ça vous dit quelque chose maintenant j’espère ? Dites moi que oui svp, et que j’ai été clair jusqu’à maintenant ! 😵‍💫🤧😂

Aller chercher => fetch !! 

Envoi des données à l'API et affichage du n° de commande reçue dans la réponse

fetch POST

Nous allons donc utiliser un fetch pour envoyer nos données à l’API. Nous avons jusqu’à maintenant en paramètre de la fonction fetch, transmis une URL. 

En plus de la requête transmise en 1er paramètre, nous pouvons transmettre également un objet d’initialisation qui contiendra les paramètres de cette requête

Ces paramètres peuvent être nombreux, mais pour notre cas, nous devrons définir la méthode (POST), le header Content-Type (JSON) et le body (object contact et tableau produits).

Voici le fetch que nous codons, toujours dans la condition if :

				
					//; Fetch avec la méthode POST pour réception du n° de commande
const urlPost = 'http://localhost:3000/api/products/order/';
fetch(urlPost, {
      method: "POST",
      headers: {
         "Content-Type" : "application/json"
      },
      body: JSON.stringify({ contact, products })                
    })
    .then((response) => response.json())
    .then((data) => {
       localStorage.setItem('order', JSON.stringify(data));
       document.location.href = "../html/confirmation.html";
     })    
     .catch((erreur) => console.log("erreur : " + erreur));

				
			

Dans ce fetch, nous commençons par déclarer l’url de la requête dans une constante. Nous passons ensuite cette url en 1er paramètre, puis en 2ème paramètre l’objet d’initialisation de la requête où nous indiquons qu’elle sera de type POST, que nous ajouterons le header Content-Type avec comme valeur application/json, et enfin dans le corps de la requête , nous transmettons l’objet contact et le tableau products.

Retour des promesses

Nous disons ensuite que nous recevons en réponse par retour de promesses, une réponse au format json, et nous disons ensuite que les données reçues seront stockées dans le localStorage avec comme clé “order”, et nous demandons à être rediriger vers la page de confirmation.html

Enfin nous mettons un bloc catch en cas d’erreur du fetch

Maintenant si l’utilisateur renseigne les champs du formulaire comme il faut, et que nous avons bien transmis les bonnes données dans la requête POST, nous devons recevoir en retour les données, qui sont l’objet contact, le tableau products et un n° de commande appelé orderId.

Récupérer et afficher l’orderId

Nous devons afficher cet orderId sur la page de confirmation. 

Alors, nous commençons par récupérer l’orderId depuis le localStorage.

Ensuite, nous ciblons comme toujours l’élément HTML où nous devons injecter dynamiquement l’orderId.

				
					const orderId= JSON.parse(localStorage.getItem("order")) || [];

    let informations = document.querySelector("#orderId");
    if (informations != null) {
        informations.innerHTML += `${orderId.orderId}`;
    }

				
			

Notre site E-Commerce est maintenant fonctionnel et avons vu à travers ce tutoriel, comment faire appel à une API, et comment modifier le DOM dynamiquement à l’aide de JavaScript.

Conclusion

J’espère que ce tutoriel vous aura plu, et qu’il aura pu vous apporter une aide. 

Réaliser cette série m’a pris beaucoup de temps, entre la préparation, le code en amont, l’enregistrement des vidéos, l’encodage et le montage de celles-ci, l’écriture du tutoriel et la mise en ligne des vidéos sur YouTube.. 

Bref, si vous pouvez prendre quelques secondes pour vous inscrire sur Activateur Web, ce qui vous permettra d’être averti en avant première de la sortie des prochains tutoriels. Vous pouvez aussi vous abonner à ma page Facebook, Instagram, ou à ma chaîne YouTube. Vous pouvez même faire tout cela à la fois

Prendre quelques secondes pour m’encourager à continuer de produire de tels contenus. N’hésitez pas non plus à me laisser vos commentaires et je vous répondrai à coup sûr. 

Ceux qui me suivent savent déjà que sur Activateur Web, il y a souvent des bonus aux tutoriels publiés, alors vous savez quoi ? Si vous décidez de poursuivre il y a en effet un bonus concernant quelques améliorations que nous pouvons apporter au projet que nous venons de faire. 

Si vous décidez de ne pas poursuivre, je vous dis à bientôt sur Activateur Web. 

Prenez soin de vous, et surtout restez curieux !! 

Pour les autres, c’est parti pour le BONUS 🎇🎉🍾🎊

Bonus 🍾🎉🎇🎆🎈✨🧨🎁🎀

Ne vous inquiétez pas, le bonus arrive très vite.

Je n’ai pour le moment (13  Février 2023) pas eu encore le temps de travailler dessus et de le publier.

Revenez très vite car il sera en ligne très vite…

Code JavaScript du projet avant les améliorations

index.js

				
					// Url de l'API pour récupérer tous les produits
const url = 'http://localhost:3000/api/products/';

fetch(url)
    .then((response) => response.json())
    .then((data) => {
        addCards(data)
    })
    .catch ((error) => {
        alert("Attention votre serveur Node n'est pas lancé !")
    });

// Fonction pour affichage des produits en page d'accueil
function addCards(data) {
    for (product of data) {
        const card = document.getElementById('items');

        card.innerHTML += `
                            <a href="./product.html?_id=${product._id}">
                                <article>
                                    <img decoding="async" src="${product.imageUrl}" alt="${product.altTxt}">
                                    <h3 class="productName">${product.name}</h3>
                                    <p class="productDescription">${product.description}</p>
                                </article>
                            </a>
                            `;
    }
}
				
			

product.js

				
					// Panier
const cart = JSON.parse(localStorage.getItem("products")) || [];

// class Product
class Product {
    constructor(id, name, description, price, colors, imageUrl, altTxt, quantity)
    {
        this.id = id;
        this.name = name;
        this.description = description;
        this.price = +price;
        this.colors = colors;
        this.imageUrl = imageUrl;
        this.altTxt = altTxt;
        this.quantity = +quantity;
    }
}

// Constante nécessaire à la récupération de la chaine de requête et paramètre de l'url
const searchParams = new URLSearchParams(location.search);

// récupére l'id du produit
const newId = searchParams.get('_id');

// Url du produit
const newUrl = `http://localhost:3000/api/products/${newId}`;

// Fetch pour récupérer le produit
fetch(newUrl)
    .then((response) => response.json())
    .then ((data) => {
        const product = data;
        addCard(product);

        function addCard(product) {
            const productImage = document.querySelector('.item__img');
            productImage.innerHTML += `<img decoding="async" src="${product.imageUrl}" alt="${product.altTxt}" />`;

            const productName = document.getElementById('title');
            productName.innerHTML += `${product.name}`;
    
            const productPrice = document.getElementById('price');
            productPrice.innerHTML += `${product.price}`;
    
            const productDescription = document.getElementById('description');
            productDescription.innerHTML += `${product.description}`;

            addColors(product);
        }

        function addColors(product) {
            let options = document.getElementById('colors');

            for (let colors of product.colors) {
                options.innerHTML += `<option value="${colors}">${colors}</option>`
            }
        }

        let btnAddToCart = document.getElementById('addToCart');

        btnAddToCart.addEventListener("click", () => {
                let colors = document.getElementById("colors");
                let quantity = document.getElementById("quantity");

                let objectProduct = new Product(
                    newId,
                    product.name,
                    product.description,
                    product.price,
                    colors.value,
                    product.imageUrl,
                    product.altTxt,
                    quantity.value
                );

                if (colors.value == "") {
                    alert("Vous devez sélectionner une couleur ! 😉");
                } else if (quantity.value == 0) {
                    alert("Vous devez indiquer une quantité ! 😉");
                } else {
                    let isInCart = false;
                    let indexModification;
                    for (products of cart) {
                        switch(products.colors) {
                            case objectProduct.colors:
                                isInCart = true;
                                indexModification = cart.indexOf(products);
                        }
                    }
                    if (isInCart) {
                        cart[indexModification].quantity = +cart[indexModification].quantity + +objectProduct.quantity;
                        localStorage.setItem("products", JSON.stringify(cart));
                    } else {
                        cart.push(objectProduct);
                        localStorage.setItem("products", JSON.stringify(cart));
                    }
                    alert("Votre produit à bien été ajouté au panier 👍");
                }
        })

    })
    .catch((error) => {
        alert(
            "Attention votre serveur Node n'est pas lancé !"
        );
    });
				
			

cart.js

				
					// panier
cart = JSON.parse(localStorage.getItem("products")) || [];
// Si le panier contient au moins 1 produit
if (cart.length > 0) {
    for (product of cart) {
        displayCart(product);
    }
    productsInCart();
    displayTotalCart();
}

// Affichage des produits présents dans le panier
function displayCart(product) {
    const indexProduct = cart.indexOf(product);
    const productList = document.getElementById("cart__items");
    if( productList != null) {

        productList.innerHTML += `
                                <article class="cart__item" data-id="${product.id}">
                                    <div class="cart__item__img">
                                        <img decoding="async" src="${product.imageUrl}" alt="${product.altTxt}">
                                    </div>
                                    <div class="cart__item__content">
                                        <div class="cart__item__content__titlePrice">
                                            <h2>${product.name}</h2>
                                            <p>${product.price * product.quantity} €</p>
                                        </div>
                                        <div class="cart__item__content__settings">
                                            <div><p>Color : ${product.colors}</p><br></div>
                                            <div class="cart__item__content__settings__quantity">
                                            <p>Qté : </p>
                                            <input type="number" class="itemQuantity" name="itemQuantity" min="1" max="100" value="${product.quantity}" data-index=${indexProduct}>
                                            </div>
                                            <div class="cart__item__content__settings__delete">
                                            <p class="deleteItem" onclick="remove(${indexProduct})">Supprimer</p>
                                            </div>
                                        </div>
                                    </div>
                                </article>  
                                `;
    }
}

// Fonction pour affichage du nombre d'articles dans le panier
function productsInCart() {
    let productNumbers = document.getElementById("totalQuantity");

    if (cart.length > 0) {
        let productInCart = 0;
        for (product of cart) {
            productInCart += product.quantity;
        }
        if (productNumbers != null) {
            productNumbers.innerHTML += `${productInCart}`;
        }
    }
}

// Calcul du montant total du panier
function updateTotalCost () {
    let totalCart = 0;
    cart.forEach((product) => {
        totalCart = totalCart + (product.quantity * product.price);
    });
    return totalCart;
}

// Affichage du montant total du panier
function displayTotalCart() {
    const totalContent = document.getElementById("totalPrice");
   if (totalContent != null) {
       totalContent.innerHTML += updateTotalCost();
   }
}

// Gestion des quantités de produits
const inputList = document.querySelectorAll(".itemQuantity");
for (input of inputList) {
    let ind = input.getAttribute("data-index");
    input.addEventListener("click", (e) => {
        const newValue = e.target.value;
        for (products of cart) {
            let index = cart.indexOf(products);
            let qty = cart[index].quantity;
            if(index == ind && newValue > qty) {
                cart[index].quantity++;
                localStorage.setItem("products", JSON.stringify(cart));
                location.reload();
            }else if (index == ind && newValue < qty) {
                cart[index].quantity--;
                localStorage.setItem("products", JSON.stringify(cart));
                location.reload();
            } else {
                newValue == qty;
            }
        }
    });
}

// Fonction pour supprimer le produit
function remove(index) {
    cart.splice(index, 1);
    localStorage.setItem("products", JSON.stringify(cart));
    location.reload();
    displayCart();
}

// ! Formulaire de commande
// regex pour la validation des données du formulaire
const regexName = /^(([a-zA-ZÀ-ÿ]+[\s\-]{1}[a-zA-ZÀ-ÿ]+)|([a-zA-ZÀ-ÿ]+))$/;
const regexCity = /^(([a-zA-ZÀ-ÿ]+[\s\-]{1}[a-zA-ZÀ-ÿ]+)|([a-zA-ZÀ-ÿ]+)){1,10}$/;
const regexMail = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]{2,}\.[a-z]{2,4}$/;
const regexAddress = /^(([a-zA-ZÀ-ÿ0-9]+[\s\-]{1}[a-zA-ZÀ-ÿ0-9]+)){1,10}$/;

const order = document.querySelector('#order');

if (order != null) {

    order.addEventListener("click", (event) => {
        //; On rassemble les informations du formulaire dans l'objet contact
        let contact = {
            firstName: document.getElementById('firstName').value,
            lastName: document.getElementById('lastName').value,
            address: document.getElementById('address').value,
            city: document.getElementById('city').value,
            email: document.getElementById('email').value
        }
        if (
            (regexName.test(contact.firstName) == true) &
            (regexName.test(contact.lastName) == true) &
            (regexAddress.test(contact.address) == true) &
            (regexCity.test(contact.city) == true) &
            (regexMail.test(contact.email) == true) 
        ) {
    
            let products = [];
            for (listId of cart) {
                products.push(listId.id);
            }
            // Envoi objet contact et tableau products à l'API
            const urlPost = 'http://localhost:3000/api/products/order/';
            fetch(urlPost, {
                method: "POST",
                headers: {
                    "Content-Type" : "application/json"
                },
                body: JSON.stringify({ contact, products })
            })
            .then((response) => response.json())
            .then((data) => {
                localStorage.setItem("order", JSON.stringify(data));
                document.location.href = "../html/confirmation.html";
            })
            
            .catch((error) => console.log("erreur : " + error));
    
            // Si erreur dans les champs de formulaire
        } else {
            errorMsgFirstName = document.getElementById("firstNameErrorMsg");
            if (regexName.test(contact.firstName) == false) {
                errorMsgFirstName.innerHTML += "Merci de renseigner ce champs correctement";
            }
    
            errorMsgLastName = document.getElementById("lastNameErrorMsg");
            if (regexName.test(contact.lastName) == false){
                errorMsgLastName.innerHTML = "Merci de renseigner ce champs correctement";
            }
    
            errorMsgAdress = document.getElementById("addressErrorMsg");
            if (regexAdress.test(contact.address) == false) {
                errorMsgAdress.innerHTML += "Merci de renseigner ce champs correctement";
            }
    
            errorMsgCity = document.getElementById("cityErrorMsg");
            if (regexCity.test(contact.city) == false) {
                errorMsgCity.innerHTML += "Merci de renseigner ce champs correctement";
            }
    
            errorMsgEmail = document.getElementById("emailErrorMsg");
            if (regexMail.test(contact.email) == false) {
                errorMsgEmail.innerHTML += "Merci de renseigner ce champs correctement";
            }
        }
    });
}
// Affichage de n° de commande sur confirmation.html
const orderId = JSON.parse(localStorage.getItem("order")) || [];
let informations = document.querySelector("#orderId");
if (informations != null) {
    informations.innerHTML += `${orderId.orderId}`; 
}
				
			

Une réponse

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Articles similaires

Développeur indépendant

Passionné par le développement web, j’aime créer les contenus web qui permettent à mes clients d’obtenir une marque, un style, un site à leur image.

Catégories
Les catégories d’articles
Mes Articles Préférés
Retrouvez Moi
Sur YouTube

Sur ma chaine Youtube, je partage avec vous sur différents sujets.

Sur Facebook
Liens Amis
Le Fouet Enchanté
Site e-commerce Le Fouet Enchanté
A découvrir

Connectez Vous à votre compte