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.
Related
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 };
}
});
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);
});
}
});
});
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
I wonder if I'm just doing something fundamentally wrong here but I'm trying to have a model define the style attribute in a view. So for example the ember view uses the card template and starts out with <div style="color: green">...</div> that is backed by the model property color. When I change it somewhere else via App.Card.find(2).set("color", "color: red").save() I expect the template to update the value, but it does nothing. Using {{ bindAttr style model.color }} in the template directly does keep the value in sync, but then I have an extra ember-view div element.
http://jsfiddle.net/dbhWg/3/
javascript:
App = Ember.Application.create();
App.Store = DS.Store.extend({
adapter: 'DS.FixtureAdapter'
});
App.Router.map(function () {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function () {
return App.Card.find()
}
});
App.Card = DS.Model.extend({
color: DS.attr('string'),
});
App.Card.FIXTURES = [{
id: 1,
color: "color: green"
}, {
id: 2,
color: "color: blue"
}];
App.CardView = Ember.View.extend({
templateName: "card",
attributeBindings: ['style'],
style: function () {
return this.get('controller.model.color')
}.property('controller.model'),
didInsertElement: function () {
App.Card.find(2).set('color', "color: red").save()
console.log(App.Card.find(2).get('color'))
}
});
templates:
<script type="text/x-handlebars" data-template-name="card">
<h1> HELLO THERE </h1>
</script>
<script type="text/x-handlebars">
<h2> Welcome to Ember.js </h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{#each item in model}}
{{render "card" item}}
{{/each}}
</script>
You forgot to add the dependecency for the color in computed property
style: function () {
return this.get('controller.model.color')
}.property('controller.model.color'),
Working Fiddle
As far as I know, you can't update CSS using bindAttr what I'd suggest you to use class instead, define classes as follows:
.red{
color: red;
}
.green{
color: green;
}
.blue: {
color: blue;
}
The update fixtures as:
App.Card.FIXTURES = [{
id: 1,
color: "green"
}, {
id: 2,
color: "blue"
}];
Bind the color to class as follows
App.CardView = Ember.View.extend({
templateName: "card",
classNameBindings: ['color'],
color: function () {
return this.get('controller.model.color');
}.property('controller.model.color'),
didInsertElement: function () {
App.Card.find(2).set('color', "red").save();
console.log(App.Card.find(2).get('color'));
}
});
I'm looking for advice on how to trigger this view function insertNewLine from a button (see view and template below). I'm guessing there's probably a better way to structure this code. Thanks for your help.
// view
App.SearchView = Ember.TextField.extend({
insertNewline: function() {
var value = this.get('value');
if (value) {
App.productsController.search(value);
}
}
});
// template
<script type="text/x-handlebars">
{{view App.SearchView placeholder="search"}}
<button id="search-button" class="btn primary">Search</button>
</script>
You could use the mixin Ember.TargetActionSupport on your TextField and execute triggerAction() when insertNewline is invoked. See http://jsfiddle.net/pangratz666/zc9AA/
Handlebars:
<script type="text/x-handlebars">
{{view App.SearchView placeholder="search" target="App.searchController" action="search"}}
{{#view Ember.Button target="App.searchController" action="search" }}
Search
{{/view}}
</script>
JavaScript:
App = Ember.Application.create({});
App.searchController = Ember.Object.create({
searchText: '',
search: function(){
console.log('search for %#'.fmt( this.get('searchText') ));
}
});
App.SearchView = Ember.TextField.extend(Ember.TargetActionSupport, {
valueBinding: 'App.searchController.searchText',
insertNewline: function() {
this.triggerAction();
}
});