Created
September 15, 2011 02:33
-
-
Save d4tocchini/1218383 to your computer and use it in GitHub Desktop.
G.coffee
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
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