Do something! - Working with Events

5. Lesson of RiotJS

Mastering Events is comparable to learning how a Spacerocket works inside out. Riot decided to rebel against this profession of event-wizardry and made it easy for everyone.

Martin Muzatko Written by Martin Muzatko
on

intro

Events are something that is only very hard to simplify, since there are roughly 32 Standard Event API categories, and 67 Addon Event categories according to Mozilla Docs.

This is an unbelievable amount of events to grasp. And only a portion of them are DOM Events. Events which can be attached to HTML elements and listen to actions like click, focus, typing, change of a video or audio element, etc. Luckily, for most of what we need, DOM Events are sufficient, and Riot did an excellent job in making these easy to write and access.

A simple click

Although there is a tremendous amount of events, lets think what we can do with just a simple mouseclick before we move on with the lesson.

We could

  • (de)select an item
  • Activate (toggle) something
  • like a tab
  • or an inline editing tool
  • a modal (or popover, tooltip,...)
  • a timer
  • a dropdown
  • play or stop a video or an audio file
  • pick a date or time

The list is endless.

Without further ado - lets create our first component: A small picker for a contactform of users.

What will we need:

  1. An HTML container that contains our picked users
  2. A possibility to open the list of users where we pick from
  3. A possibility to pick (select) users
  4. A way to close that list again
  5. Extra Bit: Filtering for a user via input

The Markup

Since we already have the data to pick from (our users), we begin by crafting the HTML layout.

RIOT
<picker>
    <div class="picked-items">
        <span if={user.selected} each={user in users}>
            {user.name} - {user.email}
        </span>
        <input type="text">
    </div>
    <div class="picker-list">
        <a>&times;</a>
        <div class={selected: user.selected} each={user in users}>
            {user.name} - {user.email}
        </div>
    </div>
</picker>

14 Lines of code - that's all we need for a basic layout. The picker-list is derived from our previous lesson. The only thing we need to add to our data, is the selected property. Also note that I use the class selected when a user is selected. The class name doesn't have to be the same name as the property.

If we would implement the selected property, our list of users would look like this

JAVASCRIPT
this.users = [
    {name : 'John',     email : 'john1987@gmail.com',         selected : false},
    {name : 'Jake',     email : 'info@cookiemonster.org',     selected : false},
    {name : 'Jayson',   email : 'j.miller@startup.fr',         selected : false},
    {name : 'Jenny',    email : 'jenny@anotherstartup.de',     selected : false},
    {name : 'Jessica',  email : 'info@jessicastyle.com',     selected : false}
]

But we don't want to pollute our object with a selected property. Thats ugly. So we are going to add that selected attribute programmatically. We get to that later in this lesson.

Attaching Events

Right now, the list of users is always open. Our List of users should only open when we focus on the input, and close when we press the × button.

Hint: We use onfocus instead of onclick, because an input can also get focus by tabbing into it. So it is friendly for keyboard-people.

Events are functions we define in the script part of our component. Riot uses the very short ES6 Syntax for Event-methods.

isPicking is our flag to determine wether or not the list is open. We can implement that with if={isPicking} in the picker-list div element.

JAVASCRIPT
this.isPicking = false
openList() {
    this.isPicking = true
}

closeList() {
    this.isPicking = false
}

Events are easily attached to HTML elements inline, just like any other HTML attribute. The only difference is, that we don't use doublequotes (") but curly brackets like with the other Riot/JS expressions. You can combine both though, but double-quotes are not required when using these expressions.

Another thing to note is, that Riot is always updating the DOM whenever an event is fired.

HTML
<a onclick={closeList}>&times;</a>
<input onfocus={openList} type="text">

So after focusing on the input, Riot executes the openList function and updates the DOM.

So far so good - opening and closing the picker already works:

<script type="riot/tag"> <riot-picker> <div class="picked-items"> <span if=\{user.selected\} each=\{user in users\}> \{user.name\} - \{user.email\} </span> <input onfocus=\{openList\} type="text"> </div> <div if=\{isPicking\} class="picker-list"> <a onclick=\{closeList\}>&times;</a> <div onclick=\{pick\} each=\{user in users\}> \{user.name\} - \{user.email\} </div> </div> <style> .picker-list a \{ position: absolute; cursor: pointer; font-size: 1.25em; right: .5em; top: .5em; \} .picker-list \{ position: relative; display: inline-block; padding: 1em; background: #EEE; \} .picker-list div \{ cursor: pointer; \} </style> this.isPicking = false openList() \{ this.isPicking = true \} closeList() \{ this.isPicking = false \} this.users = [ \{name : 'John', email : 'john1987@gmail.com', selected : false\}, \{name : 'Jake', email : 'info@cookiemonster.org', selected : false\}, \{name : 'Jayson', email : 'j.miller@startup.fr', selected : false\}, \{name : 'Jenny', email : 'jenny@anotherstartup.de', selected : false\}, \{name : 'Jessica', email : 'info@jessicastyle.com', selected : false\} ] </riot-picker> </script> <riot-picker></riot-picker>

Looped Events

Next step is to select the users. And this is where Riot really shines compared to other approaches. You have access to the data within your javascript event-function when executing an event that is attached to a loop (each).

Riot uses the first argument of the function to convey that data.

JAVASCRIPT
pick(event) {
    event.item.user.selected = !event.item.user.selected
}

Remember that we didn't want to pollute our objects with the selected property? It is now added whenever we pick something. This works because the negative of undefined is true - exactly what we need. Remember that we have later to filter out this property when we fetch our data.

The event is attached just on the same level where you have defined the each attribute.

Phew, that was a lot to chew now.
So - what is going on?

The event attribute in the event-function (pick) is carrying important data:

  • event.currentTarget points to the element where the event handler is specified.
  • event.target is the originating element. This is not necessarily the same as currentTarget.
  • event.which is the key code in a keyboard event (keypress, keyup, etc…).
  • event.item contains the data that belongs to the looped item.

This data - except for item - can be retrieved by every event. event.item is only part of looped events.

Since Javascript objects are always a reference to the original object, and not a clone (if you assign, for example var object = anotherobject) all changes you apply to event.item.user are happening on the actual object. Also - the name of the user object within event.item is always populated, based on our key we choose in the each statement: each={user in users}

In our pick function, we set it to the opposite of what is already the state (true or false) because we also want to be able to deselect the picked users.

So all in one, this is the final component we created:

<script type="riot/tag"> <riot-picker2> <div class="picked-items"> <span if=\{user.selected\} each=\{user in users\}> \{user.name\} - \{user.email\} </span> <input onfocus=\{openList\} type="text"> </div> <div if=\{isPicking\} class="picker-list"> <a onclick=\{closeList\}>&times;</a> <div class=\{selected: user.selected\} onclick=\{pick\} each=\{user in users\}> \{user.name\} - \{user.email\} </div> </div> <style> .picker-list a \{ position: absolute; cursor: pointer; font-size: 1.25em; right: .5em; top: .5em; \} .picker-list \{ position: relative; display: inline-block; padding: 1em; background: #EEE; \} .picker-list div \{ cursor: pointer; padding: .1em; \} .picked-items span \{ margin-right: .5em; margin-bottom: .2em; padding: .1em; display: inline-block; \} .selected, .picked-items span \{ background: #3377B5; color: #F1F1F1; \} input \{ display: block; \} </style> this.isPicking = false openList() \{ this.isPicking = true \} closeList() \{ this.isPicking = false \} pick(event) \{ event.item.user.selected = !event.item.user.selected \} this.users = [ \{name : 'John', email : 'john1987@gmail.com',\}, \{name : 'Jake', email : 'info@cookiemonster.org'\}, \{name : 'Jayson', email : 'j.miller@startup.fr'\}, \{name : 'Jenny', email : 'jenny@anotherstartup.de'\}, \{name : 'Jessica', email : 'info@jessicastyle.com'\} ] </riot-picker2> </script> <riot-picker2></riot-picker2>

There are a few drawbacks now that are left to fix:

  • The input element is pretty pointless as of now: it does not filter yet, you can replace that with a simple button if you prefer.
  • Once the picker is closed, we can only remove elements by opening it again. We could also add a pick event to the yellow (picked) items
  • We have to filter out the selected property of our object.
  • We have yet to find out how to make this component multi-usable by passing the data to select from as option - I'll cover that in the next lesson.

Summary - What have we learned?

We now know how to attach various events to HTML elements. These events can perform anything you tell them to do. Events have access to the whole scope of the tag. So you can access this.isPicking. We could use that for closing the picker after one pick for example.

Ready to Use Components

Like the picker, there are a lot of reusable components waiting to be used in production. Modals, Tooltips, etc.

RiotGear: http://riotgear.js.org/

Docs and further Reading

List of DOM events on the Riot Cheatsheet: http://martinmuzatko.github.io/riot-cheatsheet/#global-events

Anything else?

Compared to vanillaJS Eventhandling, Riot is really easy. If you find anything that you didn't quite grasp yet, please write in the comments below.

How do you build re-usable interactive webcomponents without wasting your precious time bending the DOM to your needs? Get my latest content about building front-end web-apps delivered directly to your inbox and get free code samples for all the components I create.

Previous Lesson

Filtering Outputs


4. Lesson of RiotJS

Conditionals are very easy to implement. With the power of inline conditions, you can quickly filter your data.

Next Lesson

Reusable Components - Introduction


6. Lesson of RiotJS

Previous lesson, we turned a bunch of data, some events and a simple HTML layout into a working app. Now we are going to take that a step further and make it reusable.

See all Lessons