Can't use external images


#1

I’m working on an app that calls an external API, this api gives me some info along with the url to an image that I would like to use in my template. When I try to use it I get this error.

Assertion failed: Attributes must be numbers, strings or booleans, not [http://yummly-recipeimages-compressed.s3.amazonaws.com/Spinach-artichoke-scalloped-potatoes-recipe-307879-271887.s.jpg]

How can I get this to work?


#2

ohh… ok, the [ ] brackets suggest that the return value is an array.

You can either pop that array and get the first image or use the {{each}} helper to show multiple images.

Let me know if you need any further help on this.


#3

You’re right, I didn’t see the square brackets, that’s strange because all the results only have one available. So right now I’m using this to try to get the image in the SRC…

<img class="recipe-image" {{bindAttr src="smallImageUrls"}} />

And adding a [0] to the end doesn’t work so it would seem that I need to somehow handle that before handlebars attempts to place it correct? Do I need to workout a fixture or something to be able to serialize it first?

All this Ember Data and API stuff it getting me a little confused, I can’t seem to find something that will show me how to get a fixture set up and what the ember way of iterating over a JSON stack is.

Right now I have a variable that I’ve just plopped in a JSON stack that got returned from a url I put in the address bar, I wasn’t able to get the API to actually call from Ember because of the cross origin stuff but I can’t shake this feeling that I’m not doing it right.

Do you have a link to an article or some other resource that can tell me a bit more about using API’s with ember and how to correctly work with a stack of JSON data?


#4

Yes, you need to use what’s called a computed property. A computed property observes changes in another property and returns a value. For example

App.ApplicationController = Ember.Controller.extend({
    smallImageUrls: function() {
        var image = this.get('image');
        if ( Em.isArray(image) && image.length ) {
            return image[0];
        }
        return image;
    }.property('image') // <- specify what property you're observing
});

Ignore the Ember Data stuff for now. Here is a link that will show you how to get data from a JSON into your app. My next article is going to be on this, but that might not be until end of next week.

Let me know how it goes.


#5

Okay I think I understand that. The artile you sent is a huge help, thanks so much. With it I have been able to successfully call the API with JSONP and list out my recipes. However you’re code I’m not quite understanding. How does it know where to .get() from? From what I’ve been picking up on with all these tutorials is it should be the router which gets it from the model correct?

So with that information I thought something like this would work but I’m still not able to get the image to work despite everything I’ve tried…

App.Recipe = Ember.Object.extend();

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

App.IndexController = Ember.ArrayController.extend({
    smallImageUrls: function() {
        var image = this.get('image');
        if ( Em.isArray(image) && image.length ) {
            return image[0];
        }
        return image;
    }.property('image') // <- specify what property you're observing
});

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

Also not sure if this will help but here is what a sample json stack looks like from the API I’m using…

https://developer.yummly.com/documentation/search-recipes-response-sample

Thought maybe it would give some perspective on how it comes in from the server.

Thanks o much for your help dude, you’re awesome, and I appreciate it.


#6

My original code assumed that you were using the ObjectController, but you’re using ArrayController, which is correct.

You’re missing one small piece of this puzzle to make it work.

The IndexController, which is an array controller, proxies an array of objects that are instances of Recipe. When the template is handing each of the images, it should have access to the object and its properties. All you should have to do is move smallImageUrls property up to App.Recipe class.

Also, on Ember Sherpa in the CRUD tutorial, I should how to use Ember Inspector to see what’s happening with the template. This is very helpful to understand what’s hooked to what.

Give that a try.

PS: its my pleasure, glad I can help.


#7

Hmm still seems to be not working. Correct me if I’m wrong but in my console the error looks like what the template is being handed is still in array format?

Also here is my handlebars template, I’m starting to lean towards the fact that maybe it’s causing a problem

<section id="recipes">
	{{#each}}
		<li class="recipe odd">
        	<a href="recipes/cottagepie.html" >
        	<div class="recipe-overlay">
            	<ul class="recipe-details">
                	<li class="recipe-cals"><img src="images/icon-cals.png">387 Calories</li>
                    <li class="recipe-time"><img src="images/icon-clock.png">50 Minutes</li>
                    <li class="recipe-button"><img src="images/view-recipe.png"></li>
            	</ul>
            	
            </div>
            <img class="recipe-image" {{bind-attr src="smallImageUrls"}} />
            <h2 class="recipe-title">{{recipeName}}</h2>
            <img class="recipe-rating" src="images/icon-rating.png">
            
            <ul class="recipe-ingredients">
            {{#each item in ingredients}}
            	<li class="ingredient">
                	<p>{{item}}<p>
                </li>
            {{/each}}
            </ul>
            <div class="clearl"></div>
            </a>
        </li><!-- /recipe -->
	{{/each}}
</section>

It can successfully get the title and ingredients list just fine.

Also as a reference here is what I working Ember code looks like…

App.Recipe = Ember.Object.extend();

App.Recipe.reopenClass({
    findAll: function(){
	return $.getJSON('**JSON URL HERE**').then(
		function(response) {
			var matches = Em.A();
			response.matches.forEach(function(recipe){
				matches.pushObject(App.Recipe.create(recipe));
			});
			return matches;
		});
    },
     smallImageUrls: function() {
         var image = this.get('smallImageUrls');
         if ( Em.isArray(image) && image.length ) {
             return image[0];
         }
         return image;
     }.property('smallImageUrls') // <- specify what property you're observing
});

App.IndexRoute = Ember.Route.extend({
    model: function(){
	return App.Recipe.findAll().then(function(response){
		return response;
	});
	console.log(response);
    }
});

#8

When you use reopenClass, you’re defining static methods. Static methods don’t have access to the scope of the object that you need. smallImageUrls has to be an instance method. To make it instance method, pass smallImageUrls as a property to Ember.Object.extend like

App.Recipe = Ember.Object.extend({
    smallImageUrls: function() {
         var image = this.get('image');
         if ( Em.isArray(image) && image.length ) {
             return image[0];
         }
         return image;
     }.property('image') // <- specify what property you're observing
});

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

This code -> .property('image') tells Ember that this property observes the value of image property which will keep the src of the image in sync with the value of image. Inside the function, you have to call this.get('image') to get the value of image.

You should use the debug version of Ember ( don’t copy and paste the contents, do save as ) when developing and I was referring to Ember Inspector.


#9

Still not working, still getting the assertion failed in the console for the images. Ember inspector doesn’t really have anything useful to me at this point since it just shows me what I already know and what should be working but is not.


#10

Can you show me your latest version of the code? or better yet, create a JSBin on http://emberjs.jsbin.com ?


#11

I didn’t know this was a thing, so much easier. I have pasted everything and stripped out all the script and style calls (is that what I’m supposed to do?) and now there seems to be no output at all


#12

Here you go http://emberjs.jsbin.com/AguxEVA/6/edit

I didn’t realize that smallImageUrls was a property from JSON that’s returned by the API endpoint. I created another method called smallImagSrc and I’m using it to get the value of src attribute.


#13

So was it just the fact that the property and the value from json were named the same thing that was causing the problem? I wondered at some point if that might have been it.


#14

Yeah, we needed to take the original value, change it and return it. So, we needed another property that would do that. I thought smallImageUrls was already that property.


#15

Awesome, I’m up and running now with Images! Thanks dude!


#16

You’re welcome. Let me know if you need help with anything else.


#17

Got another quick question for you here. If I get back something that looks like this…

"images": [{
    "imageUrlsBySize": {
        "90": "http://lh6.ggpht.com/BGZKPO1f6GyBFlSQd2HUYC7fXogjhDSADrISJVO4AuPKDlGkT0Do2mxZroRgQV4L8RHQ_7W0eWCQPBzcmTu64g=s90-c",
        "360": "http://lh6.ggpht.com/BGZKPO1f6GyBFlSQd2HUYC7fXogjhDSADrISJVO4AuPKDlGkT0Do2mxZroRgQV4L8RHQ_7W0eWCQPBzcmTu64g=s360-c"
    },
    "hostedLargeUrl": "http://i.yummly.com/Spinach-artichoke-scalloped-potatoes-recipe-307879-271887.l.jpg",
    "hostedSmallUrl": "http://i.yummly.com/Spinach-artichoke-scalloped-potatoes-recipe-307879-271887.s.jpg"
}],

And I want to grab the “360” property I need to map over the first item in the images array first to have access to the objects inside and then I can do my usual object traversing correct?

I’m having a little trouble getting that to work.

fullImage: function() {
    var orig = this.get('images[0].imageUrlsBySize.360');
    return orig;
 }.property('images[0].imageUrlsBySize.360') // <- specify what property you're observing

It’s not returning anything, no errors either.


#18

You don’t need to observe that value. You actually don’t really need to observe it at all if your value is not going to change. Obeserving only makes sense if your value will change in the lifetime of that object.

fullImage: function() {
    var images = this.get('images');
    return images[0]['imageUrlsBySize']['360'];
 }

That should do the trick


#19

So could I do the same thing right in the template then when I’m binding the src attr?


#20

It’s a bit different in the template. You have to use a different syntax. It would look something like {{images.[0].['imageUrlsBySize'].['360']}}