ViewModel 2 - A new level of simplicity

MDG folks showed an interest in moving it to free Galaxy account, you won’t be interested?

Of course, but I only have 2 weeks to do something so I have to operate under the assumption that I have to use my own servers.

For now I’ll just use the money from the videos to pay for the hosting.

1 Like

When you say

using Viewmodel properties in Autoform hooks

do you mean like the following? This is simplified code from updating a forum post.

Template.myTemplate.viewmodel({
  isLoading: false,
  formId: null,
  onCreated() {
    this.formId(`myTemplate_form_${this._id()}`);

    AutoForm.hooks({
          [this.formId()]: {
            beginSubmit: () => {
              this.isLoading(true);
            },
            onSuccess: () => {
              this.parent().isFormVisible(false);
              Meteor.promise('forumPosts/getPost', this._id())
                .then(post=>{
                  this.load(post);
                })
                .done(()=>{
                  this.isLoading(false);
                });
            }
          }
        }, true);
      }
});

In the template:

{{#autoForm collection="Collections.forumPosts" id=formId type="update" doc=this}}
4 Likes

@nathantreid LOL I can’t believe it was so easy.

Turns out, I had no idea you can put Autoform.hooks directly into viewmodel. I always put my hooks outside of template code, so I had problems to find out how to call the viewmodel from them.

Thanks a lot!

1 Like

What am I missing here? The label’s outside an each loop reactively update fine but inside a loop it does not.

<template name="demoVM">
    <input {{b "value: title"}} type="text">
    <label {{b "text: title"}} ></label> <!-- DOES UPDATE ON VALUE CHANGE -->
    <ul>
    {{#each array}}
        <li>
            <input {{b "value: this.item"}} type="text">
            <label {{b "text: this.item"}} ></label> <!-- DOES NOT UPDATE ON VALUE CHANGE -->
        </li>
    {{/each}}
    </ul>
</template>

What happens if you do

<label>{{this.item}}</label>

Good catch. Pick up v4.0.1

Thanks for the help

You’re probably modifying the content of an array object (e.g. array[0] = 'newItem'). In that case you’re not triggering a reactive change.

Please make a repro so we’re on the same page.

I get the same results. Thanks though.

Will do…

Btw below is my viewModel. Are you saying that arrays are not reactive?

Template.demoVM.viewmodel({
    title: "hello world",
    array:[
        {item:"foo"},
        {item:"bar"}
    ]
});

Here we go: https://github.com/7immy/viewmodel-demo :slightly_smiling:

No, normal arrays aren’t reactive. You have to use the reactive array package, it has been also developed by a guy called @manuel :smiley:

Ah, didn’t know that! Thanks :slight_smile:
I’ll take a look!

Think I found it: http://reactivearray.meteor.com

Yep, that’s the package :slight_smile:

Okay, now I understand.

(in ViewModel) Arrays are reactive for the items in the array, not the properties of the items themselves. So array.push({}) will trigger a reactive change (it’s changing the array itself), but array[0].item = {} won’t trigger a change in the array (the array stays the same, no items have been added or removed from it).

In your case you’re binding to a non-reactive property of an item in the array. Nothing happens when it changes because it doesn’t trigger a change event (it’s not a reactive source).

If the array won’t change you can do the following:

<template name="demoVM">
    <input {{b "value: title"}} type="text">
    <label {{b "text: title"}} ></label>
    <ul>
    {{#each array}}
      {{> arrayItem }}
    {{/each}}
    </ul>
</template>

<template name="arrayItem">
  <li>
    <input {{b "value: item"}} type="text">
    <label {{b "text: item"}} ></label>
  </li>
</template>

In this case arrayItem will inherit all the properties from each object in the array.

I say “if the array won’t change” because here you’re effectively creating two sources of truth: the array and the arrayItem view models. If you need to add/remove elements from the array AND be able to modify the items themselves, then you’re better off using a client side collection, not an array.

Hello @manuel ,

Thank you for taking the time to look at that for me! I hope you don’t mind if I quiz you again but I was wondering if you could help me further.

You last talked about using collections instead if the array needs to be added, deleted, sorted, changed etc.

I gave this a shot but I’m a little stuck as I can’t work out a few things:

  • I’m not sure how to update the collection when changing/editing an input value. Editing an input shows no effect when I run ‘this.data()’.
  • I’m not sure how to best remove an item from a collection through an event.
  • I’ve added a {{>Template.dynamic}} to the template “arrayItem” and while this works I get error messages related to the inputs in the dynamic templates.

Here is a branch of the repo: https://github.com/7immy/viewmodel-demo/tree/dynamic-form

Thank you for your time and help again!! :relaxed:

The thing is that you’re putting objects of different types in the same collection and that’s firing all kinds of alarms in my brain.

Are you sure you can’t have a collection for foo and another for bar? Can you give a more realistic example?

Hi @manuel,
Thanks for replying! Basically I’m trying to re-create this input form type from a wordpess plugin. It’s called a flexible content field. The input field contains flexible sub fields which allow you to create an infinite set of sub field groups (called layouts). With these layouts predefined, you can then add them into your field whenever you want and wherever you want.

If you take a look at the video on the page and go to about 2:43mins you’ll see it in action (notice the predefined layouts in the dropdown menu). It’s a really good way to create customised page layouts. I’ve tried to recreate this in AutoForm and Template:forms and had no luck. Now I’ve just discovered ViewModel and I’m hoping things turn out better!

You’re on the right path. The following should give you some ideas. btw, please don’t use event maps, they’re an eyesore with ViewModel.

<head>
  <title>viewmodel-demo</title>
</head>

<body>
  {{> demoVM}}
</body>

<template name="demoVM">
    <input {{b "value: title"}} type="text">
    <label {{b "text: title"}}></label>
    <br />
    <button {{b "click: addFoo"}}>Add Foo</button>
    <button {{b "click: addBar"}}>Add Bar</button>
    <ul>
    {{#each collection}}
        {{> arrayItem}}
    {{/each}}
    </ul>
    <button {{b "click: submit"}}>submit</button>
</template>

<template name="arrayItem">
    <li>
        <hr />
        <button {{b "click: delete"}}>Del</button>

        {{>Template.dynamic template=item data=itemData }}
        <hr />
    </li>
</template>

<template name="foo">
    <div>
        <div>Foo Item:</div>
        <input {{b "value: title"}} type="text">
        <input {{b "value: alignment"}} type="text">
      <button {{b "click: save"}}>Save</button>
    </div>
</template>

<template name="bar">
    <div>
        <div>Bar Item:</div>
        <input {{b "value: text"}} type="text">
        <input {{b "value: chocolate"}} type="text">
      <button {{b "click: save"}}>Save</button>
    </div>
</template>
if (Meteor.isClient) {

  localColl = new Mongo.Collection(null);
  localColl.insert({item:"foo", itemData:{title:"hello moon", alignment: "left"}});
  localColl.insert({item:"bar", itemData:{text:"What's your favorite bar?", chocolate:"Twix"}});
  localColl.insert({item:"foo", itemData:{title:"hello sun", alignment: "top"}});

  Template.demoVM.viewmodel({
    title: "hello world",
    collection() {
      return localColl.find();
    },
    submit(){
      alert("Submit!");
    },
    addFoo(){
      localColl.insert({
        item: 'foo',
        itemData: {
          title: '',
          alignment: ''
        }
      })
    },
    addBar(){
      localColl.insert({
        item: 'bar',
        itemData: {
          text: '',
          chocolate: ''
        }
      })
    }
  });
  
  Template.arrayItem.viewmodel({
    delete() {
      localColl.remove({ _id: this._id() });
    }
  });

  ViewModel.mixin({
    saveProps: {
      save() {
        localColl.update(this.parent()._id(), { $set: { itemData: this.data() }});
      }
    }
  })

  Template.foo.viewmodel({
    mixin: 'saveProps'
  });
  Template.bar.viewmodel({
    mixin: 'saveProps'
  });
}
3 Likes

Wow! This is amazing! I can see why you split up the sections now, it definitely makes sense!

Thank you so much @manuel, its really appreciated! :grin: