Skip to content

Variable formatting functions #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Simple interpolation: `{{=value}}`
* Scrubbed interpolation: `{{%unsafe_value}}`
* Name-spaced variables: `{{=User.address.city}}`
* Formatted output (see below): `{{=value | formatter1[, formatter2]...}}`
* If/else blocks: `{{value}} <<markup>> {{:value}} <<alternate markup>> {{/value}}`
* If not blocks: `{{!value}} <<markup>> {{/!value}}`
* Object/Array iteration: `{{@object_value}} {{=_key}}:{{=_val}} {{/@object_value}}`
Expand All @@ -19,6 +20,35 @@
var template = new t("<div>Hello {{=name}}</div>");
document.body.innerHtml = template.render({name: "World!"});

### Formatted output

Either value tag, i.e.:`{{=` or `{{%` can have the variable name followed by a vertical bar and one or more formatters separated by commas.

var template = new t(
'<div>Name: {{=user.name}}, age: {{=user.dob | dob2age}}</div>',
{
dob2age: (dob) => Math.floor((Date.now() - dob.getTime()) / 1000 / 60 / 60 / 24 / 365.25)
}
);
document.body.innerHTML = template({name:'Joe', dob: new Date(2000,0,1)});

The formatter functions should be provided along the template as an object containing functions that will receive the value to be formatted and return the formatted value. The property name for that function, `dob2age`, is the one used in the template.

Several formatters can be chained, the value of the first served to the next in the chain.

Values need not be limited to simple values but they can be objects as well, a pointless use would be:

var template = new t(
'<div>Full Name: {{=user | getFullName}}</div>',
{
getFullName: (user) => `${user.firstName} ${user.lastName}`
}
)

The test suite contains both simple and complex uses of formatters.

---

For more advanced usage check the [`t_test.html`](https://github.com/jasonmoo/t.js/blob/master/t_test.html).

This software is released under the MIT license.
Expand Down
28 changes: 18 additions & 10 deletions t.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
(function() {

var blockregex = /\{\{(([@!]?)(.+?))\}\}(([\s\S]+?)(\{\{:\1\}\}([\s\S]+?))?)\{\{\/\1\}\}/g,
valregex = /\{\{([=%])(.+?)\}\}/g;
valregex = /\{\{([=%])([\.\w]+?)\s*(\|\s*([\w,]+))?\s*\}\}/g;

function t(template) {
function t(template, formatters) {
this.t = template;
this.f = formatters || {};
}

function scrub(val) {
Expand All @@ -40,7 +41,7 @@
return vars;
}

function render(fragment, vars) {
function render(fragment, vars, formatters) {
return fragment
.replace(blockregex, function(_, __, meta, key, inner, if_true, has_else, if_false) {

Expand All @@ -50,19 +51,19 @@

// handle if not
if (meta == '!') {
return render(inner, vars);
return render(inner, vars, formatters);
}
// check for else
if (has_else) {
return render(if_false, vars);
return render(if_false, vars, formatters);
}

return "";
}

// regular if
if (!meta) {
return render(if_true, vars);
return render(if_true, vars, formatters);
}

// process array/obj iteration
Expand All @@ -75,7 +76,7 @@
if (val.hasOwnProperty(i)) {
vars._key = i;
vars._val = val[i];
temp += render(inner, vars);
temp += render(inner, vars, formatters);
}
}
vars._key = _;
Expand All @@ -84,8 +85,15 @@
}

})
.replace(valregex, function(_, meta, key) {
var val = get_value(vars,key);
.replace(valregex, function (_, meta, key, __, fns = '') {
const val = fns
.split(',')
.reduce(
function (val, fn) {
return typeof formatters[fn] == 'function' ? formatters[fn](val) : val
},
get_value(vars, key)
);

if (val || val === 0) {
return meta == '%' ? scrub(val) : val;
Expand All @@ -95,7 +103,7 @@
}

t.prototype.render = function (vars) {
return render(this.t, vars);
return render(this.t, vars, this.f);
};

window.t = t;
Expand Down
3 changes: 1 addition & 2 deletions t.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 81 additions & 3 deletions t_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<script src="http://code.jquery.com/qunit/qunit-1.9.0.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/qunit/qunit-1.9.0.css">

<script src="t.min.js"></script>
<script src="t.js"></script>
<script type="t/template" id="test">

<h1>{{=greeting}}</h1>
Expand Down Expand Up @@ -64,6 +64,8 @@ <h4>Test Values</h4>

{{/@test_values}}

<p id="DMY-date">{{=YMD_date|YMD2DMY}}</p>


</script>
<script>
Expand All @@ -73,7 +75,14 @@ <h4>Test Values</h4>
test("t.js tests", function() {

var div = document.createElement('div');
var template = new t(document.getElementById('test').innerHTML);
var template = new t(
document.getElementById('test').innerHTML,
{
YMD2DMY: function (d) {
return d.split('-').reverse().join('/')
}
}
);
div.innerHTML = template.render({
greeting: "Welcome!",
user: {
Expand All @@ -95,10 +104,73 @@ <h4>Test Values</h4>
"zero": 0,
"string_zero": "0",
"null": null
}
},
YMD_date: '2023-10-16'
});
document.body.appendChild(div);
console.log(div.innerHTML);

// example showing a formatter to recursively render a tree-like object of varying depth.
var div1 = document.createElement('div');

// The rendering function will become the formatter:
function render1(obj) {
return template1.render(obj)
}
var template1 = new t(`
{{@entries}}
<li>{{=_val.item}}
{{_val.entries}}
<ul>{{=_val | loop}}</ul>
{{:_val.entries}}
: {{=_val.value}}
{{/_val.entries}}
</li>
{{/@entries}}`,
{
// The formatter is the rendering function itself.
loop: render1
}
);
div1.innerHTML = render1({entries:
[
{
item: 'a',
entries: [
{
item: 'a-1',
entries: [
{
item: 'a-1-1',
value: 'A-One_One'
},
{
item: 'a-1-2',
entries: [
{
item: 'a-1-2-1',
value: 'A-One-Two-One'
}
]
}
]
},
{
item: 'a-1',
value: 'A-One'
}

]
},
{
item: 'b',
value: 'B'
}
]
});

document.body.appendChild(div1);
console.log(div1.innerHTML);

ok(/<h1>Welcome!<\/h1>/.test(div.innerHTML), "Simple interpolation.");
ok(/<h2>Jason<\/h2>/.test(div.innerHTML), "Name-spaced interpolation.");
Expand All @@ -118,7 +190,13 @@ <h4>Test Values</h4>
equal(document.getElementById("null").innerHTML, "", "Does not print null");

equal((new t("{{%quot}}")).render({quot: '"'}), "&quot;", "Escapes quotes properly")
equal(document.getElementById("DMY-date").innerHTML, "16/10/2023","Formatter")

equal(
div1.innerHTML.replaceAll(/\s/g,''),
'<li>a<ul><li>a-1<ul><li>a-1-1:A-One_One</li><li>a-1-2<ul><li>a-1-2-1:A-One-Two-One</li></ul></li></ul></li><li>a-1:A-One</li></ul></li><li>b:B</li>',
"Loop via formatter"
);
});


Expand Down