Creating a new widget blueprint
To build our own widget blueprint, we will combine our own html structure with the data provided to us by liqud tags and filters.
Building a widget blueprint is very simple, you basically reference different objects and their properties and place them in the html structure of your liking. Let’s see how this works in an example widget blueprint. Below is a real-world example of a widget blueprint that displays a list of articles:
<section class="container articles-list">
{% for a in article_collection.items %}
<article class="article-container">
<h2 class="article-title"><a href="{{a.link}}" title="{{a.title}}">{{a.title}}</a></h2>
{% if a.featured_image %}
<div class="thumb-container">
<img src="{{a.featured_image.sizes.original.url}}" alt="{{a.featured_image.name}}" />
</div>
{% endif %}
<div class="article-header">
<p class="small">Published: {{a.published_on | date: "%b %d"}} <span class="divider">|</span>By: {{a.author.first_name}} {{a.author.last_name}}</p>
</div>
<div class="article-excerpt">
<p>{{a.body | truncate : 300}}</p>
<a class="read-more" href="{{a.link}}">Read more</a>
</div>
</article>
{% endfor %}
<div class="pagination">
{% if article_collection.paginate.total_pages > 1 %}
{% if article_collection.paginate.previous %}
<a class="prev-btn" href="{{article_collection.paginate.previous_url}}"> Previous </a>
{% endif %}
{{article_collection.paginate.current}} / {{article_collection.paginate.total_pages}}
{% if article_collection.paginate.next %}
<a class="next-btn" href="{{article_collection.paginate.next_url}}"> Next </a>
{% endif %}
{% endif %}
</div>
</section>
Let’s analyse the code line by line and see how html and liquid tags are working together.
First we wrap the widget in the section tag and apply container articles-list classes to it. This structure and class names are entirely provisional, and you can use any structure fit for your design.
Next we have a liquid for loop, opening tag
{% for a in article_collection.items %}, and closing tag {% endfor %}. To understand what this loop does, let’s take a look at the article_collection object we are referencing here. This object is provided by unroole in form of a liquid tag, and just by referencing it in our widget we have access to all of its properties.
article_collection = {
items: array[article_object],
paginate: {
previous: int,
previous_url: string,
next: int,
next_url: string,
current_page: int,
total_pages: int,
per_page: int,
total_items: int
}
}
As you can see, in our for loop we have referenced the
article_collection.items property, which is an array of article objects, and we are telling widget to execute the block of code in between {% for a in article_collection.items %} and {% endfor %} for each object we have stored in article_collection.items property. Properties referenced in this block of code belong to single article object. If we inspect the article object we will see all the properties we used to build the content of our article container:
article = {
id: int,
title: string,
featured: boolean,
activates_at: date,
published_on: date,
updated_at: date,
body: string,
link: string (URL),
author: {
first_name: string,
last_name: string
},
category: {
id: int,
name: string,
description: string,
urlized_name: string,
updated_at: date
},
type: {
id: int,
name: string,
urlized_name: string,
updated_at: date
},
featured_image: object
}
First we wrap an article in article tag and apply article-container class to it. Next, we build a title and link to the entire article. As per liquid syntax, whenever we are accessing object properties we need to enclose it in double curly brackets, so to build a url of each article we use {{a.link}} and for title {{a.title}}. Again, these are the properties of article object above. Next we want to display the featured image of the article and a .thumb-container surrounding it, but only if the image is assigned to the article. For this flow control we use liquids if statement with opening {% if a.featured_image %} and closing {% endif %} tag. This is telling our widget to only display the block of code in between these two if a condition is met. Our condition here is if article’s property featured_image has image object stored or not. If you inspect our article object above, you will see that featured_image property is an object, and that object is provided to us by asset image liquid tag, and object that is stored has the following structure:
asset_image = {
id: int,
name: string,
description: string,
updated_at: date,
tags: array,
sizes: {
original: {
url: string,
type: string,
file_format: string,
},
thumb: {
url: string,
type: string,
file_format: string,
},
icon: {
url: string,
type: string,
file_format: string,
}
}
}
So, to build an actual link to the image in src attribute of the
img tag we are using {{a.featured_image.sizes.original.url}}. Let’s break it down a bit to see exactly from where our data is coming:
- a - this is our article object in the for loop
- featured_image - this is a property of the article in the loop
- sizes - this is property of image object stored inside featured_image
- original - this is object inside sizes property, which stores all the data of the image in the original size. Besides original we have thumb and icon available
- url - this is the url string of the image in the original size
After featured image sorted out in our widget blueprint, next we want to display the author and published date:
<div class="article-header">
<p class="small">Published: {{a.published_on | date: "%b %d"}} <span class="divider">|</span>By: {{a.author.first_name}} {{a.author.last_name}}</p>
</div>
By now this should be pretty straight forward, we are referencing articles
published_on and author properties. author property has two strings stored within, first_name and last_name. Authors’ name is broken down in two strings so we would give programmers and designers total control over how the data is displayed. You may want to apply different classes to the first and last name, or to display just one or the other.
What is new here is how we are displaying the published date,
{{a.published_on | date: "%b %d"}}. We used a liquid filter date to format the string retrieved with a.published_on. Format in which dates and times are stored with the unroole is yyyy-mm-dd T hh:mm:ss.sss -timezone, and if we omitted the filter and just used {{a.published_on}} the resulting string would be in the default format, which is not usable in this particular situation. Parameters %b %d will produce the following format of the date: Jan 04. Full list of date liquid filters can be found here.
Next block of code is displaying our article content and read more link:
<div class="article-excerpt">
<p>{{a.body | truncate : 300}}</p>
<a class=”read-more” href=”{{a.link}}”>Read more</a>
</div>
First we are referencing the
body property of article in the for loop. body property stores the actual content of the article. We are also applying filter to it, truncate, which is used to display only certain number of characters in a string. Here we passed 300, so in this place only first 300 characters will be displayed. Read more link is built the same way our title link was built, we used articles link property inside the href attribute.
With this block we finished or for loop and each article will be displayed following this structure.
Next we have a block of code that will build our pagination on the page where the widget will be used:
<div class="pagination">
{% if article_collection.paginate.total_pages > 1 %}
{% if article_collection.paginate.previous %}
<a class="prev-btn" href="{{article_collection.paginate.previous_url}}"> Previous </a>
{% endif %}
{{article_collection.paginate.current}} / {{article_collection.paginate.total_pages}}
{% if article_collection.paginate.next %}
<a class="next-btn" href="{{article_collection.paginate.next_url}}"> Next </a>
{% endif %}
{% endif %}
</div>
Here, we are only referencing
paginate and its nested properties of article_collection object and we are using if statements to control whether to display the next and previous links. Let’s see this object once again:
article_collection = {
items: array[article_object],
paginate: {
previous: int,
previous_url: string,
next: int,
next_url: string,
current_page: int,
total_pages: int,
per_page: int,
total_items: int
}
}
Inside paginate property we have all the data needed to output the pagination links.
At the beginning we want to check if there is a need for pagination in the first place
{% if article_collection.paginate.total_pages > 1 %}. total_pages property stores number of pages needed to display all the articles in the collection, so with this if statement we are telling our widget to display the pagination only if number of pages is greater than 1.
Next, we are checking if there are any pages to be shown before and after the current page with
{% if article_collection.paginate.previous %} and {% if article_collection.paginate.next %}. These two properties store the index number of previous and next page respectively. The flow control we have in place is telling the widget to display nested block of code only if there are next and previous pages to be shown. Inside href attributes of next and previous links we are placing appropriate urls with {{article_collection.paginate.next_url}} and {{article_collection.paginate.previous_url}}
After we save the widget blueprint we can add it to a page. As we have referenced
article_collection in our widget blueprint, user has the following options to configure the widget:
- Select the categories and types of articles that will be included in the collection
- Number of articles per page - this will be used for our pagination flow control
- The page to be used when the link of an article is clicked. This page should contain a widget that is referencing the article result liquid tag
In this example we were using
article_collection as a backbone of our widget. You can use any of the liquid tags and objects to build your custom widgets, i.e. for displaying single articles, navigations, galleries, etc. If you would like to add custom fields to your widgets to gather additional input from users read Going further with a custom widget.
Going further with a custom widget
In article Creating a new widget we covered how to use predefined liquid tags when creating our custom widget blueprints. But, custom widget blueprints are not limited only by the content stored in the unroole. You can add various fields to your widget in order to gather input from the user on how certain elements should behave, or custom content that should be displayed on the page. This custom content could be a text or any asset uploaded to the account.
As a simple demonstration we will create a widget blueprint that will be used to create social media links and icons.
<div class=”social-link”>
<a href=”{{simple_string-Social_URL}}” class=”social-button”>
<i class=”fa fa-{{simple_string-Icon_Name}}”></i>
</a>
</div>
When added to a page/layout in the editor, widget above will have two input fields “Social URL” and “Icon Name”. These input fields are generated by simply referencing the liquid tag
simple_string in the widget code. The label of the input field is generated by words added after the ‘-’ following the liquid tag. Always use underscores to denote a space between words.
When rendered on a website, input given by the user will show up where we placed it in a widget. So, Social URL will populate the
href attribute of the link, and Icon Name will be added to the class of i tag. On our example website we will use FontAwesome framework so the icon name will determine which icon will be displayed.
Here is html of the widget rendered on the website, if the user added ‘http://twitter.com/unroole’ as Social URL and ‘twitter’ as Icon Name:
<div class=”social-link”>
<a href=”http://twitter.com/unroole” class=”social-button”>
<i class=”fa fa-twitter”></i>
</a>
</div>
Website owners usually want to add several social media links to their website and this number is not known by the website designers and developers in advance. Especially if you are building a theme to sell on one of the markets. The widget blueprint above can be added to page/layout for each social media link. But, indefinite number of these links can break the intended layout of the website.
That is why it would be wise to have a wrapping div tag, with class social-wrapper applied, and place all the widgets inside. As we don’t know how many social links are there going to be, we will create a separate Wrapper widget blueprint whose only purpose will be to add this wrapper div around all the widgets. Here is the code of our Wrapper widget:
<div class=”social-wrapper”>
{{structured}}
</div>
structured liquid tag we used here denotes that this widget accepts child widgets inside of it and where will those widgets be rendered in the html. If we add wrapper widget to our page/layout it will have an arrow next to the title telling us that it is a structured widget and that other widgets can be placed inside as child widgets.
In page/layout editor if you add Wrapper widget and place three social links widgets inside it, this is how the final html will be rendered:
<div class=”social-wrapper”>
<div class=”social-link”>
<a href=”http://twitter.com/unroole” class=”social-button”>
<i class=”fa fa-twitter”></i>
</a>
</div>
<div class=”social-link”>
<a href=”http://facebook.com/unroole” class=”social-button”>
<i class=”fa fa-facebook”></i>
</a>
</div>
<div class=”social-link”>
<a href=”http://youtube.com/unroole” class=”social-button”>
<i class=”fa fa-youtube”></i>
</a>
</div>
</div>
Input from a user is not limited to text input fields. You can use dropdowns and numbers inputs as well.
Lets build a custom Gallery Slider widget blueprint which will give users the option to choose a type of slide animation, slide duration and whether or not to display the thumbnails below the slider. For this widget we will use a Gallery liquid tag for gallery data, and several other tags to gather input from the user.
{% dropdown-animation_type {"Fade": "fade", "Slide up" : "slide-up", "Slide left" : "slide-left", "Slide right": "slide-right"} %}
{% dropdown-show_thumbnails {"Yes": "yes", "No" : "no" } %}
<div class="gallery-wrapper" data-animation="{{dropdown-animation_type}}" data-duration="{{simple_number-Slide_Duration}}">
<div class="gallery-container">
{% for img in gallery.items %}
<div class="slide">
<img src="{{img.sizes.original.url}}" alt="{{img.sizes.name}}" />
</div>
{% endfor %}
</div>
{% if dropdown-show_thumbnails == "Yes" %}
<div class="gallery-thumbs">
{% for img in gallery.items %}
<div class="thumb">
<img src="{{img.sizes.thumbnail.url}}" alt="{{img.sizes.name}}" />
</div>
{% endfor %}
</div>
{% endif %}
</div>
Let’s analyse the code from the top. On the first line we are declaring our dropdown
{% dropdown-animation_type {"Fade": "fade", "Slide up" : "slide-up", "Slide left" : "slide-left", "Slide right": "slide-right"} %}
. As it is wrapped in a liquid brackets (
{% %} ) this declaration will not be rendered in the html.
- dropdown
- this is a liquid tag for creating a dropdown
- animation_type - this is the name of our dropdown which we will use later in the code where we want the selected value to show up
- {"Fade": "fade", "Slide up" : "slide-up", "Slide left" : "slide-left", "Slide right": "slide-right"} - these are the key : value pairs of each option in the dropdown. Key will be shown to the user and value will be stored as selected item
Same goes for the dropdown that will let user decide to show the thumbnails below the gallery or not.
Next we will fill our data attributes in the
.gallery-wrapper div, data-animation="{{dropdown-animation_type}}" and data-duration="{{simple_number-Slide_Duration}}" which we will reference in our CSS or JavaScript to customize the animation per user input.
data-animation="{{dropdown-animation_type}}" will store value of the user-selected key, so if user selects “Slide Left” in the dropdown when adding a widget to a page/layout, this attribute will be rendered as data-animation="slide-left" in the html.
{{simple_number}} tag is similar to {{simple_string}} tag, with the difference being that {{simple_number}} is limited to only numbers on input. So if a user enters text in this field while adding a widget, it will display an error. With our data-duration=”{{simple_number-Slide_Duration}}” we are creating an input field for the widget, and once the user enters a value i.e. 3000, it will be rendered as
data-duration=”3000” in the html.
Next piece of code is using a gallery tag and the user will have an option to select which gallery to use. We created a for loop that will iterate through every asset object stored in the
gallery.items and execute the block of code between {% for img in gallery.items %} and {% endfor %}. To better understand what we are doing with this loop let’s inspect the object that is available to us when using the gallery tag:
gallery = {
id: int,
name: string,
type: string,
description: string,
size: int,
items: [
{
asset: object,
sizes: object,
name: string,
description: string,
link: string,
html_id: string,
html_class: string,
target: string,
link_label: string
}
]
}
As you can see
gallery.items is an array of objects, and each object represents an asset added to the gallery. These objects are identical to the
asset_image object we covered in the Create a new widget article. In our block of code that is executed for each asset of the gallery we are simply outputting the properties of this object.
<div class="slide">
<img src="{{img.sizes.original.url}}" alt="{{img.sizes.name}}" />
</div>
For the src attribute of the img tag we are using the url of the asset in original size, and for the alt attribute we are using the asset name.
Next we want to give the user an option to choose whether to display the thumbnails below the gallery or not.
{% if dropdown-show_thumbnails == "yes" %}
<div class="gallery-thumbs">
{% for img in gallery.items %}
<div class="thumb">
<img src="{{img.sizes.thumbnail.url}}" alt="{{img.sizes.name}}" />
</div>
{% endfor %}
</div>
{% endif %}
We are adding a control flow with {{dropdown}} tag. In the widget, this tag will output a select box with two options, Yes and No, and the title of the checkbox will be “Show Thumbnails”. After the if statement we are repeating the same for loop as in the gallery container only with thumbnail image size instead of original.
When the user adds this widget to the page, s/he will have the following options to configure:
- Gallery - selects a gallery to display
- Animation Type - dropdown with animations to choose from
- Slide Duration - number input box to add slide duration value
- Show Thumbnails - dropdown with options Yes and No
In this example we were using Gallery as a backbone of our widget. You can use any of the liquid tags to build your custom widgets, i.e. for displaying single articles, navigations, article collections, etc.
To learn more about the liquid tags and syntax used in the unroole, please read this article.