ViewModel 2 - A new level of simplicity

@manuel, I’ve just stumbled upon https://atmospherejs.com/manuel/viewmodel-explorer
Is this some kind of debug tool? how do I use it? I kinda overlooked it in the docs (is it mentionned??)

1 Like

Yes it is a debug tool. Cool one to be clear.
It pretty much smillar to redux store view, except, it allows full control over viewmodel during development.
Such as setting inputs, data, props, firing events and etc. When you include it in your app, look for new UI el.

1 Like

I don’t remember the last time I used VM Explorer for Blaze so I don’t know if it even works anymore. My guess is that it doesn’t work for VM Blaze v2 (but of course I’ve been wrong before). The React version works similarly and you can see how it works at https://viewmodel.org by clicking on the paper plane icon:

May I ask what does the future of ViewModel for Blaze look like? Grim, aging gracefully or keep evolving? :slight_smile:

One way to look at VM for Blaze is as being part of the old Meteor stack. Blaze itself is pretty much frozen and so is VM for Blaze. VM is pretty much feature complete so it’s just a matter of fixing eventual bugs. So “aging gracefully” is the most appropriate term :slight_smile:

1 Like

Btw, Manuel - does VM feature set (by any chance) include a way to profile what triggered reactive update to vm property or caused vm method to rerun? I’d like to log that data for development purposes…

That’s a tough one. I don’t have a good answer. It’s technically possible for VM to log when properties are changed and also when functions rerun due to a change in their dependencies, but I’m not going to code that in any time soon.

Chrome’s async option helps sometimes (https://www.html5rocks.com/en/tutorials/developertools/async-call-stack/).

Could you nudge me in the right direction? Maybe I’d monkeypatch VM to do just that…

You can do a quick and dirty monkey patch by updating the loadProperties method to wrap the functions in a another that logs when the function is about to run:

  @loadProperties = (toLoad, container) ->
    loadObj = (obj) ->
      for key, value of obj when not ViewModel.properties[key]
        if ViewModel.reserved[key]
          throw new Error "Can't use reserved word '" + key + "' as a view model property."
        else
          if _.isFunction(value)
            # we don't care, just take the new function
            container[key] = value
            
            # Instead do something like:
            #
            # wrapped = (args...) ->
            #   console.log "About to run: " + key
            #   value args
            #
            # And then:
            # container[key] = wrapped

And also funProp:

    funProp = (value) ->
      if arguments.length
        if _value isnt value
          changeValue = ->
            # console.log "Going to change " + _value + " to " + value

It will probably be a good idea to pass the property name to makeReactiveProperty to log it too. Right now funProps are anonymous.

Hope it helps.

1 Like

Incidentally, @manuel, I was thinking about a scenario that I could not use viewmodel for…

Imagine a mortgage calculator, where number of years affects the monthly payment, AND the monthly payment might be set by the user to a lower value, and expect that the # of years would change… In this case, which field is the raw one, and which is the derived?

What’s solved this for me is that redux ‘smears’ the state out over time. I write a reducer that changes the state underneath, depending on which quantity the user wants to adjust. Then I have a pure-function renderer which, minimally, updates the DOM based on which parts have changed.

It’s been interesting to work more with React… I was just looking back to see if I wanted to use viewmodel for my current use case, and thought it’d be worth an ask if you saw a way to do this in VM. (perhaps by grabbing events on derived properties, and turning them into changes on the ‘leaf’ properties…)

cheers,
Dean

Incidentally - I just saw your Babel transpiler React one - very cool!

What a coincidence, I work in the Mortgage industry =)

The key point is that you have circular references and thus “something’s got to give”. We deal with situations like this all the time. Here’s a not so made up example:

You have 3 interdependent fields: Loan Amount, Total Years, and Monthly Payment (let’s forget interest for simplicity’s sake). And so the formula is:
Monthly Payment = Loan Amount / (TotalYears * 12)

Fair enough, the problem now is that any value affects the other so, again, “something’s got to give”. So we (a business analyst actually) are going to decree the following 2 things:

  1. The value entered by the user is always right and thus can’t change in our computations.
  2. Loan Amount is more important than Total Years, and Total Years is more important than Monthly Payment.

