Tags

Even though tags are just very thin wrappers around native DOM elements, they do have some functionality that is worth knowing. If you want to deal with the DOM element directly, you can always access that through tag.dom. The DOM element also has a reference to its Imba.Tag wrapper, through domElement:_tag

var element = <div.one.two> "Hello" element # => Imba.Tag - thin wrapper element.dom # => HTMLDivElement - real dom elementvar Imba = require('imba'), _1 = Imba.createElement; var element = (_1('div').flag('one').flag('two').setText("Hello")); element; // => Imba.Tag - thin wrapper element.dom(); // => HTMLDivElement - real dom element

imba.io is written entirely in Imba, and the views are generated using tags. The same code is rendering both on the server and the client, using the same logic for routing etc. If you are interested, the sourcecode is publicly available at github.

Instances

You can think of the tag syntax as a very consise specialized syntax for creating instances of a certain kind of objects. Just like [] is a shorter way to create an array new Array()

var array = [] # shorthand for creating an array var node = <div> # shorthand for creating a div element <div.one title='hello'> # is really just shorthand for Imba.createElement('div').flag('one').setTitle('hello').end()var Imba = require('imba'), _1 = Imba.createElement; var array = []; // shorthand for creating an array var node = (_1('div')); // shorthand for creating a div element (_1('div').flag('one').setTitle('hello')).end(); // is really just shorthand for Imba.createElement('div').flag('one').setTitle('hello').end();

Lifecycle

There are a few methods you can override on custom tags to hook into the lifecycle of a tag. Here we override all these methods in our custom Example tag

tag Example def build # called once, before properties are set # setting an instance variable 'counter' @counter = 0 def setup # called once, after properties are set def mount # called when tag has been added to the document # schedule tag (to call tick) every 1000ms schedule(interval: 1000) def unmount # called when tag has been removed from the document # unscheduling the tag unschedule def tick # called when/if tag is scheduled and scheduler is active # default is to call render render def render # declare inner view <self.bar> <strong> "Title: {data:name}" <em> "Counting {@counter++}" # Mount an instance of Example - with some data Imba.mount <Example[{name: "Lifecycle"}]>var Imba = require('imba'), _1 = Imba.createElement; var Example = Imba.defineTag('Example', function(tag){ tag.prototype.build = function (){ // called once, before properties are set // setting an instance variable 'counter' return this._counter = 0; }; tag.prototype.setup = function (){ // called once, after properties are set }; tag.prototype.mount = function (){ // called when tag has been added to the document // schedule tag (to call tick) every 1000ms return this.schedule({interval: 1000}); }; tag.prototype.unmount = function (){ // called when tag has been removed from the document // unscheduling the tag return this.unschedule(); }; tag.prototype.tick = function (){ // called when/if tag is scheduled and scheduler is active // default is to call render return this.render(); }; tag.prototype.render = function (){ // declare inner view var $ = this.$; return this.$open(0).flag('bar').setChildren($.$ = $.$ || [ _1('strong',$,0,this), _1('em',$,1,this) ],2).synced(( $[0].setText("Title: " + (this.data().name)), $[1].setText("Counting " + (this._counter++)) ,true)); }; }); // Mount an instance of Example - with some data Imba.mount((_1(Example)).setData({name: "Lifecycle"}).end());

Interface

tag Example def interface dom # access to the native dom elementvar Imba = require('imba'); var Example = Imba.defineTag('Example', function(tag){ tag.prototype.interface = function (){ return this.dom(); // access to the native dom element }; });

Scheduling

When you mount a tag using Imba.mount, you shouldn't usually need to think about scheduling.

var counter = 0 var status = "Hello" tag App def doSomething self def loadAsync status = "loading" Promise.new do |resolve| setTimeout(&,500) do resolve(status = "loaded") def render <self.bar> <button> "noop" <button :tap.doSomething> "handle" <button :tap.loadAsync> "async" <div> "Rendered: {++counter}" <div> "Status: {status}" # when mounting a node with Imba.mount it will automatically # be scheduled to render after dom events and Imba.commit Imba.mount <App>var Imba = require('imba'), _1 = Imba.createElement; var counter = 0; var status = "Hello"; var App = Imba.defineTag('App', function(tag){ tag.prototype.doSomething = function (){ return this; }; tag.prototype.loadAsync = function (){ status = "loading"; return new Promise(function(resolve) { return setTimeout(function() { return resolve(status = "loaded"); },500); }); }; tag.prototype.render = function (){ var $ = this.$; return this.$open(0).flag('bar').setChildren($.$ = $.$ || [ _1('button',$,0,this).setText("noop"), _1('button',$,1,this).on$(0,['tap','doSomething'],this).setText("handle"), _1('button',$,2,this).on$(0,['tap','loadAsync'],this).setText("async"), _1('div',$,3,this), _1('div',$,4,this) ],2).synced(( $[3].setText("Rendered: " + (++counter)), $[4].setText("Status: " + status) ,true)); }; }); // when mounting a node with Imba.mount it will automatically // be scheduled to render after dom events and Imba.commit Imba.mount((_1(App)).end());

Even though most changes to the state of your application will happen as a result of user interactions, there are still a few places you need to notify Imba that things have changed. E.g. if you receive data from a socket you want to call Imba.commit after receiving messages socket.addEventListener('message',Imba:commit), and if you are fetching data from a server outside of event handlers, you want to call Imba.commit at the end of the fetch.

tag App def mount schedule(raf: true) def onmousemove e @x = e.x @y = e.y def render <self.bar> <div> "Mouse is at {@x or 0} {@y or 0}" Imba.mount <App>var Imba = require('imba'), _1 = Imba.createElement; var App = Imba.defineTag('App', function(tag){ tag.prototype.mount = function (){ return this.schedule({raf: true}); }; tag.prototype.onmousemove = function (e){ this._x = e.x(); return this._y = e.y(); }; tag.prototype.render = function (){ var $ = this.$; return this.$open(0).flag('bar').setChildren( $[0] || _1('div',$,0,this) ,2).synced(( $[0].setText("Mouse is at " + (this._x || 0) + " " + (this._y || 0)) ,true)); }; }); Imba.mount((_1(App)).end());
tag Clock def mount schedule(interval: 1000) def render <self> Date.new.toLocaleString Imba.mount <Clock>var Imba = require('imba'), _1 = Imba.createElement; var Clock = Imba.defineTag('Clock', function(tag){ tag.prototype.mount = function (){ return this.schedule({interval: 1000}); }; tag.prototype.render = function (){ return this.$open(0).setChildren(new Date().toLocaleString(),3).synced(); }; }); Imba.mount((_1(Clock)).end());