I'm having hard time to understand the different ways to bind models to templates in Ember and when use one or the others.
Following different tutorial my code should work but it doesn't.
cal.hbs
<div class="calendar">
{{#each cal}}
{{#if days_label.length}}
{{days_label.[1]}}
{{/if}}
{{test}}
{{/each}}
</div>
cal_model.js
WebCalendar.Cal = DS.Model.extend({
today: DS.attr('date'),
test: DS.attr('number'),
days_label: DS.attr('array'),
months_label: DS.attr('array'),
days_per_month: DS.attr('array')
});
WebCalendar.Cal.FIXTURES = [{
id: 1,
test: 2,
today: new Date(),
days_label: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
months_label: ['January', 'February', 'March', 'April',
'May', 'June', 'July', 'August', 'September',
'October', 'November', 'December'],
days_per_month: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
}];
Router.js
WebCalendar.CalRoute = Ember.Route.extend({
model: function(){
return this.store.find('cal');
}
});
cal_controller.js
WebCalendar.CalController = Ember.ArrayController.extend({
month: function(){
var today = this.get('test');
return today + 1;
}.property('model'),
monthCal: function(year, month){
var today = this.store.find('cal', { today: new Date() });
this.year = today.getFullYear();
this.month = today.getMonth();
},
updateContent: function() { //doesn't work if is called arrangedContent
return this.get('model').content;
}.property('model')
});
For what I understood there are these ways to hook the model:
First
<div class="calendar">
{{#each cal}}
{{#if days_label.length}}
{{days_label.[1]}}
{{/if}}
{{test}}
{{/each}}
</div>
Second
<div class="calendar">
{{#each cal in controller}}
{{#if days_label.length}}
{{days_label.[1]}}
{{/if}}
{{test}}
{{/each}}
</div>
Third
<div class="calendar">
{{#each item in arrangedContent}} //this work if I don't specify arrangedContent in my controller
{{#if item.days_label.length}}//this doesn't work
{{item.days_label.[1]}}//this doesn't work
{{/if}}
{{item.test}}// this works
{{/each}}
</div>
What I want to understand and I can't find good explanation around is: what's the difference between those ways, when use what and what exactly is and does arrangedContent
Thank you very much for any explanation!
ArrangedContent ,if Im not wrong , is an array generated by
EMBER.SORTABLEMIXIN CLASS that is nowday implemented by default to EMBER.ARRAYPROXY CLASS , so your arraycontrollers inherit the arrangedcontent property.
What it really is , an array that is your controller's content , sorted by the properties you'll define in your controller (sortProperties, sortAscending).
So if I have a controller content(that is an array of objects that have the property name) , I can choose to sort them by the name property (sortProperties: ['name'] , thus rendering them in alphabetical order ascending (sortAscending: true).
The difference between your first and second question, is what is passed inside the loop as the current object.
In first you will call {{days_label}} to get the days_label that is equivalent as saying this.days_label.
In your second example though you can call {{days_label}}, but also call cal.days_label that in both cases is the same but it's needed if you were let's say go deeper inside an iteration, as:
{{#each cal in controller}}
{{#if days_label.length}}
{{#each day in days_label}}
{{day.someProperty}}
{{days_label.someOtherProperty}}
{{/each}}
{{/if}}
{{test}}
{{/each}}
In this example I could just do {{#each controller}} ,inside it {{#each days_label}} and then do {{someProperty}} inside it , but then I wouldn't be able to do the same for {{days_label.someOtherProperty}} because this would have changed.
One more thing to consider (very usefull when you are using ember data), is the difference between the model and the content of the controller.
The content is set inside route's setupController (controller, model) but can be overwritten.
UPDATE
Thank you again for your answer :) Right, the code is bit messy now but what i'm try to achieve now is very simple. With the model that I have, I want to display the current month. I try to take 'today' from my model, in my controller, do today.getMonth() and save as property(month), so i should be able to display in the hbs with {{month}}. – Giorgia Sambrotta
What I would do then is move the logic from the controller inside the model.
Some basic things to understand about ember data is that calling this.store.find('resourceName'), makes a request to your api's resoure index route thus returning an array of objects.
This call will be triggered each time you access this route because ember is agnostic of whereas your records have been changed at your server.
So given that you get an array of objects that all have some sort of date in them and you just want to get their month from it , you can define a computed property inside your WebCalendar.Cal model.
Inside WebCalendar.Cal
month: (function() {
var today = this.get('today');
//do something to get the value you want here
}).property('today')
Another thing to consider is that since months_label, days_label and days_per_month are static arrays you really don't need to pass them in each record but instead move them inside your model too, as properties.
So now that we've moved logic to the model you can just call {{month}} inside itteration and this will give you the result of the model's computed property. Also if a cal record happens to change today you will get that change imediately.
The only reason you wouldn't want to do it this way is if you wouldn't want this month property anywhere else except this specific controller.
In this case you would go
Inside WebCalendar.CalController
setMonthToEachCal: function(){
this.get('content').forEach(function(cal) {
today = cal.get('today');
//prepare the month value here and set it in month var
cal.set('month', month);
});
}.observes('content.#each.today'),
Related
So I thought I had this solved already as it worked with my prototype using just arrays, but now that I'm actually dealing with the model, I'm not sure if it will work.
If I create a property on my controller like so:
myAttributes: [{
name: 'attr1',
label: 'Attribute 1',
value: null
}, {
name: 'attr2',
label: 'Attribute 2',
value: 1
}]
then I can loop through myAttributes with {{#each}}, so while each object is essentially a field, that's different than the fields on a single model, which only have a value, so I can't do {{#each model as |rec|}} when there's only one record.
In a nutshell, I want to have a button group to set the value for each field in my model, like so:
I have around 60 of these fields so that's why I wanted to use {{#each}} and my component for each field, but of course each goes over records in the model, not fields in a record.
Is this impossible to do? Do I just have to bite the bullet and write out the markup for each field like I would do if I had only a few fields?
Update: Even if I could loop through the fields on a single record (maybe with {{#each model.#each as |field|}}?), for this case what I also need to do is break out the fields into sections in the UI, so for example loop through fields 1-10 in the first section, and 11-20 in the next section, and there doesn't seem to be a good way to do that.
In the end, I think I'm better off just using a component on each field, like so:
{{attribute-component value=model.attr1}}
{{attribute-component value=model.attr2}}
.
.
.
There's a neat helper called each-in that iterates over key/attributes of an object.
https://guides.emberjs.com/v2.6.0/templates/displaying-the-keys-in-an-object/
There is a way to do that in your case, but I think that actually a simpler way to get the same effect is this:
template.hbs
{{#each myArrayOfPeople as | person | }}
{{#each attribs as | anAttrib | }}
{{get person (mut anAttrib)}},
{{/each}}
<br>
{{/each}}
component.js
attribKeys:['name', 'phoneNumber', 'otherAttrib'];
This will output something like:
joe, 123, something,
sam, 456, anotherthing,
sarah, 944, foo
you can use that same (mut anAttrib) helper to bind an attribute to an input or whatever your need is.
{{attribute-component value=(mut anAttrib)}}
I have now forgone trying to iterate over each field because I actually need to break out the fields into accordion containers based on other criteria, although #averydev's answer about nested {{#each}} was a very cool tip and useful.
I also updated to Ember 1.13 in order to use the mut helper (although I had previously used an action on the component, that passes the new value to an action in the controller, that sets the value to the model property. Since that was convoluted, this new method using mut is much more understandable.
Just to help anyone else out in the future, here's my code (simplified for SO, the real code has nothing to do with rooms and furniture). I have a custom component that uses a radio button group component to actually set the value (this is from ember-radio-button):
template.hbs
{{room-style title='Living Room' field=(mut model.livingRoomStyle)}}
{{room-style title='Master Bedroom' field=(mut model.masterStyle)}}
templates/components/room-style.hbs
<div class="btn-group" data-toggle="buttons">
{{#radio-button value=1 groupValue=field classNames="btn btn-default" changed="changed"}}
Modern
{{/radio-button}}
{{#radio-button value=2 groupValue=field classNames="btn btn-default" changed="changed"}}
French
{{/radio-button}}
{{#radio-button value=3 groupValue=field classNames="btn btn-default" changed="changed"}}
Rustic
{{/radio-button}}
</div>
components/room-style.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
changed: function() {
//update() is a function you get with attrs.[prop] when using the mut helper
this.attrs.field.update(this.get('field'));
}
}
});
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|}}
In an indexview I have links that set the sorting:
# Template
{{#link-to 'products' (query-params sortBy="title")}}Title{{/link-to}}
{{#link-to 'products' (query-params sortBy="price")}}Price{{/link-to}}
# Controller
queryParams: ['sortBy']
sortBy: 'id'
sortProperties: ( ->
[#get("sortBy")]
).property("sortBy")
That generates links that always have the class of 'active', but I want to highlight the currently active sort filter. What is the best way to do it?
I tried binding to a computed property like this:
{{#link-to 'products' (query-params sortBy="price") classNameBindings='sortByPrice'}}Price{{/link-to}}
sortByPrice: -> (
#get('sortBy') == 'price'
).property('sortBy')
That didn't quite work, but even if it did, that's not DRY at all – and eventually I would like to add a lot of different attributes on which to sort.
As I understand, the problem is that ember adds the 'active' class when it's in the context of that controller, which it always is with different query-params.
(Running the latest canary build of Ember as of 14th June)
This has been fixed in Ember Canary, as of https://github.com/emberjs/ember.js/pull/5109
QueryParams should add the "active" class based on whether the declared parameter in the {{#link-to}} helper has the same value as the attribute at that moment, as I can demonstrate in this jsbin.
That said, I'm having the same problem, so I believe there's some fringe case where this doesn't work right, and I'd be happy if you could modify this example to reflect that.
I'm facing the same problem now and I have temporary solution.
<!-- Posts Template -->
<!-- Categories -->
<div class="block step visible-desktop visible-tablet">
<div class="header">
<h3>Category</h3>
</div>
<div class="area categories">
<ul>
{{#each staticCategory in controller.staticCategories}}
{{post-category currentCategory=currentCategory staticCategory=staticCategory}}
{{/each}}
</ul>
</div>
</div>
<!-- Categories end -->
//Posts Controller
staticCategories: ['Front-End', 'JavaScript', 'jQuery', 'null'],
currentCategory: function () {
return this.get('category');
}.property('category'),
queryParams: ['category'],
category: null,
filteredContent: function () {
var category = this.get('category');
var posts = this.get('model');
return category ? posts.filterBy('category', category) : posts;
}.property('category', 'model')
//Post-Category Component template
{{#link-to 'posts' (query-params category=staticCategory)}}
{{staticCategory}}
{{/link-to}}
//Post-Category Component js
Blog.PostCategoryComponent = Ember.Component.extend({
tagName: 'li',
isActive: function () {
return this.get('staticCategory') === this.get('currentCategory')
}.property('currentCategory', 'staticCategory'),
classNameBindings: ['isActive:active']
});
I've found a solution for this. Ember (currently) seems to make a distinction between linking to a resource and linking to a sub-route, e.g doing {{link-to "resource"}} will always set the active class, but doing {{link-to "resource.index"}} will toggle the active state according to their query params.
Here's a jsbin showcasing the difference: http://emberjs.jsbin.com/zawukucisoni/3/edit
I've opened an issue that can be found here: https://github.com/emberjs/ember.js/issues/5359
Problem Summary: While I can get the children of a collection (defined on an ArrayController) to use a specific object controller for the individuals, this doesn't work on filtered subsets of the children.
Short context: I've got Subcriptions, which have Items. I'd like to filter the subscriptions in my view by type, and have the items within those subscriptions sort by timestamp. Here's the SubscriptionsController:
Social.SubscriptionsController = Ember.ArrayController.extend({
itemController: 'subscription',
announcements: function() {
return this.get('model').filterBy('kind', 'announcement');
}.property('model.#each.kind'),
user_sites: function() {
return this.get('model').filterBy('kind', 'user');
}.property('model.#each.kind')
});
I've defined SubscriptionController thusly:
Social.SubscriptionController = Ember.ObjectController.extend({
items: function() {
return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['post_timestamp'],
sortAscending: false,
content: this.get('content.items')
});
}.property('content.items'),
});
And here's the relevant bit of my handlebars template:
{{#each controller}}
<li>{{controller.description}} {{controller.kind}} {{controller.feed_url}} {{controller.base_url}}</li>
<ul>
{{#each item in controller.items}}
<li>{{item.post_timestamp}}: {{{item.summary}}}</li>
{{/each}}
</ul>
{{/each}}
That code more-or-less does what I want: it renders the items, sorted by item.post_timestamp, as SubscriptionController defines it.
The problem is if I change {{#each controller}} to {{#each site in user_sites}}, the itemController property doesn't seem to magically apply to the sublist. Is there some kind of Sorcery I should use to inform Ember in my filters that I'd rather return the controller for the objects rather than the objects themselves?
EDITed to add: I know I can just add a new property like sorted_items on the Subscription model itself, but this feels wrong, design-wise. The model holds the data, the view shows the data, and the controller deals with sorting / filtering and all that jazz. Or at least that's part of how I think about MVC separation.
You can manually set the itemController for loops. You might try this in your template:
{{#each site in user_sites itemController="subscription"}}
...
{{/each}}
Posted this on the emberjs forums, but SO seems more appropriate.
Hi! I have two routes called classyears and classyear. They're nested like so:
this.resource('classyears', function(){
this.resource('classyear', { path: '/classyear/:classyear_id'});
});
Posterkiosk.ClassyearsRoute = Ember.Route.extend({
model: function() {
return Posterkiosk.Classyear.find();
}
});
Posterkiosk.ClassyearRoute = Ember.Route.extend({
model: function(model) {
return Posterkiosk.Classyear.find(model.classyear_id);
}
});
My templates are:
Classyears:
<div class="yearList">
{{#each item in model}}
{{#linkTo 'classyear' item}}{{item.id}}{{/linkTo}}
{{/each}}
</div>
{{outlet}}
Classyear:
<div class="transformContainer">
{{trigger sizeComposites}}
{{name}}
{{#each students}}
{{partial student}}
{{/each}}
</div>
(The "trigger" helper is from another SO post. The issue was happening prior to adding it, though)
I'm using the Ember-model RESTAdapter. When I load /classyear/:classyear_id, it looks like classyear is rendering its data twice. Once with the correctly-loaded data, and once with no data loaded. The order appears to be random. If the no-data option happens last, it wipes out the correctly-loaded data, leaving a blank page. Vice-versa, and the page content displays just fine.
Any thoughts?
/edit 2: More info:
It looks as though the 0-record reply is from classyears loading. So, it's likely that the zero-record reply is actually just zero records in my hasMany field "students".
If I load /classyears (no class year specified), it only loads once, to get the class year options. If I then click on a class year, it doesn't reload classyears unless I refresh the page, at which time, it loads both, and if the classyears load (a findall) finishes second, it displays no data on the page (other than the classyears template, correctly populated, at the top).
So... maybe my classyears model isn't handling the hasMany field correctly?
I feel like I'm getting closer, but still not sure what's up.
First of all you need to specify a model for a Student, like so:
Posterkiosk.Student = Ember.Model.extend({
id: Ember.attr(),
name: Ember.attr(),
imageUrl: Ember.attr(),
gradyear: Ember.attr()
});
Posterkiosk.Student.adapter = fixtureAdapter;
Now, in your example you are setting the key for the has many to students, but students is an array of objects, not id's, so create a property called student_ids, and pass an array of ids, now that is your key.
Posterkiosk.Classyear = Ember.Model.extend({
students: Ember.hasMany('Posterkiosk.Student', {key: 'student_ids'})
});
If you set embedded: true, then your Classyears server response should come back like this:
{
classyears: [
{..},
{..}
],
students: [
{..},
{..}
]
}
Otherwise, EM would make a separate call to the endpoint on the Student model, and get that data based on the student_ids property.
See the working jsbin.
Tip: RC.7+ removed the underscore from partials, plus the partial name should be in quotes..