Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
PitPik committed Jan 12, 2025
1 parent 197c2e6 commit d365e3f
Show file tree
Hide file tree
Showing 4 changed files with 650 additions and 577 deletions.
166 changes: 72 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,65 @@
# {{schnauzer.js}} - (almost) Logic-less templates with JavaScript
# {{schnauzer.js}} - Handlebars templates with JavaScript

[Schanuzer](http://github.com/PitPik/schnauzer) is largely compatible with Mustache and Handlebars templates. In most cases it is possible to swap out Mustache or Handlebars with Schanuzer and continue using your current templates.
Schanuzer is a Handlebars parser/renderer that lets you build **semantic templates** effectively with no frustration.

Schanuzer is also very small and fast. It has the power of Handlebars but is almost the size of Mustage (12.1KB minified, ~4.89KB gZip) and therefore also perfectly suitable for mobile applications.
Renderin with Schnauzer is about ~33% faster than with Handlebars, when using inline partials, up to 10x faster. Parsing is also blasting fast, almost as fast as rendering.
Schanuzer is largely compatible with Mustache and Handlebars templates. In most cases it is possible to swap out Mustache or Handlebars with Schanuzer and continue using your current templates.

Schanuzer is also very small and fast. It has the power of Handlebars but is almost the size of Mustage (10.90KB minified, ~4.6KB compressed) and therefore also perfectly suitable for mobile applications.
Rendering with Schnauzer is about ~33% faster than Handlebars, and parsing is around as fast a rendering.

## Where to use schnauzer.js?

You can use schnauzer.js to render templates anywhere you can use JavaScript. This includes web browsers, server-side environments such as [node](http://nodejs.org/), and [CouchDB](http://couchdb.apache.org/) views.
You can use schnauzer.js to render templates anywhere you can use JavaScript. This includes web browsers (even Internet Explorer), server-side environments such as [node](http://nodejs.org/), and [CouchDB](http://couchdb.apache.org/) views.

schnauzer.js ships with support for both the [CommonJS](http://www.commonjs.org/) module API and the [Asynchronous Module Definition](https://github.com/amdjs/amdjs-api/wiki/AMD) API, or AMD.

### Dynamic rendering
### Dynamic rendering, keeps you template alive.

Other than handlebars, schnauzer has 2 optional functions that get triggered with every single tag that gets rendered so the template can be kept alive even after the first rendering.
`renderHook()` and `loopHelper()`. With those functions it's possible to keep track of all the rendered variables and rendering functions including a special character `%` set infront of every variable. This way, when used in the DOM, it's possible to overwrite parts of the rendered template after it was first rendered without having to re-render the whole template.
This is perfect for developing MVC like libraries/frameworks that need to partialy update HTML on the fly.
Schnauzer has 2 optional functions that get triggered with every single tag that gets rendered so the template can be kept alive even after the initial rendering.
`renderHook()` and `loopHelper()`. With those functions it's possible to keep track of all the rendered variables and rendering functions including a special character `%` set in front of every variable. This way, when used in the DOM, it's possible to overwrite parts of the rendered template after it was first rendered without having to re-render the whole template.
This is perfect for developing MVC like libraries/frameworks that need to partially update HTML on the fly.

* * *

## handlebars.js [not present any more.]

(Handlebars facade for Schnauzer) ... maybe back one day.

## Usage

Below is a quick example how to use schnauzer.js:

```js
var viewModel = {
title: "Joe",
calc: function ($1) {
return parseFloat($1) * 0.9;
}
};

var output = new Schnauzer("{{title}} spends {{calc 200}}").render(viewModel);
var template = `
<p>Hello, my name is {{name}}.
I am from {{hometown}}.
I have
{{kids.length}} kids:
</p>
<ul>
{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}
</ul>
`;
var schnauzer = new Schnauzer(template);
var data = {
"name": "Peter",
"hometown": "Somewhere, CA",
"kids": [
{"name": "Jimmy", "age": "7"},
{"name": "Sally", "age": "4"}
]};
var result = schnauzer.render(data);

// Would render:
// <p>Hello, my name is Peter. I am from Somewhere, CA. I have 2 kids:</p>
// <ul>
// <li>Jimmy is 7</li>
// <li>Sally is 4</li>
// </ul>
```

In this example `Schnauzer()` is initialized with the template as first argument (options would be the second optional argument) and the `render()` function that takes one parameters: the `viewModel` object that contains the data and code needed to render the template.
In this example `Schnauzer()` is initialised with the template as first argument (options would be the second optional argument) and `render()` that takes one parameters: the `viewModel` object that contains the data and code needed to render the template.

## API

```js
new Schnauzer(templateOrOptions: String | { [key: String]: any }, options?: { [key: String]: any }) {
tags: ['{{', '}}'], // used tags: default is {{ }}
new Schnauzer(templateOrOptions?: String | { [key: String]: any }, options?: { [key: String]: any }) {
entityMap: { // characters to be escaped
'&': '&amp;',
'<': '&lt;',
Expand All @@ -56,25 +71,24 @@ new Schnauzer(templateOrOptions: String | { [key: String]: any }, options?: { [k
'=': '&#x3D;'
},
escapeHTML: true, // if false, Schnauzer renders like all tags are set to {{{ }}}
helpers: { [name: String]: Function }, // short-cut for registerHelper
partials: { [name: String]: String | Function }, // short-cut for registerPartial
helpers: { [name: String]: Function }, // short-cut for registerHelper()
partials: { [name: String]: String | [] }, // short-cut for registerPartial()
self: 'self', // name of initial partial
limitPartialScope: true, // sets limiting of scope inside partials (like in HBS)
strictArguments: true, // sets all arguments from a helper automatically to strict (model value before helper)
renderHook: Function // called every time an inline | block element renders
loopHelper: Function // Loop cycle callback for Array | Object inside #each
loopHelper: Function // Loop cycle callback for Array | Object inside #each or #myArray
})
.render(data: { [key: String]: any }, extraData: { [key: String]: any }): string
.parse(text: String): Function // returns a partial for re-cycling, re-usage in other instance
.render(data: { [key: String]: any }, extraData: any): string
.parse(text: String): [] // returns a partial for re-cycling, re-usage in other instance
.registerHelper(name: String, func: Function): void
.unregisterHelper(name: String): void
.registerPartial(name: String, html: String | Function): Function // Function: pre-parsed
.registerPartial(name: String, html: String | []): [] // parsed tree
.unregisterPartial(name: String): void
.setTags(tags: [String, String]): void
.escapeExpression(string): String // returns escaped text according to entityMap
```
`parse()` is only needed if the template was not passed to `Schnauzer()` in the first place. This might be handy if you're not sure if this template will ever be used...
`parse()` is only needed if the template was not passed to `new Schnauzer()` in the first place. This might be handy if you're not sure if this template will ever be used...

In `render(data, extraData)` you can pass some extra data source needed for parsing your template. If renderer doesn't find the required data in `data` then it looks inside `extraData`. `extraData` can be and opbject or an array of objects.
In `render(data, extraData)` you can pass some extra data source needed for parsing your template. If renderer doesn't find the required data in `data` then it looks inside `extraData`.
This can be very handy if you have, for example, an array of links to render where the root of the link is always the same (stored in extraModel) but the end of the link is different (stored in the array). So you don't have to put the root inside evey item of the array.

```js
Expand All @@ -95,76 +109,40 @@ var output = new Schnauzer('{{#links}}<a href="{{root}}/{{link}}">{{text}}</a>{{
.render(data, extraData);
```

#### Functions in helpers

All the functions used inside the model or being registered or passed in options as helpers can be used as inline or block elements that have the same arguments and scope:
## Precompiling Templates
Schnauzer allows templates to be precompiled and included as a JSON tree rather than the Schnauzer template allowing for faster startup time.

```handlebars
{{#helper foo}}some text with {{meaning}}{{/helper}}
or inline
{{helper foo}}
```
```js
var data = {
meaning: 'some more meaning',
foo: 'This would be',
helper: helper
}

function helper([$1, $2,...]) { // can also be passed as option or registered via .registerHelper()
var args = [];
var options = arguments[arguments.length - 1];

for (var i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}

return $1 + ' ' + options.fn(this);
}
```
In this case you would get "This would be some text with some more meaning" in the block and "This would be " if used as inline helper.
$1 etc. represent the String passed with the block (here "foo").
```options.fn()``` is the text that was rendered if it was inside a block element (empty if inline usage), ```options.inverse()``` the block after an else (if exists, else undefined).

Options inside helper functions work almost like with Handlebars:
```js
options: {
data: { root: {…}, scope, parent, first, last index, key, number, length },
hash: {}, // keeps all the parameters as a hash
name: "", // name of the helper
fn?: ƒ (context, options), // only on block helpers; same as with Handlebars
inverse?: ƒ (context, options), // only on block helpers; same as with Handlebars
escapeExpression: ƒ(), // like Handlebars.escapeExpression
SafeString: ƒ(), // like Handlebars.SafeString
keys: ƒ(), // like window.Object.keys()
extend: ƒ(newObject, hostObject), // like Handlebars.Utils.extend
concat: ƒ(newArray, hostArray), // Concats 2 arrays
getDataDetails: ƒ(), // returns details of the data (arguments)
}
```

Inline helpers can be used for something like the following:

```handlebars
{{today}}
var template = `
{{#*inline 'myInlinePartial'}}
{{foo}} ...
{{/inline}}
{{> myInlinePartial}}
`;
var schnauzer = new Schnauzer(template, {
partials: { another: 'Simple {{bar}}' }
});
```
This would produce 3 partial trees that can be found in `schnauzer.partials` and would look like:

```js
today: function() {
return new Date().toLocaleString();
{
another: [{…}, {…}],
myInlinePartial: [{…}, {…}],
self: [{…}, {…}, {…}] // main partial
}
```
You could now use `JSON.stringify()` to convert this to a text file.
When received by the browser as a regular JSON again it then can be registered back like `new Schnauzer({ partials: partials })` in one go for all 3 partials.
This explains also why `.registerPartial(name, template)` can receive an array as 2nd argument. You can do: `.registerPartial('another', another)` and pass the array containing the tree of the `another` partial.

## How Schnauzer templates works

All basic features of Schnauzer are explained in the [Handlebars decumentation](https://handlebarsjs.com/guide/).

## How Schnauzer templates works

## Pre-parsing and Caching Templates
All basic features of Schnauzer are explained in the [Handlebars documentation](https://handlebarsjs.com/guide/).

By default, when schnauzer.js first parses a template, it builds a tree of currying functions that keeps all data cached. The currying functions not only already hold the parsed HTML snippets but also the right key to the JSON being passed so it can concatenate strings on the fly. The rendering functions are highly optimised therefore Schnauzer currently renders 1/3 faster than Handlebars. Parsing is about 10x faster.

### New in 2.x.x
### New in 3.x.x

The new version 2.x.x has a new parser that is faster, it renders faster than the previous major version (from 65ms down to 52ms on a standard comparison test with 500 runs: 16K template + 1k partial and quite a bit of data), uses a lot less memory and allowes a more flexible way of using variables within tags (no limits on characters). It has better support (API) for "active rendering" after 1st rendering. All Schnauzer functionalities are now supported to be altered through the new API (renderHook, loopHelper). The "activate" API doesn't compromise speed any more if not used so you can have 99.99% of the high speed parsing/rendering if used only as a one-time template-rendering engine.
Unfortunately the new parser is a bit bigger (1.02KB source) than the old one, but we profit now from more speed, less memory usage and more flexibility.
The new version 3.x.x is completely re-programmed, has a new parser that is 2.6 times faster than in version 2.x.x, renders faster than ever and uses less memory. It is now also possible to pre-compile.
Loading

0 comments on commit d365e3f

Please sign in to comment.