--- title: Liquid Drops --- LiquidJS also provides a mechanism similar to [Shopify Drops][shopify-drops], allowing template authors to incorporate custom functionality in resolving variable values. {% note info Drop for JavaScript %} Drop interface is implemented differently in LiquidJS compared to built-in filters and other template functionalities. Since LiquidJS runs in JavaScript, custom Drops need to be reimplemented in JavaScript anyway. There's no compatibility between JavaScript classes and Ruby classes. {% endnote %} ## Basic Usage ```javascript import { Liquid, Drop } from 'liquidjs' class SettingsDrop extends Drop { constructor() { super() this.foo = 'FOO' } bar() { return 'BAR' } } const engine = new Liquid() const template = `foo: {{settings.foo}}, bar: {{settings.bar}}` const context = { settings: new SettingsDrop() } // Outputs: "foo: FOO, bar: BAR" engine.parseAndRender(template, context).then(html => console.log(html)) ``` [Runkit link](https://runkit.com/embed/2is7di4mc7kk) As shown above, besides reading properties from context scopes, you can also call methods. You only need to create a custom class inherited from `Drop`. {% note tip Async Methods %} LiquidJS is fully async-friendly. You can safely return a Promise in your Drop methods or define your methods in Drop as `async`. {% endnote %} ## liquidMethodMissing For cases when there isn't a fixed set of properties, you can leverage `liquidMethodMissing` to dynamically resolve the value of a variable name. ```javascript import { Liquid, Drop } from 'liquidjs' class SettingsDrop extends Drop { liquidMethodMissing(key) { return key.toUpperCase() } } const engine = new Liquid() // Outputs: "COO" engine.parseAndRender("{{settings.coo}}", { settings: new SettingsDrop() }) .then(html => console.log(html)) ``` `liquidMethodMissing` supports Promise, meaning you can make async calls within it. A more useful case can be fetching the value dynamically from the database. By using Drops, you can avoid hardcoding each property into the context. For example: ```javascript import { Liquid, Drop } from 'liquidjs' class DBDrop extends Drop { async liquidMethodMissing(key) { const record = await db.getRecordByKey(key) return record.value } } const engine = new Liquid() const context = { db: new DBDrop() } engine.parseAndRender("{{db.coo}}", context).then(html => console.log(html)) ``` ## valueOf Drops can implement a `valueOf()` method, the return value of which can be used to replace itself in the output. For example: ```javascript import { Liquid, Drop } from 'liquidjs' class ColorDrop extends Drop { valueOf() { return 'red' } } const engine = new Liquid() const context = { color: new ColorDrop() } // Outputs: "red" engine.parseAndRender("{{color}}", context).then(html => console.log(html)) ``` ## toLiquid `toLiquid()` is not a method of `Drop`, but it can be used to return a `Drop`. In cases where you have a fixed structure in the `context` that cannot change its values, you can implement `toLiquid()` to let LiquidJS use the returned value instead of itself to render the templates. ```javascript import { Liquid, Drop } from 'liquidjs' const context = { person: { firstName: "Jun", lastName: "Yang", name: "Jun Yang", toLiquid: () => ({ firstName: this.firstName, lastName: this.lastName, // use a different `name` name: "Yang, Jun" }) } } const engine = new Liquid() // Outputs: "Yang, Jun" engine.parseAndRender("{{person.name}}", context).then(html => console.log(html)) ``` Of course, you can also return a `PersonDrop` instance in the `toLiquid()` method and implement this functionality within `PersonDrop`: ```javascript import { Liquid, Drop } from 'liquidjs' class PersonDrop extends Drop { constructor(person) { super() this.person = person } name() { return this.person.lastName + ", " + this.person.firstName } } const context = { person: { firstName: "Jun", lastName: "Yang", name: "Jun Yang", toLiquid: function () { return new PersonDrop(this) } } } const engine = new Liquid() // Outputs: "Yang, Jun" engine.parseAndRender("{{person.name}}", context).then(html => console.log(html)) ``` {% note info toLiquid() vs. valueOf() Difference %} {% endnote %} ## Special Drops LiquidJS itself implements several built-in drops to facilitate template writing. This part is compatible with Shopify Liquid, as we need templates to be portable. ### blank Useful to check whether a string variable is `false`, `null`, `undefined`, an empty string, or a string containing only blank characters. ```liquid {% unless author == blank %} {{author}} {% endif %} ``` ### empty Useful to check if an array, string, or object is empty. ```liquid {% if authors == empty %} Author list is empty {% endif %} ``` {% note info empty implementation %} For arrays and strings, LiquidJS checks their `.length` property. For objects, LiquidJS calls `Object.keys()` to check whether they have keys. {% endnote %} ### nil `nil` Drop is used to check whether a variable is not defined or defined as `null` or `undefined`, essentially equivalent to JavaScript `== null` check. ```liquid {% if notexist == nil %} null variable {% endif %} ``` ### Other Drops There are still several Drops for specific tags, like `forloop`, `tablerowloop`, `block`, which are covered by respective tag documents. [shopify-drops]: https://github.com/Shopify/liquid/wiki/Introduction-to-Drops