-
Notifications
You must be signed in to change notification settings - Fork 649
Feature: clean model when a field is removed from view, such as when a condition is no longer satisfied. #371
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
Feature: clean model when a field is removed from view, such as when a condition is no longer satisfied. #371
Conversation
|
||
|
||
var DEFAULT_DESTROY_STRATEGY; | ||
if (scope.options && scope.options.formDefaults) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
formDefaults
should not be checked directly, it's already copied to the form object, see https://github.com/Textalk/angular-schema-form/blob/development/src/services/schema-form.js#L70
But in this case with null
as a valid option distinct from undefined
it might be better to move this to a global option. i.e. instead of checking scope.option.formDefaults.destroyStrategy
just check scope.option.destroyStrategy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this again, I think I might have overlooked the implications of having the formDefaults copied to the form object. (It makes sense now, so I may have just had the wrong idea when I started trying to use it). It seemed like a convenient way to set the default for the whole form if someone wanted to use an alternative to undefined, but a standard global option accomplishes that. I'll make a change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! :)
Hi @jbsaff and thank you for the PR! This is a much sought after feature and even though it changes current behavior I think that is a good call. It's probably what you want anyway and its the current behavior that surprises people. Nice work 👍 I made some comments, I think it's a good idea to add a global option for it instead of checking formDefaults explicitly. It was unfortunate that the change in indentation broke the diff :) was a bit hard to read! |
Thanks for the feedback. I'm pretty comfortable in Java, but needed something client-side for my current project. I'll incorporate your feedback into another commit. Thanks for your work on ASF. It's going to save me a ton of time! |
…destroyStrategy to be picked up directly from globalOptions, instead of looking in formDefaults. Properly handles for the difference between the destroyStrategy declared as undefined and completely undeclared, both at the globalOptions level and the form field definition level.
@davidlgj Fixes checked in, and I think I got my branch rebased to match Textalk/development. Both the code and documentation should be updated to include the globalOption and form defined destroyStrategy. I had to rework my approach to account for the difference between having the destroyStrategy property declared as undefined and not having it declared at all. Thanks! |
👍 Awesome! I'm hoping to test and merge start of next week :) One thing that struck me though, what happens if you remove the entire form? Like when navigating away in a single page app, or an ng-if on something around the form. Won't that trigger the |
Well crud. Yes, if there is an ng-if wrapping the form, the entire form would be subject to that $destroy. I'll look for another solution. |
Okay, I've given it a thought. There are actually two parts to the logic that ought to trigger data cleanup. One is removal from the DOM, which can be identified by the $destroy. The other is that the condition on the form element is not satisfied (which might trigger a $destroy due to how conditions are wired in with ng-if). I'm going to head in that direction and see how it goes. |
I was thinking that there might be a solution if the $destroy is called on fre 1 maj 2015 15:22 James Saffell [email protected] skrev:
|
Changed some titles.
…a form field element triggers the $destroy. Uses a new service, based on Select, to traverse the model and update it to the value chosen as part of the configured destroyStrategy. This destroyStrategy can be configured at the field, or as part of the forms global options. If both are defined, the field-level strategy will override.
…destroyStrategy to be picked up directly from globalOptions, instead of looking in formDefaults. Properly handles for the difference between the destroyStrategy declared as undefined and completely undeclared, both at the globalOptions level and the form field definition level.
… prevents the form from wiping itself when a $destroy event occurs that is not connected to the condition logic (such as if the FormController contains an element that wraps the form with an ng-if).
…rategy' into feature/cleanModelUsingDestroyStrategy Conflicts: README.md src/directives/schema-validate.js
Trying to understand the implications of the Angular docs for the $destroy event. It says that the listener is called just prior to the actual destruction occurring. Does that mean that it happens after all of the children have finished processing the $destroy in a depth-first fashion? If so, the $on $destroy for sfSchema would be too late to set the flag, as all of the children have already gone. I'm going to try to improve my understanding of the situation and determine what the event firing order is. |
… to ignore the destroyStrategy when the $destroy event occurs on sfSchema. This will prevent the data from being cleaned up when the form is removed from the DOM.
I've put together an initial implementation of a flag to retain the model instead of allowing the destroyStrategy to be used when the sfSchema element is destroyed. |
I'll merge it locally and test it out! |
scope.$on('$destroy', function() { | ||
|
||
var form = getForm(); | ||
var conditionResult = $parse(form.condition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was looking through the code and testing some, and I'm not quite sure why we need to check on the condition here? Shouldn't we always just follow the destroy strategy if we get a destroy event? I'm thinking that condition might not be the only thing that can remove a form element from the form, any other add on might use a ng-if
somewhere and since any field not in the DOM won't get any validation done on it anyways It's probably best to remove it from the model.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had left it in as a way to help show where I was coming from with my solution, because I wasn't 100% confident that the service I put in would be correct (I'd still label myself an apprentice with Javascript). Assuming that the flag will handle for the case where the destroy is coming from outside of the form, then I agree that destroys initiated within the form should just follow the destroyStrategy. It'll be more consistent (and still configurable) and easier to explain.
Ok, so I didn't get through i tonight, I'll try to pick it up again soon. Keeping the flag in a service won't work since that service is shared across all instances of schema form you might have on the page, but I think I got a solution for that :) ...just got to try it out first! |
Okay, thanks for the assist. I appreciate the help polishing it up! |
@jbsaff I've started to merge it in, but I've also changed some things. I really hope you don't mind. You've done some solid work and I couldn't have got this done without your PR to base it on! Initial manual testing looks good, I pushed the branch so you can see it here if you feel like checking it out: https://github.com/Textalk/angular-schema-form/tree/jbsaff-feature/cleanModelUsingDestroyStrategy It needs some cleaning and some tests but I think it works :) Major points of what I have changed:
|
@davidlgj I'm glad to hear my work was a useful starting point. I try not to take any ego into a project; if my stuff is good, it will stay. The changes you described sound like they will make the feature far more useable within ASF, so I'm very much in favor. Thanks for the reviews and suggestions. It made my first contribution to a project a lot of fun. |
Only thing I noticed is that when an array element (like in a tabarray) is removed, the console fills up with "TypeError: Cannot convert undefined or null to object". If I set the global option to null, I get "TypeError: Cannot set property '[object Array]' of undefined" instead. It looks like the Remove cleans up the model (as expected), then the new feature tries to do the same thing and gets an error. Do we expect that, in a case where sfSelect would return undefined, we would still want to attempt to set the value using the destroyStrategy? |
Of course! We might not have a model if the user hasn't filled out the entire form :) Added a check! btw I've been testing with this schema and form definition: https://gist.github.com/02ed18af7570ef149c92 |
Ok, moved it around a bit and added some tests. I tried doing removal of empty objects, but that is a bit tricky. Could easily end up mucking up arrays by removing the empty object needed for it to display an empty form. I'm not convinced it's actually needed, so I'm gonna let it be as it is. I think its ready enough for merging into the development branch :) Thanks for the PR and all the help! |
@davidlgj Having the behavior in the decorator directive looks like it works for the case where the destroy is triggered by something outside the form. However, when a form field destroy is triggered by a condition no longer being satisfied within that form, the sf-decorator tag remains in the DOM and the cleanup of the model isn't triggered. Would it make sense to split the behavior off into a directive that can be applied to the outermost div in each decorator template? It would require add-ons/extensions to opt-in to using the new directive, which could be a drawback. I like the way that moving the event handling up from the schema-validate level means that it will only fire once per field, instead of once for every input (long checkbox/radio lists, commonly). |
@jbsaff You're absolutely right! A separate directive is probably the cleanest, I'll try to re-work soon. |
This feature extends the schema-validate directive to allow users to configure an individual field or the entire form to one of a predefined set of behaviors when the $destroy event is broadcast. The common use case would be when a field is no longer visible due to a condition, which is evaluated by ng-if, no longer being met. Data that had previously been collected was remaining in the model, which would provide erroneous results on submission. By default, if no configuration is done, this feature would set the model value to undefined, just as if the field had never been visible in the form at all.
In order to properly handle complex nested arrays and objects, a new service (sfUnselect) was created. Based on the sfSelect service, sfUnselect will traverse a given path and attempt to set the endpoint to the provided value. If at any point an expected object or array is undefined, the service returns and does not create the missing object or array.
This addresses both #344 and #204
Also, in case it comes up in testing, this is related to #370, as the checkboxes will not work with this new feature until the schema-validate directive is correctly spelled.
Please pardon the commit log; I managed to fumble my git-fu roll when trying to create two pull requests.