twigjs / twig.js Goto Github PK
View Code? Open in Web Editor NEWJS implementation of the Twig Templating Language
License: BSD 2-Clause "Simplified" License
JS implementation of the Twig Templating Language
License: BSD 2-Clause "Simplified" License
{% if test %}
foo
{% elseif test2 %}
bar
{% elseif test3 %}
foo
{% else %}
bar
{% endif%}
doen't work : Twig.logic.type.elseif not expected after a Twig.logic.type.elseif
It would be great if the Template.render were a plain Javascript function. At this moment if I'm not wrong it just interprets a set of nodes instead of creating a native javascript function with "eval".
Because of the overhead of that interpretation, it is a bit slow for me.
Here is a project that generates javascript code (thought it is generated by PHP):
https://github.com/schmittjoh/twig.js
But the idea is similar: the engine loads the template, compiles it into plain Javascript and then returns a javascript native function that can be executed directly.
I have done something similar for .NET: https://github.com/soywiz/csharputils/tree/master/CSharpUtils/CSharpUtils.Templates/Templates
https://github.com/soywiz/csharputils/blob/master/CSharpUtils/CSharpUtilsTests/Templates/TemplateTest.cs
If you want I can help you implementing the javascript dynarec for twig.js.
Hi There,
This is a really basic problem I fear but; I'm trying to use twig.min.js without node or express.
I include twig.min.js in the script tag and it's definitely there.
then (in view object):
this.template: twig({data:'
throws the error:
TypeError: Property 'twig' of object [object Window] is not a function
however I can console.log(twig) // returns Object -- so it's there
Any ideas?
What are the differences between this implementation of twig and Schmittjoh's twig.js?
The approach I would take to implementing macros is as follows:
look at the {% block %}
implementation as a guide for {% macro %}
compiling
You'll need to write a regular expression and some code to match the {% macro fnName(var1, var2, ...) %} syntax and extract the function name and the argument names. The regular expression matches are provided to the compile function as token.match
parsing
This is the important part you need for parsing a macro with a context:
Twig.parse.apply(this, [token.output, context])
token.output
represents the tokenized version of everything between the block
and endblock
- this is what you need to store for parsing every time the macro is called.
I would wrap token.output
in a function like this and store it in the Twig.Template object (this
):
var template = this; // keep access to template object in closure
this.macros[macroName] = function() {
var macroContext = {
// build from the function argument names parsed from the regex
// and the JS arguments object from invoking this function.
// provide access to other macros in this file
_self: this.macros
}
return Twig.parse.apply(template, [token.output, macroContext])
}
Look at the {% extends %}
implementation as a guide to the {% from ... %}
implementation
compile
You'll need to write a regex to extract the file name and the context name from the {% from ... %}
tag
parse
You're going to need code like this to load the remote template with the macros:
url = relativePath(this, fileName);
// load remote template
var remoteTemplate = Twig.Templates.loadRemote(url, {
output: 'macros', // return the template macros, not parsed output
method: this.url?'ajax':'fs',
async: false,
id: url
});
// make the macros available in the template render context
// under the name provided by the {% from fileName as contextName %}
context[contextName] = remoteTemplate.render({});
Because the macros are wrapped in a function, calling them from the template should cause the macro wrapper function to be invoked which will return the parsed macro.
You'll also need to update Twig.render
in twig.core.js
to return the macros object when the output
param is set to macros
. There's currently a check for output == 'blocks' you can model this change after.
This is mostly off the top of my head, so you're likely going to run into issues implementing, when you do just post a comment and I'll try and clarify.
I discovered this by accident (misspelled a var-name). :)
But if you pass a undefined value to the "escape"/"e" filter twig.js crashes, because the function assumes the value is a string and just calls .replace(...) on the value.
From what I can see most of the filters will have this problem.
I can easily fix most of the filter functions so they handle an undefined value, but I'm confused about how best to handle those that don't return a string, like "reverse", "sort", "key", "join" and "merge".
tilesWidget.activeTile = null
{{ tilesWidget.activeTile.title|default('') }}
throws uncaught exception: TwigException: Can't access a key title1 on an null or undefined object.
But in twig-php and https://github.com/schmittjoh/twig.js/ it works correctly
Subj. Maybe add recepie to the docs
Perhaps this is the wrong place to put this, but I was curious about soliciting input on this request...
Suppose I have a route like /:page
that will simply res.render(page + '.twig')
. These views utilized lots of API information from services like Github, Basecamp, etc.
Suppose I have in my templates:
{{ github_api('total_issues') }}
Would it be possible, if github_api
return s a Promise, for the rendering engine to continue compilation & processing once the callback is called?
When our designers & developers write templates with Twig (PHP), the synchronous nature allows lazy-loading of resources without the controllers having to expose dozens if not hundreds of data-points that are mostly not used.
With Twig.js, the asynchronous nature means that the immediate return value is only what's available, and these data points would have to be pre-fetched, which would mean hundreds of unnecessary API calls.
I'm willing to code this functionality in, but as evident in my last ticket & PR, I'd need someone to point me to the pertinent sections to adjust (otherwise I'll be floundering).
Thanks!
Howdy,
I'm new to Node.js and Express. It was cool to find some Twig ports, but then I installed Express3 without knowing it was new/alpha.
There is a new interface to implement :(
Hi,
me again. Found a strange bug (possible name clash with twig.js src ?)
Code:
var template = '{{notes|length}}:{{notes|json_encode()}}';
Twig.twig({data: template}).render({notes:['a', 'b', 'c']});
Expected Output:
3:["a","b","c"]
Actual Result:
TypeError: can't convert undefined to object
delete value._keys; (line 3627)
I've put debug statements into the json encode filter and the value passed to the function is "undefined" and not the values of the variable. But {{notes|length}}
works.
The following exception is thrown if one tries to search an array using a string with the IN operator.
Some example code:
{% if 'INACTIVE' in user.attributes%}
<button class="btn btn-danger"><i class="icon-ban-circle icon-white"></i> Account expired</button>
{% endif %}
Throws:
Uncaught TwigException: Twig.expression.type.variable cannot follow a Twig.expression.type.string at template:15 near 'in...'
Not sure what the timeline for this is or if it's worthy of an issue at the present moment.
But, it looks like it was thought of at some point:
if (async === false) {
return template;
} else {
// placeholder for now, should eventually return a deferred object.
return true;
}
The deferred object would make rendering logic a lot more readable in frameworks like Backbone.js.
{% if MyFunction('test') or MyFunction('truc') %}
foo
{% endif %}
=>
Twig.expression.type.expression cannot follow a Twig.expression.type.variable at template:24 near '('truc')...' OR Twig.expression.type.parameter.start cannot follow a Twig.expression.type.variable at template:24 near '(...'
http://twig.sensiolabs.org/doc/tags/for.html
We could have access to a loop object in a for block.
The javascript twig implementation expects dates to have millisecond resolution, but the php version expects a second resolution.
{{1348047737|date("d.m.Y")}}
twig-php: 19.09.2012
twig.js: 16.01.1970
Solution: simply multiply the unix timestamp by 1000 before creating a javascript date-object
Hi! Not sure if this is an issue (yet :D), but several filters we use in PHP (and would like to use within Express as well) behave slightly different based on res.locals as well as app.locals.
For example, the former may automatically add the right class to a navigation element depending on the current request's route, while the latter may automatically add a debug flag when the app.settings.env === 'dev'
or something.
In short, I'm wondering can twig.js functions and/or filters have access to the app
or local view variables, and if so, how?
Thanks!
{{ tilesWidget.getActiveTile().title }}
fails with uncaught exception: TwigException: Twig.expression.type.key.period cannot follow a Twig.expression.type.parameter.end at template:27 near '.title...'
This causes the parser to close the output tag before it's actually ended since it looks for a closing output tags "}}" before it parses expressions:
{{ {"key":{"i_key": "value"}} }}
The workaround is to add a space between the two closing object braces like so:
{{ {"key":{"i_key": "value"} } }}
Hi,
I updated to last tag recently to get the bugfix you made for me a month ago, and it added a new bug very difficult to isolate. The bug has been introduced by the commit 99d343f ("#46 set parent context in nested for loops correctly").
I'll give you all that I can to reproduce it, and I hope you'll find it. Meanwhile, we keep to the 0.4.5 tag.
This is the data we give to twig.js compilator : https://gist.github.com/hadrienl/adb88760ada9ad4285a2
This is the twig code to be compiled : https://gist.github.com/hadrienl/cfaf5e1cb64260174ba3
The first loop is ok, but the second is not. Posts returns an array of Data.__Posts items such as Blog.Posts. But in the second case, Post are arrays instead of objects : https://dl.dropbox.com/u/8352589/Scrinechoutes/Capture%20d%E2%80%99%C3%A9cran%202013-03-15%20%C3%A0%2010.41.47.png
Hi,
I've a compilation issue with this code :
{% if (Custom('gra')) %}
bla
{% endif %}
=> Expected filter or function before parameters, got Twig.expression.type.string
Custom
is a function wich return the attribute. The issue appears when the function is inside parenthesis. Don't tell me to remove them, because we need them in some case ;) for exemple :
{% if (Custom('foo') or Custom('bar')) and foobar == 1 %}
I want my templates to have html file extension...
This doesn't work:
twig = require('twig');
app.set('view engine', 'html');
app.engine('html', twig);
I haven't permissions to open #27 again, so create new ticket. Looks like it not completly fixed.
{% if mapController and mapController.hasWidget('route') and if mapController.getWidget('route').options.editable %}
{# object and false and undefined.options.editable #}
caused invalid 'in' operand object
. But it should stop executing other expressions on first false
I've published a Gist that extends twig.js and adds {% spaceless %}:
https://gist.github.com/mattkahl/4963386
Feel free to implement if you feel the execution is up to snuff.
{% for category in placesWidget.categories %}
{% if loop.index % 2 == 0 %}
<li><label><input type="checkbox" value="{{ category.alias }}" checked="checked" /><span class="text"><i class="icon place-{{ category.alias }}"></i>{{ category.title }}</span></label></li>
{% endif %}
{% endfor %}
throws uncaught exception: TwigException: Can't access a key index on an null or undefined object.
but
{% set i = 1 %}
{% for category in placesWidget.categories %}
{% if i % 2 == 1 %}
<li><label><input type="checkbox" value="{{ category.alias }}" checked="checked" /><span class="text"><i class="icon place-{{ category.alias }}"></i>{{ category.title }}</span></label></li>
{% endif %}
{% set i = i + 1 %}
{% endfor %}
works ok
There are cases, when ViewModels are too complex and have inheritance. So hasOwnProperty
would return wrong data (null
in most cases)
// Get the variable from the context
if (object.hasOwnProperty(key)) {
value = object[key];
} else if (object.hasOwnProperty("get"+capitalize(key))) {
value = object["get"+capitalize(key)];
} else if (object.hasOwnProperty("is"+capitalize(key))) {
value = object["is"+capitalize(key)];
} else {
value = null;
}
// -->
// Get the variable from the context
if (key in object) {
value = object[key];
} else if (object[("get"+capitalize(key)]) {
value = object["get"+capitalize(key)];
} else if (object["is"+capitalize(key)]) {
value = object["is"+capitalize(key)];
} else {
value = null;
}
Hi, me again. Sorry for finding all those bugs ;)
Template:
{% set values = [['a', 'a', 'a'], ['b', 'b', 'b']] %}
{% for val_1 in values %}
{% for val_2 in val_1 %}
{{val_2}}: loop={{loop.index0}}, parent_loop={{loop.parent.loop.index0}}
{% endfor %}
{% endfor %}
Expected Output:
a: loop=0, parent_loop=0
a: loop=1, parent_loop=0
a: loop=2, parent_loop=0
b: loop=0, parent_loop=1
b: loop=1, parent_loop=1
b: loop=2, parent_loop=1
Actual Output:
a: loop=0, parent_loop=0
a: loop=1, parent_loop=1
a: loop=2, parent_loop=2
b: loop=0, parent_loop=0
b: loop=1, parent_loop=1
b: loop=2, parent_loop=2
We have set functions in objects. Doesn't work on twig.js :(
{{ My.Object.hasAFunction('foo') }}
Hi,
I'm new to Node, and before I go any further with existing frameworks I'd like to handle Node itself.
I've been trying to use twig.js without Express by looking at the code but I can't seem to figure it out. Is it possible (without becoming an overwhelm), and if yes, may I ask you to give me some hints about it ?
Thanks a lot anyway, I'm a huge Twig fan and really appreciate this JS implementation :-)
Regards,
You should mention the version in twig.js and twig.min.js, it's always usefull.
// Twig.js - 0.5.4
// Copyright (c) 2011-2012 John Roepke
// Available under the BSD 2-Clause License
// https://github.com/justjohn/twig.js
Hi, how can extends be used in twig.js if block and parent are not supported?
Is there another way?
My use case is something like this:
{# panel.twig #}
<div class="panel">
{% block contents %}
{% endblock %}
</div>
{# child.twig #}
{% extends 'panel.twig' %}
{% block contents %}
{{ parent() }}
I'm in a .panel!
{% endblock %}
Hi Author,
I'm using twig.js into my symfony2 framework.
With non-AJAX request, my TWIG templates render successfully and being shown in my browser correctly.
Now I'm trying to use twig.js so that the AJAX part (the javascript code) and the PHP code share the same TWIG template. (As we know code sharing is very important for code maintenance).
In my template file, it contains some call to url() function, which uses a route identifier to re-construct the URL. But twig seems to be broken on this:
Error: uncaught exception: TwigException: Twig.expression.type.expression cannot follow a Twig.expression.type.variable at template:3 near '('_kuulabu_home_page...' OR Twig.expression.type.parameter.start cannot follow a Twig.expression.type.variable at template:3 near '(...'
(where _kuulabu_home_page is a route identifier)
Do you have any idea to help me solve the problem? Many thanks.
Simon
This: does not work when you use {% extends %} :
app.set('views', __dirname + '/views');
When parsing existing and working twig template i get this
ic.tokenize: ","Matched a ",e," regular expression of ",i),c}}throw new a.Erro
^
TwigException: Unable to parse 'for x in R'
This bug is very specific. There is what I have :
Posts
is an array of Post objectsBlog.Posts()
is a method which returns an array of another Posts objectsI list them in two differents places :
{% for Post in Posts %}
<h2>{{ Post.Title}}</h2>
{% endfor %}
{% for Post in Blog.Posts(10) %}
<h1>{{ Post.Title }}</h1>
{% endfor %}
No problem with that, everything is working fine.
Now, we inverse the blocks :
{% for Post in Blog.Posts(10) %}
<h1>{{ Post.Title }}</h1>
{% endfor %}
{% for Post in Posts %}
<h2>{{ Post.Title}}</h2>
{% endfor %}
Now, the Posts block is empty. Only the
Hi,
me again. The javascript twig implementation does not correctly handle the thousand-separator parameter when it's an empty string:
{{1000|number_format(2, "|", "")}}
twig-php: 1000|00
twig.js: 1,000|00
Hey, I've spottet an issue.
If you include a file and use a property on a non-existant object, twig.js crashed on line 2879 because this.options is undefined.
header.twig
<!DOCTYPE html>
<html>
<head>
<title>{{ page.title }}</title>
</head>
<body>
footer.twig
</body>
</html>
index.twig
{% include 'header.twig' %}
<p>Hello World</p>
{% include 'footer.twig' %}
If the page isn't defined I get this error:
TypeError: Cannot read property 'strict_variables' of undefined
at Twig.expression.definitions.parse (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:2879:37)
at Twig.expression.parse (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:3227:58)
at Array.forEach (native)
at Twig.expression.parse (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:3224:16)
at Twig.parse (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:425:55)
at Array.forEach (native)
at Twig.parse (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:396:16)
at Twig.Template.render (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:726:29)
at Twig.logic.definitions.parse (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:2098:38)
at Twig.logic.parse (/Users/m_abs/project/kostplan/node_modules/twig/twig.min.js:2260:43)
From what I can see looking at the source code at that line, this.options is undefined which of cause means that the line 2879 fails:
2879 if (this.options.strict_variables) {
I'm running node.js v0.8.16, express.js v3.0.5 and twig.js v0.4.6
I've implemented {{function()}} expression, but it's always impossible to chain functions and filter. For exemple :
{{ MyFunction('foo') | default('bar') }}
I'll try to do something, but it seems more complicated now :/
twig-php does not supports aliases like &&
, ||
http://twig.sensiolabs.org/doc/templates.html#logic . Imho better revoke support in twig.js also
Hi, i found your job really usefull for me ty for that ;)
but i would like do something like this :
var template = twig({
data: "{{ field('azerty') }}"
});
template.extendFunction("field", function(name) {
return(name);
});
but exactly like this it's not possible.
Have u an another solution for assign a new function to only one instance for a temporary need
ty ^^
Hi,
me again. You've done great work but i've found another bug. The twig manual describes how to use a condition inside a for loop. The implementation notes page for twig.js states that the for tag is "supported" but it does not support conditions inside a for tag.
Testcode:
var template = '{% for value in values if value == "a" %}{{value}}{% endfor %}';
Twig.twig({data: template}).render({values: ['a', 'a', 'b', 'b']});
Expected output:
aa
Real behaviour:
TwigException: Twig.expression.type.variable cannot follow a Twig.expression.type.variable at template:6 near 'if...' { message="Twig.expression.type.va...template:6 near 'if...'", name="TwigException", type="TwigException"}
http://twig.sensiolabs.org/doc/templates.html#variables . TwigJS does not calls getBar/isBar now.
Hi,
Blocks in children views do not get their contents replaced as they should.
Example : I render the test.twig file.
test.twig :
{% extends 'apps/admin/views/test_layout2.twig' %}
{% block block2 %}
content overloaded for block 2 (test.twig)
{% endblock %}
test_layout2.twig :
{% extends 'test_layout1.twig' %}
{% block block1 %}
overloaded content for block1 (test_layout2.twig)
<br>
below is block2<br>
<div id="block2" style="padding: 10px; border: 1px solid black; background: #CCCCFF">{% block block2 %}default content for block2{% endblock %}</div>
end block2
{% endblock %}
test_layout1.twig :
<html>
<head>
</head>
<body>
<h1>blocks and extends test</h1>
below is block1 (test_layout1.twig)<br>
<div id="block1" style="padding: 10px; border: 1px solid black; background: #CCFFCC">
{% block block1 %}
default content for block1
{% endblock %}
</div>
end block1
</body>
</html>
I'm sorry I'm new to git and node.js, so I figured it would be enough to put my example code and my fix proposal here.
I fixed this by removing the condition "if ( this.extend === null )" in twig.logic.js line 439. I hope I did not break anything, but it seems to work fine now.
Thank you for any input on this one justjohn !
PS : I got several errors when minifying the twig.js file because of missing semicolon ";"
http://twig.sensiolabs.org/doc/tests/sameas.html
So, i removed === and !== of the Twig.expression.operator.lookup class. We have to add a way to understand is sameas
in twig.js
{% if foo.attribute is sameas(false) %}
the foo attribute really is the ``false`` PHP value
{% endif %}
Reproducible by using express 3.0.x and extending a template via {% extends 'xxx.twig' %}
Result:
if (this.options.strict_variables) {
^
TypeError: Cannot read property 'strict_variables' of undefined
at Twig.expression.definitions.parse (/usr/share/www/search/dashionista/node_modules/twig/twig.js:2890:37)
at Twig.expression.parse (/usr/share/www/search/dashionista/node_modules/twig/twig.js:3238:58)
at Array.forEach (native)
at Twig.expression.parse (/usr/share/www/search/dashionista/node_modules/twig/twig.js:3235:16)
at Twig.parse (/usr/share/www/search/dashionista/node_modules/twig/twig.js:425:55)
at Array.forEach (native)
at Twig.parse (/usr/share/www/search/dashionista/node_modules/twig/twig.js:396:16)
at Twig.Template.render (/usr/share/www/search/dashionista/node_modules/twig/twig.js:722:29)
at Twig.Template.render (/usr/share/www/search/dashionista/node_modules/twig/twig.js:736:32)
at Twig.exports.renderFile.params.load (/usr/share/www/search/dashionista/node_modules/twig/twig.js:4213:39)
Easy fix in twig.js:
Line 733
// This template extends another, load it with this template's blocks
this.parent = Twig.Templates.loadRemote(url, {
method: this.url?'ajax':'fs',
base: this.base,
async: false,
id: url
});
Replace with
// This template extends another, load it with this template's blocks
this.parent = Twig.Templates.loadRemote(url, {
method: this.url?'ajax':'fs',
base: this.base,
async: false,
id: url,
options: this.options
});
Sorry for not submitting a pull request, but I'm tied up at the moment
{% set at = {'foo': null} %}
fails with token is undefined
I noticed that include tag is not supported yet. Are you planning to implement it?
{% set at = null %}
{% if at and at.tile == 1 %} active{% endif %}
fails with uncaught exception: TwigException: Can't access a key tile on an undefined object.
Hello,
If I use this kind of syntax :
{% - if 1 and not 2 %}{% endif %}
I've got this exception :
Uncaught TwigException: Twig.expression.type.number cannot follow a Twig.expression.type.variable at template:8 near '2...'
But {% if not 2 %} works.
Hi There,
I have some twig templates which use 'raw' only to output raw html into a template from a cms/database.
I'm curious as to why this is not a feature in this library as Mustache/jQuery templates etc all can handle this with ease.
Have you any ideas as to how I can use 'raw'? perhaps just mix-in a function/filter which just returns the template with injected html content?
Thanks very much
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.