Ember data reset record - ember.js

On my route I have a list of customers, and a form to add new ones.
I create an empty record in the setupController hook. This record is bound to the form. My idea was: on saving the record I make a (deep) copy of it, save the copy, then reset the record to empty state via http://emberjs.com/api/data/classes/DS.Model.html#method_rollbackAttributes.
Looks like it wont work, because
If the model isNew it will be removed from the store.
How should I handle this scenario? What is the best practice?
I made a JsBin to demonstrate the problem. http://jsbin.com/weqotoxiwe/3/edit?html,js,output.
Try to type something, then save it. (It should empty the record) then type again. It produce the following error in console:
Uncaught Error: Attempted to handle event didSetProperty on while in state root.deleted.saved. Called with {name: name, oldValue: sds, originalValue: undefined, value: sdsd}.
Here is the snippet:
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.ApplicationAdapter = DS.RESTAdapter;
App.Customer = DS.Model.extend({
name: DS.attr('string'),
city: DS.attr('string')
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('customer');
},
setupController: function(controller, model) {
this._super(controller, model);
var empty = this.store.createRecord('customer');
controller.set('empty', empty);
},
actions: {
save: function(record) {
/*
record.copy().then(function(copy){
copy.save();
})*/
record.rollbackAttributes();
}
}
});
/*---------------Data------------------*/
var customers = {
"customers": [{
"id": 1,
"name": "Terry Bogard",
city: 'Delaware'
}, {
"id": 2,
"name": "Joe Higashi",
city: 'Tokyo'
}, {
"id": 3,
"name": "Richard Meyer",
city: 'Albuquerque'
}, {
"id": 4,
"name": "Kim Kaphwan",
city: 'Seoul'
}, {
"id": 5,
"name": "Ryo Sakazaki ",
city: 'Tokyo'
}, {
"id": 6,
"name": "Takuma Sakazaki ",
city: 'Tokyo'
}, ]
};
$.mockjax({
url: '/customers',
dataType: 'json',
responseText: customers
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.css">
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://builds.emberjs.com/beta/ember-template-compiler.js"></script>
<script src="http://builds.emberjs.com/beta/ember.debug.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/1.6.2/jquery.mockjax.min.js"></script>
<script src="http://builds.emberjs.com/beta/ember-data.min.js"></script>
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<script type="text/x-handlebars">
<h2>Table example</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{input value=empty.name}}
<button {{action 'save' empty}}>Save</button>
{{#each model as |record|}}
<div>{{record.name}}</div>
{{/each}}
</script>
</body>
</html>
Update
In my application I handle the all saves in one central place, in application route. Usually I use http://emberjs.jsbin.com/jipani/edit?html,js,output to send the action up to the route.

I would save the record bound to the form (not a deep copy of it), and then create a new record and attach it to the form. No need to mess with deep copying or rolling back.
You should do this all on the Controller (coffeescript) -
newEmpty: (->
empty = #store.createRecord('customer')
#set('empty', empty)
).on('init')
actions:
saveEmpty: ->
#get('empty').save().then =>
#newEmpty()
Note that on('init') is needed to run newEmpty when the controller is initialized.

Related

How do I add Projects parent to Ember-CLI TodoMVC?

I am working on a todo type project using Ember-CLI. I used as a starting point the nifty todoMVC project, but built with Ember-CLI using this guide:
http://blaketv.com/2014/10/03/ember-cli-todo-mvc-tutorial-0-0-47//
My question is, how would I go about adding projects at the parent level. So we would have a master-detail type interface and in the sidebar we would have projects and you could CRUD project names, and then when you click on a project name, you see the todos in the detail pane.
I have gotten far enough defining the hasMany relationships to the models, but I cannot figure out if I need multiple {{outlets}} It is very difficult to get everything on the same page and working.
Here is my model for project:
export default DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean'),
description: DS.attr('string'),
todos: DS.hasMany('todo', {async: true})
});
and model for todos:
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
and the main Router:
Router.map(function() {
this.resource('projects', function () {
this.route('new');
this.resource('project', { path: ':id' }, function () {
this.route('todos');
});
});
});
Project Route:
export default Ember.Route.extend({
model: function(params) {
return this.store.find('project', params.id);
}
});
Index Route:
export default Ember.Route.extend({
model: function() {
return this.store.find('project');
}
});
Todos Route:
export default Ember.Route.extend({
model: function() {
return this.modelFor('todos');
}
});
So for project.hbs this is where it gets tricky. I create the sidebar with bootsrap and then this outlet shows the todos....
<div class="projects-column col-md-3">
<div id="inbox-header"><span class="glyphicon glyphicon-inbox"></span> Inbox <span class="badge">42</span></div>
<div id="projects-header"><span class="glyphicon glyphicon-list-alt"></span> Projects</div>
<div id="forecast-header"><span class="glyphicon glyphicon-calendar"></span> Forecast</div>
<div id="log-header"><span class="glyphicon glyphicon-book"></span> Sessions Log</div>
</div>
<div>{{outlet}}</div>
Index.hbs:
<ul>
{{#each model}}
<li>{{link-to title "project.todos" this}}</li>
{{/each}}
So this above when you click on the project title link, it shows the associated todos.... but it renders in the left pane... it's probably just something about the CSS layout...but something tells me there is a very Ember-ish way to do this that I am missing.
Then in /project/todo.hbs we have the iteration
{{#each model.todos}}
<li>{{title}}</li>
{{/each}}
I haven't even really addressed making the CRUD for controllers or anything. Most likely this above is laughable and there is a much more elegant way to approach this...
Basically I want a projects parent route, that I do CRUD with... and then when you render a list of project links in the sidebard and click on one, you get in the right pane the rendered ToDoMVC working app.
Of course this is just a starting point for my application. Most likely if someone comes up with a elegant way to do this, we can turn it into an open source project on github for others to learn from.
I think a bunch of burgeoning ember developers are having a hard time with this type of thing because of the multiple ways to do it (outlets, partials, render, render into other templates, views, components, etc)
Don't really know how to get any further.
Not sure if you're still stuck, but I'd try it without bootstrap as a side bar, and just put an {{#each}} [full code here]
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_BINDINGS: true,
LOG_VIEW_LOOKUPS: true,
LOG_ACTIVE_GENERATION: true,
debugMode: true
});
App.Router.map(function() {
this.resource('projects', {
path: '/'
});
this.resource('project', {
path: '/projects/:project_id'
}, function() {
// URL = '/projects/:id/todos'
this.resource('project.todos', {
path: '/todos'
}, function() {
// URL = '/project/:id/todos/new'
this.route("new");
});
});
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();
//App.Store = DS.Store.extend({adapter : DS.FixtureAdapter});
App.ProjectsRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('project');
},
actions: {
addproject: function() {
var newproject = this.store.createRecord('project', {
name: "My New project"
});
},
removeproject: function(project) {
console.log(project);
console.log(this.controller.get("model"));
this.controller.get("model").removeObject(project);
}
}
});
App.ProjectRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('project', params.project_id).then(function(project) {
return project;
});
}
});
App.ProjectsIndexRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('project');
}
});
App.ProjectTodosRoute = Em.Route.extend({
model: function(params) {
return this.modelFor('project');
},
actions: {
addtodo: function() {
this.transitionTo("project.todos.new");
}
}
});
App.projecttodosNewRoute = Em.Route.extend({
model: function(params) {
parentprojectId = this.modelFor('project').get("id");
newtodo = this.store.createRecord('todo', {
id: "5",
name: "John Doe",
//project : parentprojectId
project: this.store.getById('project', parentprojectId)
});
console.log("new todo = " + newtodo);
return newtodo;
},
actions: {
save: function() {
//console.log(this.controllerFor('projecttodosNew').content);
//console.log('save of newtodo = '+this.controllerFor('projecttodosNew').get('newtodo'));
console.log('newtodo~ ' + newtodo.get('name') + ', ' +
newtodo.id + ', ' + newtodo);
newtodo.save()
//this.controllerFor('projecttodosNew').content.save()
.then(function() {
this.transitionTo("project.todos");
});
},
cancel: function() {
console.log("rollback for " + this.get("controller.model"));
this.get("controller.model").rollback();
this.set("controller.model", null);
this.transitionTo("project.todos");
}
}
});
//App.projecttodosNewController = Ember.ObjectController
// .extend({
// needs : [ 'application', 'project'],
// newtodo : null
// });
App.Project = DS.Model.extend({
name: DS.attr(),
todos: DS.hasMany('todo', {
async: true
})
});
App.Project.FIXTURES = Em.A([{
id: 1,
name: 'Monday',
todos: ['2']
}, {
id: 2,
name: 'Tuesday',
todos: ['1', '2']
}, {
id: 3,
name: 'Wednesday',
todos: ['4']
}]);
App.Todo = DS.Model.extend({
name: DS.attr('string'),
//project : DS.belongsTo('project')
});
App.Todo.FIXTURES = [{
id: 1,
name: 'shop',
project: 1
}, {
id: 2,
name: 'sell things',
project: 2
}, {
id: 4,
name: 'dance',
project: 3
}];
/* Put your CSS here */
html,
body {
margin: 20px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/2.1.0/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.6.1/ember.js"></script>
<script src="http://builds.emberjs.com/tags/v1.0.0-beta.10/ember-data.prod.js"></script>
</head>
<body>
<script type="text/x-handlebars">
<h2>Welcome to "The Project/TODO Demo"</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="projects">
<ul>
{{#each item in model}}
<li>{{#link-to 'project.todos' item }}{{item.name}}, List of todos{{/link-to}} ,
<button {{action "removeproject" item}}>X</button>
</li>
{{/each}}
</ul>
<button type="button" {{action "addproject" this.id}}>Add a project</button>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="project/index">
<br><b>Name of project:</b> {{name}}
</script>
<script type="text/x-handlebars" data-template-name="project">
{{#link-to "projects"}}Home{{/link-to}} {{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="project/todos/index">
<h1></h1>
<b>todos</b>
<br>
<ul>
{{#each todo in todos}}
<li>{{todo.name}}</li>
{{/each}}
</ul>
<button type="button" {{action "addtodo"}}>Add a todo</button>
<br>{{#link-to 'project' this}}project details page{{/link-to}} {{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="project/todos/new">
<h1></h1>
<b>New todos</b>
<br>
<ul>
<li>Name: {{input type='text' value=model.name}}</li>
<li>todo Id: {{input type='text' value=id}}</li>
<li>Parent project Id: {{project}}</li>
</ul>
<button type="button" {{action "save"}}>Save todo</button>
<button type="button" {{action "cancel"}}>cancel</button>
<br>{{outlet}}
</script>
</body>
</html>
on the top of your homepage / index. After you get that working you can start playing with the layout with bootstrap, and getting it looking cool.
similar to links doc. Hope that gets you passed your hurdle.

EmberJS RESTAdapter not populating model using ember-data

I am trying to use the ember-data to build a model from a my own REST service. I have formatted my data according to how I understand the data should be returned from the service, but still stuck.
The issue is that I get no results showing in my view after initial page load. I dont think the model is being populated correctly.
What am I missing?
App = Ember.Application.create();
App.Account = DS.Model.extend({
first: DS.attr( 'string' ),
last: DS.attr( 'string' )
});
App.AccountAdapter = DS.RESTAdapter.extend({
namespace: 'api',
host: 'http://127.0.0.1:3000'
});
App.Router.map(function() {
this.route('home');
});
App.HomeRoute = Ember.Route.extend({
model: function() {
return this.store.find( 'account' );
}
});
App.HomeController = Ember.Controller.extend({
controllerTest : true
});
My data looks like the following:
{
"accounts": {
"id": 1,
"first": "John",
"last": "Doe"
}
}
from url:
http://127.0.0.1:3000/api/accounts
My view template is:
<script type="text/x-handlebars" data-template-name="home">
Home Template {{controllerTest}}
{{#each item in model}}
<br />
{{item.first}}
{{item.last}}
{{/each}}
</script>
Thanks.
I think your JSON format is slightly incorrect. It is my understanding you return a list of accounts, even if there's only one. Try this:
{
"accounts": [
{
"id": 1,
"first": "John",
"last": "Doe"
}
]
}
Try
{{#each }}
<br />
{{first}}
{{last}}
{{/each}}
or
{{log this}}
or use Ember-inspector to see what data do you have and whats going on there.

Models won't load from store

For the application I am developing Ember.js + Ember Data seems like a good solution. However I can not even get a simple version using both libraries to work. The problem is that the data provided by my JSON file is not correctly loaded or shown.
My app.js looks like. I run all libraries on the edge.
var App = Em.Application.create({});
App.store = DS.Store.create({
revision: 6,
adapter: DS.RESTAdapter.create({
bulkCommit: false
})
});
App.Item = DS.Model.extend({
pluginName: DS.attr('string')
});
App.regionController = Em.ArrayController.create({
content: App.store.findAll(App.Item)
});
I have one template that looks like:
<script type="text/x-handlebars">
<ul>
{{#each regionController}}
<li>{{item}}</li>
{{/each}}
</ul>
</script>
The request to the json file is made correctly (I see the request pop up in Firebug) and has the following contents:
{
items: [{
"id": "3",
"pluginName": "text"
},
{
"id": "3",
"pluginName": "split"
}]
}
Can anyone spot what I am doing wrong?
Your template should probably look like this:
<script type="text/x-handlebars">
<ul>
{{#each item in regionController}}
<li>{{item.pluginName}}</li>
{{/each}}
</ul>
</script>
Let me know if that works for you.

emberjs ArrayController doesn't work in template

I am trying out ember.js and I am struggling with rendering an array of items to the template:
index.html
<script type="text/x-handlebars">
{{App.test}}
</script>
<script type="text/x-handlebars">
{{#each App.eventsController}}
<p>{{title}}</p>
{{/each}}
</script>
<!-- le javascript -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/libs/jquery-1.7.2.min.js"><\/script>')</script>
<script src="js/libs/handlebars-1.0.0.beta.6.js"></script>
<script src="js/libs/ember-1.0.pre.min.js"></script>
<script src="js/app.js"></script>
</body>
app.js
var App = Em.Application.create({
test : 'does it work?...'
});
App.Event = Ember.Object.extend({
title: '',
body: ''
});
App.eventsController = Ember.ArrayController.create({
events: [],
init: function() {
self = this;
self.pushObject(App.Event.create({
title: "Event 1",
body: "Content"
}));
}
});
The first binding (App.test) does work fine. Only my second call does nothing but putting an empty handlebar-script-tag into my DOM.
So what am I missing here?
If you override init you need to make sure to call this._super so the controller can finish its setup process. Once you do this your code should be working as expected.
init: function() {
this._super();
this.get('content').pushObject(App.Event.create({
title: "Event 1",
body: "Content"
}));
}
I've created a jsfiddle for you so you can see it working: http://jsfiddle.net/qKXJt/188/
You'd want to put your eventsController's data inside the 'content' property:
App.eventsController = Ember.ArrayController.create({
content: [],
init: function() {
this.get('content').pushObject(App.Event.create({
title: "Event 1",
body: "Content"
}));
}
});
Your template {{#each App.eventsController}} should automatically pick up the changes and display your title.

Ember.js - Code is working with 0.9.5 but not 1.0.pre

I try to figure out why the following fiddle doesn't work with ember.js 1.0.pre while 0.9.5 works.
Version 0.9.5 (working)
http://jsfiddle.net/tPfNQ/1/
Version 1.0.pre (not working)
http://jsfiddle.net/hSzrZ/1/
I know that handlebars.js is not included in the latest build of ember.js and i have to include it by my own.
Here is the code i'm using:
<script type="text/x-handlebars">
{{#view Ember.Button target="Welcome.booksController" action="loadBooks"}}
Load Books
{{/view}}
{{#collection contentBinding="Welcome.booksController"}}
<i>Genre: {{content.genre}}</i>
{{/collection}}
</script>
Welcome = Ember.Application.create();
Welcome.Book = Ember.Object.extend({
title: '',
author: '',
genre: ''
});
var data = [ { "title": "Ready Player One", "author": "Ernest Cline", "genre": "Science Fiction" }, { "title": "Starship Troopers", "author": "Robert Heinlein", "genre": "Science Fiction" }, { "title": "Delivering Happiness", "author": "Tony Hsieh", "genre": "Business" } ];
Welcome.booksController = Ember.ArrayController.create({
content: [],
loadBooks: function(){
var self = this;
data.forEach(function(item){
self.pushObject(Welcome.Book.create(item));
});
}
});​
Source is: http://www.andymatthews.net/read/2012/03/07/Getting-Started-With-EmberJS
This is the most common issue people have with upgrading to 1.0.
The default view context has now changed to be the context of the view rather than the view itself.
So to access a value from the view's content you need to specify it via view.content.xxx.
{{#collection contentBinding="Welcome.booksController"}}
<i>Genre: {{view.content.genre}}</i>
{{/collection}}
In this particular case you could also use the #each helper if you wanted.
{{#each Welcome.booksController}}
<i>Genre: {{genre}}</i>
{{/each}}
I think there is talk of changing the #collection helper to work similarly.