Ajax

Le module AMD elgg/Ajax (introduit dans Elgg 2.1) fournit un ensemble de méthodes pour communiquer avec le serveur d’une manière concise et uniforme, ce qui permet aux plugins de collaborer sur les données de requête, la réponse du serveur et les données renvoyées côté client.

Le code client et serveur écrit pour l’API d’origine ne doit pas avoir besoin de modification.

Aperçu

Toutes les méthodes ajax effectuent ce qui suit :

  1. Côté client, l’option data (si elle est donnée en tant qu’objet) est filtrée par le hook ajax_request_data.

  2. La requête est faite au serveur, que ce soit en affichant une vue ou un formulaire, en appelant une action, ou en chargeant un chemin d’accès.

  3. La méthode renvoie un objet jqXHR, qui peut être utilisé comme promesse (Promise).

  4. Le contenu généré par le serveur est transformé en objet de réponse (Elgg\Services\AjaxResponse) contenant une chaîne (ou une valeur parsée en JSON).

  5. L’objet de réponse est filtré par le hook ajax_response.

  6. L’objet de réponse est utilisé pour créer la réponse HTTP.

  7. Côté client, les données de réponse sont filtrées par le hook ajax_response_data.

  8. La promesse (promise) jqXHR est résolue et toutes les fonctions de rappel success sont appelées.

Plus de notes :

  • Tous les hooks ont un type en fonction de la méthode et du premier argument. Voyez ci-dessous.

  • Par défaut, le module elgg/spinner est automatiquement utilisé lors des requêtes.

  • Les messages destinés à l’utilisateur générés par “system_message() et register_error() sont collectés et affichés sur le client.

  • Elgg intègre un gestionnaire d’erreurs par défaut qui affiche un message générique en cas d’échec de la sortie.

  • Les exceptions PHP ou les codes d’erreur HTTP d’accès refusé, ce qui entraîne l’utilisation du gestionnaire d’erreurs côté client.

  • La méthode HTTP par défaut est POST pour les actions, sinon GET. Vous pouvez changer cela via options.method.

  • Si une valeur options.data non vide est donnée, la méthode par défaut est toujours POST.

  • Pour la mise en cache côté client, définissez la méthode options.method sur GET et options.data.elgg_response_ttl sur l’âge maximal que vous souhaitez en secondes.

  • Pour enregistrer des messages système pour le chargement de la page suivante, définissez options.data.elgg_fetch_messages = 0. Vous pourriez vouloir faire cela si vous avez l’intention de rediriger l’utilisateur en fonction de la réponse.

  • Pour empêcher l’API côté client d’exiger des modules AMD requis côté serveur avec elgg_require_js(), définissez options.data.elgg_fetch_deps = 0.

  • Toutes les méthodes acceptent une chaîne de requête dans le premier argument. Cette chaîne est transmise à l’URL de récupération, mais n’apparaît pas dans les types de hooks.

Effectuer des actions

Considérez cette action :

// in myplugin/actions/do_math.php

elgg_ajax_gatekeeper();

$arg1 = (int)get_input('arg1');
$arg2 = (int)get_input('arg2');

// will be rendered client-side
system_message('We did it!');

echo json_encode([
    'sum' => $arg1 + $arg2,
    'product' => $arg1 * $arg2,
]);

Pour l’exécuter, utilisez ajax.action('<action_name>', options) :

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.action('do_math', {
    data: {
        arg1: 1,
        arg2: 2
    },
}).done(function (output, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {
        return;
    }

    alert(output.sum);
    alert(output.product);
});

Notes pour les actions :

  • Tous les hooks ont le type action:<action_name>. Donc, dans ce cas, trois hooks seront déclenchés :
    • côté client "ajax_request_data", "action:do_math" pour filtrer les données de la requête (avant qu’elle soit envoyée)

    • côté serveur "ajax_response", "action:do_math" pour filtrer la réponse (après que l’action a été exécutée)

    • côté client "ajax_response_data", "action:do_math" pour filtrer les données de la réponse (avant que le code appelant ne les reçoive)

  • Les jetons CSRF sont ajoutés aux données de la requête.

  • La méthode par défaut est POST.

  • Une URL d’action absolue peut être donnée à la place du nom d’action.

  • L’utilisation de forward() dans une action envoie simplement la réponse. L’URL indiquée n’est pas renvoyée au client.

Note

Lorsque vous définissez data, utilisez ajax.objectify($form) au lieu de $form.serialize(). Cela permet au hook de plugin ajax_request_data d’être déclenché, et à d’autres plugins de modifier / réagir à la requête.

Récupérer des données

Considérez ce script PHP qui s’exécute à http://example.org/myplugin_time.

// in myplugin/start.php
elgg_register_page_handler('myplugin_time', 'myplugin_get_time');

function myplugin_get_time() {
    elgg_ajax_gatekeeper();

    echo json_encode([
        'rfc2822' => date(DATE_RFC2822),
        'day' => date('l'),
    ]);

    return true;
}

Pour récupérer sa sortie, utilisez ajax.path('<url_path>', options).

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.path('myplugin_time').done(function (output, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {
        return;
    }

    alert(output.rfc2822);
    alert(output.day);
});

Notes pour les chemins :

  • Les 3 hooks (voir Actions ci-dessus) auront le type path:<url_path>. Dans ce cas, « chemin:myplugin_time ».

  • Si le gestionnaire de page renvoie une page Web normale, output sera une chaîne contenant le HTML de la page.

  • Une URL absolue peut être donnée à la place du nom du chemin.

Récupérer des vues

Considérez cette vue :

// in myplugin/views/default/myplugin/get_link.php

if (empty($vars['entity']) || !$vars['entity'] instanceof ElggObject) {
    return;
}

$object = $vars['entity'];
/* @var ElggObject $object */

echo elgg_view('output/url', [
    'text' => $object->getDisplayName(),
    'href' => $object->getUrl(),
    'is_trusted' => true,
]);

Comme il s’agit d’un fichier PHP, nous devons d’abord l’enregistrer pour Ajax :

// in myplugin_init()
elgg_register_ajax_view('myplugin/get_link');

Pour récupérer la vue, utilisez ajax.view('<view_name>', options) :

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.view('myplugin/get_link', {
    data: {
        guid: 123 // querystring
    },
}).done(function (output, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {
        return;
    }

    $('.myplugin-link').html(output);
});

Notes pour les vues :

  • Les 3 hooks (voir Actions ci-dessus) auront le type view:<view_name>. Dans ce cas, « view:myplugin/get_link ».

  • output sera une chaîne avec la vue générée.

  • Les données de la requête sont injectées dans $vars dans la vue.

  • Si les données de la requête contiennent guid, le système définit $vars['entity'] à l’entité correspondante ou à false si elle ne peut pas être chargée.

Avertissement

Dans les vues et les formulaires ajax, notez que $vars peut être peuplé par les saisies du client. Les données sont filtrées comme get_input(), mais peuvent ne pas avoir le type que vous attendez ou peuvent avoir des clefs inattendues.

Récupérer des formulaires

Considérez que nous avons une vue de formulaire. Nous l’enregistrons pour Ajax :

// in myplugin_init()
elgg_register_ajax_view('forms/myplugin/add');

Pour récupérer ceci utilisez ajax.form('<action_name>', options).

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.form('myplugin/add').done(function (output, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {
        return;
    }

    $('.myplugin-form-container').html(output);
});

Notes pour les formulaires :

  • Les 3 hooks (voir Actions ci-dessus) auront le type form:<action_name>. Dans ce cas, « form:myplugin/add ».

  • output sera une chaîne avec la vue générée.

  • Les données de la requête sont injectées dans $vars dans la vue de votre formulaire.

  • Si les données de la requête contiennent guid, le système définit $vars['entity'] à l’entité correspondante ou à false si elle ne peut pas être chargée.

Note

Seules les données de la requête sont transmises à la vue du formulaire demandé (c’est-à-dire en tant que troisième paramètre accepté par elgg_view_form()). Si vous devez passer des attributs ou des paramètres de l’élément de formulaire rendu par la vue input/form (c’est-à-dire normalement passé comme deuxième paramètre à elgg_view_form()), utilisez le hook serveur view_vars, input/form.

Avertissement

Dans les vues et les formulaires ajax, notez que $vars peut être peuplé par les saisies du client. Les données sont filtrées comme get_input(), mais peuvent ne pas avoir le type que vous attendez ou peuvent avoir des clefs inattendues.

Réagir (piggybacking) lors d’une requête Ajax

Le hook ajax_request_data côté client peut être utilisé pour ajouter ou filtrer les données envoyées par une demande elgg/Ajax.

Disons que lorsque la vue foo est récupérée, nous voulons également envoyer au serveur quelques données :

// in your boot module
var Ajax = require('elgg/Ajax');
var elgg = require('elgg');

var ajax = new Ajax();

elgg.register_hook_handler(Ajax.REQUEST_DATA_HOOK, 'view:foo', function (name, type, params, data) {
    // send some data back
    data.bar = 1;
    return data;
});

Ces données peuvent être lues côté serveur via get_input('bar');.

Note

Si les données ont été données sous forme de chaîne (par ex. $form.serialize()), les hooks de requête ne sont pas déclenchés.

Réagir (piggybacking) à une réponse Ajax

Le hook ajax_response côté serveur peut être utilisé pour ajouter ou filtrer des données de réponse (ou des métadonnées).

Disons que lorsque la vue foo est récupérée, nous voulons également envoyer au client quelques données supplémentaires :

use Elgg\Services\AjaxResponse;

function myplugin_append_ajax($hook, $type, AjaxResponse $response, $params) {

    // alter the value being returned
    $response->getData()->value .= " hello";

    // send some metadata back. Only client-side "ajax_response" hooks can see this!
    $response->getData()->myplugin_alert = 'Listen to me!';

    return $response;
}

// in myplugin_init()
elgg_register_plugin_hook_handler(AjaxResponse::RESPONSE_HOOK, 'view:foo', 'myplugin_append_ajax');

Pour capturer les métadonnées envoyées au client, nous utilisons le hook ajax_response côté client :

// in your boot module
var Ajax = require('elgg/Ajax');
var elgg = require('elgg');

elgg.register_hook_handler(Ajax.RESPONSE_DATA_HOOK, 'view:foo', function (name, type, params, data) {

    // the return value is data.value

    // the rest is metadata

    alert(data.myplugin_alert);

    return data;
});

Note

Seule la valeur data.value est renvoyée à la fonction success, ou disponible via l’interface Deferred.

Note

Elgg utilise ces mêmes hooks pour diffuser des messages système via les réponses elgg/Ajax .

Gérer les erreurs

Les réponses correspondent à l’une de ces trois catégories :

  1. Succès HTTP (200) avec un statut 0. Aucun appel register_error() n’a été fait sur le serveur.

  2. Succès HTTP (200) avec le statut -1. register_error() a été appelé.

  3. Erreur HTTP (4xx/5xx). Par ex. appeler une action avec des jetons expirés, ou une exception sur le serveur. Dans ce cas les fonctions de callbacks done et success ne sont pas appelées.

Vous devriez ne vous inquiéter que du 2e cas. Ceci peut être fait en regardant à jqXHR.AjaxData.status:

ajax.action('entity/delete?guid=123').done(function (value, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {
        // a server error was already displayed
        return;
    }

    // remove element from the page
});

Requérir des modules AMD

Chaque réponse d’un service Ajax contiendra une liste de modules AMD requis côté serveur avec elgg_require_js(). Lorsque les données de réponse sont déballées, ces modules seront chargés de façon asynchrone - les plugins ne doivent pas s’attendre à ce que ces modules soient chargés dans leurs gestionnaires $.done() et $.then() et doivent utiliser require() pour tous les modules dont ils dépendent. En outre, les modules AMD ne doivent pas s’attendre à ce que le DOM ait été modifié par une demande Ajax lorsqu’ils sont chargés - les événements DOM doivent être délégués et les manipulations sur les éléments DOM doivent être retardées jusqu’à ce que toutes les requêtes Ajax aient été résolues.

APIs elgg.ajax historiques

Elgg 1.8 a introduit elgg.action, elgg.get, elgg.getJSON, et d’autres méthodes qui se comportent de manière moins uniforme côté client et côté serveur.

elgg.action d’origine

Différences :

  • vous devez récupérer manuellement output à partir du wrapper renvoyé

  • le gestionnaire de success sera exécuté même si l’action est bloquée

  • le gestionnaire success recevra un objet wrapper. Vous devez rechercher wrapper.output

  • pas de hook ajax

elgg.action('do_math', {
    data: {
        arg1: 1,
        arg2: 2
    },
    success: function (wrapper) {
        if (wrapper.output) {
            alert(wrapper.output.sum);
            alert(wrapper.output.product);
        } else {
            // the system prevented the action from running, but we really don't
            // know why
            elgg.ajax.handleAjaxError();
        }
    }
});

notes sur elgg.action

  • Il est préférable de renvoyer une chaîne non vide, car cela facilite la validation dans la fonction success. Si l’action n’a pas été autorisée à s’exécuter pour une raison quelconque, wrapper.output sera une chaîne vide.

  • Vous pouvez utiliser le module elgg/spinner.

  • Elgg n’utilise pas wrapper.status pour quoi que ce soit, mais un appel à register_error() définit sa valeur sur -1.

  • Si l’action renvoie une chaîne non-JSON, wrapper.output va emballer cette chaîne.

  • elgg.action est basé sur jQuery.ajax et renvoie un objet jqXHR (comme Promise), si vous vouliez l’utiliser.

  • Une fois l’action PHP terminée, d’autres plugins peuvent modifier l’emballage via le hook plugin 'output', 'ajax', qui filtre l’emballage comme un tableau (pas une chaîne JSON).

  • Un appel forward() force le traitement de l’action et renvoie la sortie immédiatement, avec la valeur wrapper.forward_url définie sur l’emplacement normalisé donné.

  • Pour vous assurer que les actions Ajax ne peuvent être exécutées que via XHR, utilisez elgg_ajax_gatekeeper().

emballage de réponse JSON de elgg.action

