Skip to content

Instantly share code, notes, and snippets.

@d4tocchini
Created September 15, 2011 02:33
Show Gist options
  • Save d4tocchini/1218383 to your computer and use it in GitHub Desktop.
Save d4tocchini/1218383 to your computer and use it in GitHub Desktop.
G.coffee
###
One grid system to rule them all, and in the javascript bind them
Concerns
=====================================
- IE positioning... see isotope.js and https://github.com/louisremi/jquery.transform.js#
- needs to be absolue in relative? or absolute in absolute too?
- http://stackoverflow.com/questions/3667549/absolute-positioned-div-within-another-absolute-positioned-div-does-not-show-on-i
RESOURCES
======================================
jQuery Template Layout Spec
- http://a.deveria.com/csstpl/
Marcus Gärde's Way of Typography
- http://www.bachgarde.com/html/works/gridsystem.html
- In fact, the baselinegrid always fitted perfectly on the page. And even the gutter was in proportion to the lead.
- [InDesign Pro Grid Calc for $100](https://www.designersbookshop.com/grid-calculator-pro-edition.html)
Vignelli's UniGrid Sytem
- http://www.aisleone.net/2010/design/massimo-vignellis-unigrid-system/
DIN paper system
- http://en.wikipedia.org/wiki/Paper_size#The_international_standard:_ISO_216
The Golden Grid System
- A folding grid for responsive design.
- http://goldengridsystem.com/
Russian Modular Grid
- http://thegrids.ru/
- PS: http://modulargrid.org/#app
- Algorithms: http://cherenkevich.livejournal.com/38353.html, http://cherenkevich.livejournal.com/38454.html, http://cherenkevich.livejournal.com/38839.html
Fluid Grids
- http://www.alistapart.com/articles/fluidgrids/
Inpsiration
- http://www.aisleone.net/
THEORY
======================================
Math
-------------------------------
A grid is arbitrary division of an available space in a plane into discrete units.
Each set of discrete units is a grid dimension.
Two, non parrell grid dimensions consitute a finite vector space.
Layouts of print and web content are comprised of sets of planes fit into this vector space.
Each pie
CSS Layouts
-----------------------------
http://www.alistapart.com/articles/css-floats-101/
According to the W3C:
A float is a box that is shifted to the left or right on the current line. The most interesting characteristic of a float (or “floated” or “floating” box) is that content may flow along its side (or be prohibited from doing so by the “clear” property). Content flows down the right side of a left-floated box and down the left side of a right-floated box.
[normal flow](http://www.w3.org/TR/CSS21/visuren.html#normal-flow)
http://www.alistapart.com/articles/css-positioning-101/
http://www.positioniseverything.net/easyclearing.html
http://mattwilcox.net/archive/entry/id/1030
http://mattwilcox.net/archive/entry/id/1037/
The W3C, still, are not willing to believe or are somehow incapable of understanding that the abilities of CSS today are inadequate for the job it must now perform compared to the job it was created to handle. And they are unwilling to re-think the very fundamentals of CSS that are crippling designers today. It’s almost a decade since CSS could be said to have “gone mainstream” - and yet designers are still waiting for a way to make things line up horizontally that doesn’t involve butchering the mark-up. Are still waiting for layout tools that allow us to do some really fundamental basics. Are still waiting for layout options flexible enough for us to use without resorting to complex hacks. And the future looks as bad as the present if the god-forsaken horror that is the “Advanced” layout module of CSS3 is any indication.
(CSS needs a bit of basic love)[http://mattwilcox.net/archive/entry/id/1059/]
You can’t position relative to a given element, only to a positioned parent element. Making it impossible to truely seperate mark-up structure from display layout.
We’re still lacking constants, variables, and math. Bert Bos can argue they’re not needed until he’s blue in the face, but the fact remains he’s wrong. There are reasons that SASS and LESS were developed and are being used by designers not just developers. But as nice as they are, they’re just battle-field medicine and we need a real solution.
Masonry
----------------------------
Masonry is a dynamic grid layout plugin for jQuery. Think of it as the flip-side of CSS floats. Whereas floating arranges elements horizontally then vertically, Masonry arranges elements vertically, positioning each element in the next open spot in the grid. The result minimizes vertical gaps between elements of varying height, just like a mason fitting stones in a wall.
CSS Is Not Suited for Layout!
-----------------------------
- [Call For a Layout System](http://meyerweb.com/eric/thoughts/2009/02/17/wanted-layout-system/)
- But my issue is that they are trying to define layout systems without understanding what designers actually want to do, and how that maps to best-practice HTML authoring.
Give me math, variables, and constants. Give me positioning relative to a specified element. With those things I can make my OWN layout solution. Implement a grid system, and all independent of mark-up order. None of the proposed layout solutions address the basic problems we have.
- [The fundamental problems with CSS3](http://mattwilcox.net/archive/entry/id/1031/)
- http://www.flownet.com/ron/css-rant.html
http://www.w3.org/TR/css3-grid-layout/#basic-capabilities
fluid or static
source order independence
------------------------
- http://www.sitepoint.com/should-html-dom-order-match-visual-layout/
- http://www.sitepoint.com/give-floats-the-flick-in-css-layouts/
###
$ = jQuery
# Wrap in Closure to avoid global variables.
$ ->
root.G = {}
#8888888ba, 88 88
#8 `"8b "" ""
#8 `8b
#8 88 88 88,dPYba,,adPYba, ,adPPYba, 8b,dPPYba, ,adPPYba, 88 ,adPPYba, 8b,dPPYba,
#8 88 88 88P' "88" "8a a8P_____88 88P' `"8a I8[ "" 88 a8" "8a 88P' `"8a
#8 8P 88 88 88 88 8PP""""""" 88 88 `"Y8ba, 88 8b d8 88 88
#8 .a8P 88 88 88 88 "8b, ,aa 88 88 aa ]8I 88 "8a, ,a8" 88 88
#8888888Y"' 88 88 88 88 `"Ybbd8"' 88 88 `"YbbdP"' 88 `"YbbdP"' 88 88
###\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
TODO:
- normalize angle values
/////////////////////////////////////////////////////////////////////////////////////////////////////###
class Dimension extends RW.Model
is_horizontal: false
is_vertical: false
is_angled: false
defaults:
id: null
# angle = the perpendicular of the grid lines from
angle: 'horizontal' # 'vertical' || [NUMBER, 0-360], 0 = 'horizontal', 90 = 'vertical'
_angle: 0
align: 'middle' # 'start' || 'end'
_zoneUnit: null
unit: null
_unit: null
count: null
_count: null
countMax: null
gutter: 0
_gutter: null
lines: null
angle: null
lines: null
size: 0
defaultLines:
golden: '38.1966011%' # 1 - (phi^(-1)) = 0.381966011
golden2: '61.803399%' # 1/phi = .61
squareOf2: '70.7106781%' # sqrt(2)^(-1) = 0.707106781
# This method is attached to the the it's grid like so:
# G[grid_id][dimension_id.self]()
# Basically, just some API sugar to help drilling down Grid properties
self: (num, isZone = true) ->
# return this dimension if num is not defined
if !num
return this
# else, return dimension measurements if num is passed
if isZone
return @zoneUnit(num)
else
return @unit(num)
initialize: () ->
_.bindAll this, 'self'
that = this
if !this.id then ERROR 'Grid Dimension requires an id'
count = this.get 'count'
unit = this.get 'unit'
if !(unit or count) then ERROR 'Grid Dimension needs either unit or count'
if (unit and count) then ERROR 'Grid Dimension cant have both unit and count set'
el = this.get 'el'
if !el? then ERROR 'Grid Dimension needs an el to measure itself from'
@el = el
$el = $(el)
_.bindAll this, 'onSizeChange'
@bind 'change:size', @onSizeChange
# determine how to get the available size for this dimension
# by using the angle of the dimension
angle = @get 'angle'
if angle is 'horizontal'
@is_horizontal = true
@set {_angle: 0}, {silent: true}
this.measure_size = () ->
return $el.width()
else if angle is 'vertical'
@is_vertical = true
@set {_angle: 90}, {silent: true}
this.measure_size = () ->
return $el.height()
else if _.isNumer angle
this.measure_size = () ->
sin = Math.sin(angle)
if sin > 0
return $el.height() / sin # r = y / sin(angle)
else
return $el.width() / Math.cos(angle) # r = x / cos(angle)
# sets the dimension size, if its a new dimension the set method dispactches 'change:size'
measure: () ->
this.set
'size': this.measure_size()
onSizeChange: () ->
if @get('unit')? and !@get('count')?
@measureWithFixedUnit()
else
@measureWithFixedCount()
size: (dimUnits = 'px') ->
return this.get 'size'
count: () ->
return @get '_count'
countRemainder: () ->
return @get '_countRemainder'
remainder: () ->
return @get '_remainder'
unit: (num = 1) ->
return num * @get('_unit') + ((num - 1) * @gutter())
zoneUnit: (num = 1) ->
return num * @get '_zoneUnit'
gutter: () ->
return @get '_gutter'
# helper for templates
gutterHalf: () ->
return @gutter() / 2
# Rounding
# - Be careful rounding percentages!!! if the grid hasn't been measured yet, then the grid has to go through another measure to work, which sucks
floor: (value) ->
return @round value, 'floor'
ceil: (value) ->
return @round value, 'ceil'
round: (value, roundMethod = 'floor') ->
value = @normalizeToPxValue value
size = @size()
zoneUnit = @zoneUnit()
#if value > size then value = size
numZoneUnits = value / zoneUnit
if numZoneUnits < 1
value = zoneUnit
else
value = zoneUnit * Math[roundMethod] numZoneUnits
return value
measureWithFixedUnit : () ->
unit = @normalizeToPxValue @get 'unit'
gutter = @normalizeToPxValue @get 'gutter'
size = @size()
zoneUnit = unit + gutter
countFrac = size / zoneUnit
count = Math.floor countFrac
countRemainder = count - countFrac
remainder = countRemainder * zoneUnit
@set
'_unit' : unit
'_zoneUnit' : zoneUnit
'_count' : count
'_countRemainder' : countRemainder
'_remainder' : remainder
'_gutter' : gutter
measureWithFixedCount : () ->
count = @get('count')
gutter = @normalizeToPxValue @get 'gutter'
size = @size()
countRemainder = 0
remainder = 0
zoneUnit = ( size / count )
unit = zoneUnit - gutter
@set
'_unit' : unit
'_zoneUnit' : zoneUnit
'_count' : count
'_countRemainder' : countRemainder
'_remainder' : remainder
'_gutter' : gutter
normalizeToPxValue: (value, sizeContext) ->
sizeContext or sizeContext = @size()
return _.toPxValueRelativeTo value, sizeContext
previewItemCss: () ->
gutterHalf = @gutterHalf()
angle = @get 'angle'
if angle is 'horizontal'
return 'width: ' + @unit() + 'px; height:100%; margin: 0 ' + gutterHalf + 'px;'
else if angle is 'vertical'
return 'height: ' + @unit() + 'px; width:100%; margin: ' + gutterHalf + 'px 0;'
previewItems: () ->
items = []
count = @count()
dimension = this
_.each _.range(count), (i) ->
items[i] = dimension
return items
previewZones: () ->
previewZone: () ->
angle = @get 'angle'
if angle is 'horizontal'
model =
width: @zoneUnit()
height: '100%'
else if angle is 'vertical'
model =
height: @zoneUnit()
width: '100%'
nodeSpan: (num) ->
return @unit()
sizeOf: (num, isContainer) ->
return
class DimensionCollection extends RW.Collection
model: Dimension
###888888888
,88
,88"
,88" ,adPPYba, 8b,dPPYba, ,adPPYba,
,88" a8" "8a 88P' `"8a a8P_____88
,88" 8b d8 88 88 8PP"""""""
88" "8a, ,a8" 88 88 "8b, ,aa
888888888888 `"YbbdP"' 88 88 `"Ybb###
###
TODO:
- Order
- suffix, prefix...
- Spawn Methods
- layouts
- orphaned?
###
class Zone extends RW.Model
className: () ->
return 'g_zone_' + @cid
itemClassName: () ->
return 'g_zone_' + @cid + '_item'
#items: null
defaults:
id : null
is_active : false
htmlTag : 'div'
clear : false # doesnt do anything yet
generator : null
# size
size : null
width : 'auto'
_width : 'auto'
height : 'auto'
_height : 'auto'
# min / max size
min_width : 0
max_width : 'none'
min_height : 0
max_height : 'none'
_min_width : 0
_max_width : 'none'
_min_height : 0
_max_height : 'none'
# hierarchy
parentId : null
spawner : null
childDefaults : null
# z-index
#z : 0
# layout
float : 'left' # ''
_css_position : 'relative' # 'absolute'
childSelector : null
# position
top : 'auto'
left : 'auto'
bottom : 'auto'
right : 'auto'
_top : 'auto'
_left : 'auto'
_bottom : 'auto'
_right : 'auto'
# t r l b gutters
_bGutter : 0
_lGutter : 0
_rGutter : 0
_tGutter : 0
lGutter : () ->
return this.grid.dimensions.models[0].gutter() / 2
rGutter : () ->
return this.grid.dimensions.models[0].gutter() / 2
tGutter : () ->
return this.grid.dimensions.models[1].gutter() / 2
bGutter : () ->
#if @get('height') is 'auto'
# return 'auto'
return this.grid.dimensions.models[1].gutter() / 2
initialize: () ->
@grid = @get 'grid'
if !@grid? then ERROR 'Zone needs a Grid!'
###
childZones = this.get 'zones'
if childZones?
this.addZones childZones
###
render: () ->
this.el = $.templates.G_zone_template( this ) #$.templates.G_zone_template(zone).width(z_w).height(z_h).css({float:'left'}).appendTo($el)
return this.el
place: (itemEl, guttered = true) ->
$item = $(itemEl)
attrName = 'data-placed'
$item.attr attrName, @id
# place item in zone
if guttered
$item.addClass(@itemClassName()).addClass('guttered').appendTo(this.el)
#$item.addClass(@itemClassName()).addClass('guttered').position
# my: 'top left'
# at: 'top left'
# of: this.el
else
$item.addClass(@itemClassName()).appendTo(this.el)
# TODO, make better
#@items.push $item
measurables: ['width', 'height', 'lGutter', 'rGutter', 'tGutter', 'bGutter', 'min_width', 'max_width', 'min_height', 'max_height', 'top', 'right', 'bottom', 'left']
measure: () ->
that = this
# get an object with the internal version of measurable attributes that are CSS safe.
setObj = @getInternalMeasurements()
# Before setting the internal measurements, let's check them for dependencies that may change other attributes.
setObj = @validateInternalMeasurements(setObj)
# set the internal measurements, which dispatches a change event, and thus will update the zone's CSS
@set setObj
populate: () ->
attrOld = 'data-g-position'
# D4 TODO: better context handling?
context = $('html') #@grid.bufferEl
# place els into generated zones
elsToPlaceSelector = '[' + attrOld + '=' + this.id + ']'
elsToPlace = context.find( elsToPlaceSelector )
elsToPlace.removeAttr attrOld
# D4 TODO: better context handling?
for el in elsToPlace
@place(el)
layout: () ->
# set children width and height and stuff
setup: () ->
@setupGenerator()
setupGenerator: () ->
generatorSettings = @get 'generator'
# do nothing if generator isnt defined
if !generatorSettings then return false
# if first time, instaniate @generator
if !@generator?
generatorSettings.zone = this
@generator = new ZoneGenerator(generatorSettings)
# else, update @generator
else
@generator.set generatorSettings
#
@generator.setup()
@generator.autoGenerate()
# Sets parentId of each Zone Model to this zone
# Then, adds the Zone Models via this zone's grid
addZones: (models) ->
that = this
if _.isArray models
_.each models, (model) ->
model.parentId = that.id
else
models.parentId = that.id
this.grid.addZones(models)
children: () ->
@grid.zones.getChildrenOf this
# !!!!!!!!!!!!!!!
childrenWidth: () ->
return $(@el).childrenWidth()
# !!!!!!!!!!!!!!!
childrenHeight: () ->
return $(@el).childrenHeight()
###
currentHeight = 0
_.each @children(), (child) ->
# only measure height of children zones whose height is not dependent on parent
if !_.includes child.get('height'), '%'
$child = $(child.el)
h = $child.position().top + $child.height()
if h > currentHeight then currentHeight = h
###
dynamicallySize: () ->
# set the zone height to height of the children
if @get('height') is 'auto'
$(@el).height(@childrenHeight())
if @get('width') is 'auto'
$(@el).width(@childrenWidth())
#finalizeSize
getInternalMeasurements: () ->
obj = {}
that = this
_.each @measurables, (attr) ->
internalAttr = '_' + attr
internal = that.toCssValue that.get(attr)
if internal then obj[internalAttr] = internal
return obj
validateInternalMeasurements: (obj) ->
obj or obj = {}
# validate position props
# if any of the t r b l position props are set, then make sure that the css position = 'absolute'
positionKeys = ['_top', '_right', '_bottom', '_left']
is_positioned = false
for posKey in positionKeys
if obj[posKey]? and obj[posKey] isnt 'auto'
is_positioned = true
break
if is_positioned
obj['_css_position'] = 'absolute'
else
obj['_css_position'] = 'relative'
# validate width / height stuff
#if obj._width is 'auto'
# TODO: validate float stuff...
return obj
toCssValue: (value) ->
if !value then return null
# call functions, if function returns number it will add 'px' suffix
if _.isFunction value then value = value.call this
# calculate grid calculations
if _.startsWith value, 'G.'
value = eval(value)
# ensure value is CSS safe, suffix with 'px' if number
if _.isNumber( value ) then value = value + 'px'
return value
# not used in internal measure flow
setInternalAttr: (attr) ->
setObj = {}
internalAttr = '_' + attr
internal = @toCssValue @get(attr)
if internal then setObj[internalAttr] = internal
@set setObj
###
activate: () ->
@set
is_active: true
deactivate: () ->
@set
is_active: false
###
$.tags.add
name: 'G_zone_template_styles'
template:
'
<style {{isTheTag}}>
.{{className}} {
float:{{float}};
position:{{_css_position}};
left:{{_left}}; right:{{_right}}; top:{{_top}}; bottom:{{_bottom}};
width:{{_width}}; height:{{_height}}; min-width:{{_min_width}}; min-height:{{_min_height}}; max-width:{{_max_width}}; max-height:{{_max_height}};
}
.{{className}} .{{itemClassName}} {
left:0px; right:0px; top:0px; bottom:0px; position:absolute !important;
}
.{{className}} .guttered {
left:{{_lGutter}}; right:{{_rGutter}}; top:{{_tGutter}}; bottom:{{_bGutter}};
}
</style>
'
$.tags.add
name: 'G_zone_template'
api:
color: () ->
return null #'hsla('+_.randomIntRange(0,360)+',100%,'+_.randomIntRange(0,100)+'%,.2)'
template:
'
<{{htmlTag}} {{isTheTag}} class="{{className}}" data-g-zone="{{id}}" style="background-color:{{color}};">
{{>G_zone_template_styles}}
</{{htmlTag}}>
'
class ZoneCollection extends RW.Collection
model: Zone
###888888888 ,ad8888ba,
,88 d8"' `"8b ,d
,88" d8' 88
,88" ,adPPYba, 8b,dPPYba, ,adPPYba, 88 ,adPPYba, 8b,dPPYba, ,adPPYba, 8b,dPPYba, ,adPPYYba, MM88MMM ,adPPYba, 8b,dPPYba,
,88" a8" "8a 88P' `"8a a8P_____88 88 88888 a8P_____88 88P' `"8a a8P_____88 88P' "Y8 "" `Y8 88 a8" "8a 88P' "Y8
,88" 8b d8 88 88 8PP""""""" Y8, 88 8PP""""""" 88 88 8PP""""""" 88 ,adPPPPP88 88 8b d8 88
88" "8a, ,a8" 88 88 "8b, ,aa Y8a. .a88 "8b, ,aa 88 88 "8b, ,aa 88 88, ,88 88, "8a, ,a8" 88
888888888888 `"YbbdP"' 88 88 `"Ybbd8"' `"Y88888P" `"Ybbd8"' 88 88 `"Ybbd8"' 88 `"8bbdP"Y8 "Y888 `"YbbdP"' 88
- The ability to dynamically generate zones in the grid was an important feature for me.
- Organicaly random layouts with ordered severity
- Flipboard, Grid Masters
- akin to a particle generator
Zones can be generated by:
- Defining 'ZoneSeeds'
- Programaticaly via interpolation of ZoneGenerator Parameters
ZoneSeeds
- A ZoneSeed generates a Zone with specified attributes
- The most common use case is, given a Collection of ZoneSeeds, to generate Zones from a set of DOM Elements
Generating Zones programatiicaly
-
Demos
- Navigation Menus
- Flipboard Layout
- Infinite Scrolling
RAQ (rarely asked questions)
- Global ZoneGenerator or ZoneFactory?
- Debated it, but each zone needs to maintain state of last rendered seed, so it made sense to init a Generator on each Zone
////////////////////////////////////////////////////////////////////////////////////////////////////////////###
class ZoneSeed extends RW.Model
defaults:
clear: false
class ZoneSeedCollection extends RW.Collection
model: ZoneSeed
class ZoneGenerator extends RW.Model
defaults:
el: null # or el
clear: false
shuffle: false
seeds: null
is_infinite: false
infinite_direction: 'vertical'
infinite_buffer: '200px'
loadingGif: null
templateId: 'wall_tiles'
initialize: () ->
@zone = @get 'zone'
if !@zone? then ERROR 'ZoneGenerator requires a zone'
@grid = @zone.grid
@id = @zone.id
# create this.seeds collection
@seeds = new ZoneSeedCollection()
setup: () ->
@setupSeeds()
setupSeeds: () ->
_.attrToSelf_collection_proxy(this, 'seeds').refresh()
addSeeds: (models) ->
models = _.flattenModels(models)
#@prepSeeds(models)
@seeds.add( models )
autoGenerate: () ->
bufferEl = @grid.bufferEl
emitAttr = 'data-g-emit'
# populate els that need their own zone emitted from the zone generator
elsToEmitSelector = '[' + emitAttr + '=' + @zone.id + ']'
elsToEmit = bufferEl.find( elsToEmitSelector )
elsToEmit.removeAttr emitAttr
#@addToLayout(elsToEmit)
if elsToEmit?.length? # D4 hack
if elsToEmit.length > 0
@generate {els: elsToEmit}
count: 0
seeds_i: 0
prev_seeds_i: 0
seeds_num: () ->
return @seeds.models.length
generateDefaults:
els: null
num: 'auto'
buffer: '200px'
generate: (options) ->
options or options = {}
that = this
# use the el to make child zones
# populate child zones with the el
parentZone = @zone
parentId = @zone.id
seeds = @seeds
seeds_num = @seeds_num()
nextSeed = () ->
seeds_i = that.seeds_i
if seeds_i > seeds_num - 1 then seeds_i = 0
seed = seeds.at seeds_i
seeds_i = seeds_i + 1
that.seeds_i = seeds_i
return seed
els = options.els
if els?
_.each els, (el) ->
$el = $(el)
# find a suitable spawn, maybe creat a new zone
seed_found = false
while !seed_found
childZone = {}
seed = nextSeed()
childZone.id = parentZone.id + '_generated_' + that.count
childZone = _.defaults childZone, seed.attributes
# add the zone if clear
if seed.get 'clear'
that._addChildZone( childZone )
else
seed_found = true
#else if !childZone.if?
# spawn_found = childZone.if.call that, $el
#!!!!! makes the el ready to be positioned into its zone
$(el).attr('data-g-position', childZone.id)
that._addChildZone( childZone )
#_getChildZoneId: () ->
# return @zone.id + '_generated_' + @count
_addChildZone: (childZone) ->
@count = @count + 1
@zone.addZones childZone
#b
#88b
#8'`8b
#8' `8b 8b,dPPYba, ,adPPYba, ,adPPYYba,
#8YaaaaY8b 88P' "Y8 a8P_____88 "" `Y8
#8""""""""8b 88 8PP""""""" ,adPPPPP88
#8' `8b 88 "8b, ,aa 88, ,88
#8' `8b 88 `"Ybbd8"' `"8bbdP"Y8
# The Grid Area defines the workable area of the Grid.
# Dynamic properties do no work here, the Grid Area is like a more specialized, and restrictive Grid Zone
class GridArea extends Zone
className: () ->
return 'g_area_' + @cid
itemClassName: () ->
return 'g_area_' + @cid + '_item'
defaults:
el : null
height : 'auto'
width : 'auto'
min_height : 0
max_height : 'none'
min_width : 0
max_width : 'none'
initialize: () ->
@attributes = _.defaults @attributes, Zone.prototype.defaults
super
###8888ba, 88 88
d8"' `"8b "" 88
d8' 88
88 8b,dPPYba, 88 ,adPPYb,88
88 88888 88P' "Y8 88 a8" `Y88
Y8, 88 88 88 8b 88
Y8a. .a88 88 88 "8a, ,d88
`"Y88888P" 88 88 `"8bbdP###
class Grid extends RW.Model
#is_grid: true
bufferSelector: () ->
return '[data-g-buffer="' + this.id + '"]'
measured: false
defaults:
el : null
id : null
dimensions : null # @attributes.area.dimensions has models for @dimensions collection
zoneLayout : null
zones : null # @attributes.zones has models for @zones collection
area : # @attributes.area is setter for @area model
top : 25
right : 50
bottom : 75
left : 100
min_height : 250
isAreaReady: false
initialize: () ->
_.bindAll this, 'on_window_resize', 'onZoneAdded', 'onZoneRemoved'
that = this
$(window).bind 'smartresize', () ->
that.on_window_resize()
el = @get('el')
$el = $(el)
@el = el
this.bufferEl = $(this.bufferSelector())
this.dimensions = new DimensionCollection()
this.zones = new ZoneCollection()
this.zones.bind 'add', @onZoneAdded
this.zones.bind 'remove', @onZoneRemoved
#this.zones.bind 'refresh', @onZonesRefresh
#/////////////////////////////////////////////////////////// Grid.measure
measure: () ->
$el = $(@el)
that = this
area = @area
# layout grid area
area.measure()
# measure Grid dimensions
this.dimensions.each (dim) ->
dim.measure()
# ... hacking with DEFERs! ...
# measure Grid zones
this.zones.each (zone) ->
zone.measure()
# size the zones
_.defer () ->
zone.dynamicallySize()
# size the area
_.defer () ->
area.dynamicallySize()
# size the grid
_.defer () ->
$el.height($el.childrenHeight() + area.get('bottom')) # .width($(area.el).width() + area.get('left') + area.get('right'))
@measured = true
#/////////////////////////////////////////////////////////// Grid.setup...
setup: () ->
# first setup the area so the dimensions can be measured
@setupArea()
# then, setup the dimensions that will use the Grid Area to for its measurements
@setupDimensions()
# setup all the statically defined Zones from which dynamic Zones can be generated
@setupZones()
# Dynamically generate Zones
#@setupZoneGenerators()
# place all items into zones
@populate()
# now that everything is setup and populated.
# set valid measurements on the Grid's Area, Dimensions and Zones
@measure()
#////////////////////////////////// Grid.setup...
setupArea: () ->
areaSettings = @get 'area'
# if first time, add the area
if !@area?
areaSettings or areaSettings = {}
areaSettings.grid = this
area = new GridArea(areaSettings)
area.render()
$(@el).prepend(area.el)
@area = area
# else update the area
else
@area.set areaSettings
#////////////////////////////////// Grid.setup...
# 1. SmartSet @zones collection
# 2. each zone.setup()
setupZones: () ->
that = this
_.attrToSelf_collection_proxy(this, 'zones').smartReset({ add: @addZones })
# setup all the zones
for zone in @zones.models
zone.setup()
populate: () ->
for zone in @zones.models
zone.populate()
refreshZones: (models) ->
@removeZones this.zones.models
this.zones.refresh null, {silent:true}
@addZones( models )
addZones: (models, addMethod='add') ->
models = _.flattenModels(models)
@prepZones(models)
@zones[addMethod]( models )
prepZones: (models) ->
that = this
if _.isArray models
_.each models, (model) ->
that._prepZone(model, models)
else if _.isObject models
that._prepZone(models, false)
_prepZone: (model, collection) ->
model.grid = this
onZoneAdded: (zone) ->
parentId = zone.get 'parentId'
zone.render()
if !parentId
parent = $(@area.el)
else
parent = $(this.zones.get(parentId).el)
$( zone.el ).appendTo( parent )
removeZones: (models) ->
for zone in models
if zone?.el? then $(zone.el).remove()
this.zones.remove models, {silent:true}
onZoneRemoved: (zone) ->
# dimensions
setupDimensions: () ->
that = this
# this.attributes.dimensions is a temporary holder of dimensions
_.attrToSelf_collection_proxy(this, 'dimensions').smartReset({ add: @addDimensions })
refreshDimensions: (models) ->
this.dimensions.refresh()
@addDimensions( models)
addDimensions: (models, addMethod='add') ->
el = @area.el
that = this
# add the el to the dimensions
_.each models, (dim) ->
dim.el = el
dim.grid = that
this.dimensions[addMethod]( models )
# a dash of syntax sugar: make the dimensions available to the G[grid_id][dimension_id]
_.each models, (dim) ->
id = dim.id
#if !that[id]? then that[id] = that.dimensions.get(id).self else ERROR 'G.grid.addDimensions() cant add a dimension with an id of: ' + id
that[id] = that.dimensions.get(id).self
previewZone:
id: '_preview'
width: '100%'
height: '100%'
guides_shown: false
guides_el: null
showGuides: () ->
@guides_shown = true
@guides_el = $.templates.grid_dimension_preview(this)
@guides_area_el = $.templates.grid_area_preview()
@area.place( @guides_area_el)
@area.place( @guides_el, false )
hideGuides: () ->
@guides_el.remove()
@guides_area_el.remove()
@guides_shown = false
#_.each this.dimensions.models, (dim) ->
# dim_id = dim.id
# count = dim.count()
on_window_resize: () ->
# guides
guides_shown = @guides_shown
if guides_shown then @hideGuides()
this.measure()
if guides_shown then @showGuides()
template:
'
{{>grid_styles}}
<section class="grid_area">
{{#fields}}
{{>fieldPartial}}
{{/fields}}
</section>
'
grid_styles:
'
<style>
.grid_area {position:absolute; background-color:red;}
</style>
'
fieldPartial:
'
<{{tagType}} data-grid-field-name="{{name}}" style="{{css}}">
</{{tagType}}>
'
$.tags.add
name: 'grid_area_preview'
template:
'
<div class="G_area_preview borderBox" style="border:1px solid red; opacity:.3;">
</div>
'
$.tags.add
name: 'grid_dimension_preview'
template:
'
<div {{isTheTag}} class="G_dimension_preview" style="position:absolute; top:0px; right:0px; bottom:0px; left:0px;">
{{#dimensions.models}}
<div style="position:absolute; top:0px; right:0px; bottom:0px; left:0px;">
{{#previewItems}}
{{#is_vertical}}
<div style="position:relative; height:{{zoneUnit}}px; width:100%; float:left;">
<div class="borderBox" style="border:1px solid red; opacity:.2; left:0px; right:0px; top:{{gutterHalf}}px; bottom:{{gutterHalf}}px; position:absolute;">
</div>
</div>
{{/is_vertical}}
{{#is_horizontal}}
<div style="position:relative; width:{{zoneUnit}}px; height:100%; float:left;">
<div class="borderBox" style="border:1px solid red; opacity:.2; top:0px; bottom:0px; left:{{gutterHalf}}px; right:{{gutterHalf}}px; position:absolute;">
</div>
</div>
{{/is_horizontal}}
{{/previewItems}}
</div>
{{/dimensions.models}}
</div>
'
class GridCollection extends RW.Collection
model: Grid
initialize: () ->
that = this
###
# API Sugar: allow easy Grid retrieval by G[id] = grid
this.bind 'add', (grid) ->
id = grid.id or ERROR '$().G() ... yo grid needs an id'
if !_.isString id then ERROR '$().G() ... yo grids id must be a String'
if G.id? then ERROR '$().G() ... grid id is invalid'
G[id] = grid
###
G.grids = new GridCollection
G.addGrid = (model) ->
id = model.id or ERROR '$().G() ... yo grid needs an id'
if !_.isString id then ERROR '$().G() ... yo grids id must be a String'
if G.id? then ERROR '$().G() ... grid id is invalid'
G[id] = new Grid( model )
grid = G[id]
G.grids.add( grid )
# have to stagger the setup method so everything is initted
return grid
$.plugins.add
name: 'G'
use$prite: false
zones: null
setupOnce: false
grid: null
api:
id: null
layout: 'float' # vbox hbox masonry positioned
children: null
###
[
{id:'title', width:'100%', height:G.v(10) }
{id:'nav', w:'100%', height:G.v(3) }
{id:'content', w:'100%', height:'auto'}
]
###
grid: (test = 'fail') ->
alert test
BP this
# called on item only the first time...
setup: (el) ->
$el = $(el)
is_zone = $el['is']('[data-g-zone]')
grid = G.addGrid( _.defaults({el:el}, this.attributes) )
this.grid = grid
update: (el) ->
grid = this.grid
if this.setupOnce
grid.set this.attributes
grid.setup()
this.setupOnce = true
#if is_zone
#else
$.plugins.add
name: 'childrenHeight'
returnsValue: true
get: (el) ->
$el = $(el)
children = $el.children()
currentHeight = 0
_.each children, (child) ->
$child = $(child)
h = $child.position().top + $child.height()
if h > currentHeight then currentHeight = h
return currentHeight
$.plugins.add
name: 'childrenWidth'
returnsValue: true
get: (el) ->
$el = $(el)
children = $el.children()
currentWidth = 0
_.each children, (child) ->
$child = $(child)
w = $child.position().left + $child.width()
if w > currentWidth then currentWidth = w
return currentWidth
###
* JQuery Plugin: "EqualHeights"
* by: Scott Jehl, Todd Parker, Maggie Costello Wachs (http://www.filamentgroup.com)
*
* Copyright (c) 2008 Filament Group
* Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php)
*
* Description: Compares the heights or widths of the top-level children of a provided element
and sets their min-height to the tallest height (or width to widest width). Sets in em units
by default if pxToEm() method is available.
* Dependencies: jQuery library, pxToEm method (article:
http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/)
* Usage Example: $(element).equalHeights();
Optional: to set min-height in px, pass a true argument: $(element).equalHeights(true);
* Version: 2.0, 08.01.2008
$.fn.equalHeights = function(px) {
$(this).each(function(){
var currentTallest = 0;
$(this).children().each(function(i){
if ($(this).height() > currentTallest) { currentTallest = $(this).height(); }
});
if (!px || !Number.prototype.pxToEm) currentTallest = currentTallest.pxToEm(); //use ems unless px is specified
// for ie6, set height since min-height isn't supported
if ($.browser.msie && $.browser.version == 6.0) { $(this).children().css({'height': currentTallest}); }
$(this).children().css({'min-height': currentTallest});
});
return this;
};
###
# Global Variables
# ====================
# Global variable scoping, setup for use with either CommonJs or Node.js!
# Coffeescript limits global variable polluting, so if one is needed, just use root.(variabile name)
# - [stackoverflow](http://stackoverflow.com/questions/4214731/coffeescript-global-variables)
root = exports ? this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment