Use same handlebars action helper from different templates - what's the best practice - ember.js

I have an ember.js app. which shows a list of objects (let's call them Post objects) using posts.handlebars. Each list item contains a link to show the details for that Post (post.handlebars).
Both the list item and the detail page contain a delete link which removes the object from the collection of Posts. Since there's no difference in implementation except for the label that show the link, it makes sense to keep it DRY.
The current code is working:
# router
App.Router = Em.Router.extend({
...
"delete": function(router, event) {
var post = event.context;
if (confirm("Are you sure you want to delete the post with title '" + (post.get('title')) + "'?")) {
post.deleteRecord();
post.store.commit();
App.router.transitionTo('posts.index');
}
}
});
# posts.handlebars
<ul>
{{#each post in controller}}
<li>
{{post.title}}
<a {{action delete post}}>x</a>
</li>
{{/each}}
</ul>
# post.handlebars
<p>{{title}}</p>
<a {{action delete content}}>Destroy</a>
But I don't want to repeat the code that contains the delete action.
My next best guess is to define a view and re-use this in both templates.
However, now I'm not able to pass the Post object as context to the action by moving it to a view (I might be doing something rong).
By moving the event from the router to the view I got it working, but that doesn't feel right.
My current solution looks like this:
App.DeletePostView = Em.View.extend({
mouseUp: function(event) {
var id, post;
id = this.get('content.id');
post = App.Post.find(id);
if (confirm("Are you sure you want to delete the post with title '" + (post.get('title')) + "'?")) {
post.deleteRecord();
post.store.commit();
App.router.transitionTo('posts.index');
}
}
});
# posts.handlebars
<ul>
{{#each post in controller}}
<li>
{{post.title}}
{{#view App.DeletePostView contentBinding="post"}}
x
{{/view}}
</li>
{{/each}}
</ul>
# post.handlebars
<p>{{title}}</p>
<div>
{{#view App.DeletePostView contentBinding="this"}}
Destroy
{{/view}}
</div>
Does anyone know if there is a better approach if I want to re-use a handlebars action helper?

Related

ember: manipulate the DOM (like hide a long list of items)

I'm new to ember. I have a demo app working, and I'm moving towards making it look nice.
One issue I'm starting to grapple with is how to manipulate DOM elements. Coming from a server-side world, it's been pretty easy to just throw some jquery at stuff like this. Doesn't appear to be as straightforward in ember. But I'm probably missing something or approaching it wrong.
The immediate problem is: I have a list of 40-some <li> elements and I want to create a toggle to show/hide the list after the first 10 items.
I got something to work in my component like this:
import Ember from 'ember';
let $ = Ember.$;
export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent() {
let listTotal = $("#myList li").length;
$("#myList li").slice(10, listTotal).hide();
}
});
The problem is that when actions trigger and the view is re-rendered, afterRenderEvent() doesn't get called again, and the list shows in its entirety.
The above component corresponds to this template:
<ul id="myList">
{{#each aggs.categories as |category|}}
<li><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>
Is there a way to get around this? OR, is there a more "ember" way to approach this problem (and DOM manipulation in general)?
Introduce showCount property in controller and have action to set showCount to total list count.
import Ember from 'ember';
export default Ember.Component.extend({
showCount: 10,
actions: {
setShowCount(count) {
//you can set total count
this.set('showCount', count);
}
}
});
Install ember truth helpers addon or write computed property to check.
<ul id="myList">
{{#each aggs.categories as |category index|}}
<li style="{{if (gt index showCount) 'display:none'}}"><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>
If you don't want to display then you can just iterate just showCount aggs.categories alone by writing computed property or using ember-composable-helpers junk method
<ul id="myList">
{{#each (chunk showCount aggs.categories) as |category index|}}
<li><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>

How to save the endpoint data in model store if call is made from actions in controller

I have created a dialog box using the ember-modal-dialog. The content that is going to displayed in the dialog is received from the server. I am able to make the call to server and fetch the data. But I don't know how to save the data into my model store from actions.
Controller.js
actions:{
fiModal1: function(photo){
Ember.$('body').addClass('centered-modal-showing');
var currentState = this;
photo.toggleProperty('fidialogShowing'))
console.log('opendialog');
raw({
url: 'http://example.co.in/api/photo/'+photo.get('like_pk')+'/likes/',
type: 'GET',
}).then(function(result){
currentState.set('model.feed.liker',result)
});
},
bookmarked:function(liker){
liker.set('is_bookmarked',true)
},
}
feed.hbs
<p {{action "fiModal" photo }}>
{{photo.0.numlikes}}
</p>
{{#if photo.fidialogShowing}}
{{#modal-dialog translucentOverlay=true close = (action "fiDialogClose" photo)}}
{{#each model.feed.liker as |liker}}
<div class = "col-sm-6">
{{#if liker.is_bookmarked}}
<a href {{action "unbookmarked" liker}}>
<img class="foll" src = "images/button-bookmark-secondary-state-dark-b-g.png">
</a>
{{else}}
<a href {{action "bookmarked" liker}}>
<img class="foll" src = "images/button-bookmark.png">
</a>
{{/if}}
</div>
{{/each}}
Now the problem is that when action inside the dialog box is fired it throws an error:
fiver.set is not function
I think that the problem is occurring because I am not saving the result in the model store. How should I do it so the action inside the dialog box also works?
You can just encapsulate the results from your server into Ember.Object
Ember.Object.create(json)
For exemple replace your line
currentState.set('model.feed.liker',result)
by
currentState.set('model.feed.liker', result.map(function(item) {
return Ember.Object.create(item);
})
that way each elements inside your model.feed.liker should have a method 'set' available.

rendering one item from a list in Ember

In my Ember app, I get a list of all the restaurants using an ajax call copied from Discourse co-founder's blog post http://eviltrout.com/2013/02/27/adding-to-discourse-part-1.html
App.Restaurant.reopenClass({
findAll: function() {
return $.getJSON("restaurants").then(
function(response) {
var links = Em.A();
response.restaurants.map(function (attrs) {
links.pushObject(App.Restaurant.create(attrs));
});
return links;
}
);
},
I have a Restaurants route set up which calls the findAll shown above and renders it into the application template
App.RestaurantsRoute = Ember.Route.extend({
model: function(params) {
return(App.Restaurant.findAll(params));
},
renderTemplate: function() {
this.render('restaurants', {into: 'application'});
}
});
The restaurants are displayed as a restaurants template like this with a link to each individual restaurant. I've also included the restaurant template
<script type="text/x-handlebars" id="restaurants">
<div class='span4'>
{{#each item in model}}
<li> {{#link-to 'restaurant' item}}
{{ item.name }}
{{/link-to }}</li>
{{/each}}
</ul>
</div>
<div class="span4 offset4">
{{ outlet}}
</div>
</script>
In the Ember router, I have a parent/child route set up like this
this.resource("restaurants", function(){
this.resource("restaurant", { path: ':restaurant_id'});
});
Therefore, I'm hoping that when I click on the link to a particular restaurant in the restaurants list, it'll insert this restaurant template into the outlet defined in the restaurantS (plural) template
<script type="text/x-handlebars" id="restaurant">
this text is getting rendered
{{ item }} //item nor item.name are getting rendered
</script>
This restaurant template is getting rendered, however, the data for the item is not getting displayed.
When I click {{#link-to 'restaurant' item}} in the list, item represents that restaurant.
In this setup, does Ember need to make another ajax call to retrieve that particular item, even though it's already been loaded from the findAll call?
In the event that I do need to query for the individual restaurant (again) I created a new route for the individual restaurant
App.RestaurantRoute = Ember.Route.extend({
model: function(params) {
console.log(params);
console.log('resto');
return App.Restaurant.findItem(params);
}
});
and a findItem on the Restaurant model
App.Restaurant.reopenClass({
findItem: function(){
console.log('is this getting called? No...');
return 'blah'
}
but none of those console.logs are getting called.
In the Ember starter video https://www.youtube.com/watch?v=1QHrlFlaXdI, when Tom Dale clicks on a blog post from the list, the post appears in the template defined for it without him having to do anything more than set up the routes (as I did) and the {{outlet}} within the posts template to receive the post.
Can you see why the same is not working for me in this situation?
When you navigate to the restaurant route, the item will be the model to this route.
So in your template, if you try
<script type="text/x-handlebars" id="restaurant">
this text is getting rendered
{{ model.name }}
</script>
You'll be able to see the name of the restaurant
And also the model hook is not called, and the further console.logs are not working,
because
Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.
Hope everything will be clear now.

ember.js and nested templates/views

I'm still trying to learn ember.js so please bear with me.
Objective
I'm currently creating a one page web application. When the application, the application will do an ajax call which will return a list of numbers lets. The application will process these numbers and create a div for each of the numbers and store it into a div container.
A click event will be associated with each div, so when the user clicks on the link a pop up dialoge will come up.
Code
Index.html
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="payloads">
<div class="page">
<div id="desktopWrap">
<div id="theaterDialog" title="Theater View" class="bibWindow1">
{{view.name}}
{{#each item in model}}
<div {{bindAttr id="item"}} {{action click item}}>
<div class="thumb1" ></div>
<div class="userDetails1">Payload {{item}}</div>
<div class="online1" ></div>
</div>
<div class="spacer10"></div>
{{/each}}
</div>
</div>
</div>
</script>
My app.js file is here:
App = Ember.Application.create();
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['Payload_1', 'Payload_2', 'Payload_3'];
}
});
App.PayloadsRoute = Ember.Route.extend({
model: function() {
return ['Payload_1', 'Payload_2', 'Payload_3'];
}
})
App.IndexController = Ember.ObjectController.extend(
{
click: function(e)
{
alert("clicked:" + e);
}
})
General Idea
The current code above will create the "theaterDialogue" div box with 3 divs. A onclick action is associated with it through the Controller for each of these divs. When a user clicks on the first div "payload 1" will be printed in an alert box, second div "payload 2" etc. Instead of the print out, I want to be able to render a new dialogue box (jquery dialogue box) where the contents will be rendered from a template. Its not clear to me how this is done.....I understand that views are used to store data for the templates...but not how you would nest a template within one that is generated by an action?
If you could point me anyone, that would be awesome!
Any advice appreciated,
D
Basic approach for nesting is,
Define the nested routes (Main step, if you get this right, you are almost there)
Add {{outlet}} in the templates if you think that this view will have something appended to it later on
For example we have 3 views A, B, C and the nesting is as follows
A
|_B
|_C
Then the templates A & B should have the {{outlet}}, while C is the last one it shouldnt have {{outlet}}
A good example

Assigning 'active' class to selected list item in EmberJS

I have a list and I'd like to set one item as class="active" automatically. Given the following bootstrap code:
<ul class="nav">
<li {{bindAttr class="atIndex:active"}}>{{#linkTo "index"}}Index{{/linkTo}}</li>
<li {{bindAttr class="atAbout:active"}}>{{#linkTo "about"}}About{{/linkTo}}</li>
<li {{bindAttr class="atLogin:active"}}>{{#linkTo "login"}}Login{{/linkTo}}</li>
</ul>
atIndex, atAbout and atLogin reside in my ApplicationController.
To render as:
<ul class="nav">
<li class="active"><a...>Index{{/linkTo}}</li>
<li><a...>About<a></li>
<li><a...>Login<a></li>
</ul>
What's the best way to do this with Ember 1.0 pre4? I'd rather not add special code to every view or controller.
PS - atIndex: true works, but atIndex: function() {return true; }.property().volatile() does not. Which makes me think I'm doing something wrong.
Thank you!
{{#link-to "dashboard" tagName="li" href=false}}
<a {{bind-attr href="view.href"}}>
Dashboard
</a>
{{/link-to}}
By far the cleanest way to solve this is by taking advantage of the linkTo helper's built-in support for setting the active class when rendering links. AFAIK this is not yet documented other than in the source code:
implementation: https://github.com/emberjs/ember.js/blob/master/packages/ember-routing/lib/helpers/link_to.js#L46
example: https://github.com/emberjs/ember.js/blob/master/packages/ember/tests/helpers/link_to_test.js#L120
To take advantage of this feature just adjust your css to style based on having an active class on the link instead of the li element. If you really need to style the li you can create a custom view and helper that extends Ember.LinkView and uses an li but changing css will be far easier.
--- UPDATE ----
Since we all love twitter bootstrap just changing the css is perhaps not such a great option. In that case, the following will do the trick:
App.ApplicationView = Ember.View.extend({
currentPathDidChange: function() {
Ember.run.next( this, function() {
this.$("ul.nav li:has(>a.active)").addClass('active');
this.$("ul.nav li:not(:has(>a.active))").removeClass('active');
});
}.observes('controller.currentPath')
});
Working example using ember linkTo helper with bootstrap pills: http://jsbin.com/ekobod/5/edit (requires ember-1.0.0-pre.4)
the active route's path is updated automatically in the ApplicationController via currentPath so I did something like that in my App...
In ApplicationController added properties like so:
isProductsActive: function(){
if ( this.get('currentPath') === 'products' ) return 'active';
else return '';
}.property('currentPath')
and in my ApplicationView template:
<li {{bindAttr class="isProductsActive"}}>
{{#linkTo "products"}}Products{{/linkTo}}
</li>
I made an ember-cli addon that handles this:
https://github.com/alexspeller/ember-cli-active-link-wrapper
EDIT:
Finally, the best way I've found to use the activate class of bootstrap li element using ember.js of the link.
{{#linkTo "dives" tagName="li"}}
<a {{bindAttr href="view.href"}}>Dives</a>
{{/linkTo}}
--------------8<--------------
DEPRECATED:
I guess the previous answers were relevant before Ember.js introduced the activeClass attribute for linkTo helper.
Now I would solve the problem like this :
<ul class="nav">
<li >{{#linkTo "index" activeClass="active"}}Index{{/linkTo}}</li>
<li >{{#linkTo "about" activeClass="active}}About{{/linkTo}}</li>
<li >{{#linkTo "login" activeClass="active}}Login{{/linkTo}}</li>
</ul>
Enber will automatically add the class when relevant.
If I may suggest another solution that requires nothing but Handlebars:
<li {{bind-attr class="view.innerLink.active:active"}}>
{{#link-to "path" viewName="innerLink"}}Click{{/link-to}}
</li>
This sets the LinkView object as a member of the parent view, which's active attribute you can then reference.
I found a pretty simple Solution using linked items in a list group(http://getbootstrap.com/components/#list-group-linked).
<div class="list-group">
{{#each thing in list}}
{{#link-to "details" thing.id tagName="a" href="view.href" class="list-group-item" {{thing.name}} {{/link-to}}
{{/each}}
</div>
Works with Bootstrap v3.1.1 and Ember v1.7.0
Just nest the {{link-to}} with a tagName on the outer one. I'm doing this on EmberJS 2.0.
{{#link-to "admin.websocket" tagName="li"}}
{{#link-to "admin.websocket"}}WebSocket{{/link-to}}
{{/link-to}}
If you want to use Bootstrap navigation in Ember then you can use Bootstrap for Ember that has out of the box support for this:
Github: https://github.com/ember-addons/bootstrap-for-ember
Demo: http://ember-addons.github.io/bootstrap-for-ember/dist/#/show_components/tabs
A lot of these answers are outdated. Here is a much cleaner (and DRY) version for Bootstrap and Ember 2.x:
{{#link-to "index" tagName="li" as |link|}}
Index Page
{{/link-to}}
I solved a similar problem by creating a view for each item and using classNameBindings (I have to say that i don't have a HTML list, i.e.<a>...</a> in my app, just list of <div>).
Here is the way it works for me:
In tasklist.handlebars i iterate over my custom view
{{#each tasks}}
{{view App.TaskListItemView contentBinding="this"....}}
{{/each}}
Ember will insert a view (i. e. <div>) for each item.
The view class for each item is defined in task_list_item_view.js as
App.TaskListItemView = Ember.View.extend({
controller: null,
classNameBindings: ['isSelected', 'isClosed'],
isClosed: function() {
var content = this.get('content');
return content && !content.isOpen(new Date);
}.property('controller.content.#each'),
isSelected: function() {
return (this.get('controller').isSelectedTask(this.get('content')));
}.property('controller.taskSelection.#each'),
....
});
Finally the template for the view just renders my link in tasklistitem.handlebars
<a {{action "selectTask" view.content target="view"}} rel="tooltip"
{{bindAttr title="view.content.comment"}} class="taskListLink">
....
</a>
AFAIK you have to specify the source data in the property() call to let ember know when to (re-) evaluate the property.
Hope that helps
I went with:
Ember.LinkView.reopen({
didInsertElement:function(){
if(this.$().hasClass('active')){
this.$().parent().addClass('active');
}
}
});
I didn't want to use the accepted answer as I wanted to keep my li elements as plain old html. There might be a better way to check the active state but I couldn't get access to the right property.