How can I trigger a commit/save? - ember.js

Users can be manipulated through form fields. But I can't figure out how I can save (commit) them. When ever I change the lastName of a User and click the save button I'll get the following error:
Uncaught Error: Attempted to handle event `save` on <App.User:ember309:1> while in state rootState.loaded.updated.uncommitted. Called with undefined
What do I have to change to save (commit) a record by clicking on the save button?
index.html
<script type="text/x-handlebars" data-template-name="users">
<table class='table'>
{{#each model}}
<tr>
<td>{{view Ember.TextField valueBinding='lastName'}}</td>
{{#with this as model}}
<td><button {{bindAttr class=":btn isDirty:btn-primary:disabled"}} {{action "save" target="model"}}>save</button></td>
{{/with}}
</tr>
{{/each}}
</table>
</script>
app.js
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
})
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: ':user_id' })
})
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.UserController = Ember.ObjectController.extend({
save: function() {
this.commit();
}
})
App.User = DS.Model.extend({
lastName: DS.attr('string')
})
App.User.FIXTURES = [{
id: 1,
lastName: "Smith"
}]

You can change your code like this
<script type="text/x-handlebars" data-template-name="users">
<table class='table'>
{{#each model in controller}}
<tr>
<td>{{view Ember.TextField valueBinding='lastName'}}</td>
<td><button {{bindAttr class=":btn isDirty:btn-primary:disabled"}} {{action "save"}}>save</button></td>
</tr>
{{/each}}
</table>
</script>
that uses each model in controller and not #with this as model
And you define then an itemController
App.UsersController = Ember.ArrayController.extend({
itemController: 'user'
});
This will allow you to have the save method of the UserController called.
Finally, make sure you save your record:
App.UserController = Ember.ObjectController.extend({
save: function() {
this.get('content.transaction').commit();
}
})

Related

How show a filtered version of my model?

My Ember app automatically show a list of tasks. The model are populated like this:
model: function(){
return this.store.find('task');
}
My question is, how i can show a filtered version of this model in view?
If i create a route to this? tasks/completed?
Updating:
I created a route:
App.Router.map(function(){
this.resource('tasks', function(){
this.resource('task', { path:'/:task_id' }, function(){
this.route('edit');
});
this.route('create');
this.route('completed');
});
});
Then i call:
App.TasksCompletedRoute = Em.Route.extend({
// What i have to put here to show only completed tasks?
});
This is my project: https://github.com/fernandoperigolo/ember-crud-todo
You can create a filteredContent property, and set with all the tasks in the TasksRoute, and with just the completed tasks in TasksCompleted. Like the following:
Routes:
App.TasksRoute = Em.Route.extend({
model: function(){
return this.store.find('task');
},
setupController: function(controller, tasks) {
controller.set('filteredContent', tasks);
},
actions:{
check:function(task){
task.set('done', true);
task.save();
},
uncheck:function(task){
task.set('done', false);
task.save();
}
}
});
App.TasksCompletedRoute = Em.Route.extend({
setupController: function() {
this.controllerFor('tasks').set('filteredContent', this.modelFor('tasks').filterBy('done', true));
}
});
So in your tasks template use the filteredContent instead of controller:
<script type="text/x-handlebars" id="tasks">
{{#link-to "tasks.create"}} Add task {{/link-to}}
<h2>Task List:</h2>
<ul>
{{#each task in filteredContent}}
<li>
{{#if task.done}}
<button {{action "uncheck" task}}>Uncheck</button>
{{else}}
<button {{action "check" task}}>Check</button>
{{/if}}
{{#link-to "task" task}}{{task.title}}{{/link-to}}
</li>
{{else}}
<li>no tasks… :(</li>
{{/each}}
</ul>
{{outlet}}
</script>

Assertion failed: Your server returned a hash with the key category but you have no mapping for it

Note really sure what to do. I'm trying to use a slug instead of ID but when I try to go directly to a link (/categories/source), I get the error. I've tried adding mappings in the RESTadapter but it hasn't worked. I ha
I've a feeling that my problem is in my CategoryRoute/model code but I can't see to get the right combo. Any help is appreciated.
To be honest, I don't know why it has to make two REST calls when it's already got the model info available. Any help is appreciated, thanks!
Json responses
{"categories":[{"id":1,"name":"Misc","slug":"misc"},{"id":2,"name":"Technology","slug":"technology"},{"id":3,"name":"Ecommerce","slug":"ecommerce"},{"id":4,"name":"Visitor","slug":"visitor"},{"id":5,"name":"Content","slug":"content"},{"id":6,"name":"evars","slug":"evars"},{"id":7,"name":"Marketing","slug":"marketing"},{"id":8,"name":"Props","slug":"props"},{"id":9,"name":"Source","slug":"source"}]}
{"category":{"id":2,"name":"Technology","slug":"technology"}}
Html file
<script type="text/x-handlebars">
<div class="container">
<div class="navbar navbar-inverse">
<a class="navbar-brand" href="#">LayerSpark</a>
<ul class="nav navbar-nav">
<li>{{#linkTo 'categories'}}Categories{{/linkTo}}</li>
<li>{{#linkTo 'help'}}Help{{/linkTo}}</li>
<li>{{#linkTo 'account'}}Account{{/linkTo}}</li>
</ul>
</div>
</div>
<div class="container">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="categories">
<div {{bindAttr class=":row"}}>
<div {{bindAttr class=":col-lg-4"}}>
<ul>
{{#each model}}
<li>{{#linkTo 'category' this}}{{name}}{{/linkTo}}</li>
{{/each}}
</ul>
</div>
<div {{bindAttr class=":col-lg-8"}}>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" id="category">
Post:<br>
My name is :: {{ name }}<br>
My id is :: {{id}}<br>
My slug is :: {{slug}}<br>
</script>
<script type="text/x-handlebars" id="categories/index">
<p class="text-warning">Please select a Category</p>
</script>
App.js
App = Ember.Application.create({
LOG_TRANSITIONS:true
});
var attr = DS.attr;
App.Category = DS.Model.extend({
name: attr('string'),
slug: attr('string')
});
DS.RESTAdapter.configure("plurals", {
category: "categories"
});
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.create({
url: '/api',
mappings:{
category:'App.Category'
}
})
});
App.Router.map(function() {
this.resource('about');
this.resource('categories', function() {
this.resource('category',{path:':category_slug'});
});
this.resource('help');
this.resource('account');
});
App.CategoriesRoute = Ember.Route.extend({
model: function() {
return App.Category.find();
}
});
App.CategoryRoute = Ember.Route.extend({
model: function(route,model) {
console.log(route);window.mod = model;
return App.Category.find({slug:route.category_slug});
},
serialize: function(model) {
return {
category_slug: model.get('slug')
};
}
});
Ember.Handlebars.registerBoundHelper('markdown', function(input) {
return new Handlebars.SafeString(showdown.makeHtml(input));
});
Ember.Handlebars.registerBoundHelper('date', function(date) {
return moment(date).fromNow();
});
I used the following router setup so that I could have a resource of Post corresponding to a route of /posts/:id. Also gives comments belonging to posts.
this.resource('posts', function() {
this.route('new');
});
this.resource('post', { path: '/posts/:post_id' }, function() {
this.resource('comments', function() {
this.route('new');
this.route('create');
});
this.route('comment', { path: 'comments/:comment_id'});
});
I had to define the categories resource separately and then explicitly state that post was under the /posts route in the definition of the resource. Maybe you need a router like:
this.resource('categories', function() {});
this.resource('category',{path:'/categories/:category_slug'});

Hide a link during users.new

I have a users template which is a table of users. Under that table is a link to create a new user. For better UX I'd like to disable it by using class="disabled" or hide that link during the create of a new user. What is the best way to do that?
index.html
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span7'>
<table class='table table-striped'>
<tbody>
{{#each model itemController="user"}}
<tr {{bindAttr class="isDirty:warning"}}>
<td>{{lastName}}</td>
</tr>
{{/each}}
</tbody>
</table>
<p>
{{#linkTo 'users.new' classNames="btn btn-small"}}Create a new user{{/linkTo}}
</p>
</div>
<div class='span5'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="users/new">
<p><strong>Last name</strong><br>{{view Ember.TextField valueBinding=lastName}}</p>
<p>
{{#if isDirty}}
<button {{action 'save' this}} class="btn btn-small">Save</button>
{{else}}
<button class="btn btn-small disabled">Save</button>
{{/if}}
</p>
</script>
app.js
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.UsersNewRoute = Ember.Route.extend({
model: function() {
return App.User.createRecord();
},
renderTemplate: function() {
this.render({ into: 'users' });
}
});
App.UsersNewController = Ember.ObjectController.extend({
save: function(model) {
model.get('transaction').commit();
App.Router.router.transitionTo('users.index')
}
});
I think one possible solution would be to add a property in the usersController, something like 'isCreating', which you can set to true in the activate hook of the UsersNewRoute, and reset to false in the deactivate. This would be something like:
App.UsersNewRoute = Ember.Route.extend({
activate: function() {
this.controllerFor('users').set('isCreating', true);
},
deactivate: function() {
this.controllerFor('users').set('isCreating', false);
},
model: function() {
return App.User.createRecord();
},
renderTemplate: function() {
this.render({ into: 'users' });
}
});
Obviously you will use this property in the template and bind the class to hide the button.

Mark the current detail entry in a master list

I have a list of users which are displayed in a master view on the left side (Twitter Bootstrap CSS). Details of each user can be shown by clicking the show button. They will be displayed on the right side (detail).
How can I remove the show button for the currently displayed user? e.g. #/users/1 shouldn't render the show button for the first user.
index.html
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span4'>
<table class='table table-striped'>
{{#each model}}
<tr>
<td>{{lastName}}</td>
<td>{{#linkTo 'user' this}}<button class="btn" type="button">show</button>{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div class='span8'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user">
<h1>{{firstName}} {{lastName}}</h1>
</script>
app.js
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
})
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: ':user_id' })
})
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
})
App.User.FIXTURES = [{
id: 1,
firstName: "Bill",
lastName: "Clinton"
}, {
id: 2,
firstName: "Barack",
lastName: "Obama"
}]
Ember provides some support for doing what you want. By default it sets the "active" css class on the selected element. You can find more information about that here: http://emberjs.com/api/classes/Ember.LinkView.html (note that the {{#linkTo}} is just a helper based on the LinkView).
The simplest way to override this behavior, since instead of "active" you want to hide the button, would be to make use of the hide class that comes with Twitter Bootstrap. So your users template would look like:
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span4'>
<table class='table table-striped'>
{{#each model}}
<tr>
<td>{{lastName}}</td>
<td>{{#linkTo 'user' this activeClass="hide"}}<button class="btn" type="button">show</button>{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div class='span8'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user">
<h1>{{firstName}} {{lastName}}</h1>
</script>

Applying JQuery effect when ember-data has finished loading and element is rendered

I have a list of items that I want to make draggable. I am using ember-data to get the items from my API and then render them in a view with an ArrayController. I can successfully load the items and render them but I don't know where or when to put the JQuery draggable function.
I have tried using didInsertElement on my view but this is triggered when the view is rendered and not when the items are loaded. I have also tried to put an observer on my ArratController to run the code when the array length changes (i.e when an element is added to the array) None of these things worked.
Any ideas?
My JS code:
var REVISION = 9;
// Application namespace
var App = Ember.Application.create({
ApplicationView: Ember.View.extend({
templateName: 'application',
classNames: ['application-view']
}),
ApplicationController: Ember.Controller.extend(),
RewardsView: Em.View.extend({
templateName: 'rewards',
click: function(event) {
console.log(event);
//window.location.href = event
},
didInsertElement: function() {
this.$(".draggable").draggable();
}
}),
RewardsController: Em.ArrayController.extend({
rewardAdded: function() {
$(".draggable").draggable({
cursor: 'move', // sets the cursor apperance
revert: 'invalid', // makes the item to return if it isn't placed into droppable
revertDuration: 900, // duration while the item returns to its place
});
}.observes('length')
}),
ready: function(){
console.log("Created App namespace");
},
Router: Ember.Router.extend({
goToRewards: Ember.Route.transitionTo('root.rewards'),
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
}),
rewards: Ember.Route.extend({
route: '/rewards',
enter: function ( router ){
console.log("The rewards sub-state was entered.");
},
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('content','rewards', App.store.findAll(App.Rewards));
}
}),
})
})
});
App.Rewards = DS.Model.extend({
provider: DS.attr('string'),
name: DS.attr('string'),
description: DS.attr('string'),
discount: DS.attr('string'),
img: DS.attr('string'),
video: DS.attr('string'),
price: DS.attr('string'),
available_quantity: DS.attr('string'),
didLoad: function() {
console.log('model loaded', this);
}
});
App.store = DS.Store.create({
revision: REVISION,
adapter: DS.DjangoTastypieAdapter.extend({
serverDomain: "http://example.com",
namespace: "api/v1"
}),
});
// Start!
App.initialize();
My handlebars template:
{% handlebars "rewards" %}
<div class="row-fluid">
<div class="span6">
<div class="box paint color_7">
<div class="title">
<div class="row-fluid">
<h4> Available Rewards </h4>
</div>
</div>
<!-- End .title -->
<div class="content">
<div class="accordion" id="accordion2">
{{#each reward in controller}}
<div class="draggable accordion-group">
{{#with reward}}
{{#if isLoaded}}
<div class="accordion-heading">
<a class="accordion-toggle collapsed" data-toggle="collapse" data-parent="#accordion2" {{bindAttr href="reward.id"}}> {{name}} {{id}} </a>
</div>
<div {{bindAttr id="reward.id"}} class="accordion-body collapse" style="height: 0px; ">
<div class="accordion-inner"> {{description}} </div>
</div>
{{else}}
Loading...
{{/if}}
{{/with}}
</div>
{{/each}}
</div>
</div>
<!-- End .content -->
</div>
<!-- End .box -->
</div>
{% endhandlebars%}
use findQuery instead of findAll
router.get('applicationController').connectOutlet('content','rewards',App.store.findQuery(App.Rewards));
You get the property isLoaded for the content, now you can add observer on isLoaded property to run your required functionality as follows
startDraggableFunctionality: function(){
if(this.get('content.isLoaded'){
/*Your code goes here...*/
}
}.observes('content.isLoaded')
After Rendering + Data loaded
Inside the view add the following method as follows
View
//This method will be executed when the view has finished rendering
afterRender: function(){
this.get('controller').set("viewRendered", true);
}
Controller
viewRendered: false, //set it to false initially
startDraggableFunctionality: function(){
if(this.get('content.isLoaded') && this.get('viewRendered')){
/*Your code goes here...*/
}
}.observes('content.isLoaded', 'viewRendered')
This way if the view renders before loading content, isLoaded make sures that function will be executed only after data has been loaded & vice-versa