[TASK] Replace DynTabMenu with jQuery/RequireJS plugin 08/19708/13
authorBenjamin Mack <benni@typo3.org>
Sat, 8 Mar 2014 11:24:09 +0000 (12:24 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Wed, 12 Nov 2014 10:13:18 +0000 (11:13 +0100)
The DynTabMenu code can be abstracted and
cleaned - done via RequireJS + jQuery.

A completely new jQuery plugin is written that stores the current
selected tab ID in the local storage (available in IE8+) or falls back
to the first tab of the menu item.
The changes separates logic (JS) from structure (HTML), and removes
plain old JS.

Resolves: #47003
Releases: master
Change-Id: Ib81606cc653ccf7d58105f463bc6a09eb742b7d5
Reviewed-on: http://review.typo3.org/19708
Reviewed-by: Alexander Opitz <opitz.alexander@googlemail.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/backend/Classes/Template/DocumentTemplate.php
typo3/sysext/backend/Resources/Public/JavaScript/Plugins/TabMenuPlugin.js [new file with mode: 0644]
typo3/sysext/backend/Resources/Public/JavaScript/TabMenuSetup.js [new file with mode: 0644]
typo3/sysext/backend/Resources/Public/JavaScript/tabmenu.js

index 9a84427..59a421f 100644 (file)
@@ -1567,97 +1567,86 @@ function jumpToUrl(URL) {
         * @param int $tabBehaviour If set to '1' empty tabs will be remove, If set to '2' empty tabs will be disabled
         * @return string JavaScript section for the HTML header.
         */
-       public function getDynTabMenu($menuItems, $identString, $toggle = 0, $foldout = FALSE, $noWrap = TRUE, $fullWidth = FALSE, $defaultTabIndex = 1, $tabBehaviour = 2) {
-               // Load the static code, if not already done with the function below
-               $this->loadJavascriptLib('sysext/backend/Resources/Public/JavaScript/tabmenu.js');
+       public function getDynTabMenu($menuItems, $identString, $toggle = 0, $foldout = FALSE, $noWrap = TRUE, $fullWidth = FALSE, $defaultTabIndex = 1, $tabBehaviour = 1) {
                $content = '';
                if (is_array($menuItems)) {
-                       // Init:
                        $options = array(array());
                        $divs = array();
-                       $JSinit = array();
-                       $id = $this->getDynTabMenuId($identString);
+
+                       // include JS for TabMenu
+                       $this->pageRenderer->loadJquery();
+                       $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/TabMenuSetup');
+
+                       // unique identifier on this page
+                       $menuId = $this->getDynTabMenuId($identString);
 
                        // Traverse menu items
-                       $c = 0;
                        $tabRows = 0;
                        $titleLenCount = 0;
-                       foreach ($menuItems as $index => $def) {
-                               // Need to add one so checking for first index in JavaScript
-                               // is different than if it is not set at all.
-                               $index += 1;
+                       foreach ($menuItems as $index => $menuItemData) {
+
+                               $menuItemId = $menuId . '-' . $index;
+
                                // Switch to next tab row if needed
-                               if (!$foldout && ($def['newline'] === TRUE && $titleLenCount > 0)) {
+                               if (!$foldout && ($menuItemData['newline'] === TRUE && $titleLenCount > 0)) {
                                        $titleLenCount = 0;
                                        $tabRows++;
                                        $options[$tabRows] = array();
                                }
-                               if ($toggle == 1) {
-                                       $onclick = 'DTM_toggle("' . $id . '","' . $index . '"); return false;';
-                               } else {
-                                       $onclick = 'DTM_activate("' . $id . '","' . $index . '", ' . ($toggle < 0 ? 1 : 0) . '); return false;';
-                               }
-                               $isEmpty = trim($def['content']) === '' && trim($def['icon']) === '';
+
+                               $isEmpty = trim($menuItemData['content']) === '' && trim($menuItemData['icon']) === '';
+
                                // "Removes" empty tabs
                                if ($isEmpty && $tabBehaviour == 1) {
                                        continue;
                                }
-                               $requiredIcon = '<img name="' . $id . '-' . $index . '-REQ" src="' . $GLOBALS['BACK_PATH'] . 'gfx/clear.gif" class="t3-TCEforms-reqTabImg" alt="" />';
+
+                               // set the classes for the container
+                               $containerClasses = ($isEmpty ? 'disabled' : 'tab');
+                               if ($noWrap) {
+                                       $containerClasses .= ' nowrap';
+                               }
+
+                               $requiredIcon = '<img name="' . $menuItemId . '-REQ" src="' . $GLOBALS['BACK_PATH'] . 'gfx/clear.gif" class="t3-TCEforms-reqTabImg" alt="" />';
+
+                               $tabHeader = $menuItemData['icon'] . ($menuItemData['label'] ? htmlspecialchars($menuItemData['label']) : '&nbsp;') . $requiredIcon;
+
+                               // wrap it in a link if it is not empty
+                               if (!$isEmpty) {
+                                       $tabHeader = '<a href="#' . $menuItemId . '" data-toggle="TabMenu" ' . ($menuItemData['linkTitle'] ? ' title="' . htmlspecialchars($menuItemData['linkTitle']) . '"' : '') . '>' . $tabHeader . '</a>';
+                               }
+
                                if (!$foldout) {
-                                       // Create TAB cell:
-                                       $options[$tabRows][] = '
-                                                       <li class="' . ($isEmpty ? 'disabled' : '') . '" id="' . $id . '-' . $index . '-MENU">' . ($isEmpty ? '' : '<a href="#" onclick="' . htmlspecialchars($onclick) . '"' . ($def['linkTitle'] ? ' title="' . htmlspecialchars($def['linkTitle']) . '"' : '') . '>') . $def['icon'] . ($def['label'] ? htmlspecialchars($def['label']) : '&nbsp;') . $requiredIcon . $this->icons($def['stateIcon'], 'margin-left: 10px;') . ($isEmpty ? '' : '</a>') . '</li>';
-                                       $titleLenCount += strlen($def['label']);
+                                       $tabHeader .= $this->icons($menuItemData['stateIcon'], 'margin-left: 10px;');
+                                       // Create TAB cell
+                                       $options[$tabRows][] = '<li class="' . $containerClasses . '" id="' . $menuItemId . '-MENU">' . $tabHeader . '</li>';
+                                       $titleLenCount += strlen($menuItemData['label']);
                                } else {
                                        // Create DIV layer for content:
-                                       $divs[] = '
-                                               <div class="' . ($isEmpty ? 'disabled' : '') . '" id="' . $id . '-' . $index . '-MENU">' . ($isEmpty ? '' : '<a href="#" onclick="' . htmlspecialchars($onclick) . '"' . ($def['linkTitle'] ? ' title="' . htmlspecialchars($def['linkTitle']) . '"' : '') . '>') . $def['icon'] . ($def['label'] ? htmlspecialchars($def['label']) : '&nbsp;') . $requiredIcon . ($isEmpty ? '' : '</a>') . '</div>';
+                                       $divs[] = '<div class="' . $containerClasses . '" id="' . $menuItemId . '-MENU">' . $tabHeader . '</div>';
                                }
+
                                // Create DIV layer for content:
-                               $divs[] = '
-                                               <div style="display: none;" id="' . $id . '-' . $index . '-DIV" class="c-tablayer">' . ($def['description'] ? '<p class="c-descr">' . nl2br(htmlspecialchars($def['description'])) . '</p>' : '') . $def['content'] . '</div>';
-                               // Create initialization string:
-                               $JSinit[] = '
-                                               DTM_array["' . $id . '"][' . $c . '] = "' . $id . '-' . $index . '";
-                               ';
-                               // If not empty and we have the toggle option on, check if the tab needs to be expanded
-                               if ($toggle == 1 && !$isEmpty) {
-                                       $JSinit[] = '
-                                               if (top.DTM_currentTabs["' . $id . '-' . $index . '"]) { DTM_toggle("' . $id . '","' . $index . '",1); }
-                                       ';
-                               }
-                               $c++;
+                               $divs[] = '<div style="display: none;" id="' . $menuItemId . '" class="c-tablayer">' . ($menuItemData['description'] ? '<p class="c-descr">' . nl2br(htmlspecialchars($menuItemData['description'])) . '</p>' : '') . $menuItemData['content'] . '</div>';
                        }
-                       // Render menu:
+
+                       // Render menu
                        if (count($options)) {
-                               // Tab menu is compiled:
+                               // Tab menu is compiled
                                if (!$foldout) {
                                        $tabContent = '';
                                        for ($a = 0; $a <= $tabRows; $a++) {
                                                $tabContent .= '
-
-                                       <!-- Tab menu -->
-                                       <ul class="nav nav-tabs" role="tablist">
+                                       <ul class="nav nav-tabs t3-dyntabnav" role="tablist">
                                                ' . implode('', $options[$a]) . '
                                        </ul>';
                                        }
-                                       $content .= '<div class="typo3-dyntabmenu-tabs">' . $tabContent . '</div>';
+                                       $content .= '<div class="typo3-dyntabmenu-tabs" id="' . $menuId . '-menucontainer">' . $tabContent . '</div>';
                                }
-                               // Div layers are added:
+                               // Div layers are added
                                $content .= '
                                <!-- Div layers for tab menu: -->
-                               <div class="typo3-dyntabmenu-divs' . ($foldout ? '-foldout' : '') . '">
-                               ' . implode('', $divs) . '</div>';
-                               // Java Script section added:
-                               $content .= '
-                               <!-- Initialization JavaScript for the menu -->
-                               <script type="text/javascript">
-                                       DTM_array["' . $id . '"] = new Array();
-                                       ' . implode('', $JSinit) . '
-                                       ' . ($toggle <= 0 ? 'DTM_activate("' . $id . '", top.DTM_currentTabs["' . $id . '"]?top.DTM_currentTabs["' . $id . '"]:' . (int)$defaultTabIndex . ', 0);' : '') . '
-                               </script>
-
-                               ';
+                               <div class="typo3-dyntabmenu-divs' . ($foldout ? '-foldout' : '') . '">' . implode('', $divs) . '</div>';
                        }
                }
                return $content;
@@ -1667,11 +1656,10 @@ function jumpToUrl(URL) {
         * Creates the id for dynTabMenus.
         *
         * @param string $identString Identification string. This should be unique for every instance of a dynamic menu!
-        * @return string The id with a short MD5 of $identString and prefixed "DTM-", like "DTM-2e8791854a
+        * @return string The id with a short MD5 of $identString and prefixed "DTM-", like "DTM-2e8791854a"
         */
        public function getDynTabMenuId($identString) {
-               $id = 'DTM-' . GeneralUtility::shortMD5($identString);
-               return $id;
+               return 'DTM-' . GeneralUtility::shortMD5($identString);
        }
 
        /**
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Plugins/TabMenuPlugin.js b/typo3/sysext/backend/Resources/Public/JavaScript/Plugins/TabMenuPlugin.js
new file mode 100644 (file)
index 0000000..69c0024
--- /dev/null
@@ -0,0 +1,143 @@
+/**
+ * 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 this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * provides a jQuery plugin called "tabMenu" that is hooked on to each
+ * item of the element of the menu
+ */
+define('TYPO3/CMS/Backend/Plugins/TabMenuPlugin', ['jquery'], function($) {
+
+       // constructor method
+       // element can be a tab (not the tab content)
+       var TabMenuPlugin = function(element, options) {
+               me = this;
+               me.$element = $(element);
+
+               // if no options given, check if one of the parents (menuElement) has the TabMenuPlugin
+               if (undefined === options) {
+                       var $parent = me.$element;
+                       do {
+                               if ($parent.data('TabMenuPlugin')) {
+                                       options = $parent.data('TabMenuPlugin').options;
+                                       break;
+                               }
+                               $parent = $parent.parent();
+                       }
+                       while ($parent.length > 0);
+               }
+               // merging options
+               me.options = $.extend({}, TabMenuPlugin.defaults, options);
+               me.detectContainerElement();
+       };
+
+       // methods applied to each TabMenu item
+       TabMenuPlugin.prototype = {
+               // used for each menu container item, so that it initializes the events
+               // for its tabs
+               initialize: function() {
+                       var $activeItem;
+
+                       // show first tab of this menu, or the last active stored in the local storage
+                       if (localStorage && localStorage.getItem('TabMenuPlugin-ActiveItem-' + me.containerId)) {
+                               $activeItem = $(localStorage.getItem('TabMenuPlugin-ActiveItem-' + me.containerId));
+                               $activeItem = $activeItem.find(me.options.tabSelector).first();
+                       }
+                       if (!$activeItem || $activeItem.length == 0) {
+                               $activeItem = me.$containerElement.find(me.options.tabSelector).first();
+                       }
+                       if ($activeItem.length > 0) {
+                               $activeItem.tabMenu('toggle');
+                       }
+                       me._initEvents();
+               },
+
+               // detect the menu element (if the plugin is called on a tab label, find the menu item)
+               detectContainerElement: function() {
+                       me.$containerElement = me.$element.closest(me.options.tabMenuContainerSelector);
+                       me.containerId = me.$containerElement.prop('id');
+               },
+
+               // initialize events
+               _initEvents: function() {
+                       // events binding to toggle the tab menu on clicking the head
+                       $(me.options.tabSelector, me.$containerElement).on('click', function(evt) {
+                               evt.preventDefault();
+                               $(this).tabMenu('toggle');
+                       });
+               },
+
+               // called on a tab item, that shows the container target
+               // and disables the other container targets on the same level
+               toggle: function() {
+                       var
+                               $activeItem = this.$element,
+                               $parent = $activeItem.closest('li'),
+                               // get DOM id of the target tab container
+                               $target = $($activeItem.attr('href'));
+
+                       // trigger jQuery hook: "show"
+                       $activeItem.trigger({
+                               type: 'show',
+                               relatedTarget: $target
+                       });
+
+                       // update tab label class, and the siblings
+                       me.$containerElement.find('li').not($parent).removeClass(me.options.activeClass);
+                       $parent.addClass(me.options.activeClass);
+
+                       // save the change in the local storage
+                       if (localStorage && $parent.prop('id')) {
+                               localStorage.setItem('TabMenuPlugin-ActiveItem-' + me.containerId, '#' + $parent.prop('id'));
+                       }
+
+                       // display target tab content
+                       $target.show().siblings().hide();
+
+                       // trigger jQuery hook "shown"
+                       $activeItem.trigger({
+                               type: 'shown',
+                               relatedTarget: $target
+                       });
+               }
+       };
+
+       // default options
+       TabMenuPlugin.defaults = {
+               // the selector for the tab label, should contain the content tab as href=""
+               tabSelector: '[data-toggle="TabMenu"]',
+               // the container selector, contains all information about the tabs
+               tabMenuContainerSelector: '.typo3-dyntabmenu-tabs',
+               // the class that the tab selector gets
+               activeClass: 'active'
+       };
+
+       // register the jQuery plugin "as $('myContainer').tabMenu()"
+       $.fn.tabMenu = function(action, options) {
+               return this.each(function() {
+                       var $this = $(this),
+                               data = $this.data('TabMenuPlugin');
+
+                       // only apply the tabmenu to an item that does not have the tabmenu initialized yet
+                       if (!data) {
+                               $this.data('TabMenuPlugin', (data = new TabMenuPlugin(this, options)));
+                       }
+
+                       // option is an action to call sth directly
+                       if (typeof action == 'string') {
+                               data[action]();
+                       }
+               });
+       };
+
+       return TabMenuPlugin;
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/TabMenuSetup.js b/typo3/sysext/backend/Resources/Public/JavaScript/TabMenuSetup.js
new file mode 100644 (file)
index 0000000..d2c3a31
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * 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 this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+/**
+ * JavaScript RequireJS module called "TYPO3/CMS/Backend/TabMenuSetup"
+ *
+ * requires the tab menu jQuery plugin that does the logic
+ * and calls the plugin for each menu (.typo3-dyntabmenu items).
+ *
+ * After that, does the usual RequireJS logic and returns the object
+ */
+define('TYPO3/CMS/Backend/TabMenuSetup', ['jquery', 'TYPO3/CMS/Backend/Plugins/TabMenuPlugin'], function($) {
+
+       /**
+        * part 1: The main module of this file
+        * initialize the TabMenu by applying the jQuery plugin
+        */
+       var TabMenu = {
+               options: {
+                       tabSelector: '[data-toggle="TabMenu"]',
+                       tabMenuContainerSelector : '.typo3-dyntabmenu-tabs'
+               },
+               initialize: function() {
+                       var me = this;
+
+                       // initialize all tabMenus that are available on dom ready
+                       $(document).ready(function() {
+                               $(me.options.tabMenuContainerSelector).tabMenu('initialize', me.options);
+                       });
+               }
+       };
+
+       /**
+        * part 2: initialize the RequireJS module, require possible post-initialize hooks,
+        * and return the main object
+        */
+       var initialize = function(options) {
+               TabMenu.initialize();
+
+               // load required modules to hook in the post initialize function
+               if (undefined !== TYPO3.settings && undefined !== TYPO3.settings.RequireJS && undefined !== TYPO3.settings.RequireJS.PostInitializationModules && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/TabMenuSetup']) {
+                       $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/TabMenuSetup'], function(pos, moduleName) {
+                               require([moduleName]);
+                       });
+               }
+
+               // return the object in the global space
+               return TabMenu;
+       };
+
+       // call the main initialize function and execute the hooks
+       return initialize();
+});
index 4269699..95a6029 100644 (file)
@@ -13,6 +13,7 @@
 
 /**
  * javascript functions regarding the "dyntabmenu" used throughout the TYPO3 backend
+ * @deprecated with TYPO3 CMS 7, will be removed with CMS 8
  */
 
 var DTM_array = DTM_array || [];