PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/docs/guides/views.rst

http://github.com/Elgg/Elgg
ReStructuredText | 653 lines | 438 code | 215 blank | 0 comment | 0 complexity | 9eab28420ff595c0d4f0b125f3688716 MD5 | raw file
Possible License(s): LGPL-3.0, MIT, GPL-2.0, BSD-3-Clause, LGPL-2.1, MPL-2.0-no-copyleft-exception
  1. Views
  2. #####
  3. .. contents:: Contents
  4. :local:
  5. :depth: 2
  6. Introduction
  7. ============
  8. Views are responsible for creating output. They handle everything from:
  9. * the layout of pages
  10. * chunks of presentation output (like a footer or a toolbar)
  11. * individual links and form inputs.
  12. * the images, js, and css needed by your web page
  13. Using views
  14. ===========
  15. At their most basic level, the default views are just PHP files with snippets of html:
  16. .. code-block:: html
  17. <h1>Hello, World!</h1>
  18. Assuming this view is located at ``/views/default/hello.php``, we could output it like so:
  19. .. code-block:: php
  20. echo elgg_view('hello');
  21. For your convenience, Elgg comes with quite a lot of views by default.
  22. In order to keep things manageable, they are organized into subdirectories.
  23. Elgg handles this situation quite nicely. For example, our simple view might live in
  24. ``/views/default/hello/world.php``, in which case it would be called like so:
  25. .. code-block:: php
  26. echo elgg_view('hello/world');
  27. The name of the view simply reflects the location of the view in the views directory.
  28. Views as templates
  29. ==================
  30. You can pass arbitrary data to a view via the ``$vars`` array.
  31. Our ``hello/world`` view might be modified to accept a variable like so:
  32. .. code-block:: html+php
  33. <h1>Hello, <?= $vars['name']; ?>!</h1>
  34. In this case, we can pass an arbitrary name parameter to the view like so:
  35. .. code-block:: php
  36. echo elgg_view('hello/world', ['name' => 'World']);
  37. which would produce the following output:
  38. .. code-block:: html
  39. <h1>Hello, World!</h1>
  40. .. warning::
  41. Views don't do any kind of automatic output sanitization by default.
  42. You are responsible for doing the correct sanitization yourself
  43. to prevent XSS attacks and the like.
  44. Views as cacheable assets
  45. =========================
  46. As mentioned before, views can contain JS, CSS, or even images.
  47. Asset views must meet certain requirements:
  48. * They *must not* take any ``$vars`` parameters
  49. * They *must not* change their output based on global state like
  50. * who is logged in
  51. * the time of day
  52. * They *must* contain a valid file extension
  53. * Bad: ``my/cool/template``
  54. * Good: ``my/cool/template.html``
  55. For example, suppose you wanted to load some CSS on a page.
  56. You could define a view ``mystyles.css``, which would look like so:
  57. .. code-block:: css
  58. /* /views/default/mystyles.css */
  59. .mystyles-foo {
  60. background: red;
  61. }
  62. .. note::
  63. Leave off the trailing ".php" from the filename and Elgg will automatically
  64. recognize the view as cacheable.
  65. To get a URL to this file, you would use ``elgg_get_simplecache_url``:
  66. .. code-block:: php
  67. // Returns "https://mysite.com/.../289124335/default/mystyles.css
  68. elgg_get_simplecache_url('mystyles.css');
  69. Elgg automatically adds the magic numbers you see there for cache-busting and
  70. sets long-term expires headers on the returned file.
  71. .. warning::
  72. Elgg may decide to change the location or structure of the returned URL in a
  73. future release for whatever reason, and the cache-busting numbers change
  74. every time you flush Elgg's caches, so the exact URL is not stable by design.
  75. With that in mind, here's a couple anti-patterns to avoid:
  76. * Don't rely on the exact structure/location of this URL
  77. * Don't try to generate the URLs yourself
  78. * Don't store the returned URLs in a database
  79. On the page you want to load the css, call:
  80. .. code-block:: php
  81. elgg_require_css('mystyles');
  82. .. _guides/views#viewtypes:
  83. Views and third-party assets
  84. ============================
  85. The best way to serve third-party assets is through views. However, instead of manually copy/pasting
  86. the assets into the right location in ``/views/*``, you can map the assets into the views system via
  87. the ``"views"`` key in your plugin's ``elgg-plugin.php`` config file.
  88. The views value must be a 2 dimensional array. The first level maps a viewtype to a list of view
  89. mappings. The secondary lists map view names to file paths, either absolute or relative to the Elgg root directory.
  90. If you check your assets into source control, point to them like this:
  91. .. code-block:: php
  92. <?php // mod/example/elgg-plugin.php
  93. return [
  94. // view mappings
  95. 'views' => [
  96. // viewtype
  97. 'default' => [
  98. // view => /path/from/filesystem/root
  99. 'js/jquery-ui.js' => __DIR__ . '/bower_components/jquery-ui/jquery-ui.min.js',
  100. ],
  101. ],
  102. ];
  103. To point to assets installed with composer, use install-root-relative paths by leaving off the leading slash:
  104. .. code-block:: php
  105. <?php // mod/example/elgg-plugin.php
  106. return [
  107. 'views' => [
  108. 'default' => [
  109. // view => path/from/install/root
  110. 'js/jquery-ui.js' => 'vendor/bower-asset/jquery-ui/jquery-ui.min.js',
  111. ],
  112. ],
  113. ];
  114. Elgg core uses this feature extensively, though the value is returned directly from ``/engine/views.php``.
  115. .. note::
  116. You don't have to use Bower, Composer Asset Plugin, or any other script for
  117. managing your plugin's assets, but we highly recommend using a package manager
  118. of some kind because it makes upgrading so much easier.
  119. Specifying additional views directories
  120. ---------------------------------------
  121. In ``elgg-plugin.php`` you can also specify directories to be scanned for views. Just provide
  122. a view name prefix ending with ``/`` and a directory path (like above).
  123. .. code-block:: php
  124. <?php // mod/file/elgg-plugin.php
  125. return [
  126. 'views' => [
  127. 'default' => [
  128. 'file/icon/' => __DIR__ . '/graphics/icons',
  129. ],
  130. ],
  131. ];
  132. With the above, files found within the ``icons`` folder will be interpreted as views. E.g. the view
  133. ``file/icon/general.gif`` will be created and mapped to ``mod/file/graphics/icons/general.gif``.
  134. .. note::
  135. This is a fully recursive scan. All files found will be brought into the views system.
  136. Multiple paths can share the same prefix, just give an array of paths:
  137. .. code-block:: php
  138. <?php // mod/file/elgg-plugin.php
  139. return [
  140. 'views' => [
  141. 'default' => [
  142. 'file/icon/' => [
  143. __DIR__ . '/graphics/icons',
  144. __DIR__ . '/more_icons', // processed 2nd (may override)
  145. ],
  146. ],
  147. ],
  148. ];
  149. Viewtypes
  150. =========
  151. You might be wondering: "Why ``/views/default/hello/world.php`` instead of just ``/views/hello/world.php``?".
  152. The subdirectory under ``/views`` determines the *viewtype* of the views below it.
  153. A viewtype generally corresponds to the output format of the views.
  154. The default viewtype is assumed to be HTML and other static assets necessary to
  155. render a responsive web page in a desktop or mobile browser, but it could also be:
  156. * RSS
  157. * ATOM
  158. * JSON
  159. * Mobile-optimized HTML
  160. * TV-optimized HTML
  161. * Any number of other data formats
  162. You can force Elgg to use a particular viewtype to render the page
  163. by setting the ``view`` input variable like so: ``https://mysite.com/?view=rss``.
  164. You could also write a plugin to set this automatically using the ``elgg_set_viewtype()`` function.
  165. For example, your plugin might detect that the page was accessed with an iPhone's browser string,
  166. and set the viewtype to ``iphone`` by calling:
  167. .. code-block:: php
  168. elgg_set_viewtype('iphone');
  169. The plugin would presumably also supply a set of views optimized for those devices.
  170. .. _guides/views#altering-views-via-plugin:
  171. Altering views via plugins
  172. ==========================
  173. Without modifying Elgg's core, Elgg provides several ways to customize almost all output:
  174. * You can `override a view <#overriding-views>`_, completely changing the file used to render it.
  175. * You can `extend a view <#extending-views>`_ by prepending or appending the output of another view to it.
  176. * You can `alter a view's inputs <#altering-view-input>`_ by plugin hook.
  177. * You can `alter a view's output <#altering-view-output>`_ by plugin hook.
  178. Overriding views
  179. ----------------
  180. Views in plugin directories always override views in the core directory;
  181. however, when plugins override the views of other plugins,
  182. :ref:`later plugins take precedent <admin/plugins#plugin-order>`.
  183. For example, if we wanted to customize the ``hello/world`` view to use an ``h2``
  184. instead of an ``h1``, we could create a file at ``/mod/example/views/default/hello/world.php`` like this:
  185. .. code-block:: html+php
  186. <h2>Hello, <?= $vars['name']; ?></h2>
  187. .. note::
  188. When considering long-term maintenance, overriding views in the core and bundled plugins has a cost:
  189. Upgrades may bring changes in views, and if you have overridden them, you will not get those changes.
  190. You may instead want to alter :ref:`the input <guides/views#altering-view-input>`
  191. or :ref:`the output <guides/views#altering-view-output>` of the view via plugin hooks.
  192. .. note::
  193. Elgg caches view locations. This means that you should disable the system cache while developing with views.
  194. When you install the changes to a production environment you must flush the caches.
  195. Extending views
  196. ---------------
  197. There may be other situations in which you don't want to override the whole view,
  198. you just want to prepend or append some more content to it. In Elgg this is called *extending a view*.
  199. For example, instead of overriding the ``hello/world`` view, we could extend it like so:
  200. .. code-block:: php
  201. elgg_extend_view('hello/world', 'hello/greeting');
  202. If the contents of ``/views/default/hello/greeting.php`` is:
  203. .. code-block:: html
  204. <h2>How are you today?</h2>
  205. Then every time we call ``elgg_view('hello/world');``, we'll get:
  206. .. code-block:: html
  207. <h1>Hello, World!</h1>
  208. <h2>How are you today?</h2>
  209. You can prepend views by passing a value to the 3rd parameter that is less than 500:
  210. .. code-block:: php
  211. // appends 'hello/greeting' to every occurrence of 'hello/world'
  212. elgg_extend_view('hello/world', 'hello/greeting');
  213. // prepends 'hello/greeting' to every occurrence of 'hello/world'
  214. elgg_extend_view('hello/world', 'hello/greeting', 450);
  215. All view extensions should be registered in your plugin's ``init,system`` event handler in ``start.php``.
  216. .. _guides/views#altering-view-input:
  217. Altering view input
  218. -------------------
  219. It may be useful to alter a view's ``$vars`` array before the view is rendered.
  220. Before each view rendering the ``$vars`` array is filtered by the
  221. :ref:`plugin hook <guides/hooks-list#views>` ``["view_vars", $view_name]``.
  222. Each registered handler function is passed these arguments:
  223. * ``$hook`` - the string ``"view_vars"``
  224. * ``$view_name`` - the view name being rendered (the first argument passed to ``elgg_view()``)
  225. * ``$returnvalue`` - the modified ``$vars`` array
  226. * ``$params`` - an array containing:
  227. * ``vars`` - the original ``$vars`` array, unaltered
  228. * ``view`` - the view name
  229. * ``viewtype`` - The :ref:`viewtype <guides/views#viewtypes>` being rendered
  230. Altering view input example
  231. ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  232. Here we'll alter the default pagination limit for the comments view:
  233. .. code-block:: php
  234. elgg_register_plugin_hook_handler('view_vars', 'page/elements/comments', 'myplugin_alter_comments_limit');
  235. function myplugin_alter_comments_limit($hook, $type, $vars, $params) {
  236. // only 10 comments per page
  237. $vars['limit'] = elgg_extract('limit', $vars, 10);
  238. return $vars;
  239. }
  240. .. _guides/views#altering-view-output:
  241. Altering view output
  242. --------------------
  243. Sometimes it is preferable to alter the output of a view instead of overriding it.
  244. The output of each view is run through the :ref:`plugin hook <guides/hooks-list#views>`
  245. ``["view", $view_name]`` before being returned by ``elgg_view()``.
  246. Each registered handler function is passed these arguments:
  247. * ``$hook`` - the string ``"view"``
  248. * ``$view_name`` - the view name being rendered (the first argument passed to ``elgg_view()``)
  249. * ``$result`` - the modified output of the view
  250. * ``$params`` - an array containing:
  251. * ``viewtype`` - The :ref:`viewtype <guides/views#viewtypes>` being rendered
  252. To alter the view output, the handler just needs to alter ``$returnvalue`` and return a new string.
  253. Altering view output example
  254. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  255. Here we'll eliminate breadcrumbs that don't have at least one link.
  256. .. code-block:: php
  257. elgg_register_plugin_hook_handler('view', 'navigation/breadcrumbs', 'myplugin_alter_breadcrumb');
  258. function myplugin_alter_breadcrumb($hook, $type, $returnvalue, $params) {
  259. // we only want to alter when viewtype is "default"
  260. if ($params['viewtype'] !== 'default') {
  261. return $returnvalue;
  262. }
  263. // output nothing if the content doesn't have a single link
  264. if (false === elgg_strpos($returnvalue, '<a ')) {
  265. return '';
  266. }
  267. // returning nothing means "don't alter the returnvalue"
  268. }
  269. Replacing view output completely
  270. --------------------------------
  271. You can pre-set the view output by setting ``$vars['__view_output']``. The value will be returned as a
  272. string. View extensions will not be used and the ``view`` hook will not be triggered.
  273. .. code-block:: php
  274. elgg_register_plugin_hook_handler('view_vars', 'navigation/breadcrumbs', 'myplugin_no_page_breadcrumbs');
  275. function myplugin_no_page_breadcrumbs($hook, $type, $vars, $params) {
  276. if (elgg_in_context('pages')) {
  277. return ['__view_output' => ""];
  278. }
  279. }
  280. .. note::
  281. For ease of use you can also use a already existing default hook callback to prevent output ``\Elgg\Values::preventViewOutput``
  282. Displaying entities
  283. ===================
  284. If you don't know what an entity is, :doc:`check this page out first </design/database>`.
  285. The following code will automatically display the entity in ``$entity``:
  286. .. code-block:: php
  287. echo elgg_view_entity($entity);
  288. As you'll know from the data model introduction, all entities have a *type*
  289. (object, site, user or group), and optionally a subtype
  290. (which could be anything - 'blog', 'forumpost', 'banana').
  291. ``elgg_view_entity`` will automatically look for a view called ``type/subtype``;
  292. if there's no subtype, it will look for ``type/type``. Failing that, before it
  293. gives up completely it tries ``type/default``.
  294. RSS feeds in Elgg generally work by outputting the ``object/default`` view in the 'rss' viewtype.
  295. For example, the view to display a blog post might be ``object/blog``.
  296. The view to display a user is ``user/default``.
  297. Full and partial entity views
  298. -----------------------------
  299. ``elgg_view_entity`` actually has a number of parameters,
  300. although only the very first one is required. The first three are:
  301. * ``$entity`` - The entity to display
  302. * ``$viewtype`` - The viewtype to display in (defaults to the one we're currently in,
  303. but it can be forced - eg to display a snippet of RSS within an HTML page)
  304. * ``$full_view`` - Whether to display a *full* version of the entity. (Defaults to ``true``.)
  305. This last parameter is passed to the view as ``$vars['full_view']``.
  306. It's up to you what you do with it; the usual behaviour is to only display comments
  307. and similar information if this is set to true.
  308. .. _guides/views#listing-entities:
  309. Listing entities
  310. ================
  311. This is then used in the provided listing functions.
  312. To automatically display a list of blog posts (:doc:`see the full tutorial </tutorials/blog>`), you can call:
  313. .. code-block:: php
  314. echo elgg_list_entities([
  315. 'type' => 'object',
  316. 'subtype' => 'blog',
  317. ]);
  318. This function checks to see if there are any entities; if there are, it first
  319. displays the ``navigation/pagination`` view in order to display a way to move
  320. from page to page. It then repeatedly calls ``elgg_view_entity`` on each entity
  321. before returning the result.
  322. Note that ``elgg_list_entities`` allows the URL to set its ``limit`` and ``offset`` options,
  323. so set those explicitly if you need particular values (e.g. if you're not using it for pagination).
  324. Elgg knows that it can automatically supply an RSS feed on pages that use ``elgg_list_entities``.
  325. It initializes the ``["head","page"]`` plugin hook (which is used by the header)
  326. in order to provide RSS autodiscovery, which is why you can see the orange RSS
  327. icon on those pages in some browsers.
  328. Entity listings will default try to load entity owners and container owners. If you want to prevent this you can turn this off.
  329. .. code-block:: php
  330. echo elgg_list_entities([
  331. 'type' => 'object',
  332. 'subtype' => 'blog',
  333. // disable owner preloading
  334. 'preload_owners' => false,
  335. ]);
  336. See also :doc:`this background information on Elgg's database </design/database>`.
  337. If you want to show a message when the list does not contain items to list, you can pass
  338. a ``no_results`` message or ``true`` for the default message. If you want even more controle over the ``no_results`` message you
  339. can also pass a Closure (an anonymous function).
  340. .. code-block:: php
  341. echo elgg_list_entities([
  342. 'type' => 'object',
  343. 'subtype' => 'blog',
  344. 'no_results' => elgg_echo('notfound'),
  345. ]);
  346. Rendering a list with an alternate view
  347. ---------------------------------------
  348. You can define an alternative view to render list items using ``'item_view'`` parameter.
  349. In some cases, default entity views may be unsuitable for your needs.
  350. Using ``item_view`` allows you to customize the look, while preserving pagination, list's HTML markup etc.
  351. Consider these two examples:
  352. .. code-block:: php
  353. echo elgg_list_entities([
  354. 'type' => 'group',
  355. 'relationship' => 'member',
  356. 'relationship_guid' => elgg_get_logged_in_user_guid(),
  357. 'inverse_relationship' => false,
  358. 'full_view' => false,
  359. ]);
  360. .. code-block:: php
  361. echo elgg_list_entities([
  362. 'type' => 'group',
  363. 'relationship' => 'invited',
  364. 'relationship_guid' => (int) $user_guid,
  365. 'inverse_relationship' => true,
  366. 'item_view' => 'group/format/invitationrequest',
  367. ]);
  368. In the first example, we are displaying a list of groups a user is a member of using the default group view.
  369. In the second example, we want to display a list of groups the user was invited to.
  370. Since invitations are not entities, they do not have their own views and can not be listed using ``elgg_list_*``.
  371. We are providing an alternative item view, that will use the group entity to display
  372. an invitation that contains a group name and buttons to access or reject the invitation.
  373. Rendering a list as a table
  374. ---------------------------
  375. Since 2.3 you can render lists as tables. Set ``$options['list_type'] = 'table'`` and provide an array of
  376. TableColumn objects as ``$options['columns']``. The service ``elgg()->table_columns`` provides several
  377. methods to create column objects based around existing views (like ``page/components/column/*``), properties,
  378. or methods.
  379. In this example, we list the latest ``my_plugin`` objects in a table of 3 columns: entity icon, the display
  380. name, and a friendly format of the time.
  381. .. code-block:: php
  382. echo elgg_list_entities([
  383. 'type' => 'object',
  384. 'subtype' => 'my_plugin',
  385. 'list_type' => 'table',
  386. 'columns' => [
  387. elgg()->table_columns->icon(),
  388. elgg()->table_columns->getDisplayName(),
  389. elgg()->table_columns->time_created(null, [
  390. 'format' => 'friendly',
  391. ]),
  392. ],
  393. ]);
  394. See the ``Elgg\Views\TableColumn\ColumnFactory`` class for more details on how columns are specified and
  395. rendered. You can add or override methods of ``elgg()->table_columns`` in a variety of ways, based on views,
  396. properties/methods on the items, or given functions.
  397. Icons
  398. =====
  399. Elgg has support for two kind of icons: generic icons to help with styling (eg. show delete icon) and Entity icons (eg. user avatar).
  400. Generic icons
  401. -------------
  402. As of Elgg 2.0 the generic icons are based on the FontAwesome_ library. You can get any of the supported icons by calling
  403. ``elgg_view_icon($icon_name, $vars);`` where:
  404. * ``$icon_name`` is the FontAwesome name (without ``fa-``) for example ``user``
  405. * ``$vars`` is optional, for example you can set an additional class
  406. ``elgg_view_icon()`` calls the view ``output/icon`` with the given icon name and outputs all the correct classes to render the FontAwesome icon.
  407. If you wish to replace an icon with another icon you can write a ``view_vars``, ``output/icon`` hook to replace the icon name with your replacement.
  408. For backwards compatibility some older Elgg icon names are translated to a corresponding FontAwesome icon.
  409. Entity icons
  410. ------------
  411. To view an icon belowing to an Entity call ``elgg_view_entity_icon($entity, $size, $vars);`` where:
  412. * ``$entity`` is the ``ElggEntity`` you wish to show the icon for
  413. * ``$size`` is the requestes size. Default Elgg supports ``large``, ``medium``, ``small``, ``tiny`` and ``topbar`` (``master`` is also
  414. available, but don't use it)
  415. * ``$vars`` in order to pass additional information to the icon view
  416. ``elgg_view_entity_icon()`` calls a view in the order:
  417. * ``icon/<type>/<subtype>``
  418. * ``icon/<type>/default``
  419. * ``icon/default``
  420. So if you wish to customize the layout of the icon you can overrule the corresponding view.
  421. An example of displaying a user avatar is
  422. .. code-block:: php
  423. // get the user
  424. $user = elgg_get_logged_in_user_entity();
  425. // show the small icon
  426. echo elgg_view_entity_icon($user, 'small');
  427. // don't add the user_hover menu to the icon
  428. echo elgg_view_entity_icon($user, 'small', [
  429. 'use_hover' => false,
  430. ]);
  431. Related
  432. =======
  433. .. toctree::
  434. :maxdepth: 1
  435. views/page-structure
  436. views/simplecache
  437. views/foot-vs-footer
  438. .. _FontAwesome: http://fontawesome.io/icons/