ViewModel 2 - A new level of simplicity

Point #1 looks to be a matter of style choice, I’d certainly not mix or match declarations over events within a view. Provided you stick to one for whatever reason it should be good.

Don’t worry im not someone who parrots separation of concerns all day however I do think #2 is valid for the other way round too. If a designer changes the markup they can introduce a bug in the bindings. Tests should cover both occurrences.

Point #3 is a super nifty example thanks.

Point #4 sure makes sense.

Either way allowing the event hash is fantastic. It makes cutting old blaze “views” over to viewmodel super easy and provides some breathing room for developers to get used to handling views within the templates.

Perhaps i’ll slowely start using the binding helper once i get all my regular views cut across to viewmodel.

Thanks!

If a designer changes the markup they can introduce a bug in the bindings. Tests should cover both occurrences.

Now the designer sees <div class="lorem ipsum">, looks into the styles and decides he doesn’t need the ipsum class anymore or it’s better moved to another element. And we’ve got our event attached to it.

Yeah but same happens on the other way round.

Developer looks into the view and removes the blahBlah method because its unused. The template still refers to it.

Either approach isn’t immune to orphaning. Should be covered by tests though in both cases.

1 Like

And we’ve got our event attached to it.

And that’s why you never attach events (logic) to style code. Instead, you use the data attributes.

<div class="lorem ipsum" data-triggers="contextmenu">

:wink:

1 Like

Hey guys,
maybe someone here can help me. I’m running into a context problem with VM, when using a Mongo transformation or the Collection Helper plugin. Just for example, I define this helper:

 Followers.helpers({
    userDetails: function() {
        return Meteor.users.findOne(this.followerId);
    }
});

Now I’m doing the following code:

#each follower
 >followerTemplate
/each

I define my viewmodel like this:

Template.followerTemplate.viewmodel({
  userDetails: function() { return this.userDetails(); }
});

This will throw an error, that “this.followerId” isn’t defined (take a look back in the Collection helper). If I change my collection helper to this, it works:

Followers.helpers({
userDetails: function() {
 return Meteor.users.findOne( this.followerId() );
 } });

As you can see, I’ve to call “followerId()” as a function now, because “this” is now the context of the ViewModel. So is there any possibility to overwrite this behavior?

@ahref

Listening for events makes your apps more brittle because you’re increasing the coupling of your application without getting any added benefit from it.

The relationship between the view (markup if you will) and the view model (code) is the same as that of an application and the services it consumes. They’re bound by a contract which only one party is obliged to honor. The view model and the back end services have to be the adults and ensure they’re not breaking the contract. On the other hand, the view and the application are these frivolous teenagers who can change all they want. All they need to know is that the view model and services will have the functionality they expect, should they decide to use it.

So when you declare events the view depends on the view model for it to work correctly but the view model doesn’t depend on the view. When you listen for events in the view model, the view still depends on the view model (it’s still the engine of the view) and now the view model itself depends on the view too.

One of the assertions of ViewModel is that it makes testing a whole lot easier so lets defend that claim =)

Given the following view model:

Template.main.viewmodel({
  name: '',
  canEnter: function() {
    return !!this.name();
  }
});

Here’s how you test it:

describe("Main View Model", function() {
  var vm;
  beforeEach(function () {
    vm = Template.main.createViewModel();
  });
  it("should have default properties", function () {
    assert.equal(vm.name(), '');
  });
  describe("canEnter", function() {
    it("should return false when name is empty", function () {
      vm.name('');
      assert.isFalse(vm.canEnter());
    });
    it("should return true when name is not empty", function () {
      vm.name('Alan');
      assert.isTrue(vm.canEnter());
    });
  });
});

This way you can easily test every single part of your code, even the methods which execute because of events.

You can also test your views without end to end tests. If you have the following template:

<template name="main">
  Name: <input type="text" {{b "value: name"}}>
  <button {{b "enabled: canEnter"}}>Enter</button>
</template>

This is how you test it:

describe("Main Bindings", function() {
  it("should bind name input", function () {
    var bind = Template.main.elementBind("input");
    assert.isTrue(_.isEqual(bind, { value: "name" }));
  });
  
  it("should bind enter button", function () {
    var bind = Template.main.elementBind("button");
    assert.isTrue(_.isEqual(bind, { enabled: "canEnter" }));
  });
});

We’re testing the view but you have a lot of wiggle room to modify it. You can add and remove elements and move them around, so as long as there’s an input bound to name and a button that triggers canEnter.

The idea is that if your code is working and the bindings are in place, then you don’t have to go through all permutations when you perform your user tests with Selenium.

You have a problem with this in the helper. I’ll answer it later (don’t have the time now).

Sure I understand it all. I just need some breathing room to cut over. I don’t like the bind helper but get why its cool.

With the events hash still being an option I can cut views that are using vanilla blaze across a bit easier. In a hurry I can use the events hash and continue to make a functional template.

For a brand new project that i’m using to learn sure I can use the binding helper. Especially once ive gotten used to it.

I guess what I’m trying to say here is thanks for providing both options. I’ll use whichever is suitable for the project in question and re-evaluate things when moving into a new project.

Thanks for a cool piece of kit.

2 Likes

Hi @manuel , I have a use case, not sure if it’s also related to “this”

I have a language switcher in .jade

template(name='i18nSwitch')
  span#en($b="click: setLang") English | 
  span#zh-HK($b="click: setLang") 中文繁體 | 
  span#zh-CN($b="click: setLang") 中文簡體

VM in .ls:

Template.i18nSwitch.viewmodel do
  setLang: (e)->
    TAPi18n.setLanguage(e.target.id)

A few questions:

  1. I tried to use “this.id” as param in setLanguage, but obvious not worked. Is there a simpler / more direct way than e.target.id ?
  2. What if I want to pass parameter from the binding ? Can’t find function binding with parameter in the doc so far.
  3. For my use case, what’s the best way to apply a style class to the selected span and unset the others?

I’m trying to twist my code in the VM way. Thanks :smile:

In your case it’s just a matter of moving the id from the element into the bind call:

template(name='i18nSwitch')
  span($b="click: setLang('en')") English | 
  span($b="click: setLang('zh-HK')") 中文繁體 | 
  span($b="click: setLang('zh-CN')") 中文簡體
Template.i18nSwitch.viewmodel do
  setLang: (langId)->
    TAPi18n.setLanguage(langId)

That said, I would normally put the list of languages in a collection and loop through them:

template(name='i18nSwitch')
  each languages
    span($b="click: setLang(this.id), text: this.language")

2 Likes

Thanks for quick hint.

By the way, for some weeks, I find that the sign in with Meteor Account of the VM board doesn’t work: https://viewmodelboard.meteor.com. I have to use email sign in.

I had the same problem.

I don’t see why not. Please make a repro.

Use v2.7.5

Let me know if it doesn’t work.

How I love the dependency resolution game…

Try with 2.7.6

Yeah, let’s take it to github.

I see a testing example in post 47, but would you add the testing part to the docs pls?

Eagerly waiting!

Could you clarify this pls?

As it turns out I’ve been working on the phone book example. Check out https://github.com/ManuelDeLeon/phonebook

I still have to update the docs though (working on another area of the docs).

About JS elements, sometimes you have a library that transforms something like <div id="A" /> into something complete different (say <span id="A"><input ></span>). In that case you don’t have access to the markup at design time to apply the bindings. In that case you have to use good old events to capture changes on the generated input.

2 Likes

In the docs, in children demo the replaceNames method calls children viewmodels, ie vm for the person template.

replaceNames: function() {
var children = this.children();
for(var i = 0, len = children.length; i < len; i++) {
  var child = children[i];
  child.name( Math.random() );
}

But template.person.viewmodel is not defined in example code. Do child templates have automatically created vms or is it simply implied in this example, that they are defined somewhere along in the code?