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
Refactor Modals to Glimmer components #1282
Conversation
88d4d83
to
d0152fc
Compare
…ehavior As discussed in #1282 (comment), we remove the optional feature introduced in #1248 again, and make its behavior the default one.
…ehavior As discussed in #1282 (comment), we remove the optional feature introduced in #1248 again, and make its behavior the default one.
d0152fc
to
75f12ad
Compare
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.
Started a review some time ago but haven't had the time to finish it yet. Only reviewed API docs for <BsModalSimple>
so far. I guess it's only a copy and paste. But noticed that some parts are outdated. Let's take the chance and fix that.
addon/components/-bs-modal-simple.js
Outdated
A modal created this way will be visible at once. You can use the `{{#if ...}}` helper to hide all modal elements from | ||
the DOM until needed. Or you can bind the `open` property to trigger showing and hiding the modal: |
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.
Is using {{if}}
helper working well with animations? I thought @open
argument is required for animations.
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.
Reviewed the rest as well. Mostly general questions not strictly related to this refactoring. Keep request changes as I think the fixes to docs from last review should be included for sure.
addon/components/bs-modal.hbs
Outdated
{{did-insert this.handleOpening}} | ||
{{did-update this.handleOpening this.isOpen}} |
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 get that changes of the value of @open
argument must trigger side effects. But I would prefer if changes to internal state does not trigger side effects as it makes reasoning about state and its flow much harder.
I get that this is only replacing an existing observer. And that we shouldn't overcomplicate the glimmer refactoring by trying to address all technical debts at once.
But maybe we can add a comment that this should be refactored? Just to make it clear that this isn't a pattern which should be used for new features?
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.
How would you refactor that?
But I would prefer if changes to internal state does not trigger side effects as it makes reasoning about state and its flow much harder.
But in our case that needs to happen. When setting our inter isOpen
to true, we need to create side effects that are not possible just by auto-updating our template, like changing the <bod>
class, adding a transitionEnd
listener etc.
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.
When setting our inter
isOpen
to true, we need to create side effects that are not possible just by auto-updating our template, like changing the<bod>
class, adding atransitionEnd
listener etc.
That shouldn't be caused by a side effect but explicitly executed at the same place as isOpen
is changed.
How would you refactor that?
Let me try to illustrate with some pseudo code.
{{did-insert this.reactOnExternalVisibilityChanges}}
{{did-update this.reactOnExternalVisibilityChanges @open}}
export default ModalComponent extends Component {
@tracked isOpen = this.args.open;
@action
show() {
if (this.isOpen) {
// Modal is already open. Nothing to do.
}
this.isOpen = true;
// ...
}
@action
hide() {
if (!this.isOpen) {
// Modal is already hidden. Nothing to do.
}
this.isOpen = false;
// ...
}
@action
close() {
if (this.args.onHide?.() !== false) {
// Before this method calls `hide` method implicitly by setting
// `this.isOpen = false`. This is the classical observer style.
// Instead we should call the `hide` method explicitly.
this.hide();
}
}
@action
reactOnExternalVisibilityChanges() {
if (this.args.open) {
this.show();
else {
this.hide();
}
}
}
Not sure if I missed something. The general idea is that internal isOpen
state is changed only through show
and hide
methods. Internal and yielded actions call these methods directly. External state passed in as @open
argument is observed using {{did-update}}
helper. If it changes show
or hide
method is executed.
{{did-insert}}
is used caused DOM must be inserted before show
method can be executed. I would have preferred to use constructor
instead. But not sure if DOM is guaranteed to exist if scheduling if for afterRender
or next runloop. That's why I didn't touched that part.
schedule('afterRender', this, function () { | ||
next(() => { |
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.
Why next
instead of afterRender
? Isn't afterRender
more precise?
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.
Yes, but somehow not working. Like it didn't see the rendering changes when they callback ran. 🤔
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.
It seems as if the callback itself also schedules something for afterRender
: https://github.com/kaliber5/ember-bootstrap/blob/75f12adb7070fd0d931bc6f90d8941c5bb957425/addon/components/bs-modal.js#L433-L462 Maybe it's just not well specified what happens if scheduling something for the run loop you are currently?
🤔
schedule('afterRender', this, function() {
schedule('afterRender', this, function() {
// Same runloop or after render of next runloop? 😕
});
});
So executing the callback in actions queue of next runloop seems to be a cleaner approach. Not sure though if maybe only the callback should be delayed for next runloop but not the transitionEnd
? Like this:
schedule('afterRender', this, function() {
let backdrop = this.backdropElement;
assert('Backdrop element should be in DOM', backdrop);
if (doAnimate) {
transitionEnd(backdrop, this.backdropTransitionDuration).then(callback);
} else {
next(() => {
callback();
});
}
});
An alternative would be to change the code in the callback so that the timing of the other callback wouldn't be affected. The other usage of handleBackdrop
doesn't seem to schedule something for afterRender: https://github.com/kaliber5/ember-bootstrap/blob/75f12adb7070fd0d931bc6f90d8941c5bb957425/addon/components/bs-modal.js#L498-L504
|
||
@class ModalHeaderClose | ||
@namespace Components | ||
@extends Glimmer.Component |
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.
Is this correct for a template only component?
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.
Idk. They are after all called template only Glimmer components, aren't they? Not that this matters much here, given this is a private component (only yielded), and extending is not supported anyways...
36e9596
to
95af298
Compare
Thanks @jelhan for your super thorough review! Applied your suggestions. The failing tests in beta and canary are due to buschtoens/ember-render-helpers#282. 🤔 |
…ehavior As discussed in ember-bootstrap#1282 (comment), we remove the optional feature introduced in ember-bootstrap#1248 again, and make its behavior the default one.
Co-authored-by: Jeldrik Hanschke <jelhan@users.noreply.github.com>
95af298
to
56ab45e
Compare
Updated @jelhan applied your suggestion regarding using |
@simonihmig Thanks a lot for pushing this over the finished line. Looks great! |
Most components refactored to template-only.