1

I'm trying to create a reusable generated element that can react to changing outside data. I'm doing this in an included view and using computed.alias, but this may be the wrong approach, because I can't seem to access the generic controller object at all.

http://emberjs.jsbin.com/nibuwevu/1/edit

App = Ember.Application.create();

App.AwesomeChartController = Ember.Object.extend({
  data: [],
  init: function() {
    this.setData();
  },
  setData: function() {
    var self = this;
    // Get data from the server
    self.set('data', [
      {
        id: 1,
        complete: 50,
        totoal: 100
      },
      {
        id: 2,
        complete: 70,
        total: 200
      }
    ]);
  }
});

App.IndexController = Ember.Controller.extend({
  needs: ['awesome_chart']
});

App.ChartView = Ember.View.extend({
  tagName: 'svg',
  attributeBindings: 'width height'.w(),
  content: Ember.computed.alias('awesome_chart.data'),
  render: function() {
    var width = this.get('width'),
        height = this.get('height');

    var svg = d3.select('#'+this.get('elementId'));

    svg.append('text')
    .text('Got content, and it is ' + typeof(content))
    .attr('width', width)
    .attr('height', height)
    .attr('x', 20)
    .attr('y', 20);


  }.on('didInsertElement')
});

And the HTML

  <script type="text/x-handlebars">
    <h2> Welcome to Ember.js</h2>

    {{outlet}}
  </script>

  <script type="text/x-handlebars" data-template-name="index">
    <h2>Awesome chart</h2>
    {{view App.ChartView width=400 height=100}}
  </script>

For what it's worth, this didn't seem to work as a component, either. Is the ApplicationController the only place for code that will be used on multiple pages? The 'needs' seems to work, but the nested view can't access it. If I make a proper Ember.Controller instance to decorate the view, that doesn't seem to work either.

Any help is much appreciated.

Update:

I can't edit my comment below, but I found a good answer on how to use related, and unrelated, models in a single route.

How to use multiple models with a single route in EmberJS / Ember Data?

Community
  • 1
  • 1
Ben Murden
  • 590
  • 2
  • 6
  • 23

2 Answers2

2

Firstly, your controllers should extend ObjectController/ArrayController/Controller

App.AwesomeChartController = Ember.Controller.extend({...});

Secondly when you create a view the view takes the controller of the parent, unless explicitly defined.

{{view App.ChartView width=400 height=100 controller=controllers.awesomeChart}}

Thirdly you already had set up the needs (needed a minor tweak), but just as a reminder for those reading this, in order to access a different controller from a controller you need to specify the controller name in the needs property of that controller.

App.IndexController = Ember.Controller.extend({
  needs: ['awesomeChart']
});

Fourthly from inside the view your computed alias changes to controller.data. Inside the view it no longer knows it as AwesomeChart, just as controller

content: Ember.computed.alias('controller.data')

Fifthly inside your on('init') method you need to actually get('content') before you attempt to display what it is. content doesn't live in the scope of that method.

var content = this.get('content'); 

http://emberjs.jsbin.com/nibuwevu/2/edit

Kingpin2k
  • 47,277
  • 10
  • 78
  • 96
  • Fantastic, thank you. Should I also assume that the init method needs this._super()? Do you have an opinion on whether this is the correct Ember idiomatic way to include shared, state-aware elements? As an aside, needs['awesome_chart'] seems to still work (with the view attribute adjusted accordingly), but is this naming convention soon to be deprecated? – Ben Murden May 17 '14 at 07:08
  • I was taught it was camelCase, so I've never tried it any other way. If it works, no loss. `this._super()` only needs to be called if you've overwritten an ember method, but in your case you didn't do that, you just have a method that's triggered on init. – Kingpin2k May 17 '14 at 19:20
  • I'm not sure if I have enough information/use cases to make a valid opinion on whether or not that's the best way to do it. – Kingpin2k May 17 '14 at 19:21
1

First, AwesomeChart does sound like it's gonna be a reusable self-contained component. In which case you should better user Ember.Component instead of Ember.View (as a bonus, you get a nice helper: {{awesome-chart}}).

App.AwesomeChartComponent = Ember.Component.extend({ /* ... */ });
// instead of App.ChartView...

Second, for AwesomeChart to be truly reusable, it shouldn't be concerned with getting data or anything. Instead, it should assume that it gets its data explicitly.

To do this, you basically need to remove the "content:" line from the awesome chart component and then pass the data in the template:

{{awesome-chart content=controllers.awesomeChart.data}}

Already, it's more reusable than it was before. http://emberjs.jsbin.com/minucuqa/2/edit

But why stop there? Having a separate controller for pulling chart data is odd. This belongs to model:

App.ChartData = Ember.Object.extend();
App.ChartData.reopenClass({
  fetch: function() {
    return new Ember.RSVP.Promise(function(resolve) {
      resolve([
        {
          id: 1,
          complete: 50,
          total: 100
        },
        {
          id: 2,
          complete: 70,
          total: 200
        }
      ]);
      // or, in case of http request:
      $.ajax({
        url: 'someURL',
        success: function(data) { resolve(data); }
      });
    });
  }
});

And wiring up the model with the controller belongs to route:

App.IndexController = Ember.ObjectController.extend();

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return App.ChartData.fetch();
  }
});

Finally, render it this way:

{{awesome-chart content=model}}

http://emberjs.jsbin.com/minucuqa/3/edit

Gosha A
  • 4,520
  • 1
  • 25
  • 33
  • Thanks for your answer and addressing the question of reuse. However, I wanted to keep this particular data mapping out of the route model, because the route will have its own data from a different model. After all, this component could be used anywhere within the application, not just on the index route. Do you have any insight on how to properly handle such a scenario, i.e. multiple models per route in, for example, a dashboard? – Ben Murden May 18 '14 at 06:55