Tour of Splice

Splice is a templating language

Splice supports nesting, partial templates, iterators, conditionals, variable assignment, local scope, comments, escape characters, and escapes HTML by default. In its minified form, it is only 3.06 KB uncompressed, and has no dependencies. This website uses Splice clientside to render the sidebar and header.

Download

The Splice engine can can be downloaded here and (minified) here as a JavaScript file.

HTML Templating

Include a script tag in your html file with an id of template, or a custom id if you prefer. Set the type attribute to text/x-template.


<script id="template" type="text/x-template">
  <ul>
    (:~ each chapters as 'chapter {
    <li>
      <a class="headerLink" href="#(: chapter.id :)">(: chapter.name :)</a>
    </li>
    }:)
  </ul>
</script>
          

Make sure to also link to a file containing the Splice engine before linking to JS files which utilize Splice.


<head>
  <!-- ... -->
  <script src="javascripts/splice.js"></script>
  <!-- ... -->
</head>
          

Call Splice.render with CSS selectors for your template source and destination elements to render your template in the DOM


Splice.render(yourData, "#template-src", "#destination");
          

For more control, call Splice.compile to return a function which returns a string for a given data set.


const evaluatorFn = Splice.compile(templateText);
const finalText = evaluatorFn({
  chapters: [{
    id: 2,
    name: 'The Vanishing Glass',
  }, {
    id: 3,
    name: 'The Letters from No One',
  }]
});
          

Partials

To register your partial template, simply invoke Splice.registerPartial with the id of your template.


Splice.registerPartial('sidebar_template');
          

Render a partial in your template:


(:~ partial sidebar_template {}:)
          

Iteration

To iterate through a collection, invoke each and pass an array. Within the body of each, you may reference elements of the collection with $.


(:~ each baskets {
  <p>(: $ :)</p>
}:)
          

Alternatively, you may refer to elements of your collection using an alias defined with as.


(:~ each baskets as 'aBasket {
  <p>(: aBasket :)</p>
}:)
          

Invocations may be nested as deeply as you please.


(:~ each valley.o as 'bogs {
  (:~ each bogs as 'holes {
    (:~ each holes as 'tree {
      <p>A rare (: tree :), a rattlin (: tree :)</p>
    }:)
  }:)
}:)
          

Conditionals

Splice has two conditional functions: if and unless. They both takes one argument and a body which may be evaluated depending on whether the argument is truthy or falsy.


(:~ if isValid {
<p>Access Granted</p>
}:)

(:~ unless isValid {
<p>Access Denied</p>
}:)
          

Assignment

Assign bindings in the current scope using the assignment function: def.


