Enyo Daily #11 - Animated Grid Layout
I started this post by looking through the API docs for a control I hadn't used yet. I came across the Grid control with no documentation and thought to myself, "This could be interesting." Turns out, not that interesting. That did lead me to something else however: an animated grid layout.
[[MORE]]The Grid control uses simple CSS floats to align its children to a grid. Each child is assigned the enyo-grid-div class which defines the height and width in pixels (64x48 to be exact). You can add a custom class (to change the dimensions, for example) to each automatically by specifying a cellClass property on the Grid component. That's about it. Oh, you also get a drop shadow ...
I created something a little more robust. I'll emphasize little as I haven't tried to consider the various use cases yet. In short, the extras.Grid control publishes height and width properties that define the size of a cell and absolutely positions each child according to that grid. I added a quick css transition to animate changes to grid size and voilá. For a little icing, I also added a collapsed property that when set to true, will collapse all the controls to the top left corner. So, if you wanted to implement an iOS-style photo stack, you could use this as a starting point.
I'm going to flesh this out a bit more and will soon add it to my enyo extras github repo. In the mean time, here's the prototype to try out:
CSS
.extras-grid {
position:relative
}
.extras-grid > * {
position:absolute;
-webkit-transition:all 250ms ease-out;
}
JavaScript
var _Grid = {
name:"extras.Grid",
kind:"Control",
className:"extras-grid",
published:{
height:200,
width:150,
collapsed:false
},
create:function() {
this.inherited(arguments);
this.resizeHandler();
},
rendered:function() {
this.inherited(arguments);
if(!this.dim) {
this.resizeHandler();
}
},
// iterates children and repositions them
positionControls:function() {
var c = this.getControls();
for(var i=0;i<c.length;i++) {
this.positionControl(c[i], i);
}
},
// calculates position for a control and applies the style
positionControl:function(control, index) {
var colsPerRow = Math.floor(this.dim.width/this.width)
var top = (this.collapsed) ? 0 : Math.floor(index/colsPerRow)*this.height;
var left = (this.collapsed) ? 0 : (index%colsPerRow)*this.width;
control.applyStyle("top", top + "px");
control.applyStyle("left", left + "px");
},
collapsedChanged:function() {
this.positionControls();
},
widthChanged:function() {
this.positionControls();
},
heightChanged:function() {
this.positionControls();
},
// reflows controls when window.resize event fires (e.g. device rotation)
resizeHandler:function() {
var n = this.hasNode();
if(!n) return;
var s = enyo.dom.getComputedStyle(n);
this.dim = {width:parseInt(s.width),height:parseInt(s.height)};
this.positionControls();
}
}
enyo.kind(_Grid);
var _Example = {
name:"com.technisode.example.App",
kind:"extras.Grid",
cellClass:"button",
components:[{kind:"Button", caption:"Collapse", onclick:"toggle"},
{kind:"Button", caption:"Small Grid", onclick:"smallGrid"},
{kind:"Button", caption:"Big Grid", onclick:"bigGrid"},
{kind:"Button", caption:"Normal Grid", onclick:"reset"},
{kind:"Button", caption:"Button 5", onclick:"toggle"},
{kind:"Button", caption:"Button 6", onclick:"toggle"},
{kind:"Button", caption:"Button 7", onclick:"toggle"},
{kind:"Button", caption:"Button 8", onclick:"toggle"},
{kind:"Button", caption:"Button 9", onclick:"toggle"}],
toggle:function() {
this.setCollapsed(!this.getCollapsed());
},
smallGrid:function() {
this.setWidth(96);
this.setHeight(72);
},
bigGrid:function() {
this.setWidth(320);
this.setHeight(240);
},
reset:function() {
this.setWidth(200);
this.setWidth(150);
}
}
enyo.kind(_Example);