Question

I have been trying to learn the fundamentals of Ember for a couple weeks now, and I am currently running into a problem when it comes to altering data in my model via an action in the controller.

All of the examples that I have found seem to be using one-dimensional fixtures. The fixtures I'm using look like this:

App.ClassGroup = DS.Model.extend({
    className: DS.attr('string'),
    isActive: DS.attr('number'),
    students: DS.hasMany('Students',{async:true}),
    selected: DS.hasMany('Students',{async:true})
});

App.ClassGroup.FIXTURES = [
    {
        id: 123,
        className: 'Class 1',
        isActive: 1,
        students: [11, 22, 33, 44, 55],
        selected: [11, 22, 33, 44, 55]
    },
    {
        id: 456,
        className: 'Class 2',
        isActive: 0,
        students: [66, 77, 88, 99],
        selected: [66, 88, 99]
    },
    {
        id: 789,
        className: 'Group 1',
        isActive: 0,
        students: [77, 22],
        selected: []
    }
];

App.Students = DS.Model.extend({
    first: DS.attr('string'),
    last: DS.attr('string'),
    classes: DS.hasMany('ClassGroup')
});

App.Students.FIXTURES = [
    {
        id: 11,
        first: 'Student',
        last: 'One',
        classes: [123]
    },
    {
        id: 22,
        first: 'Student',
        last: 'Two',
        classes: [123, 789]
    },
    {
        id: 33,
        first: 'Student',
        last: 'Three',
        classes: [123]
    },
    {
        id: 44,
        first: 'Student',
        last: 'Four',
        classes: [123]
    },
    {
        id: 55,
        first: 'Student',
        last: 'Five',
        classes: [123]
    },
    {
        id: 66,
        first: 'Student',
        last: 'Six',
        classes: [456]
    },
    {
        id: 77,
        first: 'Student',
        last: 'Seven',
        classes: [456, 789]
    },
    {
        id: 88,
        first: 'Student',
        last: 'Eight',
        classes: [456]
    },
    {
        id: 99,
        first: 'Student',
        last: 'Nine',
        classes: [456]
    }
];

My controller looks like this:

var IndexController = Ember.ArrayController.extend({
    actions: {
        isActiveTog: function(id){
            console.log(this);
            console.log(this.store.get('model'));

            var getter = this.get('classgroup');
            console.log(getter);
        }
    },
    classCount: function(){
        return this.get('length');
    }.property('@each')
});

export default IndexController;

This is our router:

import Students from "appkit/models/students";
import ClassGroup from "appkit/models/classgroup";

export default Ember.Route.extend({
    model: function() {
        return this.store.find('classgroup');
    },
    setupController: function(controller, model){
        this._super(controller, model);

        controller.set('students', this.store.find('students'));
        controller.set('classgroup', this.store.find('classgroup'));
    }
});

Here is the each block within our handlebars template (I removed the rest as it makes it too bulky):

{{#each classgroup}}
            <li id="c_{{unbound this.id}}" class="classMenu manageMenuWidth">
                <span class="classSA" id="c_{{unbound this.id}}_sas"><input type="checkbox" name="c_{{unbound this.id}}_chk" id="c_{{unbound this.id}}_chk" /></span>
                <span id="c_{{unbound this.id}}_nam" {{bind-attr class=":classLayout isActive:activeSelection"}} {{action "isActiveTog" this.id on="click"}}>{{unbound this.className}}</span>
                {{#view 'toggleclass'}}<span class="togControl" id="c_{{unbound this.id}}_tog"></span>{{/view}}
            </li>
            <ul id="c_{{unbound this.id}}_sts" class="students manageMenuWidth">
                {{#each students}}
                    <li class="student" id="s_{{unbound this.id}}_c_{{unbound classgroup.id}}">
                        <span class="studentChk" id="s_{{unbound students.id}}_c_{{unbound classgroup.id}}_img">{{unbound this.last}}, {{unbound this.first}}</span>
                        <input type="checkbox" name="s_{{unbound students.id}}_c_{{unbound classgroup.id}}_chk" id="s_{{unbound students.id}}_c_{{unbound classgroup.id}}_chk" />
                    </li>
                {{/each}}

I included as much of our code as possible because I'm still very new to Ember and I want to make sure that you have all the information you need to help answer this question, even if it means giving you too much.

So that you aren't digging too much, here is some more information. In the handlebars template there is a line {{action "isActiveTog" this.id on="click"}} within our span.

This will call a function in our controller, isActiveTog, which we would like to use to toggle the value of isActive in the model for whichever "classgroup" record was clicked in the each loop.

For example, the user clicks on "Class 1", which has an action call to isActiveTog, passing in ID = 123. I want my action in the controller to toggle value of ClassGroup[123][isActive] from 0 to 1, or vice versa.

I can say that my controller is being called correctly, because I am able to put {{classCount}} in my template and see a "3" in the output. Thus, the problem is that I am unable to figure out a way to toggle the value in the controller.

How can I use this.store.find() to search for a classgroup row with an ID equal to whatever was passed to the action, and then access the isActive value of that class record. Then I need to use this.store.set() to write back to model.

Was it helpful?

Solution

It seems like you're coming from a jQuery background because of how you're trying to embed the id of your ClassGroup records into the DOM via the template (nothing wrong with that of course as that's what I was doing when I first started with Ember coming from a jQuery heavy background). If you notice yourself doing this you're probably doing something in a non-ember way. Generally, you should only be accessing DOM elements by ID if you need to integrate some 3rd party jQuery library.

Okay, so now you know what to look out for how should we accomplish what you want in the Ember way?

There are probably several ways to go about it but what works well for me is to create another controller for each individual ClassGroup that extends ObjectController instead of ArrayController. So you would have

ClassGroupsController (ArrayController) -> ClassGroupController (ObjectController)

Notice that the array controller is pluralized. What this allows you to do is to manage the state of each individual ClassGroup record because each ClassGroup record now has its own controller and its own view.

So First create an array controller that manages the collection of ClassGroups:

App.ClassGroupsController = Ember.ArrayController.extend({

});

The ClassGroupController (Object) should then have a view which listens for click events. When we get a click event we trigger an action on the controller (ClassGroupController) called selected.

App.ClassGroupView = Ember.View.extend({
    templateName: 'classGroup',
    /*
    By specifying the tagName for this view we are telling ember
    we want to wrap the view in a <tr> element.  This is because
    each classGroup is going to be a row in our table.  By default
    each view is wrapped in a <div>
    */    
    tagName: 'tr',
    /*
    This is the click handler that is called whenever the user
    clicks on this view (in this case one of our <tr> elements
    that is displaying our ClassGroup Model for us.  The event
    parameter is the jQuery event object.
    */
    click: function(event){
        /*
        We want to forward this event to the controller where
        we can do some logic based on the fact that it was clicked.        
        */
        this.get('controller').send('selected');   
    }
});

Now we create our ObjectController for ClassGroup which is waiting and listening for the selected action which is being sent from the ClassGroupView when the user clicks on that individual ClassGroup.

App.ClassGroupController = Ember.ObjectController.extend({    
    actions:{
        /*
        This is the action we received from our view which was
        based on the user clicking on one of the <li> elements
        representing a ClassGroup Model
        */
        selected: function(){            
            console.info("You selected: " + this.get('className'));    
            /*
            Now we can easily toggle the isActive state of our model
            */
            this.toggleProperty('isActive');
            console.info("My isActive state is now: " + this.get('isActive'));
        }
    }
});

Now we need to connect this up in our template.

{{#each itemController="classGroup"}}
    {{view App.ClassGroupView}}  
{{/each}}

Notice how I specify that I want to use the classGroup as an itemController for my ArrayController. Here I specify it directly in the template. You can also specify it in the ArrayController itself using the itemController property. So for example:

App.ClassGroupsController = Ember.ArrayController.extend({
    itemController: 'classGroup'   
});

Here is a working JSFIddle using your fixtures to demonstrate the entire concept:

http://jsfiddle.net/NQKvy/872/

OTHER TIPS

To directly answer your question

you can find a record by id and modify it using

this.store.find('classgroup', id).then(function (classgroup) {
    classgroup.toggleProperty('isActive');
});

However in your case you don't really need to since you can just pass the record

so in your view use

{{action "isActiveTog" this on="click"}}

instead of this.id and then just directly modify it in the controller

isActiveTog: function(classgroup){
    classgroup.toggleProperty('isActive');
    // classgroup.save();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top