Passing some data from one controller to another


#1

I have The index controller set up to pull some values from a number of input fields and create an ember array with these values. I then have an object model set up (thanks to tarasm) to make an API call. I need a way to get that array from the index controller to the new page controller (in this case the recipes controller)

I’ve been searching and searching and came across a few things talking about using needs: and trying to access the index controller that way but I can’t seem to get it working (maybe a fault on my end).

Here is what the index controller looks like.

App.IndexController = Ember.ArrayController.extend({
    actions: {
	submit: function(ingrs){
		var data = [];
		ingrs.map(function(item){
			data.push(item.getProperties(['ingredient']));
		});
            this.transitionTo('recipes');
	}
    }
});

And the recipes controller.

App.RecipesController = Ember.ArrayController.extend({
    needs: ['index'],
    searchQuery: function(){
        var query = this.get('controllers.index');
        return query.get('data');
        console.log(query);
    }.property('cotnrollers.index.data')
});

As well as the recipes route.

App.RecipesRoute = Ember.Route.extend({
	model: function(data){
        return App.Recipe.findAll().then(function(response){
            return response;
        });
    }
});

It all references this object that I have set up (less the actual API URL).

App.Recipe = Ember.Object.extend({
    image: function() {
    var orig = this.get('imageUrlsBySize.90'),
        imageLarger = orig.replace('90-c', '360-c');

        return imageLarger;
   }.property('imageUrlsBySize.90') // <- specify what property you're observing
});

App.Recipe.reopenClass({
    findAll: function(query){
        return $.getJSON('**JSON API**').then(
        function(response) {
            var matches = Em.A();
            response.matches.forEach(function(recipe){
                matches.pushObject(App.Recipe.create(recipe));
            });
            return matches;
        });
     }
});

I’m just having trouble wrapping my mind around how the call tot he model in the recipes route can have access to that array created in the index controller.

I’m thinking (and hopefully I’m on the right track) that I need to somehow make that a computed property so I can access it that way, but how I go about doing that is beyond me. I’ve done what I thought would work in just defining an empty property on the recipe object and then trying to push to that rather than the variable but it did not work.


#2

You have a few errors in this code:

App.RecipesController = Ember.ArrayController.extend({
    needs: ['index'],
    searchQuery: function(){
        return this.get('controllers.index.data');
    }.property('controller.index.data')
});

Actually, I have an example of what you’re trying to do on Ember Sherpa. Its a work in progress article that shows how to do search with search keyword being accessible across controllers.


#3

Great article, I’ll have to try this, it looks fairly simple and straight forward anyways. Let me ask you something, I grabbed this functionality for adding multiple fields from a tutorial somewhere else and I’ve never really liked doing it that way because I figured there must be a better way. How would you do this, because it seems (and I’m not too sure what the return would look like) that having multiple inputs all binding to the same property will work just fine but I’m not sure about how I’m doing it now if it will work for this method.

App.InputFormView = Ember.View.extend({
    templateName: 'input-form',
    ingrs: [Em.Object.create({ingredient: ''})],
    actions: {
	newIngredient: function(){
		this.get('ingrs').pushObject(Em.Object.create({ingredient: ''}));
	}
    }
});

And in the view…

