Filtering an array in ember - ember.js

Ok so I'm fairly new to programing, I know how to run a filter on a JSON Array but I cant seem to figure it out when I'm pulling the data from firebase and viewing it in an Ember app.
this is my route.js code:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('user');
}
});
This is my template.hbs code the href="#!" is the generic from materialize for the dropdown buttons:
<div class="list-wrapper col-xs-10 col-xs-offset-1">
<div class="col-xs-12 button-div">
{{#md-btn-dropdown text='Filter' class="col-xs-2" belowOrigin=true}}
<li>Female</li>
<li>Male</li>
<li>All</li>
{{/md-btn-dropdown}}
</div>
{{#each model as |info|}}
<div class="col-xs-3 user-card">
<div class="card-info">
<ul>
<li>Last Name- {{info.lastName}}</li>
<li>First Name- {{info.firstName}}</li>
<li>Gender- {{info.gender}}</li>
<li>{{info.email}} </li>
</ul>
</div>
</div>
{{/each}}
</div>
{{outlet}}
This is my controller.js code which I no is all wrong:
import Ember from 'ember';
export default Ember.Controller.extend({
customFilter: function(gender) {
return function(el) {
var r = el.user;
return r.gender === gender;
};
}
});
and this is my model:
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
lastName: DS.attr('string'),
firstName: DS.attr('string'),
gender: DS.attr('string'),
email: DS.attr('string')
});
I've searched high and low and I'm sure I'm just missing something basic and stupid. What I want is for the dropdown menu to be able to filter and display only female, male or all. Again I'm new to this stuff so I apologize if this is a pretty basic thing. Thank You

What your missing is an action that updates your controller when an item in the dropdown is actually selected.
Some helpful reading:
Triggering changes with actions
Computed Properties
Here's how to put actions in your dropdown component
{{#md-btn-dropdown text='Filter' class="col-xs-2" belowOrigin=true}}
<li><a {{action "filterUpdated" "female"}}>Female</a></li>
<li><a {{action "filterUpdated" "male"}}>Male</a></li>
<li><a {{action "filterUpdated"}}>All</a></li>
{{/md-btn-dropdown}}
In your controller you then need to handle this action like so:
import Ember from 'ember';
export default Ember.Controller.extend({
// the people property is an alias of the model object
// which essentially makes people a synonym for model
// read more http://emberjs.com/api/classes/Ember.computed.html#method_alias
people: Ember.computed.alias('model'),
// holds the currently selected gender, e.g., "female". A null value indicates there is no filter.
currentFilter: null,
/*
filteredPeople is a computed array containing people models.
The array is recomputed every time the model changes or the currentFilter changes,
see the .property() bit at the end.
read more: http://emberjs.com/api/classes/Ember.computed.html#method_filter
*/
filteredPeople: Ember.computed.filter('people', function(person/*, index, array*/) {
// this function is passed each item in the model array, i.e., the person argument
// there's no need to use the index nor array arguments, so I've commented them out
if(this.get('currentFilter') === null) {
// when the filter is null, we don't filter any items from the array
return true;
} else {
// otherwise only return true if the gender matches the current filter
return person.gender === this.get('currentFilter');
}
}).property('people', 'currentFilter'),
actions: {
filterUpdated: function (value) {
if (Ember.isEmpty(value)) {
// an empty value means we should clear the filter
this.set('currentFilter', null);
}
else {
this.set('currentFilter', value);
}
}
}
});
Finally, edit your template to change
{{#each model as |info|}}
to
{{#each filteredPeople as |info|}}
Also at a meta level, don't apologize for asking questions! Everyone is new at something at somepoint, and often asking is the best way to learn. That's what stackoverflow is all about :)

Something like this would work:
gender: 'All',
filteredModel: Ember.computed.filter('model', function(person) {
return person.gender === this.get('gender');
}).property('gender'),
this assumes that it starts on all, and then when the dropdown changes the value of gender, then the filteredModel will get updated. You can then in your hbs file change the result to:
{{#each filteredModel as |info|}}

Related

How to remove class from other elements in emberjs

my functions toggles the class name on click to an element. But It's not remove the class names from other elements. so all elements are highliting now. But whenever user click on an element, it should have the classname rest of siblings has to removed the classname how do do this?
here is my try but not works:
import Ember from 'ember';
export default Ember.Component.extend({
isSelected : false,
deSelectOthers : function(){
this.set( "isSelected", false ); //deselecting all before add the class to clicked element
},
actions : {
selectCard : function(card) {
this.deSelectOthers(); //de select others not works
this.toggleProperty('isSelected'); // add class to this element only works. how to remove class from other element?
}
}
});
UPDATE
I am sorry for bad description about my requirement. I have created the requirement using jQuery - simply how to achive the same with emberjs?
Demo-Requirement
Here is the working twiddle
You need to maintain selectedIndex/value and use if check to update class based on the selectedIndex/value.
application.hbs
<div class="parent">
<ul>
{{#each model as | temp index|}}
<li class={{if (eq selectedIndex index) 'highlight' }} {{action 'changeSelectedIndex' index}}>{{temp}}</li>
{{/each}}
</ul>
</div>
routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
model(){
return [1,2,3,4];
}
});
controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
selectedIndex:undefined,
actions:{
changeSelectedIndex(index){
this.set('selectedIndex',index);
}
}
});
You need to install ember-truth-helpers addon by running ember install ember-truth-helpers

How can I get the Id from the URL in an Ember Route?

I have a two panel display where I show a list of items on the left, then detail about a selected item on the right (using nested route).
My route looks like this:
Router.map(function() {
this.route('authenticated', {path: '/'}, function() {
this.route('bakery', function() {
this.route('cakes', function() {
this.route('detail', { path: '/:id' });
});
});
});
});
My URL looks like
http://localhost:3333/bakery/cakes/e34b3ce3
When an item is selected, it is set to "active" (temporary property on the model - default is false) and highlighted via an action on the bakery/cakes route. The detail is then shown on the right.
If I refresh the page, the item is no longer highlighted - but the detail is still shown.
Ideally I'd like to use the afterModel() hook in the bakery/cakes route to set that item back to active again, but I've been unable to get the Id to be able to do this.
I've tried the following:
Accepted answer from here
This question doesn't help me as the model will have reloaded and my "active" property will be false so I can't just select where active = true.
I'm using ember 2.5.0. Thanks.
I wonder if it'd be better to architect your structure a bit differently (from what I assume you're doing).
First, load all of the cakes on the authenticated.bakery.cakes route;
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('cakes');
}
});
Secondly, show your "full width" cakes list on the authenticated.bakery.cakes.index template (the cake models will be inherited);
<div class="full width cake list">
{{#each model as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{!-- cake photo --}}
{{cake.name}}
{{!-- other cake details... --}}
{{/link-to}}
{{/each}}
</div>
Next, on your authenticated.bakery.cakes.detail route, load the specific cake along with the list of cakes;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
let cakes= this.modelFor('authenticated.bakery.cakes');
return Ember.RSVP.hash({
cakes: cakes,
cake: cakes.findBy('id', params.id)
});
}
});
Finally on the authenticated.bakery.cakes.detail template, show the condensed/narrow list of cakes along with the specific cake details. And using {{link-to}}, the 'active' class will automatically be applied;
<div class="narrow width cake list">
{{#each model.cakes as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{cake.name}}
{{/link-to}}
{{/each}}
</div>
<div class="cake details">
{{model.cake.name}}
</div>
As another option, change your model active flag on the proper route hooks should work. (I think anyway, haven't done this myself.) On your authenticated.bakery.cakes.detail route;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('cakes', params.id);
},
afterModel(cake) {
cake.set('active', true);
},
actions: {
willTransition() {
this.get('controller.model').set('active', false);
}
}
});

How does one access model data in a router/controller?

Bear with me please, I'm new.
Been breaking my head over this problem and sort of here as last resort. It's about how to access a model's data when that route loads. For instance, when /meals/2 loads, I want a function to run that sets the background of the document using that model's background-image string property. Or when /meals loads, the a function that uses a property of the collection's first item.
Any help on 'the ember way' to do this would be much appreciated.
Menu.hbs
{{#each meal in model}}
<span {{action 'mealSelected' meal.image_large}}>
{{#link-to 'menu.meal' meal tagName="li" class="meal-block" href="view.href"}}
[...]
{{/link-to}}
</span>
{{/each}}
<div id="meal-info-wrapper">
{{outlet}}
</div>
Model:
export default DS.Model.extend({
name: DS.attr('string'),
image: DS.attr('string')
});
Router.js
export default Router.map(function() {
this.route('about');
this.route('menu', { path: '/' }, function() {
this.route('meal', { path: '/meal/:id/:slug' });
});
});
routes/menu.js
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function() {
Ember.$(document).anystretch('temp-images/bg-1.png');
}
});
What I want to do in routes/menu.js for instance would be to have that image url be supplied by the model.
afterModel will run only once the model has been resolved, and the model is passed as an argument. So, based on my understanding of your app, you can adjust your routes/menu example to:
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function(model) {
Ember.$(document).anystretch(model.get('firstObject.image'));
}
});
Correct me if I misunderstood something, what you want to do is:
Change the background image of a DOM element based on a property found
in each Model's record.
Model loading is an async operation, you want to do the image swaping once you are sure the data is loaded. You used the afterModel hook to guarantee that, but that is not enough.
You want to modify the DOM inside your template, but you need to make sure that the template has been rendered. So, the DOM manipulation logic, instead of placing it in afterModel, it belongs to the didInsertElement event that Views have.
I suggest you use a component (its a view too), something like:
// your template
{{#each meal in model}}
{{meal-component content=meal}}
{{/each}}
// the meal-component
didInsertElement: function() {
var imgURLProperty = this.get('content.imgURLProperty');
Ember.$(document).anystretch(imgURLProperty);
}
Of course, you can't copy paste any of that. It just shows you the main mechanic of how you can modify a template based on the properties of a model.

How to iterate over class variables from Handlebars in ember-cli?

I'd like to iterate over a class variable array in Handlebars in ember-cli to generate a list of checkboxes (categories), with the appropriate ones checked off based on which categories the model belongs to.
I have several issues:
- I can't figure out how to access the class variable in ember-cli. I've seen tutorials showing that in ember.js, it's just App.Listing.CATEGORIES, but I'm not getting any passes through my each loop.
- How to check off the appropriate box? I have some janky code below that probably doesn't work.
listing.js:
import DS from "ember-data";
var Listing = DS.Model.extend({
categories: DS.attr(), // string array
});
Listing.reopenClass({
CATEGORIES: ['park', 'outdoors']
});
export default Listing;
show.hbs:
<ul>
{{#each category in CATEGORIES}}
<li>{{input type="checkbox" name=category checked=categories.contains(category)}} {{category}}</li>
{{/each}}
</ul>
Handlebars templates can’t look up classes like that, nor does complex logic like categories.contains(category) work. You’ll need to add a computed property to the controller or component to supply proxy objects to the template. Assuming it’s a controller, here’s a rough example:
export default Ember.Controller.extend({
selectableCategories: function() {
var model = this.get('model');
return model.constructor.CATEGORIES.map(function(category) {
var categoryProxy = Ember.Object.create({
model: model,
name: category,
checked: function(key, value) {
var model = this.get('model');
// setter; the checkbox value has changed
if (arguments.length > 1) {
if (model.get('categories').contains(this.get('name'))) {
model.get('categories').removeObject(this.get('name'));
}
else {
model.get('categories').addObject(this.get('name'));
}
}
// getter; the template is checking whether the checkbox should be checked
return model.get('categories').contains(this.get('name'));
}.property('model.categories')
});
return categoryProxy;
});
}.property('model.categories')
});
The selectableCategories computed property returns an array of objects that observe the model’s categories attribute and represent whether or not each category is found within it.
Then in your template, you can use the proxy objects like this:
{{#each category in selectableCategories}}
{{input type="checkbox" name=category.name checked=category.checked}} {{category.name}}
{{/each}}

Global CRUD Ember.js

I was wondering if someone could give me brief direction. I'm making an app that I want to be able to take notes from anywhere I'm at in the app (CRUD). I'm rendering my presentations in my application controller using {{render}} but I'm not sure how to put the full crud operations there as well. This is what I have so far:
-- Presentation Controller
import Ember from 'ember';
var PresentationController = Ember.ObjectController.extend({
actions: {
edit: function () {
this.transitionToRoute('presentation.edit');
},
save: function () {
var presentation = this.get('model');
// this will tell Ember-Data to save/persist the new record
presentation.save();
// then transition to the current user
this.transitionToRoute('presentation', presentation);
},
delete: function () {
// this tells Ember-Data to delete the current user
this.get('model').deleteRecord();
this.get('model').save();
// then transition to the users route
this.transitionToRoute('presentations');
}
}
});
export default PresentationController;
-- Presentations Controller
import Ember from 'ember';
var PresentationsController = Ember.ArrayController.extend({
actions: {
sendMessage: function ( message ) {
if ( message !== '') {
console.log( message );
}
}
}
});
export default PresentationsController;
-- Model
import DS from 'ember-data';
var Presentation = DS.Model.extend({
title: DS.attr('string'),
note: DS.attr('string')
});
-- Presentations Route
import Ember from 'ember';
var PresentationsRoute = Ember.Route.extend({
model: function() {
return this.store.find('presentation');
}
});
export default PresentationsRoute;
-- Presentation Route
import Ember from 'ember';
var PresentationRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('presentation', params.id);
}
});
export default PresentationRoute;
-- Application Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return this.store.find('category');
},
setupController: function (controller, model) {
this._super(controller, model);
controller.set('product', this.store.find('product'));
controller.set('presentation', this.store.find('presentation'));
}
});
-- Application HBS
<section class="main-section">
<div id="main-content">
{{#link-to "presentations.create" class="create-btn expand" tagName="button"}} Add presentation {{/link-to}}
{{render 'presentations' presentation}}
{{outlet}}
</div>
</section>
-- Presentations HBS
{{#each presentation in controller}}
{{#link-to 'presentation' presentation tagName='li'}}
{{presentation.title}}
{{/link-to}}
{{/each}}
{{outlet}}
-- Presentation HBS
{{outlet}}
<div class="user-profile">
<h2>{{title}}</h2>
<p>{{note}}</p>
<div class="btn-group">
<button {{action "edit" }}>Edit</button>
<button {{action "delete" }}>Delete</button>
</div>
</div>
Basically what you're describing is a modal of sorts. It'll be accessible no matter what page (route) you're viewing, and you will be able to perform actions within this modal (creating notes, editing notes, deleting notes, etc) without leaving or affecting the current page being displayed in the background. Essentially, what this means is that you should leave the router alone, since the router is what controls the current page, and you don't want to affect that. You're not going to want to have any {{#link-to}} or transitionTo or transitionToRoute calls, nor any presentation-related routes or outlets.
Instead, you're going to have to handle everything at the controller and view level. This is where components really come in handy, as they're great for encapsulation if you use them correctly. Inside of presentations.hbs, I'd use components to represent each of the presentations:
{{#each presentation in controller}}
{{individual-presentation presentationModelBinding="presentation"}}
{{/each}}
Note that you'll need a corresponding IndividualPresentationComponent object that extends Ember.Component. Going further, inside of individual-presentation.hbs, I'd have code similar to what you have now, but with allowances for various CRUD operations:
{{#if editing}}
{{input value=presentationModel.title}}
{{textarea value=presentationModel.note}}
{{else}}
<h2>{{title}}</h2>
<p>{{note}}</p>
{{/if}}
<div class="btn-group">
{{#if editing}}
<button {{action "save" }}>Save</button>
{{else}}
<button {{action "edit" }}>Edit</button>
{{/if}}
<button {{action "delete" }}>Delete</button>
</div>
Note that the context for a component's template is the component itself, not some other controller. Similarly, actions fired inside of a component's template are direct to the component's actions hash. So your IndividualPresentationComponent will need to look like this somewhat:
IndividualPresentationComponent = Ember.Component.extend({
classNames: ['user-profile'],
actions: {
save: function () {
this.sendAction('save', this.get('presentationModel'));
this.set('editing', false);
},
edit: function () {
this.set('editing', true);
},
delete: function () {
this.sendAction('delete', this.get('presentationModel'));
}
}
});
Notice I'm using sendAction here. This is how components communicate with the outside world. To get this to work, go back your presentations.hbs and intercept the actions like so:
{{#each presentation in controller}}
{{individual-presentation presentationModelBinding="presentation"
save="savePresentation"
delete="deletePresentation"}}
{{/each}}
Here you're basically saying that if the component sends the "save" action, you want to handle it with your controller's "savePresentation" action, and if the component sends the "delete" action, you want to handle it with your controller's "deletePresentation" action. So your presentations-controller.js will need to implement those actions:
var PresentationsController = Ember.ArrayController.extend({
actions: {
savePresentation: function (presentationModel) {
...
},
deletePresentation: function (presentationModel) {
...
},
}
});
And you can delete PresentationController, since all of its functionality is now handled directly by your IndividualPresentationComponent and your PresentationsController.