RSS

RiotJS Example

Info

SummaryA quick-dive in to RiotJS, with examples.
Shared2015-01-28
Revised2023-02-04 @ 18:00 UTC

When I look at any of the myriad JavaScript UI libraries out there, I now ask two questions:

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

<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 returns), this is a promising UI library that I am definitely going to consider vs. React in my future projects.