diff options
| author | pabs@pablotron.org <pabs@pablotron.org> | 2014-12-18 11:36:53 -0500 | 
|---|---|---|
| committer | pabs@pablotron.org <pabs@pablotron.org> | 2014-12-18 11:36:53 -0500 | 
| commit | eb5302a7981c4e2fc6b7675811a0a913dbe9b91e (patch) | |
| tree | 7d3fff737a35983b4f3667752968150aead6b762 /js | |
| parent | c3eef98518978638ba48ed51afc966d1af399b9b (diff) | |
| download | luigi-template-eb5302a7981c4e2fc6b7675811a0a913dbe9b91e.tar.xz luigi-template-eb5302a7981c4e2fc6b7675811a0a913dbe9b91e.zip | |
move javascript to js dir
Diffstat (limited to 'js')
| -rw-r--r-- | js/luigi-template.js | 379 | ||||
| -rw-r--r-- | js/test.js | 58 | 
2 files changed, 437 insertions, 0 deletions
| diff --git a/js/luigi-template.js b/js/luigi-template.js new file mode 100644 index 0000000..925b811 --- /dev/null +++ b/js/luigi-template.js @@ -0,0 +1,379 @@ +/** + * luigi-template.js + * ================= + * + * Links + * ----- + * * Contact: Paul Duncan (<pabs@pablotron.org>) + * * Home Page: <http://pablotron.org/luigi-template/> + * * Mercurial Repository: <http://hg.pablotron.org/luigi-template/> + * + * Overview + * -------- + * Tiny client-side JavaScript templating library. + * + * Why?  This script is: + * + *   * less than 4k minified (see `luigi-template.min.js`), + * + *   * has no external dependencies (no jQuery/YUI/Sensha), + * + *   * works in browsers as old as IE8, and + * + *   * MIT licensed (use for whatever, I don't care) + * + * Usage + * ----- + * TODO + * + * License + * ------- + * Copyright (c) 2014 Paul Duncan <pabs@pablotron.org> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + *   * Redistributions of source code must retain the above copyright + *     notice, this list of conditions and the following disclaimer. + * + *   * Redistributions in binary form must reproduce the above copyright + *     notice, this list of conditions and the following disclaimer in the + *     documentation and/or other materials provided with the distribution. + * + *   * The names of contributors may not be used to endorse or promote + *     products derived from this software without specific prior written + *     permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +LuigiTemplate = (function() { +  "use strict"; + +  var VERSION = '0.4.0'; + +  // Array.each polyfill +  var each = (function() { +    if (Array.prototype.forEach) { +      return function(a, fn) { +        a.forEach(fn); +      }; +    } else { +      return function(a, fn) { +        var i, l; + +        for (i = 0, l = a.length; i < l; i++) +          fn(a[i], i, a); +      }; +    } +  })(); + +  // Array.map polyfill +  var map = (function() { +    if (Array.prototype.map) { +      return function(a, fn) { +        return a.map(fn); +      }; +    } else { +      return function(a, fn) { +        var r = new Array(a.length); + +        each(a, function(v, i) { +          r[i] = v; +        }); + +        return r; +      }; +    } +  })(); + +  // Array.reduce polyfill +  var reduce = (function() { +    if (Array.prototype.reduce) { +      return function(a, fn, iv) { +        return a.reduce(fn, iv); +      }; +    } else { +      return function(a, fn, r) { +        each(a, function(v, i) { +          r = fn(r, v, i, a); +        }); + +        return r; +      }; +    } +  })(); + +  // String.trim polyfill +  var trim = (function() { +    if (String.prototype.trim) { +      return function(s) { +        return (s || '').trim(); +      }; +    } else { +      var re = /^\s+|\s+$/g; + +      return function(s) { +        (s || '').replace(re, ''); +      }; +    } +  })(); + +  // String.scan polyfill +  function scan(s, re, fn) { +    var m; + +    if (!re.global) +      throw 'non-global regex'; + +    while ((m = re.exec(s)) !== null) +      fn(m); +  } + +  // list of built-in filters +  var FILTERS = { +    uc: function(v) { +      return (v || '').toUpperCase(); +    }, + +    lc: function(v) { +      return (v || '').toLowerCase(); +    }, + +    s: function(v) { +      return (v == 1) ? '' : 's'; +    }, + +    length: function(v) { +      return (v || '').length; +    }, + +    trim: function(v) { +      return trim(v); +    }, + +    h: (function() { +      var LUT = { +        '"': '"', +        "'": ''', +        '>': '>', +        '<': '<', +        '&': '&' +      }; +     +      return function(v) { +        if (v === undefined || v === null) +          return ''; + +        return v.toString().replace(/(['"<>&])/g, function(s) { +          return LUT[s]; +        }); +      }; +    })() +  }; + +  var RES = { +    actions: /%\{\s*([^\s\|\}]+)\s*((\s*\|(\s*[^\s\|\}]+)+)*)\s*\}|([^%]+|%)/g, +    filter:   /(\S+)((\s*\S+)*)\s*/, +    delim_filters: /\s*\|\s*/, +    delim_args:    /\s+/ +  }; + +  function parse_template(s) { +    var r = []; + +    scan(s, RES.actions, function(m) { +      if (m[1]) { +        // action +        r.push({ +          type: 'action', +          key: m[1], +          filters: parse_filters(m[2]) +        }); +      } else { +        // text +        r.push({ +          type: 'text', +          text: m[5] +        }); +      } +    }); + +    return r; +  } + +  function parse_filters(filters) { +    var r = []; + +    each(filters.split(RES.delim_filters), function(f) { +      f = trim(f); +      if (!f) +        return; + +      var m = f.match(RES.filter); +      if (!m) +        throw new Error('invalid filter: ' + f); + +      var as = trim(m[2]); + +      r.push({ +        name: m[1], +        args: as.length ? as.split(RES.delim_args) : [] +      }); +    }); + +    return r; +  } + +  function init(s) { +    this.s = s; +    this.actions = parse_template(s); +  }; + +  function run(o) { +    var i, l, f, fs, me = this; + +    // debug +    // print(JSON.stringify(this.actions)); + +    return map(this.actions, function(row) { +      if (row.type == 'text') { +        return row.text; +      } else if (row.type == 'action') { +        if (!row.key in o) +          throw new Error('missing key: ' + row.key) + +        return reduce(row.filters, function(r, f) { +          return FILTERS[f.name](r, f.args, o, this); +        }, o[row.key]); +      } else { +        /* never reached */ +        throw new Error('BUG: invalid type: ' + row.type); +      } +    }).join(''); +  } + +  function get_inline_template(key) { +    // get script element +    var e = document.getElementById(key); +    if (!e) +      throw new Error('unknown inline template key: ' + key); + +    // return result +    return e.innerText || ''; +  } + +  // declare constructor +  var T = init; + +  // declare run method +  T.prototype.run = run; + +  // declare cache constructor +  T.Cache = function(templates, try_dom) { +    this.templates = templates; +    this.try_dom = !!try_dom; +    this.cache = {}; +  }; + +  // cache run method +  T.Cache.prototype.run = function(key, args) { +    if (!(key in this.cache)) { +      var s = null; + +      if (key in this.templates) { +        // get template from constructor templates +        s = this.templates[key].join(''); +      } else if (this.try_dom) { +        // get source from inline script tag +        s = get_inline_template(key); +      } else { +        throw new Error('unknown key: ' + key); +      } + +      // cache template +      this.cache[key] = new T(s); +    } + +    // run template +    return this.cache[key].run(args); +  }; + +  // declare domcache constructor +  T.DOMCache = function() { +    this.cache = {}; +  }; + +  // domcache run method +  T.DOMCache.prototype.run = function(key, args) { +    if (!(key in this.cache)) +      this.cache[key] = new T(get_inline_template(key)); + +    // run template +    return this.cache[key].run(args); +  }; + +  // create DOMCache singleton +  T.dom = new T.DOMCache(); + +  // expose filters and version +  T.FILTERS = FILTERS; +  T.VERSION = VERSION; + +  // add singleton run +  T.run = function(s, o) { +    return new T(s).run(o); +  } + +  // expose interface +  return T; +}()); + +/* +You automagically generate the following files: + +  * luigi-template.min.js (minified luigi-template.js), +  * readme.txt (Markdown-formatted documentation), and +  * readme.html (HTML-formatted documentation) + +by using this command: + +  grep ^build: luigi-template.js | sed 's/^build://' | ruby + +(Requires jsmin, ruby, and markdown). + +build: # generate readme.txt +build: File.write('readme.txt', File.read('luigi-template.js').match(%r{ +build:   # match first opening comment +build:   ^/\*\*(.*?)\* / +build: +build:   # match text +build:   (.*?) +build: +build:   # match first closing comment +build:   # (note: don't change " /" to "/" or else) +build:   \* / +build: }mx)[1].split(/\n/).map { |line| +build:   # strip leading asterisks +build:   line.gsub(/^ \* ?/, '') +build: }.join("\n").strip) +build: +build: # generate readme.html +build: `markdown < readme.txt > readme.html` +build: +build: # make luigi-template.min.js +build: `jsmin < luigi-template.js > luigi-template.min.js` +*/ diff --git a/js/test.js b/js/test.js new file mode 100644 index 0000000..0fedfcd --- /dev/null +++ b/js/test.js @@ -0,0 +1,58 @@ + +load('luigi-template.js'); + +// define custom template filter +function custom_filter(v) { +  return "foo" + v + "bar"; +} + +function custom_filter_with_args(v, args) { +  var i, l, r = [v]; + +  for (i = 0, l = args.length; i < l; i++) +    r.push(args[i]); + +  return r.join(' and '); +} + +// add custom template filters +LuigiTemplate.FILTERS.custom = custom_filter; +LuigiTemplate.FILTERS.custom_args = custom_filter_with_args; + +// build template string +var template_str = [ +  // test basic templates +  "%{greet}, %{name}!", + +  // test filters and filters with parameters +  "Your name uppercase is: %{name|uc}", + +  // test custom filter +  "Your custom filtered name is: %{name|custom}", + +  // test custom filter with arguments +  "Your custom_args name is: %{name|custom_args foo bar baz}", + +  // test whitespace in filters +  "random test: %{name | lc }", + +  // test pluralize filter +  'pluralize test (0): %{count_0} item%{count_0 | s}', +  'pluralize test (1): %{count_1} item%{count_1 | s}', +  'pluralize test (10): %{count_10} item%{count_10 | s}', + +  // terminating newline +  '' +].join("\n"); + +// build template +var t = new LuigiTemplate(template_str); + +// print results +print(t.run({ +  greet:    'hello', +  name:     'paul', +  count_0:  0, +  count_1:  1, +  count_10: 10 +})); | 
