I want to filter products depending of the selected category which can be selected with a drop-down menu. products belongTo category
What do I have to change in the controller to filter products depending of the chosen value of the drop-down menu?
How can I add an empty field in the drop-down menu and display all products when it is chosen?
Here is the current Ember CLI code:
app/routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return {
categories: this.store.find('category'),
products: this.store.find('product')
};
}
});
app/controllers/index.js
import Ember from 'ember';
export default Ember.Controller.extend({
selectedCategory: null,
categories: function(){
var model = this.get('model.categories');
return model;
},
products: function(){
var model = this.get('model.products');
return model;
}.property('selectedCategory')
});
app/templates/index.hbs
<p>
{{view "select"
content=model.categories
optionValuePath="content.id"
optionLabelPath="content.name"
value=selectedCategory
}}
</p>
{{#each product in products}}
<li>{{product.name}}</li>
{{/each}}
app/models/product.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
category: DS.belongsTo('category', { async: true }),
});
app/models/category.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
products: DS.hasMany('product', { async: true })
});
What do I have to change in the controller to filter products
depending of the chosen value of the drop-down menu?
You could create a computed property that filters the products like:
filteredProducts: function() {
var selectedCategory = this.get('selectedCategory');
var products = this.get('products');
return products.filter(function(product) {
return product.get('category.name') === selectedCategory;
});
}.property('selectedCategory')
How can I add an empty field in the drop-down menu and display all
products when it is chosen?
Just add prompt value in the Ember select view:
{{view "select" prompt="All products"
content=categories
optionLabelPath="content.name"
optionValuePath="content.name"
value=selectedCategory}}
And then when you observe the selectedCategory, if the user select's the prompt, the value of the selection will be null.
Thus you can update the filteredProducts computed property to take that into account as well:
filteredProducts: function() {
var selectedCategory = this.get('selectedCategory');
var products = this.get('products');
if(selectedCategory) { // return filtered products
return products.filter(function(product) {
return product.get('category.name') === selectedCategory;
});
}
return products; // return all products
}.property('selectedCategory')
Related
I sideload products of a given category. The problem is that they are not sorted. I'd like to sort them by id and render the sorted products in a select.
How can I sort them?
app/category/model.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
products: DS.hasMany('product', { async: true })
});
route.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return {
category: this.store.find('category', 1)
};
}
});
template.hbs
{{view "select" prompt="All products"
content=model.category.products
optionLabelPath="content.name"
optionValuePath="content.name"
value=selectedProduct
class="form-control"}}
You can use a computed property and the Ember.SortableMixin to sort the products in your controller:
sortedProducts: Ember.computed('model.category.products', function() {
return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['id'],
sortAscending: true,
content: this.get('model.category.products')
});
})
And then simply use sortedProducts instead of model.category.products.
Source
app/models/product.js
import DS from 'ember-data';
var Product = DS.Model.extend({
name: DS.attr('string'),
pictures: DS.hasMany('picture', { async: true })
});
export default Product;
app/models/pictures.js
import DS from 'ember-data';
var Picture = DS.Model.extend({
url: DS.attr('string'),
alt: DS.attr('string')
});
export default Picture;
In the product index view I can display all pictures with this code:
{{#each picture in product.pictures}}
<img {{bind-attr src=picture.url}} alt="example">
{{/each}}
How can I display just the first picture?
The following should work for you:
<img src={{product.pictures.firstObject.url}} alt="example">
The properties firstObject and lastObject are available in Ember.
Also note, you don't need to use bind-attr anymore.
If I understand the ember.js documentation correctly then I should see the models systemStatus value get populated, but I'm not:
<div class="form-group">
<label class="col-sm-2 control-label" for="name">Description</label>
<div class="col-sm-10">
{{view "select" content=statuses value=model.systemStatus }}
</div>
</div>
This is the controller:
import Ember from "ember";
export default Ember.Controller.extend({
statuses: ["Being Built", "Active","Inactive"],
selectedSystemStatus: 'Active',
actions: {
save: function() {
// this.model.set('systemStatus', this.selectedStatus);
var s = this.get('selectedSystemStatus');
this.model.save();
},
cancel: function() {
}
}
});
The model:
import DS from "ember-data";
export default DS.Model.extend({
name: DS.attr('string', {defaultValue: 'Hello'}),
systemStatus: DS.attr('string', {defaultValue: 'Active'}),
description: DS.attr('string', {defaultValue: 'Describe me'})
});
The router:
import Ember from "ember";
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('software-system');
}
});
Everything works up until you try to select an option from the UI. I'm not sure what I'm doing wrong here, and would like some help.
do you have any place where you call this.store.find('my-model')? This is where the model gets populated.
The default way would be to put it into a corresponding route in the model hook:
// routes/my-model.js
export default Ember.Route.extend({
model: function() {
return this.store.find('my-model');
}
});
With this approach, your controller will wait until the model is loaded.
I'm building the mandatory TODO app to learn ember.
I have tasks and tags in a belongsTo/hasMany relationship (each tag hasMany tasks). When showing tasks, I want to show a computed property on each available tag.
Models:
App.Tag = DS.Model.extend({
tasks: DS.hasMany('task', {async: true}),
..
});
App.Task = DS.Model.extend({
tag: DS.belongsTo('tag', {async: true}),
..
});
Route:
App.TasksRoute = Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
tasks: this.store.find('task'),
tags: this.store.find('tag')
});
},
setupController: function(controller, model) {
this.controllerFor('tasks').set('content', model.tasks);
this.controllerFor('tags').set('content', model.tags);
}
});
Tags controller:
App.TagsController = Ember.ArrayController.extend({
needs: ["tag"]
})
Tag controller:
App.TagController = Ember.ObjectController.extend({
taskCount: function() {
// FOLLOWING DOES NOT WORK
return this.get('tasks.length')
}.property('tasks')
});
Tag partial:
<ul>
{{#each tag in model}}
<li>
{{tag.name}} ({{controllers.tag.taskCount}} tasks)
</li>
{{/each}}
</ul>
The computed property 'taskCount' does not work. There is something wrong with 'this'.
Is this canonical way of doing it? And if so, what is wrong? Thanks
EDIT: FIXED
I had missed out
App.ApplicationSerializer = DS.ActiveModelSerializer.extend();
And I've used render to get the controller decoration:
{{render 'tag' tag}}
which calls the controller before rendering
I'm creating a basic store using ember with products and a shopping bag. I'm using LocalStorage as the adapter for the shopping bag so the user can come back to a cart with products they've added previously. At any one time, there should only be one shopping bag. Right now, I've set up a checker in the application route on activate to see if there's already a bag saved. If not, create one.
I also want to set-up the model correctly for it to be used in the controller and in templates. Here's my application route"
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);
}
});
export default ApplicationRoute;
Here is my bag model:
import DS from 'ember-data';
import Ember from "ember";
export default DS.Model.extend({
products: DS.hasMany('product', {async: true}),
bagProducts: DS.hasMany('bagProduct', {async: true}),
productCount: Ember.computed.alias('products.length')
});
In my controller, I'd like to check if the bag has products in it so I can display a product count:
import Ember from "ember";
export default Ember.ArrayController.extend({
bag: Ember.computed.alias("content"),
cartHasProducts: function() {
var bag = this.get('bag').objectAt('0');
return bag.get('productCount') > 0;
}.property('content.#each.productCount')
});
And my template:
<div id="bag" class="js-btn" {{action "showModal" 'bag-modal' model}}>
<i class="icon ion-bag"></i>
<p class="label">Your Bag</p>
{{#if controller.cartHasProducts}}
<div class="count">
<span>{{productCount}}</span>
</div>
{{/if}}
</div>
This works, but only after I use objectAt('0') to get the first instance of the bag. Shouldn't content just return the one instance of the bag? Is there a way to set up the model to just return the one current instance? What am I doing wrong?
I really really appreciate your help!
This could be a solution:
http://codepen.io/szines/pen/Aufmp?editors=101
(Should work with LocalStorage as well, above example uses FixtureAdapter.)
You can download both models in the store with RSVP, and in afterModel, you can check, bag already exist or not. If not, you can create one.
You can map your main model and your secondary model in setupController, so they gonna be exist in Controller and in views.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
bagModel: this.store.find('bag'),
productList: this.store.find('product')
})
},
afterModel: function(model, transition) {
if (model.bagModel.get('length') < 1) {
model.bagModel = this.store.createRecord('bag');
}
},
setupController: function(controller, model) {
var productList = model.productList,
bagModel = model.bagModel;
model = model.bagModel;
this._super(controller, model);
controller.set('productList', productList);
}
});
Because you have only one bag as main model, better if you use ObjectController.
Controllers and models:
App.IndexController = Ember.ObjectController.extend({
productCount: Ember.computed.alias('model.products.length'),
actions: {
addProduct: function(product) {
this.get('model').get('products').pushObject(product);
}
}
});
App.Bag = DS.Model.extend({
products: DS.hasMany('product', {async: true}),
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
});
App.Bag.FIXTURES = [];
App.Product.FIXTURES = [
{id: 1, name: 'First Product'},
{id: 2, name: 'Second Product'},
{id: 3, name: 'Third Product'}
]
and the index template:
Number of products: {{productCount}}
{{#each productList}}
<button {{action 'addProduct' this}}>Add {{name}}</button>
{{/each}}