Skip to content
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

Handle async option changes on select #91

Merged
merged 3 commits into from
Apr 10, 2017
Merged

Conversation

charpeni
Copy link
Contributor

@charpeni charpeni commented Apr 5, 2017

Fixes #88.

There's an issue with select options when they're async, the native select was updated but the changes was not reflected to the select materialized.

This PR partially fix this issue by subscribing to the DOMSubtreeModified event on select element and count options to notice any changes, if the options count doesn't match, it trigger a material_select, then changes are reflected to the select materialized.

While this solution works for a majority of use cases without adding any properties to the select, it doesn't cover when options are changed without modifying the length of the options array (ex: trigger translation). These use cases should be handle manually.

Edited : Fix now cover every use cases by keeping the last options and compare on every DOM changes.

Also, we can't do a real test case with Observable.delay because of an issue in Zone.js, it should be fixed by the latest release of zone.js. I'll verify that after we've updated our dependencies.

@scote scote self-assigned this Apr 6, 2017
Observable.fromEvent($(this.selectElement), 'DOMSubtreeModified')
.debounceTime(100)
.subscribe(() => {
if (this.optionLength !== this.optionElementsLength()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add the returned value by this.optionElementsLength() to variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.optionElementsLength() has been removed.

nativeElement = fixture.nativeElement;
fixture.detectChanges();

const optionLength = () => fixture.nativeElement.querySelectorAll('option').length;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be this way ?

const optionLength = fixture.nativeElement.querySelectorAll('option').length;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't, because we need to execute the thunk function to get the actual value.

this.optionLength = this.optionElementsLength();
}

Observable.fromEvent($(this.selectElement), 'DOMSubtreeModified')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.selectElement is already a JQuery object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, it doesn't work without a JQuery selector around.

Invalid event target.

Did I ever say that I like jQuery? 💥

@charpeni
Copy link
Contributor Author

charpeni commented Apr 7, 2017

I've pushed a new commit that changed how change event was handled.

Now, select keeps lastOptions and check for a change on every DOMSubtreeModified.

Observable.fromEvent($(this.selectElement), 'DOMSubtreeModified')
.debounceTime(100)
.filter((event: JQueryEventObject) => {
const currentOptions: any = Array.from(event.currentTarget.children);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove cast to any by replacing this line with const currentOptions = event.currentTarget.children; and use Array.from(currentOptions).some(...) at Line 232

if (prevOptions.length !== currentOptions.length) {
return true;
} else {
return currentOptions.some((option: Element, index) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type Element become implicit when removing cast to any on currentOptions


fixture.detectChanges();

fixture.whenStable().then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not change anything in this case because selectAsyncOptions is not an observable but I was told it will be the case after updating to Angular 4 so no need to change it

@@ -96,6 +103,9 @@ export class MzSelectDirective extends HandlePropChanges implements AfterViewIni
.map((element: HTMLOptionElement) => element.value);
// setTimeout is needed to this fix to work
setTimeout(() => this.selectElement.val(selectedOptions));

// prevent close on first multi select change
this.lastOptions = this.selectElement[0].children;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not unit tested?

this.initOnChange();
this.listenOptionChanges();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not unit tested?

@jfcere jfcere merged commit 7690220 into master Apr 10, 2017
@jfcere jfcere deleted the async-select-options branch April 10, 2017 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Async options in select are not reflected
3 participants