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

Refactor Modals to Glimmer components #1282

Merged
merged 6 commits into from Dec 29, 2020
Merged

Refactor Modals to Glimmer components #1282

merged 6 commits into from Dec 29, 2020

Conversation

simonihmig
Copy link
Contributor

Most components refactored to template-only.

simonihmig added a commit that referenced this pull request Oct 12, 2020
…ehavior

As discussed in #1282 (comment), we remove the optional feature introduced in #1248 again, and make its behavior the default one.
simonihmig added a commit that referenced this pull request Oct 12, 2020
…ehavior

As discussed in #1282 (comment), we remove the optional feature introduced in #1248 again, and make its behavior the default one.
@simonihmig simonihmig changed the title WIP: Refactor Modals to Glimmer components Refactor Modals to Glimmer components Oct 18, 2020
Copy link
Contributor

@jelhan jelhan left a 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 Show resolved Hide resolved
addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
Comment on lines 14 to 15
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:
Copy link
Contributor

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.

addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
addon/components/-bs-modal-simple.js Outdated Show resolved Hide resolved
Copy link
Contributor

@jelhan jelhan left a 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.

Comment on lines 1 to 2
{{did-insert this.handleOpening}}
{{did-update this.handleOpening this.isOpen}}
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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 a transitionEnd 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.

Comment on lines -540 to +525
schedule('afterRender', this, function () {
next(() => {
Copy link
Contributor

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?

Copy link
Contributor Author

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. 🤔

Copy link
Contributor

@jelhan jelhan Oct 23, 2020

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

addon/components/bs-modal.js Outdated Show resolved Hide resolved
addon/components/bs-modal.js Outdated Show resolved Hide resolved
addon/components/bs-modal.js Outdated Show resolved Hide resolved
addon/components/bs-modal/-footer.js Outdated Show resolved Hide resolved
addon/components/bs-modal/-footer.js Outdated Show resolved Hide resolved
addon/components/bs-modal/footer.hbs Outdated Show resolved Hide resolved

@class ModalHeaderClose
@namespace Components
@extends Glimmer.Component
Copy link
Contributor

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?

Copy link
Contributor Author

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...

addon/helpers/bs-action-fallback.js Outdated Show resolved Hide resolved
@simonihmig
Copy link
Contributor Author

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. 🤔

marcemira pushed a commit to CloudTomatoes/ember-bootstrap that referenced this pull request Nov 13, 2020
…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.
@simonihmig
Copy link
Contributor Author

Updated ember-render-helpers which makes tests pass on CI.

@jelhan applied your suggestion regarding using {{did-update ... @open}} above (still need this._isOpen though) in the last commit. I did not have time to fiddle with the schedule timings you asked about though...

@simonihmig simonihmig merged commit 5936597 into master Dec 29, 2020
@simonihmig simonihmig deleted the modal-glimmer branch December 29, 2020 16:06
@jelhan
Copy link
Contributor

jelhan commented Dec 30, 2020

@simonihmig Thanks a lot for pushing this over the finished line. Looks great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants