I would like to do something like this
App.FooComponent = Ember.Component.extend({
tagName: 'ul',
propertyPath: '',
data: []
});
And in foo-component.hbs (this obviously doesn't work):
{{#each item in data}}
<li>{{propertyPath}}</li>
{{/each}}
I would use the component as follows:
{{foo-component data=content propertyPath='name'}}
where content is a collection of objects, each with a 'name' property.
I've tried to do it within the component by using a computed property and binding to that:
itemNames: function() {
var propertyPath = 'data.#each.' + this.get('propertyPath');
return this.get(propertyPath);
}.property(???)
But that has the problem of how to set the dependent key so the property is recomputed.
I would use something like this:
items: Ember.computed.map('data', function(dataObject) {
return {
name: dataObject.name,
otherProp: dataObject.otherProp
};
}).property('data.#each.{name, otherProp}')
You declare a computed property that maps the objects in data to some other objects. In your function, you can just return an object with the properties you need (I used name and otherProp). Then, to make sure that the property watches the name and otherProp properties on every object, I override the dependent keys with the .property() call. (By default, Ember.computed.map will call .property('data.#each') for you.)
Then, in your template:
{{#each items}}
<li>{{name}}</li>
{{/each}}
EDIT:
The dynamic property name is a little weird. One way you could do it is to declare a computed property on the class at creation time.
init: function() {
var propertyPath = this.get('propertyPath');
this.reopen({
propertyPathUpdater: function() {
this.notifyPropertyChange('data');
}.observes('data.#each.' + propertyPath)
});
}
Depending on how your values update, you might have to mess with it a little, but hopefully you get the idea. The idea being that Ember doesn't like dynamic properties. :)
Related
How can I pass dynamic arguments to the component helper.
In production, I am iterating over a json hash that will render different components with different attrs each time.
I have a scaled down example in this jsbin.
I am returning this json hash:
columns: Ember.computed('people.[]', function() {
return this.get('people').map(function(person){
return {
component: 'x-person',
attrs: {
person: person
}
};
});
})
That I then iterate over and try and pass the arguments into the component:
{{#each columns as |column|}}
{{component column.component column.attrs}}
{{/each}}
The component is getting created but the attributes are not set.
How can I properly pass the arguments to the component helper?
If you want more flexibility like not having to update the way each attr is used in the dynamic component, I suggest this:
Create an initializer to extend Ember.Component
import Ember from 'ember';
var Component = {
loadAttrsHash: Ember.on('didReceiveAttrs',function(attrs){
if(this.get('attrsHash')){
let hash = this.get('attrsHash');
Object.keys(hash).forEach(key=>{
this.set(key,hash[key]);
});
}
}),
};
export function initialize(/* application */) {
Ember.Component = Ember.Component.extend(Component);
}
export default {
name: 'component',
initialize
};
Then you can create dynamic components with an attrs hash
{#each columns as |column|}}
{{component column.component attrsHash=column.model}}
{{/each}}
Then you can access attrsHash.person, simply with {{person}} in the child component just as if it was passed directly.
This way, the component is more durable as it can be used with direct attributes as well as attrsHash, and should update whenever attrsHash changes.
You have to follow some convention to pass all data to dynamic components using model property.
Columns:
columns: Ember.computed('people.[]', function() {
return this.get('people').map(function(person){
return {
component: 'x-person',
model: {
person: person
}
};
});
})
Template:
{#each columns as |column|}}
{{component column.component model=column.model}}
{{/each}}
And in all components that are dynamically created you should access properties via model attribute passed to component. So, if something were color before it should now become model.color etc.
Every google result is about an ArrayController sorting. Need a sorting mechanism without using ArrayController.
There is a model where there are sort params. Like say 'sortOrder' as one of the properties in the model (which will be from a back end).
Will be rendering this model using #each but this should do the iteration based on the sortOrder property and not the model's ID property.
In Ember 2.0 SortableMixin is deprecated and is on its way out too.
In the Controller (not the ArrayController) you may define a new computed property like SortedUsers1,2,3 below:
export default Ember.Controller.extend({
sortProps: ['lastName'],
sortedUsers1: Ember.computed.sort('model', 'sortProps'),
sortedUsers2: Ember.computed.sort('content', 'sortProps'),
sortedUsers3: Ember.computed('content', function(){
return this.get('content').sortBy('lastName');
})
});
The assumption above is that the model itself is an array of users with lastName as one of user properties. Dependency on 'model' and 'content' look equivalent to me. All three computed properties above produce the same sorted list.
Note that you cannot replace 'sortProps' argument with 'lastName' in sortedUsers1,2 - it won't work.
To change sorting order modify sortProps to
sortProps: ['lastName:desc']
Also if your template is in users/index folder then your controller must be there as well. The controller in users/ would not do, even if the route loading model is in users/.
In the template the usage is as expected:
<ul>
{{#each sortedUsers1 as |user|}}
<li>{{user.lastName}}</li>
{{/each}}
</ul>
Here is how I manually sort (using ember compare)
import Ember from "ember";
import { attr, Model } from "ember-cli-simple-store/model";
var compare = Ember.compare, get = Ember.get;
var Foo = Model.extend({
orderedThings: function() {
var things = this.get("things");
return things.toArray().sort(function(a, b) {
return compare(get(a, "something"), get(b, "something"));
});
}.property("things.#each.something")
});
You just need to include a SortableMixin to either controller or component and then specify the sortAscending and sortProperties property.
Em.Controller.extend(Em.SortableMixin, {
sortAscending: true,
sortProperties: ['val']
});
Here is a working demo.
In situations like that, I use Ember.ArrayProxy with a Ember.SortableMixin directly.
An ArrayProxy wraps any other object that implements Ember.Array
and/or Ember.MutableArray, forwarding all requests. This makes it very
useful for a number of binding use cases or other cases where being
able to swap out the underlying array is useful.
So for example, I may have a controller property as such:
sortedItems: function(){
var items = Ember.ArrayProxy.extend(Ember.SortableMixin).create({content: this.get('someCollection')});
items.set('sortProperties', ['propNameToSortOn']);
return items;
}.property()
Like so: JSBin
I have component that I want to provide data too. I am using Ember-CLI if that helps.
The component is a map that I am loading onto the page that I than want to place markers on. I used a component so I could use the didInsertElement method to get access to the element once it is ready.
export default Ember.Component.extend({
componentMap: '',
didInsertElement: function() {
navigator.geolocation.getCurrentPosition(position => {
//Initialize map...
this.populateMap();
});
},
populateMap: function() {
//Get store
var store = this.get('parentView.targetObject.store');
console.log(store);
//Search Store
var data = store.find('restaurant');
//Where is the data?!
data.map(item => {
console.log(item.get('name'));
});
}
});
I am having an issues getting the data from a store. I have seen a couple methods, here shows two different methods. First being the this.get('parentView.targetObject.store') or this.get('targetObject.store'). I have also tried the {{component store=store}} method, but that was not working for me either. This might have to do with a fundamental lack of understanding of data flow in an ember app.
I am using Ember CLI and I am wondering if it has anything to do with the context of this inside modules?
If I am way off base as to how I should do this, please let em know!
UPDATE: Adding route, controller and template for context.
Route
export default Ember.Route.extend({
model: function() {
return this.store.find('restaurant');
}
});
Controller
export default Ember.Controller.extend({
actions: {
add: function() {
var $addForm = $('.add-form');
$addForm.show();
}
}
});
Template (index.hbs, which is output in application.hbs {{outlet}})
{{main-map store=store}}
Thanks.
What is happening is as follows:
The model associated with your control is populated as an array of restaurants, not a single map or anything of that sort.
return this.store.find('restaurant'); returns an array of restaurants from the store which ultimately populates the model of your controller.
If you want access to the data contained within your model in your component, you should pass the model as an argument into your component.
So, you can pass the array of restaurants as follows (rename the property as appropriate):
{{main-map data=model}}
Or, if in theory you wanted to display a component for each restaurant:
{{#each restaurant in model}}
{{your-component name=restuarant.name}}
{{/each}}
Here is the linkTo helper in my handlebars template
{{#linkTo 'person.page' nextPage target="controller"}}Next{{/linkTo}}
Here is my controller
PersonApp.PersonController = Ember.ArrayController.extend(Ember.PaginationMixin, {
itemsPerPage: 2
});
Here is the computed property in the mixin
nextPage: function() {
var nextPage = this.get('currentPage') + 1;
var availablePages = this.get('availablePages');
if (nextPage <= availablePages) {
return Ember.Object.create({id: nextPage});
}else{
return Ember.Object.create({id: this.get('currentPage')});
}
}.property('currentPage', 'availablePages'),
when I console log just before each return statement I can see the page id is correct ... yet my html isn't updated. Anything simple that I'm doing wrong in the above?
Also I do see a print each time I change the page (so the computed properties I depend on are being fired)
Here is a full blown jsfiddle showing that after you click next the first time ... it still points at /#/page/2 instead of /#/page/3
http://jsfiddle.net/RhTyx/2/
Thank you in advance
First off: It would be nice, if you would not link a fiddle where the most important code is not part of the fiddle (the FilterSortSliceMixin). Therefore one cannot test anything, despite the fact the fiddle was really huge and contained lots of unnecessary code.
Regarding your problem:
I think this cannot work because the dependent properties you specified do not do anything. Your {{#linkTo}} helper sends the user into the the PersonPageRoute. The code of this route is:
PersonApp.PersonPageRoute = Ember.Route.extend({
model: function(params) {
return PersonApp.Person.find(params.page_id);
},
setupController: function(controller, model) {
this.controllerFor('person').set('selectedPage', model.get('id'));
}
});
So you are getting the personcontroller and set the property selectedPage. But this property is not specified in your dependent keys. So therefore i would suggest this:
//maybe even remove currentPage??
}.property('currentPage', 'availablePages' , 'selectedPage'),
So i guess you got confused with your naming. I guess, you should either have the property 'selectedPage' or 'currentPage', right?
Update: This is definitely a bug. Heres an excerpt from the LinkView class, which is used with the linkTo helper:
var LinkView = Ember.View.extend({
attributeBindings: ['href', 'title'],
href: Ember.computed(function() {
var router = this.get('router');
return router.generate.apply(router, args(this, router));
})
});
As you see it does not specify any dependent keys. Maybe you can reopen/patch this class and add the dependent key. Don't know which one this would have to be, but maybe context?
I'm learning ember.js, I haven't built anything with it yet.
I'm storing my data in model objects and using controllers to act as a glue between the view and the models. A controller has content set to an instance of the model, and the view has a binding to the content in controller (and hence to the model by proxy). Like so:
// js
App.MyModel = Ember.Object.extend({foo:''});
App.myController = Ember.Object.create({
content: App.MyModel.create()
});
// html
{{view Ember.TextInput valueBinding="App.myController"}}
So far so good. But I don't know how to apply this paradigm to nested collections:
//js
App.ChildController = Ember.ArrayController.extend();
App.NestedModel = Ember.Object.extend({
init: function() {
this._super();
this.set('children', []);
// Here: I can't give a global name for the content binding, and I don't know how to give a relative one
this.set('childController', App.ChildController.create({contentBinding: 'children');
}
});
App.myController = Ember.ArrayController.create({
content:[],
newChild: function() {
this.pushObject(App.NestedModel.create());
}
});
// html
{{#collection contentBinding="App.myController"}}
{{#collection contentBinding="content.childController"}} <!-- nope -->
{{content.childField}}
{{/collection}}
{{/collection}}
Here's something you can fiddle with: http://jsfiddle.net/DwheG/
What I'm asking is:
am I even modeling stuff correctly?
how do I bind the child controller's content? Do I have to use a string? Passing in objects (this) hasn't worked for me. Is Ember's path resolution algo documented anywhere?
how do I bind the nested collection helper to the nested controller?
I read the source and confirmed that bindings can only be specified with paths.
Ember uses getPath(root, path) to resolve paths. root is the object to start looking from, and path is a string. The root element can be ommited if the path is global (in which case the root will be set to window). So, for instance, getPath(myController, 'foo.bar') will return myController.foo.bar when evaluated.
So: given that I needed to somehow reference the containing object for my array in order to create the binding, and that there are no built-in facilities to do this, I just added a reference to the containing object in my model. See here: http://jsfiddle.net/CP448/1/