Bind to a value that will be fetched from an API - ember.js

I have an endpoint that returns a list of artists (json data).
And an endpoint that returns a specific property given an id.
What I would like to do is to iterate through all the artists and display one or more properties in the template to the user but the property should only be fetched from the API if it is bound in the template.
In my ArtistsRoute, I set the model to be all those artists fetched by calling getJSON...
I want, somehow to be able to fetch a property for an artist and display it (through binding).
The Properties map could be stored in the ArtistController maybe.
I could not find a good example for this. Any help is appreciated!
Template example:
Name is on the artist object itself, but the Properties object has been created manually. So in the ArtistController it could be initialized to empty:
Properties = {}
And then it sets Propertes['ShortName'] = to the fetched value.
<ul>
{{#each}}
<li>
{{Name}}
{{Properties.ShortName}}
<img {{bind-attr src=Properties.MainImage}} />
</li>
{{/each}}
</ul>
Should I use a function instead as a property or a Handlebars helper? Like:
{{Property this 'ShortName'}}
where 'this' is the ArtistController and 'ShortName' is the property to fetched. The property id can be calculated through the ArtistController and propertyName.
function(tag, propertyName) {
Ember.$.getJSON('/Properties/' + tag.Id + '_' + propertyName).then(function(response) {
var propertyValueToBind = response.Value; // This is the value I want to display in the template.
});
}
Then the Property function has to know when to rerender the template (once the property has been fetched from the API).

Firstly you'll want to generally avoid properties that are uppercase. They are usually considered global properties, and not in scope.
Ember lazy loads computed properties by default, so adding that functionality to a computed property would be an excellent way to go.
someProperty: function(){
var promise = Ember.$.getJSON('/Properties/buildupurl');
return Ember.ObjectProxy.extend(Ember.PromiseProxyMixin).create({
promise: promise
});
}.property()
Then in your template someProperty would be accessed like this
{{someProperty.value}}
And would be asynchronously populated.
Example: http://emberjs.jsbin.com/OxIDiVU/769/edit

Related

Ember.js/handlebars how to bind variable passed to a component to a model

Let's say I have a model called "Article" with only one property called "title"...
I could write hanblebars to edit the article property like this:
<span>Title</span><span>{{input value=title}}</span>
And Ember/handlebars magically binds the value of that input box to the "title" property value.
No problem there. But I am working on a project in which I will need to generate the handlebars code dynamically, based on a model definition model.
For example, I will not know there is a property called "title", but would have instead to loop into a "modelFields" list in the model definition model.
So, the handlebars will be something like this:
Looking at the code below:
{{#each modelField in modelFields}}
<span>modelField.displayName</span><span>{{input value=modelField.propertyName}}</span>
{{/each}}
The result HTML for the "title" property will be:
<span>Title</span><span><input value="title"></span>
Now here is my question, is there a way to have the value coming dynamically from propertyName (title, in this example) to be handled by ember as a binding property title, instead of a string title?
To clarify, is there a way for the result of this:
{{#each modelField in modelFields}}
<span>modelField.displayName</span><span>{{input value=modelField.propertyName}}</span>
{{/each}}
to be treated as this (title is a binding property):
<span>Title</span><span>{{input value=title}}</span>
instead of this (title is a string):
<span>Title</span><span><input value="title"></span>
?
I tried with views,components, with no luck.
I've found answer in another post that help me find the solution. The post is:
Ember.js: TextField with dynamic binding
Although, for my purposes, I had to tweak his/her solution a little bit:
Ember.Handlebars.helper('dataTextField', function (key, options) {
options.hash.valueBinding = 'controller.' + key;
return Ember.Handlebars.helpers.input.apply(this, [options]);
});
and in the template I call:
{{#each modelField in modelFields}}
<span>{{modelField.displayName}}</span>
<span>
{{dataTextField modelField.name}}
</span>
{{/each}}

Ember Checkbox -- Accessing model property from different controller

I have a checkbox on a modal popup and need it to be checked based on the value on an unrelated model. The model is Team and the property I need to access is called syncAccount (boolean), so the input helper would likely look something like this:
{{input type="checkbox" name="syncAccount" checked=team.syncAccount }}
How can I access or bind to team.syncAccount from the modal? I have a ModalImportController, but no associated route. Is there some way in this controller I can assign a property that looks up or binds to the value of syncAccount for the current team (and is updated as they toggle it)?
Similarly, I need toggling the checkbox to send an update request for this field. Will this need to be an ajax request, or is there some way to set the model used by the checkbox to point to a team so that I can call #model.save()?
To get access to a property from another controller you first need to include that via needs like so:
App.ModalImportController = Ember.ArrayController.extend({
needs: "team",
teamController: Ember.computed.alias("controllers.team"),
});
then you would have access to it's properties like so:
// still in ModalImportController
syncAccount: function() {
return this.get('teamController.syncAccount');
}.property('teamController.syncAccount')
I haven't tested it now, but that's the way I did it in a slightly different setup.
source:
[http://guides.emberjs.com/v1.13.0/controllers/dependencies-between-controllers/][1]
For toggeling to send an update request I use:
syncAccount: Ember.computed('model.syncAccount', {
get: function() {
return this.get('model.syncAccount');
},
set: function(key, value) {
this.set('model.syncAccount', value);
// do all you need to do via AJAX here
return value;
}
})
note, that you also GET the value from here, so change your input-helper to:
{{input type="checkbox" name="syncAccount" value=syncAccount }}

how to inject a store into a component (when using localstorage adapter)

Ember docs say to define a store like this
MyApp.Store = DS.Store.extend();
If you are looking up records in components, this doc says you can inject the store into the component like this
// inject the store into all components
App.inject('component', 'store', 'store:main');
However, I am using the local storage adapter which I define like this
App.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'my-namespace'
});
Therefore, I don't know how to inject this into the component (where I need to look up a record) following the above instructions.
Following the instructions of this SO answer, I tried to inject the store into a component by passing it in like store=store and/or store=controller.store
<li> {{my-component id=item.customid data=item.stats notes=item.notes store=store}} </li>
or
<li> {{my-component id=item.customid data=item.stats notes=item.notes store=controller.store}} </li>
The goal was then to be able to do this in an action in the componeent
var todo = this.get('store');
console.log(todo, "the new store");
todo.set('notes', bufferedTitle);
console.log(todo, "todo with notes set");
todo.save();
However, todo.save(); always triggers
Uncaught TypeError: undefined is not a function
Notice that I logged the store? this is what it shows
Class {_backburner: Backburner, typeMaps: Object, recordArrayManager: Class, _pendingSave: Array[0], _pendingFetch: ember$data$lib$system$map$$Map…}
If i inspect it(by opening the tree, which isn't shown here), it does indeed show that notes were set via todo.set('notes', bufferedTitle); however, it doesn't have any of the other attributes of my model that I defined for the index route, and this object doesn't have a 'save' method. Therefore, it doesn't seem to be the actual store, but rather just some backburner object.
I got the same results trying this SO answer where it says to get the store of the targetObject
var todo = this.get('targetObject.store');
Note, I also tried this, i.e. setting the store to be the store of the item.
<li> {{my-component id=item.customid data=item.stats notes=item.notes store=item.store}} </li>
It should be noted that if I set the store in the component, I can print the store on the page by doing {{store}} which gives me
<DS.Store:ember480>
but I can't do var todo = this.get('store'); in the action that handles the click even in the application code.
Question, using the localStorage adapter, how am I able to look up a record in a component (with the aim of then being able to alter the record and then save it again)
Note, if it's important, I define a model for the (index) route like this
App.Index = DS.Model.extend({
title: DS.attr('string'),
version (unfortunately I don't know what version of Ember data or the adapter I'm using)
Ember Inspector
1.7.0
Ember
1.9.1
Ember Data
<%= versionStamp %>
Handlebars
2.0.0
jQuery
1.10.2
Update in response to request for more info
The code that sets up the problem is very simple.
here's the router (with a bad name for the resource :)
App.Router.map(function(){
this.resource('index', { path: '/'});
}
Here's the route that gets the record to use in the Index route
App.IndexRoute = Ember.Route.extend({
model: function{
var resource = this.store.find('index');
return resource;
}
});
I have an Index Controller which does nothing in particular for the component (unless I should be defining methods on the Controller that get triggered by component events)
In the html, I do this with handlebars to pass data to the component
{{#each item in items}}
<li> {{my-component id=item.customid data=item.stats notes=item.notes store=store}}
{{/each}}
Then, in components/my-component, I have a label that when clicked is supposed to trigger an action that will let me edit one of the attributes on the model
<label> {{action "editTodo" on="doubleClick">{{notes}}</label>
that click triggers this code in App.MyComponent, which triggers the error that prompted this question
var todo = this.get('store')
todo.set('notes', bufferedTitle);
todo.save()
IMHO injecting store into components is not the best idea... By design, components should be isolated and shouldn't have any knowledge about the store.
In the doc you've given, it's written: In general, looking up models directly in a component is an anti-pattern, and you should prefer to pass in any model you need in the template that included the component.
However, if you really need it for some reason, then why not just to pass the variable store to the component?
{{my-component store=store}}
Then, you can pass the store from your controller only in the components where you really need that.
Injecting the store in all your components will most likely lead you to the bad design (although it seems tempting at first).
Here's an updated answer for Ember 2:
Ember Data's store is now a Service, and we can easily inject it into all Components via an Initializer, e.g. app/initializers/inject-store-into-components:
export function initialize(application) {
application.inject('component', 'store', 'service:store');
}
export default {
name: 'inject-store-into-components',
initialize,
}
Then, in your Components, you can access the store with this.get('store'). The obviates the need to directly pass the store as an argument to Components, which requires a lot of boilerplate in your templates.
Whilst the accepted answer is sensible for simple applications it is perfectly acceptable to inject a store into a component if that component doesn't have a relationship with the url, like side bar content or a configurable widget on a dashboard.
In this situation you can use an initializer to inject the store into your component.
However, initializers can be a pain to mimic in testing. I have high hopes that the excellent Ember.inject API that is testing friendly will extend beyond services and accommodate stores. (Or that stores will simply become services).
According to this docThe preferred way to inject a store into a component is by setting a store variable to the record, for example
{{#each item in arrangedContent}}
<li> {{my-component store=item}} </li>
{{/each}}
Then in application code, you can do
var store = this.get('store');
store.set('todo', bufferedTitle);

EmberJS - change how an object property is displayed (not tied to route's controller)

Within my Ember app, I have an object model, called Item, with a property 'price', which holds a float value, such as 20.5. When I use the property in a template {{price}}, I would like for it to be formatted to look like so: $20.50.
This object is not the model that is tied to the controller at the route, but rather an element in an Ember Array, called items, which is a property of the model at the route. So I have something like this in my template:
{{#each item in items}}
{{item.price}}
{{/each}}
The problem seems pretty simple, but I can't really find a solution. The idea is that I do not want to change the name of the property to get it to look the way I want, since I could make a computed property that formats the price property, but then I would have to use a new name in the templates.
What you could do is write your own custom handlebars helper.
For your use case it would look something like this:
Ember.Handlebars.registerBoundHelper('formatNumber', function(value) {
return "$" + value.toFixed(2);
});
And then use it in your templates this way:
{{#each item in items}}
{{formatNumber item.price}}
{{/each}}
Please see here for a working jsbin.
Hope it helps.

ember.js #each order by property

I have an array of Ember.Objects which is displayed by Handlebars {{#each}} helper which I want to be sorted by an property of those objects everytime the array changes.
So something like this:
var arr = [
Ember.Objects.create({
position:0,
label:"foo"
}),
Ember.Objects.create({
position:1,
label:"bar"
}),
];
And the handlebar
{{#each arr}}
<div class="label">{{label}}</div>
{{/each}}
So if I update the positions and the bar object becomes first, I want the view to be updated. Can I depend the {{#each}} Helper on an property?
You have to use an ArrayController proxy on your data, and set the sortProperties attribute. Then, use the controller as the each data source.
Sample # http://jsfiddle.net/MikeAski/Epjqp/
Using the controller as the data source provides an arranged content. Take care not to use directly the controller's content, as it is the raw source data...