-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
unwanted side effect when calling Tag.updateValue during a call to setProperty #417
Comments
That's an interesting scenario. JsViews looks at the bound properties, (in this case, dataSource and currentPage, from bindTo...) and works out the dependencies for those paths, (in this case ajaxResponse, ajaxResponse and currentPage) but does not track which dependencies come from which property. (Indeed, sometimes the same dependency may apply to two or more props). Now if a dependency (ajaxResponse) changes, it will as you say, call setProperty on all of them, (in this case first dataSource and then currentPage). But if the dependency (ajaxResponse) only applies to one of them (dataSource) then calling setProperty on the other (currentPage) will be a no-op, since it has not changed (10). But in your case, you are listening to the change on dataSource, and then immediately updating currentPage (0). Now the calling code continues, moves to updating currentPage, and finds it has indeed changed (it is now 0, changing to 10) so it updates it to 10. So the obvious fix for your example is to make the update to page 0 be async, so it happens only after the calling code has completed. The calling code will be a no-op for currentPage, and your code will then successfully update to 0.
In fact there is an undocumented parameter,
|
I found a fix for the problem but I dont know if it could cause issues of it's own?
If possible I would prefer a solution where the consumer doesn't have to consider how jsviews work internally. Changing a property when some other property is changed seem like a normal enough scenario in a bigger application. What's your take on this? |
Thanks Johan. I need to finish some work in another area, first, but I'll then get back to taking a closer look at this. |
Hi Johan, I have some issues with the approach you suggest, but it was very helpful in motivating some other alternatives I looked at. In fact there are some improvements to One of the of the results of those changes is that calling So in your scenario your call to update the currentPage when dataSource changes should work as intended, since it will occur after the initial updating of both dataSource and currentPage, and will not get overridden by the latter. This should work whether or not the initial call changes currentPage. Here is an update that you can test: Let me know if it works correctly for you... |
Thanks I'll test it next week and get back to you afterwards. |
I have verified that this does indeed solve our problem. |
I did come across an issue which I think is related to some work on computed observables. Depending on your thoughts on this matter you might want to move this to a separate thread? Considering that this version is not yet official I post my findings here. In our template we want to bind event handlers for keydown event on an input. So to invoke myhandler on keydown we use a template like this:
key is a helper such as (code simplified):
I found that with this update the link expression "~key.enter.stop" behaves differently.
The value produced by the token "enter" is checked for a function, which it is. What's new is that the function is invoked directly. The code examples related to computed observables all use parentheses in the template expressions to indicate a computed observable such as
Perhaps the detection of a computed observable could be more strict such to prevent false positives. I.e. the function must have a property (function) named "set" to be considered a computed observable?
Anyway in my specific case I could just change the key helper into:
However I find the dot notation more elegant. Also you can do more powerful stuff if it's possible to differentiate between pointing out a computed observable, executing a function and pointing out a property on a function. |
Thanks again for this valuable feedback. Interesting to see how you are using JsViews, and those dot paths on functions. The reason for the new code that invokes the function is related to supporting chained dependencies for a hierarchy of getter functions, or of VMs, such as Person, Address, Street. In the template you might have
But you should also be able to do:
which was not working as expected. I'm looking into a fix that might be compatible with your scenario. Not sure yet... Another related issue is that for a computed or VM which returns an array, the following will listen to both property change (setting the But if you have a getter function,
does not currently listen to array changes. I am trying to make things more consistent here between getter functions and plain objects/arrays, so considering a change... But of course these are breaking changes, - as would be your suggestion of requiring a getter function to be detected by the presence of a setter, I'll keep you posted when I reach some conclusions... |
Given the person example. When a function is found (i.e. "person" in the expression "person.address.street") you could decide to execute it or not based on checking the token on the right hand side of the dot (i.e. "address"). It is still a breaking change but it seem to me that the likelihood to cause issues is low: If the function is a getter in the first place then why would you add functions as properties to it? Even more so what's the chance a getter function such as person() when executed would return an object with a property named "address" and also have a property named "address" on it? Famous last words.
|
Hmm I was a bit hasty with my last comment. Given the current approach to invoke the function directly there is no way to pass a function as an argument to an other function - which is something else we're doing. I.e.
onClick would call someOtherFunction when done with it's work. |
Yes, we need to be able to deal with leaf properties that are functions, too. I have a new candidate update here: jsviews_1.0.3_candidateB.zip With this version, you can write dependency or observe paths like this:
If
and that will work the same. Now, if
or in a template, write:
In fact you do side-by-side use of both styles - 'functions as getters' with parens, and 'functions as objects', not using parens:
If you use compiled VMs then the compiled getters are automatically created with Let me know how it works for you... |
BTW I may have a modified version in a day or two, if you want to wait before trying the update.... I'm hesitating on whether to simply require parens in observer paths, and not base things at all on having/not having a |
I ran into this odd issue that I can reproduce but I'm having a hard time understanding why it's happening.
Regarding a However jsrender is free to make life easier for the developers. Not having to write out the parentheses looks cleaner. I think |
OK, I took a look. That strange behavior is because I had enabled data-linking and observing changes of properties on functions, so you could do:
But as a result, JsViews is calling
and jQuery, in addition to attaching the listener, also treats the call $(person.someOtherFunction) as a call to invoke the function In the next version I send you, I'll remove the support for listening to observable changes on a path like |
Here is an update: jsviews_1.0.3_candidateC.zip It should fix that behavior re document ready. You can't now observe properties of functions. You can and should use parens in observe/depends paths with chaining, such as |
As far as I can tell It's now working correctly. |
I found an issue with when scripts are loaded separately. If you load jquery.observable.js first and then do something like this before loading any of the other scripts
you get the following error |
Yes, thanks again, another good catch. It should be fixed in this version: |
FYI: |
See #420 for an update which supercedes candidateD above. |
Initial testing is positive. I'll keep monitoring to see if anything shows up. |
Feature improvements: Parens are now supported within $.observe() paths - e.g. "person.address().street()" - https://www.jsviews.com/#jsvmodel@chain - https://www.jsviews.com/#jsvviewmodelsapi@ismanagersample - https://www.jsviews.com/#computed@observe-computed - https://www.jsviews.com/#computed@depend-computed Improved features for managing how tags respond to onArrayChange events - https://www.jsviews.com/#tagoptions@onarraychange New support for referencing the parent object in hierarchical View Model scenarios (parentRef): - https://www.jsviews.com/#jsrmodel@parentref - https://www.jsviews.com/#viewmodelsapi@accessparent - https://www.jsviews.com/#jsvviewmodelsapi@ismanagersample New support for asynchronous or batched observable change events (asyncObserve): - https://www.jsviews.com/#delay New features available for scenarios using sorting and filtering (mapProps and mapDepends): - https://www.jsviews.com/#tagoptions@mapprops Bug fixes: - BorisMoore/jsviews#415 (Markup within element tag - incorrect whitespace) - BorisMoore/jsviews#417 (Side-effect when calling Tag.updateValue during setProperty call) - BorisMoore/jsviews#419 (Regression: checkbox widget not working since v1.0.0) - BorisMoore/jsviews#420 (Template tag responses to array changes) - Several other minor bug fixes Additional and improved documentation topics and samples... Updated tree view samples: - https://www.jsviews.com/#samples/tag-controls/tree Updated Typescript definitions (soon to be deployed DefinitelyTyped): - https://www.jsviews.com/#typescript Many additional unit tests...
The changes discussed above are included in the new v1.0.3 release. (Let me know if you see any problems with the update. Thanks!) |
Feature improvements: Parens are now supported within $.observe() paths - e.g. "person.address().street()" - https://www.jsviews.com/#jsvmodel@chain - https://www.jsviews.com/#jsvviewmodelsapi@ismanagersample - https://www.jsviews.com/#computed@observe-computed - https://www.jsviews.com/#computed@depend-computed Improved features for managing how tags respond to onArrayChange events - https://www.jsviews.com/#tagoptions@onarraychange New support for referencing the parent object in hierarchical View Model scenarios (parentRef): - https://www.jsviews.com/#jsrmodel@parentref - https://www.jsviews.com/#viewmodelsapi@accessparent - https://www.jsviews.com/#jsvviewmodelsapi@ismanagersample New support for asynchronous or batched observable change events (asyncObserve): - https://www.jsviews.com/#delay New features available for scenarios using sorting and filtering (mapProps and mapDepends): - https://www.jsviews.com/#tagoptions@mapprops Bug fixes: - #415 (Markup within element tag - incorrect whitespace) - #417 (Side-effect when calling Tag.updateValue during setProperty call) - #419 (Regression: checkbox widget not working since v1.0.0) - #420 (Template tag responses to array changes) - Several other minor bug fixes Additional and improved documentation topics and samples... Updated tree view samples: - https://www.jsviews.com/#samples/tag-controls/tree Updated Typescript definitions (soon to be deployed DefinitelyTyped): - https://www.jsviews.com/#typescript Many additional unit tests...
I've stumbled upon this "thing".
In my template I have a tag expression like this:
{^{dataGrid dataSource=ajaxResponse^items currentPage=currentPage /}}
Basically it's a tag that is supposed to render an array of items in a tabular fasion. The code is really simplified for this demo.
Initially the items array is empty and setTimeout simulates an ajax call to retrieve data.
Upon data retrieval the ajaxResponse object is observably replaced on the viewmodel:
$.observable(viewModel).setProperty("ajaxResponse", ...);
The tag listens to changes in the dataSource array and it's supposed to reset pagination to first page when the tag is refreshed with new data:
updateValue() correctly writes the value 0 back viewModel.currentPage but as you can see in the console output the previous value is later written back to viewModel.currentPage by the setProperty method.
What's important to notice is that Tag.updateValue() is invoked during a call to setProperty().
If I observably refresh the items array directly instead of observably replacing the array entierly with setProperty the call to updateValue() work as intended:
$.observable(viewModel.ajaxResponse.items).refresh(...)
Here is a fiddle that demonstrates the problem.
From what I can tell setProperty() ends up calling onDataLinkedTagChange() which creates a hash object like
{dataSource: [<new items here>], currentPage: 10}
From this point setProperty() is called for each of these keys:
I would like it if only the property that was actually affected by the original call to setProperty is included in the hash object
{dataSource: [<new items here>]}
The text was updated successfully, but these errors were encountered: