mlienau
mlienau

Reputation: 813

Nested View Not Updating Ember.js

Some background before I get right to the problem. I've got some data (random numbers in this case), and I will need to be able to visualize this data in multiple ways. I've only implemented a table and line view in the fiddle, in prod I will have more ways to visualize the data (pie, bar, etc...), and there will be multiple sections.

Here is the fiddle.

I can correctly change the type I want to display, but I can't seem to get the view to update whenever I update the nested view. I'm probably missing something really easy, so the title of this question maybe loaded. If that's the case I apologize, but I'd greatly appreciate any help.

Handlebars:

<script type="text/x-handlebars" data-template-name="index">
    {{#each App.Controller.content}}
        {{#view App.ChartTypeContainer contentBinding="this"}}
            {{#each chartTypesWithSelected}}
                <a href="#" {{action switchChartType this target="view"}}>
                    {{#if selected}}
                        <strong>{{display}}</strong>
                    {{else}}
                        {{display}}
                    {{/if}}
                </a>
            {{/each}}
        {{/view}}
        {{#if currentView}}
            {{view currentView}}
        {{/if}}
    {{/each}}
</script>


<script type="text/x-handlebars" data-template-name="table">
 <table>
     <thead>
         <tr>
            {{#each view.data.headings}}
                <th>{{name}}</th>
            {{/each}}
        </tr>
    </thead>
    <tbody>
        {{#each view.data.data}}
            <tr>
                {{#each values}}
                    <td>{{this}}</td>
                {{/each}}
            </tr>
        {{/each}}
    </tbody>
</table>
</script>

<script type="text/x-handlebars" data-template-name="line">
</script>

js:

App = Em.Application.create({
    getRandomData: function(){
        // Generate between 1-3 random headings
        var headings=[], 
            headingCount = (Math.floor(Math.random() * 5) + 1),
            data=[];

        for(var i = 0; i < headingCount; i++){
            headings.push({name: 'heading ' + i});
        }
        // Generate between 5 and 10 rows of random data
        for(var i = 0; i< (Math.floor(Math.random() * 10) + 5);i++){
            var values = [];
            for(var j=0; j< headingCount;j++){
                values.push((Math.floor(Math.random() * 100) + 1));
            }
            data.push({values: values});
        }
        return {headings: headings, data: data};
    },
    ready: function(){
        Em.View.create({templateName:'index'}).appendTo('body');
    }
});

App.chartFactory = Em.Object.create({
    create: function(key, data){
        switch(key){
            case 'table':
                return App.TableView.create({data: data || App.getRandomData()});
            case 'line':
                return App.LineView.create();
            default:
                return;
        }
    }
});

/* MODELS */
App.ChartType = Em.Object.extend({
    key: '',
    display: ''
});

App.Section = Em.Object.extend({
    title: '',
    chartTypes: [],
    chartTypesWithSelected: function(){
        var currentSelected = this.get('selectedChartType');
        var types = this.get('chartTypes');

        var thing = types.map(function(item){
            item.set('selected', item.key === currentSelected);
            return item;
        });
        return thing;
    }.property('chartTypes.@each', 'selectedChartType'),
    data: {},
    selectedChartType: '',
    selectedChartTypeObserver: function() {
        var selectedChartType = this.get('selectedChartType');
        alert('changin chart type to: ' + selectedChartType);
        App.chartFactory.create(selectedChartType);
    }.observes('selectedChartType'),
    currentView: null
});

/* VIEWS */
App.ChartTypeContainer = Em.View.extend({
    switchChartType: function(chartType) {
        this.get('content').set('selectedChartType', chartType.key);
    }
})

App.TableView = Em.View.extend({
    templateName: 'table',
    data: {}
});

App.LineView = Em.View.extend({
    templateName:'line',
    data: {},
    didInsertElement: function(){
        var data = App.getRandomData();
        var headings = data.headings.map(function(item){
            return item.name;
        });
        var series = data.data.map(function(item){
            return {data: item.values};
        });

        this.$().highcharts({
            title: null,
            series: series, 
            xAxis: {categories: headings}, 
            yAxis: {min: 0, max: 100, title: {text: 'Random Numbers'}}
        });
    }
})

/* CONTROLLER */
App.Controller = Em.Object.create({
    content: [
        App.Section.create({
            title: 'First Section', 
            chartTypes: [
                App.ChartType.create({key: 'table', display: 'Table Display'}),
                App.ChartType.create({key: 'line', display: 'Line Display'})
            ],
            selectedChartType: 'table', // CHANGE HERE TO SEE THE OTHER VIEW, use 'line' or 'table'
            currentView: App.chartFactory.create('table') // CHANGE HERE TO SEE THE OTHER VIEW, use 'line' or 'table'
        })
    ]
});

UPDATE: Setting the newly created view on the next run cycle using Ember.run.next seems to produce the required behavior correctly. Updated Fiddle

Upvotes: 3

Views: 523

Answers (1)

ahaurw01
ahaurw01

Reputation: 1202

I suggest taking a look at the official ember guides. They've gotten really good lately and can shed some light on best practices and ways to not "fight" the framework.

I forked your fiddle and provided something that is closer to "the true ember way" when it comes to showing multiple views for the same underlying data as you are trying to do. In my opinion, manually creating and/or attaching views to the DOM is an anti-pattern in ember.

The basic approach is having a resource represent your data, and then routes under that resource that map to the templates that you want to render. What I've done is provide a template for the resource that merely has links to the two routes beneath it and an {{outlet}} into which those templates will get rendered. I removed your manual creation and swapping of views. By default I have it transition to the chart view so that you don't just see a blank page with links.

As with any framework, if you find yourself writing a ton of boilerplate code and things aren't working properly, that's probably a good sign that you're fighting the framework. Or the framework is bad. :P

Upvotes: 1

Related Questions