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.
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 forclick
that works across mobile, tablets, and desktops.
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, self = {};
// call preventDefault on the submit-event, then call doSomething
(_1('form').on$(0,['submit','prevent','doSomething'],self)).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 itselfFor 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, self = {};
// trigger addItem when enter is pressed
(_1('input').setType('text').on$(0,['keydown','enter','addItem'],self)).end();
// multiple handlers for the same event is allowed
// trigger gotoPrev/gotoNext when pressing up/down keys
(_1('div').on$(0,['keydown','up','gotoPrev'],self).on$(1,['keydown','down','gotoNext'],self));
# 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, self = {};
// only trigger when ctrl is pressed
(_1('button').on$(0,['click','ctrl','myHandler'],self));
// only trigger when shift is pressed
(_1('button').on$(0,['click','shift','myHandler'],self));
// 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'],self));
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 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 tagLoop($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());
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:metaKey
var 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
};
});