display last object from dynamically updating array + emberjs + handlebars - ember.js

I am using rails 4 with emberjs. I have an array of users that I get from the server. I need to display the last object on the UI. Also, the user can add new users to the array but only the last/latest-added user should be visible.
My ember version details are as
Ember : 1.7.0-beta.1
Ember Data : 1.0.0-beta.8.2a68c63a
Handlebars : 1.3.0
jQuery : 1.11.1
My current handlebars code is as. After adding the new user to users array, the new user details is displayed.
{{each users}}
//display all the users here.
{{/each}}
What i want is something like this
{{#each users}}
{{#if #last}}
//display user details
{{/if}}
{{/each}}
I tried adding a property in the associated controller and removed the 'each' loop. I displayed the lastUser details which works. But if I add a new user, the UI still displays
the previous user details.
lastUser: (->
return #model.users.get('lastObject')
).property('#model.users')
How can this be acheived? Thanks.

You can simply use the following:
{{users.lastObject.firstName}}
{{users.lastObject.lastName}}
or more cleanly:
{{#with users.lastObject}}
{{firstName}}
{{lastName}}
{{/with}}
With just redefines this within the block to the object provided--in this case the users.lastObject object. Your lastUser computed property is unnecessary because Ember enumerables already provide this for free!

To watch additions to the users array, you need to watch the 'users.#each'
See the following documentation http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/

Related

Differing models inside ember #each

I'm working on an Ember.js application and have a table of devices, where I would like each listed device to open a modal dialog, when clicked, using that specific device as a model for the dialog.
The official documentation is slightly vague on how to do this, so I've been doing some guessing. I tried to pass the id of each device as a parameter inside the template:
{{#each}}
<button data-toggle="modal" data-target="#modal" {{action 'openViewModal' 'viewDeviceModal' id}} class="btn btn-default btn-xs">View details</button>
{{/each}}
{{id}} works as intended elsewhere in the template.
Now, inside the route, I put this:
actions: {
openModal: function(modalName, id) {
this.controllerFor(modalName).set("model", this.store.find('device', id));
return this.render(modalName, {
into: "application",
outlet: "deviceModal"
});
},
This gives me the following error:
"The value that #each loops over must be an Array. You passed (generated devices controller)"
Presumably, there is some confusion with the model of the list of devices (#each) and the model of the modal dialog. The modal dialog does not have #eachinside it.
I also lack a means to find out if my variables actually are what they are supposed to be. console.log does not work. I have not yet read all the documentation on debugging ember, so I'm hoping there will be some pointers there. Meanwhile, any help on how to get my modal dialog models to work would be greatly appreciated.
This is not a problem with your each loop, but with your model. You're setting the model for the controller as a single record when it looks like it expects an array of records. Change the first line of your action to this:
this.controllerFor(modalName).set("model", [this.store.find('device', id)]);
Also, as a tip, don't load the record from the store again when you already have it. Just pass the whole record in the action helper:
{{action 'openViewModal' 'viewDeviceModal' this}}
Then your action handler can look like this:
openModal: function(modalName, record) {
this.controllerFor(modalName).set("model", [record]);

Ember Navbar needs to show model data upon change

I am working on an application that needs to modify the content of the navbar after login. Here's a basic sketch I put together (with some help from other samples online):
http://jsbin.com/umutag/1/ with this underlying code: http://jsbin.com/umutag/1/edit
How do I get the header view to display model data?
Should I be using a different helper for the template? (e.g. a {{view}}, {{render}}, or {{control}})
BTW, I've scoured this site and others, but most entries are a few months old and I see ember has been changing a lot since then (or I'm missing something obvious). The above example uses Ember 1.0.0 RC6.
Bryan
You ultimately want to bind the value in the controller (probably ApplicationController) that keeps track of whether the user is logged in or not. Since this is pertaining to login, you most likely have something like a SessionController that keeps track of the token. Here's one way to go about it:
App.SessionController = Em.Controller.extend({
token: null,
username: null,
isLoggedIn: function() {
return !!this.get("token");
}.property("token");
// ...
});
App.ApplicationController = Em.Controller.extend({
needs: "session",
isLoggedInBinding: "controllers.session.isLoggedIn",
usernameBinding: "controllers.session.username"
//...
});
And in your navbar in the template:
{{#if isLoggedIn}}
<li>Logged in as {{username}}</li>
<li>{{#linkTo "index"}}Home{{/linkTo}}</li>
<li>{{#linkTo "secret"}}Secret{{/linkTo}}</li>
{{else}}
<li>{{#linkTo "login"}}Log in{{/linkTo}}</li>
{{/if}}

Persisting object controller details

I have the following template setup displaying a list of items, and wrapping each list in its own controller:
{{#each controller itemController="contact"}}
<div class="contact-entry">
<div class="title" {{action toggle on="click"}}>{{title}}</div>
{{#if showDetails}}
<div class="details">
<div>{{safe details}}</div>
</div>
{{/if}}
</div>
{{/each}}
The main controller is an ArrayController called ContentsController, and each item is wrapped in an ObjectController called ContentController. The content of the ContentsController is set explicitly in the route (I've simplified the data):
App.ContactsRoute = Em.Route.extend({
model: function() {
return [{'title': 'first'}, {'title': 'second'}];
}
});
This works really well. However if I navigate to another path and then back again, the settings on the ContentController don't persist. What happens is that the model data gets reloaded, and I assume a ObjectController gets created for each of the list items. Incidentally this is not the case for the ContentsController, which keeps its settings.
What is the solution to preventing ember of creating new ContentController for every list item every time I access the page?
I'm assuming your reference to ContentController is really ContactsController since you are using itemController="contact" in your #each block.
What kind of data are you trying to persist? The showDetails flag? The ContactControllers are going be created and destroyed anytime you exit / enter the route and there isn't anyway I know of to keep those around.
The ContactsController keeps its properties because its a singleton controller generated because you have a ContactsRoute.

View click action not returning correct object

I'm having trouble with a click handler in a view. It's not returning the expected member of the collection, but the collection as a whole.
I've created a jsfiddle to demonstrate the issue. I have an ArrayController, whose content I pre-populate. The view for this controller then uses the #each helper for the controller with another view:
{{#each controller}}
{{view App.ActivityListItemView}}
{{/each}}
This works, in that I see the name of the item on the page, and can click it.
The problem is in the click handler - if I #get('content'), the content for the parent controller is returned. How do I get the item that was clicked on? If you have a look at the console output in the jsfiddle you'll see the issue. I assume this is a context issue?
I've tried adding contentBinding="this" to the view:
{{#each controller}}
{{view App.ActivityListItemView contentBinding="this"}}
{{/each}}
but that makes no difference.
thanks,
Martin
How do I get the item that was clicked on? If you have a look at the console output in the jsfiddle you'll see the issue. I assume this is a context issue?
Exactly. You want the view's context instead of it's controller's content. So:
click: (data)->
console.log 'clicked on an activity'
selected = #get('context')
#get('controller').set('selectedActivity', selected)
console.log(#get('controller').get('selectedActivity.name'))
Why?
By default the {{#each}} helper does not create a new controller instance for items in the array. So when you#get('controller')` from the view helper it searches up the view heirarchy until a controller is found - in this case that is the array controller.
If you want to have a separate controller for each item you could provide an itemController attribute to the each helper - see http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_each
Right, I've got this working, I think the issue sprang for a lack of understanding of the contentBinding argument. Basically I've changed to using a specific name of 'activityBinding' within the #each block, and then referring explicitly to the activity in the click handler. See jsfiddle for a working demo.
{{#each controller}}
{{view App.ActivityListItemView activityBinding="this"}}
{{/each}}
and
click: ->
console.log 'clicked on an activity'
console.log #get('activity.name')
content = #get('activity')
#get('controller').set('selectedActivity', content)

text field filtering a list using ember + ember data

I'm new at using ember, but already familiar with it, basically following some tutorials here and there and reading the api docs. But tutorials don't go too deep into more complex topics.
These are the details: I already implemented a web page that shows a list of items. The following are the relevant code excerpts from different parts of the app.
// the data model, the view and the controller
App.Item = DS.Model.extend({
name: DS.attr('string')
});
App.ItemsController = Ember.ArrayController.extend();
App.ItemsView = Ember.View.extend({ templateName: 'items' })
// in the router's corresponding route
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('items', App.Item.find())
}
// in the handlebars template
<ul class="items">
{{#each content}}
<li>{{name}}</li>
{{/each}}
</ul>
The data for this list is loaded remotely via ember-data (notice the App.Item.find() call in the route's connectOutlet method above) and a handlebar template displays it, and dynamically updates the list as the data changes. Up to here this is basic ember.
Now I want to have a text field at the top of the list, and when the user types in this text field, the list should be updated, by filtering and showing only the items with a name that matches what the user is typing. The actual definition of what a matching name is, is irrelevant to this question. It could be those names that contain the typed string, or that start with it.
I know my next step is to include a textfield view on top of the list in the handlebars template:
<div class="search-bar">
{{view Ember.TextField}}
</div>
<ul class="items">
{{#each content}}
<li>{{name}}</li>
{{/each}}
</ul>
So my questions at this point are the following:
How do I refer to this text field in javascript code so I can attach a listener to it to detect when it changes?
And more importantly, what do I need to do inside this event listener so the list gets filtered?
I would like to know how to achieve it filtering data loaded locally, but also how to do it by loading the filtering data remotely everytime the user types.
I actually need to implement something slightly more complex than this, but knowing how to do this will help.
You can have a computed property on your controller that filters the content based on a text field.
App.ItemsController = Ember.ArrayController.extend({
// ...
searchedContent: function() {
var regexp = new RegExp(this.get('search'));
return this.get('content').filter(function(item) {
return regexp.test(item.get('name'));
});
}.property('search', 'content.#each.name')
});
Then you just bind your text field to controller.search. Example: http://www.emberplay.com#/workspace/2356272909
To filter data remotely you should have ember data load more items every time search changes. This can be done by sending an event to the router every time there is a change.