From bb640dd342d85e7fed9deb9e6ff68798954df49c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 6 Feb 2012 16:19:35 -0700 Subject: [PATCH] Use `$$ ->` to render ad-hoc document fragments Also eliminate stdlib/template directory which held code related to SpacePen's precursor framework. --- spec/spec-bootstrap.coffee | 6 +- spec/stdlib/template/builder-spec.coffee | 75 ---------------- src/atom/editor.coffee | 18 ++-- src/atom/selection.coffee | 5 +- src/stdlib/template/builder.coffee | 105 ----------------------- src/stdlib/template/close-tag.coffee | 7 -- src/stdlib/template/open-tag.coffee | 12 --- src/stdlib/template/text.coffee | 15 ---- vendor/space-pen.coffee | 43 ++++++++-- 9 files changed, 48 insertions(+), 238 deletions(-) delete mode 100644 spec/stdlib/template/builder-spec.coffee delete mode 100644 src/stdlib/template/builder.coffee delete mode 100644 src/stdlib/template/close-tag.coffee delete mode 100644 src/stdlib/template/open-tag.coffee delete mode 100644 src/stdlib/template/text.coffee diff --git a/spec/spec-bootstrap.coffee b/spec/spec-bootstrap.coffee index 24f084725..d39d4cb7d 100644 --- a/spec/spec-bootstrap.coffee +++ b/spec/spec-bootstrap.coffee @@ -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' diff --git a/spec/stdlib/template/builder-spec.coffee b/spec/stdlib/template/builder-spec.coffee deleted file mode 100644 index 5c4695255..000000000 --- a/spec/stdlib/template/builder-spec.coffee +++ /dev/null @@ -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 "
" - - builder.reset() - builder.tag 'ol' - expect(builder.toHtml()).toBe "
    " - - it "can generate tags with content", -> - builder.tag 'ol', -> - builder.tag 'li' - builder.tag 'li' - - expect(builder.toHtml()).toBe "
    " - - it "can generate tags with text", -> - builder.tag 'div', "hello" - expect(builder.toHtml()).toBe "
    hello
    " - - builder.reset() - builder.tag 'div', 22 - expect(builder.toHtml()).toBe "
    22
    " - - it "HTML escapes tag text", -> - builder.tag('div', "
    ") - expect(builder.toHtml()).toBe "
    <br/>
    " - - 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 '
    ' - - describe ".raw(text)", -> - it "does not escape html entities", -> - builder.raw ' ' - expect(builder.toHtml()).toBe ' ' - diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 9f0e7f4fc..9621e8b5b 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -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 ' ' + $$ -> + @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 ' ' setBuffer: (@buffer) -> @highlighter = new Highlighter(@buffer) diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index d84ea7ab8..c9e9275f7 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -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) diff --git a/src/stdlib/template/builder.coffee b/src/stdlib/template/builder.coffee deleted file mode 100644 index fa68e447c..000000000 --- a/src/stdlib/template/builder.coffee +++ /dev/null @@ -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 = [] - diff --git a/src/stdlib/template/close-tag.coffee b/src/stdlib/template/close-tag.coffee deleted file mode 100644 index 3e4874dfc..000000000 --- a/src/stdlib/template/close-tag.coffee +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = -class CloseTag - constructor: (@name) -> - - toHtml: -> - "" - diff --git a/src/stdlib/template/open-tag.coffee b/src/stdlib/template/open-tag.coffee deleted file mode 100644 index ec37868aa..000000000 --- a/src/stdlib/template/open-tag.coffee +++ /dev/null @@ -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 diff --git a/src/stdlib/template/text.coffee b/src/stdlib/template/text.coffee deleted file mode 100644 index 1270086f8..000000000 --- a/src/stdlib/template/text.coffee +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = -class Text - constructor: (@string, @raw=false) -> - - toHtml: -> - if @raw - @string - else - @string - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>') - diff --git a/vendor/space-pen.coffee b/vendor/space-pen.coffee index 648f8f5ef..fc0b04480 100644 --- a/vendor/space-pen.coffee +++ b/vendor/space-pen.coffee @@ -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)