-
Notifications
You must be signed in to change notification settings - Fork 88
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
Templating: passing arguments to stacked filters #70
Comments
you want to put code into your templates? urgh 😁 to add some content to the post: |
Well the controller is here to fetch relevant business data and pass it to the template. Then the template formats the data. The formatting part can be quite complex, depending on the input data: pick array column, format price, etc.. so yes that's code. And custom filters greatly improve readability for that matter. Here's another example. Let's say we have an
Of course, workarounds are always possible, but I was wondering if we could do something about it. |
As of now, the filter list is splittable, so |
hi flo. What about a workaround: <F3:set books="{{ @author->getBooks(), title | pick }}" />
<F3:set books="{{ @books, ' / ' | join }}" />
{{ @books }} But setting variables and doing things to prepare the view data, also looks like controller code to me.
{{ join(' \ ', \Helper::instance()->pick('title',@author->getBooks())) }}
{{ 'pick{title}, join{\' / \'}', @author->getBooks() | chain }}
<F3:content model="books" foo="bar" as="book" />
<div>Title: <strong>{{@book.title}}</strong></div>
</F3:content>
{{ @author->getBooks(), 'title', ' / ' | pick,join }} but use func_num_args() to get all unused parameters, and pass this to the chained |
Yeah of course workarounds are possible. That's what I've been doing all the time ^^. It's just occured to me that, with filters we could turn such an ugliness : {{ explode(' / ',array_map(function(@b){return @b.title;}, @author.books)) }} into something very easy to read and maintain: {{ @author.books, title | pick, join }} There are many cases where extracting a column from an array doesn't fit in the controller. For example, if you have a generic controller with custom templates. Anyway, the question was not specific about that column picker. |
Okay I see your point. Well what do you think about the responsibility of that filter arguments. Should the filter implementation care about the arguments, and push unused addional arguments to the next filter (i.e. always return an array [$ownFilterResults, func_get_arg(1),func_get_arg(2), ...]), or should the template parser get a new syntax for defining multiple parameters for token? The current implementation sends all arguments to the first filter, and form there it only returns the result which used as argument for the next filter |
Well the first suggestion is interesting as it's easy to implement and it shouldn't break existing code (except custom filters). Though it may be tricky with optional arguments: {{ arg1,null,arg2 | alias, myfunc }} <!-- null is mandatory for arg2 to be passed to myfunc --> I've had a look at strategies used by various template engines and they all seem to end up with either Need to think more about it. |
what about a generic filter that calls other filters in conjunction? {{ 'pick{title}, join{\' / \'}', @author->getBooks() | chain }} yeah it's like from behind through the chest, but would work. |
When it comes to formatting I tend to inject the data and a formatter as a callback then in the view I do If I need additional parameters then I curry them into my $formatter ahead of time to keep my logic out of the views though I only do that if the $formatter is doing something that the result couldn't be considered data in itself, if that is the case then sometimes it's just better to array_map the formatted data into the dataset |
Yeah of course, Considering it again, I think that changing the filter separator from comma to pipe would provide more flexibility. Instead of This way, we could pass arguments to subsequent filters: What do you think? |
In rails $formatters are stored in helper classes see: To help retain semantic meaning of views I would recommend using F3's View class instead of it's Template class Keep in mind I don't speak as a developer of F3 I'm just a long time user |
Looks like we're on the same page =) Template filters are precisely helpers injected into views. |
Another pipe char as filter separator is no good idea. The regex that splits the expression and the filters is hardly trimmed to recognize the last single pipe char and it's good like that because the expression itself may contain multiple pipe chars. One way could be to wrap the arguments: Maybe a simple custom chain filter is more comprehensible: |
They could be escaped or enclosed in quotes. Also I don't understand how the |
Why make it complicated when |
seems its getting really complex and imho this is not very maintainable. If you really want to pipe outputs then you have to mask the pipe-chars that are between two the output is always the first arg or the arg has to be declared in the pattern: |
Here's another use case. Let's say you have a {{ @deposit | price }} Now if you need to combine this filter with {{ @intro, My\Long\Namespace::formatPrice(@deposit) | format }} Would be nice to ease that kind of stuff... although that looks even trickier to achieve than the first use case ;) |
That's not a good sample, because you can already do that pretty neat with the intro = "Please pay the {0,number,currency} deposit before." <p>{{ @intro, 400 | format }}</p> |
OK ;) But actually the Anyway you see what I mean: inject a custom filter into another one. It could be anything else, like a country code formatter: {{ @code | country }} How to smartly combine it with |
ok, but there could be more solutions for this. I think that this could also be made directly in your model, because you probably need that price for more than just the frontend view. but yeah I see what you mean. So in essence you want something like <p>{{ @intro, {{@code | country}} | format }}</p> |
Actually not. In this case, this is a typical job for the views. Raw numbers are manipulated inside models and formatted prices are displayed inside views. As for the syntax, you're right: the issue is about how to group function calls. We need something that performs like: format($intro,country($code))
join(pick($ppl,'name'),'-') One way to do it is what you're suggesting (nested braces). Or maybe just with parenthesis: @intro, (@code | country) | format
(@ppl, 'name' | pick),'-' | join Another way could be to ease the calls to filters: @intro, @this->country(@code) | format
@this->pick(@ppl,'name'), '-' | join There's also the suggestion from @KOTRET: @code | country, @intro %1 | format
@ppl, 'name' | pick, '-' | join |
|
Exhuming this topic with a cleaner solution. Since the context of each rendered template is the templating class itself ( {{ @this->raw('&') }} So we could implement the {{ @this->pick(@author->getBooks(), title), '/' | join }}
{{ @this->raw(@str), 140 | excerpt }}
{{ @intro, @this->price(@deposit) | format }}
{{ @intro, @this->country(@code) | format }} What do you think? The major drawback of this solution is that it introduces a risk of naming collision with the class core methods. However it should be possible to find a solution to avoid this issue. |
When I had to use more than one simple filter, I currently tend to register a custom filter that will do what is needed... so I end up having multiple custom filters, but that is fine. |
btw: @xfra35 regarding your country code sample: wouldn't it be possible to just put that country selector into a simple function instead of a filter? Then it could go like this: $f3->set('countryCode', function($code) { return $whatever });
|
Of course but the point is: couldn't we make the framework a bit more flexible about filters, so that we don't have to resort to workarounds whenever the use case is out of scope. Let's take an example. We have a website about countries & currency rates. To make things easy, we create a filter which converts a country code to a country name. So it most of templates, we have snippets like Now in some particular template, we need to include the country name in a whole sentence. So we need to combine the
None of those solutions is smooth. They are workarounds, and that situation defeats the purpose of filters. |
I think your "sample" is not a filter issue, but a formatting issue.. what you need is a custom FORMATTER: $f3->set('FORMATS.country',function($code){
$code=strtolower($code);
$countries=[
'de'=>'Germany',
'fr'=>'France',
'en'=>'England',
];
return isset($countries[$code]) ? $countries[$code] : $code;
});
$intro='Welcome to {0, country} - the best country in the World.';
echo $f3->format($intro,'de'); Then the issue about stacking filters due to the usage for formatting purpose solves itself, as it becomes: I think a syntax for stacking filters in general like The real issue left here are chained filters: There's obviously an issue here with chaining multiple filter and setting arguments to a filter that's not the first one: But it's not backwards compatible.. |
Not really... I don't want to write: {{ {0, country}, @code | format }} when I can write: {{ @code | country }} Moreover, string formatters are mostly useful to ease localization, by providing the ability to use different formatters on a per-language basis (e.g #156). Apart for these language-specific cases, the responsibility for data formatting falls, imho, on the template rather than the translation files. Writing The Angular syntax looks interesting but it doesn't solve the nesting issue. |
Well actually you only need to adjust the country formatter in that case and not all the dictionary files.. I think parsing text and formatting it is a good job for the new custom formatters and a nice way to spice up the language files. It also opens the way to use dictionary keys within other dictionary keys, like putting translated month names into a string... If your country filter doesn't do something else, it would fit there fine as well, but do it as you want of course. NB: originally filters were introducted to transform data, remember |
I'm pretty convinced that the But OK, let's wait a bit more and see if someone comes with a better idea. |
Hi guys,
Since @ikkez has introduced the configurable template filters, I love this feature. It's very powerful 👍
The only thing is that arguments can be only passed to the first filter.
For example, let's say we have a mapper with 1:m relation, on which we want to pick one column and join the resulting array. The following does work:
{{ @author->getBooks(), title | pick, join }}
but we can't pass any argument to the second filter (in this example, passing
' / '
tojoin
would override the default gluing string).Any ideas on how to implement that?
The text was updated successfully, but these errors were encountered: