<?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>JavaScript Archives | DOTPROGS</title>
	<atom:link href="https://www.dotprogs.com/etiquette/javascript/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>JavaScript Archives | DOTPROGS</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<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" fetchpriority="high" /></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" /></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>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" /></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>
