<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>DOTPROGS</title>
	<atom:link href="https://www.dotprogs.com/author/dotprogs/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>DOTPROGS - Conception de sites web</description>
	<lastBuildDate>Tue, 19 Mar 2024 19:23:07 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.dotprogs.com/wp-content/uploaded-files/cropped-dotprogs-favicon-32x32.png</url>
	<title>DOTPROGS</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>www.abeille-infos.com, les premières fondations du site sous Drupal !</title>
		<link>https://www.dotprogs.com/un-futur-site-web-pour-les-abeilles/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Sun, 12 Sep 2021 20:04:00 +0000</pubDate>
				<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[Projet personnel]]></category>
		<category><![CDATA[Webdesign]]></category>
		<category><![CDATA[abeille]]></category>
		<category><![CDATA[actualité]]></category>
		<category><![CDATA[apiculture]]></category>
		<category><![CDATA[apprendre]]></category>
		<category><![CDATA[comprendre]]></category>
		<category><![CDATA[Drupal 9+]]></category>
		<category><![CDATA[environnement]]></category>
		<category><![CDATA[informations.]]></category>
		<category><![CDATA[menaces]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[planète]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=1</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/abeille-infos.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Nouveau site web abeille-infos.com" decoding="async" fetchpriority="high" /></p>
<h2 class="dp-level2-title">L'abeille comme révélateur des problématiques environnementales</h2>
<h3 class="dp-level3-title">Des sujets très vastes portés par ce merveilleux insecte.</h3>
<p>En attendant la création du prochain site web www.abeille-infos.com en cours de réalisation, sans doute pour un certain nombre de semaines, j'ai défini également son identité avec un logo évocateur en ce qui concerne son objectif.<br />
Bien que ce logo ne soit pas encore définitif, notamment au niveau des couleurs choisies, je considère qu'il me convient déjà en l'état, car il illustre bien la volonté d'informer et traiter l'actualité autour de l'abeille, comme témoin des évolutions.</p>
<p>Il sera aussi question d'aborder les causes et conséquences de l'activité de l'homme sur la planète avec l'abeille comme sentinelle involontaire et aux avant-postes.</p>
<p>D'autres thématiques seront abordées comme bien sûr l'apiculture responsable et davantage respectueuse de l'insecte, les menaces qui pèse sur sa disparition et les effets constatés, quelques bonnes adresses pour trouver du miel de qualité directement auprès du producteur, des annuaires spécifiques ...<br />
Mais aussi des articles proposeront de s'initier à l'apiculture en tant que débutant, de manière simple et sans prétention, pour contribuer à son niveau, au maintien de l'existence des abeilles. La mise en avant d'actions associatives ou autres en faveur de l'abeille feront l'objet de toute mon attention.</p>
<p>Bref, des mauvaises nouvelles en guise de prise de conscience, mais aussi surtout des approches positives mises en exergue qui veulent donner un avenir à l'abeille et à la planète !</p>
<p>Le futur site web est actuellement développé sous Drupal 9+, ceci afin de joindre ma formation technique et mes convictions.</p>
<p>Cet article <a href="https://www.dotprogs.com/un-futur-site-web-pour-les-abeilles/">www.abeille-infos.com, les premières fondations du site sous Drupal !</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/abeille-infos.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Nouveau site web abeille-infos.com" decoding="async" /></p><h2 class="dp-level2-title">L'abeille comme révélateur des problématiques environnementales</h2>
<h3 class="dp-level3-title">Des sujets très vastes portés par ce merveilleux insecte.</h3>
En attendant la création du prochain site web www.abeille-infos.com en cours de réalisation, sans doute pour un certain nombre de semaines, j'ai défini également son identité avec un logo évocateur en ce qui concerne son objectif.
Bien que ce logo ne soit pas encore définitif, notamment au niveau des couleurs choisies, je considère qu'il me convient déjà en l'état, car il illustre bien la volonté d'informer et traiter l'actualité autour de l'abeille, comme témoin des évolutions.

Il sera aussi question d'aborder les causes et conséquences de l'activité de l'homme sur la planète avec l'abeille comme sentinelle involontaire et aux avant-postes.

D'autres thématiques seront abordées comme bien sûr l'apiculture responsable et davantage respectueuse de l'insecte, les menaces qui pèse sur sa disparition et les effets constatés, quelques bonnes adresses pour trouver du miel de qualité directement auprès du producteur, des annuaires spécifiques ...
Mais aussi des articles proposeront de s'initier à l'apiculture en tant que débutant, de manière simple et sans prétention, pour contribuer à son niveau, au maintien de l'existence des abeilles. La mise en avant d'actions associatives ou autres en faveur de l'abeille feront l'objet de toute mon attention.

Bref, des mauvaises nouvelles en guise de prise de conscience, mais aussi surtout des approches positives mises en exergue qui veulent donner un avenir à l'abeille et à la planète !

Le futur site web est actuellement développé sous Drupal 9+, ceci afin de joindre ma formation technique et mes convictions.<p>Cet article <a href="https://www.dotprogs.com/un-futur-site-web-pour-les-abeilles/">www.abeille-infos.com, les premières fondations du site sous Drupal !</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Exemple d&#8217;utilisation d&#8217;un « token CSRF » sans formulaire avec Symfony</title>
		<link>https://www.dotprogs.com/exemple-utilisation-token-csrf-sans-formulaire-avec-symfony/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Thu, 20 Feb 2020 15:50:20 +0000</pubDate>
				<category><![CDATA[Développement web]]></category>
		<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[CSRF]]></category>
		<category><![CDATA[sécurité]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[Symfony]]></category>
		<category><![CDATA[token]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=991</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/bout-de-code0120.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Bout de code - Token CSRF sans formulaire avec Symfony" decoding="async" /></p>
<p>L'idée, ici, est de présenter l'usage d'un "token" (jeton) pour se prémunir d'une attaque <a href="https://fr.wikipedia.org/wiki/Cross-site_request_forgery" title="Présentation Cross Site Request Forgery" target="_blank" rel="noopener noreferrer nofollow">CSRF</a> (Cross Site Request Forgery) en utilisant le framework Symfony 4+ (doit pouvoir fonctionner en Sf 3 également à peu de chose près) mais sans s'appuyer sur un formulaire.</p>
<p>En effet les tokens CSRF sont proposés pour la sécurité d'un formulaire Symfony, donc l'objectif de ma démarche avec ce "bout de code" est de mettre en place la même sécurité, en utilisant un simple lien ou un bouton HTML par exemple avec une requête "DELETE" en AJAX pour appeler l'action dédiée "<strong>DeleteMemberAction</strong>" ci-dessous.<br />
<strong><u>Note :</u> utiliser une requête en "GET" pour une suppression de ressource est une mauvaise pratique d'un point de vue</strong> <a href="https://developer.mozilla.org/fr/docs/Web/HTTP/Resources_and_specifications" title="Protocole HTTP" target="_blank" rel="noopener noreferrer nofollow">HTTP</a><strong>.</strong></p>
<p>Cet exemple tient compte du fait que l'utilisateur à l'origine de l'action est <strong>normalement authentifié</strong> sur un espace sécurisé, ce qui fait le danger par principe d'une attaque CSRF.<br />
Un compte "membre" doit être supprimé, ce que l'on peut considérer comme une opération sensible !</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php

declare(strict_types = 1);

namespace App\Action;

use App\Responder\DeleteMemberResponder; 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

/**
 * Class DeleteMemberAction.
 *
 * Delete a member account.
 */
class DeleteMemberAction
{
    /**
     *  Manage member deletion.
     *
     * @Route({
     *     "en": "/user/{id}/delete/{token}"
     * }, name="delete_member", methods={"DELETE"})
     *
     * @param CsrfTokenManagerInterface $csrfTokenManager
     * @param DeleteMemberResponder     $responder
     * @param Request                   $request
     * 
     * @return Response
     *
     * @throws InvalidCsrfTokenException
     */ 
    public function __invoke(CsrfTokenManagerInterface $csrfTokenManager, DeleteMemberResponder $responder, Request $request): Response
    {
        // "delete_member" must be a unique token id for session storage inside application
        $token = new CsrfToken('delete_member', $request-&gt;attributes-&gt;get('token'));

        // Action is stopped since token is not allowed!
        if (!$csrfTokenManager-&gt;isTokenValid($token)) {
            throw new InvalidCsrfTokenException('CSRF Token is not valid.');
        }

        // Handle request and proceed to parameters validation and member deletion
        // ...
        // Return expected response (e.g. JSON, HTML...)
    }
}</pre>
<p><strong><u>Note :</u> cette action est auto-configurée en tant que "controller" au sein du framework pour être appelée correctement via la route précisée.</strong><br />
Ce "controller" va en outre enclencher la vérification de la validité du token par l'intermédiaire d'un service "<strong>CsrfTokenManager</strong>" implémentant une interface. <a href="https://github.com/symfony/security-csrf/blob/master/CsrfTokenManager.php" title="Symfony - class CsrfTokenManager" target="_blank" rel="noopener noreferrer nofollow">Vous trouverez ici</a> le détail de cette classe concrète.</p>
<p>Cet objet "CsrfTokenManager" vérifie la validité d'un autre objet "CsrfToken" associant un identifiant et une valeur de token.</p>
<p><strong>Mais à ce propos comment le token est généré ?</strong><br />
Généralement, il est créé dans un template via une fonction Twig avec "<strong>csrf_token('delete_member')</strong>" qui permet d'avoir le retour de la valeur du token CSRF en passant l'identifiant correspondant attendu.<br />
Voici ce que cela peut donner sur un simple lien HTML côté Twig (l'allié fidèle de Symfony) :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">{# "id" parameter can also be a uuid value! #}
&lt;a href="{{ path('delete_member', {'id': 450, 'token': csrf_token('delete_member')}) }}" title="Validate deletion"&gt;Delete account&lt;/a&gt;</pre>
<p>Bien entendu, il est préférable par exemple de demander une confirmation avant suppression, afin d'être plus user friendly !</p>
<p>Le token CSRF peut être généré aussi simplement dans un service, toujours grâce à l'instance "CsrfTokenManager" et sa méthode "getToken('delete_member')" qui retourne un objet "<strong>CsrfToken</strong>" et va générer/stocker le token s'il n'existe pas déjà.<br />
L'objet "CsrfToken" a quant à lui une méthode "getValue()" afin de récupérer en définitive la valeur du token.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">{# Strange getter which can also generate a token! #}
$token = $csrfTokenManager-&gt;getToken('delete_member')-&gt;getValue();</pre>
<p>Ce manager fait appel à d'autres instances pour générer le token et le stocker en session notamment via les classes concrètes "<strong>UriSafeTokenGenerator</strong>" et "<strong>NativeSessionTokenStorage</strong>" ou des implémentations similaires.</p>
<p>J'espère que ces quelques lignes pourront vous rendre service, sans mauvais jeu de mot !</p>
<p>Cet article <a href="https://www.dotprogs.com/exemple-utilisation-token-csrf-sans-formulaire-avec-symfony/">Exemple d&rsquo;utilisation d&rsquo;un « token CSRF » sans formulaire avec Symfony</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/bout-de-code0120.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Bout de code - Token CSRF sans formulaire avec Symfony" decoding="async" loading="lazy" /></p>L'idée, ici, est de présenter l'usage d'un "token" (jeton) pour se prémunir d'une attaque <a href="https://fr.wikipedia.org/wiki/Cross-site_request_forgery" title="Présentation Cross Site Request Forgery" target="_blank" rel="noopener noreferrer nofollow">CSRF</a> (Cross Site Request Forgery) en utilisant le framework Symfony 4+ (doit pouvoir fonctionner en Sf 3 également à peu de chose près) mais sans s'appuyer sur un formulaire.

En effet les tokens CSRF sont proposés pour la sécurité d'un formulaire Symfony, donc l'objectif de ma démarche avec ce "bout de code" est de mettre en place la même sécurité, en utilisant un simple lien ou un bouton HTML par exemple avec une requête "DELETE" en AJAX pour appeler l'action dédiée "<strong>DeleteMemberAction</strong>" ci-dessous.
<strong><u>Note :</u> utiliser une requête en "GET" pour une suppression de ressource est une mauvaise pratique d'un point de vue</strong> <a href="https://developer.mozilla.org/fr/docs/Web/HTTP/Resources_and_specifications" title="Protocole HTTP" target="_blank" rel="noopener noreferrer nofollow">HTTP</a><strong>.</strong>

Cet exemple tient compte du fait que l'utilisateur à l'origine de l'action est <strong>normalement authentifié</strong> sur un espace sécurisé, ce qui fait le danger par principe d'une attaque CSRF.
Un compte "membre" doit être supprimé, ce que l'on peut considérer comme une opération sensible !
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php

declare(strict_types = 1);

namespace App\Action;

use App\Responder\DeleteMemberResponder; 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

/**
 * Class DeleteMemberAction.
 *
 * Delete a member account.
 */
class DeleteMemberAction
{
    /**
     *  Manage member deletion.
     *
     * @Route({
     *     "en": "/user/{id}/delete/{token}"
     * }, name="delete_member", methods={"DELETE"})
     *
     * @param CsrfTokenManagerInterface $csrfTokenManager
     * @param DeleteMemberResponder     $responder
     * @param Request                   $request
     * 
     * @return Response
     *
     * @throws InvalidCsrfTokenException
     */ 
    public function __invoke(CsrfTokenManagerInterface $csrfTokenManager, DeleteMemberResponder $responder, Request $request): Response
    {
        // "delete_member" must be a unique token id for session storage inside application
        $token = new CsrfToken('delete_member', $request-&gt;attributes-&gt;get('token'));

        // Action is stopped since token is not allowed!
        if (!$csrfTokenManager-&gt;isTokenValid($token)) {
            throw new InvalidCsrfTokenException('CSRF Token is not valid.');
        }

        // Handle request and proceed to parameters validation and member deletion
        // ...
        // Return expected response (e.g. JSON, HTML...)
    }
}</pre>
<strong><u>Note :</u> cette action est auto-configurée en tant que "controller" au sein du framework pour être appelée correctement via la route précisée.</strong>
Ce "controller" va en outre enclencher la vérification de la validité du token par l'intermédiaire d'un service "<strong>CsrfTokenManager</strong>" implémentant une interface. <a href="https://github.com/symfony/security-csrf/blob/master/CsrfTokenManager.php" title="Symfony - class CsrfTokenManager" target="_blank" rel="noopener noreferrer nofollow">Vous trouverez ici</a> le détail de cette classe concrète.

Cet objet "CsrfTokenManager" vérifie la validité d'un autre objet "CsrfToken" associant un identifiant et une valeur de token.

<strong>Mais à ce propos comment le token est généré ?</strong>
Généralement, il est créé dans un template via une fonction Twig avec "<strong>csrf_token('delete_member')</strong>" qui permet d'avoir le retour de la valeur du token CSRF en passant l'identifiant correspondant attendu.
Voici ce que cela peut donner sur un simple lien HTML côté Twig (l'allié fidèle de Symfony) :
<pre class="EnlighterJSRAW" data-enlighter-language="php">{# "id" parameter can also be a uuid value! #}
&lt;a href="{{ path('delete_member', {'id': 450, 'token': csrf_token('delete_member')}) }}" title="Validate deletion"&gt;Delete account&lt;/a&gt;</pre>
Bien entendu, il est préférable par exemple de demander une confirmation avant suppression, afin d'être plus user friendly !

Le token CSRF peut être généré aussi simplement dans un service, toujours grâce à l'instance "CsrfTokenManager" et sa méthode "getToken('delete_member')" qui retourne un objet "<strong>CsrfToken</strong>" et va générer/stocker le token s'il n'existe pas déjà.
L'objet "CsrfToken" a quant à lui une méthode "getValue()" afin de récupérer en définitive la valeur du token.
<pre class="EnlighterJSRAW" data-enlighter-language="generic">{# Strange getter which can also generate a token! #}
$token = $csrfTokenManager-&gt;getToken('delete_member')-&gt;getValue();</pre>
Ce manager fait appel à d'autres instances pour générer le token et le stocker en session notamment via les classes concrètes "<strong>UriSafeTokenGenerator</strong>" et "<strong>NativeSessionTokenStorage</strong>" ou des implémentations similaires.

J'espère que ces quelques lignes pourront vous rendre service, sans mauvais jeu de mot !<p>Cet article <a href="https://www.dotprogs.com/exemple-utilisation-token-csrf-sans-formulaire-avec-symfony/">Exemple d&rsquo;utilisation d&rsquo;un « token CSRF » sans formulaire avec Symfony</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Exemple d&#8217;utilisation d&#8217;une fonction « macro » Twig récursive</title>
		<link>https://www.dotprogs.com/exemple-utilisation-fonction-macro-twig-recursive/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Tue, 04 Jun 2019 18:50:43 +0000</pubDate>
				<category><![CDATA[Développement web]]></category>
		<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[Intégration web]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[function macro]]></category>
		<category><![CDATA[import]]></category>
		<category><![CDATA[récursivité]]></category>
		<category><![CDATA[Symfony]]></category>
		<category><![CDATA[template]]></category>
		<category><![CDATA[Twig]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=965</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/bout-de-code0119.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Bout de code - Fonction macro Twig récursive" decoding="async" loading="lazy" /></p>
<p>Partons du principe que l'on souhaite lister les erreurs de validation lors de l'utilisation d'un formulaire avec Symfony 4.<br />
Ça ne sert pas à grand chose mais cet exemple permet de récupérer "l'équivalent" simplifié des données qui remontent dans le profiler de Symfony pour les violations de contraintes sur les champs de ce formulaire.</p>
<p>Pour faire du "debug" rapide, j'utilise simplement un <strong>dump()</strong> des erreurs directement côté Twig pour m'assurer que les contraintes s'appliquent bien en testant visuellement le formulaire.</p>
<p>J'ai donc déclaré une fonction <strong>"macro"</strong> utilisée de façon récursive et exécutée pour boucler sur les objets <strong>FormErrorsIterator</strong> de chaque champ enfant (et enfant(s) d'enfant ...).</p>
<p><strong><u>Note:</u> "_self" désigne le template courant et permet une utilisation immédiate.</strong></p>
<p>Pour Symfony, tout type de champ de formulaire est également un <a href="https://symfony.com/doc/current/forms.html" title="Formulaire Symfony" rel="noopener" target="_blank">formulaire</a>, il existe donc la notion de formulaire parent et enfant.</p>
<p>La récursivité pour cet exemple provient simplement du fait que j'ai décidé d'afficher l'ensemble des violations de contraintes présentes au sein du <strong>root form</strong> ici appelé "<strong>myRootForm</strong>" (variable déclarée pour le formulaire global) :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">{% macro form_errors(forms) %}
  {% for form in forms %}
    {% for formError in form.vars.errors %}
      {{ dump(formError.cause.propertyPath, formError.cause.constraint, formError.cause.message) }}
    {% endfor %}
    {% if form.children is not empty %}
      {# Use macro recursively #}
      {{ _self.form_errors(form.children) }}
    {% endif %}
  {% endfor %}
{% endmacro %}

{# Use macro immediately after declaration in the same template #}
{{ _self.form_errors(myRootForm.children) }}</pre>
<p>Ce "dump" affiche le propertyPath (le champ concerné), le type de contrainte objet, et le message d'erreur personnalisé ou proposé par défaut.<br />
Les contraintes personnalisées (<a href="https://symfony.com/doc/current/validation/custom_constraint.html" title="Custom constraints Symfony" rel="noopener" target="_blank">custom constraints</a>) proposées par Symfony, éventuellement mises en place sur un projet, remontent également dans les boucles.</p>
<p>Pour utiliser votre macro où vous le souhaitez, il suffit de la déclarer dans un template dédié aux déclarations des fonctions macros et ensuite de faire un import de ce template dans un autre template pour pouvoir l'utiliser :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">{# Import macro in another template #}
{% from 'macros.html.twig' import form_errors %}

{# Use macro #}
{{ form_errors(myRootForm.children) }}

{# ------------------------------------------- #}

{# Or import macro like this #} 
{% import "macros.html.twig" as macros %}

{# Use macro in the same way #}
{{ macros.form_errors(myRootForm.children) }}</pre>
<p>Vous pouvez consulter la <a href="https://twig.symfony.com/doc/3.x/tags/macro.html" title="Documentation macro Twig" rel="noopener" target="_blank">documentation</a> concernant les macros Twig.</p>
<p>Voilà, c'est déjà fini !</p>
<p>Cet article <a href="https://www.dotprogs.com/exemple-utilisation-fonction-macro-twig-recursive/">Exemple d&rsquo;utilisation d&rsquo;une fonction « macro » Twig récursive</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/bout-de-code0119.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Bout de code - Fonction macro Twig récursive" decoding="async" loading="lazy" /></p>Partons du principe que l'on souhaite lister les erreurs de validation lors de l'utilisation d'un formulaire avec Symfony 4.
Ça ne sert pas à grand chose mais cet exemple permet de récupérer "l'équivalent" simplifié des données qui remontent dans le profiler de Symfony pour les violations de contraintes sur les champs de ce formulaire.

Pour faire du "debug" rapide, j'utilise simplement un <strong>dump()</strong> des erreurs directement côté Twig pour m'assurer que les contraintes s'appliquent bien en testant visuellement le formulaire.

J'ai donc déclaré une fonction <strong>"macro"</strong> utilisée de façon récursive et exécutée pour boucler sur les objets <strong>FormErrorsIterator</strong> de chaque champ enfant (et enfant(s) d'enfant ...).

<strong><u>Note:</u> "_self" désigne le template courant et permet une utilisation immédiate.</strong>

Pour Symfony, tout type de champ de formulaire est également un <a href="https://symfony.com/doc/current/forms.html" title="Formulaire Symfony" rel="noopener" target="_blank">formulaire</a>, il existe donc la notion de formulaire parent et enfant.

La récursivité pour cet exemple provient simplement du fait que j'ai décidé d'afficher l'ensemble des violations de contraintes présentes au sein du <strong>root form</strong> ici appelé "<strong>myRootForm</strong>" (variable déclarée pour le formulaire global) :
<pre class="EnlighterJSRAW" data-enlighter-language="php">{% macro form_errors(forms) %}
  {% for form in forms %}
    {% for formError in form.vars.errors %}
      {{ dump(formError.cause.propertyPath, formError.cause.constraint, formError.cause.message) }}
    {% endfor %}
    {% if form.children is not empty %}
      {# Use macro recursively #}
      {{ _self.form_errors(form.children) }}
    {% endif %}
  {% endfor %}
{% endmacro %}

{# Use macro immediately after declaration in the same template #}
{{ _self.form_errors(myRootForm.children) }}</pre>
Ce "dump" affiche le propertyPath (le champ concerné), le type de contrainte objet, et le message d'erreur personnalisé ou proposé par défaut.
Les contraintes personnalisées (<a href="https://symfony.com/doc/current/validation/custom_constraint.html" title="Custom constraints Symfony" rel="noopener" target="_blank">custom constraints</a>) proposées par Symfony, éventuellement mises en place sur un projet, remontent également dans les boucles.

Pour utiliser votre macro où vous le souhaitez, il suffit de la déclarer dans un template dédié aux déclarations des fonctions macros et ensuite de faire un import de ce template dans un autre template pour pouvoir l'utiliser :
<pre class="EnlighterJSRAW" data-enlighter-language="php">{# Import macro in another template #}
{% from 'macros.html.twig' import form_errors %}

{# Use macro #}
{{ form_errors(myRootForm.children) }}

{# ------------------------------------------- #}

{# Or import macro like this #} 
{% import "macros.html.twig" as macros %}

{# Use macro in the same way #}
{{ macros.form_errors(myRootForm.children) }}</pre>
Vous pouvez consulter la <a href="https://twig.symfony.com/doc/3.x/tags/macro.html" title="Documentation macro Twig" rel="noopener" target="_blank">documentation</a> concernant les macros Twig.

Voilà, c'est déjà fini !<p>Cet article <a href="https://www.dotprogs.com/exemple-utilisation-fonction-macro-twig-recursive/">Exemple d&rsquo;utilisation d&rsquo;une fonction « macro » Twig récursive</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Tester le chargement (disponibilité) d&#8217;une video en iframe avec AJAX</title>
		<link>https://www.dotprogs.com/tester-le-chargement-d-une-video-en-iframe-avec-ajax/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Tue, 05 Feb 2019 21:17:18 +0000</pubDate>
				<category><![CDATA[Développement web]]></category>
		<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[AJAX]]></category>
		<category><![CDATA[C.O.R.S]]></category>
		<category><![CDATA[Cross Origin Resource Sharing]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[XMLHttpRequest]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=909</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/iframe-loading-cors-request.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Iframe loading with C.O.R.S AJAX request" decoding="async" loading="lazy" /></p>
<p>J'ai été confronté à un problème que je n'avais pas envisagé en voulant lister en iframe un certain nombre de vidéos Youtube, Vimeo et Dailymotion (toutes disponibles en lecture) sur une page web.<br />
Ces vidéos sont gérées avec leur API JavaScript respectives pour interagir entre elles (autrement-dit stopper une video en lecture si une autre démarre), car quoi de plus embêtant que d'avoir deux videos lues en même temps, super cacophonie !<br />
Mon objectif initial était de gérer au mieux l'expérience utilisateur avec un loader pour chacune des videos en attendant que le chargement sur la page soit terminé.<br />
<strong><u>Note :</u> la notion de chargement consiste ici à s'assurer que la vidéo est un contenu disponible (sans erreur 404 par exemple).</strong><br />
Cela semblait assez nécessaire étant donné que plusieurs vidéos d'origine différente pouvaient être chargées au même moment.<br />
Au passage, je comprends mieux pourquoi Youtube ou d'autres services affichent une vidéo à la fois, car cela est plus judicieux et pragmatique !<br />
Seulement voilà, je n'avais pas accès au contenu des iframes du fait de la fameuse restriction de sécurité dans un contexte de "<a href="https://developer.mozilla.org/fr/docs/Web/HTTP/CORS" title="C.O.R.S" target="_blank" rel="noopener noreferrer nofollow">Cross Origin Resource Sharing</a>" (contenu provenant d'un domaine différent).<br />
je n'avais donc pas la possibilité d'accéder à un contenu externe (vidéo) en iframe et surtout tester son chargement directement en JavaScript !<br />
D'autant plus que la gestion d'évènement en JavaScript sur une iframe est plutôt limitée ...</p>
<p>Après un certain nombre de recherche et en me documentant sur de possibles solutions, j'ai opté pour une approche similaire à ce qui est présenté ici en vidéo :<br />
Cross domain proxy et requête AJAX : <a href="https://www.youtube.com/watch?v=o8puzjzpjqo" title="Cross domain proxy et requête AJAX" target="_blank" rel="noopener noreferrer nofollow">https://www.youtube.com/watch?v=o8puzjzpjqo</a></p>
<p>L'idée pour moi est d'effectuer une requête AJAX en passant en paramètre l'URL de la vidéo afin de vérifier en PHP que la vidéo est une ressource exploitable.<br />
Le simple retour attendu sera une chaîne JSON contenant l'équivalent d'un booléan 0 ("chargement" impossible) et 1 ("chargement" exploitable côté client en JavaScript) pour basiquement faire apparaître soit un message d'erreur, soit faire disparaître le loader et rendre accessible l'iframe. Cela ressemble plus à une astuce mais permet de s'assurer que la vidéo va être disponible pour la lecture par l'utilisateur.</p>
<p>Seules les portions de code principales sont présentées ici, je pense qu'elles suffisent à voir globalement le principe.<br />
Tous les commentaires sont en anglais car c'est une habitude personnelle !<br />
Le code PHP provient de développements présents dans un projet Symfony 4, le principe requête - réponse reste cependant classique.</p>
<p>- Voici la partie requête AJAX réalisée côté client :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">// Ajax request ES6 function
return new Promise((resolve, reject) =&gt; {
        let xhr = new XMLHttpRequest();
        xhr.open(obj.method || "GET", obj.url, obj.async || false );
        if (obj.overrideMimeType) {
            xhr.overrideMimeType(obj.overrideMimeType);
        }
        if (obj.responseType) {
            xhr.responseType = obj.responseType;
        }
        if (obj.withCredentials) {
            xhr.withCredentials = obj.withCredentials;
        }
        if (obj.headers) {
            Object.keys(obj.headers).forEach(key =&gt; {
                xhr.setRequestHeader(key, obj.headers[key]);
            });
        }
        // Custom functions for loader
        if (obj.onProgressFunction) {
            // Custom loader
            xhr.onprogress = () =&gt; {
                obj.onProgressFunction(xhr);
            };
        }
        if (obj.onLoadStartFunction) {
            xhr.onloadstart = () =&gt; {
                obj.onLoadStartFunction(xhr);
            };
        }
        if (obj.onLoadEndFunction) {
            xhr.onloadend = () =&gt; {
                obj.onLoadEndFunction(xhr);
            };
        }
        xhr.onerror = () =&gt; reject(xhr);
        xhr.onload = xhr.onreadystatechange = () =&gt; {
            if (xhr.readyState === XMLHttpRequest.DONE ) {
                if (xhr.status &gt;= 200 &amp;&amp; xhr.status &lt; 300) {
                    resolve(xhr.response);
                } else {
                    reject(xhr);
                }
            }
        };
        // Send request
        xhr.send(obj.body !== undefined ? obj.body : null);
    });
};</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="php">import request from './all/ajax-request';

export default function() {

    // Other scripts before here ...
    
    // Check video loading with C.O.R.S
    function checkLoadingCORSRequest(method, url, resolvedCallback, errorCallback, args, timeOut) {
        // XMLHttpRequest object
        const obj = {
            method: method,
            url: url.replace(/(\?.+)/gi, ''),
            async: true,
            withCredentials: false,
            responseType: 'json'
        };
        // Use promise with callbacks
        request(obj).then((response) =&gt; {
            // no need to parse with JSON.parse(response).status: response is already an object
            if (response.status === 1) {
                resolvedCallback.apply(null, args);
                // Dispatch checked video success event to manage asynchronous execution and enable API
                let customEvent = new Event('checkedVideoSuccess');
                args[0].dispatchEvent(customEvent);
            } else {
                errorCallback.apply(null, args);
            }
            // Cancel timeOut
            clearTimeout(timeOut);
        }).catch(() =&gt; {
            errorCallback.apply(null, args);
            // Cancel timeOut
            clearTimeout(timeOut);
        });
    }

    // Manage iframe after loading success: media argument is an iframe element
    function afterMediaLoaded(media) {
        // Make loader disappear and render iframe correctly for instance
        // ...
    }

    // Manage loading failure: media argument is an iframe element    
    function whenMediaError(media) {
        // Display an error message instead of iframe element for instance
        // ...
    }

    // Convert NodeLists to arrays (example with multiple Youtube iframes in slider)
    let singleSliderElement = document.getElementById('...');
    let youtubeIframes = Array.from(singleSliderElement.querySelectorAll('...')),
    let ytTimeOut = [];
    for (let i = 0; i &lt; youtubeIframes.length; i ++) { // Call iframe loading rendering behavior with Youtube example in loop: ytTimeOut[i] = setTimeout(() =&gt; {
            // Dynamic URL to call action PHP class script (can obviously be static)
            let proxyURL = singleSliderElement.getAttribute('data-video-proxy') + youtubeIframes[i].getAttribute('src');
            checkLoadingCORSRequest('GET', proxyURL, afterMediaLoaded, whenMediaError, [youtubeIframes[i]], ytTimeOut[i]);
        }, 10);
    }

    // Other scripts after here ...
}</pre>
<p>- L'action (controller) appelée par la requête AJAX qui envoie la réponse JSON grâce à un responder (pattern ADR), une fois la vidéo contrôlée par un service :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
 
declare(strict_types = 1);
 
namespace App\Action;
 
use App\Responder\Json\JsonResponder;
use App\Service\Medias\VideoURLProxyChecker;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
 
/**
 * Class AjaxVideoURLCheckAction.
 *
 * Verify if video URL can be loaded using ajax.
 */
class AjaxVideoURLCheckAction
{
    use LoggerAwareTrait;
 
    /**
     * @var VideoURLProxyChecker
     */
    private $videoChecker;
 
    /**
     * AjaxTrickListAction constructor.
     *
     * @param VideoURLProxyChecker $videoChecker
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    public function __construct(VideoURLProxyChecker $videoChecker, LoggerInterface $logger)
    {
       $this-&gt;videoChecker = $videoChecker;
       $this-&gt;setLogger($logger);
    }
 
    /**
     * Check if single trick video URL can be loaded from ajax request.
     *
     * @Route("/{_locale}/load-video/url/{url&lt;(.+)&gt;?}", name="load_video_url_check")
     *
     * @param JsonResponder $responder
     * @param Request       $request
     *
     * @return Response
     *
     * @see https://symfony.com/doc/current/routing/slash_in_parameter.html
     */
    public function __invoke(JsonResponder $responder, Request $request): JsonResponse
    {
        // Check video URL value
        $url = $this-&gt;videoChecker-&gt;filterURLAttribute($request);
        if (\is_null($url)) {
            $this-&gt;logger-&gt;error(
                "[trace app videos] AjaxVideoURLCheckAction/__invoke =&gt; " .
                "Technical error due to video url set to null: check loading process for both client and server side!"
            );
        }
        // Check if URL is formatted as expected (validation) and accessible
        $data = $this-&gt;videoChecker-&gt;verify($url);
        return $responder($data);
    }
}

</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
 
declare(strict_types = 1);
 
namespace App\Responder\Json;
 
use Symfony\Component\HttpFoundation\JsonResponse;
 
/**
 * Class JsonResponder.
 *
 * Manage a simple JSON response with status from ajax request.
 */
final class JsonResponder
{
    /**
     * Invokable Responder with Magic method.
     *
     * @param array $data
     *
     * @return JsonResponse
     */
    public function __invoke(array $data): JsonResponse
    {
        // Encode data with JSON string with serializer
        return new JsonResponse($data);
    }
}</pre>
<p>- Le service, classe PHP simple qui effectue les contrôles sur la ressource vidéo :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">&lt;?php

declare(strict_types = 1);

namespace App\Service\Medias;

use Symfony\Component\HttpFoundation\Request;

/*
 * Class VideoURLProxyChecker.
 *
 * Check if a video URL can be correctly loaded.
 * .
 */
class VideoURLProxyChecker
{
    // CAUTION: these iframe URL patterns should certainly be improved and are very important for a quite "secure" use!
    // Even more, they can evolve, so it is preferable to use providers APIs!
    const ALLOWED_URL_PATTERNS = [
        '/^https?:\/\/www\.youtube\.com\/embed\/[a-zA-Z0-9_-]+$/', // [\w-]+
        '/^https?:\/\/player\.vimeo\.com\/video\/[0-9]+$/',
        '/^https?:\/\/www\.dailymotion\.com\/embed\/video\/[a-zA-Z0-9]+$/'
    ];

    /**
     * Filter provided URL.
     *
     * @param Request $request
     * @param bool    $isDecoded
     *
     * @return null|string
     */
    public function filterURLAttribute(Request $request, bool $isDecoded = false): ?string
    {
        // Get URL to check
        $url = null;
        if (!\is_null($request-&gt;attributes-&gt;get('url'))) {
            $url = $request-&gt;attributes-&gt;get('url');
        }
        return $isDecoded ? $url : urldecode($url);
    }

    /**
     * Check if URL format is allowed.
     *
     * @param string|null $url
     *
     * @return bool
     */
    public function isAllowed(?string $url): bool
    {
        if (\is_null($url)) {
            return false;
        }
        $patterns = self::ALLOWED_URL_PATTERNS;
        // Use of "array_filter" would be more appropriate here!
        for ($i = 0; $i &lt; count($patterns); $i ++) {
            if (preg_match( $patterns[$i], urldecode($url))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Request URL to check if a content can be loaded. Choice is made to use cURL here.
     *
     * CAUTION: do not use this method alone because of potential "SSRF" attacks! At least use isAllowed() before...
     * @link https://www.vaadata.com/blog/understanding-web-vulnerability-server-side-request-forgery-1/
     *
     * @param string|null $url
     *
     * @return bool
     */
    public function isContent(?string $url): bool
    {
        if (\is_null($url)) {
            return false;
        }
        // Youtube particular case to check availability correctly
        // otherwise HTTP code is always 200!
        if (preg_match( '/youtube/', $url)) {
            $url = $this-&gt;prepareAccessToYoutubeVideoContent($url);
        }
        // Use cURL
        $handle = curl_init(urldecode($url));
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
        // Avoid content loading by getting the headers only
        curl_setopt($handle, CURLOPT_NOBODY, 1);
        // Request with cURL
        curl_exec($handle);
        $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
        // Check resource availability with HTTP code 200
        $isContentFound = 200 === $httpCode ? true : false;
        curl_close($handle);
        return $isContentFound;
    }

    /**
     * Check particular youtube video availability.
     *
     * @link Particular case for youtube video:
     * https://stackoverflow.com/questions/29166402/verify-if-video-exist-with-youtube-api-v3
     *
     * @param $url
     *
     * @return string the correct url to use to check availability
     */
    private function prepareAccessToYoutubeVideoContent($url): string
    {
        // Extract video id and use correct URL
        preg_match( '/embed\/(.+)/', urldecode($url), $matches);
        $videoID = $matches[1];
        $url ='https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=' . $videoID;
        return $url;
    }

    /**
     * Return a status code to be converted later in JSON string.
     *
     * Value 1 means URL can be loaded and value 0 means error context must be used!
     *
     * @param string|null $url
     *
     * @return array
     *
     * @see https://symfony.com/doc/current/controller.html#returning-json-response
     */
    public function verify(?string $url): array
    {
        // Prepare array to be converted in JSON string with Symfony JsonResponse object (no need to use "json_encode" here)
        if (\is_null($url)) {
            return ['status' =&gt; 0];
        }
        $url = urldecode($url);
        return $this-&gt;isAllowed($url) &amp;&amp; $this-&gt;isContent($url) ? ['status' =&gt; 1] : ['status' =&gt; 0];
    }

}</pre>
<p>Des erreurs (de copié-collé) peuvent s'être glissées dans le code bien que celui-ci soit issu d'un projet opérationnel.<br />
Je vous invite à me les signaler en me contactant si c'est le cas.<br />
Je suis également preneur si vous avez des conseils pour améliorer certains scripts, ou si vous estimez que certains points sont inexactes.</p>
<p>L'idée de départ, bien que perfectible, reste simple et intéressante pour vérifier l'accès à des ressources externes.</p>
<p>- Promesse et XMLHttpRequest : la fonction JavaScript "request(object)" est en grande partie inspirée de ce script : <a href="http://ccoenraets.github.io/es6-tutorial-data/promisify" title="XMLHttpRequest et promesse pour de l'AJAX" target="_blank" rel="noopener noreferrer nofollow">http://ccoenraets.github.io/es6-tutorial-data/promisify</a><br />
J'apprécie cette manière de faire notamment pour la gestion d'évènements vraiment souple.<br />
il n'y a pas que "axios" et "fetch api" dans la vie !<br />
- Voici un lien vers <a href="https://symfony.com/4" title="Symfony 4" target="_blank" rel="noopener noreferrer nofollow">Symfony 4</a> qui est utilisé en partie dans ce post.</p>
<p>Cet article <a href="https://www.dotprogs.com/tester-le-chargement-d-une-video-en-iframe-avec-ajax/">Tester le chargement (disponibilité) d&rsquo;une video en iframe avec AJAX</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/iframe-loading-cors-request.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Iframe loading with C.O.R.S AJAX request" decoding="async" loading="lazy" /></p>J'ai été confronté à un problème que je n'avais pas envisagé en voulant lister en iframe un certain nombre de vidéos Youtube, Vimeo et Dailymotion (toutes disponibles en lecture) sur une page web.
Ces vidéos sont gérées avec leur API JavaScript respectives pour interagir entre elles (autrement-dit stopper une video en lecture si une autre démarre), car quoi de plus embêtant que d'avoir deux videos lues en même temps, super cacophonie !
Mon objectif initial était de gérer au mieux l'expérience utilisateur avec un loader pour chacune des videos en attendant que le chargement sur la page soit terminé.
<strong><u>Note :</u> la notion de chargement consiste ici à s'assurer que la vidéo est un contenu disponible (sans erreur 404 par exemple).</strong>
Cela semblait assez nécessaire étant donné que plusieurs vidéos d'origine différente pouvaient être chargées au même moment.
Au passage, je comprends mieux pourquoi Youtube ou d'autres services affichent une vidéo à la fois, car cela est plus judicieux et pragmatique !
Seulement voilà, je n'avais pas accès au contenu des iframes du fait de la fameuse restriction de sécurité dans un contexte de "<a href="https://developer.mozilla.org/fr/docs/Web/HTTP/CORS" title="C.O.R.S" target="_blank" rel="noopener noreferrer nofollow">Cross Origin Resource Sharing</a>" (contenu provenant d'un domaine différent).
je n'avais donc pas la possibilité d'accéder à un contenu externe (vidéo) en iframe et surtout tester son chargement directement en JavaScript !
D'autant plus que la gestion d'évènement en JavaScript sur une iframe est plutôt limitée ...

Après un certain nombre de recherche et en me documentant sur de possibles solutions, j'ai opté pour une approche similaire à ce qui est présenté ici en vidéo :
Cross domain proxy et requête AJAX : <a href="https://www.youtube.com/watch?v=o8puzjzpjqo" title="Cross domain proxy et requête AJAX" target="_blank" rel="noopener noreferrer nofollow">https://www.youtube.com/watch?v=o8puzjzpjqo</a>

L'idée pour moi est d'effectuer une requête AJAX en passant en paramètre l'URL de la vidéo afin de vérifier en PHP que la vidéo est une ressource exploitable.
Le simple retour attendu sera une chaîne JSON contenant l'équivalent d'un booléan 0 ("chargement" impossible) et 1 ("chargement" exploitable côté client en JavaScript) pour basiquement faire apparaître soit un message d'erreur, soit faire disparaître le loader et rendre accessible l'iframe. Cela ressemble plus à une astuce mais permet de s'assurer que la vidéo va être disponible pour la lecture par l'utilisateur.

Seules les portions de code principales sont présentées ici, je pense qu'elles suffisent à voir globalement le principe.
Tous les commentaires sont en anglais car c'est une habitude personnelle !
Le code PHP provient de développements présents dans un projet Symfony 4, le principe requête - réponse reste cependant classique.

- Voici la partie requête AJAX réalisée côté client :
<pre class="EnlighterJSRAW" data-enlighter-language="php">// Ajax request ES6 function
return new Promise((resolve, reject) =&gt; {
        let xhr = new XMLHttpRequest();
        xhr.open(obj.method || "GET", obj.url, obj.async || false );
        if (obj.overrideMimeType) {
            xhr.overrideMimeType(obj.overrideMimeType);
        }
        if (obj.responseType) {
            xhr.responseType = obj.responseType;
        }
        if (obj.withCredentials) {
            xhr.withCredentials = obj.withCredentials;
        }
        if (obj.headers) {
            Object.keys(obj.headers).forEach(key =&gt; {
                xhr.setRequestHeader(key, obj.headers[key]);
            });
        }
        // Custom functions for loader
        if (obj.onProgressFunction) {
            // Custom loader
            xhr.onprogress = () =&gt; {
                obj.onProgressFunction(xhr);
            };
        }
        if (obj.onLoadStartFunction) {
            xhr.onloadstart = () =&gt; {
                obj.onLoadStartFunction(xhr);
            };
        }
        if (obj.onLoadEndFunction) {
            xhr.onloadend = () =&gt; {
                obj.onLoadEndFunction(xhr);
            };
        }
        xhr.onerror = () =&gt; reject(xhr);
        xhr.onload = xhr.onreadystatechange = () =&gt; {
            if (xhr.readyState === XMLHttpRequest.DONE ) {
                if (xhr.status &gt;= 200 &amp;&amp; xhr.status &lt; 300) {
                    resolve(xhr.response);
                } else {
                    reject(xhr);
                }
            }
        };
        // Send request
        xhr.send(obj.body !== undefined ? obj.body : null);
    });
};</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="php">import request from './all/ajax-request';

export default function() {

    // Other scripts before here ...
    
    // Check video loading with C.O.R.S
    function checkLoadingCORSRequest(method, url, resolvedCallback, errorCallback, args, timeOut) {
        // XMLHttpRequest object
        const obj = {
            method: method,
            url: url.replace(/(\?.+)/gi, ''),
            async: true,
            withCredentials: false,
            responseType: 'json'
        };
        // Use promise with callbacks
        request(obj).then((response) =&gt; {
            // no need to parse with JSON.parse(response).status: response is already an object
            if (response.status === 1) {
                resolvedCallback.apply(null, args);
                // Dispatch checked video success event to manage asynchronous execution and enable API
                let customEvent = new Event('checkedVideoSuccess');
                args[0].dispatchEvent(customEvent);
            } else {
                errorCallback.apply(null, args);
            }
            // Cancel timeOut
            clearTimeout(timeOut);
        }).catch(() =&gt; {
            errorCallback.apply(null, args);
            // Cancel timeOut
            clearTimeout(timeOut);
        });
    }

    // Manage iframe after loading success: media argument is an iframe element
    function afterMediaLoaded(media) {
        // Make loader disappear and render iframe correctly for instance
        // ...
    }

    // Manage loading failure: media argument is an iframe element    
    function whenMediaError(media) {
        // Display an error message instead of iframe element for instance
        // ...
    }

    // Convert NodeLists to arrays (example with multiple Youtube iframes in slider)
    let singleSliderElement = document.getElementById('...');
    let youtubeIframes = Array.from(singleSliderElement.querySelectorAll('...')),
    let ytTimeOut = [];
    for (let i = 0; i &lt; youtubeIframes.length; i ++) { // Call iframe loading rendering behavior with Youtube example in loop: ytTimeOut[i] = setTimeout(() =&gt; {
            // Dynamic URL to call action PHP class script (can obviously be static)
            let proxyURL = singleSliderElement.getAttribute('data-video-proxy') + youtubeIframes[i].getAttribute('src');
            checkLoadingCORSRequest('GET', proxyURL, afterMediaLoaded, whenMediaError, [youtubeIframes[i]], ytTimeOut[i]);
        }, 10);
    }

    // Other scripts after here ...
}</pre>
- L'action (controller) appelée par la requête AJAX qui envoie la réponse JSON grâce à un responder (pattern ADR), une fois la vidéo contrôlée par un service :
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
 
declare(strict_types = 1);
 
namespace App\Action;
 
use App\Responder\Json\JsonResponder;
use App\Service\Medias\VideoURLProxyChecker;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
 
/**
 * Class AjaxVideoURLCheckAction.
 *
 * Verify if video URL can be loaded using ajax.
 */
class AjaxVideoURLCheckAction
{
    use LoggerAwareTrait;
 
    /**
     * @var VideoURLProxyChecker
     */
    private $videoChecker;
 
    /**
     * AjaxTrickListAction constructor.
     *
     * @param VideoURLProxyChecker $videoChecker
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    public function __construct(VideoURLProxyChecker $videoChecker, LoggerInterface $logger)
    {
       $this-&gt;videoChecker = $videoChecker;
       $this-&gt;setLogger($logger);
    }
 
    /**
     * Check if single trick video URL can be loaded from ajax request.
     *
     * @Route("/{_locale}/load-video/url/{url&lt;(.+)&gt;?}", name="load_video_url_check")
     *
     * @param JsonResponder $responder
     * @param Request       $request
     *
     * @return Response
     *
     * @see https://symfony.com/doc/current/routing/slash_in_parameter.html
     */
    public function __invoke(JsonResponder $responder, Request $request): JsonResponse
    {
        // Check video URL value
        $url = $this-&gt;videoChecker-&gt;filterURLAttribute($request);
        if (\is_null($url)) {
            $this-&gt;logger-&gt;error(
                "[trace app videos] AjaxVideoURLCheckAction/__invoke =&gt; " .
                "Technical error due to video url set to null: check loading process for both client and server side!"
            );
        }
        // Check if URL is formatted as expected (validation) and accessible
        $data = $this-&gt;videoChecker-&gt;verify($url);
        return $responder($data);
    }
}

</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
 
declare(strict_types = 1);
 
namespace App\Responder\Json;
 
use Symfony\Component\HttpFoundation\JsonResponse;
 
/**
 * Class JsonResponder.
 *
 * Manage a simple JSON response with status from ajax request.
 */
final class JsonResponder
{
    /**
     * Invokable Responder with Magic method.
     *
     * @param array $data
     *
     * @return JsonResponse
     */
    public function __invoke(array $data): JsonResponse
    {
        // Encode data with JSON string with serializer
        return new JsonResponse($data);
    }
}</pre>
- Le service, classe PHP simple qui effectue les contrôles sur la ressource vidéo :
<pre class="EnlighterJSRAW" data-enlighter-language="generic">&lt;?php

declare(strict_types = 1);

namespace App\Service\Medias;

use Symfony\Component\HttpFoundation\Request;

/*
 * Class VideoURLProxyChecker.
 *
 * Check if a video URL can be correctly loaded.
 * .
 */
class VideoURLProxyChecker
{
    // CAUTION: these iframe URL patterns should certainly be improved and are very important for a quite "secure" use!
    // Even more, they can evolve, so it is preferable to use providers APIs!
    const ALLOWED_URL_PATTERNS = [
        '/^https?:\/\/www\.youtube\.com\/embed\/[a-zA-Z0-9_-]+$/', // [\w-]+
        '/^https?:\/\/player\.vimeo\.com\/video\/[0-9]+$/',
        '/^https?:\/\/www\.dailymotion\.com\/embed\/video\/[a-zA-Z0-9]+$/'
    ];

    /**
     * Filter provided URL.
     *
     * @param Request $request
     * @param bool    $isDecoded
     *
     * @return null|string
     */
    public function filterURLAttribute(Request $request, bool $isDecoded = false): ?string
    {
        // Get URL to check
        $url = null;
        if (!\is_null($request-&gt;attributes-&gt;get('url'))) {
            $url = $request-&gt;attributes-&gt;get('url');
        }
        return $isDecoded ? $url : urldecode($url);
    }

    /**
     * Check if URL format is allowed.
     *
     * @param string|null $url
     *
     * @return bool
     */
    public function isAllowed(?string $url): bool
    {
        if (\is_null($url)) {
            return false;
        }
        $patterns = self::ALLOWED_URL_PATTERNS;
        // Use of "array_filter" would be more appropriate here!
        for ($i = 0; $i &lt; count($patterns); $i ++) {
            if (preg_match( $patterns[$i], urldecode($url))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Request URL to check if a content can be loaded. Choice is made to use cURL here.
     *
     * CAUTION: do not use this method alone because of potential "SSRF" attacks! At least use isAllowed() before...
     * @link https://www.vaadata.com/blog/understanding-web-vulnerability-server-side-request-forgery-1/
     *
     * @param string|null $url
     *
     * @return bool
     */
    public function isContent(?string $url): bool
    {
        if (\is_null($url)) {
            return false;
        }
        // Youtube particular case to check availability correctly
        // otherwise HTTP code is always 200!
        if (preg_match( '/youtube/', $url)) {
            $url = $this-&gt;prepareAccessToYoutubeVideoContent($url);
        }
        // Use cURL
        $handle = curl_init(urldecode($url));
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
        // Avoid content loading by getting the headers only
        curl_setopt($handle, CURLOPT_NOBODY, 1);
        // Request with cURL
        curl_exec($handle);
        $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
        // Check resource availability with HTTP code 200
        $isContentFound = 200 === $httpCode ? true : false;
        curl_close($handle);
        return $isContentFound;
    }

    /**
     * Check particular youtube video availability.
     *
     * @link Particular case for youtube video:
     * https://stackoverflow.com/questions/29166402/verify-if-video-exist-with-youtube-api-v3
     *
     * @param $url
     *
     * @return string the correct url to use to check availability
     */
    private function prepareAccessToYoutubeVideoContent($url): string
    {
        // Extract video id and use correct URL
        preg_match( '/embed\/(.+)/', urldecode($url), $matches);
        $videoID = $matches[1];
        $url ='https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=' . $videoID;
        return $url;
    }

    /**
     * Return a status code to be converted later in JSON string.
     *
     * Value 1 means URL can be loaded and value 0 means error context must be used!
     *
     * @param string|null $url
     *
     * @return array
     *
     * @see https://symfony.com/doc/current/controller.html#returning-json-response
     */
    public function verify(?string $url): array
    {
        // Prepare array to be converted in JSON string with Symfony JsonResponse object (no need to use "json_encode" here)
        if (\is_null($url)) {
            return ['status' =&gt; 0];
        }
        $url = urldecode($url);
        return $this-&gt;isAllowed($url) &amp;&amp; $this-&gt;isContent($url) ? ['status' =&gt; 1] : ['status' =&gt; 0];
    }

}</pre>
Des erreurs (de copié-collé) peuvent s'être glissées dans le code bien que celui-ci soit issu d'un projet opérationnel.
Je vous invite à me les signaler en me contactant si c'est le cas.
Je suis également preneur si vous avez des conseils pour améliorer certains scripts, ou si vous estimez que certains points sont inexactes.

L'idée de départ, bien que perfectible, reste simple et intéressante pour vérifier l'accès à des ressources externes.

- Promesse et XMLHttpRequest : la fonction JavaScript "request(object)" est en grande partie inspirée de ce script : <a href="http://ccoenraets.github.io/es6-tutorial-data/promisify" title="XMLHttpRequest et promesse pour de l'AJAX" target="_blank" rel="noopener noreferrer nofollow">http://ccoenraets.github.io/es6-tutorial-data/promisify</a>
J'apprécie cette manière de faire notamment pour la gestion d'évènements vraiment souple.
il n'y a pas que "axios" et "fetch api" dans la vie !
- Voici un lien vers <a href="https://symfony.com/4" title="Symfony 4" target="_blank" rel="noopener noreferrer nofollow">Symfony 4</a> qui est utilisé en partie dans ce post.<p>Cet article <a href="https://www.dotprogs.com/tester-le-chargement-d-une-video-en-iframe-avec-ajax/">Tester le chargement (disponibilité) d&rsquo;une video en iframe avec AJAX</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Mettre en place une pagination personnalisée avec Twig</title>
		<link>https://www.dotprogs.com/mettre-en-place-une-pagination-personnalisee-avec-twig/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Sun, 18 Nov 2018 18:02:48 +0000</pubDate>
				<category><![CDATA[Développement web]]></category>
		<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[Intégration web]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Symfony]]></category>
		<category><![CDATA[Templating]]></category>
		<category><![CDATA[Twig]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=856</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/twig-php-pagination-1.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Pagination personnalisée avec Twig" decoding="async" loading="lazy" /></p>
<p>Dans le cadre de la création d'un blog, je me suis documenté sur quelques approches basiques pour réaliser une pagination en PHP - MySQL afin de lister des articles.<br />
une fois le principe de base compris, j'ai décidé par la suite de "personnaliser" un tantinet cette pagination et l'afficher avec <a href="https://twig.symfony.com" target="_blank" rel="noopener">Twig</a> dans un autre projet utilisant le framework <a href="https://symfony.com" target="_blank" rel="noopener">Symfony</a>.</p>
<p>- J'utilise par exemple un "<strong>entity service layer</strong>" qui est une classe PHP qui sert ici de passerelle entre un Repository Doctrine et une Action/Controller.<br />
Je prends en compte également un affichage descendant ou ascendant "$order" qui est bien sûr facultatif car on peut faire plus simple !<br />
L'objectif est de faire une requête SQL en utilisant les paramètres OFFSET ET LIMIT - ici grâce au Repository avec <strong>findByLimitOffsetWithOrder(...)</strong> dans la méthode <strong>getFilteredList(...)</strong> - pour obtenir les posts (articles) propres à la page courante "currentPage" définie dans <strong>getPaginationParameters(...)</strong>.<br />
Les principales méthodes personnelles nécessaires de cet "entity service layer" que j'utilisent sont présentées ci-dessous :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php 
 
declare(strict_types = 1); 
 
namespace App\Service; 
 
// Used classes are not declared to simplify code demo! 
// use ... 
// use ... 
 
class PostServiceLayer 
{ 
    // Traits, properties and constructor with dependency injection and other previous methods 
    // ...
 
    /** 
    * Count all posts without filter. 
    * 
    * @return int 
    * 
    * @throws \Doctrine\ORM\NonUniqueResultException 
    * @throws \UnexpectedValueException 
    */ 
    public function countAll() : int 
    { 
        $result = $this-&gt;repository-&gt;countAll();
        if (\is_null($result)) {
            throw new \UnexpectedValueException('Post total count error: list can not be generated!');
        }
        return $result;
    }
 
    /**
     * Get filtered post list depending on parameters.
     *
     * @param int|null $offset
     * @param int      $limit
     * @param string   $order
     *
     * @return array
     *
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function getFilteredList(
        int $offset = null,
        int $limit = Post::POST_NUMBER_PER_LOADING,
        string $order = Post::POST_LOADING_MODE
    ) : array {
        // Init value to define starting rank
        $init = ('DESC' === $order) ? $this-&gt;countAll() : -1;
        // Offset starts at 0 (i.e. the 15th Post rank has a value of 14)
        $start = $offset;
        $end = $offset + $limit;
        return $this-&gt;repository-&gt;findByLimitOffsetWithOrder($order, $init, $start, $end);
    }
 
    /**
     * Get default parameters to show a post list.
     *
     * (e.g. sort direction, post number for "load more", ...)
     *
     * @return array
     */
    public function getListDefaultParameters() : array
    {
        return [
            'loadingMode'      =&gt; Post::POST_LOADING_MODE,
            'numberPerLoading' =&gt; Post::POST_NUMBER_PER_LOADING,
            'numberPerPage'    =&gt; Post::POST_NUMBER_PER_PAGE
        ];
    }
 
    /**
     * Get pagination parameters to manage page links.
     *
     * This is used on complete post list accessible on "posts" page.
     *
     * @param int $pageIndex
     *
     * @return array|null
     *
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function getPaginationParameters(int $pageIndex) : ?array
    {
        $countAll = $this-&gt;countAll();
        $listDefaultParameters = $this-&gt;getListDefaultParameters();
        $postNumberPerPage = $listDefaultParameters['numberPerPage'];
        $pageCount = $countAll % $postNumberPerPage == 0
            ? $countAll / $postNumberPerPage
            : (int) floor($countAll / $postNumberPerPage) + 1;
        $loadingMode = $listDefaultParameters['loadingMode'];
        if ($pageIndex &lt;= 0 || $pageIndex &gt; $pageCount) {
            return null;
        }
        if ('DESC' === $loadingMode) {
            $offset = $countAll - $pageIndex * $postNumberPerPage &lt; 0
                ? 0 : $countAll - $pageIndex * $postNumberPerPage;
            $limit = $offset === 0
                ? $countAll % $postNumberPerPage : $postNumberPerPage;
 
        } else {
            $offset = $pageIndex === 1
                ? 0 : ($pageIndex - 1) * $postNumberPerPage;
            $limit = $offset + $postNumberPerPage &gt; $countAll - 1
                ? $countAll % $postNumberPerPage : $postNumberPerPage;
        }
        return [
            'currentPage'   =&gt; $pageIndex,
            'currentOffset' =&gt; $offset,
            'currentLimit'  =&gt; $limit,
            'pageCount'     =&gt; $pageCount,
            'loadingMode'   =&gt; $loadingMode,
            'postCount'     =&gt; $countAll
        ];
    }
 
    // Other following methods
    // ...
 
}</pre>
<p>- Voici la partie Twig à intégrer dans un template (les commentaires sont en anglais, c'est une habitude personnelle !) :<br />
Les deux variables importantes à transmettre à la vue depuis une "<strong>Action</strong>" ou un "<strong>Controller</strong>" (non détaillé ici) sont le nombre de page total "<strong>pageCount</strong>" et la page courante "<strong>currentPage</strong>" pour sa mise en exergue et structurer les comportements autour d'elle.<br />
Dans une deuxième temps, il s'agit d'initialiser des variables en fonction des conditions induites par la personnalisation que l'on souhaite obtenir.<br />
Je décide par exemple d'afficher les 2 pages précédentes et suivantes (libre à vous d'en afficher plus!) autour de la page courante : si cela n'est pas possible, j'évalue la possibilité d'afficher 1 page, ou aucune, avant ou après la page courante, ce qui explique les conditions initialisées au préalable.<br />
Je substitue les autres numéros de page par "<strong>...</strong>" pour les matérialiser, à l'exception de la première et la dernière pour conserver les bornes extrêmes, et ainsi gérer un nombre conséquent de page à afficher le cas échéant.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="html">&lt;!-- Generate pagination block if there is at least more than 1 page! --&gt;
    {% if pageCount &gt; 1 %}
        &lt;!-- Pagination --&gt;
        {# Page quantity to show around current page is 2 or a calculated minimum value #}
        {% set defaultPageQuantityAround = 2 %}
        {# Mininum value #}
        {% set minimumPageQuantityAround = min(currentPage - 1, pageCount - currentPage) %}
        {# Condition to show the right page numbers before current page: default or minimum value #}
        {% set conditionBefore = currentPage != 1 and minimumPageQuantityAround &lt;= currentPage - 1 %}
        {# Condition to show the right page numbers after current page: default or minimum value #}
        {% set conditionAfter = currentPage != pageCount and minimumPageQuantityAround &lt;= pageCount - currentPage %}
        {# Define page numbers before, other pages will be replaced by "..." #}
        {% set PageQuantityAroundBefore = conditionBefore ? defaultPageQuantityAround : minimumPageQuantityAround %}
        {# Define page numbers after, other pages will be replaced by "..." #}
        {% set PageQuantityAroundAfter = conditionAfter ? defaultPageQuantityAround : minimumPageQuantityAround %}
        &lt;div class="uk-flex uk-flex-center"&gt;
            &lt;ul class="uk-pagination uk-text-bold uk-text-uppercase"&gt;
                {# Previous link #}
                {% if currentPage - 1 != 0 %}
                &lt;li&gt;&lt;a class="st-color-yellow" href="{{ path('posts', { 'page': currentPage - 1 }) }}" title="Previous"&gt;&lt;span class="uk-margin-small-right" uk-pagination-previous&gt;&lt;/span&gt; Previous&lt;/a&gt;&lt;/li&gt;
                {% endif %}
                {% for i in 1..pageCount %}
                {# Current page to show #}
                {% if currentPage == i %}
                &lt;li class="st-color-red"&gt;{{ i }}&lt;/li&gt;
                {# Show "..." before current page depending on page numbers to show before #}
                {% elseif (i &lt; currentPage and 1 != i) and (i == currentPage - PageQuantityAroundBefore - 1) %}
                &lt;li class="uk-disabled"&gt;...&lt;/li&gt;
                {# Show "..." after current page depending on page numbers to show after #}
                {% elseif (i &gt; currentPage and pageCount != i) and (i == currentPage + PageQuantityAroundAfter + 1) %}
                &lt;li class="uk-disabled"&gt;...&lt;/li&gt;
                {# Hide pages under current page and before "..." excepted page 1 #}
                {% elseif (1 != i) and (i &lt; currentPage - PageQuantityAroundBefore - 1) %}
                &lt;li class="uk-hidden"&gt;&lt;a href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {# Hide pages over current page and after "..." excepted page with number "pageCount" (last) #}
                {% elseif (pageCount != i) and (i &gt; currentPage + PageQuantityAroundAfter + 1) %}
                &lt;li class="uk-hidden"&gt;&lt;a href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {# Apply particular style for lowest link corresponding to fisrt page 1, and Highest link corresponding to page total count #}
                {% elseif i == 1 or i == pageCount %}
                &lt;li&gt;&lt;a class="st-color-blue" href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {# Normal links which are not concerned by conditions above #}
                {% else %}
                &lt;li&gt;&lt;a href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {% endif %}
                {% endfor %}
                {# Next link #}
                {% if currentPage + 1 &lt;= pageCount %}
                &lt;li class="uk-margin-auto-left"&gt;&lt;a class="st-color-yellow" href="{{ path('posts', { 'page': currentPage + 1 }) }}" title="Next"&gt;Next &lt;span class="uk-margin-small-left" uk-pagination-next&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
                {% endif %}
            &lt;/ul&gt;
        &lt;/div&gt;
    {% endif %}</pre>
<p>Et voici le résultat visuel de la pagination :</p>
<p><img src="http://www.dotprogs.com/wp-content/uploaded-files/exemples-pagination-twig.jpg" alt="Exemples de pagination personnalisée Twig" width="500" height="250" class="size-full wp-image-872" /></p>
<p>Les classes CSS utilisées ici sont personnalisées (pour les couleurs) et issues du framework <a href="https://getuikit.com/" target="_blank" rel="noopener">UIkit</a> pour le comportement (lien désactivé, lien caché ...), donc rien de significatif pour ce qui est de l'aspect mise en forme.</p>
<p>Un tel système de pagination peut très facilement être adapté pour de l'<strong>AJAX</strong> et afficher les posts de la page courante (en renvoyant du contenu HTML avec un block Twig par exemple) en asynchrone afin d'être davantage "user friendly" !</p>
<p>Ces extraits de code sont issus d'un projet opérationnel, ceci-dit des "petites coquilles" peuvent s'être glissées entre les lignes.<br />
N'hésitez pas à me contacter pour signaler des erreurs éventuelles ou pour partager des améliorations, car on peut souvent mieux faire ou faire plus pragmatique !</p>
<p>Cet article <a href="https://www.dotprogs.com/mettre-en-place-une-pagination-personnalisee-avec-twig/">Mettre en place une pagination personnalisée avec Twig</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/twig-php-pagination-1.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Pagination personnalisée avec Twig" decoding="async" loading="lazy" /></p>Dans le cadre de la création d'un blog, je me suis documenté sur quelques approches basiques pour réaliser une pagination en PHP - MySQL afin de lister des articles.
une fois le principe de base compris, j'ai décidé par la suite de "personnaliser" un tantinet cette pagination et l'afficher avec <a href="https://twig.symfony.com" target="_blank" rel="noopener">Twig</a> dans un autre projet utilisant le framework <a href="https://symfony.com" target="_blank" rel="noopener">Symfony</a>.

- J'utilise par exemple un "<strong>entity service layer</strong>" qui est une classe PHP qui sert ici de passerelle entre un Repository Doctrine et une Action/Controller.
Je prends en compte également un affichage descendant ou ascendant "$order" qui est bien sûr facultatif car on peut faire plus simple !
L'objectif est de faire une requête SQL en utilisant les paramètres OFFSET ET LIMIT - ici grâce au Repository avec <strong>findByLimitOffsetWithOrder(...)</strong> dans la méthode <strong>getFilteredList(...)</strong> - pour obtenir les posts (articles) propres à la page courante "currentPage" définie dans <strong>getPaginationParameters(...)</strong>.
Les principales méthodes personnelles nécessaires de cet "entity service layer" que j'utilisent sont présentées ci-dessous :
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php 
 
declare(strict_types = 1); 
 
namespace App\Service; 
 
// Used classes are not declared to simplify code demo! 
// use ... 
// use ... 
 
class PostServiceLayer 
{ 
    // Traits, properties and constructor with dependency injection and other previous methods 
    // ...
 
    /** 
    * Count all posts without filter. 
    * 
    * @return int 
    * 
    * @throws \Doctrine\ORM\NonUniqueResultException 
    * @throws \UnexpectedValueException 
    */ 
    public function countAll() : int 
    { 
        $result = $this-&gt;repository-&gt;countAll();
        if (\is_null($result)) {
            throw new \UnexpectedValueException('Post total count error: list can not be generated!');
        }
        return $result;
    }
 
    /**
     * Get filtered post list depending on parameters.
     *
     * @param int|null $offset
     * @param int      $limit
     * @param string   $order
     *
     * @return array
     *
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function getFilteredList(
        int $offset = null,
        int $limit = Post::POST_NUMBER_PER_LOADING,
        string $order = Post::POST_LOADING_MODE
    ) : array {
        // Init value to define starting rank
        $init = ('DESC' === $order) ? $this-&gt;countAll() : -1;
        // Offset starts at 0 (i.e. the 15th Post rank has a value of 14)
        $start = $offset;
        $end = $offset + $limit;
        return $this-&gt;repository-&gt;findByLimitOffsetWithOrder($order, $init, $start, $end);
    }
 
    /**
     * Get default parameters to show a post list.
     *
     * (e.g. sort direction, post number for "load more", ...)
     *
     * @return array
     */
    public function getListDefaultParameters() : array
    {
        return [
            'loadingMode'      =&gt; Post::POST_LOADING_MODE,
            'numberPerLoading' =&gt; Post::POST_NUMBER_PER_LOADING,
            'numberPerPage'    =&gt; Post::POST_NUMBER_PER_PAGE
        ];
    }
 
    /**
     * Get pagination parameters to manage page links.
     *
     * This is used on complete post list accessible on "posts" page.
     *
     * @param int $pageIndex
     *
     * @return array|null
     *
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function getPaginationParameters(int $pageIndex) : ?array
    {
        $countAll = $this-&gt;countAll();
        $listDefaultParameters = $this-&gt;getListDefaultParameters();
        $postNumberPerPage = $listDefaultParameters['numberPerPage'];
        $pageCount = $countAll % $postNumberPerPage == 0
            ? $countAll / $postNumberPerPage
            : (int) floor($countAll / $postNumberPerPage) + 1;
        $loadingMode = $listDefaultParameters['loadingMode'];
        if ($pageIndex &lt;= 0 || $pageIndex &gt; $pageCount) {
            return null;
        }
        if ('DESC' === $loadingMode) {
            $offset = $countAll - $pageIndex * $postNumberPerPage &lt; 0
                ? 0 : $countAll - $pageIndex * $postNumberPerPage;
            $limit = $offset === 0
                ? $countAll % $postNumberPerPage : $postNumberPerPage;
 
        } else {
            $offset = $pageIndex === 1
                ? 0 : ($pageIndex - 1) * $postNumberPerPage;
            $limit = $offset + $postNumberPerPage &gt; $countAll - 1
                ? $countAll % $postNumberPerPage : $postNumberPerPage;
        }
        return [
            'currentPage'   =&gt; $pageIndex,
            'currentOffset' =&gt; $offset,
            'currentLimit'  =&gt; $limit,
            'pageCount'     =&gt; $pageCount,
            'loadingMode'   =&gt; $loadingMode,
            'postCount'     =&gt; $countAll
        ];
    }
 
    // Other following methods
    // ...
 
}</pre>
- Voici la partie Twig à intégrer dans un template (les commentaires sont en anglais, c'est une habitude personnelle !) :
Les deux variables importantes à transmettre à la vue depuis une "<strong>Action</strong>" ou un "<strong>Controller</strong>" (non détaillé ici) sont le nombre de page total "<strong>pageCount</strong>" et la page courante "<strong>currentPage</strong>" pour sa mise en exergue et structurer les comportements autour d'elle.
Dans une deuxième temps, il s'agit d'initialiser des variables en fonction des conditions induites par la personnalisation que l'on souhaite obtenir.
Je décide par exemple d'afficher les 2 pages précédentes et suivantes (libre à vous d'en afficher plus!) autour de la page courante : si cela n'est pas possible, j'évalue la possibilité d'afficher 1 page, ou aucune, avant ou après la page courante, ce qui explique les conditions initialisées au préalable.
Je substitue les autres numéros de page par "<strong>...</strong>" pour les matérialiser, à l'exception de la première et la dernière pour conserver les bornes extrêmes, et ainsi gérer un nombre conséquent de page à afficher le cas échéant.
<pre class="EnlighterJSRAW" data-enlighter-language="html">&lt;!-- Generate pagination block if there is at least more than 1 page! --&gt;
    {% if pageCount &gt; 1 %}
        &lt;!-- Pagination --&gt;
        {# Page quantity to show around current page is 2 or a calculated minimum value #}
        {% set defaultPageQuantityAround = 2 %}
        {# Mininum value #}
        {% set minimumPageQuantityAround = min(currentPage - 1, pageCount - currentPage) %}
        {# Condition to show the right page numbers before current page: default or minimum value #}
        {% set conditionBefore = currentPage != 1 and minimumPageQuantityAround &lt;= currentPage - 1 %}
        {# Condition to show the right page numbers after current page: default or minimum value #}
        {% set conditionAfter = currentPage != pageCount and minimumPageQuantityAround &lt;= pageCount - currentPage %}
        {# Define page numbers before, other pages will be replaced by "..." #}
        {% set PageQuantityAroundBefore = conditionBefore ? defaultPageQuantityAround : minimumPageQuantityAround %}
        {# Define page numbers after, other pages will be replaced by "..." #}
        {% set PageQuantityAroundAfter = conditionAfter ? defaultPageQuantityAround : minimumPageQuantityAround %}
        &lt;div class="uk-flex uk-flex-center"&gt;
            &lt;ul class="uk-pagination uk-text-bold uk-text-uppercase"&gt;
                {# Previous link #}
                {% if currentPage - 1 != 0 %}
                &lt;li&gt;&lt;a class="st-color-yellow" href="{{ path('posts', { 'page': currentPage - 1 }) }}" title="Previous"&gt;&lt;span class="uk-margin-small-right" uk-pagination-previous&gt;&lt;/span&gt; Previous&lt;/a&gt;&lt;/li&gt;
                {% endif %}
                {% for i in 1..pageCount %}
                {# Current page to show #}
                {% if currentPage == i %}
                &lt;li class="st-color-red"&gt;{{ i }}&lt;/li&gt;
                {# Show "..." before current page depending on page numbers to show before #}
                {% elseif (i &lt; currentPage and 1 != i) and (i == currentPage - PageQuantityAroundBefore - 1) %}
                &lt;li class="uk-disabled"&gt;...&lt;/li&gt;
                {# Show "..." after current page depending on page numbers to show after #}
                {% elseif (i &gt; currentPage and pageCount != i) and (i == currentPage + PageQuantityAroundAfter + 1) %}
                &lt;li class="uk-disabled"&gt;...&lt;/li&gt;
                {# Hide pages under current page and before "..." excepted page 1 #}
                {% elseif (1 != i) and (i &lt; currentPage - PageQuantityAroundBefore - 1) %}
                &lt;li class="uk-hidden"&gt;&lt;a href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {# Hide pages over current page and after "..." excepted page with number "pageCount" (last) #}
                {% elseif (pageCount != i) and (i &gt; currentPage + PageQuantityAroundAfter + 1) %}
                &lt;li class="uk-hidden"&gt;&lt;a href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {# Apply particular style for lowest link corresponding to fisrt page 1, and Highest link corresponding to page total count #}
                {% elseif i == 1 or i == pageCount %}
                &lt;li&gt;&lt;a class="st-color-blue" href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {# Normal links which are not concerned by conditions above #}
                {% else %}
                &lt;li&gt;&lt;a href="{{ path('posts', { 'page': i }) }}" title="Page {{ i }}"&gt;{{ i }}&lt;/a&gt;&lt;/li&gt;
                {% endif %}
                {% endfor %}
                {# Next link #}
                {% if currentPage + 1 &lt;= pageCount %}
                &lt;li class="uk-margin-auto-left"&gt;&lt;a class="st-color-yellow" href="{{ path('posts', { 'page': currentPage + 1 }) }}" title="Next"&gt;Next &lt;span class="uk-margin-small-left" uk-pagination-next&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
                {% endif %}
            &lt;/ul&gt;
        &lt;/div&gt;
    {% endif %}</pre>
Et voici le résultat visuel de la pagination :

<img src="http://www.dotprogs.com/wp-content/uploaded-files/exemples-pagination-twig.jpg" alt="Exemples de pagination personnalisée Twig" width="500" height="250" class="size-full wp-image-872" />

Les classes CSS utilisées ici sont personnalisées (pour les couleurs) et issues du framework <a href="https://getuikit.com/" target="_blank" rel="noopener">UIkit</a> pour le comportement (lien désactivé, lien caché ...), donc rien de significatif pour ce qui est de l'aspect mise en forme.

Un tel système de pagination peut très facilement être adapté pour de l'<strong>AJAX</strong> et afficher les posts de la page courante (en renvoyant du contenu HTML avec un block Twig par exemple) en asynchrone afin d'être davantage "user friendly" !

Ces extraits de code sont issus d'un projet opérationnel, ceci-dit des "petites coquilles" peuvent s'être glissées entre les lignes.
N'hésitez pas à me contacter pour signaler des erreurs éventuelles ou pour partager des améliorations, car on peut souvent mieux faire ou faire plus pragmatique !<p>Cet article <a href="https://www.dotprogs.com/mettre-en-place-une-pagination-personnalisee-avec-twig/">Mettre en place une pagination personnalisée avec Twig</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Qui peut prétendre vraiment s&#8217;appuyer sur un designer « UX » ?</title>
		<link>https://www.dotprogs.com/qui-utilise-vraiment-le-design-ux/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Mon, 19 Dec 2016 16:54:23 +0000</pubDate>
				<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[Réflexion métier]]></category>
		<category><![CDATA[design thinking]]></category>
		<category><![CDATA[développeur front end]]></category>
		<category><![CDATA[directeur artistique]]></category>
		<category><![CDATA[gestion de projet]]></category>
		<category><![CDATA[UI]]></category>
		<category><![CDATA[UI designer]]></category>
		<category><![CDATA[user experience]]></category>
		<category><![CDATA[user interface]]></category>
		<category><![CDATA[UX]]></category>
		<category><![CDATA[UX designer]]></category>
		<category><![CDATA[webdesigner]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=692</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/ui-ux-design.jpg" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Le vrai UI-UX designer !" decoding="async" loading="lazy" /></p>
<h2 class="dp-level2-title">L'expérience utilisateur, une aventure vraiment sérieuse&nbsp;!</h2>
<h3 class="dp-level3-title">Du design pour un expérience réussie</h3>
<p><em>Le design "UX" en bref :</em><br />
Le design d'expérience traduit la conception (élaboration, création) de l'expérience utilisateur (<strong>U</strong>ser e<strong>X</strong>perience) associée à l'usage d'un produit (service, système, etc ...).<br />
Dans le domaine du web et en particulier pour un site web donné, l'expérience utilisateur est constituée notamment de son référencement, de son apparence (graphisme), de l'ergonomie (cohérence, positionnement des éléments) , de son efficacité (actions facilitées et accès clair à l'information), de son adaptation aux nouveaux usages (prise en compte des nouveaux terminaux) ...</p>
<p>Un bon design d'expérience est un design qui répond aux besoins et aux habitudes de l'utilisateur, celui-ci provoque ainsi de l'émotion et permet une expérience satisfaisante voire aboutie. Cette expérience est rendue possible grâce au design "UI" (<strong>U</strong>ser <strong>I</strong>nterface) : la conception d'une interface entre l'utilisateur et le produit (ou service, ou autre) doit être en particulier suffisamment intuitive et efficace pour créer une expérience agréable.</p>
<p><em>Quelles sont les compétences attendues du "UX" designer ?</em><br />
Les entreprises veulent toutes créer des produits ou services ayant un succès à la clef, cependant l'idée de départ est souvent très bonne et la mise en oeuvre n'est pas toujours à la hauteur.</p>
<p>Pour cette raison, les entreprises qui veulent atteindre un objectif réel de réponses aux attentes de l'utilisateur, font appel à tous les moyens disponibles dont le design "UX" pour maximiser les chances de réussite.<br />
Pour certaines, elles prennent la décision d'embaucher spécifiquement un designer "UX" qui aura pour mission de coordonner au sein de l'entreprise cette démarche : se pose alors la nécessité d'investir et surtout de croire en ce modèle de conception qui est bien sûr reconnu dans le milieu du web.<br />
Le design "UX" a pu être pratiqué parfois de manière assez empirique, hasardeuse, il a désormais été davantage formalisé et il existe de véritables cursus de formation qui en font un ensemble de compétences professionnelles reconnues.<br />
Ce métier fait logiquement appel à de multiples connaissances variées telles qu'imaginer et concevoir des solutions correspondant aux besoins de l'utilisateur, analyser les comportements et les modes de consommation de celui-ci, concevoir des interfaces et fonctionnalités en adéquation, en restant dans le cadre autant que possible de la demande du client.<br />
Le designer "UX" sera d'autant meilleur qu'il saura travailler avec les différents intervenants impliqués dans le projet proposé.  </p>
<p><em>Peut-on attendre autant de compétences stratégiques, d'une seule personne, ou est-ce un poste qui sert à aiguiller vers un objectif majeur : l'utilisateur ?</em><br />
Les efforts d'un designer "UX" vont être concentrées sur les interactions qui vont naître entre le produit et l'utilisateur : autrement dit l'interface proposée pour un produit, service, système aura un rôle central.<br />
Il est nécessaire de comprendre que le designer "UX" est un peu de le chef d'orchestre pour l'application de cette approche : il doit impulser la réflexion et la coordination avec les différentes acteurs du projets en interne (chef de projet, webdesigner, directeur artistique, intégrateur, développeur) et en externe (expliciter le concept au niveau du client) pour convaincre de l'utilité de la démarche et la rendre concrète.</p>
<p>Les graphistes vont par exemple concevoir des interfaces agréables répondant au service proposé, les intégrateurs et développeurs vont mettre en place des fonctionnalités et les rendre vivantes et magnifier l'interface.<br />
Quel que soit le domaine de compétence, chacun doit prendre en compte le design "UX" et est naturellement impliqué, et pas simplement la personne dédiée à cette démarche.</p>
<p>En réalité, le designer "UX" ne peut pas raisonnablement remplir l'ensemble de ces rôles, car les compétences techniques et conceptuelles sont sans doute trop vastes pour un seul individu. Si son expérience personnelle et professionnelle est conséquente et pluridisciplinaire, elle lui permettra ceci-dit, d'avoir un regard global assez pointu.</p>
<p><em>Pourquoi en définitive, il est indispensable d'accorder de l'importance au design "UX" ?</em><br />
Si cette discipline est maîtrisée par une entreprise, elle peut en faire un avantage concurrentiel majeur pour se différencier.</p>
<p>Il est certain qu'intégrer le design "UX" dans un processus de conception, induit une remise en question conséquente des équipes au sein d'une agence par exemple pour les sensibiliser, les former, les impliquer, et leur permettre d'adhérer à ce type de méthodes innovantes.</p>
<p>On voit clairement apparaître le besoin impérieux de sensibiliser l'ensemble d'une équipe au design "UX" pour mener à bien la conception de l'expérience utilisateur : toutes les entreprises n'ont pas franchi le pas, mais peuvent difficilement ignorer la démarche.</p>
<p>Sans moyens financiers et humains, et aussi sans conviction, il ne peut pas, sans doute, y avoir de design "UX" sincère et efficace au sein de l'entreprise, il ne suffit pas d'en parler pour sembler fonctionner de manière plus professionnelle.</p>
<p>Personnellement et à titre d'exemple, mes modestes expériences dans le web ne m'ont pas permis de côtoyer des gestions de projets aussi abouties gravitant autour du design "UX", la professionnalisation pourtant grandissante de notre secteur d'activité laisse très certainement encore des possibilités a beaucoup de structures, pour se démarquer et mieux travailler avec des méthodologies vraiment pertinentes voire indispensables comme le design "UX".</p>
<p>Vous trouverez ici une <a href="http://blogdummi.fr/infographie/ux-design-comprendre-design-experience-10-images/" title="présentation du design UI" target="_blank" rel="noopener">présentation</a> du design "UI" ainsi que des <a href="http://newflux.fr" title="ressources sur le design UX" target="_blank" rel="noopener">ressources</a> complètes sur ce site web.</p>
<p>Cet article <a href="https://www.dotprogs.com/qui-utilise-vraiment-le-design-ux/">Qui peut prétendre vraiment s&rsquo;appuyer sur un designer « UX » ?</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/ui-ux-design.jpg" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Le vrai UI-UX designer !" decoding="async" loading="lazy" /></p><h2 class="dp-level2-title">L'expérience utilisateur, une aventure vraiment sérieuse&nbsp;!</h2>
<h3 class="dp-level3-title">Du design pour un expérience réussie</h3>
<em>Le design "UX" en bref :</em>
Le design d'expérience traduit la conception (élaboration, création) de l'expérience utilisateur (<strong>U</strong>ser e<strong>X</strong>perience) associée à l'usage d'un produit (service, système, etc ...).
Dans le domaine du web et en particulier pour un site web donné, l'expérience utilisateur est constituée notamment de son référencement, de son apparence (graphisme), de l'ergonomie (cohérence, positionnement des éléments) , de son efficacité (actions facilitées et accès clair à l'information), de son adaptation aux nouveaux usages (prise en compte des nouveaux terminaux) ...

Un bon design d'expérience est un design qui répond aux besoins et aux habitudes de l'utilisateur, celui-ci provoque ainsi de l'émotion et permet une expérience satisfaisante voire aboutie. Cette expérience est rendue possible grâce au design "UI" (<strong>U</strong>ser <strong>I</strong>nterface) : la conception d'une interface entre l'utilisateur et le produit (ou service, ou autre) doit être en particulier suffisamment intuitive et efficace pour créer une expérience agréable.

<em>Quelles sont les compétences attendues du "UX" designer ?</em>
Les entreprises veulent toutes créer des produits ou services ayant un succès à la clef, cependant l'idée de départ est souvent très bonne et la mise en oeuvre n'est pas toujours à la hauteur.

Pour cette raison, les entreprises qui veulent atteindre un objectif réel de réponses aux attentes de l'utilisateur, font appel à tous les moyens disponibles dont le design "UX" pour maximiser les chances de réussite. 
Pour certaines, elles prennent la décision d'embaucher spécifiquement un designer "UX" qui aura pour mission de coordonner au sein de l'entreprise cette démarche : se pose alors la nécessité d'investir et surtout de croire en ce modèle de conception qui est bien sûr reconnu dans le milieu du web. 
Le design "UX" a pu être pratiqué parfois de manière assez empirique, hasardeuse, il a désormais été davantage formalisé et il existe de véritables cursus de formation qui en font un ensemble de compétences professionnelles reconnues.
Ce métier fait logiquement appel à de multiples connaissances variées telles qu'imaginer et concevoir des solutions correspondant aux besoins de l'utilisateur, analyser les comportements et les modes de consommation de celui-ci, concevoir des interfaces et fonctionnalités en adéquation, en restant dans le cadre autant que possible de la demande du client.
Le designer "UX" sera d'autant meilleur qu'il saura travailler avec les différents intervenants impliqués dans le projet proposé.  

<em>Peut-on attendre autant de compétences stratégiques, d'une seule personne, ou est-ce un poste qui sert à aiguiller vers un objectif majeur : l'utilisateur ?</em>
Les efforts d'un designer "UX" vont être concentrées sur les interactions qui vont naître entre le produit et l'utilisateur : autrement dit l'interface proposée pour un produit, service, système aura un rôle central.
Il est nécessaire de comprendre que le designer "UX" est un peu de le chef d'orchestre pour l'application de cette approche : il doit impulser la réflexion et la coordination avec les différentes acteurs du projets en interne (chef de projet, webdesigner, directeur artistique, intégrateur, développeur) et en externe (expliciter le concept au niveau du client) pour convaincre de l'utilité de la démarche et la rendre concrète.

Les graphistes vont par exemple concevoir des interfaces agréables répondant au service proposé, les intégrateurs et développeurs vont mettre en place des fonctionnalités et les rendre vivantes et magnifier l'interface.
Quel que soit le domaine de compétence, chacun doit prendre en compte le design "UX" et est naturellement impliqué, et pas simplement la personne dédiée à cette démarche.

En réalité, le designer "UX" ne peut pas raisonnablement remplir l'ensemble de ces rôles, car les compétences techniques et conceptuelles sont sans doute trop vastes pour un seul individu. Si son expérience personnelle et professionnelle est conséquente et pluridisciplinaire, elle lui permettra ceci-dit, d'avoir un regard global assez pointu.

<em>Pourquoi en définitive, il est indispensable d'accorder de l'importance au design "UX" ?</em>
Si cette discipline est maîtrisée par une entreprise, elle peut en faire un avantage concurrentiel majeur pour se différencier.

Il est certain qu'intégrer le design "UX" dans un processus de conception, induit une remise en question conséquente des équipes au sein d'une agence par exemple pour les sensibiliser, les former, les impliquer, et leur permettre d'adhérer à ce type de méthodes innovantes.

On voit clairement apparaître le besoin impérieux de sensibiliser l'ensemble d'une équipe au design "UX" pour mener à bien la conception de l'expérience utilisateur : toutes les entreprises n'ont pas franchi le pas, mais peuvent difficilement ignorer la démarche.

Sans moyens financiers et humains, et aussi sans conviction, il ne peut pas, sans doute, y avoir de design "UX" sincère et efficace au sein de l'entreprise, il ne suffit pas d'en parler pour sembler fonctionner de manière plus professionnelle.

Personnellement et à titre d'exemple, mes modestes expériences dans le web ne m'ont pas permis de côtoyer des gestions de projets aussi abouties gravitant autour du design "UX", la professionnalisation pourtant grandissante de notre secteur d'activité laisse très certainement encore des possibilités a beaucoup de structures, pour se démarquer et mieux travailler avec des méthodologies vraiment pertinentes voire indispensables comme le design "UX".

Vous trouverez ici une <a href="http://blogdummi.fr/infographie/ux-design-comprendre-design-experience-10-images/" title="présentation du design UI" target="_blank" rel="noopener">présentation</a> du design "UI" ainsi que des <a href="http://newflux.fr" title="ressources sur le design UX" target="_blank" rel="noopener">ressources</a> complètes sur ce site web.
<p>Cet article <a href="https://www.dotprogs.com/qui-utilise-vraiment-le-design-ux/">Qui peut prétendre vraiment s&rsquo;appuyer sur un designer « UX » ?</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Quand le yoga entretient la condition physique &#8230;</title>
		<link>https://www.dotprogs.com/yoga-entretient-condition-physique/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Mon, 28 Nov 2016 16:17:05 +0000</pubDate>
				<category><![CDATA[Dotplus]]></category>
		<category><![CDATA[Yoga]]></category>
		<category><![CDATA[Ashtanga]]></category>
		<category><![CDATA[bien-être]]></category>
		<category><![CDATA[bienfaits]]></category>
		<category><![CDATA[dynamique]]></category>
		<category><![CDATA[physique]]></category>
		<category><![CDATA[santé]]></category>
		<category><![CDATA[sédentarité]]></category>
		<category><![CDATA[sport]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=744</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/ashtanga-yoga.jpg" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Yoga Ashtanga et condition physique" decoding="async" loading="lazy" /></p>
<h2 class="dp-level2-title">Ashtanga, un yoga intense et bienfaisant</h2>
<h3 class="dp-level3-title">Le plaisir est une quête qui passe par la culture de l'effort.</h3>
<p>Une vidéo d'introduction qui plante le décor, et qui bien sûr n'est pas l'illustration de ce qui nous attend lorsqu'on débute le yoga, mais qui démontre quel niveau de prouesses, un pratiquant peut atteindre en "Ashtanga" Yoga, l'expression "effort intense" prend ici tout son sens !</p>
<p>Je précise tout de suite que cette forme de yoga est vraiment portée sur l'effort physique en plus de la méditation, et devient "quasi acrobatique" à haut niveau. Elle n'est en rien le symbole de la pratique du yoga, très divers par ces courants et évolutions, voire modernisé dans certains cas.</p>
<p>Quelques vidéos en lien ci-dessous permettent de réaliser quel est le cheminement à suivre avant d'espérer simplement progresser spécifiquement en Yoga "Ashtanga" ou pour certains peut-être arriver à un véritable niveau avancé. Le plaisir, le bien-être et prendre soin de soi sont des objectifs beaucoup plus fondamentaux que la performance, vous l'aurez bien compris.</p>
<p><iframe class="dp-iframe" width="560" height="315" src="https://www.youtube.com/embed/dQpx7bF0OkY" frameborder="0" allowfullscreen></iframe></p>
<p class="dp-text-center">Ashtanga Yoga niveau expert en vidéo !</p>
<p>Tout d'abord, je tiens à dire que je ne prétends pas connaître le yoga, cependant je m'y intéresse pour avoir eu la chance d'être initié avec surprise et étonnement à quelques séances par des connaissances, et en garde un regard agréable et convaincu.<br />
J'avais des à-priori avant cette découverte, je l'admets, dont notamment un regard faussé, en considérant que le yoga se réduisait beaucoup à de la méditation associée à des postures assez lentes ...</p>
<p>Avant toute chose, j'ai souhaité évoqué ce sujet en soulignant que le yoga est une forme de discipline qui permet de contrer la sédentarité excessive que l'on peut connaître en travaillant toute la journée assis sur une chaise, comme par exemple, lorsqu'on travaille dans le web !</p>
<p>Le Yoga "Ashtanga" est un "style" parmi les nombreuses orientations existantes qui se caractérise par un enchaînement de postures gradué par un certains nombre de "séries" de plus en plus exigeantes. La respiration et le regard sont deux axes de pratique caractéristiques de cette forme de yoga : le souffle est nécessaire par rapport à l'intensité et l'effort demandé par les postures, fixer son regard permet aussi de travailler son attention et se focaliser uniquement sur la pratique. Le Yoga "Ashtanga" demande d'être présent physiquement et psychiquement pour produire pleinement son bénéfice.</p>
<p>Les séries permettent à chacun d'évoluer à son rythme, et c'est surtout cela qu'il faut retenir, en prenant en compte le principe que chaque individu a des objectifs différents. </p>
<p>Par la pratique il est possible de trouver son espace d'expression, de satisfaction, de progression et aussi parfois de spiritualité.<br />
Le yoga (en particulier le courant "Ashtanga") est aussi une excellente préparation physique pour un autre sport, ou à défaut d'autres activités, assure une bonne condition au quotidien, et peut avoir de nombreux <a href="http://www.achacunsonyoga.net/quen-dit-la-science.php" title="yoga et santé">effets "thérapeutiques"</a> sur notre santé, s'il est bien encadré et correctement pratiqué bien évidemment.</p>
<p>- Présentation des différents courants ou pratiques de Yoga dont "Ashtanga" sur <a href="http://www.yogapassion.fr/differents-styles-yoga" title="présentation du Yoga" target="_blank" rel="noopener">cette page</a><br />
- Exemple de postures pour débutant en Yoga Ashtanga en <a href="https://youtu.be/Z8In0I1WHFs" title="Ashtanga Yoga pour débutants" target="_blank" rel="noopener">video</a><br />
- Exemple de postures pour intermédiaire en Yoga Ashtanga en <a href="https://youtu.be/WK92nupSKkc" title="Ashtanga Yoga pour intermédiaires" target="_blank" rel="noopener">video</a><br />
- Exemple de postures pour confirmé en Yoga Ashtanga en <a href="https://youtu.be/jwPFXr5dXyc" title="Ashtanga Yoga pour confirmés" target="_blank" rel="noopener">video</a></p>
<p>Cet article <a href="https://www.dotprogs.com/yoga-entretient-condition-physique/">Quand le yoga entretient la condition physique &#8230;</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/ashtanga-yoga.jpg" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Yoga Ashtanga et condition physique" decoding="async" loading="lazy" /></p><h2 class="dp-level2-title">Ashtanga, un yoga intense et bienfaisant</h2>
<h3 class="dp-level3-title">Le plaisir est une quête qui passe par la culture de l'effort.</h3>

Une vidéo d'introduction qui plante le décor, et qui bien sûr n'est pas l'illustration de ce qui nous attend lorsqu'on débute le yoga, mais qui démontre quel niveau de prouesses, un pratiquant peut atteindre en "Ashtanga" Yoga, l'expression "effort intense" prend ici tout son sens !

Je précise tout de suite que cette forme de yoga est vraiment portée sur l'effort physique en plus de la méditation, et devient "quasi acrobatique" à haut niveau. Elle n'est en rien le symbole de la pratique du yoga, très divers par ces courants et évolutions, voire modernisé dans certains cas.

Quelques vidéos en lien ci-dessous permettent de réaliser quel est le cheminement à suivre avant d'espérer simplement progresser spécifiquement en Yoga "Ashtanga" ou pour certains peut-être arriver à un véritable niveau avancé. Le plaisir, le bien-être et prendre soin de soi sont des objectifs beaucoup plus fondamentaux que la performance, vous l'aurez bien compris.

<iframe class="dp-iframe" width="560" height="315" src="https://www.youtube.com/embed/dQpx7bF0OkY" frameborder="0" allowfullscreen></iframe>
<p class="dp-text-center">Ashtanga Yoga niveau expert en vidéo !</p>

Tout d'abord, je tiens à dire que je ne prétends pas connaître le yoga, cependant je m'y intéresse pour avoir eu la chance d'être initié avec surprise et étonnement à quelques séances par des connaissances, et en garde un regard agréable et convaincu.
J'avais des à-priori avant cette découverte, je l'admets, dont notamment un regard faussé, en considérant que le yoga se réduisait beaucoup à de la méditation associée à des postures assez lentes ...

Avant toute chose, j'ai souhaité évoqué ce sujet en soulignant que le yoga est une forme de discipline qui permet de contrer la sédentarité excessive que l'on peut connaître en travaillant toute la journée assis sur une chaise, comme par exemple, lorsqu'on travaille dans le web !

Le Yoga "Ashtanga" est un "style" parmi les nombreuses orientations existantes qui se caractérise par un enchaînement de postures gradué par un certains nombre de "séries" de plus en plus exigeantes. La respiration et le regard sont deux axes de pratique caractéristiques de cette forme de yoga : le souffle est nécessaire par rapport à l'intensité et l'effort demandé par les postures, fixer son regard permet aussi de travailler son attention et se focaliser uniquement sur la pratique. Le Yoga "Ashtanga" demande d'être présent physiquement et psychiquement pour produire pleinement son bénéfice.

Les séries permettent à chacun d'évoluer à son rythme, et c'est surtout cela qu'il faut retenir, en prenant en compte le principe que chaque individu a des objectifs différents. 

Par la pratique il est possible de trouver son espace d'expression, de satisfaction, de progression et aussi parfois de spiritualité. 
Le yoga (en particulier le courant "Ashtanga") est aussi une excellente préparation physique pour un autre sport, ou à défaut d'autres activités, assure une bonne condition au quotidien, et peut avoir de nombreux <a href="http://www.achacunsonyoga.net/quen-dit-la-science.php" title="yoga et santé">effets "thérapeutiques"</a> sur notre santé, s'il est bien encadré et correctement pratiqué bien évidemment.

- Présentation des différents courants ou pratiques de Yoga dont "Ashtanga" sur <a href="http://www.yogapassion.fr/differents-styles-yoga" title="présentation du Yoga" target="_blank" rel="noopener">cette page</a> 
- Exemple de postures pour débutant en Yoga Ashtanga en <a href="https://youtu.be/Z8In0I1WHFs" title="Ashtanga Yoga pour débutants" target="_blank" rel="noopener">video</a>
- Exemple de postures pour intermédiaire en Yoga Ashtanga en <a href="https://youtu.be/WK92nupSKkc" title="Ashtanga Yoga pour intermédiaires" target="_blank" rel="noopener">video</a>
- Exemple de postures pour confirmé en Yoga Ashtanga en <a href="https://youtu.be/jwPFXr5dXyc" title="Ashtanga Yoga pour confirmés" target="_blank" rel="noopener">video</a><p>Cet article <a href="https://www.dotprogs.com/yoga-entretient-condition-physique/">Quand le yoga entretient la condition physique &#8230;</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>SVG et son utilisation pour le web : des ressources variées</title>
		<link>https://www.dotprogs.com/format-svg-utilisation-web/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Sun, 13 Nov 2016 17:25:02 +0000</pubDate>
				<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[Intégration web]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[pattern]]></category>
		<category><![CDATA[responsive design]]></category>
		<category><![CDATA[SVG]]></category>
		<category><![CDATA[vecteur]]></category>
		<category><![CDATA[XML]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=305</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/scalable-vector-graphics.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="SVG - Scalable Vector Graphics" decoding="async" loading="lazy" /></p>
<h2 class="dp-level2-title">Des possibilités d'intégration intéressantes avec le format "SVG"</h2>
<h3 class="dp-level3-title">Ses atouts majeurs</h3>
<p>"SVG" (pour "Scalable Vector Graphic") est un format d'image pour les vecteurs graphiques et c'est ce qui fait son charme !<br />
Ceci implique que ce format est facilement adaptable en dimensions, sans perte de qualité. Les fichiers en format "SVG" ont une taille réduite et bénéficie d'un bon taux de compression. L'affichage des images "SVG" sur des périphériques à haute définition et densité de pixel (type "retina" ou autre) est du coup bien géré.</p>
<p>Les formes dessinées sont aisément contrôlables, nous pouvons les animer facilement et leur appliquer des styles et de l'interactivité avec CSS et JavaScript.</p>
<p>Les navigateurs modernes permettent désormais de profiter de ce format, mais bien que "SVG" ne soit pas une véritablement une nouveauté, il n'est pas régulièrement utilisé sur un site web.</p>
<p>L'intégration, bien que clairement documentée nécessite un certain apprentissage. Il existe aussi certaines limites et exceptions comme souvent qui nécessitent de les contourner notamment en consultant <a tile="SVG et compatibilité navigateurs" href="http://caniuse.com/#feat=svg" target="_blank" rel="noopener">caniuse</a>, notre référence pour les problématiques d'utilisation.</p>
<p>"SVG" est basé sur le langage "XML", un fichier est donc facilement manipulable dans un simple éditeur de code.<br />
un exemple de format "SVG" qui représente un pictogramme "tools" présent sur le visuel de cet article :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="html">&lt;svg version="1.1" id="Tools" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="300px" height="300px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve"&gt;
&lt;path d="M3.135,6.89c0.933-0.725,1.707-0.225,2.74,0.971c0.116,0.135,0.272-0.023,0.361-0.1c0.088-0.078,1.451-1.305,1.518-1.361
 C7.82,6.341,7.9,6.231,7.795,6.108C7.688,5.985,7.301,5.483,7.052,5.157c-1.808-2.365,4.946-3.969,3.909-3.994
 c-0.528-0.014-2.646-0.039-2.963-0.004C6.715,1.294,5.104,2.493,4.293,3.052C3.232,3.778,2.836,4.204,2.771,4.263
 c-0.3,0.262-0.048,0.867-0.592,1.344C1.604,6.11,1.245,5.729,0.912,6.021C0.747,6.167,0.285,6.513,0.153,6.628
 C0.02,6.745-0.004,6.942,0.132,7.099c0,0,1.264,1.396,1.37,1.52C1.607,8.741,1.893,8.847,2.069,8.69
 c0.177-0.156,0.632-0.553,0.708-0.623C2.855,8.001,2.727,7.206,3.135,6.89z M8.843,7.407c-0.12-0.139-0.269-0.143-0.397-0.029
 L7.012,8.63c-0.113,0.1-0.129,0.283-0.027,0.4l8.294,9.439c0.194,0.223,0.53,0.246,0.751,0.053L17,17.709
 c0.222-0.195,0.245-0.533,0.052-0.758L8.843,7.407z M19.902,3.39c-0.074-0.494-0.33-0.391-0.463-0.182
 c-0.133,0.211-0.721,1.102-0.963,1.506c-0.24,0.4-0.832,1.191-1.934,0.41c-1.148-0.811-0.749-1.377-0.549-1.758
 c0.201-0.383,0.818-1.457,0.907-1.59c0.089-0.135-0.015-0.527-0.371-0.363c-0.357,0.164-2.523,1.025-2.823,2.26
 c-0.307,1.256,0.257,2.379-0.85,3.494l-1.343,1.4l1.349,1.566l1.654-1.57c0.394-0.396,1.236-0.781,1.998-0.607
 c1.633,0.369,2.524-0.244,3.061-1.258C20.057,5.792,19.977,3.884,19.902,3.39z M2.739,17.053c-0.208,0.209-0.208,0.549,0,0.758
 l0.951,0.93c0.208,0.209,0.538,0.121,0.746-0.088l4.907-4.824L7.84,12.115L2.739,17.053z"/&gt;
&lt;/svg&gt;</pre>
<p>Des formes géométriques simples peuvent être traitées (rect, circle, elipse et polygon), ou plus complexes (balises polyline et path) comme l'exemple ci-dessus.</p>
<p>Le code avec balise "svg" ci-dessus peut-être placé tel quel dans une page web en HTML5, il existe en réalité différentes manières d'afficher et utiliser un "SVG" pour un site web dont notamment la balise "iframe", "object" et "img" et également l'intégration en background CSS.</p>
<p>L’attribut "viewBox" est important et permet l'affichage des formes représentées. Les attributs "width" et "height" définissent quant à eux le cadre fondamental qui va le contenir et l'afficher dans une page web. Lors de l'intégration, il faut donc apporter une attention particulière à ses valeurs. Voici ce que cela donne concrètement avec une <a title="SVG et attributs viewBox, width, height" href="http://codepen.io/dbj/pen/RWmQNJ" target="_blank" rel="noopener">illustration</a>.</p>
<p>Vous pouvez exporter un fichier SVG à partir d'un éditeur graphique tel que Illustrator, Inkscape ou autres, cependant le code généré nécessitera d'être <a title="SVG et optimisation" href="https://github.com/svg/svgo" target="_blank" rel="noopener">optimisé</a>.</p>
<p>"SVG" permet des effets graphiques comme le détourage, masquage (clipPath et mask), ainsi que des filtres, et surtout il est adaptatif ce qui le rend pratique pour le responsive design.</p>
<p>Le "profil" de "SVG" a été brièvement abordé dans cet article, pour aller plus loin dans le domaine, je vous propose un <a title="SVG et intégration web" href="https://la-cascade.io/tag/svg/" target="_blank" rel="noopener">lien</a> très riche avec de nombreuses ressources et une revue assez large de ses capacités et surtout comme apprendre à l'utiliser.</p>
<p>Cet article <a href="https://www.dotprogs.com/format-svg-utilisation-web/">SVG et son utilisation pour le web : des ressources variées</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/scalable-vector-graphics.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="SVG - Scalable Vector Graphics" decoding="async" loading="lazy" /></p><h2 class="dp-level2-title">Des possibilités d'intégration intéressantes avec le format "SVG"</h2>
<h3 class="dp-level3-title">Ses atouts majeurs</h3>
"SVG" (pour "Scalable Vector Graphic") est un format d'image pour les vecteurs graphiques et c'est ce qui fait son charme !
Ceci implique que ce format est facilement adaptable en dimensions, sans perte de qualité. Les fichiers en format "SVG" ont une taille réduite et bénéficie d'un bon taux de compression. L'affichage des images "SVG" sur des périphériques à haute définition et densité de pixel (type "retina" ou autre) est du coup bien géré.

Les formes dessinées sont aisément contrôlables, nous pouvons les animer facilement et leur appliquer des styles et de l'interactivité avec CSS et JavaScript.

Les navigateurs modernes permettent désormais de profiter de ce format, mais bien que "SVG" ne soit pas une véritablement une nouveauté, il n'est pas régulièrement utilisé sur un site web.

L'intégration, bien que clairement documentée nécessite un certain apprentissage. Il existe aussi certaines limites et exceptions comme souvent qui nécessitent de les contourner notamment en consultant <a tile="SVG et compatibilité navigateurs" href="http://caniuse.com/#feat=svg" target="_blank" rel="noopener">caniuse</a>, notre référence pour les problématiques d'utilisation.

"SVG" est basé sur le langage "XML", un fichier est donc facilement manipulable dans un simple éditeur de code.
un exemple de format "SVG" qui représente un pictogramme "tools" présent sur le visuel de cet article :
<pre class="EnlighterJSRAW" data-enlighter-language="html">&lt;svg version="1.1" id="Tools" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="300px" height="300px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve"&gt;
&lt;path d="M3.135,6.89c0.933-0.725,1.707-0.225,2.74,0.971c0.116,0.135,0.272-0.023,0.361-0.1c0.088-0.078,1.451-1.305,1.518-1.361
 C7.82,6.341,7.9,6.231,7.795,6.108C7.688,5.985,7.301,5.483,7.052,5.157c-1.808-2.365,4.946-3.969,3.909-3.994
 c-0.528-0.014-2.646-0.039-2.963-0.004C6.715,1.294,5.104,2.493,4.293,3.052C3.232,3.778,2.836,4.204,2.771,4.263
 c-0.3,0.262-0.048,0.867-0.592,1.344C1.604,6.11,1.245,5.729,0.912,6.021C0.747,6.167,0.285,6.513,0.153,6.628
 C0.02,6.745-0.004,6.942,0.132,7.099c0,0,1.264,1.396,1.37,1.52C1.607,8.741,1.893,8.847,2.069,8.69
 c0.177-0.156,0.632-0.553,0.708-0.623C2.855,8.001,2.727,7.206,3.135,6.89z M8.843,7.407c-0.12-0.139-0.269-0.143-0.397-0.029
 L7.012,8.63c-0.113,0.1-0.129,0.283-0.027,0.4l8.294,9.439c0.194,0.223,0.53,0.246,0.751,0.053L17,17.709
 c0.222-0.195,0.245-0.533,0.052-0.758L8.843,7.407z M19.902,3.39c-0.074-0.494-0.33-0.391-0.463-0.182
 c-0.133,0.211-0.721,1.102-0.963,1.506c-0.24,0.4-0.832,1.191-1.934,0.41c-1.148-0.811-0.749-1.377-0.549-1.758
 c0.201-0.383,0.818-1.457,0.907-1.59c0.089-0.135-0.015-0.527-0.371-0.363c-0.357,0.164-2.523,1.025-2.823,2.26
 c-0.307,1.256,0.257,2.379-0.85,3.494l-1.343,1.4l1.349,1.566l1.654-1.57c0.394-0.396,1.236-0.781,1.998-0.607
 c1.633,0.369,2.524-0.244,3.061-1.258C20.057,5.792,19.977,3.884,19.902,3.39z M2.739,17.053c-0.208,0.209-0.208,0.549,0,0.758
 l0.951,0.93c0.208,0.209,0.538,0.121,0.746-0.088l4.907-4.824L7.84,12.115L2.739,17.053z"/&gt;
&lt;/svg&gt;</pre>
Des formes géométriques simples peuvent être traitées (rect, circle, elipse et polygon), ou plus complexes (balises polyline et path) comme l'exemple ci-dessus.

Le code avec balise "svg" ci-dessus peut-être placé tel quel dans une page web en HTML5, il existe en réalité différentes manières d'afficher et utiliser un "SVG" pour un site web dont notamment la balise "iframe", "object" et "img" et également l'intégration en background CSS.

L’attribut "viewBox" est important et permet l'affichage des formes représentées. Les attributs "width" et "height" définissent quant à eux le cadre fondamental qui va le contenir et l'afficher dans une page web. Lors de l'intégration, il faut donc apporter une attention particulière à ses valeurs. Voici ce que cela donne concrètement avec une <a title="SVG et attributs viewBox, width, height" href="http://codepen.io/dbj/pen/RWmQNJ" target="_blank" rel="noopener">illustration</a>.

Vous pouvez exporter un fichier SVG à partir d'un éditeur graphique tel que Illustrator, Inkscape ou autres, cependant le code généré nécessitera d'être <a title="SVG et optimisation" href="https://github.com/svg/svgo" target="_blank" rel="noopener">optimisé</a>.

"SVG" permet des effets graphiques comme le détourage, masquage (clipPath et mask), ainsi que des filtres, et surtout il est adaptatif ce qui le rend pratique pour le responsive design.

Le "profil" de "SVG" a été brièvement abordé dans cet article, pour aller plus loin dans le domaine, je vous propose un <a title="SVG et intégration web" href="https://la-cascade.io/tag/svg/" target="_blank" rel="noopener">lien</a> très riche avec de nombreuses ressources et une revue assez large de ses capacités et surtout comme apprendre à l'utiliser.<p>Cet article <a href="https://www.dotprogs.com/format-svg-utilisation-web/">SVG et son utilisation pour le web : des ressources variées</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Kirki, la boîte à outils pour le « customizer » de WordPress</title>
		<link>https://www.dotprogs.com/kirki-wp-custom-controls/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Sat, 15 Oct 2016 07:43:49 +0000</pubDate>
				<category><![CDATA[Développement web]]></category>
		<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[custom controls]]></category>
		<category><![CDATA[custom panels]]></category>
		<category><![CDATA[custom sections]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[librairie Kirki]]></category>
		<category><![CDATA[personnalisation WordPress]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress theme customizer]]></category>
		<category><![CDATA[WP customizer]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=510</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/wp-customizer-kirki-tools.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="WP customizer API - Kirki Tools" decoding="async" loading="lazy" /></p>
<h2 class="dp-level2-title">DE NOUVELLES FONCTIONNALITÉS POUR LE CUSTOMIZER DE WORDPRESS</h2>
<h3 class="dp-level3-title">UNE EXPÉRIENCE UTILISATEUR AMÉLIORÉE ET PLUS AGRÉABLE</h3>
<p>Le "customizer" de WordPress est l'interface de personnalisation avancée proposée au sein de l'administration du CMS (système de gestion de contenu).</p>
<p>Avant le customizer, le principe de "pages d'options" peu uniformes en fonction des thèmes était la règle. Le "customizer" propose de ce fait d'unifier cette approche avec une API ("interface de programmation").</p>
<p>Le "customizer" a pour intérêt de permettre de visualiser sa page web en même temps qu'elle est modifiée.</p>
<p>On y accède depuis le menu "Apparence -&gt; Personnaliser", il est possible de piloter la forme et le fond du site web en fonction des champs de formulaires ("custom controls") définis et configurés par le thème activé.</p>
<p>Ses "contrôles personnalisés" sont organisés en "panels" (qui sont les grands ensembles de personnalisation), des "sections" divisent ces "panels" en sous-ensembles pour rendre plus lisibles les actions proposées par ces custom controls".</p>
<p>En fonction du thème choisi, la personnalisation peut être très avancée ou quasi inexistante si les développeurs de thèmes n'ont pas fait le choix du "customizer".</p>
<p>Le principe de base de l'API du customizer est de permettre la création de classes php qui héritent de classes natives aussi bien pour les panels, les sections que pour les "custom controls". Ainsi des contrôles personnalisés totalement nouveaux peuvent être créés sur la base d'un code uniforme proposé par cette API.</p>
<p>Un exemple de "custom control" non natif créé par un développeur est présenté ci-dessous, et propose spécifiquement les typographie de Google les plus populaires pour personnaliser les textes d'un site web :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
 
if ( ! class_exists( 'WP_Customize_Control' ) )
    return NULL;
 
/**
 * A class to create a dropdown for all google fonts
 */
 class Google_Font_Dropdown_Custom_Control extends WP_Customize_Control
 {
    private $fonts = false;
 
    public function __construct($manager, $id, $args = array(), $options = array())
    {
        $this-&gt;fonts = $this-&gt;get_fonts();
        parent::__construct( $manager, $id, $args );
    }
 
    /**
     * Render the content of the category dropdown
     *
     * @return HTML
     */
    public function render_content()
    {
        if(!empty($this-&gt;fonts))
        {
            ?&gt;
                &lt;label&gt;
                    &lt;span class="customize-category-select-control"&gt;&lt;?php echo esc_html( $this-&gt;label ); ?&gt;&lt;/span&gt;
                    &lt;select &lt;?php $this-&gt;link(); ?&gt;&gt;
                        &lt;?php
                            foreach ( $this-&gt;fonts as $k =&gt; $v )
                            {
                                printf('&lt;option value="%s" %s&gt;%s&lt;/option&gt;', $k, selected($this-&gt;value(), $k, false), $v-&gt;family);
                            }
                        ?&gt;
                    &lt;/select&gt;
                &lt;/label&gt;
            &lt;?php
        }
    }
 
    /**
     * Get the google fonts from the API or in the cache
     *
     * @param  integer $amount
     *
     * @return String
     */
    public function get_fonts( $amount = 30 )
    {
        $fontFile = '/cache/google-web-fonts.txt';
 
        //Total time the file will be cached in seconds, set to a week
        $cachetime = 86400 * 7;
 
        if(file_exists($fontFile) &amp;&amp; $cachetime &lt; filemtime($fontFile))
        {
            $content = json_decode(file_get_contents($fontFile));
        } else {
 
            $googleApi = 'https://www.googleapis.com/webfonts/v1/webfonts?sort=popularity&amp;key={API_KEY}';
 
            $fontContent = wp_remote_get( $googleApi, array('sslverify'   =&gt; false) );
 
            $fp = fopen($fontFile, 'w');
            fwrite($fp, $fontContent['body']);
            fclose($fp);
 
            $content = json_decode($fontContent['body']);
        }
 
        if($amount == 'all')
        {
            return $content-&gt;items;
        } else {
            return array_slice($content-&gt;items, 0, $amount);
        }
    }
 }</pre>
<p>Plus d'exemples sur le site des développeurs sont accessibles par ce <!-- <a title="exemples de custom controls développés sur la base de l'API du customizer" href="https://paulund.co.uk/custom-wordpress-controls" target="_blank" rel="noopener">lien</a> --> <a title="exemples de custom controls développés sur la base de l'API du customizer" href="https://docs.themeum.com/kirki/getting-started/introduction/" target="_blank" rel="noopener">lien</a>.</p>
<p>Quelques développeurs web ou sociétés concevant des thèmes WordPress premium enrichissent, petit à petit&nbsp;par leur travail, la connaissance du "customizer" et contribuent à accroître les ressources sur le sujet.</p>
<p>Il existe peu de ressources de manière générale (même si un certain nombre de sites web anglophones s'y mettent)&nbsp;bien que le customizer existe depuis la version WordPress 3.4. A ce jour, on peut considérer qu'il a déjà bien évolué même s'il n'est pas un outil mature.</p>
<p>Il faut remarquer qu'il existe également une API JavaScript en parallèle mise à disposition par le "customizer" avec de <a title="tutoriels sur l'API JavaScript du customizer" href="https://code.tutsplus.com/tutorials/customizer-javascript-apis-getting-started--cms-26838" target="_blank" rel="noopener">bons tutoriels</a> ici ou là, ce qui le rend très efficace.</p>
<p>En addition de cette API JavaScript, les créateurs de sites web ont aussi la possibilité de réaliser des "custom controls"&nbsp;avec des templates <a title="Undescore JS" href="http://underscorejs.org/" target="_blank" rel="noopener">Underscore JS</a>.</p>
<p>On peut aussi noter que la documentation proposée à la communauté par WordPress est assez succincte à ce jour, les développeurs web ont donc la nécessité d'étudier le code en détail !</p>
<p>Quelques types de champs efficaces sont proposés nativement, <a title="API du customizer" href="https://developer.wordpress.org/themes/customize-api/" target="_blank" rel="noopener">la documentation de l'API</a> les présente brièvement, cependant ils limitent les possibilités de personnalisations avancées que l'on peut trouver ailleurs sur de nombreux formulaires servant à de la configuration.</p>
<p>Heureusement l'API du "customizer" est étudiée&nbsp;pour que les codeurs puissent s'exprimer et adapter celui-ci&nbsp;à leurs besoins et objectifs.</p>
<p>C'est à ce niveau qu'intervient <a title="librairie PHP Kirki" href="https://github.com/themeum/kirki" target="_blank" rel="noopener">Kirki</a>&nbsp;en proposant des composants d'interface variés visible sur <a title="composants de Kirki" href="https://github.com/themeum/kirki/tree/develop/docs/files" target="_blank" rel="noopener">cette page</a>, dit "user friendly" (facilitant l'expérience utilisateur), et très pratiques pour contrôler l'apparence et le contenu de son site web. Le "customizer" de WordPress, est de fait, véritablement&nbsp;amélioré et ses capacités sont accrues.</p>
<p>La librairie proposée par Kirki a pour but de rendre plus aisée l'utilisation des "custom controls" tout en respectant au maximum les bases de l'API.</p>
<p>Kirki est disponible sous forme de plugin et peut aussi être intégré directement à un thème pour les besoins des développeurs et intégrateurs web.</p>
<p>Avec Kirki, il est davantage question de l'API PHP du "customizer", adaptée par ce dernier pour accélérer le déploiement de l'interface.<br />
Bien sûr, étant donné l'apparence des contrôles de la librairie, du JavaScript et des CSS3 rendent le rendu très soigné !</p>
<p>Le <a title="plugin WordPress Kirki" href="https://fr.wordpress.org/plugins/kirki/" target="_blank" rel="noopener">plugin</a> WordPress Kirki est disponible sur wordpress.org.</p>
<p>Des&nbsp;ressources plus générales sur le "customizer" sont présentes <a title="ressources sur le customizer" href="https://make.wordpress.org/themes/2015/05/07/customizer-tutorials-and-documentation/" target="_blank" rel="noopener">ici</a>.</p>
<p>Cet article <a href="https://www.dotprogs.com/kirki-wp-custom-controls/">Kirki, la boîte à outils pour le « customizer » de WordPress</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/wp-customizer-kirki-tools.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="WP customizer API - Kirki Tools" decoding="async" loading="lazy" /></p><h2 class="dp-level2-title">DE NOUVELLES FONCTIONNALITÉS POUR LE CUSTOMIZER DE WORDPRESS</h2>
<h3 class="dp-level3-title">UNE EXPÉRIENCE UTILISATEUR AMÉLIORÉE ET PLUS AGRÉABLE</h3>
Le "customizer" de WordPress est l'interface de personnalisation avancée proposée au sein de l'administration du CMS (système de gestion de contenu).

Avant le customizer, le principe de "pages d'options" peu uniformes en fonction des thèmes était la règle. Le "customizer" propose de ce fait d'unifier cette approche avec une API ("interface de programmation").

Le "customizer" a pour intérêt de permettre de visualiser sa page web en même temps qu'elle est modifiée.

On y accède depuis le menu "Apparence -&gt; Personnaliser", il est possible de piloter la forme et le fond du site web en fonction des champs de formulaires ("custom controls") définis et configurés par le thème activé.

Ses "contrôles personnalisés" sont organisés en "panels" (qui sont les grands ensembles de personnalisation), des "sections" divisent ces "panels" en sous-ensembles pour rendre plus lisibles les actions proposées par ces custom controls".

En fonction du thème choisi, la personnalisation peut être très avancée ou quasi inexistante si les développeurs de thèmes n'ont pas fait le choix du "customizer".

Le principe de base de l'API du customizer est de permettre la création de classes php qui héritent de classes natives aussi bien pour les panels, les sections que pour les "custom controls". Ainsi des contrôles personnalisés totalement nouveaux peuvent être créés sur la base d'un code uniforme proposé par cette API.

Un exemple de "custom control" non natif créé par un développeur est présenté ci-dessous, et propose spécifiquement les typographie de Google les plus populaires pour personnaliser les textes d'un site web :
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
 
if ( ! class_exists( 'WP_Customize_Control' ) )
    return NULL;
 
/**
 * A class to create a dropdown for all google fonts
 */
 class Google_Font_Dropdown_Custom_Control extends WP_Customize_Control
 {
    private $fonts = false;
 
    public function __construct($manager, $id, $args = array(), $options = array())
    {
        $this-&gt;fonts = $this-&gt;get_fonts();
        parent::__construct( $manager, $id, $args );
    }
 
    /**
     * Render the content of the category dropdown
     *
     * @return HTML
     */
    public function render_content()
    {
        if(!empty($this-&gt;fonts))
        {
            ?&gt;
                &lt;label&gt;
                    &lt;span class="customize-category-select-control"&gt;&lt;?php echo esc_html( $this-&gt;label ); ?&gt;&lt;/span&gt;
                    &lt;select &lt;?php $this-&gt;link(); ?&gt;&gt;
                        &lt;?php
                            foreach ( $this-&gt;fonts as $k =&gt; $v )
                            {
                                printf('&lt;option value="%s" %s&gt;%s&lt;/option&gt;', $k, selected($this-&gt;value(), $k, false), $v-&gt;family);
                            }
                        ?&gt;
                    &lt;/select&gt;
                &lt;/label&gt;
            &lt;?php
        }
    }
 
    /**
     * Get the google fonts from the API or in the cache
     *
     * @param  integer $amount
     *
     * @return String
     */
    public function get_fonts( $amount = 30 )
    {
        $fontFile = '/cache/google-web-fonts.txt';
 
        //Total time the file will be cached in seconds, set to a week
        $cachetime = 86400 * 7;
 
        if(file_exists($fontFile) &amp;&amp; $cachetime &lt; filemtime($fontFile))
        {
            $content = json_decode(file_get_contents($fontFile));
        } else {
 
            $googleApi = 'https://www.googleapis.com/webfonts/v1/webfonts?sort=popularity&amp;key={API_KEY}';
 
            $fontContent = wp_remote_get( $googleApi, array('sslverify'   =&gt; false) );
 
            $fp = fopen($fontFile, 'w');
            fwrite($fp, $fontContent['body']);
            fclose($fp);
 
            $content = json_decode($fontContent['body']);
        }
 
        if($amount == 'all')
        {
            return $content-&gt;items;
        } else {
            return array_slice($content-&gt;items, 0, $amount);
        }
    }
 }</pre>
Plus d'exemples sur le site des développeurs sont accessibles par ce <!-- <a title="exemples de custom controls développés sur la base de l'API du customizer" href="https://paulund.co.uk/custom-wordpress-controls" target="_blank" rel="noopener">lien</a> --> <a title="exemples de custom controls développés sur la base de l'API du customizer" href="https://docs.themeum.com/kirki/getting-started/introduction/" target="_blank" rel="noopener">lien</a>.

Quelques développeurs web ou sociétés concevant des thèmes WordPress premium enrichissent, petit à petit&nbsp;par leur travail, la connaissance du "customizer" et contribuent à accroître les ressources sur le sujet.

Il existe peu de ressources de manière générale (même si un certain nombre de sites web anglophones s'y mettent)&nbsp;bien que le customizer existe depuis la version WordPress 3.4. A ce jour, on peut considérer qu'il a déjà bien évolué même s'il n'est pas un outil mature.

Il faut remarquer qu'il existe également une API JavaScript en parallèle mise à disposition par le "customizer" avec de <a title="tutoriels sur l'API JavaScript du customizer" href="https://code.tutsplus.com/tutorials/customizer-javascript-apis-getting-started--cms-26838" target="_blank" rel="noopener">bons tutoriels</a> ici ou là, ce qui le rend très efficace.

En addition de cette API JavaScript, les créateurs de sites web ont aussi la possibilité de réaliser des "custom controls"&nbsp;avec des templates <a title="Undescore JS" href="http://underscorejs.org/" target="_blank" rel="noopener">Underscore JS</a>.

On peut aussi noter que la documentation proposée à la communauté par WordPress est assez succincte à ce jour, les développeurs web ont donc la nécessité d'étudier le code en détail !

Quelques types de champs efficaces sont proposés nativement, <a title="API du customizer" href="https://developer.wordpress.org/themes/customize-api/" target="_blank" rel="noopener">la documentation de l'API</a> les présente brièvement, cependant ils limitent les possibilités de personnalisations avancées que l'on peut trouver ailleurs sur de nombreux formulaires servant à de la configuration.

Heureusement l'API du "customizer" est étudiée&nbsp;pour que les codeurs puissent s'exprimer et adapter celui-ci&nbsp;à leurs besoins et objectifs.

C'est à ce niveau qu'intervient <a title="librairie PHP Kirki" href="https://github.com/themeum/kirki" target="_blank" rel="noopener">Kirki</a>&nbsp;en proposant des composants d'interface variés visible sur <a title="composants de Kirki" href="https://github.com/themeum/kirki/tree/develop/docs/files" target="_blank" rel="noopener">cette page</a>, dit "user friendly" (facilitant l'expérience utilisateur), et très pratiques pour contrôler l'apparence et le contenu de son site web. Le "customizer" de WordPress, est de fait, véritablement&nbsp;amélioré et ses capacités sont accrues.

La librairie proposée par Kirki a pour but de rendre plus aisée l'utilisation des "custom controls" tout en respectant au maximum les bases de l'API.

Kirki est disponible sous forme de plugin et peut aussi être intégré directement à un thème pour les besoins des développeurs et intégrateurs web.

Avec Kirki, il est davantage question de l'API PHP du "customizer", adaptée par ce dernier pour accélérer le déploiement de l'interface.
Bien sûr, étant donné l'apparence des contrôles de la librairie, du JavaScript et des CSS3 rendent le rendu très soigné !

Le <a title="plugin WordPress Kirki" href="https://fr.wordpress.org/plugins/kirki/" target="_blank" rel="noopener">plugin</a> WordPress Kirki est disponible sur wordpress.org.

Des&nbsp;ressources plus générales sur le "customizer" sont présentes <a title="ressources sur le customizer" href="https://make.wordpress.org/themes/2015/05/07/customizer-tutorials-and-documentation/" target="_blank" rel="noopener">ici</a>.<p>Cet article <a href="https://www.dotprogs.com/kirki-wp-custom-controls/">Kirki, la boîte à outils pour le « customizer » de WordPress</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Fonction en JavaScript, « IIFE », et usage dit professionnel &#8230;</title>
		<link>https://www.dotprogs.com/function-iife-usages-javascript/</link>
		
		<dc:creator><![CDATA[DOTPROGS]]></dc:creator>
		<pubDate>Wed, 28 Sep 2016 12:59:51 +0000</pubDate>
				<category><![CDATA[Développement web]]></category>
		<category><![CDATA[Dotweb]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[appel]]></category>
		<category><![CDATA[bonnes pratiques]]></category>
		<category><![CDATA[déclaration]]></category>
		<category><![CDATA[fonction]]></category>
		<category><![CDATA[IIFE]]></category>
		<category><![CDATA[invocation]]></category>
		<category><![CDATA[objet]]></category>
		<category><![CDATA[requireJS]]></category>
		<guid isPermaLink="false">https://www.dotprogs.com/?p=488</guid>

					<description><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/javascript-function-invocation.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Invocation de fonction JavaScript, IIFE et bonnes pratiques" decoding="async" loading="lazy" /></p>
<h2 class="dp-level2-title">Fonctions et usages JavaScript</h2>
<h3 class="dp-level3-title">La notion de fonction "auto-exécutée"</h3>
<p>Cet article peut sembler simpliste, mais il apparaît utile de connaître les principales possibilités de déclarer une fonction en JavaScript.<br />
Personnellement, il m'arrive de produire du Javascript ponctuellement pour des besoins simples, je pense que c'est toujours constructif de s'interroger sur les bases de ce langage, l'objectif est avant tout de mieux comprendre ce que l'on écrit en JavaScript.</p>
<p>La tentation est souvent grande de bidouiller du code qui fonctionne malgré tout, mais c'est parfois bon aussi de s'arrêter pour faire le point sur de "modestes" notions.</p>
<p>Loin de moi l'idée, de faire de ce post, un exposé hasardeux sur les fonctions en JavaScript. Il faut bien plus que quelques lignes pour faire le tour du sujet, par contre, je suis parti de la notion de fonction en JavaScript pour mieux comprendre de quelle manière les usages actuels évoluent.</p>
<p>Petit tour d'horizon concernant les fonctions en JS pour arriver au sujet :<br />
- La fonction déclarative (déclaration classique voire basique)</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">function hello() {
    console.log(hello.name); //affiche hello  
}
hello();</pre>
<p>- L'expression de fonction anonyme</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">var myFunction = function() {
    console.log('myFunction called');
};
myFunction(); //affiche myFunction called</pre>
<p>- L'expression de fonction nominative</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">var myFunction = function hello() {
    console.log(hello.name);
};
myFunction(); //affiche hello
console.log(myFunction.name); //affiche hello</pre>
<p>- La méthode d'objet</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">//déclaration d'une fonction qui est un objet
var hello = function hello() {
};
//déclaration d'une méthode de l'objet
hello.myMethod = function() {
    console.log(this.name);
};
//appel
hello.myMethod(); //affiche hello</pre>
<p>- L'expression de fonction immédiatement invoquée (IIFE pour Immediatly Invoked Function Expression)</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">var myFunction = function hello() {
    console.log(hello.name); //affiche hello
}();
console.log(myFunction); //affiche undefined</pre>
<p>- L'expression de fonction immédiatement invoquée évaluée sans variable</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">// écriture 1
(function hello() {
    console.log(hello.name); //affiche hello
})();</pre>
<p>ou</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">// écriture 2
(function hello() {
     console.log(hello.name); //affiche hello
}());</pre>
<p>- Un exemple avec des arguments :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">// écriture 1
(function hello(argument1, argument2) {
    console.log(hello.name); //affiche hello
    console.log(argument2); //affiche 1
})('test', 1);</pre>
<p>- L'instanciation de fonction avec constructeur "Function"</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">var hello = new Function('a', 'b', 'return a+b;');
console.log(hello(2, 3)); //affiche 5</pre>
<p>- Avec l'écriture "Arrow function" ES6 (plusieurs variantes de déclaration existent selon le nombre d'arguments !) :</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">((argument1, argument2) =&gt; {
    //votre script
})('test', 1);</pre>
<p>Une pratique désormais répandue est d'utiliser l'<a title="IIFE et scope" href="http://www.nicoespeon.com/fr/2013/05/bien-isoler-ses-variables-en-javascript/" target="_blank" rel="noopener">IIFE</a> pour structurer et surtout encapsuler voire "protéger ses scripts" au sein de la fonction avec la notion de "scope" (contexte) local à lui opposer au scope global (l'objet "window" du navigateur) : voici un très bon <a title="Concept d'IIFE" href="https://makina-corpus.com/blog/metier/2015/bien-demarrer-avec-javascript" target="_blank" rel="noopener">article</a> qui présente l'IIFE comme une bonne approche.</p>
<p>La notion de programmation en objet JavaScript en utilisant une IIFE est abordée avec humour <a title="Objet JavaScript et IIFE" href="https://www.miximum.fr/blog/pour-enfin-comprendre-javascript/" target="_blank" rel="noopener">ici</a>.</p>
<p>Pour aller plus loin et comprendre comment travaillent finalement les "spécialistes" du JavaScript, il est désormais question de <a title="Concept modulaire en JavaScript" href="http://blog.dynacase.org/bonnes-pratiques-dev-specifique-web-app/comment-decouper-votre-code-en-module-librairie-javascript-independante" target="_blank" rel="noopener">concept modulaire</a>, et là ça devient assez technique avec les approches du moment !</p>
<p>L'objectif de cet article est finalement d'y voir plus clair et comprendre quels sont les bons usages du JavaScript à l'heure actuelle et pointer le fait que l'IIFE (fonction "auto-exécutée" pour faire court) répond à des besoins bien spécifiques pour structurer son code.</p>
<p>Enfin un <a title="Le milieu du Javascript évolue sans cesse." href="http://news.humancoders.com/t/javascript/items/14473-ce-que-j-ai-ressenti-en-apprenant-javascript-en-20/redirect" target="_blank" rel="noopener">témoignage</a> assez sympa (en anglais) qui montre bien que JavaScript est vraiment une jungle et qu'il faut en faire une compétence particulière pour rester dans les "rails" comme beaucoup d'autres domaines ...</p>
<p>Cet article <a href="https://www.dotprogs.com/function-iife-usages-javascript/">Fonction en JavaScript, « IIFE », et usage dit professionnel &#8230;</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1024" height="576" src="https://www.dotprogs.com/wp-content/uploaded-files/javascript-function-invocation.png" class="attachment-md_post_thumb_large size-md_post_thumb_large wp-post-image" alt="Invocation de fonction JavaScript, IIFE et bonnes pratiques" decoding="async" loading="lazy" /></p><h2 class="dp-level2-title">Fonctions et usages JavaScript</h2>
<h3 class="dp-level3-title">La notion de fonction "auto-exécutée"</h3>
Cet article peut sembler simpliste, mais il apparaît utile de connaître les principales possibilités de déclarer une fonction en JavaScript.
Personnellement, il m'arrive de produire du Javascript ponctuellement pour des besoins simples, je pense que c'est toujours constructif de s'interroger sur les bases de ce langage, l'objectif est avant tout de mieux comprendre ce que l'on écrit en JavaScript.

La tentation est souvent grande de bidouiller du code qui fonctionne malgré tout, mais c'est parfois bon aussi de s'arrêter pour faire le point sur de "modestes" notions.

Loin de moi l'idée, de faire de ce post, un exposé hasardeux sur les fonctions en JavaScript. Il faut bien plus que quelques lignes pour faire le tour du sujet, par contre, je suis parti de la notion de fonction en JavaScript pour mieux comprendre de quelle manière les usages actuels évoluent.

Petit tour d'horizon concernant les fonctions en JS pour arriver au sujet :
- La fonction déclarative (déclaration classique voire basique)
<pre class="EnlighterJSRAW" data-enlighter-language="js">function hello() {
    console.log(hello.name); //affiche hello  
}
hello();</pre>
- L'expression de fonction anonyme
<pre class="EnlighterJSRAW" data-enlighter-language="js">var myFunction = function() {
    console.log('myFunction called');
};
myFunction(); //affiche myFunction called</pre>
- L'expression de fonction nominative
<pre class="EnlighterJSRAW" data-enlighter-language="js">var myFunction = function hello() {
    console.log(hello.name);
};
myFunction(); //affiche hello
console.log(myFunction.name); //affiche hello</pre>
- La méthode d'objet
<pre class="EnlighterJSRAW" data-enlighter-language="js">//déclaration d'une fonction qui est un objet
var hello = function hello() {
};
//déclaration d'une méthode de l'objet
hello.myMethod = function() {
    console.log(this.name);
};
//appel
hello.myMethod(); //affiche hello</pre>
- L'expression de fonction immédiatement invoquée (IIFE pour Immediatly Invoked Function Expression)
<pre class="EnlighterJSRAW" data-enlighter-language="js">var myFunction = function hello() {
    console.log(hello.name); //affiche hello
}();
console.log(myFunction); //affiche undefined</pre>
- L'expression de fonction immédiatement invoquée évaluée sans variable
<pre class="EnlighterJSRAW" data-enlighter-language="js">// écriture 1
(function hello() {
    console.log(hello.name); //affiche hello
})();</pre>
ou
<pre class="EnlighterJSRAW" data-enlighter-language="js">// écriture 2
(function hello() {
     console.log(hello.name); //affiche hello
}());</pre>
- Un exemple avec des arguments :
<pre class="EnlighterJSRAW" data-enlighter-language="js">// écriture 1
(function hello(argument1, argument2) {
    console.log(hello.name); //affiche hello
    console.log(argument2); //affiche 1
})('test', 1);</pre>
- L'instanciation de fonction avec constructeur "Function"
<pre class="EnlighterJSRAW" data-enlighter-language="js">var hello = new Function('a', 'b', 'return a+b;');
console.log(hello(2, 3)); //affiche 5</pre>
- Avec l'écriture "Arrow function" ES6 (plusieurs variantes de déclaration existent selon le nombre d'arguments !) :
<pre class="EnlighterJSRAW" data-enlighter-language="js">((argument1, argument2) =&gt; {
    //votre script
})('test', 1);</pre>
Une pratique désormais répandue est d'utiliser l'<a title="IIFE et scope" href="http://www.nicoespeon.com/fr/2013/05/bien-isoler-ses-variables-en-javascript/" target="_blank" rel="noopener">IIFE</a> pour structurer et surtout encapsuler voire "protéger ses scripts" au sein de la fonction avec la notion de "scope" (contexte) local à lui opposer au scope global (l'objet "window" du navigateur) : voici un très bon <a title="Concept d'IIFE" href="https://makina-corpus.com/blog/metier/2015/bien-demarrer-avec-javascript" target="_blank" rel="noopener">article</a> qui présente l'IIFE comme une bonne approche.

La notion de programmation en objet JavaScript en utilisant une IIFE est abordée avec humour <a title="Objet JavaScript et IIFE" href="https://www.miximum.fr/blog/pour-enfin-comprendre-javascript/" target="_blank" rel="noopener">ici</a>.

Pour aller plus loin et comprendre comment travaillent finalement les "spécialistes" du JavaScript, il est désormais question de <a title="Concept modulaire en JavaScript" href="http://blog.dynacase.org/bonnes-pratiques-dev-specifique-web-app/comment-decouper-votre-code-en-module-librairie-javascript-independante" target="_blank" rel="noopener">concept modulaire</a>, et là ça devient assez technique avec les approches du moment !

L'objectif de cet article est finalement d'y voir plus clair et comprendre quels sont les bons usages du JavaScript à l'heure actuelle et pointer le fait que l'IIFE (fonction "auto-exécutée" pour faire court) répond à des besoins bien spécifiques pour structurer son code.

Enfin un <a title="Le milieu du Javascript évolue sans cesse." href="http://news.humancoders.com/t/javascript/items/14473-ce-que-j-ai-ressenti-en-apprenant-javascript-en-20/redirect" target="_blank" rel="noopener">témoignage</a> assez sympa (en anglais) qui montre bien que JavaScript est vraiment une jungle et qu'il faut en faire une compétence particulière pour rester dans les "rails" comme beaucoup d'autres domaines ...<p>Cet article <a href="https://www.dotprogs.com/function-iife-usages-javascript/">Fonction en JavaScript, « IIFE », et usage dit professionnel &#8230;</a> est apparu en premier sur <a href="https://www.dotprogs.com">DOTPROGS</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