<script type="text/x-handlebars" data-template-name="input-form">
     {{#each view.ingrs}}
	{{view App.FocusField valueBinding="ingredient" classNames="ingredient-input"}}<br>
     {{/each}}
    <button id="ingredient-btn" {{action "newIngredient" target="view"}}>New Ingredient</button><br>
    <button id="submit-btn" {{action "submit" view.ingrs}}>Find Me Something to Eat!</button>
</script>

Is there a better and cleaner way to do this or is there nothing wrong with this? Obviously it seems that this will work just fine when coupled with your technique but it just seems pointless and kind of counter productive to be adding these values to a variable that does nothing other than serve as a way for the view to know how many inputs should be output to the template.


#4

Before I answer your question, can you tell me why it feels counter productive?

I ask because it might results in one two things, either we’ll find a better way to do it or you’ll gain a better understanding of Ember architecture.

So, why does this feel unnatural or counter productive?


#5

Well I guess the part that make sit feel that way for me is that if I’m not going to be using the data variable that the template checks I don’t see the point. But then again I’ve used counting variables in the past that essentially do the same thing. So maybe instead of pushing the value of the input into that array and having the template count that I can just pass a number that means nothing other than how many inputs should be on the page.

I think I’m just thinking this way because I know how I can do it in jQuery very easily and not have to observe a property to get it done but at the same time I want to try to do as much in ember since that’s what it’s there for.

If there is nothing wrong with it then so be it. I will eventually learn to understand that ember and jquery, while they go hand in hand, are very different. Ember has taken me for a loop just because I’ve never worked with anything remotely MVC minded at all so it’s been very eye opening to think about. I may just be feeling like it’s counter productive simply because it’s not the way I’m used to doing things, and that is something that eventually I’ll learn to work around.

Now that you’ve brought up this probing question (thanks for making me think) I can see how the way I’m doing it in ember is almost more preferred than the way I would do it in jQuery, because in jQuery I would have to inject HTML to the page and while this one doesn’t pose any sort of security risks I know mixing jQuery with HTML output should be avoided as much as possible and I’ve been thinking through that a lot more lately.

After talking it through my head about how Ember is doing this it just makes more sense really. Let the template determine how many inputs based on how many an array tells it to. Simple. I think it’s really the part where the variable fills with meaningless data that I don’t like but I see how easy that is to change to just a number or whatever so I don’t feel like I’m doing something pointless to make something that makes sense in the end.


#6

It’s done this way in Ember because it allows you to decouple concerns. When you’re writing code, you can think about one thing at a time and use Ember architecture as glue.

In this scenario, you have several mechanisms working together:

  • Logic that handle data in the controller ( adding/removing form groups )
  • Presentation by the template

In the middle, you have the content property, that’s the glue that Ember provides. It could be any other property but content is a convention.

You can learn a lot more about Ember architecture by watching A tale of two MVC’s by Yehuda Katz (30 min)


#7

Alright so I’m getting this to work sort of. How would I map over the property if I’m not using underscore?


#8

Sorry, I’m not getting your question, can you please say the same thing differently?


#9

Sorry.

In your “SearchResultsRoute”

You have this…

  model: function(params) {
    this.controllerFor('search').set('keyword', params.keyword);
    return _.range(0, 10).map(function(number){
      return Em.Object.create({
         name: params.keyword + number.toString()
      });
    }); 
  },

But I want to just map over the results of keyword without using underscore.js (the _.range part) and I can’t seem to figure out how to do that.

What I’ve done gets me an error that says…

Assertion failed: The value that #each loops over must be an Array. You passed Ember.Object:ember342 (wrapped in (generated search.results controller))


#10

If understand correctly, you want to iterate over the results in the template using each helper block. To do this, your model hook has to return an array. In my code, _.range returns an array, so it works. In your case, it will be something different.

What does your code look like?


#11

Well to this point I haven’t adapted it to my project, I just was typing it out and understanding it step by step. Basically I want exactly what you have, but I don’t want to range it, I was to just get everything that was input, weather that number is 5 or 50 items.


#12

If I understand correctly, that would depend on what you’re querying. If you’re querying an API endpoint, then you have to make another request. You can do that in the model or define another static method in App.Recipe. If getting a subset of content that you got previously than you could iterate over the content value. For example

model: function(params) {
   this.controllerFor('search').set('keyword', params.keyword);
   return this.controllerFor('recipies').get('content').filter(function(item) {
       return  item.get('title') === params.keyword;
   });
},

Here is how to use filter http://emberjs.com/guides/enumerables/#toc_filtering


#13

Hmm as I’m working through this it seems that maybe I need to take another look at how the user experience needs to work.

I was going to use multiple inputs but I can’t bind multiple inputs to the same property. So I could use one input and request the user to separate each input with a comma, but that gets transmitted to the route and thusly into the URL bar which is a problem no doubt.

The scenario calls for the user to land onto the index route, on that page there is an input (and possibly a button to add a new input to fill in multiple ingredients) and a submit button to submit those ingredients to an API call.

Maybe this is slightly more complicated than I first believed.