Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Support {% ifchanged Expression(s) %} like Django does

Django ifchanged can accept parameters, so it is possible to check for a value
which does not appear in output. Django documentation says to support one or
more parameters, this implementation supports up to 4 paramters (only limited
by the parser expression, this can be improved to support even more
parameters when needed).

{% for date in list %}
  {% ifchanged date.month %}
     {{ date.month }}:{{ date.day }}
  {% else %}
     {{ date.day }}
  {% endifchanged %}
{% endfor %}

See erlydtl_unittests for some other examples.

The other (Django) syntax to use {% ifchanged %} without parameter works like before.
  • Loading branch information...
commit 11b43809a0aad01f5927e2cf365f90afb4a2bbec 1 parent b076a6e
Thomas Geffert thge authored
2  README.markdown
View
@@ -175,8 +175,6 @@ Differences from standard Django Template Language
The "regroup" tag must have an ending "endregroup" tag.
-The "ifchanged" tag cannot take arguments.
-
Tests
-----
45 src/erlydtl_compiler.erl
View
@@ -545,14 +545,24 @@ body_ast(DjangoParseTree, Context, TreeWalker) ->
{IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
ifelse_ast(Expression, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifchanged', Contents}, TreeWalkerAcc) ->
+ ({'ifchanged', Expression, Contents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(Contents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = empty_ast(TreeWalker1),
- ifchanged_ast(Contents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
- ({'ifchangedelse', IfContents, ElseContents}, TreeWalkerAcc) ->
+ case Expression of
+ '$undefined' ->
+ ifchanged_ast(Contents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+ _ ->
+ ifchanged_ast({expr, Expression}, IfAstInfo, ElseAstInfo, Context, TreeWalker2)
+ end;
+ ({'ifchangedelse', Expression, IfContents, ElseContents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
- ifchanged_ast(IfContents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+ case Expression of
+ '$undefined' ->
+ ifchanged_ast(IfContents, IfAstInfo, ElseAstInfo, Context, TreeWalker2);
+ _ ->
+ ifchanged_ast({expr, Expression}, IfAstInfo, ElseAstInfo, Context, TreeWalker2)
+ end;
({'ifelse', Expression, IfContents, ElseContents}, TreeWalkerAcc) ->
{IfAstInfo, TreeWalker1} = body_ast(IfContents, Context, TreeWalkerAcc),
{ElseAstInfo, TreeWalker2} = body_ast(ElseContents, Context, TreeWalker1),
@@ -1089,11 +1099,28 @@ for_loop_ast(IteratorList, LoopValue, Contents, {EmptyContentsAst, EmptyContents
merge_info(merge_info(Info, EmptyContentsInfo), LoopValueInfo)
}, TreeWalker2}.
-ifchanged_ast(ParseTree, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, _Context, TreeWalker) ->
- SourceText = lists:flatten(erlydtl_unparser:unparse(ParseTree)),
- {{erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged),
- [erl_syntax:string(SourceText), IfContentsAst, ElseContentsAst]),
- merge_info(IfContentsInfo, ElseContentsInfo)}, TreeWalker}.
+ifchanged_ast({expr, Expressions}, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, Context, TreeWalker) ->
+ Info = merge_info(IfContentsInfo, ElseContentsInfo),
+ ValueAstFun = fun(Expr, {LTreeWalker, LInfo, Acc}) ->
+ {{EAst, EInfo}, ETw} = value_ast(Expr, false, Context, LTreeWalker),
+ {ETw, merge_info(LInfo, EInfo), [erl_syntax:tuple([erl_syntax:integer(erlang:phash2(Expr)), EAst])|Acc]} end,
+ {TreeWalker1, MergedInfo, Changed} = lists:foldl(ValueAstFun, {TreeWalker, Info, []}, Expressions),
+ {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged), [erl_syntax:list(Changed)]),
+ [erl_syntax:clause([erl_syntax:atom(true)], none,
+ [IfContentsAst]),
+ erl_syntax:clause([erl_syntax:underscore()], none,
+ [ElseContentsAst])
+ ]), MergedInfo}, TreeWalker1};
+ifchanged_ast(Contents, {IfContentsAst, IfContentsInfo}, {ElseContentsAst, ElseContentsInfo}, _Context, TreeWalker) ->
+ Info = merge_info(IfContentsInfo, ElseContentsInfo),
+ Key = erl_syntax:integer(erlang:phash2(Contents)),
+ {{erl_syntax:case_expr(erl_syntax:application(erl_syntax:atom(erlydtl_runtime), erl_syntax:atom(ifchanged), [erl_syntax:list([erl_syntax:tuple([Key, IfContentsAst])])]),
+ [erl_syntax:clause([erl_syntax:atom(true)], none,
+ [IfContentsAst]),
+ erl_syntax:clause([erl_syntax:underscore()], none,
+ [ElseContentsAst])
+ ]), Info}, TreeWalker}.
+
cycle_ast(Names, Context, TreeWalker) ->
{NamesTuple, VarNames} = lists:mapfoldl(fun
18 src/erlydtl_parser.yrl 100755 → 100644
View
@@ -85,6 +85,11 @@ Nonterminals
IfChangedBlock
IfChangedBraced
+ IfChangedExpression
+ IfChangedExpression1
+ IfChangedExpression2
+ IfChangedExpression3
+ IfChangedExpression4
EndIfChangedBraced
IfEqualBlock
@@ -317,9 +322,18 @@ Unot -> not_keyword IfExpression : {expr, "not", '$2'}.
ElseBraced -> open_tag else_keyword close_tag.
EndIfBraced -> open_tag endif_keyword close_tag.
-IfChangedBlock -> IfChangedBraced Elements ElseBraced Elements EndIfChangedBraced : {ifchangedelse, '$2', '$4'}.
-IfChangedBlock -> IfChangedBraced Elements EndIfChangedBraced : {ifchanged, '$2'}.
+IfChangedBlock -> IfChangedBraced Elements ElseBraced Elements EndIfChangedBraced : {ifchangedelse, '$1', '$2', '$4'}.
+IfChangedBlock -> IfChangedBraced Elements EndIfChangedBraced : {ifchanged, '$1', '$2'}.
IfChangedBraced -> open_tag ifchanged_keyword close_tag.
+IfChangedBraced -> open_tag ifchanged_keyword IfChangedExpression close_tag : '$3'.
+IfChangedExpression -> IfChangedExpression1 IfChangedExpression2 : ['$1', '$2'].
+IfChangedExpression -> IfChangedExpression1 IfChangedExpression2 IfChangedExpression3 : ['$1', '$2', '$3'].
+IfChangedExpression -> IfChangedExpression1 IfChangedExpression2 IfChangedExpression3 IfChangedExpression4: ['$1', '$2', '$3', '$4'].
+IfChangedExpression -> Value : ['$1'].
+IfChangedExpression1 -> Value : '$1'.
+IfChangedExpression2 -> Value : '$1'.
+IfChangedExpression3 -> Value : '$1'.
+IfChangedExpression4 -> Value : '$1'.
EndIfChangedBraced -> open_tag endifchanged_keyword close_tag.
IfEqualBlock -> IfEqualBraced Elements ElseBraced Elements EndIfEqualBraced : {ifequalelse, '$1', '$2', '$4'}.
23 src/erlydtl_runtime.erl
View
@@ -228,16 +228,25 @@ pop_ifchanged_context() ->
[_|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE),
put(?IFCHANGED_CONTEXT_VARIABLE, Rest).
-ifchanged(SourceText, EvaluatedText, AlternativeText) ->
+ifchanged(Expressions) ->
[IfChangedContext|Rest] = get(?IFCHANGED_CONTEXT_VARIABLE),
- PreviousText = proplists:get_value(SourceText, IfChangedContext),
+ {Result, NewContext} = lists:foldl(fun (Expr, {ProvResult, Context}) when ProvResult == true ->
+ {_, NContext} = ifchanged2(Expr, Context),
+ {true, NContext};
+ (Expr, {_ProvResult, Context}) ->
+ ifchanged2(Expr, Context)
+ end, {false, IfChangedContext}, Expressions),
+ put(?IFCHANGED_CONTEXT_VARIABLE, [NewContext|Rest]),
+ Result.
+
+ifchanged2({Key, Value}, IfChangedContext) ->
+ PreviousValue = proplists:get_value(Key, IfChangedContext),
if
- PreviousText =:= EvaluatedText ->
- AlternativeText;
+ PreviousValue =:= Value ->
+ {false, IfChangedContext};
true ->
- NewContext = [{SourceText, EvaluatedText}|proplists:delete(SourceText, IfChangedContext)],
- put(?IFCHANGED_CONTEXT_VARIABLE, [NewContext|Rest]),
- EvaluatedText
+ NewContext = [{Key, Value}|proplists:delete(Key, IfChangedContext)],
+ {true, NewContext}
end.
cycle(NamesTuple, Counters) when is_tuple(NamesTuple) ->
12 src/erlydtl_unparser.erl
View
@@ -46,16 +46,16 @@ unparse([{'if', Expression, Contents}|Rest], Acc) ->
unparse(Rest, [["{% if ", unparse_expression(Expression), " %}",
unparse(Contents),
"{% endif %}"]|Acc]);
-unparse([{'ifchanged', IfContents}|Rest], Acc) ->
- unparse(Rest, [["{% ifchanged %}",
+unparse([{'ifchanged', Expression, IfContents}|Rest], Acc) ->
+ unparse(Rest, [["{% ifchanged ", unparse_expression(Expression), " %}",
unparse(IfContents),
- "{% endif %}"]|Acc]);
-unparse([{'ifchangedelse', IfContents, ElseContents}|Rest], Acc) ->
- unparse(Rest, [["{% ifchanged %}",
+ "{% endifchanged %}"]|Acc]);
+unparse([{'ifchangedelse', Expression, IfContents, ElseContents}|Rest], Acc) ->
+ unparse(Rest, [["{% ifchanged ", unparse_expression(Expression), " %}",
unparse(IfContents),
"{% else %}",
unparse(ElseContents),
- "{% endif %}"]|Acc]);
+ "{% endifchanged %}"]|Acc]);
unparse([{'ifelse', Expression, IfContents, ElseContents}|Rest], Acc) ->
unparse(Rest, [["{% if ", unparse_expression(Expression), " %}",
unparse(IfContents),
21 tests/src/erlydtl_unittests.erl
View
@@ -241,9 +241,28 @@ tests() ->
{"If changed",
<<"{% for x in list %}{% ifchanged %}{{ x }}\n{% endifchanged %}{% endfor %}">>,
[{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nthree\n">>},
+ {"If changed/2",
+ <<"{% for x, y in list %}{% ifchanged %}{{ x|upper }}{% endifchanged %}{% ifchanged %}{{ y|lower }}{% endifchanged %}\n{% endfor %}">>,
+ [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "c"], ["Three", "b"]]}], <<"ONEa\nTWO\nb\nTHREE\nc\nb\n">>},
{"If changed/else",
<<"{% for x in list %}{% ifchanged %}{{ x }}\n{% else %}foo\n{% endifchanged %}{% endfor %}">>,
- [{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nfoo\nthree\nfoo\nfoo\n">>}
+ [{'list', ["one", "two", "two", "three", "three", "three"]}], <<"one\ntwo\nfoo\nthree\nfoo\nfoo\n">>},
+ {"If changed/param",
+ <<"{% for date in list %}{% ifchanged date.month %} {{ date.month }}:{{ date.day }}{% else %},{{ date.day }}{% endifchanged %}{% endfor %}\n">>,
+ [{'list', [[{month,"Jan"},{day,1}],[{month,"Jan"},{day,2}],[{month,"Apr"},{day,10}],
+ [{month,"Apr"},{day,11}],[{month,"May"},{day,4}]]}],
+ <<" Jan:1,2 Apr:10,11 May:4\n">>},
+ {"If changed/param2",
+ <<"{% for x, y in list %}{% ifchanged y|upper %}{{ x|upper }}{% endifchanged %}\n{% endfor %}">>,
+ [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "c"], ["Three", "b"]]}], <<"ONE\n\nTWO\n\nTHREE\nTHREE\n">>},
+ {"If changed/param2 combined",
+ <<"{% for x, y in list %}{% ifchanged x y|upper %}{{ x }}{% endifchanged %}\n{% endfor %}">>,
+ [{'list', [["one", "a"], ["two", "A"], ["two", "B"], ["three", "b"], ["three", "B"], ["three", "c"]]}], <<"one\ntwo\ntwo\nthree\n\nthree\n">>},
+ {"If changed/resolve",
+ <<"{% for x in list %}{% ifchanged x.name|first %}{{ x.value }}{% endifchanged %}\n{% endfor %}">>,
+ [{'list', [[{"name", ["nA","nB"]},{"value","1"}],[{"name", ["nA","nC"]},{"value","2"}],
+ [{"name", ["nB","nC"]},{"value","3"}],[{"name", ["nB","nA"]},{"value","4"}]]}],
+ <<"1\n\n3\n\n">>}
]},
{"for/empty", [
{"Simple loop",
Please sign in to comment.
Something went wrong with that request. Please try again.