gridy.coffee | |
---|---|
Values to be used if not provided by instance config | default_confing =
rows : 1
cols : 1
transSpeed : 500
index : 0
data : []
controls : true
focusedClass: 'focused'
orientation : 'horizontal'
onChange : (elem)->
onExit : (direction)->
selectors :
container: 'ul'
wrapper : '.wrapper'
content : '.content'
focused : '.focused'
elem : 'li',
page : '.page'
tpl : 'script[type="text/tpl"]' |
Content block CSS styles | content_css =
position: 'relative'
height : '100%' |
Wrapper block CSS styles | wrapper_css =
overflow: 'hidden'
margin : 0
padding : 0 |
Page block CSS styles | page_css =
margin : 0
padding: 0
float : 'left' |
Element block CSS styles | elem_css =
float : 'left'
'list-style': 'none' |
Splitting array into pages | paginate = (arr, size) ->
size = size or arr.length
copy_arr = arr.slice(0)
copy_arr.splice(0, size) while copy_arr.length |
Simple loggers. | log = (o) -> console?.log o
dir = (o) -> console?.dir o |
Guessing what prefix to use for internal transition | transition = ''
for trans in ['WebkitTransition', 'MozTransition', 'OTransition', 'MsTransition', 'transition']
if typeof document.body.style[trans] == 'string'
transition = trans |
Interntal templating function, can be replaced in config | tpl = (str) ->
str = str.replace('data-src', 'src')
(data) ->
res = ''
for val in data
res += str.replace(/\{\{:(\w*)\}\}/g, (s, p) -> val[p] or s)
res |
Selector function | sel = (elem, sel) -> elem.querySelector(sel) |
Select function for multiple elements | selAll = (elem, sel) ->
elements = []
all = elem.querySelectorAll(sel)
for val in all
elements.push val if val instanceof Element
elements |
Getting "total" height of element | heightOf = (elem) ->
top = getComputedStyle(elem, null).getPropertyValue('margin-top')
bottom = getComputedStyle(elem, null).getPropertyValue('margin-bottom')
elem.offsetHeight + parseFloat(top) + parseFloat(bottom) |
Getting "total" width of element | widthOf = (elem) ->
right = getComputedStyle(elem, null).getPropertyValue('margin-right')
left = getComputedStyle(elem, null).getPropertyValue('margin-left')
elem.offsetWidth + parseFloat(left) + parseFloat(right) |
Setting CSS style attribute | css = (elem, styles) ->
style = []
if elem instanceof Element
for key, val of styles
style.push "#{key}: #{val}"
elem.setAttribute 'style', style.join ';'
elem |
Gridy.js class definition goes here | class @Gridy
constructor: (component_sel, options = {}) ->
@options = {} |
Merging passed options with default | for key, val of default_confing
@options[key] = val
for key, val of options
@options[key] = val
@el = sel(document, component_sel)
@wrapper = sel @el, @options.selectors.wrapper
@content = sel @el, @options.selectors.content
tpl_str = sel @el, @options.selectors.tpl |
Using intertal templating factory if not provided in config | unless @options.tpl
@options.tpl = tpl tpl_str.innerHTML |
Using intertal animation function if not provided in config | unless @options.animate
@options.animate = (direction, val, speed) =>
@content.style[transition] = "#{speed}ms ease-in-out #{direction}"
@content.style[direction] = "#{val}px" |
Inserting data from config | @insert @options.data if @options.data |
Setting up controls if required by config | if options.controls
controls = []
if options.cols > 1 or options.rows is 1
controls.push 'left'
controls.push 'right'
if options.rows > 1
controls.push 'up'
controls.push 'down'
for val in controls
c = document.createElement('span')
c.className = "#{val} control"
c.innerHTML = val
c.direction = val
c.addEventListener 'click', (e)=> @move e.currentTarget.direction
@el.appendChild c
return this |
Method in charge of DOM build process from data array | insert : (data)->
if data.length |
Paging data | page_size = @options.cols * @options.rows
paged = '' |
Building pages DOM | for val in paginate data, page_size
paged += '<ul class="page">' + (@options.tpl val) + '</ul>'
@content.innerHTML = paged if paged
@elements = selAll @content, @options.selectors.elem
@pages = selAll @content, @options.selectors.page
first_page = @pages[0]
first_elem = @elements[0] |
Setting up styles for content and wrapper | css @content, content_css;
css @wrapper, wrapper_css; |
Setting up styles for elements | if @elements
for val in @elements
css val, elem_css
row_height = heightOf first_elem
col_width = widthOf first_elem |
Setting up page dimentions | if @pages
for val in @pages
css val, page_css
val.style.height = "#{row_height * @options.rows}px"
val.style.width = "#{col_width * @options.cols}px"
@wrapper.style.width = "#{widthOf first_page}px"
@wrapper.style.height = "#{heightOf first_page}px"
if @options.orientation is 'horizontal'
@content.style.width = "#{@pages.length * widthOf first_page}px"
else
@content.style.height = "#{@pages.length * heightOf first_page}px" |
Setting data-c and data-r attributes, based on element physical "natural" position. So we are able to navigate between elements as we see them | rows = {}
cols = {}
r = 0
c = 0 |
Mapping elements into rows and columns based on their offset coords. | for val in @elements
rows[val.offsetTop] = [] unless rows[val.offsetTop]
cols[val.offsetLeft] = [] unless cols[val.offsetLeft]
cols[val.offsetLeft].push val
rows[val.offsetTop].push val |
Setting up attributes | for key, val of rows
for key, el_val of val
el_val.setAttribute "data-r", r
r += 1
for key, val of cols
for key, el_val of val
el_val.setAttribute "data-c", c
c += 1 |
Initial focus, without animation | @focus @elements[@options.index], 0
return @ |
Changing active position using direction param | move : (direction)->
focused = sel @content, @options.selectors.focused;
if focused
r = parseInt focused.getAttribute "data-r"
c = parseInt focused.getAttribute "data-c"
switch direction
when "left"
c -= 1
when "right"
c += 1
when "up"
r -= 1
when "down"
r += 1
else
next = sel @content, "[data-r='#{r}'][data-c='#{c}']" |
If destinatin element is avaliable then do focus on it. | if next
next.classList.add @options.focusedClass
focused.classList.remove @options.focusedClass
@focus next, @options.transSpeed
else |
If not then triggring callback. For instance we can foucs another control in this situation | @options.onExit direction
return @ |
Changing active element's focus | focus: (focused, speed = 0)->
focused.classList.add(@options.focusedClass);
page = focused.parentElement
if @options.orientation is "horizontal"
@options.animate 'left', -page.offsetLeft, speed
else
@options.animate 'top', -page.offsetTop, speed |
Triggering onChange callback and passing focused param | @options.onChange focused
return @
|