16
votes

i have been struggling with this for the past few hours, i am making an ember application for creating an invoice. i am using ember component (textfield) to modify the fields using the keyboard, but since actions are not sending back to the relevant controller, i cannot save the records on focusOut or insertNewLine and nothing is happening. i am using :

Ember      : 1.1.2 
Ember Data : 1.0.0-beta.3 
Handlebars : 1.0.0 
jQuery     : 1.9.1

this is supposed to look like this: https://dl.dropboxusercontent.com/u/7311507/embercomponent.png

The problem seems to lie within either the controller or the component, it seems i am missing something.

the console.log function gets called on the component, the sendAction call never works...

Thanks for the help.

ItemsRoute

App.ItemsRoute = Ember.Route.extend({
    renderTemplate: function() {
          // Render default outlet   
          this.render();
          // render extra outlets
          this.render("client", { outlet: "client", into: "application"});
      },
      model: function() {
        return this.store.find('item');
      }
    });

ItemsController

App.ItemsController = Em.ArrayController.extend({
    actions: {
      createItem: function () { // NEVER GETS CALLED FROM COMPONENT
        var title = "Nouvel élément"

        // Create the new Todo model
        var item = this.store.createRecord('item', {
          desc: title,
          qty: 1,
          price: 0
        });

        // Save the new model
        item.save();
      }
    },
    totalCount: function(){
        var total = 0;
        this.get('model').forEach(function(item){
            total += item.get('totalprice');
        });
        return total;
    }.property('@each.qty', '@each.price')
});

ItemController

App.ItemController = Em.ObjectController.extend({
    didInsertElement: function(){
        this.$().focus();
    },
    actions: {
        testAction: function(){ // NEVER GETS CALLED FROM COMPONENT
            console.log("controller recieved call for testAction");
        },
        saveItem: function(value) {
            this.get('model').save();

        },
        removeItem: function() {
            var item = this.get('model');
            item.deleteRecord();
            item.save();
          },
    },
    isHovering: false
});

Items Template

<script type="text/x-handlebars" data-template-name="items">
      <!-- ...  -->

      <tbody>
      {{#each itemController="item"}}
        {{view App.ItemView }}
      {{/each}}
      </tbody>

      <!-- ... -->
  </script>

ItemView template

<script type="text/x-handlebars" data-template-name="item">
    <td class="desc">{{edit-item value=desc}}</td>
    <td class="qty">{{edit-item-number value=qty }}</td>
    <td class="">{{edit-item-number step="25" value=price}}</td>
    <td class="totalprice">
      {{ totalprice }}
      <div class="delete-item" {{bindAttr class="isHovering"}} {{action "removeItem" on="click"}}>
        <i class="icon-trash"></i>
      </div>
    </td>
  </script>

Views / Components

App.ItemView = Em.View.extend({
    templateName: "item",
    tagName: "tr",

    mouseEnter: function(event) {
        this.get('controller').set('isHovering', true);
    },
    mouseLeave: function(event) {
        this.get('controller').set('isHovering', false);
    }
});

App.EditItem = Em.TextField.extend({
    becomeFocused: function() {
        this.$().focus();
    }.on('didInsertElement'),

    insertNewline: function(){
        console.log('Tried to insert a new line'); // WORKS
        this.triggerAction('createItem'); // DOESN'T WORK
    },

    focusOut: function(){
        console.log('Focused the Field Out') // WORKS
        this.triggerAction('testAction', this); // DOESN'T WORK
    }

});

App.EditItemNumber = App.EditItem.extend({
    becomeFocused: null,
    attributeBindings: ["min", "max", "step"],
    type: "number",
    min: "0"
});

Ember.Handlebars.helper('edit-item', App.EditItem);
Ember.Handlebars.helper('edit-item-number', App.EditItemNumber);
2

2 Answers

34
votes

You should define where the action will be sent when defining a component in the template.

{{edit-item value=desc createItem='someactionoutside'}}

this is in case the action has a different name in different places (since this is a component, it could have different meanings in different locations). It also avoids clashing actions/triggered actions. Think of the idea of having two instances of a component, and each one should trigger a different action in the controller

{{edit-item value=desc createItem='createUser'}}
{{edit-item value=desc createItem='createShoppingCart'}}

in your case you can just write

{{edit-item value=desc createItem='createItem'}}

And inside your component you would call

this.sendAction('createItem', param1, param2, ....);

If you don't care about it being self contained like a component, you might want to just use a view and not a component. You can register it as a helper and it'd look just as pretty.

Em.Handlebars.helper('edit-item', Em.View.extend({
  templateName: 'some_template',

  actions: function(){
   // etc etc
  } 

})); 

{{edit-item}}
2
votes

As an addition to the nice answer by @Kingpin2k you can also define your action's name within the component if it is always the same and you want to simplify the syntax of including your component. ie.

import Ember from 'ember';
export default Ember.Component.extend(SchoolPlayerProspectMixin, {

    //Here we define an attribute for a string that will always be the same
    transitionToRoute: "transitionToRoute",

    somethingChanged: function(){
        console.log( "OMG something changed, lets look at a post about it!" );

        //Here we are passing our constant-attribute to the sendAction.
        self.sendAction('transitionToRoute', "post.show", post );

    }.observes('changableThing'),
});

In this example the component uses the parent controllers transitionToRoute method to change routes, even though the component may not be a button/link. For example, navigating on change of a component containing several select inputs, or just changing route from within a component in general.