ViewModel 2 - A new level of simplicity

Assuming you can’t pass the edit mode condition to the post template, create a shared value between the list and the modal to indicate the edit mode.

Btw, my point about the autorun is that if you think about it, if you have an autorun which gets invalidated when a template is created and then again when it’s rendered, that is the functional equivalent of calling a function from onCreated and onRendered.

In that case I will have to garbage collect it (set to false) when modal is closed - which is a nuisance.

Suppose, I want to have a central state controller which controls all templates that are rendered. I could create rendered-templates store using VM.share, but it would be more convenient if we could simply receive a signal from a template when it’s rendered.

I really don’t understand the problem. You set the edit mode shared value to whatever it needs to be right before displaying it. Nothing to manage.

Manuel, I dont remember if asked this already - but what’s the overhead of Viewmodel on browser performance - if any?
I mean with all the reactivity and stuff…
Say I have a 100 templates in dom, each of them has a VM attached. Will there be any difference with and without VMs?

It’s negligible. A template/view model with hundreds of bindings adds a ~3-5ms to the initial render.

1 Like

manuel, is this solution still viable? I tried it, but the text of label are not updating.

Yep, the project is alive and kicking. Show me what you’re doing.

Manuel, thanks for this awesome VM package. I am loving it.

My questions is what if a property is an plain object, something like this:

Template.demoVM.viewmodel({
    stock: {
        name: 'meteor'
    }
});

<template name="demoVM">
    <input {{b "value: stock.item"}}>
    <label {{b "text: stock.item"}} ></label>
</template>

Given that the stock object is not a reactive data source itself, changes in the input field will not trigger a view update in label.

Is it possible to use ReactiveDict to solve this problem? ReactiveDict uses get and set methods, so I couldn’t figure out how to make it work with VM’s bindings.

The solution I did come up with is using the properties descriptors like this:

function Stock () {
    var self = this;
    self._item = 2
    Object.defineProperty(this, 'item', {
        get: function () {
            objTracker.depend();
            return self._item;
        },
        set: function (value) {
            objTracker.changed();
            self._item = value;
        },
    })
}

Template.demoVM.viewmodel({
    // stock: new Stock();,
    stock: {
        item: 2
    }
});

But it seems a lot of boilerplate involved, especially when there is a lot of properties involved. I am wondering what you would do in this kind of situation?

The thing is that what you have there doesn’t make sense from a view model perspective. A view model is the object representation of the view. What you have there is an object that contains an object which represents the view.

Create a view model for stock.

I guess that overly simplified example that did a very poor job of illustrate the need of it.

What I am trying to implement is a form that collects monthly data for multiple years. One import function I want to implement is the ability to duplicate the input from previous year, so I designed the viewmodels like this:

Template.form.viewmodel({

    formFields: [{
        year: 2016,
        data: {
            jan: 2,
            feb: 5,
        }
    }, {
        year: 2017,
        data: {
            jan: 0,
            feb: 0,
        }
    }, {
        year: 2018,
        data: {
            jan: 0,
            feb: 0,
        }
    }],

    duplicate: function (year) {
        const index = this.formFields().findIndex(function (elem) {
            return elem.year === year;
        })
        this.formFields()[index].data = _.clone(this.formFields()[index - 1].data)
        console.log(this.formFields())

    }
})

<template name="form">
    {{#each formFields }}
    <div class="row">
        <span {{b 'text: this.year'}}></span>
        {{#with data}}
            <input {{b 'value: this.jan'}}>
            <input {{b 'value: this.feb'}}>
        {{/with}}
        <button {{b 'click: duplicate(this.year)'}}>Duplicate</button>
    </div>
    {{/each}}
</template>

while I can verify from the console.log that the formField did gets updated after the button click, the view was never updated because of the same reason in my previous example, a non-reactive data source.

I have some similar use case. To make binding auto update. You’ll need to set the top level key. e.g.:

  stock({key1:value1,key2:value2})

For the other way round, consider autorun.

This is a common problem where you have two sources of truth. Here you have the array and the #with data. The most common thing to do is to use the top most data source as the source of truth and update that. In your case you should be updating the formFields array.

Do you mean by doing this instead?

<input {{b 'value: parent.data.jan'}}>
<input {{b 'value: parent.data.feb'}}>

No. Like daveel said, you update the whole array element.

Hey @manuel, are you still working on new videos about ViewModel? :slight_smile:

Yeah, I’m going to get back into making one per week. I was pouring all my effort into the React version of ViewModel but I’ll pace myself there and give time to other things like the videos.

Manuel, I got the idea, somewhere along the way, that ViewModel is a variant of VueJS, or viceversa, Am I mistaken?

It’s all implementations of the same pattern (MVVM). You see it in Angular, Aurelia, Knockout, and many others like Vue and Riot. You can even see it in React if you squint a little.

1 Like

Yes, that’s clear.

I should have been more specific: is Vue.js a fork of ViewModel (or viceversa)? or is there no overlapping code at all? I heard about ViewModel first, then Vue.js, and thought perhaps you had abstracted VM out of Meteor, and renamed it Vue.