Skip to content
Snippets Groups Projects
Commit f7290a7d authored by Mehdi Zaidi's avatar Mehdi Zaidi :speech_balloon:
Browse files

tp fini

parent 045c8752
No related branches found
No related tags found
No related merge requests found
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
<li><a href="/help" class="helpLink">Support</a></li> <li><a href="/help" class="helpLink">Support</a></li>
</ul> </ul>
</nav> </nav>
<div class="testdiv"><button class="test">test1</button><button class="test">test2</button><button class="test">test3</button></div>
<div class="testdiv2"><button class="test2">test1</button><button class="test2">test2</button><button class="test2">test3</button></div>
</header> </header>
<section class="viewContainer"> <section class="viewContainer">
<header class="viewTitle"></header> <header class="viewTitle"></header>
......
import Router from './Router.js'; import Router from './Router.js';
import View from './View.js'; import View from './View.js';
import $ from './lib/jqlite.js';
/** /**
* Classe de la page "A propos" * Classe de la page "A propos"
...@@ -8,6 +9,7 @@ import View from './View.js'; ...@@ -8,6 +9,7 @@ import View from './View.js';
export default class AboutView extends View { export default class AboutView extends View {
show() { show() {
super.show(); super.show();
this.element.addClass('is-loading');
// quand on affiche la page, on charge le fichier `about.html` // quand on affiche la page, on charge le fichier `about.html`
// on pourrait optimiser ici le fonctionnement en ne le chargeant qu'une seule fois // on pourrait optimiser ici le fonctionnement en ne le chargeant qu'une seule fois
// (pour le moment à chaque fois qu'on affiche la view, une nouvelle requête AJAX est déclenchée) // (pour le moment à chaque fois qu'on affiche la view, une nouvelle requête AJAX est déclenchée)
...@@ -24,12 +26,16 @@ export default class AboutView extends View { ...@@ -24,12 +26,16 @@ export default class AboutView extends View {
* @see #handleButtonClick * @see #handleButtonClick
*/ */
showFileContent(html) { showFileContent(html) {
this.element.innerHTML = html; this.element.html(html);
this.element.removeClass('is-loading');
// l'ajout d'écouteur d'événement ne peut se faire qu'une fois que la page HTML a été modifiée // l'ajout d'écouteur d'événement ne peut se faire qu'une fois que la page HTML a été modifiée
// (sinon le bouton n'existe pas encore) // (sinon le bouton n'existe pas encore)
// this.element
// .querySelector('.button')
// .addEventListener('click', event => this.handleButtonClick(event));
this.element this.element
.querySelector('.button') .find('.button')
.addEventListener('click', event => this.handleButtonClick(event)); .on('click', event => this.handleButtonClick(event));
} }
/** /**
* Méthode déclenchée par le click sur le bouton "Nous contacter" * Méthode déclenchée par le click sur le bouton "Nous contacter"
......
import { API_KEY } from './config.js'; import { API_KEY } from './config.js';
import Router from './Router.js'; import Router from './Router.js';
import View from './View.js'; import View from './View.js';
import $ from './lib/jqlite.js';
export default class GameDetailView extends View { export default class GameDetailView extends View {
game; game;
...@@ -13,7 +14,8 @@ export default class GameDetailView extends View { ...@@ -13,7 +14,8 @@ export default class GameDetailView extends View {
show(slug) { show(slug) {
super.show(); super.show();
// ajout de l'état "loading" // ajout de l'état "loading"
this.element.classList.add('is-loading'); $(this.element).addClass('is-loading');
//this.element.classList.add('is-loading');
// création des URL de l'API pour le détail et les screenshots // création des URL de l'API pour le détail et les screenshots
const detailApiUrl = const detailApiUrl =
`https://api.rawg.io/api/games/` + encodeURIComponent(slug), `https://api.rawg.io/api/games/` + encodeURIComponent(slug),
...@@ -28,7 +30,7 @@ export default class GameDetailView extends View { ...@@ -28,7 +30,7 @@ export default class GameDetailView extends View {
.then(data => { .then(data => {
this.screenshots = data; this.screenshots = data;
this.render(); this.render();
this.element.classList.remove('is-loading'); $(this.element).removeClass('is-loading');
}); });
} }
...@@ -51,7 +53,7 @@ export default class GameDetailView extends View { ...@@ -51,7 +53,7 @@ export default class GameDetailView extends View {
} = this.game; } = this.game;
const releasedDate = new Date(released); const releasedDate = new Date(released);
// rendu dans la vue // rendu dans la vue
this.element.innerHTML = /*html*/ ` $(this.element).html(`
<div class="backgroundImage"> <div class="backgroundImage">
<img src="${background_image}" /> <img src="${background_image}" />
</div> </div>
...@@ -91,14 +93,20 @@ export default class GameDetailView extends View { ...@@ -91,14 +93,20 @@ export default class GameDetailView extends View {
</div> </div>
</section> </section>
<section class="description">${description}</section> <section class="description">${description}</section>
`; `);
// écoute du clic sur les liens du "fil d'Ariane" pour éviter le rechargement de page // écoute du clic sur les liens du "fil d'Ariane" pour éviter le rechargement de page
this.element.querySelectorAll('.breadcrumb a').forEach(link => $(this.element)
link.addEventListener('click', event => { .find('.breadcrumb a')
.on('click', event => {
event.preventDefault(); event.preventDefault();
Router.navigate(event.currentTarget.getAttribute('href')); Router.navigate(event.currentTarget.getAttribute('href'));
}) });
); // this.element.querySelectorAll('.breadcrumb a').forEach(link =>
// link.addEventListener('click', event => {
// event.preventDefault();
// Router.navigate(event.currentTarget.getAttribute('href'));
// })
// );
} }
} }
...@@ -2,6 +2,7 @@ import { API_KEY } from './config.js'; ...@@ -2,6 +2,7 @@ import { API_KEY } from './config.js';
import renderGameThumbnail from './renderGameThumbnail.js'; import renderGameThumbnail from './renderGameThumbnail.js';
import Router from './Router.js'; import Router from './Router.js';
import View from './View.js'; import View from './View.js';
import $ from './lib/jqlite.js';
export default class GameListView extends View { export default class GameListView extends View {
searchForm; searchForm;
...@@ -10,13 +11,15 @@ export default class GameListView extends View { ...@@ -10,13 +11,15 @@ export default class GameListView extends View {
constructor(element) { constructor(element) {
super(element); super(element);
// détection du clic sur le bouton "loupe" pour afficher/masquer le form de recherche // détection du clic sur le bouton "loupe" pour afficher/masquer le form de recherche
this.toggleSearchButton = this.element.querySelector('.toggleSearchButton'); // this.toggleSearchButton = this.element.querySelector('.toggleSearchButton');
this.toggleSearchButton.addEventListener('click', event => this.toggleSearchButton = $(this.element).find('.toggleSearchButton');
$(this.toggleSearchButton).on('click', event =>
this.toggleSearchForm(event) this.toggleSearchForm(event)
); );
// détection de la soumission du formulaire de recherche // détection de la soumission du formulaire de recherche
this.searchForm = this.element.querySelector('.searchForm'); //this.searchForm = this.element.querySelector('.searchForm');
this.searchForm.addEventListener('submit', event => this.searchForm = $(this.element).find('.searchForm');
$(this.searchForm).on('submit', event =>
this.handleSearchFormSubmit(event) this.handleSearchFormSubmit(event)
); );
} }
...@@ -36,9 +39,11 @@ export default class GameListView extends View { ...@@ -36,9 +39,11 @@ export default class GameListView extends View {
* @param {string} ordering ordre d'affichage des résultats * @param {string} ordering ordre d'affichage des résultats
*/ */
renderGameList(search = '', ordering) { renderGameList(search = '', ordering) {
this.element.querySelector('.results').classList.add('is-loading'); // this.element.querySelector('.results').classList.add('is-loading');
this.searchForm.querySelector('button').disabled = true; // this.searchForm.querySelector('button').disabled = true;
this.searchForm.querySelector('button').setAttribute('disabled', true); // this.searchForm.querySelector('button').setAttribute('disabled', true);
$(this.element).find('.results').addClass('is-loading');
$(this.searchForm.find('button')).attr('disabled', true);
fetch( fetch(
`https://api.rawg.io/api/games?search=${encodeURIComponent( `https://api.rawg.io/api/games?search=${encodeURIComponent(
search search
...@@ -49,19 +54,28 @@ export default class GameListView extends View { ...@@ -49,19 +54,28 @@ export default class GameListView extends View {
// rendu de la liste des jeux // rendu de la liste des jeux
let html = ''; let html = '';
data.results.forEach(game => (html += renderGameThumbnail(game))); data.results.forEach(game => (html += renderGameThumbnail(game)));
this.element.querySelector('.results').innerHTML = html; // this.element.querySelector('.results').innerHTML = html;
// suppression du "loader" et réactivation du formulaire // // suppression du "loader" et réactivation du formulaire
this.element.querySelector('.results').classList.remove('is-loading'); // this.element.querySelector('.results').classList.remove('is-loading');
this.searchForm.querySelector('button').disabled = false; // this.searchForm.querySelector('button').disabled = false;
$(this.element).find('.results').html(html);
$(this.element).find('.results').removeClass('is-loading');
$(this.searchForm.find('button')).removeAttr('disabled');
// détection du clic sur les vignettes de jeu // détection du clic sur les vignettes de jeu
// pour navigation vers la page détail (sans rechargement de page) // pour navigation vers la page détail (sans rechargement de page)
const gameLinks = this.element.querySelectorAll('.results > a'); // const gameLinks = this.element.querySelectorAll('.results > a');
gameLinks.forEach(gameLink => // gameLinks.forEach(gameLink =>
gameLink.addEventListener('click', event => { // gameLink.addEventListener('click', event => {
// event.preventDefault();
// Router.navigate(gameLink.getAttribute('href'));
// })
// );
$(this.element)
.find('.results > a')
.on('click', event => {
event.preventDefault(); event.preventDefault();
Router.navigate(gameLink.getAttribute('href')); Router.navigate(event.currentTarget.getAttribute('href'));
}) });
);
}); });
} }
/** /**
...@@ -69,13 +83,13 @@ export default class GameListView extends View { ...@@ -69,13 +83,13 @@ export default class GameListView extends View {
* @param {Event} event événement déclenché par le bouton sur lequel on a cliqué * @param {Event} event événement déclenché par le bouton sur lequel on a cliqué
*/ */
toggleSearchForm(event) { toggleSearchForm(event) {
const isOpened = this.searchForm.getAttribute('style') !== 'display: none;'; const isOpened = $(this.searchForm).attr('style') !== 'display: none;';
if (!isOpened) { if (!isOpened) {
this.searchForm.setAttribute('style', ''); $(this.searchForm).attr('style', '');
this.toggleSearchButton.classList.add('opened'); $(this.toggleSearchButton).addClass('opened');
} else { } else {
this.searchForm.setAttribute('style', 'display: none;'); $(this.searchForm).attr('style', 'display: none;');
this.toggleSearchButton.classList.remove('opened'); $(this.toggleSearchButton).removeClass('opened');
} }
} }
/** /**
...@@ -86,8 +100,8 @@ export default class GameListView extends View { ...@@ -86,8 +100,8 @@ export default class GameListView extends View {
*/ */
handleSearchFormSubmit(event) { handleSearchFormSubmit(event) {
event.preventDefault(); event.preventDefault();
const searchInput = this.searchForm.querySelector('[name=search]'), const searchInput = $(this.searchForm).find('[name=search]'),
orderingSelect = this.searchForm.querySelector('[name=ordering]'); orderingSelect = $(this.searchForm).find('[name=ordering]');
this.renderGameList(searchInput.value, orderingSelect.value); this.renderGameList(searchInput.val(), orderingSelect.val());
} }
} }
import View from './View.js'; import View from './View.js';
import $ from './lib/jqlite.js';
export default class HelpView extends View { export default class HelpView extends View {
/** /**
...@@ -9,9 +10,9 @@ export default class HelpView extends View { ...@@ -9,9 +10,9 @@ export default class HelpView extends View {
constructor(element) { constructor(element) {
super(element); super(element);
// détection du submit du formulaire // détection du submit du formulaire
this.element $(this.element)
.querySelector('.helpForm') .find('.helpForm')
.addEventListener('submit', event => this.handleSubmit(event)); .on('submit', event => this.handleSubmit(event));
} }
/** /**
...@@ -23,11 +24,11 @@ export default class HelpView extends View { ...@@ -23,11 +24,11 @@ export default class HelpView extends View {
handleSubmit(event) { handleSubmit(event) {
event.preventDefault(); event.preventDefault();
// récupération des 2 champs du formulaire // récupération des 2 champs du formulaire
const subjectInput = this.element.querySelector('input[name=subject]'); const subjectInput = $(this.element).find('input[name=subject]');
const bodyTextarea = this.element.querySelector('textarea[name=body]'); const bodyTextarea = $(this.element).find('textarea[name=body]');
// récupération du texte saisi par l'utilisateur.rice // récupération du texte saisi par l'utilisateur.rice
const subject = subjectInput.value, const subject = subjectInput.val(),
body = bodyTextarea.value; body = bodyTextarea.val();
// vérification des champs obligatoires // vérification des champs obligatoires
if (subject === '') { if (subject === '') {
alert('le champ "SUJET" est obligatoire'); alert('le champ "SUJET" est obligatoire');
...@@ -42,8 +43,8 @@ export default class HelpView extends View { ...@@ -42,8 +43,8 @@ export default class HelpView extends View {
subject subject
)}&body=${encodeURIComponent(body)}`; )}&body=${encodeURIComponent(body)}`;
// on vide les champs // on vide les champs
subjectInput.value = ''; subjectInput.val('');
bodyTextarea.value = ''; bodyTextarea.val('');
// on aurait aussi pu faire : form.reset(); // on aurait aussi pu faire : form.reset();
} }
} }
import $ from './lib/jqlite.js';
/** /**
* Classe Router qui permet de gérer la navigation dans l'application sans rechargement de page. * Classe Router qui permet de gérer la navigation dans l'application sans rechargement de page.
* (Single Page Application) * (Single Page Application)
...@@ -23,15 +24,23 @@ export default class Router { ...@@ -23,15 +24,23 @@ export default class Router {
static setMenuElement(menuElement) { static setMenuElement(menuElement) {
this.#menuElement = menuElement; this.#menuElement = menuElement;
// on écoute le clic sur tous les liens du menu // on écoute le clic sur tous les liens du menu
const menuLinks = this.#menuElement.querySelectorAll('a'); // const menuLinks = this.#menuElement.querySelectorAll('a');
menuLinks.forEach(link => // menuLinks.forEach(link =>
link.addEventListener('click', event => { // link.addEventListener('click', event => {
// event.preventDefault();
// // on récupère le href du lien cliqué pour déclencher navigate(...)
// const linkHref = event.currentTarget.getAttribute('href');
// Router.navigate(linkHref);
// })
// );
const $menuLinks = $(this.#menuElement).find('a');
$menuLinks.on('click', event => {
event.preventDefault(); event.preventDefault();
// on récupère le href du lien cliqué pour déclencher navigate(...) // on récupère le href du lien cliqué pour déclencher navigate(...)
const linkHref = event.currentTarget.getAttribute('href'); const linkHref = $(event.currentTarget).attr('href');
Router.navigate(linkHref); Router.navigate(linkHref);
}) });
);
} }
/** /**
* Affiche la view correspondant à `path` dans le tableau `routes` * Affiche la view correspondant à `path` dans le tableau `routes`
...@@ -70,13 +79,14 @@ export default class Router { ...@@ -70,13 +79,14 @@ export default class Router {
viewParam = pathEnd; viewParam = pathEnd;
} }
route.view.show(viewParam); route.view.show(viewParam);
this.titleElement.innerHTML = `<h1>${route.title}</h1>`; $(this.titleElement).html(`<h1>${route.title}</h1>`);
// Activation/désactivation des liens du menu // Activation/désactivation des liens du menu
const previousMenuLink = this.#menuElement.querySelector('a.active'), const $previousMenuLink = $(this.#menuElement).find('a.active'),
newMenuLink = this.#menuElement.querySelector(`a[href="${path}"]`); $newMenuLink = $(this.#menuElement).find(`a[href="${path}"]`);
previousMenuLink?.classList.remove('active'); // on retire la classe "active" du précédent menu
newMenuLink?.classList.add('active'); // on ajoute la classe CSS "active" sur le nouveau lien $previousMenuLink.removeClass('active'); // on retire la classe "active" du précédent menu
$newMenuLink.addClass('active'); // on ajoute la classe CSS "active" sur le nouveau lien
// History API : ajout d'une entrée dans l'historique du navigateur // History API : ajout d'une entrée dans l'historique du navigateur
// pour pouvoir utiliser les boutons précédent/suivant // pour pouvoir utiliser les boutons précédent/suivant
......
import $ from './lib/jqlite.js';
/** /**
* Classe de base des vues de notre application. * Classe de base des vues de notre application.
* Permet d'associer une balise HTML à la vue et de l'afficher/masquer. * Permet d'associer une balise HTML à la vue et de l'afficher/masquer.
...@@ -15,12 +16,12 @@ export default class View { ...@@ -15,12 +16,12 @@ export default class View {
* Affiche la vue en lui ajoutant la classe CSS `active` * Affiche la vue en lui ajoutant la classe CSS `active`
*/ */
show() { show() {
this.element.classList.add('active'); $(this.element).addClass('active');
} }
/** /**
* Masque la vue en enlevant la classe CSS `active` * Masque la vue en enlevant la classe CSS `active`
*/ */
hide() { hide() {
this.element.classList.remove('active'); $(this.element).removeClass('active');
} }
} }
import $ from './jqlite.js';
export default class JQObject {
constructor(DOMElements) {
let i = 0;
DOMElements.forEach(Element => {
this[i] = Element;
i++;
});
this.length = i;
}
html(newHTML) {
for (let i = 0; i < this.length; i++) {
this[i].innerHTML = newHTML;
}
return this;
}
append(newHTML) {
return this.html(this.html() + newHTML);
}
find(querySelector) {
return $(querySelector, this[0]);
}
addClass(className) {
for (let i = 0; i < this.length; i++) {
this[i].classList.add(className);
}
return this;
}
removeClass(className) {
for (let i = 0; i < this.length; i++) {
this[i].classList.remove(className);
}
return this;
}
attr(attributeName, newValue = null) {
if (newValue != null) {
for (let i = 0; i < this.length; i++) {
this[i].setAttribute(attributeName, newValue);
}
return this;
}
return this[0].getAttribute(attributeName);
}
removeAttr(attributeName) {
for (let i = 0; i < this.length; i++) {
this[i].removeAttribute(attributeName);
}
return this;
}
val(newValue = null) {
if (newValue) {
for (let i = 0; i < this.length; i++) {
this[i].value = newValue;
}
return this;
}
return this[0].value;
}
on(eventName, eventHandler) {
if (this[0]) {
this[0].addEventListener(eventName, eventHandler);
}
return this;
}
}
import JQObject from './JQObject.js';
export default function $(querySelector, element = document) {
if (typeof querySelector === 'object') {
if (querySelector instanceof JQObject) {
return querySelector;
}
querySelector = querySelector.className;
querySelector = querySelector
.split(' ')
.map(className => '.' + className)
.join(' ');
}
return new JQObject(element.querySelectorAll(querySelector));
}
...@@ -3,22 +3,31 @@ import GameDetailView from './GameDetailView.js'; ...@@ -3,22 +3,31 @@ import GameDetailView from './GameDetailView.js';
import GameListView from './GameListView.js'; import GameListView from './GameListView.js';
import HelpView from './HelpView.js'; import HelpView from './HelpView.js';
import Router from './Router.js'; import Router from './Router.js';
import $ from './lib/jqlite.js';
$('.logo').on('click', event => {
event.preventDefault();
Router.navigate('/', true);
});
$('body > footer a').on('mouseover', event => {
$(event.currentTarget).addClass('mouseover');
});
$('body > footer a').on('mouseout', event => {
$(event.currentTarget).removeClass('mouseover');
});
// Modification du footer // Modification du footer
document.querySelector('body > footer > div:nth-of-type(2)').innerHTML += $('body > footer > div:nth-of-type(2)').append(
' / CSS inspirée de <a href="https://store.steampowered.com/">steam</a>'; ' / CSS inspirée de <a href="https://store.steampowered.com/">steam</a>'
);
// création des vues de notre application // création des vues de notre application
const helpView = new HelpView(document.querySelector('.viewContent .help')); const helpView = new HelpView($('.viewContent .help'));
const gameListView = new GameListView( const gameListView = new GameListView($('.viewContent > .gameList'));
document.querySelector('.viewContent > .gameList') const aboutView = new AboutView($('.viewContent > .about'));
); const gameDetailView = new GameDetailView($('.viewContent > .gameDetail'));
const aboutView = new AboutView(
document.querySelector('.viewContent > .about')
);
const gameDetailView = new GameDetailView(
document.querySelector('.viewContent > .gameDetail')
);
// mise en place du Router // mise en place du Router
const routes = [ const routes = [
...@@ -29,11 +38,19 @@ const routes = [ ...@@ -29,11 +38,19 @@ const routes = [
]; ];
Router.routes = routes; Router.routes = routes;
// élément dans lequel afficher le <h1> de la vue // élément dans lequel afficher le <h1> de la vue
Router.titleElement = document.querySelector('.viewTitle'); Router.titleElement = $('.viewTitle');
// gestion des liens du menu (détection du clic et activation/désactivation) // gestion des liens du menu (détection du clic et activation/désactivation)
Router.setMenuElement(document.querySelector('.mainMenu')); Router.setMenuElement($('.mainMenu'));
// chargement de la vue initiale selon l'URL demandée par l'utilisateur.rice (Deep linking) // chargement de la vue initiale selon l'URL demandée par l'utilisateur.rice (Deep linking)
Router.navigate(window.location.pathname, true); Router.navigate(window.location.pathname, true);
// gestion des boutons précédent/suivant du navigateur (History API) // gestion des boutons précédent/suivant du navigateur (History API)
window.onpopstate = () => Router.navigate(document.location.pathname, true); window.onpopstate = () => Router.navigate(document.location.pathname, true);
// console.log($('.logo span').html('<em>jquery</em>steam'));
// console.log($('a').html('<em>jquery</em>steam'));
// import $$ from './lib/jqlite.js';
// $('.testdiv').find('.test').attr('test', 'hello');
// $$('.testdiv2').find('.test2').attr('test', 'hello');
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment