It's always nice to come home after a tired day, sit down, make a tea and do some CoffeeScript to calm yourself down. It's like really your first frozen girlfriend. You do whatever you want and you even can peep under the skirt and it's still stable.
I'm still experimenting the language but it's already obvious how easy to work with a well structured syntax. The old cluttered function macaroni is now a clean (more or less) class collection. I've got some taste from the flexibility of CoffeeScript as well - how you can define the exact same thing in different ways. (Wait, isn't that JavaScript? Ups.)
So I cleaned the daily report cutter and added a little. I had to realize it's tough to make a UI controller. I wanted to use a library but I haven't found anything that supports multiple scrollers on the same controller.
A little aperitif from the hipster colored demo:
The current structure is very raw. We have a Label class that is responsible about the label items and their data:
class Label
@labels: []
@width: 100
html: null
constructor: (position) ->
Label.labels.push(this)
@html = jQuery('<input type="textfield" value="activity"/>')
jQuery('#label_bar').append(@html)
jQuery(@html).change -> StageManager.updateReport()
StageManager.updateElements()
offsetX: () ->
jQuery(@html).offset().left
setOffsetX: (offsetX) ->
jQuery(@html).css('left', offsetX)
kill: () ->
jQuery(@html).remove()
_this = @
Label.labels = Label.labels.filter (l) -> l != _this
text: () ->
jQuery(@html).val()
The other class is for the divider:
class Divider
@dividers: []
@width: 10
@widthHalf: @width >> 1
@counter: 0
@activeDivider: null
html: null
count: @counter++
constructor: (coordX) ->
dividerWidthHalf = Divider.width >> 1
Divider.dividers.push(this)
@html = jQuery('<div class="divider"/>')
@html.attr('id', 'divider_' + Divider.counter)
@setOffsetX(coordX - dividerWidthHalf)
@html.mousedown (e) =>
@mouseDown(e)
@html.click (e) =>
e.stopPropagation()
@html.dblclick StageManager.doubleClickOnDivider
jQuery('#time_bar').append(@html)
Divider.activeDivider = this
Divider.counter++
offsetX: () ->
jQuery(@html).offset().left
setOffsetX: (offsetX) ->
@html.css('left', offsetX)
mouseDown: (e) ->
Divider.activeDivider = @
e.stopPropagation()
getHTMLAttr: (param) ->
jQuery(@html).attr(param)
kill: () ->
_this = @
Divider.dividers = Divider.dividers.filter (d) -> d != _this
jQuery(@html).remove()
index: () ->
for divider, i in Divider.dividers
if divider == @
return i
-1
@getByID: (id) ->
for divider in Divider.dividers
if divider.getHTMLAttr('id') == id
return divider
null
@sort: () ->
Divider.dividers.sort (a, b) ->
jQuery(a.html).offset().left > jQuery(b.html).offset().left
It's a bit more but on the UI it also more in the central of interactions. Having these 2 objects on the scene is not really enough. When adding dividers you want to adjust on the label set also. And the logical solution is a higher level controller - that's the stage manager:
class StageManager
@updateElements: () ->
labelWidthHalf = Label.width >> 1
if Divider.dividers.length == 0
bar_width_half = jQuery('#label_bar').width() >> 1
Label.labels[0].setOffsetX(bar_width_half - labelWidthHalf)
else
prev_x = 0
for divider, i in Divider.dividers
divider_x = divider.offsetX()
label_x = (divider_x + prev_x) * 0.5 - labelWidthHalf
Label.labels[i].setOffsetX(label_x)
prev_x = divider_x
label_i = Label.labels[i]
label_i.setOffsetX((jQuery('#label_bar').width() + prev_x) * 0.5 - labelWidthHalf)
@updateReport()
@updateReport: ->
jQuery('#report').html('<ol />')
for label in Label.labels
text = label.text()
jQuery('#report ol').append('<li>' + text + ' ' + label.timeLabel() + '</li>')
@moveBarMouseMove: (e) ->
if Divider.activeDivider
Divider.activeDivider.setOffsetX(e.offsetX - Divider.widthHalf)
@timeBarMouseUp: () ->
Divider.activeDivider = null
Divider.sort()
StageManager.updateElements()
@onClickTimeBar: (e) ->
new Divider(e.offsetX)
new Label(0)
@doubleClickOnDivider: (e) ->
id = jQuery(e.delegateTarget).attr('id')
divider_to_kill = Divider.getByID(id)
idx_of_divider = divider_to_kill.index()
Label.labels[idx_of_divider].kill()
divider_to_kill.kill()
@getTimeInterval: () ->
{
start: 540,
end: 1080
}
@timeBarWidth: ->
jQuery('#label_bar').width()
It cares about the high level events, layout of labels and dividers and the app lifecycle. The whole app initialization is pretty simple:
jQuery ->
jQuery('#time_bar').click StageManager.onClickTimeBar
jQuery('#time_bar').mouseup StageManager.timeBarMouseUp
jQuery('#move_layer').mousemove StageManager.moveBarMouseMove
new Label()
And I'm sure you think it's not the best structure - and I agree. That's the brilliant :) You still can make it better. It's just a bit tricky when you're full with the dinner and want to watch the same adventure time episode again and again. Check out the current source on GitHub.
---
Peter


No comments:
Post a Comment
Note: only a member of this blog may post a comment.