Skip to content

Instantly share code, notes, and snippets.

@a-s-o
Last active August 29, 2015 14:10
Show Gist options
  • Save a-s-o/54d2a258cb63008786b5 to your computer and use it in GitHub Desktop.
Save a-s-o/54d2a258cb63008786b5 to your computer and use it in GitHub Desktop.
grid component for mithril (panels flow vertically, are absolutely positioned within the containing element and follow specified order)

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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment