RiotJS Example
Info
Summary | A quick-dive in to RiotJS, with examples. |
---|---|
Shared | 2015-01-28 |
Revised | 2023-02-04 @ 18:00 UTC |
When I look at any of the myriad JavaScript UI libraries out there, I now ask two questions:
- Does data flow in one direction?
- Do updates to the UI happen intelligently?
I am a fan of Facebook’s ReactJS library because of its DOM diffing (via the “virtual DOM”) and one-way data binding. React is a tool I use every day and have come to enjoy (sans-JSX), but I am always on the lookout for way to do things simpler.
A colleague of mine recently shared the second iteration of RiotJS with me. Of course, I was sucked in because it compared itself with React (a bold statement). You can view the comparisons between Riot and React for yourself.
One unfortunate fact about fledgling JS libraries is that they lack examples of how to accomplish common goals for the web. This is an attempt to help out with that.
Installation
Project-Specific
Given you have a package.json
set up, you can easily install the Riot compiler as a development dependency:
npm install riot --save-dev
and then set up Riot to run as an NPM script and watch for any changes
"scripts": {
"watch:riot": "riot -w src/ build/"
}
which is then run by a simple
npm run watch:riot
Alternatively, you can download the riot.js library via any of their recommended methods.
Global Installation (alternative)
If you don’t have a package.json
and want to install Riot globally:
npm install riot -g
riot -w src/ build/
Tab Example
I decided to start small and make a tabbing example where clicking a tab shows content related to it underneath. Here is the final product:
Step 1: Start With Markup
Starting with a blank HTML document, add the <riot-tabs></riot-tabs>
tag to your document:
<!DOCTYPE html>
<html>
<head></head>
<body>
<riot-tabs></riot-tabs>
</body>
</html>
As mentioned previously, we know we need the (very tiny) RiotJS library, so don’t forget to include it:
<body>
<riot-tabs></riot-tabs>
<script src="path/to/riot-2.0.1.js"></script>
</body>
Easy enough, right? Given Riot doesn’t write our applications for us, we will need to tell Riot to mount some component, which in this case is “tabs.”
<body>
<riot-tabs></riot-tabs>
<script src="path/to/riot-2.0.1.js"></script>
<script>riot.mount('riot-tabs')</script>
</body>
When we run this code through the browser, we’re going to receive an error telling us that 'tabs'
is not a thing. Congrats! Time for Step 2.
Step 2: Creating Your Custom Tag
Riot’s NPM package, as mentioned earlier, allows us to write and compile pseudo-markup mixed with a little JS. To get started, create a src
folder and add a tabs.tag
file to it, then run
npm run watch:riot
if you have an NPM script set up or
riot -w src/ build/
to compile and watch for more changes to the file/folder.
Back in the tabs.tag
file, add this:
<riot-tabs>
<h2>Tabs</h2>
<ul>
<li class={ tabItem: true }>Tab 1</li>
<li class={ tabItem: true }>Tab 2</li>
<li class={ tabItem: true }>Tab 3</li>
</ul>
</riot-tabs>
That looks almost exactly like vanilla HTML, save for the conditional class(es), which we will use later with is-active
classes. Also, they are way better than concatenating className
strings yourself.
Refreshing your browser will show you that you now have content that is nested within a <riot-tabs></riot-tabs>
tag.
Next up, we can add in the different tabs’ contents:
<riot-tabs>
<h2>Tabs</h2>
<ul>
<li class={ tabItem: true }>Tab 1</li>
<li class={ tabItem: true }>Tab 2</li>
<li class={ tabItem: true }>Tab 3</li>
</ul>
<div class="tabContent">
<div class={ tabContent__item: true }>(1) Lorem ispum dolor...</div>
<div class={ tabContent__item: true }>(2) Lorem ispum dolor...</div>
<div class={ tabContent__item: true }>(3) Lorem ispum dolor...</div>
</div>
</riot-tabs>
Okay, this is no big deal, so far.
Being software developers, we hate writing things over and over, so let’s start with the tabs.
<riot-tabs>
<h2>Tabs</h2>
<ul>
<li each={ tab, i in tabs } class={ tabItem: true }>{tab.title}</li>
</ul>
<div class="tabContent">
<div class={ tabContent__item: true }>(1) Lorem ispum dolor...</div>
<div class={ tabContent__item: true }>(2) Lorem ispum dolor...</div>
<div class={ tabContent__item: true }>(3) Lorem ispum dolor...</div>
</div>
this.tabs = [
{ title: 'Tab 1' },
{ title: 'Tab 2' },
{ title: 'Tab 3' }
]
</riot-tabs>
Riot has a nice each={ item, i in array } attribute, similar to JavaScript’s for … in …
While we’re at it, why not iterate over the content items, as well?
<riot-tabs>
<h2>Tabs</h2>
<ul>
<li each={ tab, i in tabs } class="tabItem">{tab.title}</li>
</ul>
<div class="tabContent">
<div each={ tab, i in tabs } class="tabContent__item">{tab.content}</div>
</div>
this.tabs = [
{ title: 'Tab 1', content: "(1) Lorem ipsum dolor" },
{ title: 'Tab 2', content: "(2) Lorem ipsum dolor" },
{ title: 'Tab 3', content: "(3) Lorem ipsum dolor" }
]
</riot-tabs>
Next, we need to set a default “active tab” and “active content.”
Step 3: Conditional Classes
We want to be able to specify a default tab and tab content. This is accomplished via a conditional is-active
class on both the .tabItem
as well as the corresponding .tabContent__item
. To keep track of what tab/content is active, we can
- add a property to the
this.tabs
array objects - set an
activeTab
property and - create a function to check if the currently iterated tab is the
activeTab
<riot-tabs>
<h2>Tabs</h2>
<ul>
<li each={ tab, i in tabs } class="tabItem { is-active: parent.isActiveTab(tab.ref) }">{tab.title}</li>
</ul>
<div class="tabContent">
<div each={ tab, i in tabs } class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div>
</div>
this.tabs = [
{ title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
{ title: 'Tab 2', ref: 'tab2', content: "(2) Lorem ipsum dolor" },
{ title: 'Tab 3', ref: 'tab3', content: "(3) Lorem ipsum dolor" }
]
this.activeTab = 'tab1'
isActiveTab(tab) {
return this.activeTab === tab
}
</riot-tabs>
Since these are conditional classes, they will either be evaluated as true or false (I believe that anything that is not falsy is considered true; for example, new Date()
is considered true
). Here, we create a function called isActiveTab
and call it from the item itself, but because the function is not scoped to the item, we need to access the parent
scope and call the function on that.
Finally, we need a way to react to events.
Step 4: Toggling the Tabs
When we click on a tab, we want that tab to now be active, and we want the corresponding tab content to be displayed. This can be done via an onclick
handler that calls a function on the parent called toggleTab
<riot-tabs>
<h2>Tabs</h2>
<ul>
<li each={ tab, i in tabs } class="tabItem { is-active: parent.isActiveTab(tab.ref) }" onclick={ parent.toggleTab }>{tab.title}</li>
</ul>
<div class="tabContent">
<div each={ tab, i in tabs } class="tabContent__item { is-active: parent.isActiveTab(tab.ref) }">{tab.content}</div>
</div>
this.tabs = [
{ title: 'Tab 1', ref: 'tab1', content: "(1) Lorem ipsum dolor" },
{ title: 'Tab 2', ref: 'tab2', content: "(1) Lorem ipsum dolor" },
{ title: 'Tab 3', ref: 'tab3', content: "(1) Lorem ipsum dolor" }
]
this.activeTab = 'tab1'
isActiveTab(tab) {
return this.activeTab === tab
}
toggleTab(e) {
this.activeTab = e.item.tab.ref
return true
}
</riot-tabs>
The onclick
event handler receives an event object that is packed with information. What we want is the current tab that we are clicking on, and this is accessed through e.item.tab.ref
, which is just the ref
property on the tab
object of the currently iterated item
.
According to the Riot docs, when an event handler is called, Riot will automatically call this.update()
and re-render the component. However, I found that after I altered my data, I had to return true
.
Once this event handler is completed and the component is re-rendered, the correct tab and content will be displayed, and you will be happy.
Wrap up
In sum, playing with Riot was a mostly enjoyable experience, and I am thankful to the Muut folks for releasing it.
While there are quirks (single quote vs double quote issues, among others) and opinions (neglecting the use of semi-colons, as well as return
s), this is a promising UI library that I am definitely going to consider vs. React in my future projects.