<?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/rubrique/dotweb/developpement-web/javascript/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>DOTPROGS - Conception de sites web</description>
	<lastBuildDate>Wed, 17 Nov 2021 16:00:08 +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>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" /></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>
