One place for hosting & domains

      Comment utiliser ViewChild dans Angular pour accéder à un composant enfant, à une directive ou un élément DOM


      Introduction

      Dans cet article, vous allons vous expliquer ce qu’est un décorateur ViewChild d’Angular.

      Il se peut que dans certaines situations vous souhaitiez accéder à une directive, un composant enfant ou un élément DOM à partir d’une catégorie de composants parent. Le décorateur ViewChild renvoie le premier élément qui correspond à un sélecteur de référence de directive, de composant ou de modèle donné.

      Utilisation de ViewChild avec des directives

      ViewChild permet d’accéder aux directives.

      Supposons que nous ayons une SharkDirective.

      L’idéal serez que vous puissiez utiliser @angular/cli pour générer votre directive :

      • ng generate directive shark

      Ou alors, il vous faudra l’ajouter manuellement à app.module.ts :

      app.module.ts

      import { SharkDirective } from './shark.directive';
      ...
      @NgModule({
        declarations: [
          AppComponent,
          SharkDirective
        ],
        ...
      })
      

      Notre directive recherchera les éléments avec l’attribut appShark et ajoutera le texte dans l’élément avec le terme Shark :

      shark.directive.ts

      import {
        Directive,
        ElementRef,
        Renderer2
      } from '@angular/core';
      
      @Directive(
        { selector: '[appShark]' }
      )
      export class SharkDirective {
        creature="Dolphin";
      
        constructor(elem: ElementRef, renderer: Renderer2) {
          let shark = renderer.createText('Shark ');
          renderer.appendChild(elem.nativeElement, shark);
        }
      }
      

      Ensuite, nous allons ajouter un Shark à Fin en l’utilisant dans le modèle de composant :

      app.component.html

      <span appShark>Fin!</span>
      

      Lorsque vous visualiserez l’application dans un navigateur, l’affichage sera le suivant :

      Output

      Shark Fin!

      Maintenant, nous pouvons accéder à la variable d’instance creature de SharkDirective. Elle configurera une variable extraCreature avec sa valeur :

      app.component.ts

      import {
        Component,
        ViewChild,
        AfterViewInit
      } from '@angular/core';
      import { SharkDirective } from './shark.directive';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css']
      })
      export class AppComponent implements AfterViewInit {
        extraCreature: string;
      
        @ViewChild(SharkDirective)
        set appShark(directive: SharkDirective) {
          this.extraCreature = directive.creature;
        };
      
        ngAfterViewInit() {
          console.log(this.extraCreature); // Dolphin
        }
      }
      

      Ici, nous avons utilisé une méthode de réglage pour configurer la variable extraCreature. Notez que nous attendons que le hook de cycle de vie AfterViewInit ait accès à notre variable, car c’est à ce moment-là que les composants et les directives enfant seront disponibles.

      Lorsque nous consultons l’application dans un navigateur, nous voyons encore apparaître "Shark Fin!" . Cependant, dans le journal de la console, il s’affichera de la manière suivante :

      Output

      Dolphin

      Le composant parent a pu accéder à la valeur depuis la directive.

      Utilisation de ViewChild avec les éléments DOM

      ViewChild permet d’accéder aux éléments DOM natifs qui ont une variable de référence de modèle.

      Supposons que nous ayons un <input> dans notre modèle avec la variable de référence #someInput :

      app.component.html

      <input #someInput placeholder="Your favorite sea creature">
      

      Maintenant, nous pouvons accéder à <input> avec ViewChild et configurer la value :

      app.component.ts

      import {
        Component,
        ViewChild,
        AfterViewInit,
        ElementRef
      } from '@angular/core';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css']
      })
      export class AppComponent implements AfterViewInit {
        @ViewChild('someInput') someInput: ElementRef;
        ngAfterViewInit() {
          this.someInput.nativeElement.value="Whale!";
        }
      }
      

      Lorsque ngAfterViewInit se déclenche, la valeur de notre <input> sera configurée :

      Output

      Whale!

      Le composant parent a réussi à configurer la valeur de l’élément DOM enfant.

      Utilisation de ViewChild avec des composants enfant

      ViewChild permet d’accéder à un composant enfant et appeler des méthodes ou d’accéder à des variables d’instance qui sont disponibles pour l’enfant.

      Supposons que nous ayons un ChildComponent. L’idéal serez que vous puissiez utiliser @angular/cli pour générer votre composant :

      • ng generate component child --flat

      Ou alors, il vous faudra créer les fichiers child.component.css et child.component.html et l’ajouter manuellement à app.module.ts :

      app.module.ts

      import { ChildComponent } from './child.component';
      ...
      @NgModule({
        declarations: [
          AppComponent,
          ChildComponent
        ],
        ...
      })
      

      Nous allons ajouter une méthode whoAmI à ChildComponent qui renverra le message suivant :

      child.component.ts

      whoAmI() {
        return 'I am a child component!';
      }
      

      Ensuite, nous allons référencer le composant dans notre modèle d’application :

      app.component.html

      <app-child>child works!</app-child>
      

      Maintenant, nous pouvons appeler la méthode whoAmI de notre catégorie de composants parent avec ViewChild de la manière suivante :

      app.component.ts

      import {
        Component,
        ViewChild,
        AfterViewInit
      } from '@angular/core';
      import { ChildComponent } from './child.component';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css'],
      })
      export class AppComponent implements AfterViewInit {
        @ViewChild(ChildComponent) child: ChildComponent;
        ngAfterViewInit() {
          console.log(this.child.whoAmI()); // I am a child component!
        }
      }
      

      Lorsque vous visualiserez l’application dans un navigateur, le journal de la console s’affichera :

      Output

      I am a child component!

      Le composant parent a réussi à appeler la méthode whoAmI du composant enfant.

      Conclusion

      Vous avez appris à utiliser ViewChild pour accéder à une directive, à un composant enfant et à un élément DOM dans une catégorie de composants parent.

      Si la référence est remplacée de manière dynamique par un nouvel élément, ViewChild mettra à jour automatiquement sa référence.

      Dans le cas où vous souhaitez accéder à plusieurs composants enfant, utilisez plutôt ViewChildren.

      Si vous souhaitez en savoir plus sur Angular, consultez notre page thématique Angular pour des exercices et des projets de programmation.



      Source link

      Comment ajouter une authentification à votre application avec Flask-Login


      Introduction

      Permettre aux utilisateurs de se connecter à votre application est l’une des fonctionnalités les plus courantes que vous ajouterez à votre application web. Cet article explique comment ajouter une authentification à votre application Flask avec le paquet Flask-Login.

      Gif animé de l'application Flask et boîte de connexion

      Nous allons créer des pages d’enregistrement et de connexion qui permettent aux utilisateurs de se connecter et d’accéder à des pages protégées que les utilisateurs qui ne sont pas connectés ne peuvent pas voir. Nous prendrons les informations du modèle utilisateur et les afficherons sur nos pages protégées lorsque l’utilisateur se connectera pour simuler à quoi ressemblerait un profil.

      Nous aborderons les points suivants dans cet article :

      • Utiliser la bibliothèque Flask-Login pour la gestion des sessions
      • Utiliser l’utilitaire Flask intégré pour le hachage des mots de passe
      • Ajouter des pages protégées à notre application pour les utilisateurs connectés uniquement
      • Utiliser Flask-SQLAlchemy pour créer un modèle d’utilisateur
      • Créer des formulaires d’enregistrement et de connexion pour que nos utilisateurs puissent créer des comptes et se connecter
      • Envoyer rapidement des messages d’erreur aux utilisateurs lorsque quelque chose ne va pas
      • Utiliser les informations du compte de l’utilisateur pour les afficher sur la page de profil

      Le code source de ce projet est disponible sur GitHub.

      Conditions préalables

      Pour terminer ce tutoriel, vous aurez besoin des éléments suivants :

      Notre application utilisera le modèle d’usine de l’application Flask avec des plans. Nous aurons un modèle qui gère tout ce qui est lié à la propriété auth, et nous en aurons un autre pour nos itinéraires réguliers, qui comprennent l’index et la page de profil protégé. Dans une véritable application, vous pouvez décomposer la fonctionnalité comme vous le souhaitez, mais la solution présentée ici fonctionnera bien pour ce tutoriel.

      Voici un diagramme qui vous donnera une idée de la structure des fichiers de votre projet une fois que vous aurez terminé le tutoriel :

      .
      └── flask_auth_app
          └── project
              ├── __init__.py       # setup our app
              ├── auth.py           # the auth routes for our app
              ├── db.sqlite         # our database
              ├── main.py           # the non-auth routes for our app
              ├── models.py         # our user model
              └── templates
                  ├── base.html     # contains common layout and links
                  ├── index.html    # show the home page
                  ├── login.html    # show the login form
                  ├── profile.html  # show the profile page
                  └── signup.html   # show the signup form
      

      Au fur et à mesure que nous avancerons dans le tutoriel, nous créerons ces répertoires et ces fichiers.

      Étape 1 — Installer des paquets

      Il y a trois paquets principaux dont nous avons besoin pour notre projet :

      • Flask
      • Flask-Login : pour gérer les sessions utilisateur après authentification
      • Flask-SQLAlchemy : pour représenter le modèle d’utilisateur et l’interface avec notre base de données

      Nous utiliserons SQLite pour éviter d’avoir à installer des dépendances supplémentaires pour la base de données.

      Tout d’abord, nous allons commencer par créer le répertoire des projets :

      Ensuite, nous devons naviguer vers le répertoire du projet :

      Vous voudrez créer un environnement Python si vous n’en avez pas. Selon la façon dont Python a été installé sur votre machine, vos commandes seront similaires :

      • python3 -m venv auth
      • source auth/bin/activate

      Note : vous pouvez consulter le tutoriel adapté à votre environnement local pour la mise en place de venv.

      Exécutez les commandes suivantes depuis votre environnement virtuel pour installer les paquets nécessaires :

      • pip install flask flask-sqlalchemy flask-login

      Maintenant que vous avez installé les paquets, vous êtes prêt à créer le fichier principal de l’application.

      Étape 2 — Création du dossier d’application principale

      Commençons par créer un répertoire project :

      Le premier dossier sur lequel nous travaillerons sera le fichier __init__.py pour notre projet :

      Ce fichier aura pour fonction de créer notre app, qui initialisera la base de données et enregistrera nos plans. Pour l’instant, cela ne fera pas grand-chose, mais ce sera nécessaire pour le reste de notre application. Nous devons initialiser SQLAlchemy, définir quelques valeurs de configuration et enregistrer nos plans ici.

      project/__init__.py

      from flask import Flask
      from flask_sqlalchemy import SQLAlchemy
      
      # init SQLAlchemy so we can use it later in our models
      db = SQLAlchemy()
      
      def create_app():
          app = Flask(__name__)
      
          app.config['SECRET_KEY'] = 'secret-key-goes-here'
          app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
      
          db.init_app(app)
      
          # blueprint for auth routes in our app
          from .auth import auth as auth_blueprint
          app.register_blueprint(auth_blueprint)
      
          # blueprint for non-auth parts of app
          from .main import main as main_blueprint
          app.register_blueprint(main_blueprint)
      
          return app
      

      Maintenant que nous avons le fichier principal de l’application, nous pouvons commencer à ajouter des itinéraires.

      Étape 3 — Ajouter des itinéraires

      Pour nos itinéraires, nous utiliserons deux plans. Pour notre plan principal, nous aurons une page d’accueil (/) et la page de profil (/profile) pour après s’être connecté. Si l’utilisateur tente d’accéder à la page de profil sans être connecté, il sera envoyé sur l’intinéraire de connexion.

      Pour notre plan d’authentification, nous aurons des itinéraires pour récupérer à la fois la page de connexion (/login) et la page d’inscription (/sign-up). Nous aurons également des itinéraires pour traiter les demandes POST provenant de ces deux itinéraires. Enfin, nous aurons un itinéraire de déconnexion (/logout) pour déconnecter un utilisateur actif.

      Pour l’instant, nous allons définir le login, signup, et logout avec des retours simples. Nous les réexaminerons à une étape ultérieure et les mettrons à jour avec la fonctionnalité souhaitée.

      Tout d’abord, créez main.py pour votre main_blueprint :

      project/main.py

      from flask import Blueprint
      from . import db
      
      main = Blueprint('main', __name__)
      
      @main.route('/')
      def index():
          return 'Index'
      
      @main.route('/profile')
      def profile():
          return 'Profile'
      

      Ensuite, créez auth.py pour votre auth_blueprint :

      project/auth.py

      from flask import Blueprint
      from . import db
      
      auth = Blueprint('auth', __name__)
      
      @auth.route('/login')
      def login():
          return 'Login'
      
      @auth.route('/signup')
      def signup():
          return 'Signup'
      
      @auth.route('/logout')
      def logout():
          return 'Logout'
      

      Dans un terminal, vous pouvez définir les valeurs FLASK_APP et FLASK_DEBUG :

      • export FLASK_APP=project
      • export FLASK_DEBUG=1

      La variable d’environnement FLASK_APP indique à Flask comment charger l’application. Elle doit indiquer où create_app est situé. Pour nos besoins, nous indiquerons le répertoire des projets.

      La variable d’environnement FLASK_DEBUG est activée en lui donnant la valeur 1, ce qui activera un débogueur qui affichera les erreurs de l’application dans le navigateur.

      Assurez-vous que vous êtes dans le répertoire flask_auth_app et puis exécutez le projet :

      Maintenant, dans un navigateur web, vous devriez être en mesure de naviguer vers les cinq URL possibles et voir le texte renvoyé qui a été défini dans auth.py et main.py.

      Par exemple, visiter localhost:5000/profile affiche : Profile :

      Capture d'écran du projet au port 5000 de localhost dans le navigateur

      Maintenant que nous avons vérifié que nos itinéraires se comportent comme prévu, nous pouvons passer à la création de modèles.

      Étape 4 — Créer des modèles

      Allons de l’avant et créons les modèles qui sont utilisés dans notre application. C’est la première étape avant que nous puissions mettre en œuvre la fonctionnalité de connexion proprement dite. Notre application utilisera quatre modèles :

      • index.html
      • profile.html
      • login.html
      • signup.html

      Nous aurons également un modèle de base qui aura un code commun à chacune des pages. Dans ce cas, le modèle de base sera doté de liens de navigation et de la disposition générale de la page. Créons-les maintenant.

      Tout d’abord, créez un répertoire de modèles sous le répertoire de project :

      • mkdir -p project/templates

      Ensuite, créez base.html :

      • nano project/templates/base.html

      Ensuite, ajoutez le code suivant au fichier base.html :

      project/templates/base.html

      <!DOCTYPE html>
      <html>
      
      <head>
          <meta charset="utf-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Flask Auth Example</title>
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />
      </head>
      
      <body>
          <section class="hero is-primary is-fullheight">
      
              <div class="hero-head">
                  <nav class="navbar">
                      <div class="container">
      
                          <div id="navbarMenuHeroA" class="navbar-menu">
                              <div class="navbar-end">
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
                                      Home
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
                                      Profile
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
                                      Login
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
                                      Sign Up
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
                                      Logout
                                  </a>
                              </div>
                          </div>
                      </div>
                  </nav>
              </div>
      
              <div class="hero-body">
                  <div class="container has-text-centered">
                     {% block content %}
                     {% endblock %}
                  </div>
              </div>
          </section>
      </body>
      
      </html>
      

      Ce code créera une série de liens de menu vers chaque page de l’application et une zone où le contenu apparaîtra .

      Remarque : En arrière-plan, nous utilisons Bulma pour s’occuper du style et de la mise en page. Pour une plongée plus approfondie dans Bulma, pensez à lire la documentation officielle de Bulma.

      Ensuite, créez templates/index.html:

      • nano project/templates/index.html

      Ajoutez le code suivant au fichier nouvellement créé pour ajouter du contenu à la page :

      project/templates/index.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Flask Login Example
      </h1>
      <h2 class="subtitle">
        Easy authentication and authorization in Flask.
      </h2>
      {% endblock %}
      

      Ce code permettra de créer une page d’index de base avec un titre et un sous-titre.

      Ensuite, créez templates/login.html :

      • nano project/templates/login.html

      Ce code génère une page de connexion avec des champs pour Email et Password. Il y a également une case à cocher pour « se souvenir » d’une session connectée.

      project/templates/login.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Login</h3>
          <div class="box">
              <form method="POST" action="/login">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Your Password">
                      </div>
                  </div>
                  <div class="field">
                      <label class="checkbox">
                          <input type="checkbox">
                          Remember me
                      </label>
                  </div>
                  <button class="button is-block is-info is-large is-fullwidth">Login</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Ensuite, créez templates/signup.html :

      • nano project/templates/signup.html

      Ajoutez le code suivant pour créer une page d’inscription avec des champs pour email, name, et password :

      project/templates/signup.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Sign Up</h3>
          <div class="box">
              <form method="POST" action="/signup">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Password">
                      </div>
                  </div>
      
                  <button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Ensuite, créez templates/profile.html :

      • nano project/templates/profile.html

      Ajoutez ce code pour créer une page simple avec un titre codé en dur pour accueillir Anthony:

      project/templates/profile.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Welcome, Anthony!
      </h1>
      {% endblock %}
      

      Plus tard, nous ajouterons du code pour saluer dynamiquement tout utilisateur.

      Une fois que vous avez ajouté les modèles, nous pouvons mettre à jour les déclarations de retour dans chacun des itinéraires ; nous devons renvoyer les modèles au lieu du texte.

      Ensuite, mettez à jour main.py en modifiant la ligne d’importation et les itinéraires pour index et profile :

      project/main.py

      from flask import Blueprint, render_template
      ...
      @main.route('/')
      def index():
          return render_template('index.html')
      
      @main.route('/profile')
      def profile():
          return render_template('profile.html')
      

      Vous allez maintenant mettre à jour auth.py en modifiant la ligne d’importation et les itinéraires pour login et signup :

      project/auth.py

      from flask import Blueprint, render_template
      ...
      @auth.route('/login')
      def login():
          return render_template('login.html')
      
      @auth.route('/signup')
      def signup():
          return render_template('signup.html')
      

      Une fois que vous avez effectué ces changements, voici à quoi ressemble la page d’inscription si vous naviguez vers /signup :

      Page d'inscription à /signup

      Vous devriez pouvoir voir également les pages pour /, /login et /profile.

      Nous laisserons /logout seul pour l’instant car il n’affichera pas de modèle quand il sera terminé.

      Étape 5 — Créer des modèles d’utilisateurs

      Notre modèle d’utilisateur représente ce que cela signifie pour notre app d’avoir un utilisateur. Nous aurons des champs pour une adresse électronique, un mot de passe et un nom. Dans votre application, vous pouvez décider que vous souhaitez que beaucoup plus d’informations soient stockées par utilisateur. Vous pouvez ajouter des éléments tels que l’anniversaire, la photo de profil, la localisation ou toute autre préférence de l’utilisateur.

      Les modèles créés dans Flask-SQLAlchemy sont représentés par des classes qui sont ensuite traduites en tables dans une base de données. Les attributs de ces classes se transforment alors en colonnes pour ces tables.

      Allons de l’avant et créons ce modèle d’utilisateur :

      Ce code crée un modèle d’utilisateur avec des colonnes pour un id, email, password et un name :

      project/models.py

      from . import db
      
      class User(db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Maintenant que vous avez créé un modèle d’utilisateur, vous pouvez passer à la configuration de votre base de données.

      Étape 6 — Configurer la base de données

      Comme indiqué dans les conditions préalables, nous utiliserons une base de données SQLite. Nous pourrions créer nous-mêmes une base de données SQLite, mais laissons Flask-SQLAlchemy le faire pour nous. Nous avons déjà le chemin de la base de données spécifié dans le fichier__init__.py,il suffit donc de dire à Flask-SQLAlchemy de créer la base de données dans le REPL Python.

      Si vous arrêtez votre application et ouvrez un REPL en Python, nous pouvons créer la base de données en utilisant la méthode create_all sur l’objet db. Assurez-vous que vous êtes toujours dans l’environnement virtuel et dans le répertoire flask_auth_app.

      • from project import db, create_app
      • db.create_all(app=create_app()) # pass the create_app result so Flask-SQLAlchemy gets the configuration.

      Note : Si l’utilisation de l’interpréteur Python est nouvelle pour vous, vous pouvez consulter la documentation officielle.

      Vous allez maintenant voir un fichier db.sqlite dans le répertoire de votre projet. Cette base de données contiendra notre table d’utilisateurs.

      Étape 7 — Mettre en place la fonction d’autorisation

      Pour notre fonction d’inscription, nous allons prendre les données que l’utilisateur tape dans le formulaire et les ajouter à notre base de données. Avant de les ajouter, nous devons nous assurer que l’utilisateur n’existe pas déjà dans la base de données. Si ce n’est pas le cas, nous devons nous assurer que nous avons bien haché le mot de passe avant de le placer dans la base de données, car nous ne voulons pas que nos mots de passe soient stockés en clair.

      Commençons par ajouter une deuxième fonction pour traiter les données du formulaire POST. Dans cette fonction, nous allons d’abord recueillir les données transmises par l’utilisateur.

      Créez la fonction et ajoutez une redirection vers le bas. Cela permettra à l’utilisateur de faire l’expérience d’une inscription réussie et d’être dirigé vers la page de connexion.

      Mettez à jour auth.py en modifiant la ligne d’importation et en mettant en œuvre signup_post :

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          # code to validate and add user to database goes here
          return redirect(url_for('auth.login'))
      

      Maintenant, ajoutons le reste du code nécessaire à l’inscription d’un utilisateur.

      Pour commencer, nous devrons utiliser l’objet de demande pour obtenir les données du formulaire.

      Continuez à mettre à jour auth.py en ajoutant des importations et en mettant en œuvre signup_post:

      auth.py

      from flask import Blueprint, render_template, redirect, url_for, request
      from werkzeug.security import generate_password_hash, check_password_hash
      from .models import User
      from . import db
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          email = request.form.get('email')
          name = request.form.get('name')
          password = request.form.get('password')
      
          user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database
      
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              return redirect(url_for('auth.signup'))
      
          # create a new user with the form data. Hash the password so the plaintext version isn't saved.
          new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
      
          # add the new user to the database
          db.session.add(new_user)
          db.session.commit()
      
          return redirect(url_for('auth.login'))
      

      Remarque : Le stockage des mots de passe en clair est considéré comme une mauvaise pratique de sécurité. Vous voudrez généralement utiliser un algorithme de hachage complexe et un sel de mot de passe pour assurer la sécurité des mots de passe.

      Étape 8 — Tester la méthode d’inscription

      Maintenant que la méthode d’inscription est terminée, nous devrions être en mesure de créer un nouvel utilisateur. Utilisez le formulaire pour créer un utilisateur.

      Il y a deux façons de vérifier si l’inscription a fonctionné : vous pouvez utiliser un visualiseur de base de données pour regarder la ligne qui a été ajoutée à votre table, ou vous pouvez essayer de vous inscrire à nouveau avec la même adresse électronique, et si vous obtenez une erreur, vous savez que le premier courriel a été enregistré correctement. Adoptons donc cette approche.

      Nous pouvons ajouter un code pour faire savoir à l’utilisateur que le courriel existe déjà et lui dire de se rendre à la page de connexion. En appelant la fonction flash, nous enverrons un message à la demande suivante, qui dans ce cas, est la redirection. La page sur laquelle nous arrivons aura alors accès à ce message dans le modèle.

      D’abord, nous ajoutons le flash avant de rediriger vers notre page d’enregistrement.

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for, request, flash
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          ...
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              flash('Email address already exists')
              return redirect(url_for('auth.signup'))
      

      Pour obtenir le message clignotant dans le modèle, nous pouvons ajouter ce code au-dessus du formulaire. Le message s’affichera alors directement au-dessus du formulaire.

      project/templates/signup.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}. Go to <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">login page</a>.
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/signup">
      

      Boîte d'inscription affichant un message qui indique que l'adresse électronique existe déjà. Allez à la page de login dans une boîte rose foncé

      Étape 9 — Ajouter la méthode de connexion

      La méthode de connexion est similaire à la fonction d’inscription en ce sens que nous prenons les informations de l’utilisateur et en faisons quelque chose. Dans ce cas, nous comparerons l’adresse électronique saisie pour voir si elle se trouve dans la base de données. Si c’est le cas, nous testerons le mot de passe fourni par l’utilisateur en hachant le mot de passe que l’utilisateur nous a communiqué et en le comparant au mot de passe haché dans la base de données. Nous savons que l’utilisateur a saisi le bon mot de passe lorsque les deux mots de passe hachés correspondent.

      Une fois que l’utilisateur a passé le contrôle du mot de passe, nous savons qu’il possède les bonnes références et nous pouvons le connecter en utilisant Flask-Login. En appelant login_user, Flask-Login créera une session pour cet utilisateur qui persistera tant que l’utilisateur restera connecté, ce qui lui permettra de voir les pages protégées.

      Nous pouvons commencer par un nouveau mode de traitement des données POSTed. Nous redirigerons vers la page de profil lorsque l’utilisateur se connectera avec succès :

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          # login code goes here
          return redirect(url_for('main.profile'))
      

      Maintenant, nous devons vérifier si l’utilisateur possède les bons identifiants

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          email = request.form.get('email')
          password = request.form.get('password')
          remember = True if request.form.get('remember') else False
      
          user = User.query.filter_by(email=email).first()
      
          # check if the user actually exists
          # take the user-supplied password, hash it, and compare it to the hashed password in the database
          if not user or not check_password_hash(user.password, password):
              flash('Please check your login details and try again.')
              return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page
      
          # if the above check passes, then we know the user has the right credentials
          return redirect(url_for('main.profile'))
      

      Ajoutons le bloc dans le modèle pour que l’utilisateur puisse voir le message clignotant. Comme pour le formulaire d’inscription, ajoutons le message d’erreur potentiel directement au-dessus du formulaire :

      project/templates/login.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/login">
      

      Nous avons maintenant la possibilité de dire qu’un utilisateur a été connecté avec succès, mais il n’y a rien pour connecter l’utilisateur. C’est là que nous introduisons Flask-Login pour gérer les sessions des utilisateurs.

      Avant de commencer, nous avons besoin de quelques éléments pour que Flask-Login fonctionne. Commencez par ajouter le UserMixin à votre modèle User. Le UserMixin ajoutera les attributs de Flask-Login au modèle afin que Flask-Login puisse travailler avec lui.

      models.py

      from flask_login import UserMixin
      from . import db
      
      class User(UserMixin, db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Ensuite, nous devons spécifier notre chargeur d’utilisateurs. Un chargeur d’utilisateur indique à Flask-Login comment trouver un utilisateur spécifique à partir de son identifiant stocké dans son cookie de session. Nous pouvons l’ajouter dans notre create_app ainsi que le code init du Flask-Login :

      project/__init__.py

      ...
      from flask_login import LoginManager
      ...
      def create_app():
          ...
          db.init_app(app)
      
          login_manager = LoginManager()
          login_manager.login_view = 'auth.login'
          login_manager.init_app(app)
      
          from .models import User
      
          @login_manager.user_loader
          def load_user(user_id):
              # since the user_id is just the primary key of our user table, use it in the query for the user
              return User.query.get(int(user_id))
      

      Enfin, nous pouvons ajouter le login_user juste avant de rediriger vers la page de profil pour créer la session :

      project/auth.py

      from flask_login import login_user
      from .models import User
      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          ...
          # if the above check passes, then we know the user has the right credentials
          login_user(user, remember=remember)
          return redirect(url_for('main.profile'))
      

      Avec la configuration de Flask-Login, nous pouvons utiliser l’itinéraire /login. Lorsque tout est en place, vous verrez la page de profil.

      Page de profil avec « Welcome, Anthony! »

      Étape 10 — Protéger les pages

      Si votre nom n’est pas aussi Anthony, alors vous verrez que votre nom est faux. Ce que nous voulons, c’est que le profil affiche le nom dans la base de données. Nous devons donc d’abord protéger la page et ensuite accéder aux données de l’utilisateur pour obtenir le nom.

      Pour protéger une page lors de l’utilisation de Flask-Login, nous ajoutons le décorateur @login_required entre l’itinéraire et la fonction. Cela empêchera un utilisateur qui n’est pas connecté de voir l’itinéraire. Si l’utilisateur n’est pas connecté, il sera redirigé vers la page de connexion, selon la configuration du Flask-Login.

      Avec les itinéraires qui sont décorés avec le décorateur @login_required, nous avons alors la possibilité d’utiliser l’objet current_user à l’intérieur de la fonction. Ce current_user représente l’utilisateur de la base de données, et nous pouvons accéder à tous les attributs de cet utilisateur avec la notation par points. Par exemple, current_user.email, current_user.password, et current_user.name, et current_user.id renverront les valeurs réelles stockées dans la base de données pour l’utilisateur connecté.

      Utilisons le nom de l’utilisateur actuel et envoyons-le au modèle. Nous utiliserons alors ce nom et afficherons sa valeur.

      project/main.py

      from flask_login import login_required, current_user
      ...
      @main.route('/profile')
      @login_required
      def profile():
          return render_template('profile.html', name=current_user.name)
      

      Ensuite, dans le fichier profile.html, mettez à jour la page pour afficher la valeur name :

      project/templates/profile.html

      ...
      <h1 class="title">
        Welcome, {{ name }}!
      </h1>
      

      Une fois que nous nous rendons sur notre page de profil, nous voyons alors que le nom de l’utilisateur apparaît.

      Page d'accueil de l'utilisateur avec le nom de l'utilisateur actuellement connecté

      La dernière chose que nous pouvons faire est de mettre à jour la vue de déconnexion. Nous pouvons appeler le logout_user dans un itinéraire de déconnexion. Nous avons le décorateur @login_required parce qu’il n’est pas logique de déconnecter un utilisateur qui n’est pas connecté au départ.

      project/auth.py

      from flask_login import login_user, logout_user, login_required
      ...
      @auth.route('/logout')
      @login_required
      def logout():
          logout_user()
          return redirect(url_for('main.index'))
      

      Lorsque nous nous déconnectons et que nous essayons de consulter à nouveau la page de profil, nous voyons apparaître un message d’erreur. C’est parce que Flask-Login fait clignoter un message pour nous lorsque l’utilisateur n’est pas autorisé à accéder à une page.

      Page de connexion avec un message indiquant que l'utilisateur doit se connecter pour accéder à la page

      Une dernière chose que nous pouvons faire est de mettre des déclarations if dans les modèles pour afficher uniquement les liens pertinents pour l’utilisateur. Ainsi, avant que l’utilisateur ne se connecte, il aura la possibilité de se connecter ou de s’inscrire. Une fois que l’on se connecte, on peut aller sur son profil ou se déconnecter :

      templates/base.html

      ...
      <div class="navbar-end">
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
              Home
          </a>
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
              Profile
          </a>
          {% endif %}
          {% if not current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
              Login
          </a>
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
              Sign Up
          </a>
          {% endif %}
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
              Logout
          </a>
          {% endif %}
      </div>
      

      Page d'accueil avec la navigation « Home, Login, and Sign Up» en haut de l'écran

      Grâce à cela, vous avez réussi à construire votre application avec authentification.

      Conclusion

      Nous avons utilisé Flask-Login et Flask-SQLAlchemy afin de créer un système de connexion pour notre app. Nous avons abordé la manière d’authentifier un utilisateur en créant d’abord un modèle d’utilisateur et en stockant les informations sur l’utilisateur. Ensuite, nous avons dû vérifier que le mot de passe de l’utilisateur était correct en hachant le mot de passe du formulaire et en le comparant à celui stocké dans la base de données. Enfin, nous avons ajouté l’autorisation à notre app en utilisant le décorateur @login_required sur une page de profil afin que seuls les utilisateurs connectés puissent voir cette page.

      Ce que nous avons créé dans ce tutoriel sera suffisant pour les petites applications, mais si vous souhaitez avoir plus de fonctionnalités dès le début, vous pouvez envisager d’utiliser les bibliothèques Flask-User ou Flask-Security qui sont toutes deux construites sur la bibliothèque Flask-Login.



      Source link

      Comment créer une pagination personnalisée avec React


      Introduction

      Nous nous impliquons souvent dans la construction d’applications web où nous devons récupérer de grands ensembles de données à partir d’un serveur distant, d’une API ou d’une base de données. Si vous mettez en place un système de paiement, par exemple, il peut aller chercher des milliers de transactions. S’il s’agit d’une app de réseaux sociaux, elle peut récupérer de nombreux commentaires, profils ou activités d’utilisateurs. Quoi qu’il en soit, il existe plusieurs solutions pour présenter les données d’une manière qui ne submerge pas l’utilisateur final qui interagit avec l’app.

      Une méthode pour traiter de grands ensembles de données consiste à utiliser la pagination. La pagination fonctionne efficacement lorsque vous connaissez à l’avance la taille de l’ensemble de données (le nombre total d’enregistrements contenu dans l’ensemble de données). Deuxièmement, vous ne chargez que le morceau de données requis de l’ensemble des données en fonction de l’interaction de l’utilisateur final avec le contrôle de pagination. C’est la technique utilisée pour afficher les résultats de recherche dans Google Search.

      Dans ce tutoriel, vous apprendrez comment construire un composant de pagination personnalisé avec React pour la pagination de grands ensembles de données. Vous construirez une vue paginée des pays du monde – un ensemble de données dont la taille est connue.

      Voici une démo de ce que vous allez construire dans ce tutoriel :

      Capture d'écran de l'application de démo - montrant les pays du monde

      Conditions préalables

      Pour suivre ce tutoriel, vous aurez besoin de :

      Ce tutoriel a été vérifié avec Node v14.2.0, npm v6.14.4, react v16.13.1, et react-scripts v3.4.1.

      Étape 1 – Mettre en place le projet

      Lancez une nouvelle application React en utilisant la commande create-react-app. Vous pouvez nommer l’application comme vous le souhaitez, mais ce tutoriel lui donnera le nom react-pagination :

      • npx create-react-app react-pagination

      Ensuite, vous installerez les dépendances nécessaires pour votre application. Tout d’abord, utilisez la fenêtre du terminal pour accéder au répertoire du projet :

      Exécutez la commande suivante pour installer les dépendances requises :

      • npm install bootstrap@4.1.0 prop-types@15.6.1 react-flags@0.1.13 countries-api@2.0.1 node-sass@4.14.1

      Vous installerez alors bootstrap, prop-types, react-flags, countries-api et node-sass.

      Vous avez installé le bootstrap comme une dépendance de votre demande, car vous aurez besoin d’un style par défaut. Vous utiliserez également les styles du composant de pagination Bootstrap.

      Pour inclure Bootstrap dans l’application, modifiez le fichier src/index.js :

      Et ajoutez la ligne suivante avant les autres déclarations d’import :

      src/index.js

      import "bootstrap/dist/css/bootstrap.min.css";
      

      Maintenant, le style Bootstrap sera disponible dans toute votre application.

      Vous avez également installé react-flags comme une dépendance de votre application. Afin d’accéder aux icônes des drapeaux à partir de votre application, vous devez copier les images des icônes dans le répertoire public de votre application.

      Créez un répertoire img dans votre répertoire public :

      Copiez les fichiers images dans flags vers img :

      • cp -R node_modules/react-flags/vendor/flags public/img

      Ce document fournit une copie de tous les react-flag à votre application.

      Maintenant que vous avez inclus quelques dépendances, lancez l’application en exécutant la commande suivante avec npm de la pagination du répertoire du projet react-pagination :

      Maintenant que vous avez lancé l’application, le développement peut commencer. Vous remarquerez qu’un onglet du navigateur a été ouvert pour vous avec la fonctionnalité de rechargement live pour rester en phase avec l’application au fur et à mesure de son développement.

      À ce stade, la vue de l’application devrait ressembler à la capture d’écran suivante :

      Vue initiale - Bienvenue sur React Screen

      Vous êtes maintenant prêt à commencer à créer des composants.

      Étape 2 – Créer le composant CountryCard

      Dans cette étape, vous allez créer le composant CountryCard. Le composant CountryCard génère le nom, la région et le drapeau d’un pays donné.

      Tout d’abord, créons un composant dans le répertoire src :

      Ensuite, créez une nouvelle CountryCard.js dans le répertoire src/components :

      • nano src/components/CountryCard.js

      Et ajoutez-y le code suivant :

      src/components/CountryCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import Flag from 'react-flags';
      
      const CountryCard = props => {
        const {
          cca2: code2 = '', region = null, name = {}
        } = props.country || {};
      
        return (
          <div className="col-sm-6 col-md-4 country-card">
            <div className="country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light">
              <div className="h-100 position-relative border-gray border-right px-2 bg-white rounded-left">
                <Flag country={code2} format="png" pngSize={64} basePath="./img/flags" className="d-block h-100" />
              </div>
              <div className="px-3">
                <span className="country-name text-dark d-block font-weight-bold">{ name.common }</span>
                <span className="country-region text-secondary text-uppercase">{ region }</span>
              </div>
            </div>
          </div>
        )
      }
      
      CountryCard.propTypes = {
        country: PropTypes.shape({
          cca2: PropTypes.string.isRequired,
          region: PropTypes.string.isRequired,
          name: PropTypes.shape({
            common: PropTypes.string.isRequired
          }).isRequired
        }).isRequired
      };
      
      export default CountryCard;
      

      Le composant CountryCard nécessite un prop country qui contient les données sur le pays à afficher. Comme on l’a vu dans le module propTypes pour le composant CountryCard, l’objet prop country doit contenir les données suivantes :

      • cca2 – un code de pays à deux chiffres
      • region – la région du pays (par exemple, « Africa »)
      • name.common – le nom commun du pays (par exemple, « Nigeria »)

      Voici un exemple d’objet country :

      {
        cca2: "NG",
        region: "Africa",
        name: {
          common: "Nigeria"
        }
      }
      

      Notez également comment vous rendez le drapeau du pays en utilisant le paquet react-flags. Vous pouvez consulter la documentation react-flags pour en savoir plus sur les props nécessaires et sur la façon d’utiliser le paquet.

      Vous avez maintenant un composant CountryCard complété. Enfin, vous utiliserez CountryCard plusieurs fois pour afficher dans votre application différents drapeaux et différentes informations sur le pays.

      Dans cette étape, vous créerez le composant Pagination. Le composant Pagination contient la logique de construction, de rendu et de commutation des pages sur le contrôle de pagination.

      Ensuite, créez une nouvelle Pagination.js dans le répertoire src/components :

      • nano src/components/Pagination.js

      Et ajoutez-y le code suivant :

      src/components/Pagination.js

      import React, { Component, Fragment } from 'react';
      import PropTypes from 'prop-types';
      
      class Pagination extends Component {
        constructor(props) {
          super(props);
          const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props;
      
          this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 30;
          this.totalRecords = typeof totalRecords === 'number' ? totalRecords : 0;
      
          // pageNeighbours can be: 0, 1 or 2
          this.pageNeighbours = typeof pageNeighbours === 'number'
            ? Math.max(0, Math.min(pageNeighbours, 2))
            : 0;
      
          this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      
          this.state = { currentPage: 1 };
        }
      }
      
      Pagination.propTypes = {
        totalRecords: PropTypes.number.isRequired,
        pageLimit: PropTypes.number,
        pageNeighbours: PropTypes.number,
        onPageChanged: PropTypes.func
      };
      
      export default Pagination;
      

      Le volet Pagination peut prendre quatre props spéciaux, comme indiqué dans l’objet propTypes.

      • onPageChanged est une fonction appelée avec les données de l’état de pagination actuel uniquement lorsque la page en cours change.
      • totalRecords indique le nombre total de documents à paginer. C’est un obligatoire.
      • pageLimit indique le nombre d’enregistrements à afficher par page. Si elle n’est pas spécifiée, la valeur par défaut est 30 tel que défini dans la constructor().
      • pageNeighbours indique le nombre de numéros de page supplémentaires à afficher de chaque côté de la page en cours. La valeur minimale est 0 et la valeur maximale est 2. Si elle n’est pas spécifiée, la valeur par défaut est 0, comme définie dans la fonction constructor().

      L’image suivante illustre l’effet des différentes valeurs du prop pageNeighbours :

      Illustration de la page Neighbours

      Dans la fonction constructor(), vous calculez le nombre total de pages comme suit :

      this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      

      Vous remarquerez que vous utilisez la fonctionMath.ceil() ici pour vous assurer que vous obtenez une valeur entière pour le nombre total de pages. Cela garantit également que les documents excédentaires soient saisis à la dernière page, en particulier dans les cas où le nombre de documents excédentaires est inférieur au nombre de documents à afficher par page.

      Enfin, vous avez initialisé l’état avec la propriété currentPage réglée sur 1. Vous avez besoin de cette propriété d’état pour garder une trace de la page actuellement active en interne.

      Ensuite, vous créerez la méthode pour générer les numéros de page.

      Après les importations mais avant la classe Pagination, ajoutez les constantes et la fonction de plage suivantes :

      src/components/Pagination.js

      // ...
      
      const LEFT_PAGE = 'LEFT';
      const RIGHT_PAGE = 'RIGHT';
      
      /**
       * Helper method for creating a range of numbers
       * range(1, 5) => [1, 2, 3, 4, 5]
       */
      const range = (from, to, step = 1) => {
        let i = from;
        const range = [];
      
        while (i <= to) {
          range.push(i);
          i += step;
        }
      
        return range;
      }
      

      Dans la classe Pagination, après la fonction constructor, ajoutez la méthode fetchPageNumbers qui suit :

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        /**
         * Let's say we have 10 pages and we set pageNeighbours to 2
         * Given that the current page is 6
         * The pagination control will look like the following:
         *
         * (1) < {4 5} [6] {7 8} > (10)
         *
         * (x) => terminal pages: first and last page(always visible)
         * [x] => represents current page
         * {...x} => represents page neighbours
         */
        fetchPageNumbers = () => {
          const totalPages = this.totalPages;
          const currentPage = this.state.currentPage;
          const pageNeighbours = this.pageNeighbours;
      
          /**
           * totalNumbers: the total page numbers to show on the control
           * totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls
           */
          const totalNumbers = (this.pageNeighbours * 2) + 3;
          const totalBlocks = totalNumbers + 2;
      
          if (totalPages > totalBlocks) {
            const startPage = Math.max(2, currentPage - pageNeighbours);
            const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);
            let pages = range(startPage, endPage);
      
            /**
             * hasLeftSpill: has hidden pages to the left
             * hasRightSpill: has hidden pages to the right
             * spillOffset: number of hidden pages either to the left or to the right
             */
            const hasLeftSpill = startPage > 2;
            const hasRightSpill = (totalPages - endPage) > 1;
            const spillOffset = totalNumbers - (pages.length + 1);
      
            switch (true) {
              // handle: (1) < {5 6} [7] {8 9} (10)
              case (hasLeftSpill && !hasRightSpill): {
                const extraPages = range(startPage - spillOffset, startPage - 1);
                pages = [LEFT_PAGE, ...extraPages, ...pages];
                break;
              }
      
              // handle: (1) {2 3} [4] {5 6} > (10)
              case (!hasLeftSpill && hasRightSpill): {
                const extraPages = range(endPage + 1, endPage + spillOffset);
                pages = [...pages, ...extraPages, RIGHT_PAGE];
                break;
              }
      
              // handle: (1) < {4 5} [6] {7 8} > (10)
              case (hasLeftSpill && hasRightSpill):
              default: {
                pages = [LEFT_PAGE, ...pages, RIGHT_PAGE];
                break;
              }
            }
      
            return [1, ...pages, totalPages];
          }
      
          return range(1, totalPages);
        }
      }
      

      Ici, vous définissez d’abord deux constantes : LEFT_PAGE et RIGHT_PAGE. Ces constantes seront utilisées pour indiquer les points où vous avez des contrôles de page pour vous déplacer respectivement à gauche et à droite.

      Vous avez également défini une aide avec la fonctionrange() qui peut vous aider à générer des séries de chiffres.

      Note : Si vous utilisez une bibliothèque d’utilité comme Lodashdans votre projet, vous pouvez alors utiliser la fonction _.range() fournie par Lodash à la place. L’extrait de code suivant montre la différence entre la fonction range() que vous venez de définir et celle de Lodash :

      range(1, 5); // returns [1, 2, 3, 4, 5]
      _.range(1, 5); // returns [1, 2, 3, 4]
      

      Ensuite, vous avez défini la méthode fetchPageNumbers() dans la classe Pagination. Cette méthode gère la logique de base pour générer les numéros de page à afficher sur le contrôle de pagination. Vous voulez que la première page et la dernière page soient toujours visibles.

      Tout d’abord, vous avez défini quelques variables. totalNumbers représente le nombre total de pages qui seront affichées sur le contrôle. totalBlocks représente le nombre total de pages à afficher, plus deux blocs supplémentaires pour les indicateurs de page gauche et droite.

      Si le nombre totalPages n’est pas supérieur à totalBlocks, vous renvoyez une série de chiffres allant de 1 à totalPages. Sinon, vous renvoyez le tableau des numéros de page, avec LEFT_PAGE et RIGHT_PAGE aux endroits où vous avez des pages qui débordent respectivement à gauche et à droite.

      Toutefois, notez que votre contrôle de pagination garantit que la première page et la dernière page sont toujours visibles. Les contrôles des pages gauche et droite apparaissent vers l’intérieur.

      Maintenant, vous allez ajouter la fonction render() pour vous permettre de contrôler la pagination.

      Dans la classe Pagination, après la méthode constructor et fetchPageNumbers, ajoutez la méthode render suivante :

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        render() {
          if (!this.totalRecords || this.totalPages === 1) return null;
      
          const { currentPage } = this.state;
          const pages = this.fetchPageNumbers();
      
          return (
            <Fragment>
              <nav aria-label="Countries Pagination">
                <ul className="pagination">
                  { pages.map((page, index) => {
      
                    if (page === LEFT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Previous" onClick={this.handleMoveLeft}>
                          <span aria-hidden="true">&laquo;</span>
                          <span className="sr-only">Previous</span>
                        </a>
                      </li>
                    );
      
                    if (page === RIGHT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Next" onClick={this.handleMoveRight}>
                          <span aria-hidden="true">&raquo;</span>
                          <span className="sr-only">Next</span>
                        </a>
                      </li>
                    );
      
                    return (
                      <li key={index} className={`page-item${ currentPage === page ? ' active' : ''}`}>
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" onClick={ this.handleClick(page) }>{ page }</a>
                      </li>
                    );
      
                  }) }
      
                </ul>
              </nav>
            </Fragment>
          );
        }
      }
      

      Ici, vous générez le tableau des numéros de page en appelant la fonction fetchPageNumbers() que vous avez créée précédemment. Vous générez ensuite chaque numéro de page en utilisant Array.prototype.map(). Notez que vous enregistrez des gestionnaires d’événements de clic sur chaque numéro de page généré pour gérer les clics.

      Notez également que le contrôle de la pagination ne sera pas effectué si le proptotalRecords n’a pas été correctement transmis au composant Pagination ou dans les cas où il n’y a qu’une page 1.

      Enfin, vous définirez les méthodes de gestion des événements.

      Dans la classe Pagination, après la méthode con fetchPageNumbers et la méthode render, ajoutez ce qui suit :

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        componentDidMount() {
          this.gotoPage(1);
        }
      
        gotoPage = page => {
          const { onPageChanged = f => f } = this.props;
          const currentPage = Math.max(0, Math.min(page, this.totalPages));
          const paginationData = {
            currentPage,
            totalPages: this.totalPages,
            pageLimit: this.pageLimit,
            totalRecords: this.totalRecords
          };
      
          this.setState({ currentPage }, () => onPageChanged(paginationData));
        }
      
        handleClick = page => evt => {
          evt.preventDefault();
          this.gotoPage(page);
        }
      
        handleMoveLeft = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage - (this.pageNeighbours * 2) - 1);
        }
      
        handleMoveRight = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage + (this.pageNeighbours * 2) + 1);
        }
      }
      

      Vous définissez la méthode gotoPage() qui modifie l’état et met currentPage à la page spécifiée. Elle garantit que l’argument de la page a une valeur minimale de 1 et une valeur maximale du nombre total de pages. Il appelle enfin la fonction onPageChanged() qui a été transmise comme prop, avec des données indiquant le nouvel état de pagination.

      Lorsque le composant est monté, vous passez à la première page en appelant this.gotoPage(1) comme indiqué dans la méthode de cycle de vie componentDidMount().

      Remarquez comment vous utilisez (this.pageNeighbours * 2) dans handleMoveLeft() et handleMoveRight() pour faire glisser les numéros de page vers la gauche et vers la droite, respectivement, en fonction du numéro de page en cours.

      Voici une démonstration de l’interaction du mouvement de gauche à droite.

      Mouvement gauche-droite de l'interaction

      Vous avez maintenant terminé le composant Pagination. Les utilisateurs pourront interagir avec les commandes de navigation de ce composant pour afficher différentes pages de drapeaux.

      Étape 4 – Création du composant App

      Maintenant que vous avez un composant CountryCard et Pagination, vous pouvez les utiliser dans votre composant App.

      Modifiez le fichier App.js dans le répertoire src :

      Remplacez le contenu de App.js par les lignes de code suivantes :

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.css';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      
      class App extends Component {
        state = { allCountries: [], currentCountries: [], currentPage: null, totalPages: null }
      
        componentDidMount() {
          const { data: allCountries = [] } = Countries.findAll();
          this.setState({ allCountries });
        }
      
        onPageChanged = data => {
          const { allCountries } = this.state;
          const { currentPage, totalPages, pageLimit } = data;
          const offset = (currentPage - 1) * pageLimit;
          const currentCountries = allCountries.slice(offset, offset + pageLimit);
      
          this.setState({ currentPage, currentCountries, totalPages });
        }
      }
      
      export default App;
      

      Ici, vous initialisez l’état du composant App avec les attributs suivants :

      • allCountries – Voici un tableau de tous les pays qui figurent dans votre app. Initialisé vers un tableau vide ([]).
      • currentCountries – Il s’agit d’un tableau de tous les pays qui doivent être affichés sur la page actuellement active. Initialisé vers un tableau vide ([]).
      • currentPage – Le numéro de page de la page actuellement active. Initialisé vers la valeur null.
      • totalPages – Le nombre total de pages pour l’ensemble des fiches pays. Initialisé vers la valeur null.

      Ensuite, dans la méthode du cycle de vie de componentDidMount(), vous récupérez tous les pays du monde en utilisant le paquet countries-api en invoquant Countries.findAll(). Vous mettez ensuite à jour l’état de l’application, en définissant allCountries pour contenir tous les pays du monde. Vous pouvez voir la documentation countries-apipour en savoir plus sur le paquet.

      Enfin, vous avez défini la méthode onPageChanged() qui sera appelée chaque fois que vous naviguerez vers une nouvelle page à partir du contrôle de pagination. Cette méthode sera transmise à la prop onPageChanged du composant Pagination.

      Il y a deux lignes auxquelles il convient de prêter attention dans cette méthode. La première est cette ligne :

      const offset = (currentPage - 1) * pageLimit;
      

      La valeur offset indique l’index de départ pour récupérer les enregistrements de la page en cours. En utilisant (currentPage - 1), on s’assure que la valeur offset est basée sur zéro. Disons, par exemple, que vous affichez 25 enregistrements par page, et que vous êtes en train de consulter la page 5. La valeur offset sera alors de ((5 - 1) * 25 = 100).

      Par exemple, si vous récupérez des enregistrements sur demande à partir d’une base de données, voici un exemple de requête SQL pour vous montrer comment utiliser la valeur offset :

      SELECT * FROM `countries` LIMIT 100, 25
      

      Comme vous ne récupérez pas les enregistrements d’une base de données ou d’une source externe, vous avez besoin d’un moyen d’extraire le bloc d’enregistrements requis pour la page en cours.

      La deuxième est cette ligne :

      const currentCountries = allCountries.slice(offset, offset + pageLimit);
      

      Ici, vous utilisez la méthode Array.prototype.slice() pour extraire le bloc d’enregistrements requis de allCountries en passant la valeur offset comme indice de départ de la tranche et (offset + pageLimit) comme indice avant lequel la tranche doit se terminer.

      Note : Dans ce tutoriel, vous n’avez pas récupéré d’enregistrements provenant d’une source externe. Dans une véritable application, vous irez probablement chercher des enregistrements dans une base de données ou une API. La logique de récupération des enregistrements peut être consultée dans la méthodeonPageChanged() du composant App.

      Supposons que vous ayez un point final API fictif /api/countries?page={current_page}&limit={page_limit}. L’extrait suivant montre comment vous pouvez récupérer des pays à la demande à partir de l’API en utilisant le paquet HTTP [axios][“axios] :

      onPageChanged = data => {
        const { currentPage, totalPages, pageLimit } = data;
      
        axios.get(`/api/countries?page=${currentPage}&limit=${pageLimit}`)
          .then(response => {
            const currentCountries = response.data.countries;
            this.setState({ currentPage, currentCountries, totalPages });
          });
      }
      

      Maintenant, vous pouvez achever le composantApp en ajoutant la méthode render().

      Dans la classe App, mais après componentDidMount et onPageChanged, ajoutez la méthode render suivante :

      src/App.js

      class App extends Component {
        // ... other methods here ...
      
        render() {
          const { allCountries, currentCountries, currentPage, totalPages } = this.state;
          const totalCountries = allCountries.length;
      
          if (totalCountries === 0) return null;
      
          const headerClass = ['text-dark py-2 pr-4 m-0', currentPage ? 'border-gray border-right' : ''].join(' ').trim();
      
          return (
            <div className="container mb-5">
              <div className="row d-flex flex-row py-5">
                <div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">
                  <div className="d-flex flex-row align-items-center">
                    <h2 className={headerClass}>
                      <strong className="text-secondary">{totalCountries}</strong> Countries
                    </h2>
                    { currentPage && (
                      <span className="current-page d-inline-block h-100 pl-4 text-secondary">
                        Page <span className="font-weight-bold">{ currentPage }</span> / <span className="font-weight-bold">{ totalPages }</span>
                      </span>
                    ) }
                  </div>
                  <div className="d-flex flex-row py-4 align-items-center">
                    <Pagination totalRecords={totalCountries} pageLimit={18} pageNeighbours={1} onPageChanged={this.onPageChanged} />
                  </div>
                </div>
                { currentCountries.map(country => <CountryCard key={country.cca3} country={country} />) }
              </div>
            </div>
          );
        }
      }
      

      Dans la méthode render(), vous générez le nombre total de pays, la page en cours, le nombre total de pages, le contrôle <Pagination>, puis la <CountryCard> pour chaque pays de la page en cours.

      Vous remarquerez que vous avez passé la méthode onPageChanged() que vous avez définie précédemment au prop onPageChanged du contrôle de <Pagination>. Ceci est très important pour la saisie des changements de page à partir du composant Pagination. Vous affichez également 18 pays par page.

      À ce stade, l’application ressemblera à la capture d’écran suivante :

      Capture d'écran de l'application avec 248 pays listés et les numéros de page indiqués en haut pour parcourir chaque page

      Vous avez maintenant une App qui affiche plusieurs composants CountryCard et uncomposant Pagination qui décompose le contenu en pages séparées. Ensuite, vous étudierez le style de votre application.

      Étape 5 – Ajout de styles personnalisés

      Vous avez peut-être remarqué que vous avez ajouté des classes personnalisées aux composants que vous avez créés précédemment. Définissons quelques règles de style pour ces classes dans le fichier src/App.scss.

      Le fichierApp.scss ressemblera à l’extrait suivant :

      src/App.scss

      /* Declare some variables */
      $base-color: #ced4da;
      $light-background: lighten(desaturate($base-color, 50%), 12.5%);
      
      .current-page {
        font-size: 1.5rem;
        vertical-align: middle;
      }
      
      .country-card-container {
        height: 60px;
        cursor: pointer;
        position: relative;
        overflow: hidden;
      }
      
      .country-name {
        font-size: 0.9rem;
      }
      
      .country-region {
        font-size: 0.7rem;
      }
      
      .current-page,
      .country-name,
      .country-region {
        line-height: 1;
      }
      
      // Override some Bootstrap pagination styles
      ul.pagination {
        margin-top: 0;
        margin-bottom: 0;
        box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
      
        li.page-item.active {
          a.page-link {
            color: saturate(darken($base-color, 50%), 5%) !important;
            background-color: saturate(lighten($base-color, 7.5%), 2.5%) !important;
            border-color: $base-color !important;
          }
        }
      
        a.page-link {
          padding: 0.75rem 1rem;
          min-width: 3.5rem;
          text-align: center;
          box-shadow: none !important;
          border-color: $base-color !important;
          color: saturate(darken($base-color, 30%), 10%);
          font-weight: 900;
          font-size: 1rem;
      
          &:hover {
            background-color: $light-background;
          }
        }
      }
      

      Modifiez votre fichierApp.jspour référencer App.scss au lieu de App.css.

      Remarque : Pour plus d’informations à ce sujet, voir la documentation de l’application Create React.

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.scss';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      

      Après avoir ajouté les styles, l’application ressemblera maintenant à la capture d’écran suivante :

      Capture d'écran de l'app, page 1 sur 14, avec les styles

      Vous avez maintenant une application complète avec un style personnalisé supplémentaire. Vous pouvez utiliser des styles personnalisés pour modifier et améliorer les styles par défaut fournis par des bibliothèques comme Bootstrap.

      Conclusion

      Dans ce tutoriel, vous avez créé un widget de pagination personnalisé dans votre application React. Bien que vous n’ayez pas fait d’appel à une API ou interagi avec une base de données dans ce tutoriel, votre application peut exiger de telles interactions. Vous n’êtes en aucun cas limité à l’approche utilisée dans ce tutoriel – vous pouvez l’étendre comme vous le souhaitez pour répondre aux exigences de votre application.

      Pour obtenir le code source complet de ce tutoriel, consultez le dépôt build-react-pagination-demo sur GitHub. Vous pouvez également obtenir une démo live de ce tutoriel sur Code Sandbox.

      Si vous souhaitez en savoir plus sur React, consultez notre série Comment coder dans React.js ou consultez notre page thématique React pour des exercices et des projets de programmation.



      Source link