-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlayout.src.js
504 lines (382 loc) · 12.9 KB
/
layout.src.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
/*
Copyright (c) Marcel Greter 2012 - OCBNET Layouter 1.0.0
This plugin available for use in all personal or commercial projects under both MIT and GPL licenses.
The Layouter takes care of widgets that have aspect ratios.
These Widgets can run into problem when the scrollbar appears.
This lib does the same as any (tested) browser does when displaying
images with an aspect ratio and a flexible width. When the vertical
scrollbar appears due to the widget/image beeing to tall, the width
of the widget would be decreased which in turn decreases the height.
This then can make the scrollbar to disapear again, which in turn
would increase the width and height of the widget again. To solve
this endless loop, we force a vertical scrollbar to be shown.
This fixes layout and scrollbar problems like these:
http://stackoverflow.com/questions/6818096
*/
/* @@@@@@@@@@ STATIC CLASS @@@@@@@@@@ */
// create private scope
(function (jQuery)
{
// jquery win and body object
// body will be set when the first
// widget is added to the collection
var win = jQuery(window), body = null;
// frames per second to layout
// only needed when not in vsync mode
var fps = 60;
// store scheduled timeout
var scheduled;
// widgets without parent
var roots = jQuery();
// widgets to be layouted
var widgets = jQuery();
// old body overflow style
var overflow, overflow_x, overflow_y;
// get the user agent string in lowercase
// copy feature from jquery migrate plugin
// this was included in jquery before v1.9
var ua = navigator.userAgent.toLowerCase();
// only match for ie and mozilla so far
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
/(msie) ([\w.]+)/.exec( ua ) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
[];
// get splitted information about the user agent
var browser = match[ 1 ] || '', version = match[ 2 ] || '0';
// defer the resize event and filter multiple calls
// this is a bugfix for ie 8 where the resize event may is
// triggered multiple times when scrollbars appear/disappear
var vsync = ! browser == 'msie' || parseInt(version, 10) != 8;
// get firefox mode on startup / initialization
// firefox will show both scrollbars when the layout
// does not 'fit' perfectly. All other browsers will
// only show the scrollbar in the direction needed.
var firefox_overflow = browser == 'mozilla';
// Firefox regressed at some point to this
var use_overflow_only = firefox_overflow;
// use requestAnimationFrame to defer functions
// this seems to work quite well, so include it
var setDefered = window.requestAnimationFrame,
clearDefered = window.cancelAnimationFrame ||
window.cancelRequestAnimationFrame;
// search for requestAnimationFrame by vendor
var vendors = ['ms', 'moz', 'webkit', 'o'];
// loop vendors until the request animation frame function is found
for(var x = 0; x < vendors.length && !setDefered; ++x)
{
setDefered = window[vendors[x]+'RequestAnimationFrame'];
clearDefered = window[vendors[x]+'CancelAnimationFrame'] ||
window[vendors[x]+'CancelRequestAnimationFrame'];
}
// create function to take out delay argument (returns identifier)
if (setDefered) var callDefered = function (cb) { return setDefered(cb); };
// use timeouts as a fallback
if (!callDefered) callDefered = window.setTimeout;
if (!clearDefered) clearDefered = window.clearTimeout;
// remember default functions
var defCallDefered = callDefered;
var defClearDefered = clearDefered;
// static local function
// call function on all widgets
function exec(fn, data, widgets)
{
// loop all widgets in order of registration
for(var i = 0, l = widgets.length; i < l; i++)
{
// skip any disabled widgets
if (widgets[i].disabled) continue;
// call method in widget context
if (jQuery.isFunction(widgets[i][fn]))
{ widgets[i][fn].call(widgets[i], data); }
}
}
// EO exec
// static local function
// call function on all widgets
function layout(data, widgets)
{
// first call pre on all widgets
exec('preLayout', data, widgets);
// loop all widgets in order of registration
for(var i = 0, l = widgets.length; i < l; i++)
{
// skip any disabled widgets
if (widgets[i].disabled) continue;
// get childrens for widget from options
var children = widgets[i].layout.children;
// call layout for all childrens
if (children && children.length)
{ layout(data, children); }
}
// then call update on all widgets
exec('postLayout', data, widgets);
}
// EO layout
// static local function
// call function on all widgets
function finalize(data, widgets)
{
Manager.initialized = true;
// first call post on all widgets
exec('updateLayout', data, widgets);
// loop all widgets in order of registration
for(var i = 0, l = widgets.length; i < l; i++)
{
// skip any disabled widgets
if (widgets[i].disabled) continue;
// get childrens for widget from options
var children = widgets[i].layout.children;
// call finalize for all childrens
if (children && children.length)
{ finalize(data, children); }
}
}
// EO finalize
// static global function
// do the layout on all widgets
function Manager (force, widgets)
{
// shared data (assign flag)
var data = { force: force };
// get nodes to manage in this run
var nodes = widgets ? jQuery(widgets) : roots;
// restore the previous overflow style on the document body
// needed so our layout can trigger the scrollbar to appear/disapear
if (use_overflow_only)
{
if (overflow) { body.css('overflow', overflow); overflow = null; }
}
else {
if (overflow_y) { body.css('overflow-y', overflow_y); overflow_y = null; }
if (overflow_x) { body.css('overflow-x', overflow_x); overflow_x = null; }
}
// get the initial dimensions
var body_1st_x = win.innerWidth();
var body_1st_y = win.innerHeight();
// reflow layout
layout(data, nodes);
// get the dimensions afterwards
var body_2nd_x = win.innerWidth();
var body_2nd_y = win.innerHeight();
// check if layout triggered any scrollbar changes
if (body_1st_x != body_2nd_x || body_1st_y != body_2nd_y)
// if (body_1st_x > body_2nd_x || body_1st_y > body_2nd_y)
{
// reflow layout
layout(data, nodes);
// get the dimensions afterwards
var body_3rd_x = win.innerWidth();
var body_3rd_y = win.innerHeight();
if (body_2nd_x != body_3rd_x || body_2nd_y != body_3rd_y)
// if (body_2nd_x < body_3rd_x || body_2nd_y < body_3rd_y)
{
// helper function (dry)
var resetBodyScrollbars = function ()
{
if (use_overflow_only)
{
// check if we should force the horizontal scrollbar
if (firefox_overflow || body_2nd_y != body_3rd_y || body_2nd_x != body_3rd_x)
{
// store previous scollbar setting
overflow = body.css('overflow');
// reset to scroll if not hidden
if (overflow != 'hidden')
{ body.css('overflow', 'scroll'); }
}
}
else
{
// check if we should force the horizontal scrollbar
if (firefox_overflow || body_2nd_y != body_3rd_y)
{
// store previous scollbar setting
overflow_x = body.css('overflow-x');
// reset to scroll if not hidden
if (overflow_x != 'hidden')
{ body.css('overflow-x', 'scroll'); }
}
// check if we should force the vertical scrollbar
if (firefox_overflow || body_2nd_x != body_3rd_x)
{
// store previous scollbar setting
overflow_y = body.css('overflow-y');
// reset to scroll if not hidden
if (overflow_y != 'hidden')
{ body.css('overflow-y', 'scroll'); }
}
}
}
// reset to scrollbars if not hidden
if (Manager.initialized) resetBodyScrollbars();
// reflow layout
layout(data, nodes);
// reset to scrollbars if not hidden
if (!Manager.initialized) resetBodyScrollbars();
}
// EO if 2nd changed
}
// EO if 1st changed
// execute last (only once)
finalize(data, nodes);
};
// EO Manager
// expose ua info
Manager.ua = {
'browser': browser,
'version': version
};
// static global function
Manager.config = function (key, value)
{
// assign config option
switch (key)
{
case 'fps': fps = value; break;
case 'vsync': vsync = value; break;
case 'default':
callDefered = defCallDefered;
clearDefered = defClearDefered;
break;
case 'fallback':
callDefered = window.setTimeout;
clearDefered = window.clearTimeout;
break;
}
// reassign the resizer function
resizer = vsync ? function () { Manager(); } : deferer;
};
// EO config
// static global function
// schedule a layout call in delay ms
// normally we keep the current waiting timeout
// set reset if you want to reschedule the repaint
Manager.schedule = function (delay, reset)
{
// do not re-schedule, execute the first timeout.
// we want it to be called from time to time
if (!reset && scheduled) return;
// we should reset the scheduled callback
// this will enforce the delay to stay
if (scheduled) clearDefered(scheduled);
// schedule a layout execution
scheduled = callDefered(function()
{
// call layout
Manager();
// reset timer status
// we are ready for more
scheduled = null;
}, delay || 0);
}
// EO Manager.schedule
// EO Manager.defer
Manager.defer = function (fn, delay)
{
// delay is optional
if (typeof delay == 'undefined')
{ delay = 1000 / fps; }
// add scheduled function
return callDefered(fn, delay);
}
// EO Manager.defer
// EO Manager.undefer
Manager.undefer = function (scheduled)
{
// clear scheduled function
return clearDefered(scheduled);
}
// EO Manager.undefer
// static global function
// add a widget under our control
Manager.add = function (widget)
{
// assign the body object only once
if (!body) body = jQuery('BODY:first');
// extend/initialize layout options property
widget.layout = jQuery.extend({ children: [] }, widget.layout)
// check if widget has a parent with children
// add ourself to our parent's children array
if (widget.layout && widget.layout.parent)
{
if (!widget.layout.parent.layout.children)
{ widget.layout.parent.layout.children = []; }
widget.layout.parent.layout.children.push(widget);
}
// otherwise it's a root widget without parent
else { roots = roots.add(jQuery(widget)); }
// call start layout hook on widget
if (jQuery.isFunction(widget.startLayout))
{ widget.startLayout.call(widget); }
// jQueryfy input argument
widget = jQuery(widget);
// attach resize event to call resizer
if (widgets.length == 0 && widget.length > 0)
{ jQuery(window).bind('resize', resizer); }
// push instances to static array
widgets = widgets.add(widget)
// make static array a global
// Manager.widgets = widgets;
// Manager.roots = roots;
};
// EO Manager.add
// static global function
// add a widget under our control
Manager.del = function (widget)
{
// call stop layout hook on widget
if (jQuery.isFunction(widget.stopLayout))
{ widget.stopLayout.call(widget); }
// jQueryfy input argument
widget = jQuery(widget);
// remove from static arrays
widgets = widgets.not(widget)
roots = roots.not(widget);
// remove the resize handler when there are no widgets left
if (widgets.length == 0) jQuery(window).unbind('resize', resizer);
// make static array a global
// Manager.widgets = widgets;
// Manager.roots = roots;
};
// EO Manager.del
// make static array a global
// Manager.widgets = widgets;
// Maybe we should defer the resize event.
// This is needed to avoid a possible endless
// loop in internet explorer 8. This Browser
// can trigger resize events after scrollbars
// appear/disapear or on reflow of some element.
// IE 7 and below always show a scrollbar, so this
// problem does not seem to exist, otherwise we
// should also set those browsers to defer the resize.
// only enqueue one
var resizing = false;
// @@@ function: deferer @@@
var deferer = function ()
{
// only register one callback
if (resizing) return false;
// register a callback for next idle loop
resizing = callDefered(function()
{
// call layouter
Manager();
// reset the lock
resizing = false;
}, 1000 / fps)
}
// @@@ EO function: deferer @@@
// Set resizer to the desired function to execute.
// Set this on initialization as the decision is always
// based on information that must not change during runtime.
// Will be bound to resize event when first widget is added.
var resizer = vsync ? function () { Manager(); } : deferer;
// make sure our global namespace exists
// but do not reset it if already present
if (typeof OCBNET == 'undefined') window.OCBNET = {};
// assign class to global namespace
OCBNET.Layout = Manager;
})(jQuery);
// EO private scope