Presenting a new ViewModel package

I’ve written a new viewmodel package for Meteor, inspired by manuel:viewmodel and nikhizzle:session-bind:

The point of using viewmodels in Meteor is to reduce the amount of code and make things more declarative. In some cases, pretty radically so.

My reason for writing a new package was that I felt things could be simplified quite a bit, while preserving the full power of the VM pattern. I believe I’ve accomplished my goal, but at the same time have added some useful features.

I’m working a bit closer with Blaze, compared to manuel:viewmodel, in the way that everything is registered as helpers, and instead of having bindings such as class and text, you are encouraged to use helpers in the standard way.

Importantly, bind declarations work inside #if and #each helpers.

Among other features are:

  • Persist viewmodel state across hot code push
  • Optionally persist viewmodel state across navigation (new route and then back button)
  • Optionally keep instances of the same viewmodel in sync on the page
  • Very easy and declarative bindings API
  • Automatic creation of undeclared viewmodels and properties
  • And more, as they say…

This package showed out to be a great fit for ES2015, by the way, which I highly recommend using.

Check it out and give me your opinions :dancer:

5 Likes

I really like the concept of manuel:viewmodel however the one thing that really tripped me up is that the binding is done once, after the rendering of the template as explained here: http://viewmodelboard.meteor.com/posts/rrpagb6DnciFQde8J/f-a-q
This effectively renders all of my #if, #each, #with code with binding inside unable to use viewmodel and forces me to declare templates for even the most trivial case.

Does your package allow the following:

<template name="example">
  <input type="text" {{bind 'value: text'}}>
  <input type="checkbox" {{bind 'checked: show'}}>

  {{#if show}}
    <button {{bind 'click: clickEvent'}}>{{text}}</button>
  {{/if}}
</template>

//js
Template.example.viewmodel({
  clickEvent: function () {
    console.log('called even if show is false sometimes');
  }
});

Sorry, I had a quick look through your doco but wasn’t able to convince myself for sure this worked.

Edit: The syntax doesn’t seem to work with jade

+if show
  button({{bind 'click: clickEvent'}}) {{text}}
//or
  button {{bind 'click: clickEvent'}} {{text}}
//or
  button("{{bind 'click: clickEvent'}}") {{text}}

You get various errors with jade compiling, or html not recognising bind as an attribute etc…

I’m happy to say that it does.

In regard to Jade, it apparently works with the $dyn attribute, but I wonder if a prettier solution couldn’t be found.

button($dyn='{{bind "openModal: myModal"}}')
1 Like

I’ve improved the quick-start example in the docs and added a Jade section.

About a week ago I decided I’ll start using viewmodels from now on for the various benefits over the current standard in Meteor. Now there are two potential candidates, which one to choose going forward? A feature comparison and pro vs cons comparison would be good, it would also help both packages figure out their weaknesses and thus room for improvements.

Long-term maybe we could end up with one package for viewmodels combining the best of both worlds. Having one package is always good as it doesn’t split the community but rather paves the way to having one through and through tested package.

Could you maybe be persuaded to do the community a favor and try out both packages, and then give your opinion? :wink:

I totally agree that it’s better to have a standard library than two competing ones. Only, sometimes a fresh perspective is good for moving things forward.

My package is greatly inspired by manuel:viewmodel, but my decision to write it (which, to my actual surprise, only took two days – VM is a powerful pattern), came after spending a couple of weeks with manuel:viewmodel – I was having some challenges with it that I knew I could fix, but it would quickly become incompatible with the existing API.

At any rate, much respect to @manuel. His package still has a few features that aren’t in the scope of my package:

  • Reflect viewmodel state in location path
  • Bindings like text and class (since his package doesn’t use Blaze helpers as much)
  • ViewModel inspection panel for development

Of course, I welcome any input he might have for my package – maybe we can unite them again at some point. I know Manuel is very active in making tutorials and helpful stuff that can be applied to the pattern in general, which I think is great.

1 Like

Another point in regard to manuel:viewmodel, is that I try to be a bit more opinionated in the documentation.

There are basically many ways to apply the VM pattern, but I’ve focused the library on the ones that I personally find give the least fuss.

1 Like

Tested it, it does work.
I can also just swap out bits and pieces as I go, very flexible.
It is able to bind templates in +if naturally as claimed, which feels natural.
Excitement building…

The only unnatural thing is the syntax

//good god man! my eyes...! :)
input($dyn="{{bind 'value: email'}}")

When it comes to declarative events, blaze-magic-events is still the simplest, cleanest and most effective:

//magic events
input(onclick="{{ doSomething params }}")

//viewmodel
input($dyn="{{bind 'click: doSomething' params}}")

It looks like it might clean up a few of my template states which are solely for toggling, passing value to functions, so I’m going to continue exploring and see how I go.

Thank you for making the package!

The package is now on Atmosphere in version 0.5.3.

1 Like

I completely agree – the Jade $dyn attribute syntax is horrific. It’s been a while since I’ve used Jade, so any input on how to improve the situation is more than welcome. @rhjulskov @jakobloekke

The magic events syntax looks nice on the surface, but is limited to events – not two-way bindings – and only one event at a time.

Also, there’s a lot of power in creating custom bindings – using keyword parameters for them, for instance, can be very useful inside native block helpers. Here’s a gist for an openModal binding that I’m using – short and sweet.

Haha, I’ve been reading the code example you provided here and suddenly it vanished and I went “woah, where’s my code?”. It took me a while to realize that it’s meteor we’re talking about and it’s capable of doing such things.

As for the package… I’m personally happy that you did it even if there’s manuel’s ViewModel already and I’ll be trying it. I like your syntax better.

1 Like

I totally agrees about the Jade syntax.

I came around that solution because “&attributes” isn’t currently supported in meteor-jade: https://github.com/mquandalle/meteor-jade/issues/127

Ideally the bind command would be a custom attribute in the Jade compiler. But if that’s a possibility, I don’t know.

1 Like

Just pushed version 0.5.5 with a binding for radio buttons.

2 Likes

Hi @deb,
great package! I’ve been using manuel:viewmodel for a long time, but like
to have an alternative. The one thing I don’t like about manuel:viewmodel is
the idea of adding another layer on top of the existing template engine. This
has caused flickering problems for me in the past which still remain in Safari. I’ve started writing a package which converts <template>s with data-bind syntax to React, but using Blaze makes much more sense.

However, I do have some questions; hope you can answer them :slight_smile:

Flickering

Let’s add a really simple binding (great syntax btw):

ViewModel.addBinding 'visible',
  set: ($elem, newValue, args, kwhash) ->
    if newValue
      $elem.show()
    else
      $elem.hide()

CoffeeScript, but should be easy to understand. I don’t want to use Meteor’s {{# if}} here; using a visible binding doesn’t make much sense, but it’s just an example for another use-case. This code will cause flickering when the template has rendered. Another thing here: Please give us the key in set as well.

Alternative Syntax

I think it would be cool to allow an alternative syntax like

1. {{bind click='submit'}} // or even
2. {{bind click=submit}}

Should be easy to implement (just check arguments for instanceof Spacebars.kw I guess), but makes things even cleaner in my opinion. I’m just not sure if the second thing works for functions, or properties, which are not defined in a viewmodel (would they be undefined?).

CSS Classes

I personally prefer @manuel’s class: { green: selected } syntax over pure Spacebars syntax. It’s great that people can use helpers now, but writing a lot if {{# if}} statements or an additional helper just for the element to return an object of classes looks wrong to me. It would be great if you’d at least allow an object as an argument for bindings, so that we can write our own class binding. It would also flicker at the moment, but that shouldn’t stop us :slight_smile:

I had some more questions when I looked at your package yesterday, but forgot them :stuck_out_tongue: But again, amazing package!

Hey, thanks for your interest – and great questions. Keep them coming.

Generally speaking, the reason I like using Blaze instead of jQuery, is that elements (or attributes etc.) are completely ready when they are rendered. They don’t exist before, at all.

Flickering

… so flickering doesn’t become a problem.

Alternative syntax

I agree that using a string with a colon in it looks more artificial than a keyword argument.

Actually, I realize that I’ve been inconsistent in the way that bindings can have a detached option, but you can’t omit the property name from the bind expression without the library failing. I’ve corrected this for the next version.

Here’s the problem with using keyword arguments as bind expressions, though:

Every keyword argument would designate a binding, so we would lose the possibility of passing actual keyword arguments to the bindings.

This is a feature we can’t do without, since it is the only way to pass dynamic data to a binding (not just a string argument inside the bind expression). Which happens to be one of the major advantages of this package :smile:

Classes

I don’t want to parse objects inside bind expression strings. It’s too complex. Here’s my suggestion for having a class binding:

<div {{bind 'class' classes=classes}}></div>
// viewmodel
{
  red: true,
  classes: function () {
    return { red: this.red() };
  }
}

I’ll add this binding to the set and push version 0.5.6.

Oh, and about passing the key to set – I don’t want to do that, because I want to make it impossible to write back to the property inside set, since this would create an infinite loop.

You shouldn’t need the key inside set – the key is arbitrary. You shouldn’t write a binding in a way that needs to know the key (i.e. the property name).

If you really, really need the key inside set, it will be the first item in the args array. Don’t do it, though :wink:

Thanks for your response. I’ll just keep the old order here :slight_smile:

Flickering

It actually becomes a problem, when trying to write bindings, which initially hide an element. However, this seems to be a problem not specific to this library or Meteor, but jQuery and browser behavior. Looks like I need to work around by hiding the element using CSS at page load.

Alternative Syntax

I just noticed that it’s not possible to add more than one binding at once. Even if you disagree with the following, my vote goes for multiple bindings, separated by semicolon.

What I’m doing a lot at the moment is something like data-bind: click: _submit, class: { disabled: !_isValid }. So what I was hoping for is {{bind value=name disabled='!validName'}}. I don’t know where I’d need dynamic data for binding; and if I did, what’s wrong with using this[args[0]]? Should be dynamic as well.

Classes

Thanks for your suggestion, but we’d have to write a function for every element on the page where we want to use dynamic classes, instead of just adding that “logic” directly to the element.

Flickering

My opinion is, basically, that you shouldn’t use jQuery to hide an element after render. If you want it to be hidden instead of unrendered, because of some jQuery plugin logic, make sure it gets rendered with the correct class and/or settings.


Alternative syntax

I think I made a good argument about the syntax before, but if having multiple bindings – {{bind 'value: value' 'disabled: disabled'}} – is currently broken, I will fix it right away. EDIT: Fixed

In regard to dynamic data – the args passed to a binding’s set and get are hardcoded as part of the bind expression. They are any space separated strings that come after the colon – with the first item usually being a key. (N.B. before version 0.5.7, the key was omitted from the args array.)

{{bind 'myBinding: arg0 arg1 arg2 etc'}}

Dynamic arguments are used to interface with a component.

Let’s say I’ve written a widget – a filter search field. Now, this widget is used many places in the application, but in some instances I want the input to be throttled.

So, in those cases, I include my widget like this:

{{> myWidget throttle=500}}

And inside the widget template, this keyword argument is passed on to the binding:

<template name="myWidget">
  <input {{bind 'value: value' throttle=throttle}}>
</template>

Make sense? :four_leaf_clover:


Classes

I think that’s a fair call – to be able to just write the viewmodel properties that equal classes.

So I’ll extend the classes binding now to use its args, with the optional classes keyword argument still taking precedence.

So the binding can be used like this:

<p {{bind 'classes: red large hidden'}}></p>
// viewmodel
{
  red: true,
  large: false,
  hidden: true
}

Version 0.5.7 fixes the bug with multiple bind expression, improves the classes binding (as described above)

(It also contains a tiny API change, where the args argument now contains the key as the first item.)

1 Like

Version 0.5.8 is released with a tiny API change. The section in the docs for the classes binding has been updated.

(The actual viewmodel property (getter-setter) is passed to get instead of its key, because this is what I originally meant was the most correct interface, and the key now has become available as the first item in args.)

I’m changing to a new user after this post, btw.