Use $$ -> to render ad-hoc document fragments

Also eliminate stdlib/template directory which held code related to
SpacePen's precursor framework.
This commit is contained in:
Nathan Sobo 2012-02-06 16:19:35 -07:00
parent c712f598e5
commit bb640dd342
9 changed files with 48 additions and 238 deletions

View File

@ -1,14 +1,14 @@
$$ = require 'template/builder'
{$$} = require 'space-pen'
nakedLoad 'jasmine'
nakedLoad 'jasmine-html'
nakedLoad 'jasmine-focused'
$ = require 'jquery'
$('head').append $$.render ->
$('head').append $$ ->
@link rel: "stylesheet", type: "text/css", href: "static/jasmine.css"
$('body').append $$.render ->
$('body').append $$ ->
@div id: 'jasmine_runner'
@div id: 'jasmine-content'

View File

@ -1,75 +0,0 @@
Builder = require 'template/builder'
describe "Builder", ->
builder = null
beforeEach -> builder = new Builder
describe "tag class methods", ->
it "calls render, assuming the arguments to the current method as the first tag", ->
fragment =
Builder.div ->
@ol class: 'cool-list', =>
@li()
@li()
expect(fragment).toMatchSelector('div')
expect(fragment.find('ol.cool-list')).toExist()
expect(fragment.find('li').length).toBe 2
describe "@render", ->
it "runs the given function in a fresh builder instance and returns the resulting view fragment", ->
fragment =
Builder.render ->
@div =>
@ol class: 'cool-list', =>
@li()
@li()
expect(fragment).toMatchSelector('div')
expect(fragment.find('ol.cool-list')).toExist()
expect(fragment.find('li').length).toBe 2
describe ".tag(name, args...)", ->
it "can generate simple tags", ->
builder.tag 'div'
expect(builder.toHtml()).toBe "<div></div>"
builder.reset()
builder.tag 'ol'
expect(builder.toHtml()).toBe "<ol></ol>"
it "can generate tags with content", ->
builder.tag 'ol', ->
builder.tag 'li'
builder.tag 'li'
expect(builder.toHtml()).toBe "<ol><li></li><li></li></ol>"
it "can generate tags with text", ->
builder.tag 'div', "hello"
expect(builder.toHtml()).toBe "<div>hello</div>"
builder.reset()
builder.tag 'div', 22
expect(builder.toHtml()).toBe "<div>22</div>"
it "HTML escapes tag text", ->
builder.tag('div', "<br/>")
expect(builder.toHtml()).toBe "<div>&lt;br/&gt;</div>"
it "can generate tags with attributes", ->
builder.tag 'div', id: 'foo', class: 'bar'
fragment = builder.toFragment()
expect(fragment.attr('id')).toBe 'foo'
expect(fragment.attr('class')).toBe 'bar'
it "can generate self-closing tags", ->
builder.tag 'br', id: 'foo'
expect(builder.toHtml()).toBe '<br id="foo">'
describe ".raw(text)", ->
it "does not escape html entities", ->
builder.raw '&nbsp;'
expect(builder.toHtml()).toBe '&nbsp;'

View File

@ -1,4 +1,4 @@
{View} = require 'space-pen'
{View, $$} = require 'space-pen'
Buffer = require 'buffer'
Point = require 'point'
Cursor = require 'cursor'
@ -7,7 +7,6 @@ Highlighter = require 'highlighter'
Range = require 'range'
$ = require 'jquery'
$$ = require 'template/builder'
_ = require 'underscore'
module.exports =
@ -108,13 +107,14 @@ class Editor extends View
buildLineElement: (row) ->
tokens = @highlighter.tokensForRow(row)
$$.pre class: 'line', ->
if tokens.length
for token in tokens
classes = token.type.split('.').map((c) -> "ace_#{c}").join(' ')
@span { class: token.type.replace('.', ' ') }, token.value
else
@raw '&nbsp;'
$$ ->
@pre class: 'line', =>
if tokens.length
for token in tokens
classes = token.type.split('.').map((c) -> "ace_#{c}").join(' ')
@span { class: token.type.replace('.', ' ') }, token.value
else
@raw '&nbsp;'
setBuffer: (@buffer) ->
@highlighter = new Highlighter(@buffer)

View File

@ -1,7 +1,6 @@
Cursor = require 'cursor'
Range = require 'range'
{View} = require 'space-pen'
$$ = require 'template/builder'
{View, $$} = require 'space-pen'
module.exports =
class Selection extends View
@ -52,7 +51,7 @@ class Selection extends View
else
css.right = 0
region = $$.div(class: 'selection').css(css)
region = ($$ -> @div class: 'selection').css(css)
@append(region)
@regions.push(region)

View File