{
    current_url: {String} "http://example.org/action/example/math", // not very useful
    forward_url: {String} "http://example.org/foo", ...if forward('foo') was called
    output: {String|Object} from echo in action
    status: {Number} 0 = success. -1 = an error was registered.
    system_messages: {Object}
}

Avertissement

Il est probablement préférable de s’appuyer uniquement sur la clef output, et de la valider au cas où l’action PHP ne pourrait pas s’exécuter pour une raison quelconque, par exemple parce que l’utilisateur a été déconnecté ou parce qu’une attaque CSRF n’a pas fourni de jetons.

Avertissement

Si forward() est utilisé dans une réponse à une demande ajax héritée (par exemple elgg.ajax), Elgg répondra toujours avec ce wrapper, même si ce n’est pas dans une action.

Récupération d’une vue héritée

Un plugin peut utiliser un script d’affichage pour gérer les demandes XHR GET. Voici un exemple simple d’une vue qui renvoie un lien vers un objet donné par son GUID :

// in myplugin_init()
elgg_register_ajax_view('myplugin/get_link');
// in myplugin/views/default/myplugin/get_link.php

if (empty($vars['entity']) || !$vars['entity'] instanceof ElggObject) {
    return;
}

$object = $vars['entity'];
/* @var ElggObject $object */

echo elgg_view('output/url', [
    'text' => $object->getDisplayName(),
    'href' => $object->getUrl(),
    'is_trusted' => true,
]);
elgg.get('ajax/view/myplugin/get_link', {
    data: {
        guid: 123 // querystring
    },
    success: function (output) {
        $('.myplugin-link').html(output);
    }
});

Le système de vues Ajax fonctionne de manière significativement différente du système d’action.

  • Il n’y a pas de contrôle d’accès sur la base du statut de session.

  • Les requêtes non XHR sont automatiquement rejetées.

  • Les variables GET sont injectées dans $vars dans la vue.

  • Si la requête contient $_GET['guid'], le système définit $vars['entity'] à l’entité correspondante ou false si celle-ci ne peut pas être chargée.

  • Il n’y a pas aucun emaballage (wrapper) placé autour de la sortie de la vue.

  • Les messages/erreurs système ne doivent pas être utilisés, car ils ne s’affichent pas tant que l’utilisateur n’a pas chargé une autre page.

  • Selon le suffixe de la vue (.js, .html, .css, etc.), l’entête Content-Type correspondant est ajouté.

Avertissement

Dans les vues et les formulaires ajax, notez que $vars peut être peuplé par les saisies du client. Les données sont filtrées comme

get_input(), mais peut ne pas être le type que vous attendez ou peut avoir des clefs inattendues.

Renvoyer du JSON depuis une vue

Si les sorties des vues sont encodées en JSON, vous devez utiliser elgg.getJSON pour les récupérer (ou utiliser une autre méthode pour définir l’option ajax dataType de jQuery sur json). Votre fonction success recevra l’objet décodé.

Voici un exemple de récupération d’une vue qui renvoie un tableau d’heures encodé en JSON :

elgg.getJSON('ajax/view/myplugin/get_times', {
    success: function (data) {
        alert('The time is ' + data.friendly_time);
    }
});

Récupération de formulaire d’origine

Si vous enregistrez une vue de formulaire (nom commençant par forms/), vous pouvez la récupérer pré-générée avec elgg_view_form(). Il suffit d’utiliser ajax/form/<action> (au lieu de ajax/view/<view_name>) :

// in myplugin_init()
elgg_register_ajax_view('forms/myplugin/add');
elgg.get('ajax/form/myplugin/add', {
    success: function (output) {
        $('.myplugin-form-container').html(output);
    }
});

Seules les données de la requête sont transmises à la vue du formulaire demandé (c’est-à-dire en tant que troisième paramètre accepté par elgg_view_form()). Si vous devez passer des attributs ou des paramètres de l’élément de formulaire rendu par la vue input/form (c’est-à-dire normalement passé comme deuxième paramètre à elgg_view_form()), utilisez le hook serveur view_vars, input/form.

Avertissement

Dans les vues et les formulaires ajax, notez que $vars peut être peuplé par les saisies du client. Les données sont filtrées comme

get_input(), mais peut ne pas être le type que vous attendez ou peut avoir des clefs inattendues.

Fonctions d’aide d’origine

Ces fonctions étendent les fonctionnalités Ajax natives de jQuery.

elgg.get() est un wrapper pour $.ajax() de jQuery, mais force GET et normalise l’URL.

// normalizes the url to the current <site_url>/activity
elgg.get('/activity', {
    success: function(resultText, success, xhr) {
        console.log(resultText);
    }
});

elgg.post() est un wrapper pour $.ajax() de jQuery, mais force POST et normalise l’URL.