Search code examples
javascriptractivejs

button click binding and view model encapsulation


I want to implement simple toggle button functionality using Ractivejs. Here is the code:

function MyViewModel(model) {
    var self = this;
    self.value = false;
    self.toggleValue = function () {
        self.value = !self.value; //this will probably not update view, but it is not the point here
    };
}

var viewModel = new MyViewModel(model);
var ractive = new Ractive({ el: 'mydiv', template: '#mytemplate', data: viewModel });

and here is my template

<script id='mytemplate' type='text/ractive'>
    <p>Value: {{value}}</p>
    <button type="button">Toggle</button>
</script>

How do I call MyViewModel.toggleValue function when the button is clicked? For now I know two options:

  1. I can add on-click='toggle("value")' to button element. However, let's assume that my toggleValue function may be somewhat more complicated, so this solution is not enough.
  2. I can add on-click='toggleValue' to button element. But then I need to add this code:

    ractive.on('toggleValue', function () { viewModel.toggleValue(); });

which means that:

  1. I am writing code that has to know about my view model outside this view model. This is bad from encapsulation perspective.
  2. It also means that toggleValue is global for Ractive instance and not view model specific which means maintenance issues sooner or later.
  3. It is not in any way better than using jQuery $("button").click(myFunction).

So how do I do it correctly? I need equivalent of KnockoutJs' data-bind="click: toggleValue".

EDIT: For working code have a look here: http://jsfiddle.net/xak5k8rd/


Solution

  • You could create that type of syntax by adding a prototype method to Ractive:

    Ractive.prototype.bind = function(context, method){
    
        // if using < 0.6.1 you may encounter bug {{this}} or {{.}} not returning model root
        //context = context || this.data
    
        context[method].call(context, this)
    }
    

    then consume in your template as:

    <button type="button" on-click="bind(this, 'toggleValue')">Toggle</button>
    <ul>
    {{#tab}}
        <li on-click="bind(this, 'click')">{{val}}</li>
    {{/tab}}
    </ul>
    

    See http://jsfiddle.net/obdk4k2o/2/

    EDITED: Added context to bind function. Note any template model value could be passed, but this is certainly handy for lists.

    EDITED: In 0.6.1, method calls have access to this.event meaning the keypath is available as well as the context (so it doesn't need to be passed in).

    The method becomes:

    Ractive.prototype.bind = function(method){
        var context = this.event.context,
            keypath = this.event.keypath
        context[method].call(context, this, keypath)
    }
    

    Template is:

    <button type="button" on-click="bind('toggleValue')">Toggle</button>
    <ul>
    {{#tab}}
        <li on-click="bind('click')">{{val}}</li>
    {{/tab}}
    </ul>
    

    And in your viewmodel:

    self.click=function(ractive, keypath){
        ractive.set(keypath + ".val", !self.val);
        alert('click');
    }
    

    See http://jsfiddle.net/obdk4k2o/6/