Fixed emulated dropdowns on mobile
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Ui / Mobile.js
1 /**
2 * Modifies the interface to provide a better usability for mobile devices.
3 *
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 WoltLabSuite/Core/Ui/Mobile
8 */
9 define(
10 [ 'Core', 'Environment', 'EventHandler', 'Language', 'List', 'Dom/ChangeListener', 'Dom/Traverse', 'Ui/CloseOverlay', 'Ui/Screen', './Page/Menu/Main', './Page/Menu/User'],
11 function(Core, Environment, EventHandler, Language, List, DomChangeListener, DomTraverse, UiCloseOverlay, UiScreen, UiPageMenuMain, UiPageMenuUser)
12 {
13 "use strict";
14
15 var _buttonGroupNavigations = elByClass('buttonGroupNavigation');
16 var _enabled = false;
17 var _knownMessages = new List();
18 var _main = null;
19 var _messages = elByClass('message');
20 var _options = {};
21 var _pageMenuMain = null;
22 var _pageMenuUser = null;
23 var _messageGroups = null;
24
25 /**
26 * @exports WoltLabSuite/Core/Ui/Mobile
27 */
28 return {
29 /**
30 * Initializes the mobile UI.
31 *
32 * @param {Object=} options initialization options
33 */
34 setup: function(options) {
35 _options = Core.extend({
36 enableMobileMenu: true
37 }, options);
38
39 _main = elById('main');
40
41 if (Environment.touch()) {
42 document.documentElement.classList.add('touch');
43 }
44
45 if (Environment.platform() !== 'desktop') {
46 document.documentElement.classList.add('mobile');
47 }
48
49 var messageGroupList = elBySel('.messageGroupList');
50 if (messageGroupList) _messageGroups = elByClass('messageGroup', messageGroupList);
51
52 UiScreen.on('screen-md-down', {
53 match: this.enable.bind(this),
54 unmatch: this.disable.bind(this),
55 setup: this._init.bind(this)
56 });
57 },
58
59 /**
60 * Enables the mobile UI.
61 */
62 enable: function() {
63 _enabled = true;
64
65 if (_options.enableMobileMenu) {
66 _pageMenuMain.enable();
67 _pageMenuUser.enable();
68 }
69
70 if (_messageGroups) this.rebuildShadow(_messageGroups, '.messageGroupLink');
71 },
72
73 /**
74 * Disables the mobile UI.
75 */
76 disable: function() {
77 _enabled = false;
78
79 if (_options.enableMobileMenu) {
80 _pageMenuMain.disable();
81 _pageMenuUser.disable();
82 }
83
84 if (_messageGroups) this.removeShadow(_messageGroups);
85 },
86
87 _init: function() {
88 _enabled = true;
89
90 this._initSearchBar();
91 this._initButtonGroupNavigation();
92 this._initMessages();
93 this._initMobileMenu();
94
95 UiCloseOverlay.add('WoltLabSuite/Core/Ui/Mobile', this._closeAllMenus.bind(this));
96 DomChangeListener.add('WoltLabSuite/Core/Ui/Mobile', this._initButtonGroupNavigation.bind(this));
97
98 if (_messageGroups) this.rebuildShadow(_messageGroups, '.messageGroupLink');
99 },
100
101 _initSearchBar: function() {
102 var _searchBar = elById('pageHeaderSearch');
103 var _searchInput = elById('pageHeaderSearchInput');
104
105 EventHandler.add('com.woltlab.wcf.MainMenuMobile', 'more', function(data) {
106 if (data.identifier === 'com.woltlab.wcf.search') {
107 _searchBar.style.setProperty('top', elById('pageHeader').offsetHeight + 'px', '');
108 _searchBar.classList.add('open');
109 _searchInput.focus();
110
111 data.handler.close(true);
112 }
113 });
114
115 _main.addEventListener(WCF_CLICK_EVENT, function() { _searchBar.classList.remove('open'); });
116 },
117
118 _initButtonGroupNavigation: function() {
119 for (var i = 0, length = _buttonGroupNavigations.length; i < length; i++) {
120 var navigation = _buttonGroupNavigations[i];
121
122 if (navigation.classList.contains('jsMobileButtonGroupNavigation')) continue;
123 else navigation.classList.add('jsMobileButtonGroupNavigation');
124
125 navigation.parentNode.classList.add('hasMobileNavigation');
126
127 var button = elCreate('a');
128 button.className = 'dropdownLabel';
129
130 var span = elCreate('span');
131 span.className = 'icon icon24 fa-ellipsis-v';
132 button.appendChild(span);
133
134 var list = elBySel('.buttonList', navigation);
135 list.addEventListener(WCF_CLICK_EVENT, function(event) {
136 event.stopPropagation();
137 });
138
139 (function(navigation, button) {
140 button.addEventListener(WCF_CLICK_EVENT, function(event) {
141 event.preventDefault();
142 event.stopPropagation();
143
144 navigation.classList.toggle('open');
145 });
146 })(navigation, button);
147
148 navigation.insertBefore(button, navigation.firstChild);
149 }
150 },
151
152 _initMessages: function() {
153 Array.prototype.forEach.call(_messages, function(message) {
154 if (_knownMessages.has(message)) {
155 return;
156 }
157
158 var navigation = elBySel('.jsMobileNavigation', message);
159 var quickOptions = elBySel('.messageQuickOptions', message);
160
161 if (quickOptions) {
162 quickOptions.addEventListener(WCF_CLICK_EVENT, function (event) {
163 if (_enabled) {
164 event.preventDefault();
165 event.stopPropagation();
166
167 navigation.classList.toggle('open');
168 }
169 });
170 }
171 if (navigation) {
172 navigation.addEventListener(WCF_CLICK_EVENT, function(event) {
173 event.stopPropagation();
174
175 // mimic dropdown behavior
176 window.setTimeout(function () {
177 navigation.classList.remove('open');
178 }, 10);
179 });
180 }
181
182 _knownMessages.add(message);
183 });
184 },
185
186 _initMobileMenu: function() {
187 if (_options.enableMobileMenu) {
188 _pageMenuMain = new UiPageMenuMain();
189 _pageMenuUser = new UiPageMenuUser();
190 }
191
192 elBySelAll('.boxMenu', null, function(boxMenu) {
193 boxMenu.addEventListener(WCF_CLICK_EVENT, function(event) {
194 event.stopPropagation();
195
196 if (event.target === boxMenu) {
197 event.preventDefault();
198
199 boxMenu.classList.add('open');
200 }
201 });
202 });
203 },
204
205 _closeAllMenus: function() {
206 elBySelAll('.jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open, .boxMenu.open', null, function (menu) {
207 menu.classList.remove('open');
208 });
209 },
210
211 rebuildShadow: function(elements, linkSelector) {
212 var element, parent, shadow;
213 for (var i = 0, length = elements.length; i < length; i++) {
214 element = elements[i];
215 parent = element.parentNode;
216
217 shadow = DomTraverse.childByClass(parent, 'mobileLinkShadow');
218 if (shadow === null) {
219 if (elBySel(linkSelector, element).href) {
220 shadow = elCreate('a');
221 shadow.className = 'mobileLinkShadow';
222 shadow.href = elBySel(linkSelector, element).href;
223
224 parent.appendChild(shadow);
225 parent.classList.add('mobileLinkShadowContainer');
226 }
227 }
228 }
229 },
230
231 removeShadow: function(elements) {
232 var element, parent, shadow;
233 for (var i = 0, length = elements.length; i < length; i++) {
234 element = elements[i];
235 parent = element.parentNode;
236
237 if (parent.classList.contains('mobileLinkShadowContainer')) {
238 shadow = DomTraverse.childByClass(parent, 'mobileLinkShadow');
239 if (shadow !== null) {
240 elRemove(shadow);
241 }
242
243 parent.classList.remove('mobileLinkShadowContainer');
244 }
245 }
246 }
247 };
248 });