@ -1,105 +0,0 @@
_ = require 'underscore'
$ = require 'jquery'
OpenTag = require 'template/open-tag'
CloseTag = require 'template/close-tag'
Text = require 'template/text'
module.exports =
class Builder
@render: (fn) ->
builder = new this
fn.call(builder)
builder.toFragment()
@elements:
normal: 'a abbr address article aside audio b bdi bdo blockquote body button
canvas caption cite code colgroup datalist dd del details dfn div dl dt em
fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup
html i iframe ins kbd label legend li map mark menu meter nav noscript object
ol optgroup option output p pre progress q rp rt ruby s samp script section
select small span strong style sub summary sup table tbody td textarea tfoot
th thead time title tr u ul video'.split /\s+/
void: 'area base br col command embed hr img input keygen link meta param
source track wbr'.split /\s+/
@allElements: ->
@elements.normal.concat(@elements.void)
@buildTagClassMethod: (tagName) ->
this[tagName] = (args...) ->
@render ->
argsWithBoundFunctions = args.map (arg) =>
if _.isFunction(arg)
_.bind(arg, this)
else
arg
@tag(tagName, argsWithBoundFunctions...)
@buildTagInstanceMethod: (tagName) ->
@prototype[tagName] = (args...) -> @tag(tagName, args...)
@allElements().forEach (tagName) => @buildTagClassMethod(tagName)
@allElements().forEach (tagName) => @buildTagInstanceMethod(tagName)
constructor: ->
@reset()
toHtml: ->
_.map(@document, (x) -> x.toHtml()).join('')
toFragment: ->
fragment = $(@toHtml())
@wireOutlets fragment
fragment.find('*').andSelf().data('view', fragment)
fn(fragment) for fn in @postProcessingFns
fragment
tag: (name, args...) ->
options = @extractOptions(args)
@document.push(new OpenTag(name, options.attributes))
if @elementIsVoid(name)
if (options.text? or options.content?)
throw new Error("Self-closing tag #{name} cannot have text or content")
else
options.content?()
@text(options.text) if options.text
@document.push(new CloseTag(name))
subview: (outletName, subview) ->
subviewId = _.uniqueId('subview')
@tag 'div', id: subviewId
@postProcessingFns.push (view) ->
view[outletName] = subview
subview.parentView = view
view.find("div##{subviewId}").replaceWith(subview)
elementIsVoid: (name) ->
name in @constructor.elements.void
extractOptions: (args) ->
options = {}
for arg in args
options.content = arg if _.isFunction(arg)
options.text = arg if _.isString(arg)
options.text = arg.toString() if _.isNumber(arg)
options.attributes = arg if _.isObject(arg) and not _.isFunction(arg)
options
text: (string) ->
@document.push(new Text(string))
raw: (string) ->
@document.push(new Text(string, true))
wireOutlets: (view) ->
view.find('[outlet]').each ->
elt = $(this)
outletName = elt.attr('outlet')
view[outletName] = elt
reset: ->
@document = []
@postProcessingFns = []

View File

@ -1,7 +0,0 @@
module.exports =
class CloseTag
constructor: (@name) ->
toHtml: ->
"</#{@name}>"

View File

@ -1,12 +0,0 @@
_ = require 'underscore'
module.exports =
class OpenTag
constructor: (@name, @attributes) ->
toHtml: ->
"<#{@name}#{@attributesHtml()}>"
attributesHtml: ->
s = _.map(@attributes, (value, key) -> "#{key}=\"#{value}\"").join(' ')
if s == "" then "" else " " + s

View File

@ -1,15 +0,0 @@
module.exports =
class Text
constructor: (@string, @raw=false) ->
toHtml: ->
if @raw
@string
else
@string
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')

View File

@ -1,4 +1,4 @@
# Modified from 26fca5374e546fd8cc2f12d1140f915185611bdc
# Modified from e2c7296822952f9dcb4b7d3a39e16cca7b5dd462
# Add require 'jquery'
$ = jQuery = require('jquery')
@ -24,15 +24,41 @@ events =
idCounter = 0
class View extends jQuery
elements.forEach (tagName) ->
View[tagName] = (args...) -> @builder.tag(tagName, args...)
@builderStack: []
@subview: (name, view) -> @builder.subview(name, view)
@text: (string) -> @builder.text(string)
@raw: (string) -> @builder.raw(string)
elements.forEach (tagName) ->
View[tagName] = (args...) -> @currentBuilder().tag(tagName, args...)
@subview: (name, view) ->
@currentBuilder().subview(name, view)
@text: (string) -> @currentBuilder().text(string)
@raw: (string) -> @currentBuilder().raw(string)
@currentBuilder: ->
@builderStack[@builderStack.length - 1]
@pushBuilder: ->
@builderStack.push(new Builder)
@popBuilder: ->
@builderStack.pop()
@buildHtml: (fn) ->
@pushBuilder()
fn.call(this)
[html, postProcessingSteps] = @popBuilder().buildHtml()
@render: (fn) ->
[html, postProcessingSteps] = @buildHtml(fn)
fragment = $(html)
step(fragment) for step in postProcessingSteps
fragment
constructor: (params={}) ->
postProcessingSteps = @buildHtml(params)
[html, postProcessingSteps] = @constructor.buildHtml -> @content(params)
jQuery.fn.init.call(this, html)
@constructor = jQuery # sadly, jQuery assumes this.constructor == jQuery in pushStack
@wireOutlets(this)
@bindEventHandlers(this)
@ -46,7 +72,6 @@ class View extends jQuery
@constructor.content(params)
[html, postProcessingSteps] = @constructor.builder.buildHtml()
@constructor.builder = null
jQuery.fn.init.call(this, html)
postProcessingSteps
wireOutlets: (view) ->
@ -157,5 +182,5 @@ for methodName in ['prependTo', 'appendTo', 'insertAfter', 'insertBefore']
result
(exports ? this).View = View
(exports ? this).$$ = (fn) -> View.render.call(View, fn)