Action on self created View - ember.js

If I've a simple template with a Button which has an Action, and create a ember View using this template, how can I let the action target the function on teh View.
Example:
http://jsfiddle.net/Krutius/DxsXz/
Handlebars / HTML:
<div id="content">
<div id="main"></div>
</div>
<script type="text/x-handlebars" data-template-name="test">
<button class="btn btn-primary" {{action go}}>Suchen</button>
</script>​
JavaScript:
$(function() {
App = Em.Application.create({
rootElement: "#content"
});
Em.View.create({
templateName: 'test',
go: function() {
alert: "go";
}
}).append("#main");
});​

The problem is your append call: Ember.View#append does not take any arguments, see the code.
I don't know what the final application / html should look like, so there are several answers how you could solve your problem. The simplest would be to inline the button into a template for the application, see http://jsfiddle.net/pangratz666/vY9PE/:
Handlebars:
<script type="text/x-handlebars" data-template-name="test">
<div id="content">
<div id="main">
<button class="btn btn-primary" {{action go}}>Suchen</button>
</div>
</div>
</script>​
JavaScript:
App = Em.Application.create({});
Em.View.create({
templateName: 'test',
go: function(evt) {
console.log('go', evt);
}
}).append();​
Ember.js IS able to insert an element at a specific position though, but this should only be necessary for an applications' main view. So you could use Ember.View#appendTo(target). But again, this should only be used in rare cases.

Related

how to add a new record UI in ember

