ajax.rst 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. Ajax
  2. ####
  3. Actions
  4. =======
  5. From JavaScript we can execute actions via XHR POST operations. Here's an example action and script for some basic math:
  6. .. code:: php
  7. // in myplugin/actions/do_math.php
  8. if (!elgg_is_xhr()) {
  9. register_error('Sorry, Ajax only!');
  10. forward();
  11. }
  12. $arg1 = (int)get_input('arg1');
  13. $arg2 = (int)get_input('arg2');
  14. system_message('We did it!');
  15. echo json_encode([
  16. 'sum' => $arg1 + $arg2,
  17. 'product' => $arg1 * $arg2,
  18. ]);
  19. .. code:: js
  20. elgg.action('do_math', {
  21. data: {
  22. arg1: 1,
  23. arg2: 2
  24. },
  25. success: function (wrapper) {
  26. if (wrapper.output) {
  27. alert(wrapper.output.sum);
  28. alert(wrapper.output.product);
  29. } else {
  30. // the system prevented the action from running
  31. }
  32. }
  33. });
  34. Basically what happens here:
  35. #. CSRF tokens are added to the data.
  36. #. The data is posted via XHR to http://localhost/elgg/action/example/add.
  37. #. The action makes sure this is an XHR request, and returns a JSON string.
  38. #. Once the action completes, Elgg builds a JSON response wrapper containing the echoed output.
  39. #. Client-side Elgg extracts and displays the system message "We did it!" from the wrapper.
  40. #. The ``success`` function receives the full wrapper object and validates the ``output`` key.
  41. #. The browser alerts "3" then "2".
  42. elgg.action notes
  43. -----------------
  44. * It's best to echo a non-empty string, as this is easy to validate in the ``success`` function. If the action
  45. was not allowed to run for some reason, ``wrapper.output`` will be an empty string.
  46. * You may want to use the :doc:`elgg/spinner</guides/javascript>` module.
  47. * Elgg does not use ``wrapper.status`` for anything, but a call to ``register_error()`` causes it to be
  48. set to ``-1``.
  49. * If the action echoes a non-JSON string, ``wrapper.output`` will contain that string.
  50. * ``elgg.action`` is based on ``jQuery.ajax`` and returns a ``jqXHR`` object (like a Promise), if you should want to use it.
  51. * After the PHP action completes, other plugins can alter the wrapper via the plugin hook ``'output', 'ajax'``,
  52. which filters the wrapper as an array (not a JSON string).
  53. * A ``forward()`` call forces the action to be processed and output immediately, with the ``wrapper.forward_url``
  54. value set to the normalized location given.
  55. * To make sure Ajax actions can only be executed via XHR, check ``elgg_is_xhr()`` first.
  56. The action JSON response wrapper
  57. --------------------------------
  58. .. code::
  59. {
  60. current_url: {String} "http://example.org/action/example/math", // not very useful
  61. forward_url: {String} "http://example.org/foo", ...if forward('foo') was called
  62. output: {String|Object} from echo in action
  63. status: {Number} 0 = success. -1 = an error was registered.
  64. system_messages: {Object}
  65. }
  66. .. warning::
  67. It's probably best to rely only on the ``output`` key, and validate it in case the PHP action could not run
  68. for some reason, e.g. the user was logged out or a CSRF attack did not provide tokens.
  69. Fetching Views
  70. ==============
  71. A plugin can use a view script to handle XHR GET requests. Here's a simple example of a view that returns a
  72. link to an object given by its GUID:
  73. .. code:: php
  74. // in myplugin_init()
  75. elgg_register_ajax_view('myplugin/get_link');
  76. .. code:: php
  77. // in myplugin/views/default/myplugin/get_link.php
  78. if (empty($vars['entity']) || !$vars['entity'] instanceof ElggObject) {
  79. return;
  80. }
  81. $object = $vars['entity'];
  82. /* @var ElggObject $object */
  83. echo elgg_view('output/url', [
  84. 'text' => $object->getDisplayName(),
  85. 'href' => $object->getUrl(),
  86. 'is_trusted' => true,
  87. ]);
  88. .. code:: js
  89. elgg.get('ajax/view/myplugin/get_link', {
  90. data: {
  91. guid: 123 // querystring
  92. },
  93. success: function (output) {
  94. $('.myplugin-link').html(output);
  95. }
  96. });
  97. The Ajax view system works significantly differently than the action system.
  98. * There are no access controls based on session status.
  99. * Non-XHR requests are automatically rejected.
  100. * GET vars are injected into ``$vars`` in the view.
  101. * If the request contains ``$_GET['guid']``, the system sets ``$vars['entity']`` to the corresponding entity or
  102. ``false`` if it can't be loaded.
  103. * There's no "wrapper" object placed around the view output.
  104. * System messages/errors shouldn't be used, as they don't display until the user loads another page.
  105. * If the view name begins with ``js/`` or ``css/``, a corresponding Content-Type header is added.
  106. .. warning::
  107. Unlike views rendered server-side, Ajax views must treat ``$vars`` as completely untrusted user data.
  108. Returning JSON from a view
  109. --------------------------
  110. If the view outputs encoded JSON, you must use ``elgg.getJSON`` to fetch it (or use some other method to set jQuery's
  111. ajax option ``dataType`` to ``json``). Your ``success`` function will be passed the decoded Object.
  112. Here's an example of fetching a view that returns a JSON-encoded array of times:
  113. .. code:: js
  114. elgg.getJSON('ajax/view/myplugin/get_times', {
  115. success: function (data) {
  116. alert('The time is ' + data.friendly_time);
  117. }
  118. });
  119. Fetching Forms
  120. ==============
  121. If you register a form view (name starting with ``forms/``), you can fetch it pre-rendered with ``elgg_view_form()``.
  122. Simply use ``ajax/form/<action>`` (instead of ``ajax/view/<view_name>``):
  123. .. code:: php
  124. // in myplugin_init()
  125. elgg_register_ajax_view('forms/myplugin/add');
  126. .. code:: js
  127. elgg.get('ajax/form/myplugin/add, {
  128. success: function (output) {
  129. $('.myplugin-form-container').html(output);
  130. }
  131. });
  132. * The GET params will be passed as ``$vars`` to *your* view, **not** the ``input/form`` view.
  133. * If you need to set ``$vars`` in the ``input/form`` view, you'll need to use the ``("view_vars", "input/form")``
  134. plugin hook (this can't be done client-side).
  135. .. warning::
  136. Unlike views rendered server-side, Ajax views must treat ``$vars`` as completely untrusted user data. Review
  137. the use of ``$vars`` in an existing form before registering it for Ajax fetching.
  138. Ajax helper functions
  139. ---------------------
  140. These functions extend jQuery's native Ajax features.
  141. ``elgg.get()`` is a wrapper for jQuery's ``$.ajax()``, but forces GET and does URL normalization.
  142. .. code:: js
  143. // normalizes the url to the current <site_url>/activity
  144. elgg.get('/activity', {
  145. success: function(resultText, success, xhr) {
  146. console.log(resultText);
  147. }
  148. });
  149. ``elgg.post()`` is a wrapper for jQuery's ``$.ajax()``, but forces POST and does URL normalization.