cab400d7930ba97932fdddf6b23e3b96463dfaa6
2 * Provides a touch-friendly fullscreen menu.
4 * @author Alexander Ebert
5 * @copyright 2001-2016 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7 * @module WoltLab/WCF/Ui/Page/Menu/Abstract
9 define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Dom/Util', 'Ui/Screen'], function(Environment
, EventHandler
, ObjectMap
, DomTraverse
, DomUtil
, UiScreen
) {
12 var _pageContainer
= elById('pageContainer');
15 * @param {string} eventIdentifier event namespace
16 * @param {string} elementId menu element id
17 * @param {string} buttonSelector CSS selector for toggle button
20 function UiPageMenuAbstract(eventIdentifier
, elementId
, buttonSelector
) { this.init(eventIdentifier
, elementId
, buttonSelector
); }
21 UiPageMenuAbstract
.prototype = {
23 * Initializes a touch-friendly fullscreen menu.
25 * @param {string} eventIdentifier event namespace
26 * @param {string} elementId menu element id
27 * @param {string} buttonSelector CSS selector for toggle button
29 init: function(eventIdentifier
, elementId
, buttonSelector
) {
30 this._activeList
= [];
33 this._eventIdentifier
= eventIdentifier
;
34 this._items
= new ObjectMap();
35 this._menu
= elById(elementId
);
36 this._removeActiveList
= false;
38 var callbackOpen
= this.open
.bind(this);
39 var button
= elBySel(buttonSelector
);
40 button
.addEventListener(WCF_CLICK_EVENT
, callbackOpen
);
45 EventHandler
.add(this._eventIdentifier
, 'open', callbackOpen
);
46 EventHandler
.add(this._eventIdentifier
, 'close', this.close
.bind(this));
48 var itemList
, itemLists
= elByClass('menuOverlayItemList', this._menu
);
49 this._menu
.addEventListener('animationend', (function() {
50 if (!this._menu
.classList
.contains('open')) {
51 for (var i
= 0, length
= itemLists
.length
; i
< length
; i
++) {
52 itemList
= itemLists
[i
];
54 // force the main list to be displayed
55 itemList
.classList
.remove('active');
56 itemList
.classList
.remove('hidden');
61 this._menu
.children
[0].addEventListener('transitionend', (function() {
62 this._menu
.classList
.add('allowScroll');
64 if (this._removeActiveList
) {
65 this._removeActiveList
= false;
67 var list
= this._activeList
.pop();
69 list
.classList
.remove('activeList');
74 var backdrop
= elCreate('div');
75 backdrop
.className
= 'menuOverlayMobileBackdrop';
76 backdrop
.addEventListener(WCF_CLICK_EVENT
, this.close
.bind(this));
78 DomUtil
.insertAfter(backdrop
, this._menu
);
84 * @param {Event} event event object
86 open: function(event
) {
91 if (event
instanceof Event
) {
92 event
.preventDefault();
95 this._menu
.classList
.add('open');
96 this._menu
.classList
.add('allowScroll');
97 this._menu
.children
[0].classList
.add('activeList');
99 UiScreen
.scrollDisable();
101 _pageContainer
.classList
.add('menuOverlay-' + this._menu
.id
);
103 document
.documentElement
.classList
.add('pageOverlayActive');
109 * @param {(Event|boolean)} event event object or boolean true to force close the menu
111 close: function(event
) {
112 if (event
instanceof Event
) {
113 event
.preventDefault();
116 if (this._menu
.classList
.contains('open')) {
117 this._menu
.classList
.remove('open');
119 UiScreen
.scrollEnable();
121 _pageContainer
.classList
.remove('menuOverlay-' + this._menu
.id
);
123 document
.documentElement
.classList
.remove('pageOverlayActive');
128 * Enables the touch menu.
131 this._enabled
= true;
135 * Disables the touch menu.
137 disable: function() {
138 this._enabled
= false;
144 * Initializes all menu items.
148 _initItems: function() {
149 elBySelAll('.menuOverlayItemLink', this._menu
, this._initItem
.bind(this));
153 * Initializes a single menu item.
155 * @param {Element} item menu item
158 _initItem: function(item
) {
159 // check if it should contain a 'more' link w/ an external callback
160 var parent
= item
.parentNode
;
161 var more
= elData(parent
, 'more');
163 item
.addEventListener(WCF_CLICK_EVENT
, (function(event
) {
164 event
.preventDefault();
165 event
.stopPropagation();
167 EventHandler
.fire(this._eventIdentifier
, 'more', {
178 var itemList
= item
.nextElementSibling
, wrapper
;
179 if (itemList
=== null) {
183 // handle static items with an icon-type button next to it (acp menu)
184 if (itemList
.nodeName
!== 'OL' && itemList
.classList
.contains('menuOverlayItemLinkIcon')) {
186 wrapper
= elCreate('span');
187 wrapper
.className
= 'menuOverlayItemWrapper';
188 parent
.insertBefore(wrapper
, item
);
189 wrapper
.appendChild(item
);
191 while (wrapper
.nextElementSibling
) {
192 wrapper
.appendChild(wrapper
.nextElementSibling
);
198 var isLink
= (elAttr(item
, 'href') !== '#');
199 var parentItemList
= parent
.parentNode
;
200 var itemTitle
= elData(itemList
, 'title');
202 this._items
.set(item
, {
204 parentItemList
: parentItemList
207 if (itemTitle
=== '') {
208 itemTitle
= DomTraverse
.childByClass(item
, 'menuOverlayItemTitle').textContent
;
209 elData(itemList
, 'title', itemTitle
);
212 var callbackLink
= this._showItemList
.bind(this, item
);
214 wrapper
= elCreate('span');
215 wrapper
.className
= 'menuOverlayItemWrapper';
216 parent
.insertBefore(wrapper
, item
);
217 wrapper
.appendChild(item
);
219 var moreLink
= elCreate('a');
220 elAttr(moreLink
, 'href', '#');
221 moreLink
.className
= 'menuOverlayItemLinkIcon' + (item
.classList
.contains('active') ? ' active' : '');
222 moreLink
.innerHTML
= '<span class="icon icon24 fa-angle-right"></span>';
223 moreLink
.addEventListener(WCF_CLICK_EVENT
, callbackLink
);
224 wrapper
.appendChild(moreLink
);
227 item
.classList
.add('menuOverlayItemLinkMore');
228 item
.addEventListener(WCF_CLICK_EVENT
, callbackLink
);
231 var backLinkItem
= elCreate('li');
232 backLinkItem
.className
= 'menuOverlayHeader';
234 wrapper
= elCreate('span');
235 wrapper
.className
= 'menuOverlayItemWrapper';
237 var backLink
= elCreate('a');
238 elAttr(backLink
, 'href', '#');
239 backLink
.className
= 'menuOverlayItemLink menuOverlayBackLink';
240 backLink
.textContent
= elData(parentItemList
, 'title');
241 backLink
.addEventListener(WCF_CLICK_EVENT
, this._hideItemList
.bind(this, item
));
243 var closeLink
= elCreate('a');
244 elAttr(closeLink
, 'href', '#');
245 closeLink
.className
= 'menuOverlayItemLinkIcon';
246 closeLink
.innerHTML
= '<span class="icon icon24 fa-times"></span>';
247 closeLink
.addEventListener(WCF_CLICK_EVENT
, this.close
.bind(this));
249 wrapper
.appendChild(backLink
);
250 wrapper
.appendChild(closeLink
);
251 backLinkItem
.appendChild(wrapper
);
253 itemList
.insertBefore(backLinkItem
, itemList
.firstElementChild
);
255 if (!backLinkItem
.nextElementSibling
.classList
.contains('menuOverlayTitle')) {
256 var titleItem
= elCreate('li');
257 titleItem
.className
= 'menuOverlayTitle';
258 var title
= elCreate('span');
259 title
.textContent
= itemTitle
;
260 titleItem
.appendChild(title
);
262 itemList
.insertBefore(titleItem
, backLinkItem
.nextElementSibling
);
267 * Renders the menu item list header.
271 _initHeader: function() {
272 var listItem
= elCreate('li');
273 listItem
.className
= 'menuOverlayHeader';
275 var wrapper
= elCreate('span');
276 wrapper
.className
= 'menuOverlayItemWrapper';
277 listItem
.appendChild(wrapper
);
279 var logoWrapper
= elCreate('span');
280 logoWrapper
.className
= 'menuOverlayLogoWrapper';
281 wrapper
.appendChild(logoWrapper
);
283 var logo
= elCreate('span');
284 logo
.className
= 'menuOverlayLogo';
285 logo
.style
.setProperty('background-image', 'url("' + elData(this._menu
, 'page-logo') + '")', '');
286 logoWrapper
.appendChild(logo
);
288 var closeLink
= elCreate('a');
289 elAttr(closeLink
, 'href', '#');
290 closeLink
.className
= 'menuOverlayItemLinkIcon';
291 closeLink
.innerHTML
= '<span class="icon icon24 fa-times"></span>';
292 closeLink
.addEventListener(WCF_CLICK_EVENT
, this.close
.bind(this));
293 wrapper
.appendChild(closeLink
);
295 var list
= DomTraverse
.childByClass(this._menu
, 'menuOverlayItemList');
296 list
.insertBefore(listItem
, list
.firstElementChild
);
300 * Hides an item list, return to the parent item list.
302 * @param {Element} item menu item
303 * @param {Event} event event object
306 _hideItemList: function(item
, event
) {
307 if (event
instanceof Event
) {
308 event
.preventDefault();
311 this._menu
.classList
.remove('allowScroll');
312 this._removeActiveList
= true;
314 var data
= this._items
.get(item
);
315 data
.parentItemList
.classList
.remove('hidden');
317 this._updateDepth(false);
321 * Shows the child item list.
323 * @param {Element} item menu item
327 _showItemList: function(item
, event
) {
328 if (event
instanceof Event
) {
329 event
.preventDefault();
332 var data
= this._items
.get(item
);
334 var load
= elData(data
.itemList
, 'load');
336 if (!elDataBool(item
, 'loaded')) {
337 var icon
= event
.currentTarget
.firstElementChild
;
338 if (icon
.classList
.contains('fa-angle-right')) {
339 icon
.classList
.remove('fa-angle-right');
340 icon
.classList
.add('fa-spinner');
343 EventHandler
.fire(this._eventIdentifier
, 'load_' + load
);
349 this._menu
.classList
.remove('allowScroll');
351 data
.itemList
.classList
.add('activeList');
352 data
.parentItemList
.classList
.add('hidden');
354 this._activeList
.push(data
.itemList
);
356 this._updateDepth(true);
359 _updateDepth: function(increase
) {
360 this._depth
+= (increase
) ? 1 : -1;
362 this._menu
.children
[0].style
.setProperty('transform', 'translateX(' + (this._depth
* -100) + '%)', '')
366 return UiPageMenuAbstract
;