(:~ def 'snack meal {}:)
          

Then use the new binding to access its value within the current scope.


<p>I'm just going to have a (: snack :).</p>
          

Properties

If a binding references an object, access its properties using dot notation.


(: person.community.region :)
          

Execution Context

in takes a scope object and uses it to create different context for evaluating bindings within its body. For instance:


<li>(: person.name :)</li>
<li>(: person.job :)</li>
          

becomes:


(:~ in person {
  <li>(: name :)</li>
  <li>(: job :)</li>
}:)
          

Escaping

By default, text referenced by a binding is HTML-escaped to avoid cross-site scripting. To render unescaped html, use (:! instead of (: when evaluating a binding.


(:~ def 'unsafe '<a>docs</a> {}:)
<li>(:! unsafe :)</li>
          

In addition, any character anywhere in a Splice template may be explicitly escaped by prefixing it with \. This prevents a character from being evaluated as syntax. For instance, \(: will render (: as text. To display a backslash in the template, you must escape it.


<p>A Splice expression begins with \(: and ends with \:).</p>
          

Browser Compatibility

Splice does not support Internet Explorer.

Splice in Depth

Expressions

Every Splice expression returns a string which is inserted in place within the template at evalution time, even if it is an empty string. Splice expressions begin with (: and end with :)

There are two kinds of Splice expressions: evaluations and invocations. Evalutions are used to evaluate a binding in scope and return its text content.

Evaluate a binding:


  • (: id :)
  • Function invocation begins with (:~ and always contains a body defined by opening and closing brackets {} which may or may not be evaluated. Function bodies can contain either kind of expression or text.

    Evaluate a function:

    
    (:~ if response {
      <p>Response received!</p>
      (:~ each response.headers {
        <li>(: $ :)</li>
      }:)
    }:)
              

    Scope

    Rendering a Splice template requires an object representing the global scope of the template. Within the template, properties of the global scope object can be accessed by bindings. Invoking Splice.compile returns a function which takes a scope and uses it to evaluate its internal AST.

    
    const template = `
      (:~ each pages as 'page {
        <p>(: page.title :)</p>
        (:~ if page.articles {
        <ul>
          (:~ each articles as 'article {
          <li>(: article :)</li>
          }:)
        </ul>
        }:)
      }:)
    `
    const scope = {
      pages: [{
        title: 'Unforseen Consequences',
        articles: ['When to expect the unexpe....', 'Losing sight...'],
      }, {
        title: 'New Beginnings',
        articles: ['As Simon discovered....', 'Lab test demonstrate...'],
      }, {
        title: 'A bench in the woods',
        articles: false,
      }]
    }
    
    Splice.compile(template)(scope);
              

    Function bodies define a new scope. Splice uses lexical scoping rules, meaning that inner functions have access to variables defined in the outer scope but not vice-versa.

    The exception is that the in function allows a user to set a new context for evaluating bindings, and in doing so, truncates the scope.

    Data Types

    Within a Splice template there are two data types that can be created: text and atoms.

    Text

    Text literals are defined by any set of characters outside of a Splice expression or within a function body not enclosed by (: :).

    Atoms

    Atom literals are defined by prefixing a set of non-whitespace characters with a single quote mark ' within a list of arguments to a function invocation. Atoms are used to pass text to a function invocation.

    
    (:~ def 'pop soda {}:)
              

    All else is either syntax or bindings which can reference either built in functions or data stored in scope. Javascript objects and arrays stored in scope are treated by Splice as 'scope objects'. Strings are treated as text.

    Source

    The source code is devided in five sections.

    • Lexer & Parser

    • Generator

    • In-template functions

    • Internal Utility Functions

    • Public Interface

    The in-template functions section will be of most interest if you'd like to implement your own helper functions. The first parameter must reserved for the scope, and the last for the body of the function (an abstract syntax tree).

    Source here

    
    const Splice = (function() {
      // IN-TEMPLATE HELPER FUNCTIONS
      // ============================
    
      //...
    
      // if :: Object, Object, Array{Object} -> String
      templateFns.if = (scope, expr, body) => {
        const innerScope = Object.assign({}, scope);
        return evaluate(expr, innerScope) ? evaluateAll(body, innerScope) : '';
      };
    
      //...
    }());
              

    JavaScript Interface

    Splice.compile

    :: String -> [Object] -> String

    Returns a function which accepts a scope object and returns a string.

    
    const fn = Splice.compile('<p>(: message :)</p>');
    fn({ message: 'Hello, World!' });
    // returns "<p>Hello, World!"</p>
              

    Splice.registerPartial

    :: String, String

    :: String

    The first argument sets the name for the partial template that can be used to access it within your Splice template(s). Splice.registerPartial takes a second optional argument which if present, is used as the partial template input string. If not present, Splice.registerPartial assumes the name of the partial (argument 1) is also the id of a DOM element containing the partial template.

    
    Splice.registerPartial('sidebar');
              
    
    const partialTemplate = `
    <aside class="sidebar (: chapter.id :)">
      <nav>
        (:~ each chapter.pages as 'page {
        <ul>
          <li><a class="pageLink" href="#(: page.id :)">(: page.title :)</a></li>
          <ul>
            (:~ each page.articles as 'article {
            <li><a class="articleLink (: page.id :)" href="#(: article.id :)">(: article.title :)</a></li>
            }:)
          </ul>
        </ul>
        }:)
      </nav>
    </aside>
    `
    
    Splice.registerPartial('sidebar', partialTemplate);
              

    See also Template Functions::partial

    Splice.render

    :: [Object], String, String

    The first argument represents the 'scope' for the evaluation of the template. The second argument is used as a CSS selector to identify the script tag containing the template within the DOM. Likewise the third argument is used as a CSS selector to identify the desination element where the final html generated from the template will be rendered.

    
    Splice.render({ message: 'Hello, World!' }, "#src", "#destination");
              

    Template Functions

    each

    :: Array{}, "as" Atom -> String

    :: Array{} -> String

    each is used to iterate through a collection specified by the first argument. Elements can be referenced within the function body using $. Appending as 'alias to the arguments list defines an alias for the current element in the collection. each evaluates its body with a new scope giving access to the current member of the collection and returns a string of these evaluations concatenated together.

    
    <ul>
    (:~ each library as 'book {
      <li><p>(: book.knowledge :)</p></li>
    }:)
    </ul>
              

    comment

    :: _ -> String

    comment takes no arguments and returns an empty string. The body provides a space for comments. Any function that always returns an empty string can contain comments in its body.

    
    (:~ comment { This is a comment. }:)
              

    def

    :: Atom, b -> String

    def is used to assign variables. It returns an empty string.

    
    (:~ def 'list array {}:)
              

    No we can use list to reference array.

    
    <ul>
    (:~ each list {
      <li><p>(: $ :)</p></li>
    }:)
    </ul>
              

    if

    :: a -> String

    The body of if will be evaluated and returned if the argument to if is truthy. Otherwise, it returns an empty string.

    
    (:~ if isTrue {
      <li><p>(: big :)</p></li>
    }:)
              

    in

    :: Object -> String

    in limits the scope of its body to the argument that is passed in. It can then evaluate bindings in this new context. For instance, suppose we have a global scope that looks like this:

    
    Splice.compile(html)({
      context: { foo: 'Hello, World!' }
    })
              

    Instead of using dot notation to access context.foo, we could use in:

    
    (:~ in context {
      <li><p>(: foo :)</p></li>
    }:)
              

    partial

    :: Array{Object} -> String

    Used in conjunction with Splice.registerPartial to render a partial template.

    
    (:~ each chapters as 'chapter {
      (:~ partial sidebar_template {}:)
    }:)
    

    See also Splice Functions::partial

    unless

    :: a -> String

    The body of unless will be evaluated and returned if the argument to unless is falsy. Otherwise, it returns an empty string.

    
    (:~ unless error {
      <li><p>(: headers.contentType :)</p></li>
    }:)
              

    Try splice

    Template:

    Javascript:

    Output: