ViewModel 2 - A new level of simplicity

@charliecl

You can put any event inside {{b "someEvent: someMethod"}}. In fact, there is no click bind. When you do {{b "click: sayHello"}} the click is assumed to be the event (because there isn’t a click bind) and is attached to the element with sayHello as callback.

I think this is what you mean by an echo example:

<body>
  <input type="text" {{b "value: message" }}>
  <p {{b "text: message" }}></p>
</body> 
Template.body.viewmodel({
  message: ''
});

If there is one idea that drives the design of ViewModel, it is this:

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.

-Antoine de Saint-Exupery

1 Like

In fact, there is no click bind. When you do {{b “click: sayHello”}} the click is assumed to be the event (because there isn’t a click bind) and is attached to the element with sayHello as callback.

In fact this had me going blindly through the documentation in order to find a chapter about events. Ultimately I found the explanation, but It would be nice to define it as a separate chapter of Bindings for new users, even if it’s just 2-3 lines of “don’t worry, all events are there by default”.

1 Like

Hm, ok, maybe I am naive but I always gravitated to the solution where I could get from A to B with writing as little code as possible. I think current blaze with ViewModel is a rather smart and elegant way and doable for people who don’t have that much time to sink into a computer because you’ve got a job, a kid, etc.

I was doing Django for a few years before Meteor and left because it got to fat and complex to work with - also the entire community got hang up discussing tiny things for ages, progress wasn’t possible anymore.

I kind of feel the same when I read this, initially Meteor seemed to be something tiny but smart and agile. This idea with react is probably a good one (not enough of an expert to tell) but from a weekend programmer point of view it seems I’ll end up with yet another hippo really soon now and thus have to look out again for something less complex…

Of course, if you have 20+ hours a week to spend then Meteor is probably going to be the go to thingy for you, even more so after that react plan springs into existence. For people like myself with a day job and kids, blaze and ViewModel is something that attracts me because it’s within my time frame I can invest. I look at the react site, the code examples, scroll down… pause a little and leave, never come back. To much, to complex.

3 Likes

@manuel By the way: Will there be a larger documentation for ViewModel 2? I’m just wondering, because the doc of ViewModel 1 was more extended.

//E: Ah I see, there is now an older link to the V1 doc :slight_smile:

@brajt
I must have missed it. There should be a section that explains it. I’ll update the docs.

@XTA
The new documentation covers almost everything the old one did. To my knowledge the only thing missing is events and how to test view models. What are you missing?

@manuel how does ViewModel work with models returned from collection finders? Do you have any examples combining the two. Your current examples only show essentially static models, in other words “view models” lol.

For example we want to do this:

<template name="Parent">
   {{#each posts}}
      {{> PostViewModel}}
   {{/each}}
</template>

<template name="PostViewModel">
   <h1>{{title}}</h1>
   <p>{{body}}</p>
</template>

Template.MyViewModel.viewmodel({
   //what goes here? 
   //specifically what enhnacements could go here for dealing with dynamic data/collections.
});

Perhaps to get the real benefits we need to be working with forms and event handlers??

<template name="EditPostViewModel">
   <input value="{{title}}" />
   <textarea>{{body}}</textarea>
   <button>SAVE</butt>
</template>
Template.MyViewModel.viewmodel({
  'click button': function() {
      //basically, you're reaching into the DOM via the old approach here:
      this.save({title: $('input').val(), body: $('textarea').val()); //our models have a `save()` method obviously

      //ideally it would be something like this:
      this.populate().save();

      //or simply:
      this.saveFromForm();

      //or perhaps yours looks closer to something like this:
      this.save({title: this.title() body: this.body()}); //though the `title` method conflicts with a prop of the same key

      //so maybe this:
      this.save({title: this.viewmodel.title() body: this.viewmodel.body()}); 

      //or:
      this.model().save({title: this.title(), body: this.body()});

      //or:
      this.save({title: Template.viewmodel().title(), body: Template.viewmodel().body()});
  }
});

So basically the goal is for your automatic bindings to continue on to true models. The above event handler is full of contrived examples obviously–what’s the best practices way to accomplish that with ViewModel 2? I guess the real question would be how to make the bindings for methods such as this.title() to the true model. And is having to make those bindings really that much of an improvement over giving your model a method to suck values out of AutoForm inputs on the page?

The following seems a lot simpler for the reality where when you’re dealing with inputs it’s 85% of the time for collection models:

this.populate('formName').save(); //formName is optional; if not provided, `populate()` will find the first AutoForm on the page

and if you only wanna do one field:

this.populateField('title').save();

Obviously it’s assuming you’re using something like AutoForm and built the simple populate() method into your model class, which couples the model nicely with AutoForm.

You may say populate() is extra work. But for any medium-level application in complexity, it’s a necessary evil. It’s a feature rather. Why you may ask? Because who says you want to save whatever the user enters all the way to the database?? You very often need the old values on the model to compare, or perform various options (perhaps use the old value, if the new value doesn’t suffice). So serious applications don’t want to just take what’s in the form without the option to maintain the old data. In short, populate() is a necessary “evil,” and a 10 character evil at that–so it’s no big deal.

I’m having a hard time seeing how ViewModel isn’t for anything but the most trivial of use cases where all the state is purely client side, or rather, when all the state is client side and doesn’t deal with collections (you can have client side only collections obviously). It just seems like any gains you would have gotten from ViewModel you spend marshalling data back and forth from a true model to a view model. What you lose by using a true model instead of ViewModel you make up with simple features like populate and the popular AutoForm package. Overall, it’s simpler, but perhaps more importantly, it’s more standards-based, i.e. what developers are already used to. They don’t have to learn a specialized system. I’m not against learning specialized systems, but it needs to come at substantial gains.

That all said, I love your package, tell me what I’m missing because surely there must be a way in ViewModel that trumps the model-based AutoForm standard solution I provided.

@faceyspacey

I think you’re confusing the M in ViewModel with the M in MVC. A view model is “the model of the view” and it’s not meant to represent your domain entities. It’s meant to represent the state of your view and the actions that can happen on the view.

This is the same pattern used by Angular, Knockout, Aurelia, and many other libraries and frameworks. Hey, you don’t even have to squint too hard to see it in React too. I didn’t invent this wheel, I just made it aerodynamic =)

ViewModel uses Meteor’s reactivity so any method is reactive as long as it uses a reactive source. I was once accused of “cheating” because the the code required to do anything with ViewModel was so small only because it was made specifically for Meteor. My answer to that is “guilty as charged”. I take full advantage of everything Meteor has to offer in order to get rid of all the ceremonial crap you have to do in other libraries and frameworks.

In your scenario you just have to return the results of a collection and then use it wherever you need to. For example:

<template name="postList">
   {{#each posts}}
      {{> postItem}}
   {{/each}}
</template>

<template name="postItem">
   <h1>{{title}}</h1>
   <p>{{body}}</p>
</template>
Template.postList.viewmodel({
  posts: function() {
    return Posts.find();
  }
});

btw, unless you’re dealing with elements generated by javascript, you rarely (if ever) have to declare event listeners.
As for what to save back to the database, that’s entirely up to you. Again a view model isn’t concerned about your business entities, only the state of view and the actions that can happen.

Let me know if you have questions or need me to clarify anything.

2 Likes

so basically you’re saying that using standard models (provided you have a proper Model library and are using something like AutoForm) won’t get much benefit from ViewModel, but where ViewModel excels is when you want to represent a form and don’t plan to save it to the database–perhaps collect information in a contact form and trigger sending an email on the server. In other words, in what other frameworks are called “Form Model”??

You can save your forms to the database but ViewModel isn’t a code generator. You are responsible for saving your data. If you’re looking for autoforms, this isn’t it. You can use autoforms with ViewModel though.

Another way of thinking about it is, would you expect Blaze or React to save a form to the database for you? No, they’re only concerned with the view.

1 Like

Are the 3 examples Leaderboard, Contacts and Phonebook from the V1 documentation available for ViewModel2 ?

Hmm, I like this.

But i do have one complaint.

I like the events hash in vanilla blaze templates. Adding weird syntax to templates just doesn’t fit in my books. Especially naming the function that should be called.

If you allowed both options it would be perfect:

Template.greeting.viewmodel({
  firstName: '',
  lastName: '',
  agree: false,
  fullName: function() {
    return this.firstName() + ' ' + this.lastName();
  },
  greet: function() {
    alert( "Hello " + this.fullName() );
  },
  canGreet: function() {
    return this.firstName() && this.lastName() && this.agree();
  },
  clear: function() {
    this.reset();
  },
  events: {
    'click #greet':greet 
  }
});

I get that it avoids ids etc but I really dont like complexity creep in any templates and i can see the event binding getting unmanageable. React seems to have this done better using standard-ish/familiar html attributes onChange={this.updateFirstName} so that they are somewhat separated from each other in the tag.

I wish it were that easy. But right now, I don’t have the time or energy to do a big refactor like that. If somebody wants to work on this together though, I’d be happy to talk more about it!

2 Likes

I added the leaderboard example: https://viewmodel.meteor.com/docs/examples#leaderboard

I’ll add the contacts example later on. The phone book will have to wait a bit because it’s bigger.

1 Like

You can do that right now:

<template name="greeting">
  <input type="text" {{b "change: updateMessage"}} value={{message}}>
  <button id="alertMessage" >Alert Message</button>
</template>
Template.greeting.viewmodel({
  message: '',
  updateMessage: function (event) {
    this.message( event.target.value );
  },
  events: {
    'click #alertMessage': function() {
      alert( this.message() );
    }
  }
});

I do have a few thoughts (When do I not???):

  1. Notice how you think that the input change event should be declared (change: updateMessage), but the click event should be handled with an event listener ('click #alertMessage'). I prefer all events to be declared.

  2. Adding event listeners is less flexible than declaring them. This is not a matter of “separation of concerns”. When your code depends on the markup, you can introduce bugs much more easily because if you change the markup you need to check if you’re breaking the code too.

  3. There is absolutely no value in making developers add manual 2-way bindings. It’s just more code that can break or be wired wrong. Angular 2 tried that and then backtracked. For example:

<template name="greeting">
  <input type="text" {{b "change: updateMessage"}} value={{message}}>
</template>
Template.greeting.viewmodel({
  message: '',
  updateMessage: function (event) {
    this.message( event.target.value );
  }
});

The above provides no added value over the following:

<template name="greeting">
  <input type="text" {{b "value: message"}} >
</template>
Template.greeting.viewmodel({
  message: ''
});

And let’s say you need to do something with the input value as it’s changing. You can do just that:

<template name="greeting">
  <input type="text" {{b "value: message, change: messageChanged"}} >
</template>
Template.greeting.viewmodel({
  message: '',
  messageChanged: function() {
    // Do something
  }
});
  1. Declaring events and using bindings make testing much easier and less brittle. You can test your view (the bindings) and your full code (the view model). I’ll give an example tomorrow (it’s late).
1 Like

Let’s talk when the dust settles and we know what’s going to happen with Blaze 1, Blaze to React, and React. I’d be happy to work with you.

1 Like

So you’d recommend change: messageChanged over autorun?

In that example it doesn’t really matter because you would get the same effect either way. In general you use ‘change’ if you’re tracking changes in the input element iself. You use autorun if you’re tracking changes in one or more properties or functions.

So for a single input/property, what are focusing on, a change in the element or a change in the property? theres a suttle difference.

1 Like

Another way of thinking about it is, who decides what happens when a change occurs, the UI (eg. shake that other element when this one changes) or the code (eg. Add a timestamp and log the value as its changing)?

1 Like

Thanks a lot for clarifying!