Ember.js load page by URL - ember.js

Copied example from emberjs official site called Mailbox (under ROUTING).
App.Mailbox = Em.Object.extend();
App.Mailbox.reopenClass({
find: function(id) {
if (id) {
return App.FIXTURES.findBy('id', id);
} else {
return App.FIXTURES;
}
}
});
App.Router.map(function() {
this.resource('mailbox', { path: '/:mailbox_id' }, function() {
this.resource('mail', { path: '/:message_id' });
});
});
App.ApplicationRoute = Em.Route.extend({
model: function() {
return App.Mailbox.find();
}
});
App.MailRoute = Em.Route.extend({
model: function(params) {
return this.modelFor('mailbox').messages.findBy('id', params.message_id);
}
});
fixtures:
App.FIXTURES = [
{
name: "Inbox",
id: "inbox",
messages: [
{
id: 1,
subject: "Welcome to Ember",
from: "tomster#emberjs.com",
to: "user#example.com",
date: new Date(),
body: "Welcome to Ember. We hope you enjoy your stay"
}, {
id: 2,
subject: "Great Ember Resources",
from: "tomster#emberjs.com",
to: "user#example.com",
date: new Date(),
body: "Have you seen embercasts.com? How about emberaddons.com?"
}]}, {
name: "Spam",
id: "spam",
messages: [
{
id: 3,
subject: "You have one the Nigerian lottery!!!111ONEONE",
from: "419#thereallotteryhonest.ng",
to: "user#example.com",
date: new Date(),
body: "You have ONE the lottery! You only have to send us a small amount of monies to claim your prize"
}]}, {
name: "Sent Mail",
id: "sent-mail",
messages: [
{
id: 4,
subject: "Should I use Ember",
from: "user#example.com",
to: "tomster#emberjs.com",
date: new Date(),
body: "Ember looks pretty good, should I use it?"
}]}];
and html:
<script type="text/x-handlebars">
<div class="url">URL: {{target.url}}</div>
<aside>
<ul>
<li><h2>Mailboxes</h2></li>
{{#each}}
<li>
{{#link-to "mailbox" this currentWhen="mailbox"}}
<span class="count">
{{messages.length}}
</span>
{{name}}
{{/link-to}}
</li>
{{/each}}
</ul>
</aside>
<section class="main">
{{outlet}}
</section>
</script>
<script type="text/x-handlebars" id="index">
<div class="index">
<h1>TomsterMail</h1>
<span class="tomster"></span>
</div>
</script>
<script type="text/x-handlebars" id="index">
<div class="mail">
<dl>
<dt>From</dt>
<dd>{{from}}</dd>
<dt>To</dt>
<dd>{{to}}</dd>
<dt>Date</dt>
<dd>{{date date}}</dd>
</dl>
<h4>{{subject}}</h4>
<p>{{body}}</p>
</div>
</script>
<script type="text/x-handlebars" id="mailbox">
<table>
<tr>
<th>Date</th>
<th>Subject</th>
<th>From</th>
<th>To</th>
</tr>
{{#each messages}}
{{#link-to "mail" this tagName=tr}}
<td>{{date date}}</td>
<td>{{view.isActive}}{{subject}}</td>
<td>{{from}}</td>
<td>{{to}}</td>
{{/link-to}}
{{/each}}
</table>
{{outlet}}
</script>
<script type="text/x-handlebars" id="mailbox/index">
<div class="mailbox-index">
Select an email
</div>
</script>
When I go to url /index.html#/inbox/1 following exception thrown:
Assertion failed: Cannot call get with 'id' on an undefined object.
I have web application which needs same functionality, but with server requests (not from FIXTURES)

Try changing your code for retrieving the model:
return this.modelFor('mailbox').get('messages').findBy('id', params.message_id);
In Ember, you need to use get() to retrieve properties and associations
\

If i replace this.modelFor('mailbox').get('messages').findBy('id', params.message_id); with ajax call then everything works as expected
App.MailRoute = Em.Route.extend({
model: function(params) {
var ref = this;
return $.get("/inbox/"+params.message_id).then(function(data){
return data.findBy('id', params.message_id);
});
}
});
});

Related

Ember.js: programmatically load hasmany relationship

Disclamer: I started using Ember.js few days ago
Suppose having these 2 models:
App.Question = DS.Model.extend({
text: DS.attr('string'),
answers: DS.hasMany('answer', {async: true})
});
App.Answer = DS.Model.extend({
question: DS.belongsTo('question'),
text: DS.attr('string')
});
For some reasons I got this JSON (without "answers" list)
{
...
"questions": [
{"id": 1, "text": "Foo?"}, ...
]
}
In the template I want to load and show answers only if explicitly needed
{{#each questions itemController="question"}}
<div class="answer-wrapper">
{{text}}
<button {{action "loadAnswers"}}>Load answers</button>
<ul>
{{#each answers}}
<li>{{text}}</li>
{{/each}}
</ul>
</div>
{{/each}}
How can I do this in the controller's loadAnswer action?
App.QuestionController = Ember.ObjectController.extend({
...
actions: {
loadAnswers: function() {
// programatically load answers
}
}
});
Workaround: I can do this changing the attribute in the template
{{#each loadedAnswers}}
<li>{{text}}</li>
{{/each}}
and defining correctly the action
App.QuestionController = Ember.ObjectController.extend({
...
actions: {
loadAnswers: function() {
// or loaded with ajax...
this.set("loadedAnswers", [
{id: 1, text: "foo"},
{id: 2, text: "bar"}
]);
}
}
});
You can wrap that portion of the template with an if statement. The async relationship won't be fetched unless it's requested.
Controller
App.QuestionController = Ember.ObjectController.extend({
showAnswers: false,
...
actions: {
loadAnswers: function() {
this.set('showAnswers', true);
}
}
});
Template
{{#if showAnswers}}
<ul>
{{#each answers}}
<li>{{text}}</li>
{{/each}}
</ul>
{{/if}}

Why is the URL not updated when I click the link for that route

I am using EmberJs version 1.4.
When I click on one of the links I would expect the URL to include the id of the selected widget but nothing appears and when I look at the params parameter in the route model hook it has no properties and I would expect the id to be one of its properties so could someone help me to understand what am I missing?
In other words I would expect the URL to become awesome.html#/widgets/5 but it always is awesome.html#/widgets
Thank you!
This is my ember code:
window.Awesome = Ember.Application.create();
Awesome.Router.map(function() {
this.resource("awesome", {path: "/"}, function(){
this.route('login');
});
this.resource("widgets", function () {
this.resource('widget', { path: '/:widgetId' }, function () {
this.route('general', { path: 'info' });
this.route('configuration');
this.route('operations');
})
});
});
Awesome.WidgetsRoute = Awesome.AuthenticationRoute.extend({
model: function(){
//TODO: Call a service to get the model.
return { widgets: [{ widgetId: 1, widgetName: "Great Widget" }, { widgetId: 2, widgetName: "Fantastic Widget" }, { widgetId: 3, widgetName: "Brutal Widget" }] };
}
});
Awesome.WidgetIndexRoute = Awesome.AuthenticationRoute.extend({
model: function (params) {
var receivedWidgetId = params.widgetId;
return { widgetName: "Hardcoded Widget", widgetId: receivedWidgetId };
}
});
These are the templates:
<script type="text/x-handlebars" data-template-name="widgets">
<section class="span3 left-section">
<div class="btn-group-vertical btn-group-justified registration-actions-menu">
<button id="createNewWidget" class="btn btn-link">Create New Widget</button>
<button id="joinWidgetTeam" class="btn btn-link">Join Widget Team</button>
</div>
<div class="registered-widgets-menu">
<div class="btn-group-vertical">
{{#each widget in widgets}}
{{#link-to 'widget' widget class="btn btn-link"}}{{widget.widgetName}}{{/link-to}}
{{/each}}
</div>
</div>
</section>
<section class="span8">
{{outlet}}
</section>
</script>
<script type="text/x-handlebars" data-template-name="widget">
<div id="widgetOptions">
<!-- TODO: Change the anchors for handlebars link-to helpers. -->
<h1>{{widgetName}}</h1> <h5>{{widgetId}}</h5>
<ul id="widgetNavigation">
<li>Widget Info</li>
<li>Widget Configuration</li>
<li>Widget Operations</li>
</ul>
</div>
<div id="widgetContent">
<!-- TODO: Design some awesome widget content. -->
Some awesome widget content
</div>
</script>
I also have an authentication route from which the other routes inherit. Even though I don't believe it has something to do with the issue I'll include just in case I am wrong.
Awesome.AuthenticationRoute = Ember.Route.extend({
beforeModel: function(transition){
if(!Awesome.get('loggedUser')){
this.redirectToLogin(transition);
}
},
redirectToLogin: function(transition) {
var loginController = this.controllerFor('awesome.login');
loginController.set('attemptedTransition', transition);
this.transitionTo('awesome.login');
}
});
It looks like it's totally working to me, when you click on one of the widgets
http://emberjs.jsbin.com/mohex/1
Additionally it looks like you're mixing up the WidgetIndexRoute and WidgetRoute. The widget resource should be displayed like this (though this is unrelated to the issue you're describing)
Awesome.WidgetRoute = Awesome.AuthenticationRoute.extend({
model: function (params) {
var receivedWidgetId = params.widgetId;
return { widgetName: "Hardcoded Widget", widgetId: receivedWidgetId };
}
});

Emberjs: How to refresh parent template after model create

I'm building an app that has the following code:
Routes:
App.Router.map(function() {
this.resource('gradebooks', function() {
this.resource('gradebook', { path: ':gradebook_id' });
});
});
App.IndexRoute = Em.Route.extend({
redirect: function() {
this.transitionTo('gradebooks');
}
})
App.GradebooksRoute = Em.Route.extend({
setupController: function(controller, model) {
Em.$.getJSON('/data/gradebooks/get', function(data) {
controller.set('model', data);
});
}
});
App.GradebookRoute = Em.Route.extend({
model: function(params) {
var id = params.gradebook_id;
return Em.$.getJSON('/data/gradebooks/get/' + id);
}
})
Controllers:
App.GradebooksController = Em.ArrayController.extend({
isActive: false,
actions: {
createGradebook: function(newTitle) {
var self = this;
if(newTitle.trim()) {
Em.$.get('/data/gradebooks/add/' + newTitle, {}, function(json) {
Em.$('#create-gradebook-modal').modal('hide').find('input').val('');
self.transitionToRoute('gradebook', json.id);
}, 'json');
}
}
}
});
Templates (gradebooks.hbs):
<div class="sidebar">
<div class="btn-container">
<button class="btn btn-block btn-info" data-toggle="modal" data-target="#create-gradebook-modal">
<i class="fa fa-book"></i>
Create Gradebook
</button>
</div>
<ul>
{{#each}}
<li {{bind-attr class="isActive:active"}}>
{{#link-to "gradebook" _id}}
{{title}}
{{/link-to}}
</li>
{{/each}}
</ul>
</div>
<div class="content">
<div class="container-fluid">
{{outlet}}
</div>
</div>
Templates (gradebook.hbs):
<h1>{{title}} <small>{{_id}}</small></h1>
<div class="btn-container">
<button class="btn btn-default"><i class="fa fa-pencil"></i> Rename</button>
<button class="btn btn-danger"><i class="fa fa-trash-o"></i> Trash</button>
</div>
What I'm having trouble with is under the GradebooksController.actions.createGradebook, I send a request to create the model and then I transition to the gradebook I just created. The creation and transition works just fine, but I want the sidebar (gradebooks.hbs) to show the newly created gradebook.
I think it has something to do with updating the model in the GradebooksRoute. How can I make it fetch for the models from the server again after creating a gradebook.
To elaborate on #fanta's comment, you'll want to do something like this:
App.GradebooksController = Em.ArrayController.extend({
isActive: false,
actions: {
createGradebook: function(newTitle) {
var self = this;
if(newTitle.trim()) {
var gradebook = Em.$.getJSON('/data/gradebooks/add/' + newTitle, {}, function(json) {
Em.$('#create-gradebook-modal').modal('hide').find('input').val('');
self.transitionToRoute('gradebook', json.id);
}, 'json');
self.get('content').pushObject(gradebook);
}
}
}
});

Router understanding issue with Ember 1.0 RC6

The following code worked fine with RC4 but it doesn't work with RC6. index.html#/users/1/edit doesn't fill the form to edit the user entry. Can anybody tell me what I have to change to get this working with the new router?
app.js
App = Ember.Application.create();
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: ':user_id' }, function() {
this.route('edit');
});
})
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.UserController = Ember.ObjectController.extend();
App.UserEditRoute = Ember.Route.extend({
model: function() {
return this.modelFor("user")
},
renderTemplate: function() {
this.render({ into: 'users' });
},
setupController: function(controller, model) {
if (model.get('isNew') == false) {
var map = this.map || Ember.Map.create();
this.map = map;
var transaction = this.get('store').transaction();
if (map.get(model)) {
transaction = map.get(model);
} else {
map.set(model, transaction);
transaction.add(model);
}
}
},
events: {
commitThis: function(model) {
var map = this.map;
var transaction = map.get(model);
transaction.commit();
map.remove(model);
App.Router.router.transitionTo('users.index')
},
rollbackThis: function(model) {
var map = this.map;
var transaction = map.get(model);
transaction.rollback();
transaction.add(model);
App.Router.router.transitionTo('users.index')
}
}
});
App.UserEditController = Ember.ObjectController.extend({
save: function(model) {
this.send('commitThis', model)
},
undo: function(model) {
this.send('rollbackThis', model)
}
});
App.Store = DS.Store.extend({
adapter: 'DS.FixtureAdapter'
});
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
App.User.FIXTURES = [{
id: 1,
firstName: "Yehuda",
lastName: "Katz"
}, {
id: 2,
firstName: "Tom",
lastName: "Dale"
}]
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Userlist Demo</title>
<link href="css/bootstrap.css" rel="stylesheet">
<style>
body {
padding-top: 60px;
}
</style>
<link href="css/bootstrap-responsive.css" rel="stylesheet">
</head>
<body>
<script type="text/x-handlebars">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="#">Demo</a>
<div class="nav">
<ul class="nav">
<li>{{#linkTo 'users'}}Users{{/linkTo}}</li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Demo Ember.js Application</h2>
<p>
A list of all users can be found {{#linkTo 'users'}}here{{/linkTo}}.
</p>
</script>
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span7'>
<table class='table table-striped'>
<thead>
<tr>
<th>First name</th>
<th>Last name <i class="icon-arrow-down"></i></th>
<th></th>
</tr>
</thead>
<tbody>
{{#each this itemController="user"}}
<tr {{bindAttr class="isDirty:warning"}}>
<td>{{firstName}}</td>
<td>{{lastName}}</td>
<td>
{{#unless isNew}}
{{#linkTo 'user.edit' this activeClass="disabled" classNames="btn btn-small"}}<i class="icon-edit"></i> Edit{{/linkTo}}
{{/unless}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<div class='span5'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user/edit">
<h2>Edit</h2>
<p><strong>First name</strong><br>{{input value=firstName type=text tabindex=1}}</p>
<p><strong>Last name</strong><br>{{input value=lastName type=text tabindex=2}}</p>
<p>
<button {{action 'save' this}} {{bindAttr class=":btn :btn-small :btn-primary content.isDirty:enabled:disabled"}} tabindex=4>Save changes</button>
<button {{action 'undo' this}} {{bindAttr class=":btn :btn-small content.isDirty:enabled:disabled"}} tabindex=5>Undo changes</button>
</p>
</script>
<script src="js/libs/jquery-1.9.1.js"></script>
<script src="js/libs/handlebars.js"></script>
<script src="js/libs/ember.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="js/libs/md5.js"></script>
<script src="js/app.js"></script>
</body>
</html>
Pretty sure this is because App.UserEditRoute.setupController is not calling _super. Strange cause I thought that breaking change was back in RC4. Anyway, try this:
App.UserEditRoute = Ember.Route.extend({
// ...
setupController: function(controller, model) {
this._super(controller, model);
// ...
}
}
Possibly related: Seems like setupController is saving state (this.map) on the route object. Surprised it works at all, for sure has potential to cause problems. Instead setupController should set properties on the local controller or model arguments, or use this.controllerFor() to access another controller.
In this case seems like a lot of code is not necessary, could just save/rollback on the model itself. So to simplify:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: ':user_id' }, function() {
this.route('edit');
});
})
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.UserController = Ember.ObjectController.extend();
App.UserEditRoute = Ember.Route.extend({
model: function() {
return this.modelFor("user")
},
renderTemplate: function() {
this.render({ into: 'users' });
},
events: {
save: function(model) {
model.save().then( function() {
App.Router.router.transitionTo('users.index')
}, function() {
alert('save failed!');
});
},
undo: function(model) {
model.rollback();
App.Router.router.transitionTo('users.index')
}
}
});
App.Store = DS.Store.extend({
adapter: 'DS.FixtureAdapter'
});
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
App.User.FIXTURES = [{
id: 1,
firstName: "Yehuda",
lastName: "Katz"
}, {
id: 2,
firstName: "Tom",
lastName: "Dale"
}]
See working example here: http://jsbin.com/ixucos/2/edit

Getting to know Ember StateManager, am I coding this right?

I am getting my feet wet with Ember.JS StateManager, and I am currently following the online documentation at: http://emberjs.com/api/classes/Ember.StateManager.html
I have done my best with this fiddle but cannot seem to get anything visual out at all. I have done numerous searches here at SOF and Google.
I have three states with three list item which trigger these states in view. I have placed a "ready function" within "Application.create({}" which fires, but as soon as the StateManager is initialized, "ready function" doesn't fire. I would highly appreciate your input and help.
Here is the fiddle: http://jsfiddle.net/exciter/NRmHc/12/
APP CODE
$(function(){
App = Ember.Application.create({
ready: function(){
//alert("APP INIT");
}
});
App.stateManager = Ember.StateManager.create({
showFirstState: function(manager){
App.stateManager.transitionTo('startupState');
},
showSecondState: function(manager){
App.stateManager.transitionTo('second');
},
showThirdState: function(manager){
App.stateManager.transitionTo('third');
},
showFourthState: function(manager){
App.stateManager.transitionTo('fourth');
},
// INIT STATE
initialState: 'startupState',
startupState: Ember.State.create({
templateName: 'startup',
classNames: ['state','black'],
enter: function() { alert("STARTUP ENTERED"); }
}),
second: Ember.State.create({
templateName: 'second',
classNames: ['state','orange'],
enter: function() { alert("SECOND ENTERED"); }
}),
third: Ember.State.create({
templateName: 'third',
classNames: ['state','lime'],
enter: function() { alert("THIRD ENTERED"); }
}),
fourth: Ember.State.create({
templateName: 'fourth',
classNames: ['state','blue'],
enter: function() { alert("FOURTH ENTERED"); }
}),
});
App.initialize();
});
HTML:
<!-- CHECK TO SEE IF CSS IS FUNCTIONAL
<div id="state" class="state blue">
STATE
</div>
-->
<script type="text/x-handlebars">
<nav>
<ul>
<li {{action "showFirstState" target="App.stateManager"}}>First State</li>
<li {{action "showSecondState" target="App.stateManager"}}>Second State</li>
<li {{action "showThirdState" target="App.stateManager"}}>Third State</li>
<li {{action "showFourthState" target="App.stateManager"}}>Fourth State</li>
</ul>
</nav>
</script>
<script type="text/x-handlebars" data-template-name="startup">
<h2> STARTUP STATE </h2>
</script>
<script type="text/x-handlebars" data-template-name="second">
<h2>SECOND STATE</h2>
</script>
<script type="text/x-handlebars" data-template-name="third">
<h2>THIRD STATE</h2>
</script>
<script type="text/x-handlebars" data-template-name="fourth">
<h2>THIRD STATE</h2>
</script>
CSS:
nav { background-color:#e9e9e9; padding: 1em 0 1em 0; }
nav li { display: inline; cursor: pointer; padding:0 1em 0 1em;}
.state { width:700px; height:500px; margin:0 auto; padding:0; background-color:#c9c9c9; }
.black { background-color:#666; }
.blue { background-color:#6699cc; }
.orange { background-color:#FF6600; }
.lime { background-color:#CCFF33; }
I get the feeling that what you want is not a statemanager but a router. Have a look at this link for further info.