Just when I think that I'm getting a handle on ember, this happens and I run into a brick wall.
I have
App.Router.map(function() {
this.resource('customers', function() {
this.resource('customer', {
path : ':customer_id'
});
customers route :
App.CustomersRoute = Ember.Route.extend({
model: function() {
return this.store.find('customer');
},
customer controller :
App.CustomerController = Em.ObjectController.extend({
needs: ['state'],
isEditing: false,
isNotEditing: Ember.computed.not('isEditing'),
actions : {
startEditing: function() {
this.set('isEditing',true);
this.set('validationErrors', '');
},
save: function() {
and these templates :
<script type="text/x-handlebars" data-template-name="customers">
...
{{outlet}}
...
</script>
<script type="text/x-handlebars" data-template-name="customer">
...
{{#if isEditing}}
<div class="well well-sm">
<a class="btn btn-success" {{action save}}>Save</a>
<a class="btn btn-warning" {{action cancel}}>Cancel</a>
</div>
{{else}}
<div class="well well-sm">
<a class="btn btn-primary" {{action startEditing}}>Edit</a>
<a class="btn btn-danger" {{action delete}}>Remove</a>
</div>
{{/if}}
this is all working well for me. I can select a customer, press the edit button, form inputs get enabled, press the save button and changed data is persistent back to the db
however : this is my brick wall.
How do I enable a feature to create a new record : I dont want to duplicate the edit form, just show blanks
I am assuming that I need to put "new" into the router map
this.resource('customers', function() {
this.resource('customer', {
path : ':customer_id'
});
route('new');
});
but do I create a CustomerNew controller and CustomerNew route and CustomerNew template ?
I have put an action into the customers controller
<a class="btn btn-primary btn-xs pull-right" {{action startNew}}>New</a>
do I really need to create a route & controller & template just to handle the new action ? Or can I reuse the customer/1 route/controller/template ?
thanks
To do things The Ember Way (TM) you'd want to use a new route, controller, and template. Ember does make it easy to consolidate your logic and not have to duplicate code. In templates you can do this with partial, and in your JS code you can do it with Ember.Mixin.
Here's a JSBin of the general idea : http://jsbin.com/ucanam/1056/edit
Here's the interesting parts of the templates :
<script type="text/x-handlebars" data-template-name="customers/new">
Add a new customer<br/>
{{partial "customers/form"}}
</script>
<script type="text/x-handlebars" data-template-name="customer/edit">
Edit a customer<br/>
{{partial "customers/form"}}
</script>
<script type="text/x-handlebars" data-template-name="customers/_form">
{{input value=name}}
<button {{action save}}>Save</button>
<button {{action cancel}}>Cancel</button>
</script>
And then the controllers :
App.CrudController = Ember.Mixin.create({
actions : {
save : function(){
this.get('model').save();
this.transitionToRoute('customers');
},
cancel : function(){
this.get('model').rollback();
this.transitionToRoute('customers');
}
}
});
App.CustomersNewController = Ember.ObjectController.extend(App.CrudController,{});
App.CustomerEditController = Ember.ObjectController.extend(App.CrudController,{});

Change a view property from an unrelated controller

I have the following view:
App.MessageTrayView = Bootstrap.AlertMessage.extend({
message: 'This is a message.',
});
Displayed in this template:
<script type="text/x-handlebars" data-template-name="nodes">
<article>
<form class="form-horizontal">
<fieldset>
{{view App.MessageTrayView id="message-tray-view"}}
<div id="legend" class="">
<legend class="">Nodes <span class="badge">{{controllers.nodesIndex.length}} records</span>
<div class="pull-right">
<a {{action destroyAllRecords}}><i class="icon-remove-circle"></i><a/>
{{#linkTo "nodes.new" class="btn btn-primary"}}Add Node{{/linkTo}}
</div>
</legend>
</div>
{{outlet}}
</fieldset>
</form>
</article>
</script>
And this unrelated controller:
App.NodesIndexController = Ember.ArrayController.extend({
destroyAllRecords: function () {
console.log('destroyAllRecords called');
App.MessageTrayView.set('message', 'All nodes have been deleted');
},
});
I want to change the message displayed as soon as the destroyAllRecords is triggered. This is not working (the error message in the console is telling me that I am doing something * very* wrong). How can I change the message property, so that the changes are directly visible on the page?
You can see the code live here
One quick way of doing this could be to define a property on the App namespace:
App = Ember.Application.create({
messageTrayContent: ''
});
then bind to it in your view using the suffix Binding after your property name:
App.MessageTrayView = Bootstrap.AlertMessage.extend({
messageBinding: 'App.messageTrayContent'
});
Now doing:
App.NodesIndexController = Ember.ArrayController.extend({
destroyAllRecords: function () {
console.log('destroyAllRecords called');
App.set('messageTrayContent', 'All nodes have been deleted');
},
});
should work.
Hope it helps.

In EmberJS my event triggers all the sub-views instead of just the targeted one

i'm learning EmberJS and building a comment section that allows 1 level of sub comments. I have an Ember View listing all the comments, when you click "reply" on a particular comment, it should display a textarea input for a user to write a sub-comment.
In my EmberJS code when you click "reply" it shows the textarea input for all the comments not just the specific one. Any advice would be appreciated :)
// View
App.commentsView = Em.View.create({
templateName: 'commentsTmpl',
showReply: false,
reply: function(e) {
e.view.set('showReply', true);
e.preventDefault();
}
});
App.replyCommentsView = Em.View.extend({
showReplyBinding: 'App.commentsView.showReply'
});
// Template
<script data-template-name="commentsTmpl" type="text/x-handlebars">
</h2>comment</h2>
{{#each App.commentsController}}
<div class="comment-group clearfix">
<div class="comment">
<img class="comment-pic" {{bindAttr src="userPic"}} alt="user pic">
<div class="comment-content">
{{userName}}
<span class="comment-body">{{text}}</span>
<div class="comment-actions clearfix">
<a href="#" {{action "reply"}}>Reply</a>
</div>
</div>
</div>
{{#view App.replyCommentsView}}
{{#if showReply}}
<div class="comment-reply">
<h2>sub-comment</h2>
<textarea class="txt-comment-reply" rows="2" cols="65"></textarea>
</div>
{{/if}}
{{/view}}
</div>
{{/each}}
</script>
Currently you are binding the showReply to App.commentsView which is the whole container. To be make it easy activate single comments, I'd suggest looking into a CollectionView, this way each of your comments will have their own view and you can toggle showReply on an individual comment's view.
Something like this: (Sorry, I haven't tested it)
App.commentsView = Em.View.create({
templateName: 'commentsTmpl'
});
App.CommentView = Em.View.extend({
classNames: "comment-group clearfix".w(),
showReply: false,
reply: function(e) {
e.preventDefault()
this.set("showReply", true)
}
})
// Template
<script data-template-name="commentsTmpl" type="text/x-handlebars">
</h2>comment</h2>
{{#collection contentBinding="App.commentsController" itemViewClass="App.CommentView"}}
<div class="comment">
<img class="comment-pic" {{bindAttr src="content.userPic"}} alt="user pic">
<div class="comment-content">
{{content.userName}}
<span class="comment-body">{{content.text}}</span>
<div class="comment-actions clearfix">
<a href="#" {{action "reply"}}>Reply</a>
</div>
</div>
</div>
{{#if showReply}}
<div class="comment-reply">
<h2>sub-comment</h2>
<textarea class="txt-comment-reply" rows="2" cols="65"></textarea>
</div>
{{/if}}
{{/each}}
</script>

Should Ember.CollectionView's respect layoutName?

Edit: Question rescinded. CollectionViews, as a subclass of ContentViews, do not respect layout, frustrating as that is.
CollectionViews don't seem to work with layouts. For example:
Test case: http://jsfiddle.net/73sWp/
Templates:
<script type="text/x-handlebars" data-template-name="layoutTest">
<div class="my-collection">
<h1>{{title}}</h1>
{{yield}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="layoutTest-child">
<div class="an-item">
Hi there.
</div>
</script>
Script:
var TestView = Ember.CollectionView.extend({
layoutName: "layoutTest",
title: "My Collection",
childViews: [
Ember.View.create({
templateName: 'layoutTest-child'
}),
Ember.View.create({
templateName: 'layoutTest-child'
})
]
});
$(function () {
TestView.create().appendTo(document.body);
});
Expected:
<div class="my-collection">
<h1>My Collection</h1>
<div class="an-item">
Hi there.
</div>
<div class="an-item">
Hi there.
</div>
</div>
Actual:
<div class="an-item">
Hi there.
</div>
<div class="an-item">
Hi there.
</div>
Am I missing something obvious?
Layouts are template based and CollectionViews have been intended to be used without templates.
This issue has been discussed a bit and I think the consensus is that we'd like to support using layouts with ContainerViews.
A pull request has been submitted that adds this functionality: https://github.com/emberjs/ember.js/pull/928

Ember.js - CollectionView, add an attribute to the div wrapper of the child view

I'm trying to combine two ebmer.js examples: Integrating with jQuery UI and the todos example from emberjs.com. I want to have a todo list that is sortable.
Everything went smooth until I got to a point where I wanted to serialize the sortable. For that to work, I need to be able to add an attribute to the sortable items.
this is the template:
{{#collection Todos.TodosListView}}
{{#view Todos.TodoView contentBinding="content" checkedBinding="content.isDone"}}
<label>{{content.title}}</label>
{{/view}}
{{/collection}}
Todos.TodosListView is a CollectionView, similar to the menu in the jQuery UI example. Todos.TodoView is a Checkbox.
This generates the following html:
<div class="ember-view todo-list ui-sortable" id="ember267">
<div class="ember-view" id="ember284">
<input type="checkbox" class="ember-view ember-checkbox todo" id="ember297">
<label>
<script type="text/x-placeholder" id="metamorph-1-start"></script>
something to do
<script type="text/x-placeholder" id="metamorph-1-end"></script>
</label>
</div>
</div>
What I need to be able to do is edit the <div> that wraps the <input>. Assuming the todo's id is 1, I want to add serial=todos_1. I tried to add didInsertElement to TodoView and add an attribute to the parent view, but I didn't have access to the content of the view (the todo itself).
Is this possible?
Thanks for your help.
EDIT:
I found a workaround - adding the ID to the DOM as a hidden element.
The updated template:
{{#collection Todos.TodosListView}}
{{#view Todos.TodoView contentBinding="content" checkedBinding="content.isDone" serial="content.serial"}}
<label>{{content.title}}</label>
<span style="display: none;" class="todo-id">{{content.id}}</span>
{{/view}}
{{/collection}}
Todos.TodoView.didInsertElement:
didInsertElement: function() {
var $div = this.get('parentView').$();
var id = $div.children('.todo-id').text();
$div.attr('serial', 'todos_' + id);
}
Generated html:
<div class="ember-view todo-list ui-sortable" id="ember267">
<div class="ember-view" id="ember284" serial="todos_100">
<input type="checkbox" class="ember-view ember-checkbox todo" id="ember297">
<label>
<script type="text/x-placeholder" id="metamorph-1-start"></script>
something to do
<script type="text/x-placeholder" id="metamorph-1-end"></script>
</label>
<span class="todo-id" style="display: none;">
<script type="text/x-placeholder" id="metamorph-2-start"></script>
100
<script type="text/x-placeholder" id="metamorph-2-end"></script>
</span>
</div>
</div>
I would still like to know if there's a more elegant way of achieving this.
You can create a computed property serial and add this property to the attributeBindings (documented here) of your itemViewClass of Todos.TodosListView, see http://jsfiddle.net/pangratz666/6X4QU/:
Todos.TodosListView = Ember.CollectionView.extend({
itemViewClass: Ember.View.extend({
attributeBindings: ['serial'],
serial: function() {
return 'todos_' + Ember.getPath(this, 'content.id');
}.property('content.id').cacheable()
})
});