3. Components
- What is a Component
- Rendering a Component
- Composing Components
- Working with Properties
- Conditional Rendering
What is a Component
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. This page provides an introduction to the idea of components. Conceptually, components are like functions. They accept arbitrary inputs (called "Props") and return the html describing which should appear on the screen.
Components are just ordinary .tpl files located in the /src folder of the project.
Rendering a Component
When Smarty sees an element representing a user-defined component, it passes parameters and children to this component as a single object. We call this object "Props". To access the props on a component we used the implicit variable {$Props}, which provides a get and children method for access. The get method allows a 2nd parameter which is a default value that will be return if the parameter doesn't exist.
Always start component names with a capital letter.
Smarty treats components starting with lowercase letters as functions. For example, {div} represents a function, but {Div} represents a component.
For example, this is a small component which will render the prop title in a <h1> tag and render the children in a <div> underneath.
{* Filename: Section.tpl *}
{$title = $Props->get("value", "i am default value")}
{$children = $Props->children()}
<div>
<h1>{$title}</h1>
<div>{$children}</div>
</div>
To use render this component you simply do the following
{Section title="I am title"}
This is the children
{/Section}
It's not possible for a component to modify its properties, as components follow the principle of immutability and pure. Consider the following component.
{$a = $Props->get("a")}
{$b = $Props->get("b")}
<div>{$a + $b}</div>
Such components are called "pure" because they do not attempt to change their inputs, and always return the same result for the same inputs.
Composing Components
Components can refer to other components in their output. This lets us use the same component abstraction for any level of detail. A button, a form, a dialog, a screen: in React apps, all those are commonly expressed as components.
For example, we can create another component that renders {Text} many times:
<div>
{Section title="red"}This is the red section{/Section}
{Section title="green"}This is the green section{/Section}
{Section title="blue"}This is the blue section{/Section}
</div>
Typically, you would have a single component at the very top of a view in the template layer.
Working with Properties
Properties has many use cases, but the most common is to extract a variable to then use in the component. Sometimes during composition, we unfortunately end up having to define a lot of props multiple times on different components. We refer to this practice as prop drilling and have built into the {$Props} object and component structure ways of avoiding to much prop drilling.
All components have a magic prop called passthrough. The passthrough prop is an array or key value pairs that will be merged with the other props on the component. This example applies the passthrough logic to our previously shown {Section} component and enables it to handle any undefined prop in a logical manner.
{$title = $Props->get("value", "i am default value")}
{$children = $Props->children()}
{$passthrough = $Props->passthrough()}
{Wrapper passthrough=$passthrough}
<h1>{$title}</h1>
<div>{$children}</div>
{/Wrapper}
To not loose track of unhandled properties and have a predictable structure, we recommend always using passthrough on the root of the component. If a property needs to go onto another component in the composition, it is better to handle it explicitly with the get method.
This will now pass all the unhandled properties from {Section} to the {Wrapper} component. Looking at how the wrapper component is implemented this proves quite powerful. The {Wrapper} component makes use of another tool called the rest function. This works the same as passthrough, but instead of returning an array, it returns the unhandled props as a HTML attributes string.
{$attrs = $Props->rest()}
{$children = $Props->children()}
<div {$attrs}>
{$children}
</div>
Now using the section component, we can pass any parameter and know exactly where it ends up in the generated HTML structure. Look here how we can pass a data-color attribute to the {Section} component and it will appear on the generate section HTML.
{Section title="red" data-color="red"}
This is my content
{/Section}
{* Generated HTML *}
<div data-color="red">
<h1>red</h1>
<div>This is my content</div>
</div>
To summarize the content of this section, we can use passthrough to pass unhandled properties down the composed component tree, and we can use rest to generate HTML attributes at the point in the structure that makes sense for the component.
Conditional Rendering
All components automagically implement a certain magic property we call render. This property can be used to control whether or not the component will be output in the final generated HTML. This is a convenience more then anything, but the main reason this property exists is for code readability and simplified component code. Take a look at this example where we conditionally render the {Section} component the traditional way, and then with the render property.
{if $something}
{Section title="red" data-color="red"}
This is my content
{/Section}
{/if}
// Can be simplified down to
{Section
title="red"
data-color="red"
render=$something
}
This is my content
{/Section}
As clearly visible this reduces code complexity quite a bit, and makes it very easy to do conditional rendering without bloating the code base. The render property, if not defined, will always be true. If the render property is defined on a component, it will only render if it is a truthy value. This means that anything PHP considers a falsey value, will prevent rendering.
Smarty considers the following values falsey, so if more fine grained control is needed do comparison for specific values in the render property.
false
0
0.0
"" (empty string)
NULL
[] (empty array)