CoffeeScript
how to decrease your daily {} count
Titus Stone
automatic var
x = 10
var x;
x = 10
()'s are optional*
x = add 1, 15
var x = add(1, 15);
* = except when there are no arguments
string interpolation
msg = "Hello #{adjective} world"
var msg = "Hello "+ adjective +" world";
skinny functions
+ automatic return
add = (x,y) -> x + y
var add = function(x, y) {
return x + y;
};
{}'s are optional:
block is implied by indentation
get_urls_from_sitemap = (domain) ->
xml = http_get("http://#{domain}/sitemap.xml")
parse_sitemap(xml)
var getUrlsFromSitemap = function(domain) {
var xml = httpGet("http://"+ domain +"/sitemap.xml");
return parseSitemap(xml);
};
Real World:
jasmine tests read way cleaner
it "should be true", ->
expect(true).toBeTrue()
it("should be true", function(){
expect(true).toBeTrue();
});
default values
call_search_api = (query, domain=".com") ->
# ...
var callSearchApi = function(query, domain) {
if (typeof domain === 'undefined') { domain = '.com'; }
}
nice if/then/unless syntax*
do_something() unless x
if y
do_this()
else
do_that()
if (x) { doSomething(); }
if (y) {
doThis();
} else {
doThat();
}
* = there is no `end` keyword. This will mess you up all day long.
class keyword
class Vehicle
move: (feet) ->
# ...
var Vehicle = function(){};
Vehicle.prototype.move = function(feet) {
// ...
};
@ references the instance
class Vehicle
move: (feet) ->
@open_throttle() if @is_started
var Vehicle = function(){};
Vehicle.prototype.move = function(feet) {
if (this.isStarted) {
this.openThrottle();
}
};
fat arrow maintains context
class Button
constructor: ->
@url = '/api/places'
get_places: (e) ->
$.getJson @url, (json, xhr) =>
console.log "#{@url} has been fetched and was #{json}"
...including on class methods
class Button
constructor: (options={}) ->
$('.button').click @on_click
on_click: (e) =>
if @options.whatever
# ...
automatic instance assignment
class Whatever
constructor: (url) ->
@url = url
class Whatever
constructor: (@url) ->
inheritance works like a boss
class Vehicle
constructor: (options={}) ->
move: (feet) ->
@open_throttle() if @started
class Car < Vehicle
move: (feet) ->
@release_brakes()
super()
Part 2:
My almost framework-less approach to structured front-end code
Most UI code does the same thing:
Initialize:
- take in some options (model)
- find some DOM elements (view)
- bind some events (view)
Lifecycle:
- react to events (controller)
- execute business logic (model)
- update the UI (view)
take in some options (model)
class NewsletterSubscribe
constructor: (@options={}) ->
find some DOM elements (view)
constructor: (@options={}) ->
@init_elements()
init_elements: ->
@button = $(@options.button)
@email_input = $(@options.email_input)
bind some events (view)
constructor: (@options={}) ->
@bind_events()
bind_events: ->
@button.click @on_click
react to some events (controller) and execute business logic (model)
on_click: (e) =>
post_email @email_input.val()
post_email: (email)
$.post @options.url, =>
@success_notification()
update the UI (view)
success_notification: ->
alert "You are now subscribed!"
Altogether, grouped
class NewsletterSubscribe
# -- setup -----------------
constructor: (@options={}) ->
@init_elements()
@bind_events()
init_elements: ->
@button = $(@options.button)
@email_input = $(@options.email_input)
bind_events: ->
@button.click @on_click
# -- logic ------------------
post_email: (email)
$.post @options.url
# -- view -------------------
success_notification: ->
alert "You are now subscribed!"
# -- events ------------------
on_click: (e) =>
post_email @email_input.val(), =>
@success_notification()
But half of that is boilerplate
- we know we're usually going to initialize elements
- we know we're usually going to bind events
- we know that bound events will be fairly similar
What if we introduced a convention about how we describe elements?
data-is = this element IS this
data-does = this element DOES this
HTML
<div class="newsletter-subscribe">
<p>Subscribe to our newsletter!</p>
<label>Email</label>
<input type="text" data-is="email" placeholder="you@example.com" />
<button data-does="subscribe">Subscribe</button>
</div>
Initialization
:javascript
new NewsletterSubscribe({
email: '[data-is="email"]',
subscribe: '[data-does="subscribe"]'
});
A common class could scan for these
class UiElement
constructor: (@options={}) ->
for key in @options
if typeof key === 'string'
and (key.indexOf('data-is') > -1
or key.indexOf('data-does') > -1)
@[key] = $(@options[key])
@init_elements() if typeof @init_elements === 'function'
@bind_events() if typeof @bind_events === 'function'
Our NewsletterSubscribe class gets shorter
class NewsletterSubscribe < UiElement bind_events: -> @button.click @on_click post_email: (email) $.post @options.url, => @success_notification() success_notification: -> alert "You are now subscribed!" on_click: (e) => post_email @email.val()