Back to Main Site

Contents

Filter Reference

Functions and Macros

Functions are an advanced feature which is not enabled by default (because it doesn’t exist in the original Liquid language).

To enable functions, you have to pass an options object into the parser with the AllowFunctions flag set.

var p = new FluidParser(new FluidParserOptions { AllowFunctions = true });

Functions are template members to which you can pass arguments.

For example:

{{ add(1,2) }}

(Yes, this is contrived – I realize you can do this with a built-in filter.)

The member add is a function which can call back to C# code and pass its arguments. For example, we could write a function like this:

var addFunction = (FunctionArguments a, TemplateContext c) =>
{
  var one = a.At(0).ToNumberValue();
  var two = a.At(1).ToNumberValue();
  return NumberValue.Create(one + two);
};

var c = new TemplateContext();
c.SetValue("add", new FunctionValue(addFunction));

It’s important to consider what we’ve done here – the function is simply a FluidValue. It just happens to do some work to determine what it should output.

As the C# code above should indicate, what’s returned from a function is also a FluidValue like anything else, and can be treated like one, including chaining it with other members and using filters on it.

{{ reverse_name('Deane').size }}
{{ reverse_name('Deane') | upcase }}

(So…what should be done with functions and what should be done with filters? That’s a stylistic decision you get to make.)

Enabling functions also provides “macros,” which are functions that are defined in Fluid code itself:

{% macro say_hi(name) %}
Hi {{ name }}!
{% endmacro %}

{{ say_hi('Deane') }}
Hi Deane!

Values passed into macros are assigned to local members inside the macro based on position. Those members are scoped to only the macro (for example, name outside the macro and name inside the macro have different values).

The macro essentially creates a new Scope, not unlike included templates (see Including Other Templates). The macro will “look around” its local scope for values, and then search other scopes.

So, if you refer to a member not passed into the macro, it will look a value in a higher scope.

{% assign last_name = "Barker" %}

{% macro say_hi(name) %}
Hi {{ name }} {{ last_name }}!
{% endmacro %}

{{ say_hi('Deane') }}
Hi Deane Barker!

In the above example, since last_name is not in the macro scope, it will look one scope higher and find the value assigned in the calling template.

However, if I modify the macro definition to accept last_name

{% macro say_hi(name, last_name) %}

– now last_name is local to the macro scope, so the previously assigned last_name member is ignored.

Arguments are optional. The are mapped left to right, and anything not provided simply becomes a NilValue inside the macro. Note that this will still “hide” members in higher scopes – if I don’t explicitly provide last_name in my macro call, it still appears inside the macro as a NilValue and hides the previously assigned value.

Finally, macro arguments can have default values. These have to be the final argument or arguments, meaning you can’t assign a default value to an argument, then have an argument following that which you do not assign a default value.

{% macro say_hi(name = 'He Who Shall Not Be Named') -%}
Hi {{ name }}!
{%- endmacro %}

{{ say_hi('Deane') }}
{{ say_hi() }}
Hi Deane!
Hi He Who Shall Not Be Named!