In React (JSX), you create a component like so:
function Button(props) {
return (
<button className={"button " + (props.color ? props.color : '')} type="button">
{props.value}
</button>
);
};
This renders a button
element with two properties - color
and value
, usage is like so:
<Button value="Submit" /> // "<button class='button'>Submit</button>"
<Button color="primary" value="Submit" /> // "<button class='button primary'>Submit</button>"
<Button color="secondary" value="Submit" /> // "<button class='button secondary'>Submit</button>"
These components are entirely encapsulated, meaning the properties won't leak out.
I want this within Liquid/Shopify themes.
We can achieve something like this like so:
// snippets/button.liquid
<button class="button {{ class }}" type="button">
{{ value }}
</button>
Usage:
{% assign class = "primary" %}
{% assign value = "Submit" %}
{% include "button" %} // "<button class='button primary'>Submit</button>"
This works but the properties leak, for example:
{% assign class = "primary" %}
{% assign value = "Submit" %}
{% include "button" %} // "<button class='button primary'>Submit</button>"
// ...
{{ class }} // "primary"
We can get around this by using the inline include parameter syntax:
{% include "button",
class: "primary",
value: "Submit" %} // "<button class='button primary'>Submit</button>"
This fixes the leaking issue:
{% include "button",
class: "primary",
value: "Submit" %} // "<button class='button primary'>Submit</button>"
{{ class }} // nil
This brings a slightly different issue:
{% assign value = "Some higher up/global variable because `value` is a pretty generic variable name." %}
{% include "button",
class: "primary",
value: "Submit" %} // "<button class='button primary'>Submit</button>"
Let's say the first value
variable is a global variable which should leak through into snippets/button.liquid
, it won't work because the inline include parameter syntax overrides it; you could just rename one of the variables so they don't clash but at some point there will probably be a generic variable name which clashes with a component property.
What we need to do here is make every component property name unique to that component, we need to fake encapsulation and the only means we have at our disposal are naming conventions. I toyed with prefixing each component property name with the snippet file name, like:
{% include "button",
button_class: "primary",
button_value: "Submit" %} // "<button class='button primary'>Submit</button>"
This looks pretty good for this example, but it gets a little out of hand when our components have longer names such as snippets/product-thumbnail.liquid
, an example of a component property for this is product_thumbnail_image_url
, typing long variable names gets boring fast.
My solution is to associate a number to each component, so snippets/button.liquid
would be component 1, so we'd prefix each component property name with c1_
like so (I've also added a DocBlock style header to document each component property):
// snippets/button.liquid
{% comment %}
Component 1 - Button
@param {String} c1_class
@param {String} c1_value
{% endcomment %}
<button class="button {{ c1_class }}" type="button">
{{ c1_value }}
</button>
Usage:
{% include "button",
c1_class: "primary",
c1_value: "Submit" %} // "<button class='button primary'>Submit</button>"
Now we don't have to worry about these variables ever clashing because variables beginning with c1_
will only ever exist when the snippets/button.liquid
component is used.
Buttons may no be the best example of a component here, most of the time buttons are kept pretty simple with a pretty flat DOM structure, we can usually just stick to using a class name as the style abstraction, there's not much difference between this:
{% include "button",
c1_class: "primary",
c1_value: "Submit" %} // "<button class='button primary'>Submit</button>"
...And this:
<button class="button primary">
Submit
</button>
So, personally I wouldn't even add button
as a component unless it got more complex. This concept of components shines when the DOM structure is more complex and there's a lot of class names, for example here's a component:
// snippets/faux-image.liquid
{% comment %}
Component 2
@param {String} c2_aspect_ratio
@param {String} c2_src
@param {String} c2_alt
{% endcomment %}
<div class="image-container box-placehold position-relative box-ratio--{{ c2_aspect_ratio }}">
<div class="position-absolute position-full bg-full-center" data-src="{{ c2_src }}" data-no-resize>
<img class="one-whole opacity-0 height-full" src="{{ c2_src }}" alt="{{ c2_alt }}" />
</div>
</div>
This component renders a box with a proper ratio (4:3, 16:9, etc.) with an image which will stretch to fit the dimensions of the box without distorting. Usage is like so:
{% include "faux-image",
c2_aspect_ratio: "16-9",
c2_src: "//placehold.it/2000",
c2_alt: "Awesome placeholder is awesome." %}
Which renders:
<div class="image-container box-placehold position-relative box-ratio--16-9">
<div class="position-absolute position-full bg-full-center" data-src="//placehold.it/2000" data-no-resize>
<img class="one-whole opacity-0 height-full" src="//placehold.it/2000" alt="Awesome placeholder is awesome." />
</div>
</div>