-
Notifications
You must be signed in to change notification settings - Fork 15
How to Write a Tag or Block
Updated for v0.9.6
A tag can be either unclosed:
{% mytag [args]* %}
or closed:
{% myblock [args]* %}
Inside block
{% endmyblock %}
If a tag has an "end" markup then it's a block.
To create a block you need to create a class which overrides ICustomBlockTagRenderer
. This has one method:
LiquidString Render(RenderingVisitor renderingVisitor,
ITemplateContext templateContext,
TreeNode<IASTNode> liquidBlock,
IList<Option<ILiquidValue>> args);
The arguments give you access to something that allows you to render the arguments, the current state of the rendering, the pre-rendered content, and the the tag's arguments:
-
renderingVisitor
: can render your content. Most of the time you will want to 1) evaluate the AST with the renderingVisitor and capture the result, then 2) manipulate that result and give it back to the renderer or the templateContext. The first may follow this pattern:
String evaledBlock = "";
// evaluate the tree and place the text result into "evaledBlock". Note that
// if we don't override the second argument `Action<String>`,
// the renderingVisitor will put the result of parsing the liquidBlock
// onto it's own current accumulator, meaning that the text will be written
// directly into the current result.
renderingVisitor.StartWalking(liquidBlock, str => evaledBlock += str);
DoSomethingWithTheResult(evaledBlock);
Note: You could create your own RenderingVisitor
and capture text with that instead of using the current RenderingVisitor, but you will run into subtle bugs when your tag's content contains side-effecting tags that modify global state such as cycle
. It's recommended that you use the RenderingVisitor that's passed in instead of using your own.
Normally you will either put the result directly back into the output stream, or else you'll save the result as a value somewhere. The return value of Render(...)
is what gets written out to the final result:
return LiquidString.Create(myresult);
Or you can modify the state of the template context. For example, you could write your parsed content into the global variable uglyglobal
like this:
templateContext.SymbolTableStack.DefineGlobal("uglyglobal", myresult); // set a variable
return new LiquidString(""); // render an empty string
-
The
templateContext
: -
The
liquidBlock
: is content that appears between the tags comes in theliquidBlock
. It's in the form of an AST fragment, and you can render it by callingRenderingVisitor.StartWalking
:
renderingVisitor.StartWalking(liquidBlock, myAccumulator);
-
args
is a list of theILiquidValue
s arguments that were passed to the block. These have already been evaluated (in contrast to other implementations of liquid).
public class WordReverserBlockTag : ICustomBlockTagRenderer
{
public StringValue Render(
RenderingVisitor renderingVisitor,
ITemplateContext templatecontext,
TreeNode<IASTNode> liquidBlock,
IList<Option<ILiquidValue>> args)
{
// evaluate the contents of the block
var result = EvalLiquidBlock(renderingVisitor, liquidBlock);
// perform the tag logic in the string result and return
// it to be rendered.
return Reverse(result);
}
private static String EvalLiquidBlock(
RenderingVisitor renderingVisitor,
TreeNode<IASTNode> liquidBlock)
{
// walk the AST with an accumulator that shunts the output
// temporarily to "result".
String result = "";
renderingVisitor.StartWalking(liquidBlock, str => result += str);
return result;
}
private static LiquidValue Reverse(string result)
{
var words = result.Split(new[] {' '},
StringSplitOptions.RemoveEmptyEntries);
return LiquidValue.Create(String.Join(" ",
words.Select(x => new String(x.Reverse().ToArray()))));
}
}
Once you have a block tag, you need to register it in your template context:
var ctx = new TemplateContext()
.WithAllFilters()
.WithCustomTagBlockRenderer<WordReverserBlockTag>("reversewords")
Now you can try it out:
{% assign myword = 'hello world' %}
{% reversewords %}{{ myword }}{% endreversewords %}
==>
"world hello"