With that we can construct our component:

App({
  
  loanAmount: 200000,
  loanAmountFocus: false,
  
  totalYears: 30,
  totalYearsFocus: false,
  
  monthlyPayment: 555.56,
  monthlyPaymentFocus: false,

  autorun: [
    function() {
      // Watch for changes in Loan Amount
      const loanAmount = this.loanAmount();
      // Only do this if the user is changing Loan Amount
      if (!this.loanAmountFocus()) return;
      const totalYears = this.totalYears.value;
      // We "decree" that Total Years is more important than Monthly Payment
      // And thus we only update Monthly Payment based on Total Years
      this.monthlyPayment( (loanAmount / ( totalYears * 12 )).toFixed(2) );
    },
    function() {
      // Watch for changes in Total Years
      const totalYears = this.totalYears();
      // Only do this if the user is changing Total Years
      if (!this.totalYearsFocus()) return;
      const loanAmount = this.loanAmount.value;
      // We "decree" that Loan Amount is more important than Monthly Payment
      // And thus we only update Monthly Payment based on Loan Amount
      this.monthlyPayment( (loanAmount / ( totalYears * 12 )).toFixed(2) );
    },
    function() {
      // Watch for changes in Monthly Payment
      const monthlyPayment = this.monthlyPayment();
      // Only do this if the user is changing Monthly Payment
      if (!this.monthlyPaymentFocus()) return;
      const loanAmount = this.loanAmount.value;
      // We "decree" that Loan Amount is more important than Total Years
      // And thus we only update Total Years based on Loan Amount
      this.totalYears( (loanAmount / ( monthlyPayment * 12 )).toFixed(2) );
    }
  ],
  render(){
    <div>
      Loan Amount: <input b="value: loanAmount, focus: loanAmountFocus"/><br />
      Total Years: <input b="value: totalYears, focus: totalYearsFocus"/><br />
      Monthly Payment: <input b="value: monthlyPayment, focus: monthlyPaymentFocus"/><br />
      <button b="click: reset">Reset Values</button>
    </div>
  }
});

Here’s the repro: https://github.com/ManuelDeLeon/mortgage-calculator

In our case we offload such computations to a calc engine (a service/mixin). We don’t do it in the component itself but the logic is the same. We just pass the calc engine the value the user is working on and “freeze” it.

1 Like

Thanks! I absolutely love working with viewmodel-react. Even after months of working with it, I honestly smile when I see my code. There’s something right about seeing “just my code”. The main page of viewmodel.org has the React documentation.

3 Likes

In your case I would not bind input fields to state directly and eliminate a notion of derived property.
In your case properties should not ‘observe’ anything and simply hold the state.
I would submit input events to controller or dispatcher that would detect which input was modified and what consequences must follow.
Based on those colculations it should submit new values to state properties. Via ‘actions’ or whatever.
What’s good with this approach is that you can actually determine what was the cause of state change via logging dispatcher actions.

Could you expand on that

and that

Our system has a loan object which is basically a bag of fields related to a mortgage. It has information about borrowers, properties, prices, interest rates, etc. As you mentioned, we can’t just use computed fields for everything because a lot of the fields are interdependent.

The calc engine is basically a set of functions that contain all the business logic regarding how fields are related to one another. At risk of oversimplification, you give it a loan object plus the new field that needs to change, and it will give you a new loan object based on the existing rules.

By freeze I mean make sure the calc engine doesn’t change the value the user just entered. We use the term “lock a value” too. We also have the ability to freeze/lock multiple values at the same time (while the calc engine does its thing). Again it’s all based on business rules.

Still on the Blaze ViewModel here for a while! Thanks for your hard work.

3 Likes

Incidentally, I use just the same approach.
I store all the business logic that is used by several components in the global Logic object which is itself a set of pure functions.
If business logic is unique to the component though - I do keep it inside the component.

What’s the best way to use a Subscription Cache/Manager with ViewModel, especially in a Meteor 1.3+ project? At this point, we’re only looking to implement a global cache. Any pointers to how to go about this would be really helpful. Thanks.

Please clarify what you mean with “Subscription Cache/Manager” and “global cache”. The first thing that comes to mind is redux.