Thanks to Barney Carroll and Leo Horie with issue#351
Result: http://i.imgur.com/vbij1am.png
In the above example, the panel order is set at [2, 1, 0, 3, 4, 5, 6, 7, 8, 9], therefore, the panels are intentionally out of order in the screenshot.
Thanks to Barney Carroll and Leo Horie with issue#351
Result: http://i.imgur.com/vbij1am.png
In the above example, the panel order is set at [2, 1, 0, 3, 4, 5, 6, 7, 8, 9], therefore, the panels are intentionally out of order in the screenshot.
import Grid from './grid.js'; | |
var grid = new Grid(); | |
m.module(document.getElementById("grid-container"), { | |
controller: function () { | |
grid.init(); | |
// Generate a random number of panels (with random | |
// number of child tiles to create variations in height) | |
this.panels = randRange().map(function (idx) { | |
return { tiles: randRange(), key: idx }; | |
}); | |
}, | |
view: grid.view | |
}); | |
function randRange () { | |
var rand = Math.floor( Math.random() * 8 ) + 3; | |
var arr = []; | |
while (rand--) { | |
arr.unshift(rand); | |
} | |
return arr; | |
} |
// This is basically lodash with some dom helpers | |
// and shortcut to velocity.js for animation | |
import $ from 'helpers'; | |
import * as m from 'mithril'; | |
function GridViewModel () { | |
// Panels are stored in a hash by key | |
// panelOrder preserves the order in which they are | |
// displayed and also allows display in a arbritrary order | |
// for instance to allow user sorting | |
var panels = {}; | |
var panelOrder = [2, 1, 0, 3, 4, 5, 6, 7, 8, 9]; | |
// The containing element | |
var container; | |
// Setup performs some calculations based on | |
// container width then calls render | |
function setup () { | |
var containerW = container.width(); | |
// Desired panel width (in this case, we are fixing | |
// the panel width, but the columns could also be | |
// by modifying the calculations below) | |
var panelW = 300; | |
var columns = Math.floor( containerW / panelW ); | |
var excessW = containerW - (panelW * columns); | |
var gutterW = Math.max(0, Math.min(10, excessW / (columns + 1))); | |
var leftGutter = Math.max(0, (excessW - (gutterW * columns - 1)) / 2); | |
// Trigger the render method | |
render({ columns, leftGutter, panelW, gutterW }); | |
// Bind event listeners here | |
} | |
function teardown () { | |
// Unbind event listeners here | |
} | |
// Render | |
// ----------------------------------------------------- | |
// Calculates the appropriate placement of panels | |
// Separated from the setup method so that the container | |
// based calculations can be cached and render can be called | |
// indendently (ex: when a single panel's height changes) | |
function render ({ columns, leftGutter, panelW, gutterW }) { | |
// Get total height of the all panels | |
var totalH = $.list.reduce(panelOrder, function (total, key) { | |
if (panels[key]) total += panels[key].el.height(); | |
return total; | |
}, 0); | |
// Calculate average height of columns | |
var averageH = totalH / columns; | |
// Used to check if a panel should remain in the current | |
// columnn or move to the next | |
var remainingH = totalH, remainingC = columns - 1; | |
// An array of panel positions, consists of objects as follows: | |
// { key, col, x, y, h, w } | |
var positions = []; | |
// Interate over the panel order and position each panel | |
$.list.each(panelOrder, function (key) { | |
// Only add panel if it has been | |
// registered using the vm.panel() method | |
if (panels[key]) { | |
var panel = panels[key]; | |
// Get the panel before the current panel from the positions array | |
// If this is a first panel, place it at [leftGutter, 0] coordinates | |
// starting from column # 1 | |
var prev = positions.length ? positions[ positions.length - 1] : { | |
col: 1, x: leftGutter, y: 0, h: 0 | |
}; | |
// Variables for current panel's position | |
var x, y, col = prev.col, h = panel.el.height(); | |
// The current column's height (after we add this panel) | |
var currentH = prev.y + prev.h + h + gutterW; | |
// Keep the panel in the current column if any | |
// of the following cases are true, otherwise, | |
// move it to the next column | |
switch (true) { | |
case (prev.y === 0): break; | |
case (currentH < averageH * 1.33): break; | |
case (remainingC === 0): break; | |
case (currentH < remainingH / remainingC): break; | |
default: col = Math.min(++col, columns); | |
} | |
if (col > prev.col) { | |
// If the panel is placed in a new column | |
remainingC = remainingC - 1; | |
// Calculate new column's left attribute in pixels | |
x = prev.x + panelW + gutterW; | |
// Position at the top of the column | |
y = gutterW; | |
} else { | |
// If placing the panel in the same column as previous panel, | |
// the left value is the same as the previous panel | |
x = prev.x; | |
// Position just below the previous column | |
y = prev.y + prev.h + gutterW; | |
} | |
// Remove current panel's height from the remaining height | |
remainingH = remainingH - h; | |
// Add current panel to the positions array | |
positions.push({ key, col, x, y, h, w: panelW }); | |
// Set the left position and width of the | |
// current panel (we don't want it animate) | |
panel.el.css({ | |
left: x + 'px', | |
width: (panelW - (gutterW * 2)) + 'px' | |
}); | |
// Slide each panel down from top of column | |
// to its new y-position in the column | |
panel.el.animate({ top: y }, { | |
duration: 300, | |
easing: [150, 20] | |
}); | |
} | |
}); | |
} | |
return { | |
container: function registerContainer () { | |
return function (element, init, ctx) { | |
if (!init) { | |
container = $.el(element); | |
setup(); | |
ctx.onunload = teardown; | |
} | |
}; | |
}, | |
panel: function registerPanel ( panel ) { | |
// Add panel to the panel map | |
var key = panel.key; | |
panels[key] = panel; | |
return function (element, init, ctx) { | |
if (!init) { | |
panel.el = $.el(element); | |
ctx.onunload = function unregister () { | |
// Remove panel from panel map | |
panels[key] = null; | |
}; | |
} | |
}; | |
} | |
}; | |
} | |
export default function Grid () { | |
if (!(this instanceof Grid)) return new Grid(); | |
var grid = {}; | |
grid.init = function () { | |
grid.vm = new GridViewModel(); | |
}; | |
grid.view = function (ctrl) { | |
return m('.viewport', { config: grid.vm.container() }, [ | |
ctrl.panels.map(function (panel) { | |
return m('.egrid-panel', { key: panel.key, config: grid.vm.panel(panel) }, [ | |
m('.egrid-title', panel.key), | |
// Add tiles to the panel | |
m('ul', panel.tiles.map(tile => m('.egird-tile', tile))) | |
]); | |
}) | |
]); | |
}; | |
return grid; | |
} |