From 760f675449ddab04b3eeb5897032067bd658f3f3 Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Tue, 25 Jan 2022 10:53:51 -0500 Subject: [PATCH 01/41] New Antlers docs (#692) Co-authored-by: Jason Varga --- app/Modifiers/Toc.php | 4 +- .../collections/docs/new-antlers-parser.md | 1319 +++++++++++++++++ content/globals/global.yaml | 4 + public/img/label-experimental-doc.jpg | Bin 0 -> 18993 bytes public/img/label-experimental-hackerman.jpg | Bin 0 -> 22076 bytes public/img/label-experimental-rick.jpg | Bin 0 -> 20582 bytes resources/css/components.css | 9 + resources/css/content.css | 10 +- resources/css/torchlight.css | 6 +- .../views/partials/experimental.antlers.html | 5 + resources/views/partials/header.antlers.html | 1 + 11 files changed, 1348 insertions(+), 10 deletions(-) create mode 100644 content/collections/docs/new-antlers-parser.md create mode 100644 public/img/label-experimental-doc.jpg create mode 100644 public/img/label-experimental-hackerman.jpg create mode 100644 public/img/label-experimental-rick.jpg create mode 100644 resources/views/partials/experimental.antlers.html diff --git a/app/Modifiers/Toc.php b/app/Modifiers/Toc.php index c09c38a1a..ee1818c3c 100644 --- a/app/Modifiers/Toc.php +++ b/app/Modifiers/Toc.php @@ -55,10 +55,10 @@ private function create($content, $maxHeadingLevels) $ret = preg_match('/id=[\'|"](.*)?[\'|"]/i', stripslashes($heading[2]), $anchor); if ($ret && $anchor[1] != '') { - $anchor = stripslashes($anchor[1]); + $anchor = trim(stripslashes($anchor[1])); $add_id = false; } else { - $anchor = preg_replace('/\s+/', '-', preg_replace('/[^a-z\s]/', '', strtolower(strip_tags($heading[3])))); + $anchor = preg_replace('/\s+/', '-', trim(preg_replace('/[^a-z\s]/', '', strtolower(strip_tags($heading[3]))))); $add_id = true; } diff --git a/content/collections/docs/new-antlers-parser.md b/content/collections/docs/new-antlers-parser.md new file mode 100644 index 000000000..2b20e34d9 --- /dev/null +++ b/content/collections/docs/new-antlers-parser.md @@ -0,0 +1,1319 @@ +--- +blueprint: page +title: 'Antlers Templates' +intro: > + Antlers is a simple and powerful templating engine provided with Statamic. It can fetch and filter content, display, modify, and set variables, tap into core features like user authentication and search, and handle complex logic. + + :::warning Heads up! + These docs are for our **brand new, opt-in Antlers Engine**, added in Statamic 3.3. [Read more about it](#about), learn how to enable it on your site, and keep reading to see all the new things it can do! + ::: + +template: page +id: d37b2af2-f2bf-493a-9345-7087fb5929ce +experimental: true +--- + +## Overview + +Antlers is one Statamic's foundational features. It consists of a tightly coupled template language, runtime engine, and library of [Tags](#tags) that can be used to fetch and manipulate data, handle logic, and help you write easier to maintain HTML. + +Antlers templates are also called views. Any files in the `resources/views` directory with an `.antlers.html` file extension is an "Antlers Template", and will be parsed with the Antlers Engine. + +:::tip +The `.antlers.html` extension is important. Without it, your template will be rendered as **unparsed, static HTML**. +::: + +### Basic Example + +Antlers adds dynamic features to HTML in the form of "tags" – expressions contained inside a pair of curly braces: `{{` and `}}` Those curly braces (often called double mustaches or squiggly gigglies) look a whole lot like _antlers_ to us, hence the name. + +This is a very simple Antlers tag: + +``` +{{ hello_world }} +``` + +:::tip DON'T SKIP THIS! +## About the New Antlers Engine {#about} + +Not only this new Antlers Engine a complete and fundamental rewrite, but it takes a completely different, more sophisticated approach to the business of template parsing. + +The original parser was essentially a glorified find and replace machine relying heavily on RegEx. It parsed and evaluated logic as it worked its way through the template. This means it couldn't stop, go backwards, set variables, or handle nested logic well because it had to keep moving forward. It also slowed down the larger the template got because of the amount of characters being pushed through the RegEx. + +The New Antlers Engine now has **two stages** – first, it parses and builds an Abstract Syntax Tree from your complete template, and _then_ it evaluates and executes the nodes and logic in the tree in a runtime fashion (much like a programming language) according to the established rules. + +This affords Statamic an incredible amount of control. It can go sideways and slantways and longways and backways and squareways and frontways and any other ways that you can think of. This in turn allowed us to build dozens of new features, fix every single known Parser-related bug, and support syntax scenarios that were impossible in the previous "parse _and_ evaluate" flow. Features like... + +- The ability to _set_ variables +- Syntax errors that reference the exact line, character, and type of error +- The ability to control parse order through sub-expressions +- Merge, group, order, and manipulate data natively +- Perform a robust set of mathematical operations +- Concatenate, increment, and decrement values +- More template logic and control flow operators +- More powerful template enh +- An improved Modifier syntax to provide better readability and type handling +- A smarter, more forgiving matching engine so more things Just Work™ +- Self-iterating assignments +- Self-closing tags +- Run-time caching for huge performance boosts + +This new engine is a powerful factory, mad scientist laboratory, and wizarding school all rolled into one. 🏭🧑‍🔬🧙‍♀️ + +### We're calling it "Experimental" {#experimental} + +Because of how fundamental Antlers is to the entire Statamic experience, we're shipping this new version under an **opt-in** feature flag until Statamic 3.4 — just in case it affects the behavior or output of one or more of your templates in an unexpected way. Here are a few situations we wish to avoid, in order of most-to-least likely to happen: + +1. Templates that rely on a bug in the RegEx parser suddenly behave correctly, but do so unexpectedly +1. Templates that rely on undocumented or unknown behaviors that may have been fixed, removed, or otherwise not ported to the New Parser +1. Actual regressions created by the new parser +1. Performance gains are so high that your site rips a hole in the Space Time Continuum and inhales everything in the solar system into it, collapsing everything you've ever known into the space of a single atom +1. Half-Life 3 comes out but everyone is too distracted by New Antlers to notice + +If you encounter any of these scenarios (and you still exist), please [open an issue](https://github.com/statamic/cms/issues/new?assignees=&labels=&template=bug_report.yml) so we can address it or help you get sorted. + +### How to Enable It {#enable} + +To try out the new Antlers engine, switch the `statamic.antlers.version` config option from `regex` to `runtime` in `config/statamic/antlers.php`. If you don't have this config file, create it and add the following: + +```php +// config/statamic/antlers.php +return [ + 'version' => 'runtime', + // ... +]; +``` + +### Huge Thanks to John Koster 👏 + +This rewrite was a huge undertaking by the incomparable [John Koster](https://github.com/JohnathonKoster), who apparently found it a relaxing break from his day job. You can see the effort involved in this [massive PR](https://github.com/statamic/cms/pull/4257). + +We owe him a debt of gratitude for this amazing gift. +::: + +## The Basics + +### Delimiters + +There are three kinds of delimiters. + +- `{{ }}` The basic and primary delimiter pair, used to render variables, evaluate expressions, call Tags, and do almost all core Antlers things. +- `{{? ?}}` Allows you to write and execute PHP. +- `{{# #}}` Are for code comments. + +### Formatting Rules + +1. Each set of curly braces **must** stay together always, like Kenan & Kel or Wayne & Garth. There must be a left pair and a right pair, just like HTML's `<` and `>` angle braces. +1. Expressions are **case sensitive**. +3. Hyphens and underscores are **not** interchangeable. +1. Whitespace between the curly braces expression is optional, but **recommended** for readability. +1. You **may** break up an expression onto multiple lines. + +Consistency is important. We recommend using a single space between braces and the inner expression, lowercase variable names, and underscores as word separators. Pick your style and stick to it. Future you will thank you, but don't expect a postcard. + +``` antlers +This is great! +{{ perfectenschlag }} + +This is allowed. +{{squished}} + +This can make sense when you have lots of parameters. +{{ + testimonials + limit="5" + order="username" +}} + +This is terrible in every possible way. +{{play-sad_Tromb0ne }} +``` + +:::tip +We recommend indenting the markup in your HTML for **human readability and maintainability**, not for final rendered output. Anyone still caring about that this day and age probably needs a long vacation and strong Mai Tai or two. 🍹🍹 +::: + +### IDE Integrations + +Syntax highlighting and auto-completion packages are available for many of the popular IDEs: + +**The VS Code Extension is the most powerful one by far.** + +- [Antlers for VS Code](https://antlers.dev) +- [Antlers for Sublime Text](https://github.com/addisonhall/antlers-statamic-sublime-syntax) +- [Antlers for Atom](https://github.com/addisonhall/language-antlers) + +## Variables + +Data passed into your Antlers views can be rendered by wrapping the name of a variable with double curly braces. For example, given the following data: + +``` yaml +--- +title: DJ Jazzy Jeff & The Fresh Prince +--- +``` + +The `title` variable can be rendered like this: + +``` antlers +

{{ title }}

+``` + +``` html +

DJ Jazzy Jeff & The Fresh Prince

+``` + +### Valid Characters + +Variable must start with an alpha character or underscore (`a-zA-Z_`), followed by any number of additional uppercase or lowercase alphanumeric characters, hyphens, or underscores (`a-zA-Z_0-9`). Spaces or other special characters are not allowed. + +Don't be weird and mix-and-match them like a serial killer though: + +``` + +{{ this_iS-RiDicuL-ou5_ }} +``` + +### Strings + +Strings (simple sequences of text) are one of the most basic data types. They come in the form of variables or static expressions. To render a string variable, wrap the name with double curly braces. + +``` +

{{ title }}

+``` + +Antlers also handles static expressions, which are useful when concatenating strings together, setting fallback or default values, combining with [modifiers](#modifiers), and numerous other situations we can't think of right now but you may find yourself in eventually. + +To render a static string, wrap it in single or double quotes, inside a pair of curly braces. + +``` +

{{ "I will eat you, donut" | upper }}

+``` + +``` html +

I WILL EAT YOU, DONUT

+``` + +### Arrays +An array is a collection of elements (values and/or variables). Elements inside the array may be iterated or looped through using the `{{ value }}` variable. You may also "reach in" and pluck out specific elements by their index. + +#### Looping + +``` +--- +songs: + - Brand New Funk + - Parents Just Don't Understand + - Summertime +--- + + +``` + +``` html + +``` + +#### Plucking + +To pluck values out of an array, you may use "colon", "dot", or "bracket" notation to pull out values by their array key. All three of these syntaxes are equivalent, so feel free to use the one that feels most natural to you. Note that the first item of the array starts with a zero-index key. + +``` +--- +sports: + - BMXing + - rollerblading + - skateboarding + - scootering +--- + +

Let's go {{ sports:0 }}, {{ sports.1 }} or {{ sports[2] }}.

+``` + +``` html +

Let's go BMXing, rollerblading, or skateboarding.

+``` + +#### Dictionaries + +Dictionaries are represented in YAML by nested key:value pairs, _inside_ another variable name. These are sometimes called element maps, or associative arrays. + +``` yaml +mailing_address: + address: 123 Foo Ave + city: Barville + province: West Exampleton + country: Docsylvania +``` + +You can access the keys inside the dictionary "colon", "dot", or "bracket" notation to traverse the levels of the array. All three of these syntaxes are equivalent, so feel free to use the one that feels most natural to you. + +``` antlers +I live in {{ mailing_address:city }}. It's in {{ mailing_address:province }}. +``` + +#### Multi-Dimensional Arrays +More complex data is stored in objects or arrays inside arrays. This is usually called a multi-dimensional array. + +``` yaml +skaters: + - + name: Tony Hawk + style: Vert + - + name: Rodney Mullen + style: Street + - + name: Bob Burnquist + style: Vert +``` + +If you know the names of the variables inside the array, you can loop through the items and access their variables. + +``` antlers +{{ skaters }} +
+

{{ name }}

+

{{ style }}

+
+{{ /skaters }} +``` + +``` html +
+

Tony Hawk

+

Vert

+
+
+

Rodney Mullen

+

Street

+
+
+

Bob Burnquist

+

Vert

+
+``` + +You may also use "colon", "dot", or "bracket" notation to access individual values. Note that the first iteration of the array starts with a zero-index. + +``` +{{ skaters:0:name }} +{{ skaters.1.name }}
+{{ skaters[2]['name'] }}
+``` + +``` html +Tony Hawk
+Rodney Mullen
+Bob Burnquist +``` +#### Dynamic Access + +If you don't know the names of the keys inside the array – which can happen when working with dynamic or user submitted data – you can access the elements dynamically using variables for the key names. + +Using the mailing list example, we could use a `field` variable to access specific keys. + +``` md +--- +field: country +mailing_address: + address: 123 Scary Mansion Lane + country: Docsylvania + city: Arteefem + postal_code: RU 7337 +--- +{{ mailing_address[field] }} + +// Output +Docsylvania +``` + +You can combine literal and dynamic keys and get real fancy if you need to. + +``` +{{ complex_data:[3][field]['title'] }} +``` + +### Disambiguation 🆕 {#disambiguating-variables} + +As your templates grow and increase in complexity, you _may_ find yourself unsure if you're working with a variable or a [tag](#tags). You may optionally disambiguate your variables by prefixing them with a `$` dollar sign, just like PHP. + +``` +{{ $content }} +``` + +### Modifiers + +Modifiers change the output of an Antlers variable. They are used inside any expression and are separated by a pipe character `|`. + +Multiple modifiers can be chained on one output, each separated by another pipe `|`, and are are applied in order from left to right. Let's look at an example. + +```yaml +--- +title: Nickelodeon Studios +--- +``` +``` + +

{{ title | upper | ensure_right('rocks!') }}

+ + +

{{ title | ensure_right('rocks!') | upper }}

+``` + +Some modifiers accept parameters to control their behavior. Arguments can be passed inside a pair of `()` braces, just like a native PHP function. If you don't have any arguments to pass, you may omit the braces. + +You may pass `strings`, `arrays`, `booleans`, `integers`, `floats`, `objects`, or references to existing variables as arguments. + +``` +{{ var | modifier('hi', ['pooh', 'pea'], true, 42, $favoriteVar) }} +``` + +##### Examples +Here are a few examples of modifiers in action. + +```yaml +summary: "It was the best of times, it was the worst of times." +noun: soups +``` + +``` +{{ summary | replace('worst', 'yummiest') }} +{{ summary | replace('It was', 'It was also') | replace('times', $noun) }} +{{ summary | explode(' ') | ul }} +{{ (summary | contains('best')) ?= "It was lunch, is what it was." }} +``` + +``` +It was the best of times, it was the yummiest of times. +It was also the best of soups, it was the worst of soups. + +It was lunch, is what it was. +``` + +There are more than 150 built-in [modifiers](/reference/modifiers) that can do anything from array manipulations to automatically writing HTML for you. You can also [create your own modifiers](/extending/modifiers) to do unthinkable things we assumed nobody would ever need to do, until you arrived. + +You can even create [Macros](/modifiers/macro) to combine sets of often used modifiers into one, new reusable one. + +#### Legacy Syntax + +The New Antlers Parser still supports what we're now calling the "[Legacy Syntax](/antlers#stringshorthand-style)" styles, and will continue to do so until Statamic 4.0. + +### Creating Variables 🆕 + +You can now set variables by using the assignment operator, `=`. + +``` +{{ total = 0 }} + +{{ loop from="1" to="9" }} + {{ total += 1 }} +{{ /loop}} + +

I can count to {{ total }}!

+``` + +``` +

I can count to 9!

+``` + +#### Arrays +You can also create arrays, if you find the need. Keep in mind that more complex data _might_ be better suited to being managed in Entries, Globals, View Models, or Controllers. + +``` +{{ todo = ['Get haircut', 'Bake bread', 'Eat soup'] }} + + +``` + +#### Sub-Expressions + +You can assign sub-expressions or interpolated statements to variables too. In this example, you can use `{{ items }}` as if it were the actual Collection Tag. Because it is. + +``` +{{ items = {collection:products sort="rating:desc" limit="5"} }} + +

Our Top Products

+ +``` + +### Truthy and Falsy + +All variables are considered "truthy" if they exist _and_ contain a value. Variables that _don't_ exist, contain an empty string, or are structured and empty (e.g. an empty array or object) are considered "falsy". + +This is a powerful pattern that can help keep template logic simple and uncluttered. For instance, you can set a series of "fallback" variables all in one expression, allowing you to have default values and optionally override them instead of having to a bunch of `if`/`else` checks. + +``` + + + {{ if meta_title }} + {{ meta_title }} + {{ elseif title }} + {{ title }} + {{ else }} + {{ site:name }} + {{ /if }} + + + +{{ meta_title ?? title ?? site:name }} +``` + +Another use case is when you _sometimes_ have an array variable to loop through in a template to render some markup. You may skip the existence check entirely, keep the markup inside the loop, and if the variable doesn't exist, nothing inside the tag pair will be rendered. + +``` +{{ nothing_to_see_here }} + +{{ /nothing_to_see_here }} +``` + +### Escaping + +By default, Antlers `{{ }}` statements are _not_ automatically escaped. This is because in a CMS context (vs a web application), content is very often stored inside HTML markup, and this is the most logical, default behavior. + +The simplest way to escape data is by using the [sanitize](/modifiers/sanitize) modifier. This will run the data through PHP's [`htmlspecialchars()`](https://www.php.net/manual/en/function.htmlspecialchars.php) function to prevent XSS attacks. + +``` +{{ user_submitted_content | sanitize }} +``` + +:::tip +Just remember: **never render user-submitted data without escaping it first!** +::: + + +## Operators + +An operator is a special symbol or phrase that you use to check, change, or combine values. For example, the addition operator (`+`) adds numbers, as in `1 + 2`. Statamic supports many of the operators you may already know from PHP, and adds a few new ones to make your life as developer easier. + +### Control Flow + +Statamic provides a variety of control flow statements. These include `if`, `else`, `or`, `unless`, and `switch` statements to run different branches of template code based on defined conditions. + +#### if + +Executes a block of code only if a condition is `true` or "[truthy](#truthy-and-falsy)". + +``` +{{ if logged_in }} + Welcome to Narnia! +{{ /if }} +``` + +#### unless + +Unless is the opposite of `if` – executing a block of code only if a condition is **not** met. + +``` +{{ unless logged_in }} + You see a large wardrobe in front of you. +{{ /unless }} +``` + +#### elseif / else + +Adds more conditions with an `if` or `unless` block. + +``` +{{ if neighbor == "Kramer" }} + These pretzels are making me thirsty! +{{ elseif neighbor == "Newman" }} + Hello...Newman. +{{ else }} + Who are you? +{{ /if }} +``` + +#### switch 🆕 + +The `switch` is perfect for complex conditions with many possible cases, or using inside interpolated regions that don't support tag pairs, like [Tag Parameters](#tag-parameters). +``` +{{ size = 'lg' }} + +{{ switch( + (size == 'sm') => '(min-width: 768px) 35vw, 90vw', + (size == 'md') => '(min-width: 768px) 55vw, 90vw', + (size == 'lg') => '(min-width: 768px) 75vw, 90vw', + (size == 'xl') => '90vw' + () => 100vw + ) +}} +``` + +```html +(min-width: 768px) 75vw, 90vw +``` + +### Comparison + +Comparison operators, as their name implies, allow you to compare two values or expressions. + +| Name | Example {.w-32} | Description | +|------|----------------|-------------| +| Equal | `$a == $b` | `true` if `$a` is equal to `$b` after type juggling. | +| Identical | `$a === $b` | `true` if `$a` is equal to `$b`, and are of the same type | +| Greater than | `$a > $b` | `true` if `$a` is greater than `$b`. | +| Greater than or equal to | `$a >= $b` | `true` if `$a` is greater than or equal to `$b`. | +| Less than | `$a < $b` | `true` `$a` is less than the `$b`. | +| Less than or equal to | `$a <= $b` | `true` if `$a` is less than or equal `$b`. | +| Not equal | `$a != $b` | `true` if `$a` is not equal to `$b` after type juggling. | +| Not identical | `$a !== $b` | `true` if `$a` is not equal to `$b`, only if they are of the same type. | +| Spaceship 🆕 | `$a <=> $b` | Returns -1, 0 or 1 when `$a` is less than, equal to, or greater than `$b`, respectively. | + +#### Examples + +Let's compare some numbers. + +``` +{{ if songs === 1 }} +

This is a song!

+{{ elseif songs > 100 }} +

This is noisy!

+{{ elseif songs }} +

There are some songs here.

+{{ else }} +

It is quiet.

+{{ /if }} +``` + +Here's a more complicated condition involving the output from a Tag. + +``` +{{ if {collection:count from="episodes"} >= 100 }} + This show is ready to be syndicated! +{{ /if }} +``` + +### Logical + +Logical operators join two or more expressions to create compound conditions. + +| Name | Example | Description | +|------|---------|-------------| +| And | `$a && $b` or `$a and $b` |`true` if both `$a` and `$b` are `true`. | +| Or | `$a \|\| $b` or `$a or $b` | `true` if either `$a` or `$b` is `true`. | +| Not | `!$a` | `true` if `$a` is not `true`.| +| Xor | `$a xor $b` | `true` if either `$a` or `$b` is `true`, but not both. | + + +### Ternary Statements {#ternary} + +Ternary statements let you write a simple condition and return one value if `true` and another if `false`, all one one expression. + +``` +This item is {{ is_sold ? "sold" : "for sale" }}. +``` + +:::best-practice +**Ternary statements are a double-edged sword** – they can simplify template code when used effectively, and greatly complicate it if pushed too far — like nesting one ternary inside another using a sub-expression. Make sure other developers will be able wrap their Mind Grapes™ around your ternary statements. + +``` + +{{ is_sold ? "sold" : (on_sale ? "on sale" : "for sale") }} +``` +::: + +### Null Coalescence + +The null coalescing operator (`$a ?? $b`) considers each variable in a statement _optional_, returning the first one that passes a "truthy" check. This lets you set fallback or default values for optional data. + +``` +{{ meta_title ?? title ?? "Someone Forgot the Title" }} +``` + +### The Gatekeeper (Truthy Assignment) {#gatekeeper} + +The Gatekeeper operator (`a ?= b`) will execute an expression **if and only if** it passes a "truthy" check. It doesn't exist in any programming language — we invented this one. Enjoy! + +``` +{{ show_bio ?= author:bio }} + +{{ show_newsletter ?= {partial:newsletter} }} +``` + +This syntax can handle any valid expression on the right-hand side of the operator. Just make sure that when using the Gatekeeper that it's the most readable way to construct the template. + + +### Concatenation 🆕 + +There are two methods for concatenating strings. + +First, to concatenate and render a string in a single tag, you may use a `+` plus sign between variables and string literals to combine them. (You may also use multiple tags. Opt for whatever makes the code most readable.) + +```yaml +title: Marv's Coffee Shop +quality: pretty good +``` + +``` +{{# These are equivalent #}} +

{{ $title + " makes " + $quality + " donuts." }}

+

{{ title }} makes {{ quality }} donuts.

+``` + +```html +

Marv's Coffee Shop makes pretty good donuts.

+

Marv's Coffee Shop makes pretty good donuts.

+``` + +You may also concatenate through assignment, allowing you to render the result later in a template. + +``` +{{ string = "Hello" }} + +{{ if something }} + {{ string += " World"}} +{{ else }} + {{ string += " Universe" }} +{{ /if }} + +{{ string }} +``` + +### Math 🆕 + +Math is all the rage. Teenagers have been found in back rooms and back alleys doing math and nobody can seem to stop them. And since the cool kids are doing it, Antlers does math now too! + +| Name | Example | Description | +|------|---------|-------------| +| Addition | `$a + $b` | Sum of `$a` and `$b`. | +| Subtraction | `$a - $b`. | Difference of `$a` and `$b`. | +| Multiplication | `$a * $b`. | Product of `$a` and `$b`. | +| Division | `$a / $b`. | Quotient of `$a` and `$b`. | +| Modulo | `$a % $b`. | Remainder of `$a` divided `$b`. | +| Exponentiation | `$a ** $b` | Result of raising `$a` to the `$b`'th power. | +| Factorial | `$a!` | Factorial of `$a`. | + +### Assignment 🆕 + +The basic assignment operator is `=`. You might immediately think that means "equal to", but stop right there. Do not pass go and do not receive $200. This means left operand gets set to the value of the expression on the right. + +This is how you create variables as well as increment, decrement, or otherwise manipulate numerical variables. + +| Name | Example {.w-32} | Description | +|------|---------|-------------| +| Left Assignment | `$a = $b` | Sets the value of `$a` to the value of `$b`. | +| Addition | `$a += $b` | Assigns the sum `$a` and `$b` to `$a`. | +| Subtraction | `$a -= $b` | Assigns the difference of `$a` and `$b` to `$a`. | +| Multiplication | `$a *= $b` | Assigns the product of `$a` and `$b` to `$a`. | +| Division | `$a /= $b` | Assigns the quotient of `$a` and `$b` to `$a`. | +| Modulus | `$a %= $b` | Assigns the remainder of `$a` divided by `$a` to `var`. | + +### Self-Iterating Assignments 🆕 + +The left assignment operator has a super power not shared by the others. If the value of the **right-hand** expression returns a value that can be iterated (arrays, objects, etc.), the captured variable name can be used as a tag pair to iterate the returned value immediately. + +``` +{{ pages = {collection:pages} }} + {{ title }} +{{ /pages }} +``` + +## Advanced Operators 🆕 + +These operators are here for the edge cases, the wild ideas, and the unexpected client requests at midnight the night before a site launch. **These are the data wangjanglers.** + +Much of what they do is already handled by Modifiers or Tag Parameters (and you should use those if and whenever you can), but these operators become very useful as part of **assignment expressions** — when you've left the safety of a Tag or simplicity of a primitive variable in the dust behind you. + +### Merge 🆕 + +The `merge` operator can merge two or more "array-like" variables or expressions. The resulting data is immediately iterable without any kind of intermediate step, if you desire. + +``` +{{ articles = favourite_articles merge not_favourite_articles }} + +{{ articles }} + {{# do your thing here #}} +{{ /articles }} + +{{ items = {collection:headlines} merge {collection:news limit="5"} }} + {{# your thing can be done here too #}} +{{ /items }} +``` + +:::best-practice +You shouldn't need to merge collections this way because the [Collection Tag](/tags/collection) already supports the feature (and is more performant), but we want to show what's technically possible. + +``` +{{ %collection from="headline|news" }} + +{{ /%collection }} +``` +::: + +### OrderBy 🆕 + +The `orderby` operator can be applied to any array and supports ordering by multiple properties as well as dynamic fields and directions. + +Arguments are passed into a pair of parenthesis `()` in the following format, which accepts variables of literal `'asc'` and `'desc'` strings or boolean `true` and `false` for ascending and descending sort directions, respectively. + +``` +{{ var orderby (FIELD_1 DIRECTION, FIELD_2 DIRECTION) }} +``` + +#### Examples + +```yaml +dir: 'asc' +shouldSortAscending: false +``` + +``` +{{ people orderby (age 'desc', last_name 'asc', first_name 'asc') }} + +{{ places orderby (state $dir, city $dir, zip_code $dir) }} + +{{ things = + {collection:hats} merge {collection:books} + orderby (rating $shouldSortAscending) +}} +``` + +### GroupBy 🆕 + +The `groupby` operator can be applied to any array or tag output as part of an assignment expression, which automatically iterates through the newly created groups. + +Arguments are passed into a pair of parenthesis `()`. Each argument accepts the name of a field to group by and an optional alias, with additional arguments for additional fields separated by commas `,`. If you don't set an alias, it will match the name of the field you pass in. + +Additionally you may set the name of the per-group `values` array with `as 'anything_you_want` at the end of the expression. + +``` +groupby (FIELD 'KEY1', FIELD2 'KEY2') as 'things' +``` +#### Examples + +We'll use the following data for a few of the next examples. + +``` yaml +players: + - { team: Chicago Bulls, name: Michael Jordan, position: Guard } + - { team: Chicago Bulls, name: Scottie Pippen, position: Forward } + - { team: Chicago Bulls, name: Dennis Rodman, position: Forward } + - { team: Detroit Pistons, name: Isiah Thomas, position: Guard } + - { team: Detroit Pistons, name: Terry Mills, position: Forward } + - { team: Detroit Pistons, name: Joe Dumars, position: Guard } +``` + +##### Group by Single Field + +``` +{{ items = players groupby (team) }} +

{{ key }}

+ +{{ /items }} +``` + +```html +

Chicago Bulls

+ +

Detroit Pistons

+ +``` + +##### Group by Multiple Fields +``` +{{ items = players groupby (team, position) }} +

{{ key:team }} - {{ key:position }}

+ +{{ /items }} +``` + +``` html +

Chicago Bulls - Guard

+ + +

Chicago Bulls - Forward

+ + +

Detroit Pistons - Guard

+ + +

Detroit Pistons - Forward

+ +``` +#### Group Collection Entries by Year + +``` +{{ blog = {collection:blog} groupby (date|format('Y') 'year') as 'entries' }} +

{{ year }}

+ +{{ /blog }} +``` + +### Where 🆕 + +Everything you can do inside a regular Antlers condition can be performed inside a `where` statement. Additionally, you can use an "arrow function" (`x => x.field`) to establish a scoped context inside an array or object. + +#### Examples + +``` +products: + - [name: Talkboy, price: 30] + - [name: Super Nintendo, price: 90] + - [name: Pogs, price: 1] +budget: 50 +``` + +``` +{{ bulls = players where (team == "Chicago Bulls") }} +{{# returns [Michael Jordan, Scottie Pippen, Dennis Rodman] #}} + +{{ afford = products where (x => x.price < budget) }} +{{# returns [Talkboy, Pogs] #}} + +{{ electronic = products where + (name == "Talkboy" || name == "Super Nintendo") +}} +{{# returns [Talkboy, Super Nintendo] #}} +``` + +### Take 🆕 + +You may use the `take` operator to limit the number of results returned from an assignment operation. + +``` +{{ players = players take (2) }} +``` + +### Skip 🆕 + +You may use the `skip` operator to skip a given number of results returned from an assignment operation. + +``` +{{ players = players skip (2) }} +``` + +### Pluck 🆕 + +If you would like to retrieve the values from a single field, use `pluck`. + +``` +{{ players = players pluck ('name') }} + {{ value }} +{{ /players }} +``` + +```output +Michael Jordan +Scottie Pippen +Dennis Rodman +Isiah Thomas +Terry Mills +Joe Dumars +``` + +### The Terminator 🆕 + +Multiple expressions or statements can be performed inside a single Antlers tag pair by terminating each with `;`. These terminators can often lend to more readable code for multi-line statements. However, if you don't like them, you can tell 'em "hasta la vista, baby" because they're optional (just like in JavaScript). + +``` +{{ + $michael = 9986000; + $minutes_in_a_year = 60 * 24 * 365; + (($michael / $minutes_in_a_year) | format_number(0)) + " years"; +}} +``` + +``` +19 years +``` + +## Expressions and Statements + +If you want the computer science answer, an "expression" is a combination of values and functions that are combined and interpreted to create new values, whereas a "statement" is a standalone unit of execution that doesn't return anything. 🥱 + +Simply put, expressions show things and statements do things. Even more simply put — they're the stuff between `{{ }}` braces. It's not terribly important to remember the semantic differences as it is usually clear from context whether you're trying to show a thing or do a thing. + +Let's just go through the list of valid "in between braces stuff" so you can accomplish your goals and hopefully win a trophy of some kind. 🏆 + +``` +{{ "This is a single expression that renders this very text. nothing more and nothing oesseiuhdieuhd " }} + +{{# This statement fetches Entries and begins iterating through them #}} +{{ collection:blog limit="5" }} + +{{# This statement runs a condition check #}} +{{ if template == "home" }} + +{{# Brace yourself — this complex statement assigns a boolean value + to a new variable based on a the result of a condition inside a + sub-expression, and then writes a value to the user session in + a separate statement, all inside a single Antlers region. 😅 #}} +{{ $show_sale_popup = ( + global:active_sale === true + && !{session:has key="seen_popup"} + ); + {session:set seen_popup="true"}; +}} +``` + +### Literals + +Literals are the simplest type of expression. They include strings, arrays, booleans, integers, and so on. Antlers can handle literals as stand-alone expressions, as arguments, and during assignments (creating and updating variables) . + +To check the type of any variable or value, use the `type_of` modifier: + +``` +{{ "Wazzaaap" | type_of }} -> string +{{ [1, 2, 3] | type_of }} -> array +{{ false | type_of }} -> boolean +{{ 42 | type_of }} -> integer +{{ 26.2 | type_of }} -> double (aka float) +``` + +### Sub-Expressions 🆕 + +Sub-expressions are indicated by wrapping a pair of parenthesis around `()` a portion of text. Anything inside a sub-expression will be parsed immediately and independently, which allows you to control the order of operations inside an Antlers tag and improve code readability. + +``` +{{ 5 + 3 * 2 }} -> 11 +{{ (5 + 3) * 2 }} -> 16 + +{{ if (gallery | length) >= 12 && (content | read_time) > 5 }} +``` + +Sub-expressions are supported everywhere: variable assignments, logic conditions, interpolated Tag arguments, you name it. + +## Tags + +Tags (note the capital "T") are the primary method for accessing data from Statamic and tapping into many of the available dynamic features like search, forms, nav building, pagination, entry listing, filtering, image resizing, and so on. Check out the [full list of Tags](/reference/tags) to see what's available. + +Tags usually operate as pairs as they're often fetching data (like entries or assets) and looping through the results. + +``` +