Write markup on the final frontier
SpacePen is a powerful but minimalistic client-side view framework for CoffeeScript. It combines the "view" and "controller" into a single jQuery object, whose markup is expressed with an embedded DSL similar to Markaby for Ruby.
This version of SpacePen depends on HTML 5 custom elements to support lifecycle
hooks that previously depended on all DOM manipulation being performed via
beforeRemove hooks have been replaced with
detached and their semantics have been altered.
If you need to use SpacePen in an environment that doesn't support custom elements, consider using the previous major version or switching frameworks.
View objects extend from the View class and have a @content class method where you express their HTML contents with an embedded markup DSL:
: ->@div =>@h1 "Spacecraft"@ol =>@li "Apollo"@li "Soyuz"@li "Space Shuttle"
Views descend from jQuery's prototype, so when you construct one you can call
jQuery methods on it just as you would a DOM fragment created with
view =viewfind'ol'append'<li>Star Destroyer</li>'viewon 'click''li'->alert "They clicked on "
But SpacePen views are more powerful than normal jQuery fragments because they let you define custom methods:
: -> ...:@find'ol'append "<li></li>"view =viewaddSpacecraft "Enterprise"
You can also pass arguments on construction, which get passed to both the
@content method and the view's constructor.
:@div =>@h1 paramstitle@ol =>@li name for name in paramsspacecraftview = title: "Space Weapons"spacecraft: "TIE Fighter""Death Star""Warbird"
Methods from the jQuery prototype can be gracefully overridden using
: -> ...: ->consolelog "Hiding Spacecraft List"super
If you override the View class's constructor, ensure you call
Alternatively, you can define an
initialize method, which the constructor will
call for you automatically with the constructor's arguments.
: -> ...:@title = paramstitle
SpacePen will automatically create named reference for any element with an
outlet attribute. For example, if the
ol element has an attribute
outlet=list, the view object will have a
list entry pointing to a jQuery
wrapper for the
: ->@div =>@h1 "Spacecraft"@ol outlet: "list"=>@li "Apollo"@li "Soyuz"@li "Space Shuttle":@listappend"<li></li>"
Elements can also have event name attributes whose value references a custom
method. For example, if a
button element has an attribute
click=launchSpacecraft, then SpacePen will invoke the
method on the button's parent view when it is clicked:
: ->@div =>@h1 "Spacecraft"@ol =>@li click: 'launchSpacecraft'"Saturn V":consolelog "Preparing for launch!"
As you've seen so far, the markup DSL is pretty straightforward. From the
@content class method or any method it calls, just invoke instance methods
named for the HTML tags you want to generate. There are 3 types of arguments you
can pass to a tag method:
Strings: The string will be HTML-escaped and used as the text contents of the generated tag.
Hashes: The key-value pairs will be used as the attributes of the generated tag.
Functions (bound with
=>): The function will be invoked in-between the open and closing tag to produce the HTML element's contents.
If you need to emit a non-standard tag, you can use the
method to name the tag with a string:
@tag 'bubble'type: "speech"=> ...
@text(string): Emits the HTML-escaped string as text wherever it is called.
@raw(string): Passes the given string through unescaped. Use this when you need to emit markup directly that was generated beforehand.
Subviews are a great way to make your view code more modular. The
@subview(name, view) method takes a name and another view object. The view
object will be inserted at the location of the call, and a reference with the
given name will be wired to it from the parent view. A
will be created on the subview pointing at the parent.
:@div =>@subview 'launchController'countdown: paramscountdown@h1 "Spacecraft"...
You don't need a View class to use the SpacePen markup DSL. Call
with an unbound function (
=>) that calls tag methods, and it will
return a document fragment for ad-hoc use. This method is also assigned to the
$$ global variable for convenience.
viewlistappend $$ ->@li =>@text "Starship"@em "Enterprise"
You can retrieve the view object for any DOM element by calling
view() on it.
This usually shouldn't be necessary, as most DOM manipulation will take place
within the view itself using outlet references, but is occasionally helpful.
view =$'body'appendview# assuming no other li elements on the DOM, for example purposes,# the following expression should be true$'li'view == view
initialize method is always called when the view is still a detached DOM
fragment, before it is appended to the DOM. This is usually okay, but
occasionally you'll have some initialization logic that depends on the view
actually being on the DOM. For example, you may depend on applying a CSS rule
before measuring an element's height.
For these situations, use the
attached hook. It will be called whenever your
element is actually attached to the DOM. Past versions of SpacePen would also
call this hook when your element was attached to another detached node, but that
behavior is no longer supported.
To be notified when your element is detached from the DOM, implement the
: -> ...: ->consolelog "With CSS applied, my height is"@height: ->consolelog "I have been detached."
git clone https://github.com/atom/space-pen.gitcd space-pennpm installnpm start
- Open http://localhost:1337 to run the specs
- Open http://localhost:1337/benchmark to run the benchmarks
- Open http://localhost:1337/examples to browse the examples