adopted-ember-addons / ember-paper

The Ember approach to Material Design.
https://ember-paper.netlify.app/
MIT License
887 stars 333 forks source link

paper-button async aware #569

Open knownasilya opened 7 years ago

knownasilya commented 7 years ago

Would be nice if the paper-button could be async aware, that is accept a promise as a return value on the onClick action, mainly for the form usecase.

{{#paper-button type='submit' onClick=(action returnsPromise)}}
  Submit
{{else}}
  Submitting..
{{/paper-button}}

Or if the form component handles the action, it could pass down the promise in the yields:

{{#paper-form onsubmit=(action withPromise) as |f|}}
  {{#f.submit-button}}
    Submit
  {{else}}
    Submitting..
  {{/f.submit-button}}
{{/paper-form}}

Where the form passed it down:

{{yield (hash
  submit-button=(component 'paper-button' promise=submitPromise)
)}}
miguelcobain commented 7 years ago

@knownasilya what would the button do with the promise?

knownasilya commented 7 years ago

See modified example

miguelcobain commented 7 years ago

I like that it is an addictive change. Would it toggle the disabled state?

knownasilya commented 7 years ago

Yes, it would do that.

SirZach commented 7 years ago

I think https://github.com/machty/ember-concurrency can help with that

knownasilya commented 7 years ago

I think that's overkill for this. Here's a simple implementation I use:

import Ember from 'ember';

export default Ember.Component.extend({
  tagName: 'button',
  attributeBindings: ['type', 'disabled'],
  type: 'submit',
  disabled: false,
  loading: false,

  didReceiveAttrs() {
    var promise = this.get('promise');

    if (promise) {
      this.handleAction(promise);
    }
  },

  submit() {
    var promise = this.get('action') ? this.get('action')() : this.get('promise');

    this.handleAction(promise);
  },

  click() {
    var promise = this.get('action') ? this.get('action')() : this.get('promise');

    this.handleAction(promise);
  },

  handleAction(promise) {
    if (promise && promise.then) {
      this.setProperties({
        loading: true,
        disabled: true
      });

      promise.then(result => {
        if (this.isDestroying || this.isDestroyed) {
          return;
        }
        this.setProperties({
          actionResult: result,
          loading: false,
          disabled: false
        });
      })
      .catch(error => {
        if (this.isDestroying || this.isDestroyed) {
          return;
        }
        this.setProperties({
          error: true,
          disabled: false,
          actionResult: error
        });
      });
    } else {
      if (this.isDestroying || this.isDestroyed) {
        return;
      }
      this.setProperties({
        loading: false,
        errored: false,
        actionResult: undefined
      });
    }
  }
});
{{#if loading}}
  {{yield to='inverse'}}
{{else}}
  {{yield actionResult errored}}
{{/if}}

And usage:

{{#loading-button class='btn btn-default btn-block' action=(action 'search' hasResults)}}
  Search
{{else}}
  <i class="fa fa-circle-o-notch fa-pulse"></i> Looking..
{{/loading-button}}
SirZach commented 7 years ago

I was more or less arguing that baking this into ember-paper is overkill when addons like ember-concurrency make it easy for the dev to mimic this behavior.

knownasilya commented 7 years ago

How would you use ember-concurrency with paper-button to get this effect?

SirZach commented 7 years ago

Not the exact scenario you described but http://ember-concurrency.com/#/docs/examples/loading-ui is close

panthony commented 7 years ago

I had the same need that I resolved like this:

// loading-button/component.js
import Ember from 'ember';

export default Ember.Component.extend({

  tagName: '', // no wrapper

  didReceiveAttrs() {
    this._super(...arguments);
    this.set('promise', undefined);
  },

  actions: {
    onClick() {
      this.set('promise', this.attrs.onClick());
    }
  }
});
{{!-- loading-button/template.hbs --}}
{{#paper-button onClick=(action 'onClick') raised=raised warn=warn}}
  {{#if promise}}
    {{#if (is-pending promise)}}
        {{loadingMessage}}
    {{else if (is-fulfilled promise)}}
        {{successMessage}}
    {{else}}
        Doh!
    {{/if}}
  {{else}}
    {{paper-icon icon}} {{label}}
  {{/if}}
{{/paper-button}}
{{loading-button 
icon='add-box' 
raised=true 
label='Add stuff' 
loadingMessage='Creating stuff...'  
successMessage='Created stuff' 
onClick=(route-action 'createStuff')
}}