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.
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:
-
Route-based with top level layout wrapping. Thinking to save the App states at layout VM.
-
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.)
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;
}
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.