1 /** |
|
2 * FloatPanel.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * This class creates a floating panel. |
|
13 * |
|
14 * @-x-less FloatPanel.less |
|
15 * @class tinymce.ui.FloatPanel |
|
16 * @extends tinymce.ui.Panel |
|
17 * @mixes tinymce.ui.Movable |
|
18 * @mixes tinymce.ui.Resizable |
|
19 */ |
|
20 define("tinymce/ui/FloatPanel", [ |
|
21 "tinymce/ui/Panel", |
|
22 "tinymce/ui/Movable", |
|
23 "tinymce/ui/Resizable", |
|
24 "tinymce/ui/DomUtils" |
|
25 ], function(Panel, Movable, Resizable, DomUtils) { |
|
26 "use strict"; |
|
27 |
|
28 var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = []; |
|
29 var zOrder = [], hasModal; |
|
30 |
|
31 function bindDocumentClickHandler() { |
|
32 function isChildOf(ctrl, parent) { |
|
33 while (ctrl) { |
|
34 if (ctrl == parent) { |
|
35 return true; |
|
36 } |
|
37 |
|
38 ctrl = ctrl.parent(); |
|
39 } |
|
40 } |
|
41 |
|
42 if (!documentClickHandler) { |
|
43 documentClickHandler = function(e) { |
|
44 // Gecko fires click event and in the wrong order on Mac so lets normalize |
|
45 if (e.button == 2) { |
|
46 return; |
|
47 } |
|
48 |
|
49 // Hide any float panel when a click is out side that float panel and the |
|
50 // float panels direct parent for example a click on a menu button |
|
51 var i = visiblePanels.length; |
|
52 while (i--) { |
|
53 var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target); |
|
54 |
|
55 if (panel.settings.autohide) { |
|
56 if (clickCtrl) { |
|
57 if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) { |
|
58 continue; |
|
59 } |
|
60 } |
|
61 |
|
62 e = panel.fire('autohide', {target: e.target}); |
|
63 if (!e.isDefaultPrevented()) { |
|
64 panel.hide(); |
|
65 } |
|
66 } |
|
67 } |
|
68 }; |
|
69 |
|
70 DomUtils.on(document, 'click', documentClickHandler); |
|
71 } |
|
72 } |
|
73 |
|
74 function bindDocumentScrollHandler() { |
|
75 if (!documentScrollHandler) { |
|
76 documentScrollHandler = function() { |
|
77 var i; |
|
78 |
|
79 i = visiblePanels.length; |
|
80 while (i--) { |
|
81 repositionPanel(visiblePanels[i]); |
|
82 } |
|
83 }; |
|
84 |
|
85 DomUtils.on(window, 'scroll', documentScrollHandler); |
|
86 } |
|
87 } |
|
88 |
|
89 function bindWindowResizeHandler() { |
|
90 if (!windowResizeHandler) { |
|
91 var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight; |
|
92 |
|
93 windowResizeHandler = function() { |
|
94 // Workaround for #7065 IE 7 fires resize events event though the window wasn't resized |
|
95 if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) { |
|
96 clientWidth = docElm.clientWidth; |
|
97 clientHeight = docElm.clientHeight; |
|
98 FloatPanel.hideAll(); |
|
99 } |
|
100 }; |
|
101 |
|
102 DomUtils.on(window, 'resize', windowResizeHandler); |
|
103 } |
|
104 } |
|
105 |
|
106 /** |
|
107 * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will |
|
108 * also reposition all child panels of the current panel. |
|
109 */ |
|
110 function repositionPanel(panel) { |
|
111 var scrollY = DomUtils.getViewPort().y; |
|
112 |
|
113 function toggleFixedChildPanels(fixed, deltaY) { |
|
114 var parent; |
|
115 |
|
116 for (var i = 0; i < visiblePanels.length; i++) { |
|
117 if (visiblePanels[i] != panel) { |
|
118 parent = visiblePanels[i].parent(); |
|
119 |
|
120 while (parent && (parent = parent.parent())) { |
|
121 if (parent == panel) { |
|
122 visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint(); |
|
123 } |
|
124 } |
|
125 } |
|
126 } |
|
127 } |
|
128 |
|
129 if (panel.settings.autofix) { |
|
130 if (!panel._fixed) { |
|
131 panel._autoFixY = panel.layoutRect().y; |
|
132 |
|
133 if (panel._autoFixY < scrollY) { |
|
134 panel.fixed(true).layoutRect({y: 0}).repaint(); |
|
135 toggleFixedChildPanels(true, scrollY - panel._autoFixY); |
|
136 } |
|
137 } else { |
|
138 if (panel._autoFixY > scrollY) { |
|
139 panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint(); |
|
140 toggleFixedChildPanels(false, panel._autoFixY - scrollY); |
|
141 } |
|
142 } |
|
143 } |
|
144 } |
|
145 |
|
146 function addRemove(add, ctrl) { |
|
147 var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal; |
|
148 |
|
149 if (add) { |
|
150 zOrder.push(ctrl); |
|
151 } else { |
|
152 i = zOrder.length; |
|
153 |
|
154 while (i--) { |
|
155 if (zOrder[i] === ctrl) { |
|
156 zOrder.splice(i, 1); |
|
157 } |
|
158 } |
|
159 } |
|
160 |
|
161 if (zOrder.length) { |
|
162 for (i = 0; i < zOrder.length; i++) { |
|
163 if (zOrder[i].modal) { |
|
164 zIndex++; |
|
165 topModal = zOrder[i]; |
|
166 } |
|
167 |
|
168 zOrder[i].getEl().style.zIndex = zIndex; |
|
169 zOrder[i].zIndex = zIndex; |
|
170 zIndex++; |
|
171 } |
|
172 } |
|
173 |
|
174 var modalBlockEl = document.getElementById(ctrl.classPrefix + 'modal-block'); |
|
175 |
|
176 if (topModal) { |
|
177 DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1); |
|
178 } else if (modalBlockEl) { |
|
179 modalBlockEl.parentNode.removeChild(modalBlockEl); |
|
180 hasModal = false; |
|
181 } |
|
182 |
|
183 FloatPanel.currentZIndex = zIndex; |
|
184 } |
|
185 |
|
186 var FloatPanel = Panel.extend({ |
|
187 Mixins: [Movable, Resizable], |
|
188 |
|
189 /** |
|
190 * Constructs a new control instance with the specified settings. |
|
191 * |
|
192 * @constructor |
|
193 * @param {Object} settings Name/value object with settings. |
|
194 * @setting {Boolean} autohide Automatically hide the panel. |
|
195 */ |
|
196 init: function(settings) { |
|
197 var self = this; |
|
198 |
|
199 self._super(settings); |
|
200 self._eventsRoot = self; |
|
201 |
|
202 self.addClass('floatpanel'); |
|
203 |
|
204 // Hide floatpanes on click out side the root button |
|
205 if (settings.autohide) { |
|
206 bindDocumentClickHandler(); |
|
207 bindWindowResizeHandler(); |
|
208 visiblePanels.push(self); |
|
209 } |
|
210 |
|
211 if (settings.autofix) { |
|
212 bindDocumentScrollHandler(); |
|
213 |
|
214 self.on('move', function() { |
|
215 repositionPanel(this); |
|
216 }); |
|
217 } |
|
218 |
|
219 self.on('postrender show', function(e) { |
|
220 if (e.control == self) { |
|
221 var modalBlockEl, prefix = self.classPrefix; |
|
222 |
|
223 if (self.modal && !hasModal) { |
|
224 modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' + |
|
225 prefix + 'reset ' + prefix + 'fade"></div>'); |
|
226 modalBlockEl = modalBlockEl.firstChild; |
|
227 |
|
228 self.getContainerElm().appendChild(modalBlockEl); |
|
229 |
|
230 setTimeout(function() { |
|
231 DomUtils.addClass(modalBlockEl, prefix + 'in'); |
|
232 DomUtils.addClass(self.getEl(), prefix + 'in'); |
|
233 }, 0); |
|
234 |
|
235 hasModal = true; |
|
236 } |
|
237 |
|
238 addRemove(true, self); |
|
239 } |
|
240 }); |
|
241 |
|
242 self.on('show', function() { |
|
243 self.parents().each(function(ctrl) { |
|
244 if (ctrl._fixed) { |
|
245 self.fixed(true); |
|
246 return false; |
|
247 } |
|
248 }); |
|
249 }); |
|
250 |
|
251 if (settings.popover) { |
|
252 self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>'; |
|
253 self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start'); |
|
254 } |
|
255 }, |
|
256 |
|
257 fixed: function(state) { |
|
258 var self = this; |
|
259 |
|
260 if (self._fixed != state) { |
|
261 if (self._rendered) { |
|
262 var viewport = DomUtils.getViewPort(); |
|
263 |
|
264 if (state) { |
|
265 self.layoutRect().y -= viewport.y; |
|
266 } else { |
|
267 self.layoutRect().y += viewport.y; |
|
268 } |
|
269 } |
|
270 |
|
271 self.toggleClass('fixed', state); |
|
272 self._fixed = state; |
|
273 } |
|
274 |
|
275 return self; |
|
276 }, |
|
277 |
|
278 /** |
|
279 * Shows the current float panel. |
|
280 * |
|
281 * @method show |
|
282 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. |
|
283 */ |
|
284 show: function() { |
|
285 var self = this, i, state = self._super(); |
|
286 |
|
287 i = visiblePanels.length; |
|
288 while (i--) { |
|
289 if (visiblePanels[i] === self) { |
|
290 break; |
|
291 } |
|
292 } |
|
293 |
|
294 if (i === -1) { |
|
295 visiblePanels.push(self); |
|
296 } |
|
297 |
|
298 return state; |
|
299 }, |
|
300 |
|
301 /** |
|
302 * Hides the current float panel. |
|
303 * |
|
304 * @method hide |
|
305 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. |
|
306 */ |
|
307 hide: function() { |
|
308 removeVisiblePanel(this); |
|
309 addRemove(false, this); |
|
310 |
|
311 return this._super(); |
|
312 }, |
|
313 |
|
314 /** |
|
315 * Hide all visible float panels with he autohide setting enabled. This is for |
|
316 * manually hiding floating menus or panels. |
|
317 * |
|
318 * @method hideAll |
|
319 */ |
|
320 hideAll: function() { |
|
321 FloatPanel.hideAll(); |
|
322 }, |
|
323 |
|
324 /** |
|
325 * Closes the float panel. This will remove the float panel from page and fire the close event. |
|
326 * |
|
327 * @method close |
|
328 */ |
|
329 close: function() { |
|
330 var self = this; |
|
331 |
|
332 if (!self.fire('close').isDefaultPrevented()) { |
|
333 self.remove(); |
|
334 addRemove(false, self); |
|
335 } |
|
336 |
|
337 return self; |
|
338 }, |
|
339 |
|
340 /** |
|
341 * Removes the float panel from page. |
|
342 * |
|
343 * @method remove |
|
344 */ |
|
345 remove: function() { |
|
346 removeVisiblePanel(this); |
|
347 this._super(); |
|
348 }, |
|
349 |
|
350 postRender: function() { |
|
351 var self = this; |
|
352 |
|
353 if (self.settings.bodyRole) { |
|
354 this.getEl('body').setAttribute('role', self.settings.bodyRole); |
|
355 } |
|
356 |
|
357 return self._super(); |
|
358 } |
|
359 }); |
|
360 |
|
361 /** |
|
362 * Hide all visible float panels with he autohide setting enabled. This is for |
|
363 * manually hiding floating menus or panels. |
|
364 * |
|
365 * @static |
|
366 * @method hideAll |
|
367 */ |
|
368 FloatPanel.hideAll = function() { |
|
369 var i = visiblePanels.length; |
|
370 |
|
371 while (i--) { |
|
372 var panel = visiblePanels[i]; |
|
373 |
|
374 if (panel && panel.settings.autohide) { |
|
375 panel.hide(); |
|
376 visiblePanels.splice(i, 1); |
|
377 } |
|
378 } |
|
379 }; |
|
380 |
|
381 function removeVisiblePanel(panel) { |
|
382 var i; |
|
383 |
|
384 i = visiblePanels.length; |
|
385 while (i--) { |
|
386 if (visiblePanels[i] === panel) { |
|
387 visiblePanels.splice(i, 1); |
|
388 } |
|
389 } |
|
390 |
|
391 i = zOrder.length; |
|
392 while (i--) { |
|
393 if (zOrder[i] === panel) { |
|
394 zOrder.splice(i, 1); |
|
395 } |
|
396 } |
|
397 } |
|
398 |
|
399 return FloatPanel; |
|
400 }); |
|