Sunday, January 15, 2012

Lesson 4: Introducing classes

Suppose we have one state-of-the-art html content representing 5 colorful frogs sitting on a big leaf:
<html>
<head>
<style>
div.green-leaf div {border: 1px solid white; height: 38px; width: 38px; position: absolute}
</style>
</head>
<body>
<div class='green-leaf' style='background: #458008; position: relative; width: 200px; height: 200px' >
<div id='ani3_bar0' style='background: #FF621D; left: 0; top:0'></div>
<div id='ani3_bar1' style='background: #B1D02C; left: 0; top:160px'></div>
<div id='ani3_bar2' style='background: #D5FA6F; left: 160px; top:160px'></div>
<div id='ani3_bar3' style='background: #89C128; left: 160px; top:0'></div>
<div id='ani3_bar4' style='background: #B42002; left: 80px; top:80px'></div>
</div>
<input class='button' id='ani3_select' type='button' value='Select divs'>
</body>
</html>
view raw leaf-div.html hosted with ❤ by GitHub
Ok, the markup is bulky, but it still looks great, right?
We could select those divs and put them into variables like 'frog1', 'frog2' etc, but we're no kids anymore. Instead, we put them in one place, and use some cool techniques at the same time!

The first line enumerates the integer range [0..4] and for every n creates an id selector via string interpolation (yep, that #{n} in "#ani3_bar#{n}" puts in a value ). 5 query results are collected and stored to 'bars' variable. Lookup Loops and Comprehensions for more. Short and readable, but it makes 5 id queries instead of one. If we were obsessed with execution speed, that would be bad, so there's a line with a single query too.
Same form of iteration over an array to change every element's attributes.
#Array comprehension, here it goes!
bars = ($j "#ani3_bar#{n}" for n in [0..4])
# Could be done with CSS id selectors, which is
# kinda fast, but has to be 'jQueryfied' for our needs,
# i.e. every HTMLDivElement turned to a jQuery object:
bars = ( $j(e) for e in $j('[id*=ani3_bar]') )
$j('#ani3_select').click ->
b.css('border-color', 'red') for b in bars
From now on I'm going to omit some usual stuff, like that line '$j=jQuery' at the beginning of every script, but you know it's there :)

Now we know how to define functions, let's be nice and provide our frogs with capability to jump around. Check out the four functions' different syntax, meaning totally the same. Pick the flavor that suits you at the moment.
# Put it in a line
up = (bar) ->
bar.animate(top:'-=40', height:78).animate height:38
# Or use indentation instead of parenthesis
down = (bar) ->
bar.animate
height: 78
.animate
top: '+=40'
height: 38
# Keep the syntax clean
right = (bar) ->
bar.animate(width:78).animate left: '+=40', width:38
# Or put all the brackets possible
# (but that's not what CS is about ;)
left = (bar) ->
bar.animate({left:'-=40',width:78}).animate({width:38})
# All for definitions are valid and usable:
$j('#ani3_jump_around').click ->
left down right right up up left left down right bars2[4]
Good. Now unleash the frog!
Wow! It surely is enjoyable to see it frolick. But does it go the right way?
Our jumping functions are stackable, cause they accept and return jQuery objects. That's the good part. But the frog goes doesn't start with 'left down right' as somebody less experienced with operations priority concept may expect from our click handler code. The first effective call is on the right, the string should be read like this:

left(down(right(right(up(up(left(left(down(right(bars2[4]))))))))))

We just drop the brackets for the obvious reasons ( like because we can ).

Would be nice to have directed jump functions behaving like jQuery animate(), being able to append function calls next to each other and seeing them execute in the same order. For that we need them to return something that also has all four functions inside. ( Instance methods instantly come to my mind, what do you think? )

Normally, using just JavaScript, to get many objects behave uniformly we'd need to understand how JS prototypes work. Blessed are those who do. I still find that somewhat tricky. But today we can relax and do it the good old OOP way - define a class. And once again, we'll write less code for that!
CS classes documentation, just a friendly reminder :)

Ok, lets do it:

class Frog
constructor: (@obj) ->
up: ->
@obj.animate(top:'-=40',height:78).animate height:38
down: ->
@obj.animate(height:78).animate top:'+=40',height:38
right: ->
@obj.animate(width:78).animate left:'+=40', width:38
left: ->
@obj.animate(left:'-=40', width:78).animate width:38

Wait, that's not cool. All we did is was wrapping functions code with a class declaration. Yes, we got something by this: now we can queue our jumps more naturally, like
frog.right().down().left() etc.
But that's just not cool enough. There are reasons to change it:

  • Code duplication. Four very similar lines of code, with the only defference in animate() arguments.
  • The main asset of every instance is created outside and gets inside as a constructor argument. But, unlike proper decorator implementations, class code relies on the assumption that animated objects are made just the way the class needs to work, without checking it. The class is unseparable from html outside - every time one is updated, the other has to be re-checked.
  • Methods could accept path length. Much gain easily with our new OOP powers.

To git rid of duplication, let's put all the repeated code in one method, say 'jump', that will accept direction as argument and use predefined arguments for animate(). A key-value map with 'r','l','u','d' for keys should do fine. Then we add another argument to the new jump methods and put animation logic in a loop. Then transform right,left, up and down into convenience methods, each of them now calls jump with proper direction argument. Done.

Next - the constructor. Let it accept what varies in those html lines declaring frogs' bodies and inject it into a static html line with string interpolation, then use jQuery to create new element from html, then append it to a container ( another div in our case - the square green leaf ) and store it as the only variable of the newly created class instance. Done. The whole block of html lines flattens to just one, letting us concentrate on frogs' color and position.

Looks a bit different:

class Frog
constructor: ([color, left, top], leaf) ->
@obj = $j """<div style='background: #{color};
left: #{left}px;
top:#{top}px;
border: 1px solid white;
height: 38px; width: 38px; position: absolute;'></div>
"""
@obj.appendTo leaf
$aniArgs =
u: [{top: '-=40', height: 78},{height: 38}]
d: [{height: 78},{top: '+=40', height: 38}]
l: [{left: '-=40', width: 78}, {width: 38}]
r: [{width: 78}, {left: '+=40', width: 38}]
jump: (dir, s) ->
@obj
.animate($aniArgs[dir][0])
.animate($aniArgs[dir][1]) for i in [1..s]
this
right: (n) -> this.jump 'r', n
left: (n) -> this.jump 'l', n
up: (n) -> this.jump 'u', n
down: (n) -> this.jump 'd', n
frogs = ( new Frog( params, '#leaf' ) for params in [
['#FF621D', 0, 0]
['#B1D02C', 0, 160]
['#D5FA6F', 160, 160]
['#89C128', 160, 0]
['#B42002', 80, 80]
])
$j('#ani3_long_jumps').click ->
frogs[0].down(4).right(4).up(4).left(4)
frogs[1].right(4).up(4).left(4).down(4)
frogs[2].up(4).left(4).down(4).right(4)
frogs[3].left(4).down(4).right(4).up(4)

Let's see if it works.

Not bad. Readable set of just 4 instructions for each unit, predictable result. In fact, they are so perfectly organized one may even think they are not free-minded lively frogs, but programmed robots!

On the other hand, to heck with direction-named methods, compact path strings are the best for a case like this. Look at this:

move: (path) ->
@obj
.animate($aniArgs[dir][0])
.animate($aniArgs[dir][1]) for dir in path
this
frogs[i].move(path) for path, i in [
'ddddrrrull'
'rrrulluuur'
'lulluuurrd'
'lldrddlluu'
'dluuurrddd']
One character per jump - and still readable. Next would be programming in absolute code! (Just kidding)

Time to stop - it's a bit overdone already. Of course, we love these frisky little creatures, but now it's time to move on to the next adventure!

Source code links:

No comments :

Post a Comment