So I have a requirement that there should be a panel displaying a users unsubmitted widgets. Since it has to be in every view, I made into a component that accepts a binding to a count of unsubmitted widgets:
# components/unsubmitted-widgets.emblem
.well.text-center
a href="#" My Widgets (#{unsubmittedWidgetsCount})
button.small-button type="button" click="submitWidgets"
strong Submit Widgets
I was thinking that the query for the widgets API would go into the application controller, which all other controllers can bind to
App.ApplicationController = Ember.Controller.extend
unsubmittedWidgets: (->
#store.find('unsubmittedWidget', user: #get('currentUser'))
).property()
App.HomeController = Ember.Controller.extend
needs: ["application"]
unsubmittedWidgetCount: (->
#get('controllers.application.unsubmittedWidgets').toArray().length
).property('controllers.application.unsubmittedWidgets')
So this fires off the request and I get a result. However the view doesn't get updated automatically. The view shows My Widgets() on whatever screen I'm on, and when I transition to another route where the view is present, I get the real value, but when I go back to the original page it's still not displaying everything.
How would I actually go about appropriately binding the value on the page to the length of the returned record set?
When you create a property that has a collection as dependency, you must use #each, like so:
App.HeroesController = Ember.Controller.extend({
identities: [
Em.Object.create({ mask: 'Captain America', name: 'Steve Rogers', isAvenger: true }),
Em.Object.create({ mask: 'Iron Man', name: 'Tony Stark', isAvenger: true }),
Em.Object.create({ mask: 'Batman', name: 'Bruce Wayne', isAvenger: false }),
],
justiceLeague: function() {
var identities = this.get('identities');
return identities.filterBy('isAvenger', false).get('length');
}.property('identities.#each.isAvenger'),
avengers: function() {
var identities = this.get('identities');
return identities.filterBy('isAvenger', true).get('length');
}.property('identities.#each.isAvenger')
});
The #each will run your computed property code to get the count of items that match whenever one or more objects in the identities array gets the isAvenger property updated. For this example, it should show a count of two characters, considering one out of the 3 items has the filtered property set to false. The other list, watches the exact same path, but the "formula" is different, outputting the count of 1 character for the example above.
Related
The example code on the online demo site gives the following example:
{{md-select content=frameworks
value=framework
label="Framework"
prompt="Please choose..."
optionLabelPath="content.value"
optionValuePath="content" class="col s12"}}
Is frameworks is some sort of Array on the model/route? I tried defining frameworks like this: frameworks: ["Option 1","Option 2"]
and frameworks: [{text:"Option 1",value:"1"},{text:""Option 2"",value:"2"}] but I still get only the empty select element with the default placeholder.
How do the optionLabelPath and optionValuePath options work?
TLDR; How to configure the options (and associated values) on the material select element from ember-cli-materialize addon?
The content is the array to make options from.
organismContent: [
{ value: 'F', display_name: 'Fungi' },
{ value: 'A', display_name: 'Alveolata (alveolates)' },
{ value: 'B', display_name: 'Bryophyta (mosses)' },
]
In template you would use something like this
...
content=organismContent // Array to iterate over
optionLabelPath="content.display_name" // user sees this field
optionValuePath="content.value" // user picks this field
value=run.organism // where the user selected value goes to
...
...
content=organismContent // Array to iterate over
optionLabelPath="content.display_name" // user sees this field
xxx // No need value path here
selection=run.organism // user selects the whole object
...
Where optionValuePath is the objects property "value" which gets binded to value=blah . If you use selection=blah instead of value=blah it selects the whole object with "display_name" and "value". First usecase (value=) is when your object selection is string and the second one (selection=) would be when you're using foreign keys (belongsTo).
export default Ember.Route.extend({
model: function() {
var store = self.get('store');
return Ember.RSVP.hash({
organismContentFromServer: store.find('somemodel', 1) // you can access this via model.organismContentFromServer
});
},
setupController: function(controller, model) {
this._super(controller, model);
controller.setProperties({
organismContent: [], // This property is now accessible in template
});
}
});
Binding controller and model properties to component
{{analysis/analysis-data <--- This is a component
run=model.run <--- This is a model property
componentorganismContent=organismContent <-- This is a controller property
}}
now in component hbs
i can do componentorganismContent.length and it accesses controller's organismContent. Or if i do {{run}} it accesses model's hook "Run" If you get the last part ember will be much easier for you ;)
I've created a computed property that relies on all records in the store.
I've tried making the property update on adding/removing records with .property('todos.#each.id'), .property('model.#each.id'), .property('#each.id'), .property('#each') and other combinations, no luck so far. :( When i create new records, existing recrods' property would not update.
Here's a fiddle: http://jsbin.com/UDoPajA/211/edit?output
The property is otherTodos on the Todo controller. This property is used by the <select> dropdown list on the page (via {{view Ember.Select}}).
You're out of scope of the collection. You'll need to get access to the todos controller in order to have a computed property based off of its model. needs will handle this use case. http://emberjs.com/guides/controllers/dependencies-between-controllers/
Additionally to make an easy to access alias to the todos controller's model we use computed.alias. http://emberjs.com/api/#method_computed_alias
Todos.TodoController = Ember.ObjectController.extend({
needs:['todos'],
todos: Ember.computed.alias('controllers.todos.model'),
....
foo: function(){
}.property('todos.#each.id')
});
PS note of caution, in your code you are creating multiple instances of Ember Data filter, filter collections are meant to be live collections that are long living and update as records are added/removed from the store. You might just want to grab the model from todos and filter over it instead of creating a new store filter (which then avoids the async code as well, not that that is an issue).
Here's an implementation that would avoid that (no point in using it as a setter, you are only getting from it):
otherTodos: function() {
var model = this.get('model'),
thisId = model.get('id');
var todos = this.get('todos').filter(function (todo) {
return todo.get('id') !== thisId;
});
var selectContent = todos.map( function(todo){
var selectContent = {
title: todo.get('title'),
id: todo.get('id')
};
return selectContent;
});
return selectContent;
}.property('todos.#each.id'),
Here's an updated jsbin of your code: http://jsbin.com/UDoPajA/216/edit
I am making an app with ember.js and ember-model
I have a model named Plugin defined as follows:
Eme.Plugin = Ember.Model.extend
id: Ember.attr()
name: Ember.attr()
description: Ember.attr()
downloads: Ember.attr()
tags: Ember.attr()
Eme.Plugin.url = "/api/v1/plugins"
Eme.Plugin.adapter = Ember.RESTAdapter.create()
Eme.Plugin.collectionKey = 'plugins'
I want show the most downloaded in index.hbs ( i use ember-rails)
And i fetch data in IndexRoute 's setupController hook:
Eme.IndexRoute = Em.Route.extend
setupController: (controller, model)->
console.log Eme.Plugin.findAll().toArray()
controller.set 'plugins', Eme.Plugin.findAll()
Output :
[nextObject: function, firstObject: undefined, lastObject: undefined, contains: function, getEach: function…]
But in chrome console i execute Eme.Plugin.findAll().toArray(), i got the results as follows:
[{
__ember1377710636537: "ember404"
__ember1377710636537_meta: Meta
_dirtyAttributes: Array[0]
_reference: Object
_super: undefined
get _data: function() {}
isLoaded: true
isNew: false
set _data: function(value) {}
__proto__: Object
}, {
...
}, {
...
}]
In my IndexController have a computed property:
Eme.IndexController = Em.Controller.extend
mostDownloads:(->
# console.log #get('plugins').slice(0, 3)
#get('plugins').slice(0, 3)
).property('plugins')
and i iterate the mostDownloads but there is nothing to show, however when i output {{plugins.length}}, i can't get the count of all my data
Who can give a hand to me?
Plugins looks like an array and would need to use the .#each iterator like so:
Eme.IndexController = Em.Controller.extend({
// Code
}).property('plugins.#each')
Here is documentation on #each http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/
Regarding your array length, I've never had much luck using .length, for length I usually do
plugins.get('length')
Hope that helps!
I propose two changes to make you app working.
First
I assume since it's called plugins (plural) the call to .findAll() returns an array of plugins for this to work you should change your controller type to be an ArrayController. Then because you are using # aka. this in your computed property you should use the fat arrow => to have the right reference to this, so the resulting IndexController should look like:
Eme.IndexController = Em.ArrayController.extend
mostDownloads:(=>
# console.log #get('content').slice(0, 3)
#get('content').slice(0, 3)
).property('content.[]')
Notice also that we observe content.[] this will trigger whenever the content array changes, items added or removed etc. you could also use content.#each but this is better suited for when you need to observe changes to the properties of a plugin record in the array, e.g. content.#each.name.
Second
Now change also how you set the plugins collection on your controller, you should rather set the controller's content property since this is what it is for:
Eme.IndexRoute = Em.Route.extend
setupController: (controller, model)->
# console.log Eme.Plugin.findAll().toArray()
controller.set 'content', Eme.Plugin.findAll()
This line console.log Eme.Plugin.findAll().toArray() will not work the way you expect because when you call it it will give you a promise back and not the array that is still underway (async...).
And a last change, to print out the plugins length, use the afterModel hook of your IndexRoute, since this is the right time when the model promise has being resolved (the async operation has given back control to your app).
Eme.IndexRoute = Em.Route.extend
...
afterModel: (plugins, transition) ->
console.log plugins.get 'length'
...
Hope it helps.
I am new(to ember) and trying to build a search centric Ember App w/ Ember-data also. I wanted to change the url on the fly(based on search string) and the data should change automatically(on the fly). How to do it?
This is my not working code:
Emapp.Data = DS.Model.extend({
first_name: DS.attr('string')
}).reopenClass({
url: Emapp.MyURL.get('url')
});
Emapp.MyURL = Em.Object.create({
urlParam: 'John',
url: function()
{
return 'emb/data.php?id=%#'.fmt(this.get('urlParam'));
}.property('urlParam')
});
When I execute. emapp.MyURL.set('urlParam', 'Adams'). I can inspect and see the url changed to 'Adams'. But data is not fetched again.
Edit: emapp -> Emapp (pointed out by rudi-angela)
As you have made the 'url' property a computed property, Ember takes care of updating this value when the urlParam changes. That is all you have instructed Ember to do here (and apparently it is doing it properly).
But I reckon what you want here is any change in the 'urlParam' property to trigger a fetch action. In that case a solution would be to create a separate object that observes the urlParam and will take action when the 'urlParam' value changes. Something along these lines:
emapp.watcher = Ember.Object.create({
valueBinding: "emapp.MyURL.urlParam",
observer: function() {
console.log("urlParam has changed:");
// perform your fetch here
}.observes("value"),
});
Note: I thought there was a requirement for the namespace to be capitalised (rather Emapp instead of emapp).
I have a model:
app.ObjectOne = Em.Object.extend({
id: null,
count: null
});
And another model, which computed property 'SomeProperty' I want to depend on property 'count' from ObjectOne
app.ObjectTwo = Em.Object.extend({
id: null,
someProperty: function(){
return count+5;
}.property('app.SomeObjectController.count')
});
So, can I do it that way?
Or is there any other way to do it;
The main idea is that data of ObjectOne model comes after ObjectTwo, so I what to recompute property and rerender view
If I understand well, the behavior you want is exactly what Ember.js can bring to you.
First, here is a very basic template:
<script type="text/x-handlebars">
Here, the template is updated each time this property is updated (equivalent to bound property)
{{App.objectTwo.someProperty}}
</script>​
Here is the javascript. I don't know if you really want to use an ObjectController here, but you mention it, so I have use it. An ObjectController's act as a proxy around it's content property. That means here that someObjectController.get('count') will try to get the count property from the controller itself, and if it does not exist, then the controller retrieve the count property from its content.
App = Ember.Application.create();
App.someObjectController = Ember.ObjectController.create();
App.ObjectOne = Em.Object.extend({
id: null,
count: null
});
App.objectOne = App.ObjectOne.create({
id: 1,
count: 42
});
App.someObjectController.set('content', App.objectOne);
App.ObjectTwo = Ember.Object.extend({
id: null,
someProperty: function(){
return App.someObjectController.get('count')+5;
}.property('App.someObjectController.count')
});
App.objectTwo = App.ObjectTwo.create({
id: 1
});
//in 3 seconds the count property is set to 0. You can see the template is updated
Ember.run.later(function(){
App.objectOne.set('count', 0);
},3000);
Here is the working jsfiddle: http://jsfiddle.net/Sly7/M3727/4/