[FEATURE] JavaScript-based icon API 61/43961/17
authorAndreas Fernandez <a.fernandez@scripting-base.de>
Sat, 10 Oct 2015 07:34:17 +0000 (09:34 +0200)
committerFrank Nägler <frank.naegler@typo3.org>
Wed, 14 Oct 2015 17:33:37 +0000 (19:33 +0200)
A new JavaScript-based icon API is introduced with this patch.
As first step, some spinner icons used in the topbar items are
replaced with the icon API.

Resolves: #70583
Releases: master
Change-Id: I24c0649df5ddce2eb8f3191137a01fa7db29209a
Reviewed-on: http://review.typo3.org/43961
Reviewed-by: Frans Saris <franssaris@gmail.com>
Tested-by: Frans Saris <franssaris@gmail.com>
Reviewed-by: Frank Nägler <frank.naegler@typo3.org>
Tested-by: Frank Nägler <frank.naegler@typo3.org>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
typo3/sysext/backend/Resources/Public/JavaScript/Icons.js [new file with mode: 0644]
typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ClearCacheMenu.js
typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js
typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/SystemInformationMenu.js
typo3/sysext/core/Classes/Imaging/IconFactory.php
typo3/sysext/core/Documentation/Changelog/master/Feature-70583-IntroducedIconAPIInJavaScript.rst [new file with mode: 0644]
typo3/sysext/opendocs/Resources/Public/JavaScript/Toolbar/OpendocsMenu.js

index c7977e3..6d0f688 100644 (file)
@@ -186,5 +186,11 @@ return [
     'online_media_create' => [
         'path' => '/online-media/create',
         'target' => Controller\OnlineMediaController::class . '::createAction'
+    ],
+
+    // Get icon from IconFactory
+    'icons_get' => [
+        'path' => '/icons/get',
+        'target' => \TYPO3\CMS\Core\Imaging\IconFactory::class . '::processAjaxRequest'
     ]
 ];
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Icons.js b/typo3/sysext/backend/Resources/Public/JavaScript/Icons.js
new file mode 100644 (file)
index 0000000..5abcbd7
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with DocumentHeader source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Uses the icon API of the core to fetch icons via AJAX.
+ */
+define(['jquery'], function($) {
+       'use strict';
+
+       var Icons = {
+               cache: {},
+               sizes: {
+                       small: 'small',
+                       default: 'default',
+                       large: 'large',
+                       overlay: 'overlay'
+               },
+               states: {
+                       default: 'default',
+                       disabled: 'disabled'
+               }
+       };
+
+       /**
+        * Get the icon by its identifier.
+        *
+        * @param {string} identifier
+        * @param {string} size
+        * @param {string} overlayIdentifier
+        * @param {string} state
+        * @return {Promise<Array>}
+        */
+       Icons.getIcon = function(identifier, size, overlayIdentifier, state) {
+               return $.when.apply($, Icons.fetch([[identifier, size, overlayIdentifier, state]]));
+       };
+
+       /**
+        * Fetches multiple icons by passing the parameters of getIcon() for each requested
+        * icon as array.
+        *
+        * @param {Array} icons
+        * @return {Promise<Array>}
+        */
+       Icons.getIcons = function(icons) {
+               if (!icons instanceof Array) {
+                       throw 'Icons must be an array of multiple definitions.';
+               }
+               return $.when.apply($, Icons.fetch(icons));
+       };
+
+       /**
+        * Performs the AJAX request to fetch the icon.
+        *
+        * @param {Array} icons
+        * @return {Array}
+        * @private
+        */
+       Icons.fetch = function(icons) {
+               var promises = [],
+                       requestedIcons = {};
+
+               for (var i = 0; i < icons.length; ++i) {
+                       /**
+                        * Icon keys:
+                        *
+                        * 0: identifier
+                        * 1: size
+                        * 2: overlayIdentifier
+                        * 3: state
+                        */
+                       var icon = icons[i];
+                       icon[1] = icon[1] || Icons.sizes.default;
+                       icon[3] = icon[3] || Icons.states.default;
+
+                       var cacheIdentifier = icon.join('_');
+                       if (Icons.isCached(cacheIdentifier)) {
+                               promises.push(Icons.getFromCache(cacheIdentifier));
+                       } else {
+                               requestedIcons[icon[0]] = {
+                                       cacheIdentifier: cacheIdentifier,
+                                       icon: icon
+                               };
+                       }
+               }
+
+               if (Object.keys(requestedIcons).length > 0) {
+                       promises.push(
+                               $.ajax({
+                                       url: TYPO3.settings.ajaxUrls['icons_get'],
+                                       data: {
+                                               requestedIcons: JSON.stringify(
+                                                       $.map(requestedIcons, function(o) {
+                                                               return [o['icon']];
+                                                       })
+                                               )
+                                       },
+                                       success: function(data) {
+                                               $.each(data, function(identifier, markup) {
+                                                       var cacheIdentifier = requestedIcons[identifier].cacheIdentifier,
+                                                               cacheEntry = {};
+
+                                                       cacheEntry[identifier] = markup;
+                                                       Icons.putInCache(cacheIdentifier, cacheEntry);
+                                               });
+                                       }
+                               })
+                       );
+               }
+
+               return promises;
+       };
+
+       /**
+        * Check whether icon was fetched already
+        *
+        * @param {String} cacheIdentifier
+        * @returns {Boolean}
+        * @private
+        */
+       Icons.isCached = function(cacheIdentifier) {
+               return typeof Icons.cache[cacheIdentifier] !== 'undefined';
+       };
+
+       /**
+        * Get icon from cache
+        *
+        * @param {String} cacheIdentifier
+        * @returns {String}
+        * @private
+        */
+       Icons.getFromCache = function(cacheIdentifier) {
+               return Icons.cache[cacheIdentifier];
+       };
+
+       /**
+        * Put icon into cache
+        *
+        * @param {String} cacheIdentifier
+        * @param {Object} markup
+        * @private
+        */
+       Icons.putInCache = function(cacheIdentifier, markup) {
+               Icons.cache[cacheIdentifier] = markup;
+       };
+
+       return Icons;
+});
index 1a599a7..35e5715 100644 (file)
  * main functionality for clearing caches via the top bar
  * reloading the clear cache icon
  */
