Ember: Accessing a data store in an Ember component - ember.js

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}}

Related

emberjs - Pass dynamic arguments to the component helper

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.

How is Sorting achieved in an Ember model without using Array Controller?

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

Ember Model with only One Instance

I'm trying to set up a shopping bag model in Ember. The shopping bag will be created on load of the page and saved in LocalStorage using the LocalStorage adapter. At any given time, there should only be one instance of a bag saved, as a user only needs to add products to one shopping bag. My question is this: it seems that I'm being hack-y with my methods of getting and setting data on my bag as Ember data caters to models with more than one instance. Is there a better way to structure/define my bag model that is better suited for one-instance models? Here's my model:
import DS from 'ember-data';
export default DS.Model.extend({
products: DS.hasMany('product', {async: true}),
productCount: function() {
return this.get('products.length');
}.property('products.length')
});
When I want to get the productCount in my template, the only way I can seem to get it to print is use an {{#each}} statement with {{productCount}} nested inside. As there is only one bag, this seems inefficient. In other parts of my code, I need to get the current instance of the bag and act on it. To get this to work, I'm finding all bags, then getting the firstObject, which also seems hack-y:
import Ember from "ember";
export default Ember.ArrayController.extend({
actions: {
addToBag: function(model) {
this.store.find('bag').then(function(bags) {
var bag = bags.get('firstObject');
bag.get('products').then(function(products) {
products.pushObject(model);
bag.save();
});
});
}
}
});
My application route uses the bag as its model, and sets up the controller:
import Ember from "ember";
var ApplicationRoute = Ember.Route.extend({
activate: function() {
var store = this.store;
store.find('bag').then(function(bags) {
var existing_bag = bags.get('firstObject');
// If there isn't already a bag instantiated, make one and save it
if(typeof existing_bag === 'undefined') {
var new_bag = store.createRecord('bag');
new_bag.save();
}
});
},
model: function() {
return this.store.find('bag');
},
setupController: function(controller,model) {
controller.set('content', model);
}
});
Any ideas here to make this more efficient? I don't want this to fester into code that is messy. Thanks so much in advance!
If that is your BagController above, it should be an ObjectController instead.
The reason why you're having to get the first object is because find fetches all items of that model type in your store. You may only have one, but find doesn't know that unless you provide an id and if this bag hasn't been stored in your database, it may not have one yet.
Instead of fetching the bag model from the store, I would link you Bag and Products controllers with needs, then simply access the model property of that controller. You can even set up an alias to be able to access it quickly.
For example:
import Ember from "ember";
export default Ember.ArrayController.extend({
needs: 'bag',
bag: Ember.computed.alias("controllers.bag.model").
actions: {
addToBag: function(model) {
this.get('bag').get('products').pushObject(model);
}
}
});
The ApplicationRoute is only fired once, when you're app first boots up so you don't need to check if there's already a bag model present. The only one that will be there is the one you create. You should do this in the model hook. You don't need to set content as the model. It'll be hooked up like that by default.
import Ember from "ember";
export default Ember.Route.extend({
model:function() {
return this.store.createRecord('bag');
}
});
If you may want the ability to have multiple shopping bags going forward you could try using the 'singleton' approach Discourse follows: https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/mixins/singleton.js
Basically it adds 'current' property that you can use as your single instance throughout your code.
However, if you won't have a need to have multiple instances this may not be the best choice.

model not getting set while using render helper in ember -cli

I am using ember-cli and there is one controller which gets using a render helper and hence no route. Example
{{render 'ref-type' ref-type}}
Now inside the controller ref-type
export default Ember.Controller.extend({
actions{
isShown: function() {
var m = this.get('model'); //here model is undefined can i know why?
}
}
});
and model ref-type is
export default Ember.Object.extend({
getData: function(){
return 'xyz'; //data is returned hre
}
});
why am i not able to access model in the controller.
Adding a raw JSBin example
JSBIN
Should the model always be DS.Model.extend? i do not think so.
Also Instead of ref-type i have used 'sample' as the name, so that it is easier to understand
You never initialize your model. According to your JSBin example you must have property named sample in your TodosController. ember will not create an object by itself. I have edited your JSBin. It might not be the best approach but I tried to explain what is going on.
If you put log {{log sample}} just above your render helper you will notice that your sample property is already undefined.

Getting model data from controller

In Ember if I have a model that is a list of users, if in the UsersController I do:
users = this.get('model');
users.map(function(user) {
user.name
});
should it not resolve the promise and return the user records? I'm confused on why this is not working for me, or how to get the model data the correct way. Thank you in advance.
The model promise is resolved by the router. Ember, by default, sets the controller's content property as the route's model unless you override the route's setupController() method. Your issue lies in the formatting of the map function.
It seems like you're using an array controller, because the model is an array, so do the following:
App.UsersController = Em.ArrayController.extend({
users: function() {
return this.map(function(user) {
return user.get('name');
});
}.property('#each.user'),
});
You can make this code even more streamlined by using Em.Array's mapBy() method, as follows:
App.UsersController = Em.ArrayController.extend({
users: function() {
return this.mapBy('name');
}.property('#each.user'),
});
If you're using the list of users in your template you can do this easily with a {{#each users}} helper. However, if you're using this list for other properties in the controller, be sure to to use the right observer to watch for items being added to the array:
someOtherProperty: function() {
var users = this.get('users');
// Do stuff with users array here...
}.observes('users.[]')
See setting up a route's model and setting up a controller if you're unfamiliar with how the models stuff works.