Changing tagName for a component


#1

Hello folks,

I have a Chart component which has ‘canvas’ as tagName property. I want to change tagName when there is not data to render the chart (I would avoid to render the chart). The docs say that I should destroy and create a new element view. I’m trying to do that on willInsertElement event and in fact, it destroys the view, but then it’s not created again using the new tagName:

willInsertElement: function() {
    var _this = this;
    Ember.RSVP.Promise.resolve(this.get('data')).then(function(data){
        if (!data) {
          _this.set('tagName', 'div');
          _this.destroyElement();
          _this.createElement();
        } 
    }); 
  },  

What is the proper way to do this? Is there another place where I should put this instead of willInsertElement?


#2

Could you wrap the component in an if statement, rather than altering the tagName:

{{#if data}}
  {{my-chart data=data}}
{{/if}}

#3

Thanks for you reply @domchristie.

The problem is that data is a Controller’s property processing a promise that won’t be resolved at the time of inserting the element into the DOM. So, I think that if statement will always be true.

// controllers/my-controller.js
userData: function(){
   return this.get('userStatistics').then(function(data) {
       // prepare data for chart   
       return dataForChart;
 });
}.property('userStatistics');

// templates/my-template.hbs
{{#if userData}
    {{ember-chart id="overall-performance" data=userData}}
{{else}}
    <h1>No data</h1>
{{/if}}

#4

It seems to me that this would create a perpetual loop, no?

-> willInsertElement
->  wait for promise to resolve
-> destroy element
-> createElement
-> willInsertElement
-> wait for promise
-> already resolved so then is called immediately
-> destroy element
-> create element
(repeat)

EDIT

I believe you also need to return that promise and the call to _this.createElement. Furthermore, if this.get('data') is a promise, you don’t need to wrap it in a promise that always resolves.

willInsertElement: function() {
    var _this = this;
    return this.get('data').then(function(data){
        if (!data) {
          _this.set('tagName', 'div');
          _this.destroyElement();
          return _this.createElement();
        }
       return _this;
    }); 
  }

#5

thanks for your reply @jthoburn.

Definitely, this approach was too complex, so I made the chart component to insert an html tag, when no data was retrieved from the server.

didInsertElement: function() {
    var _this = this;

    Ember.RSVP.Promise.resolve(this.get('data')).then(function(data){
        var parentDiv;
        if (data) {
          _this.setupChart(data);
        } else {
          parentDiv = $(_this.get('element')).parent();
          parentDiv.append('<div class="no-chart-data">No Chart data</div>');
        }   
    }); 
  },

Is it possible to have recursive components?
#6

@jsangilve Now that I see clearly what you are trying to do, I’d go one step simpler.

##Simple

controller.js

Ember.Controller.extend({
    hasUserData : false,
    updateUserData : function () {
        var Controller = this;
        this.get('data').then(function(){
          Controller.set('hasUserData', true);
        });
    }.on('init')
});

template.hbs

{{#if hasUserData}
    {{ember-chart id="overall-performance" data=data}}
{{else}}
    <div class="no-chart-data">No Chart data</div>
{{/if}}

##Simplest but uses a private property

This uses a private property, but if you simply want to display the notice until the promise has resolved or rejected (e.g. ember-chart handles rejection) then RSVP Promises have a _state property which is null initially, 1 on resolve, and 2 on reject.

{{#if data._result}
    {{ember-chart id="overall-performance" data=data}}
{{else}}
    <div class="no-chart-data">No Chart data</div>
{{/if}}

##Further Reading