Event Handling

Listening to Events

We can use <tag :eventname=handler> to listen to DOM events and run code when they’re triggered.

tag App prop counter def render <self.bar> # handler will be called when clicking button <button :click=(do counter++)> "Increment" <div> "count is {counter}" Imba.mount <App counter=0>var Imba = require('imba'), _1 = Imba.createElement; var App = Imba.defineTag('App', function(tag){ tag.prototype.counter = function(v){ return this._counter; } tag.prototype.setCounter = function(v){ this._counter = v; return this; }; tag.prototype.render = function (){ var self = this, $ = this.$; return self.$open(0).flag('bar').setChildren($.$ = $.$ || [ // handler will be called when clicking button _1('button',$,0,self).on$(0,['click',function() { var v_; return ((self.setCounter(v_ = self.counter() + 1),v_)) - 1; }],self).setText("Increment"), _1('div',$,1,self) ],2).synced(( $[1].setText("count is " + self.counter()) ,true)); }; }); Imba.mount((_1(App).setCounter(0)).end());

In the example above we declared the handler inline. Usually it is better to define the handlers outside of the view, and decouple them from the event itself. This can be done in several ways.

Resolving Handlers

You can also supply a string as the handler (<div :click="doSomething">). In this case, Imba will look for a method of that name on the current context (self). This means that if you have defined methods on your custom tags, you can refer to these methods. Since binding events is such an integral part of developing web applications, Imba also has a special syntax for this.

tag App prop counter def increment counter++ def step amount counter += amount def render <self.bar> # inline handler <button :click=(do counter++)> "+1" # reference to a method on self <button :click='increment'> "+1" # reference with arguments <button :click=['step',2]> "+2" # shorthand reference <button :click.increment> "+1" # shorthand reference with arguments <button :click.step(3)> "+3" <div> "count is {counter}" Imba.mount <App counter=0>var Imba = require('imba'), _1 = Imba.createElement; var App = Imba.defineTag('App', function(tag){ tag.prototype.counter = function(v){ return this._counter; } tag.prototype.setCounter = function(v){ this._counter = v; return this; }; tag.prototype.increment = function (){ var v_; return ((this.setCounter(v_ = this.counter() + 1),v_)) - 1; }; tag.prototype.step = function (amount){ var v_; return (this.setCounter(v_ = this.counter() + amount),v_); }; tag.prototype.render = function (){ var self = this, $ = this.$; return self.$open(0).flag('bar').setChildren($.$ = $.$ || [ // inline handler _1('button',$,0,self).on$(0,['click',function() { var v_; return ((self.setCounter(v_ = self.counter() + 1),v_)) - 1; }],self).setText("+1"), // reference to a method on self _1('button',$,1,self).on$(0,['click','increment'],self).setText("+1"), // reference with arguments _1('button',$,2,self).on$(0,['click',['step',2]],self).setText("+2"), // shorthand reference _1('button',$,3,self).on$(0,['click','increment'],self).setText("+1"), // shorthand reference with arguments _1('button',$,4,self).on$(0,['click',['step',3]],self).setText("+3"), _1('div',$,5,self) ],2).synced(( $[5].setText("count is " + self.counter()) ,true)); }; }); Imba.mount((_1(App).setCounter(0)).end());

tap is an alias for click that works across mobile, tablets, and desktops.

Event Modifiers

Inspired by vue.js, Imba also supports modifiers. More often than not, event handlers are simple functions that do some benign thing with the incoming event (stopPropagation, preventDefault etc), and then continues on with the actual logic. By using modifiers directly where we bind to an event, our handlers never need to know about the event in the first place.

# call preventDefault on the submit-event, then call doSomething <form :submit.prevent.doSomething>var Imba = require('imba'), _1 = Imba.createElement; // call preventDefault on the submit-event, then call doSomething (_1('form').on$(0,['submit','prevent','doSomething'],this)).end();

What is the difference between a modifier and a handler in this case? There isn't really a difference. In fact, the modifiers are implemented as methods on element, and you can define custom modifiers as well.

  • .stop - stops event from propagating
  • .prevent - calls preventDefault on event
  • .silence - explicitly tell Imba not to broadcast event to schedulers
  • .self - only trigger subsequent handlers if event.target is the element itself

Key Modifiers

For keyboard events (keydown, keyup, keypress) there are also some very handy modifiers available.

# trigger addItem when enter is pressed <input type='text' :keydown.enter.addItem> # multiple handlers for the same event is allowed # trigger gotoPrev/gotoNext when pressing up/down keys <div :keydown.up.gotoPrev :keydown.down.gotoNext>var Imba = require('imba'), _1 = Imba.createElement; // trigger addItem when enter is pressed (_1('input').setType('text').on$(0,['keydown','enter','addItem'],this)).end(); // multiple handlers for the same event is allowed // trigger gotoPrev/gotoNext when pressing up/down keys (_1('div').on$(0,['keydown','up','gotoPrev'],this).on$(1,['keydown','down','gotoNext'],this));
  • .left
  • .right
  • .up
  • .down
  • .enter
  • .tab
  • .esc
  • .space
  • .del

System Modifier Keys

  • .ctrl
  • .alt
  • .shift
  • .meta
# only trigger when ctrl is pressed <button :click.ctrl.myHandler> # only trigger when shift is pressed <button :click.shift.myHandler> # the order of modifiers matters; # always prevent default action, but only trigger myHandler if alt as pressed <button :click.prevent.alt.myHandler>var Imba = require('imba'), _1 = Imba.createElement; // only trigger when ctrl is pressed (_1('button').on$(0,['click','ctrl','myHandler'],this)); // only trigger when shift is pressed (_1('button').on$(0,['click','shift','myHandler'],this)); // the order of modifiers matters; // always prevent default action, but only trigger myHandler if alt as pressed (_1('button').on$(0,['click','prevent','alt','myHandler'],this));

Mouse Button Modifiers

  • .left
  • .right
  • .middle

Declaring default handlers

When an event is processed by Imba, it will also look for an on(eventname) method on the tags as it traverses up from the original target.

tag App def onsubmit e e.prevent window.alert('Tried to submit!') def render <self> <form> <input type='text'> <button type='submit'> "Submit" Imba.mount <App>var Imba = require('imba'), _1 = Imba.createElement; var App = Imba.defineTag('App', function(tag){ tag.prototype.onsubmit = function (e){ e.prevent(); return window.alert('Tried to submit!'); }; tag.prototype.render = function (){ var $ = this.$; return this.$open(0).setChildren( $[0] || _1('form',$,0,this).setContent([ _1('input',$,1,0).setType('text'), _1('button',$,2,0).setType('submit').setText("Submit") ],2) ,2).synced(( $[0].end(( $[1].end(), $[2].end() ,true)) ,true)); }; }); Imba.mount((_1(App)).end());

Custom events

tag.trigger(name, data = null)

Custom events will bubble like native events, but are dispatched and processed directly inside the Imba.Event system, without generating a real browser Event. Optionally supply data for the event in the second argument. Here is a rather complex example illustrating several ways of dealing with custom events

tag Todo < li def clickRename trigger('itemrename',data) def clickTitle trigger('itemtoggle',data) def render <self .done=data:done> <span :tap.clickTitle> data:title <button :tap.clickRename> 'rename' tag Todos < ul def setup @items = [ {title: "Remember milk", done: false} {title: "Test custom events", done: false} ] # the inner todo triggers a custom itemtoggle event when tapped # which will bubble up and eventually trigger onitemtoggle here def onitemtoggle e e.data:done = !e.data:done def onitemrename e var todo = e.data todo:title = window.prompt("New title",todo:title) def render <self> for item in @items <Todo[item]> Imba.mount <Todos>function iter$(a){ return a ? (a.toArray ? a.toArray() : a) : []; }; var Imba = require('imba'), _2 = Imba.createTagList, _1 = Imba.createElement; var Todo = Imba.defineTag('Todo', 'li', function(tag){ tag.prototype.clickRename = function (){ return this.trigger('itemrename',this.data()); }; tag.prototype.clickTitle = function (){ return this.trigger('itemtoggle',this.data()); }; tag.prototype.render = function (){ var $ = this.$; return this.$open(0).flagIf('done',this.data().done).setChildren($.$ = $.$ || [ _1('span',$,0,this).on$(0,['tap','clickTitle'],this), _1('button',$,1,this).on$(0,['tap','clickRename'],this).setText('rename') ],2).synced(( $[0].setContent(this.data().title,3) ,true)); }; }); var Todos = Imba.defineTag('Todos', 'ul', function(tag){ tag.prototype.setup = function (){ return this._items = [ {title: "Remember milk",done: false}, {title: "Test custom events",done: false} ]; }; // the inner todo triggers a custom itemtoggle event when tapped // which will bubble up and eventually trigger onitemtoggle here tag.prototype.onitemtoggle = function (e){ return e.data().done = !e.data().done; }; tag.prototype.onitemrename = function (e){ var todo = e.data(); return todo.title = window.prompt("New title",todo.title); }; tag.prototype.render = function (){ var self = this, $ = this.$; return self.$open(0).setChildren((function($0) { for (let i = 0, items = iter$(self._items), len = $0.taglen = items.length; i < len; i++) { ($0[i] || _1(Todo,$0,i)).setData(items[i]).end(); };return $0; })($[0] || _2($,0)),4).synced(); }; }); Imba.mount((_1(Todos)).end());

Event Interface

Imba handles all events in the dom through a single manager, listening at the root of your document. Each native event is wrapped in an Imba.Event-instance, which has a few methods worth knowing:

tag CustomElement def onclick event event.target # returns the Imba.Tag target for event event.native # returns the native DOMEvent event.type # returns the type of event, in this case 'click' event.prevent # calls preventDefault on the native event event.stop # calls stopPropagation on the native event # a bunch of methods accessing native event event.x # Event:x event.y # event.native:y event.button # event.native:button event.which # event.native:which event.alt # event.native:altKey event.shift # shiftKey event.ctrl # event.native:ctrlKey event.meta # event.native:metaKeyvar Imba = require('imba'); var CustomElement = Imba.defineTag('CustomElement', function(tag){ tag.prototype.onclick = function (event){ event.target(); // returns the Imba.Tag target for event event.native(); // returns the native DOMEvent event.type(); // returns the type of event, in this case 'click' event.prevent(); // calls preventDefault on the native event event.stop(); // calls stopPropagation on the native event // a bunch of methods accessing native event event.x(); // Event:x event.y(); // event.native:y event.button(); // event.native:button event.which(); // event.native:which event.alt(); // event.native:altKey event.shift(); // shiftKey event.ctrl(); // event.native:ctrlKey return event.meta(); // event.native:metaKey }; });