Fork me on GitHub

Yama.js

The most compact way to build reusable UI in Javascript.

Get it

Coding a to do app in ... seconds :

A simple "To Do List" app in 31 lines.


                

Live Result :

It fits in a Commodore 16

Which is useless. But Yama is really lightweight: 7 KB minified.

Have a look »

Vanilla Javascript

You love jQuery? Good for you. Yama doesn't need it.

What the...? View description as a comment?!

Yes. Why not?

It is a Yaml/Jade-like templating syntax to build the dom tree of your UI, and it binds automatically events and observables to your view object.

It's described as a copyright comment in draw function, so decent minifiers won't touch it.

Real observables

Javascript is mature enough to have accessor handlers and Yama takes advantage of it.

Observables have seamless js syntax. Yama uses native javascript accessors to handle changes in view objects. It means that you can't get in trouble with digest loop because... you know, there isn't any.

Anything you write is automatically reusable

Yama encourages forces you to describe your UI as a component. You can reuse it later inside another component.

View example »

Inspiration

It gets :

  • from React: The "everything is a component" philosophy and UI description in javascript files,
  • from Angular : Observables with seamless js syntax. myComponent.title = "Hi" instead of myComponent.title("Hi")
  • from Knockout : Declarative binding attributes

Hello world example

yama.register({
    name : 'myComponent1',
    draw : function(){
        /*!
        root
            div text:title
        !*/
    },

    title : "Hello, world !"
});
var myComponent = new yama.components.myComponent1(document.body);

To create a UI, Yama encourages you to write it as a reusable component.

First, you have to name your component with name property. Here we named it myComponent1.

Second, you have to describe the way your component should be drawn. The UI is declared as a comment in draw function. In this example, we tell yama to create a div in the component's root dom and to bind it's text to the component's title property.

And that's it. In this example, we also add a custom property to our component : title. In draw function we told yama to bind the div's inner text to the value of title property with the directive text:title.

Now your component is ready to use. In plain javascript, you can call the constructor new yama.components.myComponent1(); If you want your component to be drawn into a specific dom element : var myComponent = new yama.components.myComponent1(document.getElementById("example1"));

Building a simple UI

yama.register({
    name : 'myComponent2',
    draw : function(){
        /*!
        root
            div
                div text:title1
                div text:title2
            div
                span text:title3
                span text:title4
        !*/
    },
    title1 : "I am here",
    title2 : "Me too !",
    title3 : "Ho ho",
    title4 : "How nice"
});

Here we build a UI with nested div and span elements. The indentation inside the draw function defines the dom tree of your component.

Text inside elements are bound to properties title1 to title4

Accessing component's dom

yama.register({
    name : 'myComponent3',
    draw : function(){
        /*!
        root
            div text:title
            div:elapsedDiv
        !*/
    },
    title : "Elapsed time : ",
});

var elapsed = 0;
var component = new yama.components.myComponent3();
setInterval(function(){
    component.elapsedDiv.innerHTML = (elapsed++);
}, 1000);

The syntax div:elapsedDiv creates a div when drawing the component and binds the property elapsedDiv to this dom element.

You have access to this dom element even outside the declaration of your component with myComponent3.elapsedDiv

Basic interaction with your component

yama.register({
    name : 'myComponent4',
    draw : function(){
        /*!
        root
            div text:title
        !*/
    },
    title : "Not clicked yet.",
});

var component = new yama.components.myComponent4();
example4Btn.onclick = function(){
    component.title = "Clicked !";
};
Click here

Properties of your component like title are actually hidden observables. When you set a new value to myComponent4.title, yama detects it and notifies all observers which are observing this property : in this example, the div created in draw function observes title. So when we assign a new value to title, the text inside the div element gets updated automatically.

Unlike Angular.js, there isn't any digest loop, so the detection works even outside your component's event handlers. In this example, the event is fired from outside, namely from example4Btn.

Interaction inside your component

yama.register({
    name : 'myComponent5',
    draw : function(){
        /*!
        root
            span:btn text:btnTitle onclick:btnClick
            span text:number
        !*/
    },

    number : 0,

    btnTitle : "Click here to increment »",
    btnClick : function(){
        this.number++;
    }
});

Reuse your component : nested components

yama.register({
    name : 'myComponent6',
    draw : function(){
        /*!
        root
            div
                myComponent5
                myComponent5
                myComponent5
            div
                myComponent5
        !*/
    }
});

Now we can reuse our myComponent5 by simply putting its name instead of a native dom element in the draw function. Yama will render myComponent5 instead of a dom element.

Each of them behaves independently.

Access to a nested component

yama.register({
    name : 'myComponent7',
    draw : function(){
        /*!
        root
            div:btn text:btnTitle onclick:btnClick
            div
                myComponent5
                myComponent5:componentChild2
                myComponent5
            div
                myComponent5
        !*/
    },

    btnTitle : "Click here to set second component's value",
    btnClick : function(){
        this.componentChild2.number = 1337;
    }
});

This example shows that custom components we wrote act exactly like a native dom element. They are rendered, you can access them and their properties by naming them.

As we saw in the previous example, each of them behaves independently. But their parent has access to their properties.

We named the second instance of myComponent5 : componentChild2. That means that we get a pointer to this specific instance of myComponent5 by calling myComponent7.componentChild2. For example myComponent7.componentChild2.number = 1337; will set the property number of this instance of myComponent5 inside myComponent7.

By clicking on the button you should see the second nested components value set to 1337.

Nested component event fire

yama.register({
    name : 'myComponent8Outside',
    draw : function(){
        /*!
        root
            myComponent8Inside onMyFabulousEvent:childFiredEvent
        !*/
    },

    childFiredEvent : function(){
        alert("myComponent8Inside fired onMyFabulousEvent");
    }

});


yama.register({
    name : 'myComponent8Inside',
    draw : function(){
        /*!
        root
            div:btn text:btnTitle onclick:btnClick
        !*/
    },

    btnTitle : "Click here to fire myFabulousEvent",
    btnClick : function(){
        this.onMyFabulousEvent();
    }
});

The same way a native dom element like a div can fire onclick event, your custom component can fire custom events as well.

Arrays and "foreach" directive

yama.register({
    name : 'myComponent9',
    draw : function(){
        /*!
        root
            div text:title
            div
                foreach item:myArray
                    div text:item
        !*/
    },

    title : "Hello",
    myArray : [2, 4, 6]
});

foreach directive in the draw function iterates through an array and renders it's content for each item in the array.

Nested "foreach" directives and index numbers

yama.register({
    name : 'myComponent9',
    draw : function(){
        /*!
        root
            div text:title
            div:cont2 text:otherTitle
            div:loopCont1
                foreach:indVar1 iterator1:list1
                    div:it1cont
                        div text:it1text
                        div text:indVar1
                        div text:iterator1.title
                        div:loopCont2
                            foreach:indVar2 iterator2:iterator1.list2
                                div:it2cont
                                    div text:it2text
                                    div text:indVar1
                                    div text:indVar2
                                    div text:iterator2
        !*/
    },
    it1text : "index1",
    it2text : "index2",

    title : "Hey",
    otherTitle : "Other title",
    list1 : [
        {
            'title':"onur",
            'list2':[1,2,3]
        },
        {
            'title':"test",
            'list2':[4,5,6]
        }
    ]
});

You have access to the index of the iterator during the iteration. We just name the index variable with foreach:indVar1 and foreach:indVar2.

Two way binding

yama.register({
    name : 'myComponent11',
    draw : function(){
        /*!
        root
            div
                div text:title
                input value:title
        !*/
    },

    title : "Initial title here, write something :"
});

Changes in the input element are bound to title property of our component. As each property is a hidden observable, when the input changes the value of title, all observers, namely the div which is observing for it's text attribute gets notified and it's inner text is set to the value of title as well.