ViewModel 2 - A new level of simplicity

I will make one tomorrow, but it in my case it’s a typical thing. I am using signals to catch a click event on the document. If the click event fires, it should close all notifications - but I am also having a click binding on the notification icons, which should open a notification. So the signal event says close and the icon event says open.

I think I’ve encountered a similar issue.

I have a piece of text, which when clicked, switches to an input element so that the text becomes editable.

  {{#if editMode}}
     <input {{b "blur: editMode(false)"}} ...>
     <button {{b "click: save"}}>save</button>
  {{else}}
     <span {{b "click: editMode(true)"}}>{{text}}</span>
  {{/if}}

Now, I want to be able to close the input by clicking outside of it. For this I use the blur-bind on the input itself like this {{b "blur: showEdit(false)}}. Problems arise when I want to also show a button that enables the user to actually submit the possible changes: when trying to click the button, the input gets blurred and therefore both the input and the button removed from DOM before the click gets registered.

My quick fix was to close the input with a timeout, allowing time for the click event to be registered.

I didn’t think much of the issue and just carried on, leaving a // TODO: Solve this in a better way - comment in the code. I also tried using signal to solve this, some problems caused me not to. Can’t remember what it was.

Two months ago I solved a similar problem by checking the element that was currently in focus before deciding on the action - close or not to close… but that was too complicated to be usable.

Sorry if I’m being unclear, just thought I’d share my experiences with similar issues.

2 Likes

I honestly have no clue how to deal with that situation. How would you deal with it in any other setting? (react, Angular, elm, jQuery, etc.)

@manuel It’s not ViewModel specific, it’s a typical “event collision”. I would also use a timeout or VM’s signals (document.click event) and check whether the clicked element (e.target) is a save button (don’t set editMode to false) or another element (set editMode to false).

I know. I just wonder if someone has a cleaner solution to this problem, even if it’s with another framework.

My method may not be ‘clean’ or whatever, but it works:

tpl.viewmodel
  onRendered: ->
    $(document).on('click.eventN1', -> makeSureThatEventTargetIsTheRightElementThendoSmth())
  onDestroyed: ->
    $(document).off('click.eventN1')

Hi @manuel, what’s your recommendation for multi-page / multi-step type of SPA using VM?

I could think of two ways:

  1. Route-based with top level layout wrapping. Thinking to save the App states at layout VM.

  2. Stay in a route and use templates to switch the steps

Which is better?

Thanks.

I’d go with #1, use the route/url to land at the major sections of your app and then let VM put the “inner navigation” on the url (active tabs, list selections, etc.)

2 Likes

Manuel, could we extend .children api with a flag that when set to true would allow to return all descendant viewmodels, not only direct descendants?

Probably not. It’s added API for an edge case which can be easily solved with recursion.

You mean like .children().children()?

More or less:

allChildren(vm) {
  const children = vm.children()
  for(let child of vm.children()){
    children.push(...allChildren(child));
  }
  return children;
}
1 Like

Manuel, is there a way to set multiple autoruns and at the same time break my code into pseudo components? I mean, I want to do this:

Template.addPost.viewmodel
  # DESCRIPTION COMPONENT #
  descriptionHelpersGoHere:''
  autorun: -> doSmthRelatedToDescription

  # PRICE COMPONENT #
  priceHelpersGoHere:''
  autorun: -> doSmthRelatedToPrice

  # SUBMIT BTN COMPONENT #
  submitBtnHelpersGoHere:''
  autorun: -> doSmthRelatedToSubmitBtn

Currently I have to resort to this:

vmPropsArr = []

# DESCRIPTION COMPONENT #
vmProps =
  descriptionHelpersGoHere:''
  autorun: -> doSmthRelatedToDescription
vmPropsArr.push vmProps

# PRICE COMPONENT #
vmProps =
  priceHelpersGoHere:''
  autorun: -> doSmthRelatedToPrice
vmPropsArr.push vmProps

# SUBMIT BTN COMPONENT #
vmProps =
  submitBtnHelpersGoHere:''
  autorun: -> doSmthRelatedToSubmitBtn
vmPropsArr.push vmProps

Template.addPost.viewmodel
  load: vmPropsArr

Not very neat…

Use an array of functions with the autorun.
https://viewmodel.org/docs/viewmodels#autorun

Template.addPost.viewmodel
  descriptionHelpersGoHere:''
  priceHelpersGoHere:''
  submitBtnHelpersGoHere:''
  autorun: [ 
    (-> doSmthRelatedToDescription )
    (-> doSmthRelatedToPrice )
    (-> doSmthRelatedToSubmitBtn )
  ]

Yeah, but the whole point was to logically keep all code related to description together and not mix it with other components’ code.

Then use a mixin for each.

Does it work for you? For me it returns not only viewmodels, but also ReactveArrays.

ViewModel.mixin
  description: 
    descriptionHelpersGoHere:''
    autorun: -> doSmthRelatedToDescription

  price: 
    priceHelpersGoHere:''
    autorun: -> doSmthRelatedToPrice

  submit:
    submitBtnHelpersGoHere:''
    autorun: -> doSmthRelatedToSubmitBtn

Template.addPost.viewmodel
  mixin: ['description', 'price', 'submit']

Ah, I meant about this:

Does it work for you? For me it returns not only viewmodels, but also ReactveArrays.

I didn’t test it. It’s just to give you an idea of where to start.