This static utility class abstracts the complex logic to layout all javascript widgets on a fluid / responsive html page correctly. It will handle the browser resize event for you and dispatchs layout callbacks for each registered widget.
Handling the layout by yourself is easy, you may say. The most common approach is to bind the resize event for every instance you create. When the event fires you measure the available width and would resize the widget accordingly. This will work in most situations, but has a very ugly race condition when the browser decides to add a scrollbar after you laid out your widget. You can see this behaviour in the "broken" demo page.
I realized quite soon that there is no "perfect" layout in this situation! But I didn't had a clue how to solve this problem until I realized that a browser should have the same problem when it has to layout a fluid image (width 100% and height auto). It seems quite obvious now that the solution is to force the scrollbar to be shown in this situation.
Multiple widgets on a page that re-layout themselve on resize will cause another race condition. Imagine the second widget increases it's height which would trigger a vertical scrollbar to appear. All other widgets would need to re-layout again. Maybe the first widget would get so small that the scrollbar would disappear again. So the second widget would need a re-layout again. You are basically begging for an endless loop here. It may look funky but certainly not the way you want it to.
You need a central Layout Manager to handle all your widgets. Each widget is laid out in two phases. First it reads and stores all values that are needed for the layout. In the second phase it will use these value to do the actual layout adjustments. The widget in the demo will first read the width of itself (preLayout) then setting the height to the same value (on updateLayout). The Manager will first call preLayout on all widgets, before calling updateLayout.
To solve the scrollbar race condition we have to run quite an expensive check. The Layout Manager will check if the browser viewport has changed after it has laid out all registered widgets. It will then go for another run to layout all widgets with the new viewport dimensions. Another check is done to catch the scrollbar race condition. If this happens we force a scrollbar and redo the layout once again. Resizing can be quite jerky when this condition is occuring. The best way to avoid the performance penalty at all is to force the scrollbar to show at all times (via css overflow).
The Layout Manager must not be instantiated. It's a static "class". Basically just a bunch of static functions with static data. This makes sense since we want to manage every widget instance. To add a widget to the manager you simply have to add it via OCBNET.Layout.add(widget).
The widgets you register can be associative arrays or objects. To work with the Layout Manager these objects need to have preLayout, postLayout and updateLayout set to functions. The Manager will always call them in the context of the widget. For the widget this will look like a regular method call. On preLayout you should read all values / dimensions and on postLayout you can adjust widget elements. On updateLayout you then may adjust inner elements (like centering stuff).
OCBNET.Layout(force)
Main function to layout all widgets. Takes an optional force argument which is passed to each layout method (as data.force). Always call this function when the widget dimensions changed by some event other than resize.
OCBNET.Layout.add(widget)
Add another widget to the Layout Manager (widget must be an object).
OCBNET.Layout.del(widget)
Remove the widget from the Layout Manager (widget must be same object as on add).
OCBNET.Layout.schedule(delay, reset)
Schedule a delayed layout run in X miliseconds. Optional argument to reset already scheduled update. If you reset the scheduler to fast you may not get a layout update in a long time. Uses RequestAnimationFrame if available to redo the layout.
A widget must be an object and should define a function on the reserved keyword (all optional). We pass a data object to all functions with each layout run for you to store your data there. The force flag will also be present on this data object (data.force).
{ 'preLayout' : function (data) { var widget = this; if (data.force) {} } }
Read and store the values needed to redo the layout (do not adjust any browser elements).
{ 'postLayout' : function (data) { var widget = this; if (data.force) {} } }
Use stored values to calculate the layout (do not measure any browser elements). In this step you may only adjust the viewport that will influence the outside page. Use updateLayout to do more expensive adjustements (read below).
{ 'updateLayout' : function (data) { var widget = this; if (data.force) {} } }
Do adjustements that will not alter the outside dimensions of the widget here. The other events are called up to three times per layout run. This callback will only be executed once after the Layout Manager has decided how to layout the widgets.
Resize the browser window until the content will overflow and see what happens!
- http://www.ocbnet.ch/github/layout/demo/demo.html
- http://www.ocbnet.ch/github/layout/demo/broken.html
- http://www.ocbnet.ch/github/layout/demo/nested.html
- http://www.ocbnet.ch/github/layout/demo/fullscreen.html
Note: IE9 shows a bug with the fluid images in the demo.