-define(['jquery'], function($) {
+define(['jquery', 'TYPO3/CMS/Backend/Icons'], function($, Icons) {
 
        var ClearCacheMenu = {
-               $spinnerElement: $('<span>', {
-                       'class': 'fa fa-circle-o-notch fa-spin'
-               }),
                options: {
                        containerSelector: '#typo3-cms-backend-backend-toolbaritems-clearcachetoolbaritem',
                        menuItemSelector: '.dropdown-menu a',
-                       toolbarIconSelector: '.dropdown-toggle i.fa'
+                       toolbarIconSelector: '.dropdown-toggle span.icon'
                }
        };
 
@@ -52,16 +49,19 @@ define(['jquery'], function($) {
                // Close clear cache menu
                $(ClearCacheMenu.options.containerSelector).removeClass('open');
 
-               var $toolbarItemIcon = $(ClearCacheMenu.options.toolbarIconSelector, ClearCacheMenu.options.containerSelector);
+               var $toolbarItemIcon = $(ClearCacheMenu.options.toolbarIconSelector, ClearCacheMenu.options.containerSelector),
+                       $existingIcon = $toolbarItemIcon.clone();
+
+               Icons.getIcon('spinner-circle-light', Icons.sizes.small).done(function(icons) {
+                       $toolbarItemIcon.replaceWith(icons['spinner-circle-light']);
+               });
 
-               var $spinnerIcon = ClearCacheMenu.$spinnerElement.clone();
-               var $existingIcon = $toolbarItemIcon.replaceWith($spinnerIcon);
                $.ajax({
                        url: ajaxUrl,
                        type: 'post',
                        cache: false,
                        success: function() {
-                               $spinnerIcon.replaceWith($existingIcon);
+                               $(ClearCacheMenu.options.toolbarIconSelector, ClearCacheMenu.options.containerSelector).replaceWith($existingIcon);
                        }
                });
        };
index 0ce6b7b..a520535 100644 (file)
  * shortcut menu logic to add new shortcut, remove a shortcut
  * and edit a shortcut
  */
-define(['jquery', 'TYPO3/CMS/Backend/Modal'], function($, Modal) {
+define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Icons'], function($, Modal, Icons) {
 
        var ShortcutMenu = {
-               $spinnerElement: $('<span>', {
-                       class: 'fa fa-circle-o-notch fa-spin'
-               }),
                options: {
                        containerSelector: '#typo3-cms-backend-backend-toolbaritems-shortcuttoolbaritem',
-                       toolbarIconSelector: '.dropdown-toggle .fa',
+                       toolbarIconSelector: '.dropdown-toggle span.icon',
                        toolbarMenuSelector: '.dropdown-menu',
                        shortcutItemSelector: '.dropdown-menu .shortcut',
                        shortcutDeleteSelector: '.shortcut-delete',
@@ -107,9 +104,12 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal'], function($, Modal) {
                        // @todo: translations
                        Modal.confirm('Create bookmark', confirmationText)
                                .on('confirm.button.ok', function() {
-                                       var $toolbarItemIcon = $(ShortcutMenu.options.toolbarIconSelector, ShortcutMenu.options.containerSelector);
-                                       var $spinner = ShortcutMenu.$spinnerElement.clone();
-                                       var $existingItem = $toolbarItemIcon.replaceWith($spinner);
+                                       var $toolbarItemIcon = $(ShortcutMenu.options.toolbarIconSelector, ShortcutMenu.options.containerSelector),
+                                               $existingIcon = $toolbarItemIcon.clone();
+
+                                       Icons.getIcon('spinner-circle-light', Icons.sizes.small).done(function(icons) {
+                                               $toolbarItemIcon.replaceWith(icons['spinner-circle-light']);
+                                       });
 
                                        $.ajax({
                                                url: TYPO3.settings.ajaxUrls['shortcut_create'],
@@ -122,7 +122,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal'], function($, Modal) {
                                                cache: false
                                        }).done(function() {
                                                ShortcutMenu.refreshMenu();
-                                               $spinner.replaceWith($existingItem);
+                                               $(ShortcutMenu.options.toolbarIconSelector, ShortcutMenu.options.containerSelector).replaceWith($existingIcon);
                                                if (typeof shortcutButton === 'object') {
                                                        $(shortcutButton).addClass('active');
                                                        $(shortcutButton).attr('title', null);
index 02a3f04..8461cc5 100644 (file)
 /**
  * System information menu handler
  */
-define(['jquery', 'TYPO3/CMS/Backend/Storage'], function($, Storage) {
+define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Storage'], function($, Icons, Storage) {
        'use strict';
 
        var SystemInformationMenu = {
                identifier: {
                        containerSelector: '#typo3-cms-backend-backend-toolbaritems-systeminformationtoolbaritem',
-                       toolbarIconSelector: '.dropdown-toggle span.t3-icon',
+                       toolbarIconSelector: '.dropdown-toggle span.icon',
                        menuContainerSelector: '.dropdown-menu',
                        moduleLinks: '.t3js-systeminformation-module'
                },
                elements: {
-                       $counter: $('#t3js-systeminformation-counter'),
-                       $spinnerElement: $('<span>', {
-                               'class': 't3-icon fa fa-circle-o-notch spinner fa-spin'
-                       })
+                       $counter: $('#t3js-systeminformation-counter')
                }
        };
 
@@ -44,8 +41,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Storage'], function($, Storage) {
         */
        SystemInformationMenu.updateMenu = function() {
                var $toolbarItemIcon = $(SystemInformationMenu.identifier.toolbarIconSelector, SystemInformationMenu.identifier.containerSelector),
-                       $spinnerIcon = SystemInformationMenu.elements.$spinnerElement.clone(),
-                       $existingIcon = $toolbarItemIcon.replaceWith($spinnerIcon),
+                       $existingIcon = $toolbarItemIcon.clone(),
                        $menuContainer = $(SystemInformationMenu.identifier.containerSelector).find(SystemInformationMenu.identifier.menuContainerSelector);
 
                // hide the menu if it's active
@@ -53,6 +49,10 @@ define(['jquery', 'TYPO3/CMS/Backend/Storage'], function($, Storage) {
                        $menuContainer.click();
                }
 
+               Icons.getIcon('spinner-circle-light', Icons.sizes.small).done(function(icons) {
+                       $toolbarItemIcon.replaceWith(icons['spinner-circle-light']);
+               });
+
                $.ajax({
                        url: TYPO3.settings.ajaxUrls['systeminformation_render'],
                        type: 'post',
@@ -60,7 +60,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Storage'], function($, Storage) {
                        success: function(data) {
                                $menuContainer.html(data);
                                SystemInformationMenu.updateCounter();
-                               $spinnerIcon.replaceWith($existingIcon);
+                               $(SystemInformationMenu.identifier.toolbarIconSelector, SystemInformationMenu.identifier.containerSelector).replaceWith($existingIcon);
 
                                SystemInformationMenu.initialize();
                        }
index fdd4a79..a10b1dd 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Imaging;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\FolderInterface;
 use TYPO3\CMS\Core\Resource\InaccessibleFolder;
@@ -75,6 +77,38 @@ class IconFactory
     }
 
     /**
+     * @param ServerRequestInterface $request
+     * @param ResponseInterface $response
+     * @return string
+     * @internal
+     */
+    public function processAjaxRequest(ServerRequestInterface $request, ResponseInterface $response)
+    {
+        $parsedBody = $request->getParsedBody();
+        $queryParams = $request->getQueryParams();
+        $requestedIcons = json_decode(
+            isset($parsedBody['requestedIcons'])
+                ? $parsedBody['requestedIcons']
+                : $queryParams['requestedIcons'],
+            true
+        );
+
+        $icons = [];
+        for ($i = 0, $count = count($requestedIcons); $i < $count; ++$i) {
+            list($identifier, $size, $overlayIdentifier, $iconState) = $requestedIcons[$i];
+            if (empty($overlayIdentifier)) {
+                $overlayIdentifier = null;
+            }
+            $iconState = IconState::cast($iconState);
+            $icons[$identifier] = $this->getIcon($identifier, $size, $overlayIdentifier, $iconState)->render();
+        }
+        $response->getBody()->write(
+            json_encode($icons)
+        );
+        return $response;
+    }
+
+    /**
      * @param string $identifier
      * @param string $size "large", "small" or "default", see the constants of the Icon class
      * @param string $overlayIdentifier
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-70583-IntroducedIconAPIInJavaScript.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-70583-IntroducedIconAPIInJavaScript.rst
new file mode 100644 (file)
index 0000000..8f72562
--- /dev/null
@@ -0,0 +1,67 @@
+===================================================
+Feature: #70583 - Introduced Icon API in JavaScript
+===================================================
+
+Description
+===========
+
+A JavaScript-based icon API based on the PHP API has been introduced. The methods ``getIcon()``
+and ``getIcons()`` can be called in an RequireJS module.
+
+When imported in a RequireJS module, a developer can fetch icons via JavaScript with the same parameters as in PHP.
+The methods ``getIcon()`` and ``getIcons()`` return ``Promise`` objects.
+
+Importing
+=========
+
+.. code-block:: javascript
+
+       define(['jquery', 'TYPO3/CMS/Backend/Icons'], function($, Icons) {
+       });
+
+
+Get icons
+=========
+
+A single icon can be fetched by ``getIcon()`` which takes four parameters:
+
+.. container:: table-row
+
+   identifier
+         The icon identifier.
+
+   size
+         The size of the icon. Please use the properties of the ``Icons.sizes`` object.
+
+   overlayIdentifier
+         An overlay identifier rendered on the icon.
+
+   state
+         The state of the icon. Please use the properties of the ``Icons.states`` object.
+
+
+Multiple icons can be fetched by ``getIcons()``. This function takes a multidimensional array as parameter,
+holding the parameters used by ``getIcon()`` for each icon.
+
+To use the fetched icons, chain the ``done()`` method to the promise.
+
+Examples
+--------
+
+.. code-block:: javascript
+
+       // Get a single icon
+       Icons.getIcon('spinner-circle-light', Icons.sizes.small).done(function(icons) {
+               $toolbarItemIcon.replaceWith(icons['spinner-circle-light']);
+       });
+
+       // Get multiple icons
+       Icons.getIcons([
+               ['apps-filetree-folder-default', Icons.sizes.large],
+               ['actions-edit-delete', Icons.sizes.small, null, Icons.states.disabled],
+               ['actions-system-cache-clear-impact-medium']
+       ]).done(function(icons) {
+               // icons['apps-filetree-folder-default']
+               // icons['actions-edit-delete']
+               // icons['actions-system-cache-clear-impact-medium']
+       });
index aa22dff..442400f 100644 (file)
  *  - navigating to the documents
  *  - updating the menu
  */
-define(['jquery'], function($) {
+define(['jquery', 'TYPO3/CMS/Backend/Icons'], function($, Icons) {
 
        var OpendocsMenu = {
-               $spinnerElement: $('<span>', {
-                       'class': 'fa fa-circle-o-notch fa-spin'
-               }),
                options: {
                        containerSelector: '#typo3-cms-opendocs-backend-toolbaritems-opendocstoolbaritem',
                        hashDataAttributeName: 'opendocsidentifier',
                        closeSelector: '.dropdown-list-link-close',
                        menuContainerSelector: '.dropdown-menu',
                        menuItemSelector: '.dropdown-menu li a',
-                       toolbarIconSelector: '.dropdown-toggle i.fa',
+                       toolbarIconSelector: '.dropdown-toggle span.icon',
                        openDocumentsItemsSelector: 'li.opendoc',
                        counterSelector: '#tx-opendocs-counter'
                }
@@ -52,10 +49,12 @@ define(['jquery'], function($) {
         * Displays the menu and does the AJAX call to the TYPO3 backend
         */
        OpendocsMenu.updateMenu = function() {
-               var $toolbarItemIcon = $(OpendocsMenu.options.toolbarIconSelector, OpendocsMenu.options.containerSelector);
+               var $toolbarItemIcon = $(OpendocsMenu.options.toolbarIconSelector, OpendocsMenu.options.containerSelector),
+                       $existingIcon = $toolbarItemIcon.clone();
 
-               var $spinnerIcon = OpendocsMenu.$spinnerElement.clone();
-               var $existingIcon = $toolbarItemIcon.replaceWith($spinnerIcon);
+               Icons.getIcon('spinner-circle-light', Icons.sizes.small).done(function(icons) {
+                       $toolbarItemIcon.replaceWith(icons['spinner-circle-light']);
+               });
 
                $.ajax({
                        url: TYPO3.settings.ajaxUrls['opendocs_menu'],
@@ -64,7 +63,7 @@ define(['jquery'], function($) {
                        success: function(data) {
                                $(OpendocsMenu.options.containerSelector).find(OpendocsMenu.options.menuContainerSelector).html(data);
                                OpendocsMenu.updateNumberOfDocs();
-                               $spinnerIcon.replaceWith($existingIcon);
+                               $(OpendocsMenu.options.toolbarIconSelector, OpendocsMenu.options.containerSelector).replaceWith($existingIcon);
                        }
                });
        };