├── filename ├── render.js ├── util.js ├── .npmignore ├── requisite.js ├── dynamictemplate.js ├── .travis.yml ├── .gitignore ├── src ├── dynamictemplate.coffee ├── requisite.coffee ├── alias.coffee ├── render.coffee ├── util.coffee ├── doctype.coffee ├── test │ ├── render.coffee │ └── template.coffee ├── cache.coffee ├── schema.coffee └── template.coffee ├── example ├── list.css ├── svg.html ├── circles.html ├── quadtree.html ├── animation.html ├── list.html ├── backbone.html ├── base.css ├── svg.coffee ├── list.coffee ├── animation.coffee ├── circles.coffee ├── backbone.coffee └── quadtree.coffee ├── LICENSE ├── package.json ├── Cakefile └── README.md /filename: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /render.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/render') 3 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/util') 3 | 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | Cakefile 2 | src/ 3 | test/ 4 | example/ 5 | filename 6 | -------------------------------------------------------------------------------- /requisite.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/requisite') 3 | 4 | -------------------------------------------------------------------------------- /dynamictemplate.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/dynamictemplate') 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_script: "npm install --dev" 2 | script: "npm test" 3 | language: node_js 4 | node_js: 5 | - 0.8 6 | - 0.10 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *swp 3 | lib/* 4 | test/* 5 | example/* 6 | filename 7 | *.browser.* 8 | gh-pages 9 | .lock-wscript 10 | node_modules/ 11 | -------------------------------------------------------------------------------- /src/dynamictemplate.coffee: -------------------------------------------------------------------------------- 1 | 2 | { Tag, Builder } = require 'asyncxml' 3 | Template = require './template' 4 | 5 | module.exports = { Tag, Builder, Template } 6 | 7 | # browser support 8 | 9 | ( -> 10 | if @dynamictemplate? 11 | @dynamictemplate.Template = Template 12 | @dynamictemplate.Builder = Builder 13 | @dynamictemplate.Tag = Tag 14 | else 15 | @dynamictemplate = module.exports 16 | ).call window if process.title is 'browser' 17 | 18 | -------------------------------------------------------------------------------- /src/requisite.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | 3 | # optional require to lazy require template masks generated eg by dt-compiler 4 | requisite = (dirs..., name) -> 5 | fullname = path.join(dirs..., name) 6 | mod = (x) -> x 7 | try 8 | mod = require(fullname) 9 | catch err 10 | console.warn "requisite #{name} went missing." 11 | finally 12 | return mod 13 | 14 | # exports 15 | 16 | requisite.requisite = requisite 17 | module.exports = requisite 18 | -------------------------------------------------------------------------------- /src/alias.coffee: -------------------------------------------------------------------------------- 1 | 2 | # schema aliases 3 | aliases = 4 | 'default':'xml' 5 | 'null':'none' 6 | 'nil':'none' 7 | '0':'none' 8 | '5':'html5' 9 | 5:'html5' 10 | 0:'none' 11 | 'ce':'html-ce' 12 | '1.1':'xhtml1.1' 13 | 'html11':'xhtml1.1' 14 | 'basic':'xhtml' 15 | 'xhtml1':'xhtml' 16 | 'xhtml-basic':'xhtml' 17 | 'xhtml-strict':'strict' 18 | 'xhtml-mobile':'mobile' 19 | 'xhtml-frameset':'frameset' 20 | 'xhtml-trasitional':'transitional' 21 | 'svg':'svg1.1' 22 | 23 | # exports 24 | 25 | module.exports = { 26 | aliases, 27 | } 28 | -------------------------------------------------------------------------------- /example/list.css: -------------------------------------------------------------------------------- 1 | /* depends on base.css */ 2 | 3 | .list { 4 | clear:both; 5 | list-style-type: none; 6 | border:1px dashed gray; 7 | border-bottom:0px; 8 | padding:0; 9 | } 10 | .list li { 11 | list-style-type: none; 12 | border-bottom:1px dashed gray; 13 | padding:2px; 14 | padding-left:20px; 15 | margin:0; 16 | min-height: 2em; 17 | } 18 | 19 | .control { 20 | text-align:center; 21 | border-radius:1.1em; 22 | min-height: 1.3em; 23 | min-width: 1.3em; 24 | font-size:1.1em; 25 | float:right; 26 | padding:0; 27 | } 28 | 29 | input[type="number"] { 30 | width: 3em; 31 | } 32 | -------------------------------------------------------------------------------- /example/svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |19 | Hit the buttons! 20 | Source Code 21 | 22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name": "dynamictemplate" 2 | , "description": "Δt - async & dynamic templating engine" 3 | , "version": "0.7.1" 4 | , "homepage": "https://github.com/dodo/node-dynamictemplate" 5 | , "author": "dodo (https://github.com/dodo)" 6 | , "repository": {"type": "git", "url": "git://github.com/dodo/node-dynamictemplate.git"} 7 | , "main": "dynamictemplate.js" 8 | , "engines": {"node": ">= 0.4.x"} 9 | , "keywords": ["dt", "async", "dynamic", "event", "template", "generation", "stream", "browser"] 10 | , "scripts": { 11 | "test": "cake build && nodeunit test", 12 | "prepublish": "cake build"} 13 | , "dependencies": { 14 | "bufferstream": ">= 0.6.0", 15 | "asyncxml": ">= 0.6.0"} 16 | , "peerDependencies": { 17 | "dt-stream": ">= 0.3.0"} 18 | , "devDependencies": { 19 | "dt-stream": ">= 0.3.0", 20 | "browserify": "1.6.1", 21 | "scopify": ">= 0.2.1", 22 | "coffee-script": ">= 1.1.3", 23 | "muffin": ">= 0.2.6", 24 | "nodeunit": ">= 0.5.4"} 25 | , "licenses" : [ 26 | { "type": "MIT" , 27 | "url": "http://github.com/dodo/node-dynamictemplate/raw/master/LICENSE"} ] 28 | } 29 | -------------------------------------------------------------------------------- /example/backbone.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |21 | Hit the buttons! 22 | Source Code 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/util.coffee: -------------------------------------------------------------------------------- 1 | 2 | # add class to the list of classes in attribute 'class' 3 | exports.addClass = (tag, classes...) -> 4 | return unless tag?.attr 5 | tagclasses = (tag.attr('class') ? "").split ' ' 6 | for cls in classes when tagclasses.indexOf(cls) is -1 7 | tagclasses.push "#{cls}" 8 | tag.attr 'class', tagclasses.join(' ').trim().replace(/\s\s/g, " ") 9 | 10 | # remove class from the list of classes in attribute 'class' 11 | exports.removeClass = (tag, classes...) -> 12 | return unless tag?.attr 13 | tagclass = " #{tag.attr('class') ? ''} " 14 | tagclasses = tagclass.trim().split ' ' 15 | for cls in classes 16 | i = tagclasses.indexOf(cls) 17 | unless i is -1 18 | tagclasses[i] = '' 19 | tagclass = tagclass.replace(" #{cls} ", " ") 20 | tag.attr 'class', tagclass.trim() 21 | 22 | # run multiple functions in a row with the same this context 23 | exports.compose = (functions...) -> 24 | if functions.length is 1 and Array.isArray(functions[0]) 25 | functions = functions[0] 26 | return (args...) -> 27 | for fun in functions 28 | fun.apply this, args 29 | this 30 | 31 | # takes a function to create a clojure to call partial on an element 32 | exports.partialize = (create, moargs...) -> 33 | return (args...) -> 34 | partial = create.call(this, moargs..., args...) 35 | @partial partial 36 | return partial 37 | -------------------------------------------------------------------------------- /src/doctype.coffee: -------------------------------------------------------------------------------- 1 | 2 | doctype = 3 | 'xml': ({encoding}) -> "" 4 | 'html' : -> "" 5 | 'html5': -> "#{do doctype.html}" 6 | 'mobile' : -> 7 | '' 10 | 'html-ce': -> 11 | '' 14 | 'strict' : -> 15 | '' 18 | 'xhtml1.1': -> 19 | '' 22 | 'xhtml' : -> 23 | '' 26 | 'frameset': -> 27 | '' 30 | 'transitional': -> 31 | '' 34 | 35 | # exports 36 | 37 | module.exports = { 38 | doctype, 39 | } 40 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | { run, compileScript, minifyScript, writeFile, notify } = require 'muffin' 3 | 4 | task 'compile', 'compile coffeescript → javascript', (options) -> 5 | run 6 | options:options 7 | after:options.after 8 | files:[ 9 | "./src/**/*.coffee" 10 | ] 11 | map: 12 | 'src/test/(.+).coffee': (m) -> 13 | compileScript m[0], path.join("test" ,"#{m[1]}.js"), options 14 | 'src/(.+).coffee': (m) -> 15 | compileScript m[0], path.join("lib" ,"#{m[1]}.js"), options 16 | 17 | task 'bundle', 'build a browser bundle', (options) -> 18 | browserify = require 'browserify' 19 | { createScope } = require 'scopify' 20 | run 21 | options:options 22 | files:[ 23 | "./lib/*.js" 24 | ] 25 | map: 26 | 'lib/(dynamictemplate).js': (m) -> 27 | bundle = browserify({ 28 | require: path.join(__dirname, m[0]) 29 | cache: on 30 | }).use(createScope require:'./'+m[1]).bundle() 31 | notify m[0], "successful browserify!" 32 | filename = "#{m[1]}.browser.js" 33 | writeFile(filename, bundle, options).then -> 34 | minifyScript filename, options 35 | 36 | task 'build', 'compile && bundle', (options) -> 37 | timeout = 0 38 | options.after = -> 39 | clearTimeout(timeout) if timeout 40 | timeout = setTimeout( -> 41 | invoke 'bundle', options 42 | , 250) 43 | invoke 'compile' -------------------------------------------------------------------------------- /src/test/render.coffee: -------------------------------------------------------------------------------- 1 | { Template } = require '../dynamictemplate' 2 | { render, pipe } = require '../render' 3 | 4 | 5 | module.exports = 6 | 7 | pipe: (æ) -> 8 | output = pipe new Template schema:5, doctype:on, -> 9 | @$html -> 10 | @$head -> 11 | @$title "holla" 12 | @$body -> 13 | @$p "hello world" 14 | 15 | 16 | output.on 'data', (tag) -> æ.equal results.shift(), tag.toString() 17 | output.on 'end', -> 18 | æ.equal 0, results.length 19 | æ.done() 20 | results = [ 21 | '' 22 | '' 23 | '' 24 | '' 30 | 'hello world' 31 | '
' 32 | '' 33 | '' 34 | ] 35 | 36 | 37 | simple: (æ) -> 38 | output = render new Template schema:5, doctype:on, -> 39 | @$html -> 40 | @$head -> 41 | @$title "holla" 42 | @$body -> 43 | @$p "hello world" 44 | 45 | 46 | output.on 'data', (tag) -> æ.equal results.shift(), tag.toString() 47 | output.on 'end', -> 48 | æ.equal 0, results.length 49 | æ.done() 50 | results = [ 51 | '' 52 | '' 53 | '' 54 | '' 60 | 'hello world' 61 | '
' 62 | '' 63 | '' 64 | ] 65 | -------------------------------------------------------------------------------- /example/base.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background:white; 4 | color: black; 5 | } 6 | 7 | a, input { 8 | text-decoration:none; 9 | color: #545454; 10 | padding: 1px; 11 | border: 1px solid transparent; 12 | margin: 3px; 13 | display:inline-block; 14 | min-width: 1em; 15 | text-align:center; 16 | vertical-align:middle; 17 | -webkit-transition: 300ms; 18 | -moz-transition: 300ms; 19 | -o-transition: 300ms; 20 | transition: 300ms; 21 | border-radius:0.3em; 22 | 23 | background-size: 0% 0%; 24 | background-position: center center; 25 | background-repeat: no-repeat; 26 | background-color:transparent; 27 | background-image: -webkit-radial-gradient(circle contain, black, black 50%, transparent 50%, transparent); 28 | background-image: -moz-radial-gradient(circle contain, black, black 50%, transparent 50%, transparent); 29 | background-image: -o-radial-gradient(circle contain, black, black 50%, transparent 50%, transparent); 30 | background-image: -ms-radial-gradient(circle contain, black, black 50%, transparent 50%, transparent); 31 | background-image: radial-gradient(circle contain, black, black 50%, transparent 50%, transparent); 32 | } 33 | 34 | a:hover, a:focus, input:hover, input:focus { 35 | background-size: 2222% 2222%; 36 | border-color:transparent; 37 | color:white; 38 | } 39 | 40 | input { 41 | text-align:left; 42 | padding: 3px; 43 | 44 | -webkit-box-shadow: inset 0 0 3px rgba(0,0,0,0.42); 45 | -moz-box-shadow: inset 0 0 3px rgba(0,0,0,0.42); 46 | box-shadow: inset 0 0 3px rgba(0,0,0,0.42); 47 | } 48 | 49 | input[type="button"] { 50 | cursor: pointer; 51 | background-color:rgba(0,0,0,0.1); 52 | -webkit-box-shadow: inset 0 0 3px rgba(255,255,255,0.42); 53 | -moz-box-shadow: inset 0 0 3px rgba(255,255,255,0.42); 54 | box-shadow: inset 0 0 3px rgba(255,255,255,0.42); 55 | } 56 | -------------------------------------------------------------------------------- /example/svg.coffee: -------------------------------------------------------------------------------- 1 | srcdirurl = "https://github.com/dodo/node-dynamictemplate/blob/master/example" 2 | { Template, domify } = window.dynamictemplate 3 | { abs } = Math 4 | running = yes 5 | 6 | button = (tag, id, value) -> 7 | tag.$input 8 | class:'button' 9 | type:'button' 10 | id:id 11 | value:value 12 | title:id 13 | 14 | svgns = "http://www.w3.org/2000/svg" 15 | 16 | svg = domify new Template schema:5, -> 17 | @$div class:'controls', -> 18 | button this, "start", "▸" 19 | button this, "stop", "■" 20 | @$a href:"#{srcdirurl}/svg.coffee", "Source Code" 21 | @$div class:'canvas', -> 22 | @add new Template schema:'svg', -> 23 | @$svg { 24 | xmlns:svgns 25 | version:"1.1" 26 | height:"100px" 27 | width:"100px" 28 | preserveAspectRatio:"none" 29 | viewBox:"0 0 100 100" 30 | }, -> 31 | @$path { 32 | xmlns:svgns # is this really necessary ? 33 | d:"M0,50 Q20,0 50,50 T100,50" 34 | fill:"none" 35 | stroke:"red" 36 | }, -> 37 | i = 0 38 | setInterval => 39 | return unless running 40 | i = (i+5)%200 41 | @attr 'd', "M0,50 Q20,#{abs i-100} 50,50 T100,50" 42 | , 60 43 | 44 | 45 | # initialize 46 | 47 | svg.ready -> 48 | for el in svg.dom 49 | $('body').append el 50 | 51 | $('#start').live 'click', -> 52 | console.log "animation resumed." 53 | running = yes 54 | 55 | $('#stop').live 'click', -> 56 | console.log "animation paused." 57 | running = no 58 | 59 | console.log 'coffeescript loaded.' 60 | 61 | -------------------------------------------------------------------------------- /example/list.coffee: -------------------------------------------------------------------------------- 1 | { Template, List, jqueryify } = window.dynamictemplate 2 | EventEmitter = Template.__super__.constructor # i prefer nodejs eventemitter 3 | 4 | # the templates 5 | 6 | input = (tag, type, id, value, opts) -> 7 | tag.$input(_.extend({class:type, name:id, type, id, value}, opts)) 8 | 9 | items = null # all our list entries 10 | tplapi = new EventEmitter # every other event system should be suitable as well 11 | list = jqueryify use:List.jqueryify, new Template schema:5, -> 12 | @$div class:'controls', -> 13 | 14 | input this, 'button', "add", "list.push(Math.random())", 15 | title:"add to bottom" 16 | input this, 'button', "remove", "list.shift()", 17 | title:"remove from top" 18 | @$br() 19 | input this, 'button', "insert", "list.insert(i, text)", 20 | title:"insert at position" 21 | @span " where " 22 | @$label for:'nmb', -> 23 | @text " i=" 24 | input this, 'number','nmb', "0" 25 | @$label for:'text', -> 26 | @text " text=" 27 | input this, 'text', 'text', "", placeholder:"try me!" 28 | 29 | @$ul class:'list', -> 30 | items = new List 31 | tplapi.on 'add', => 32 | console.log "add entry" 33 | items.push @$li -> @$p "#{Math.random()}" 34 | tplapi.on 'insert', (i, text) => 35 | console.log "insert entry" 36 | items.insert i, @$li -> @$p "#{text}" 37 | tplapi.on 'remove', -> 38 | items.shift()?.remove() 39 | 40 | # initialize 41 | 42 | list.ready -> 43 | window.test = list 44 | $('body').append(list.jquery) 45 | 46 | $('#add').live 'click', -> 47 | tplapi.emit 'add' # tell the template to add a new entry 48 | 49 | $('#remove').live 'click', -> 50 | tplapi.emit 'remove' 51 | 52 | $('#insert').live 'click', -> 53 | i = parseInt($('#nmb').val()) 54 | return if i > items.length 55 | tplapi.emit 'insert', i, $('#text').val() 56 | 57 | 58 | console.log 'coffeescript loaded.' 59 | 60 | 61 | -------------------------------------------------------------------------------- /example/animation.coffee: -------------------------------------------------------------------------------- 1 | srcdirurl = "https://github.com/dodo/node-dynamictemplate/blob/master/example" 2 | { Template, jqueryify } = window.dynamictemplate 3 | { random, floor, pow } = Math 4 | running = yes 5 | maxsize = 100 6 | 7 | rnd = (most) -> 8 | floor(most*random()) 9 | 10 | differ = (c) -> 11 | r = {} 12 | for t in ['r', 'g', 'b'] 13 | r[t] = c[t] + 100 * random() - 50 14 | r[t] = 0 if c[t] < 0 15 | r[t] = 255 if c[t] > 255 16 | r[t] = floor(r[t]) 17 | return r 18 | 19 | rgb = (c) -> 20 | "background-color:rgb(#{c.r},#{c.g},#{c.b})" 21 | 22 | createSquare = (tag, col) -> 23 | tag.$div -> 24 | interval = null 25 | sat = 255/@level 26 | size = 100/pow(2,@level) 27 | setTimeout(@remove, 600 + 5*sat) if col? 28 | col ?= r:rnd(sat), g:rnd(sat), b:rnd(sat) 29 | @attr style:"#{rgb(col)};width:#{size}px;height:#{size}px;display:inline-block;" 30 | interval = setInterval => 31 | return unless running 32 | createSquare(this, differ(col)) 33 | createSquare(this, differ(col)) 34 | createSquare(this, differ(col)) 35 | createSquare(this, differ(col)) 36 | ,(300 + 4*sat) 37 | @once 'remove', -> 38 | clearInterval(interval) 39 | 40 | 41 | 42 | button = (tag, id, value) -> 43 | tag.$input class:'button', type:'button', id:id, value:value 44 | 45 | animation = jqueryify new Template schema:5, -> 46 | @$div class:'controls', -> 47 | button this, "start", "▸" 48 | button this, "stop", "■" 49 | @$a href:"#{srcdirurl}/animation.coffee", "Source Code" 50 | @$div class:'canvas', -> 51 | createSquare this 52 | createSquare this 53 | createSquare this 54 | createSquare this 55 | 56 | 57 | # initialize 58 | 59 | animation.ready -> 60 | for el in animation.jquery 61 | $('body').append el 62 | 63 | $('#start').live 'click', -> 64 | console.log "animation paused." 65 | running = yes 66 | 67 | $('#stop').live 'click', -> 68 | console.log "animation resumed." 69 | running = no 70 | 71 | console.log 'coffeescript loaded.' 72 | -------------------------------------------------------------------------------- /src/cache.coffee: -------------------------------------------------------------------------------- 1 | { Builder:DefaultBuilder } = require 'asyncxml' 2 | 3 | cache = {} 4 | 5 | 6 | ## 7 | # i made these function name short , 8 | # because its a little bit faster than with long names 9 | 10 | pp = (proto, name) -> # populate tag with specific child tag genertor 11 | proto[ name] = -> @tag.apply this, [name].concat arguments... 12 | proto["$"+name] = -> @$tag.apply this, [name].concat arguments... 13 | return 14 | 15 | ff = (proto, tags) -> # fill with tags 16 | for tagname in tags when tagname 17 | pp proto, tagname 18 | return 19 | 20 | 21 | get = (key, opts) -> 22 | ExtendedBuilder = cache[key].Builder 23 | # instantiate 24 | xml = new ExtendedBuilder opts 25 | # set Tag class so we get the same results like create(…) 26 | xml.Tag = xml.opts.Tag = cache[key].Tag 27 | return xml 28 | 29 | 30 | create = (key, opts) -> 31 | # get builder class from options 32 | Builder = opts.Builder ? DefaultBuilder 33 | # create new builder class to extend it with a schema 34 | class ExtendedBuilder extends Builder 35 | # create tag method shortcuts defined by schema 36 | ff ExtendedBuilder::, opts.schema 37 | # instantiate 38 | xml = new ExtendedBuilder opts 39 | # tag class is defined by builder 40 | Tag = xml.Tag 41 | # create new tag class to extend it with a schema 42 | class ExtendedTag extends Tag 43 | partial: (partial) -> 44 | return this unless partial.run? # tag should be already added then 45 | if partial.started 46 | console.warn "partial already started!", 47 | "you can delay it by adding option {partial:true} to your template" 48 | return this 49 | @add(partial) 50 | partial.run() # guess this is a template 51 | return this 52 | # create tag method shortcuts defined by schema 53 | ff ExtendedTag::, opts.schema 54 | # write it back so builder can use it to instantiate a new tag 55 | xml.Tag = xml.opts.Tag = ExtendedTag 56 | # fill cache 57 | cache[key] = {Builder:ExtendedBuilder, Tag:ExtendedTag} 58 | return xml 59 | 60 | 61 | lookup = (opts) -> 62 | key = opts._schema ? opts.schema 63 | return if cache[key]? 64 | get key, opts 65 | else 66 | create key, opts 67 | 68 | 69 | clear = () -> 70 | cache = {} 71 | 72 | 73 | # exports 74 | 75 | module.exports = { 76 | create, 77 | lookup, 78 | clear, 79 | get, 80 | } 81 | -------------------------------------------------------------------------------- /example/circles.coffee: -------------------------------------------------------------------------------- 1 | srcdirurl = "https://github.com/dodo/node-dynamictemplate/blob/master/example" 2 | { Template, domify } = window.dynamictemplate 3 | { random, floor, min } = Math 4 | running = yes 5 | 6 | svgns = "http://www.w3.org/2000/svg" 7 | 8 | 9 | button = (tag, id, value) -> 10 | tag.$input 11 | class:'button' 12 | type:'button' 13 | id:id 14 | value:value 15 | title:id 16 | 17 | 18 | createCircle = (tag, o) -> 19 | tag.$circle { 20 | xmlns:svgns 21 | fill:"none" 22 | stroke:"rgba(#{o.r},#{o.g},#{o.b},#{o.a})" 23 | style:"stroke-width:#{o.size}" 24 | cx:"#{o.x}" 25 | cy:"#{o.y}" 26 | r:"#{o.radius}" 27 | }, step = -> 28 | setTimeout => 29 | @attr 'r', "#{o.radius+=o['+']}" 30 | if --o.life then step.call(this) else @remove() 31 | ,60 32 | 33 | 34 | 35 | svg = domify new Template schema:5, -> 36 | @$div class:'controls', -> 37 | button this, "start", "▸" 38 | button this, "stop", "■" 39 | @$a href:"#{srcdirurl}/circles.coffee", "Source Code" 40 | @$div class:'canvas', -> 41 | @add new Template schema:'svg', -> 42 | @$svg { 43 | xmlns:svgns 44 | version:"1.1" 45 | height:"600px" 46 | width:"600px" 47 | preserveAspectRatio:"none" 48 | viewBox:"0 0 600 600" 49 | }, -> 50 | setInterval => 51 | return unless running 52 | createCircle this, 53 | '+':(0.00001 + 2 * random()) 54 | life:floor(5 + 42 * random()) 55 | radius:floor(3 + 20 * random()) 56 | size:floor(5 + 10 * random()) 57 | x:floor(600 * random()) 58 | y:floor(600 * random()) 59 | r:floor(255 * random()) 60 | g:floor(255 * random()) 61 | b:floor(255 * random()) 62 | a:min(0.8, 0.01 + random()) 63 | , 100 64 | 65 | 66 | # initialize 67 | 68 | svg.ready -> 69 | for el in svg.dom 70 | $('body').append el 71 | 72 | $('#start').live 'click', -> 73 | console.log "animation resumed." 74 | running = yes 75 | 76 | $('#stop').live 'click', -> 77 | console.log "animation paused." 78 | running = no 79 | 80 | console.log 'coffeescript loaded.' 81 | 82 | -------------------------------------------------------------------------------- /src/schema.coffee: -------------------------------------------------------------------------------- 1 | 2 | schema = 3 | 'none' : -> "" # allready includes tag 4 | 'xml' : -> "#{do schema.none }" 5 | 'html' : -> 6 | "#{do schema.xml } #{do schema['html-obsolete']} iframe label legend " + 7 | "#{do self_closing.html} html body div ul li a b body button colgroup "+ 8 | "dfn div dl dt em dd del form h1 h2 h3 h4 h5 h6 head hgroup html ins " + 9 | "li map i mark menu meter nav noscript object ol optgroup option p " + 10 | "pre script select small span strong style sub sup table tbody tfoot " + 11 | "td textarea th thead title tr u ul" 12 | 'html5': -> 13 | "#{do schema.html} #{do self_closing.html5} section article video q s "+ 14 | "audio abbr address aside bdi bdo blockquote canvas caption cite code "+ 15 | "datalist details fieldset figcaption figure footer header kbd output "+ 16 | "progress rp rt ruby samp summary time" 17 | 'strict': -> # xhtml1 18 | "#{do schema.html}" # FIXME 19 | 'xhtml': -> 20 | "#{do schema.html}" # FIXME 21 | 'xhtml1.1': -> 22 | "#{do schema.xhtml}" # FIXME 23 | 'frameset': -> 24 | "#{do schema.xhtml}" # FIXME 25 | 'transitional': -> 26 | "#{do schema.xhtml}" # FIXME 27 | 'mobile': -> 28 | "#{do schema.xhtml}" # FIXME 29 | 'html-ce': -> 30 | "#{do schema.xhtml}" # FIXME 31 | 'html-obsolete': -> 32 | "applet acronym bgsound dir frameset noframes isindex listing nextid " + 33 | "noembed plaintext rb strike xmp big blink center font marquee nobr " + 34 | "multicol spacer tt" 35 | "svg1.1": -> 36 | "altGlyph altGlyphDef altGlyphItem animate animateColor animateMotion" + 37 | "a animateTransform circle clipPath color-profile cursor defs desc" + 38 | "ellipse feBlend feColorMatrix feComponentTransfer feComposite" + 39 | "feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight" + 40 | "feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage" + 41 | "feMerge feMergeNode feMorphology feOffset fePointLight feSpotLight" + 42 | "feSpecularLighting feTile feTurbulence linearGradient polyline" + 43 | "filter font font-face font-face-format font-face-name font-face-src" + 44 | "font-face-uri foreignObject g glyph glyphRef hkern image line" + 45 | "marker mask metadata missing-glyph mpath path pattern polygon" + 46 | "radialGradient rect script set stop style svg switch symbol text" + 47 | "textPath title tref tspan use view vkern" 48 | 49 | 50 | # Valid self-closing HTML 5 elements. 51 | # set true when all tags are self closing 52 | self_closing = 53 | 'none': -> on 54 | 'xml' : -> off 55 | 'svg1.1': -> on 56 | 'html' : -> "area br col embed hr img input link meta param" 57 | 'html5': -> "#{do self_closing.html} base command keygen source track wbr main" 58 | 'mobile' : -> "#{do self_closing.xhtml}" 59 | 'html-ce': -> "#{do self_closing.xhtml}" 60 | 'strict' : -> "#{do self_closing.xhtml}" 61 | 'xhtml1.1': -> "#{do self_closing.xhtml}" 62 | 'xhtml' : -> "#{do self_closing.html}" 63 | 'frameset': -> "#{do self_closing.xhtml}" 64 | 'transitional': -> "#{do self_closing.xhtml}" 65 | 66 | 67 | # exports 68 | 69 | module.exports = { 70 | self_closing, 71 | schema, 72 | } 73 | -------------------------------------------------------------------------------- /src/template.coffee: -------------------------------------------------------------------------------- 1 | { EventEmitter } = require 'events' 2 | { schema, self_closing } = require './schema' 3 | { doctype } = require './doctype' 4 | { aliases } = require './alias' 5 | { lookup, clear } = require './cache' 6 | 7 | EVENTS = [ 8 | 'new','add' 9 | 'show', 'hide' 10 | 'attr','text', 'raw' 11 | 'remove', 'replace' 12 | 'data','close','end' 13 | ] 14 | 15 | 16 | class Template extends EventEmitter 17 | constructor: (opts = {}, template) -> 18 | # options 19 | [template, opts] = [opts, {}] if typeof opts is 'function' 20 | # defaults 21 | opts.run = off if opts.partial 22 | opts.encoding ?= 'utf-8' 23 | opts.doctype ?= off 24 | opts.end ?= on 25 | # schema 26 | opts._schema = opts.schema 27 | # resolve schema name input 28 | s = aliases[opts._schema] or opts._schema or 'xml' 29 | # load self closing schema 30 | opts.self_closing = self_closing[s]?(opts) 31 | # load tag list (xml schema) 32 | opts.schema = schema[s]?(opts).split(' ') 33 | # instantiate 34 | # lookup the cache to get a builder (and Tag class) patched with schema 35 | @xml = lookup opts 36 | @xml.template = this 37 | # override query 38 | old_query = @xml.query 39 | @xml.query = (type, tag, key) -> 40 | if type is 'tag' 41 | key.xml ? key 42 | else 43 | old_query.call(this, type, tag, key) 44 | 45 | unless opts.self_closing is off 46 | # add self closing tag behavior 47 | # some of the html tags dont need a closing tag 48 | @xml.register 'end', (tag, next) -> 49 | return next(tag) unless tag.isselfclosing 50 | if opts.self_closing is on or opts.self_closing.match tag.name 51 | tag.isempty = yes 52 | next(tag) 53 | 54 | # pipe events through 55 | EVENTS.forEach (event) => 56 | @xml.on(event, @emit.bind(this, event)) 57 | 58 | ## 59 | # start the templating process after user listened for events 60 | @run = @run.bind(this, template, opts) 61 | return if opts.run is off 62 | process.nextTick @run 63 | 64 | run: (template, opts, {restart} = {}) -> 65 | return if @started and not restart 66 | @started = yes 67 | # load doctype if enabled 68 | if opts.doctype is on 69 | opts.doctype = opts._schema or 'html' 70 | # resolve doctype name input 71 | d = aliases[opts.doctype] or opts.doctype 72 | # write doctype 73 | if opts.doctype and (dt = doctype[d]?(opts)) 74 | dt += "\n" if opts.pretty 75 | @xml.emit 'data', @xml, dt 76 | # templating process ... 77 | if typeof template is 'function' 78 | template.call @xml 79 | @end() if opts.end 80 | else if opts.end 81 | @end(template) 82 | 83 | toString: -> 84 | "[object Template]" 85 | 86 | register: => 87 | @xml.register arguments... 88 | 89 | remove: => 90 | @xml.remove arguments... 91 | @xml.template = null 92 | @xml = null 93 | 94 | end: => 95 | @xml.end arguments... 96 | 97 | ready: => 98 | @xml.ready arguments... 99 | 100 | show: => 101 | @xml.show arguments... 102 | 103 | hide: => 104 | @xml.hide arguments... 105 | 106 | 107 | # exports 108 | 109 | Template.schema = schema 110 | Template.doctype = doctype 111 | Template.self_closing = self_closing 112 | Template.aliases = aliases 113 | Template.clearCache = clear 114 | module.exports = Template 115 | -------------------------------------------------------------------------------- /example/backbone.coffee: -------------------------------------------------------------------------------- 1 | { Template, List, jqueryify } = window.dynamictemplate 2 | { random, floor } = Math 3 | { isArray } = Array 4 | 5 | # use an even simpler equals comparator for adiff 6 | 7 | adiff = window.adiff({ # npm i adiff - https://github.com/dominictarr/adiff 8 | equal: (a, b) -> 9 | return no if a and not b 10 | return no if isArray(a) and a.length isnt b.length 11 | return a is b 12 | }, window.adiff) 13 | 14 | # helpers 15 | 16 | EventHandler = (handler) -> 17 | return (ev) -> 18 | ev?.preventDefault?() 19 | handler.apply(this, arguments) 20 | no 21 | 22 | input = (tag, type, id, value, opts = {}) -> 23 | tag.$input(_.extend({class:type, name:id, type, id, value}, opts)) 24 | 25 | sync = (items, collection, options) -> 26 | # now its gonna get dirty! 27 | bycid = {} 28 | removed = [] 29 | old_models = [] 30 | # rebuild old collection state 31 | for item in items 32 | bycid[item.cid] = item 33 | old_models.push collection.getByCid(item.cid) 34 | # apply diff patches on items list 35 | for patch in adiff.diff(old_models, collection.models) 36 | # remove all items from dom before splicing them in 37 | for i in [(patch[0]) ... (patch[0]+patch[1])] 38 | removed.push items[i].remove(soft:yes) 39 | # replace models with items 40 | for i in [2 ... patch.length] 41 | patch[i] = bycid[patch[i].cid] 42 | # apply patch! 43 | items.splice.apply(items, patch) 44 | # read all removed items - this only works in the assumption, 45 | # that the collection doesn't change its size 46 | for item in removed 47 | @add(item) 48 | 49 | # different comparators 50 | 51 | ascending = (model) -> 52 | parseFloat(model.get 'value') 53 | 54 | descending = (model) -> 55 | - ascending(model) 56 | 57 | 58 | class BackboneExample extends Backbone.View 59 | 60 | # embbeded template 61 | 62 | template: (view) -> jqueryify use:List.jqueryify, new Template schema:5, -> 63 | @$div class:'controls', -> 64 | 65 | input this, 'button', "add", "collection.add({value:Math.random()})" 66 | input this, 'button', "desc", "▲", title:"descending" 67 | input this, 'button', "asc", "▼", title:"ascending" 68 | 69 | @$ul class:'list', -> 70 | items = new List 71 | view.model.on 'reset', sync.bind(this, items) 72 | view.model.on 'remove', (entry, collection, options) -> 73 | items.remove(options.index)?.remove() 74 | view.model.on 'add', (entry, collection, options) => 75 | [r,g,b] = (floor(255 * entry.get(k)) for k in ['r','g','b']) 76 | style = "background:rgba(#{r},#{g},#{b}, 0.3)" 77 | items.insert options.index, @$li {style}, -> 78 | @cid = entry.cid # mark it to find its model again (on reset) 79 | @$input 80 | type:'button' 81 | class:"remove control" 82 | 'data-cid':entry.cid 83 | value:"✖" 84 | title:"delete" 85 | @$p "#{entry.get 'value'}" 86 | 87 | # patch some backbone internals 88 | 89 | make: -> # do nothing 90 | delegateEvents: -> super if @$el? 91 | undelegateEvents: -> super if @$el? 92 | 93 | setElement: (element, delegate, callback) -> 94 | return unless element? 95 | @el = element 96 | @el.ready => 97 | console.log "ready" 98 | do @undelegateEvents if @$el 99 | @$el = @el.jquery 100 | do @delegateEvents if delegate isnt off 101 | callback?(@$el) 102 | this 103 | 104 | render: (callback) -> 105 | @setElement(@template(this), null, callback) 106 | 107 | # user input handling 108 | 109 | events: 110 | 'click #add': "on_add" 111 | 'click .remove': "on_remove" 112 | 'click #desc': "on_desc" 113 | 'click #asc': "on_asc" 114 | 115 | on_add: EventHandler (ev) -> 116 | @model.add value:"#{random()}", r:random(), g:random(), b:random() 117 | console.log "add", @model.length 118 | 119 | on_remove: EventHandler (ev) -> 120 | if(entry = @model.getByCid($(ev.target).data('cid')))? 121 | @model.remove(entry) 122 | console.log "remove", @model.length 123 | 124 | on_desc: EventHandler (ev) -> 125 | @model.comparator = descending 126 | @model.sort() 127 | 128 | on_asc: EventHandler (ev) -> 129 | @model.comparator = ascending 130 | @model.sort() 131 | 132 | 133 | # initialize 134 | 135 | $(document).ready -> 136 | example = new BackboneExample 137 | model:new Backbone.Collection 138 | window.example = example 139 | example.render ($el) -> 140 | $('body').append($el) 141 | 142 | 143 | console.log 'coffeescript loaded.' 144 | 145 | 146 | -------------------------------------------------------------------------------- /src/test/template.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | { readFile } = require 'fs' 3 | { Template } = require '../dynamictemplate' 4 | streamify = require 'dt-stream' 5 | 6 | module.exports = 7 | 8 | simple: (æ) -> 9 | xml = streamify new Template schema:0, -> 10 | @$tag('test') 11 | check = "no data" 12 | xml.stream.on 'data', (tag) -> 13 | æ.equal "\n' 82 | ' hello world\n' 83 | '
\n' 84 | ' \n' 85 | '\n' 86 | ] 87 | 88 | 89 | layout: (æ) -> 90 | 91 | layout = ({title}, callback) -> 92 | new Template schema:'html5', -> 93 | @$html -> 94 | @$head -> 95 | @$title title 96 | @$body -> 97 | @div class:'body', -> 98 | callback?.call(this) 99 | 100 | template = (data) -> 101 | layout data, -> 102 | @$p data.content 103 | @end() 104 | 105 | 106 | xml = streamify template data = 107 | title:'test' 108 | content:'..' 109 | 110 | xml.stream.on 'data', (tag) -> æ.equal results.shift(), tag 111 | xml.stream.on 'end', -> 112 | æ.equal 0, results.length 113 | æ.done() 114 | results = [ 115 | '' 116 | '' 117 | '' 124 | data.content 125 | '
' 126 | '