Init application and use Selected value in connectOutlet - ember.js

I got problem with initialization of application.
I create jsfiddle which simply works on my desktop but not on jsfiddle.
http://jsfiddle.net/zDSnm/
I hope you will catch the idea.
On the beginining od my aplication I have to get some values from rest and values to Ember.Select.
Depends on what is choosen all my connectOutlets functions use this value.
Here I get some data from REST
$.ajax({
url: 'https://api.github.com/repos/emberjs/ember.js/contributors',
dataType: 'jsonp',
context: this,
success: function(response){
[{login: 'a'},{login: 'b'}].forEach(function(c){
this.allContributors.addObject(App.Contributor.create(c))
},this);
}
})
and put it to my Select View:
{{view Ember.Select
contentBinding="App.Contributor.allContributors"
selectionBinding="App.Contributor.selectedContributor"
optionLabelPath="content.login"
optionValuePath="content.id" }}
{{outlet}}
And in every of my route I need to use this value, which is selected in this selection box
index : Ember.Route.extend({
route: '/',
connectOutlets: function(router){
router.get('applicationController').connectOutlet('oneContributor',App.Contributor.selectedContributor);
}
})
I'd also add observer to this selectedContributor value which calls connectOutlets of currentState (I know I shouldn't do this but I don't know why and how should I do this in properly way)
App.Contributor.reopenClass({
//...
refresh : function(){
App.router.currentState.connectOutlets(App.router);
}.observes('selectedContributor'),
//...
I hope there is some good way to solve such problem.
If there is something not clear please leave comment.

If I understand correctly you want to show the currently selected contributor. One way to do it is to listen for a change in the selected contributor and send a transitionTo action to the Router.
First the router:
index : Ember.Route.extend({
route: '/',
showContibutor: Ember.Route.transitionTo('show'),
showNoneSelected: Ember.Route.transitionTo('noneSelected'),
connectOutlets: function(router){
router.applicationController.connectOutlet({ name: 'contributors', context: App.Contributor.find() });
},
// if no contributor is selected, the router navigates here
// for example, when a default option "Make a selection" is selected.
noneSelected: Ember.Route.extend({
route: '/'
}),
show: Ember.Route.extend({
route: '/:contributor_id'
connectOutlets: function(router, context){
router.applicationController.connectOutlet({name: 'contributor', context: context})
},
exit: function(router) {
// This will remove the App.ContributorView from the template.
router.applicationController.disconnectOutlet('view');
}
})
})
with a template for App.ContributorsView:
{{view Ember.Select
contentBinding="controller"
selectionBinding="controller.selectedContributor"
optionLabelPath="content.login"
optionValuePath="content.id"}}
{{outlet}}
and an ArrayController to manage contributors:
App.ContributorsController = Ember.ArrayController.extend({
onSelectedContributorChange: function() {
var selectedContributor = this.get('selectedContributor');
if (selectedContributor) {
App.router.send('showContributor', selectedContributor);
} else {
App.router.send('showNoneSelected');
}
}.observes('selectedContributor')
});
The user a selects a contributor and the contributorsController tells the router to show the view for the contributor (i.e, show the view App.ContributorView with context selectedContributor).
App.ContributorView = Ember.View.extend({
templateName: 'contributor'
});
controller for selected contributor:
App.ContributorController = Ember.ObjectController.extend({
// define events and data manipulation methods
// specific to the currently selected contributor.
});
Hope this helps.
UPDATE: If you need to show the first record by default, the noneSelected route should look like this:
noneSelected: Ember.Route.extend({
route: '/',
connectOutlets: function(router){
var context = router.contributorsController.get('content.firstRecord');
router.applicationController.connectOutlet({name: 'contributor', context: context})
}
})
And define firstRecord in ContributorsController:
App.ContributorsController = Ember.ArrayController.extend({
firstRecord: function() {
return this.get('content') && this.get('content').objectAt(0)
}.property('content.[]')
});
Haven't tested it, but it should work.

Related

emberjs | save state of routes and nested resources

i am trying to build my first emberjs app and i wonder how i can save the state of a nested route to rebuild that state when the top route is revisted in the current session.
To give an example:
Lets Say a user switches from /overview/item1 to /info and then returns to
/overview/ and want to redirect him to /overview/item1
HTML
<div id="navigation">
{{#link-to 'info' class='link' }}Info{{/link-to}}
{{#link-to 'overview' class='link'}} Overview {{/link-to}}
</div>
JS
App.Router.map(function(){
this.route('info');
this.resource('overview', function () {
this.resource('item', { path : '/:item_id'});
});
});
it would be really nice if somebody could give me a hint to the right approach of this.
There are various ways for achieving your goal. Basically, you need to store state of last visited overview/:item_id route in the parent route or controller. Then, you need to check this state before resolving model of overview route. If state is not null (user was selected some item from overview/:item_id), abort current transition and start the new one (to
overview/:selected_item_id).
Schematic solution in code:
// 1st approach
App.OverviewController = Ember.ObjectController.extend({
selectedItem: null
});
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
// 2nd approach (less code)
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
},
setupController: function(controller) {
controller.reopen({ selectedItem: null });
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
It's important to keep the item itself, not it's id, because it'll way more easier to load overview/:item_id route in the future (passing stored model in this.transitionTo('overview.item', item)).

Ember.js: How to stay in Leaf Route on model change, when link-to goes to parent resource

I have a list of posts, with a nested summary and full route.:
App.Router.map(function() {
this.resource('post', {path: ':post_id'}, function() {
this.route('summary', {path: '/'});
this.route('full');
});
});
The summary route is at path: /, so when I {{#link-to 'post' somePost}}, it arrives in post.summary.
When I change the model, i.e. route to someOtherPost, I would like to stay in the current leaf route (either summary or full).
How do I do this? I tried willTransition, but I'm not sure how to check if the model changed:
Here's a JSBin with what I have so far: http://emberjs.jsbin.com/benot/2/edit?js,output
This is a great use case for a dynamic link-to.
link-to Code
App.ApplicationController = Em.ArrayController.extend({
postLink: 'post'
});
Template
{{#each item in controller}}
<li>{{#link-to postLink item}}{{item.title}}{{/link-to}}</li>
{{/each}}
You can send an action from each child route, and it will go up the router (http://emberjs.com/guides/templates/actions/) until caught. At the parent route you can change the dynamic value of the link-to and bam, you're good to go.
Route Code
App.PostSummaryRoute = Em.Route.extend({
actions:{
didTransition: function(){
this.send('swapLink', 'post.summary');
}
}
});
App.PostFullRoute = Em.Route.extend({
actions:{
didTransition: function(){
this.send('swapLink', 'post.full');
}
}
});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return [
App.Post.create({id: '1', title: 'Foo', body: 'Lorem'}),
App.Post.create({id: '2', title: 'Bar', body: 'Ipsum'})
];
},
actions: {
swapLink: function(newLink){
console.log('swapLink ' + newLink);
this.controller.set('postLink', newLink);
}
}
});
http://emberjs.jsbin.com/dekokafu/1/edit

Emberjs 1.x-pre- Ember.Router and Ember.computed issues

I was reading http://code418.com/blog/2012/03/26/advanced-emberjs-bindings/ and came across Ember.Binding.and for transform which has deprecated in the current emberjs for Ember.computed. I decided to update the old emberjs 0.9.x fiddle http://jsfiddle.net/Wjtcj/ to work with emberjs 1.x and provided an Ember.computed.and as shown in the new fiddle http://jsfiddle.net/Wjtcj/5/. Though it works, i cant make it return thesame output as the old one but when an improved version of the code http://jsfiddle.net/Wjtcj/28/ fails with
STATEMANAGER: Sending event 'navigateAway' to state root.
STATEMANAGER: Sending event 'unroutePath' to state root.
STATEMANAGER: Sending event 'routePath' to state root.
STATEMANAGER: Entering root.index
<error>
It seems the setSync function is the issue and fails because i am calling computed property on it.
The handlebars template:
<script type="text/x-handlebars" data-template-name="application" >
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="obj" >
{{#each App.ObjController}}
<p>{{this}}</p>
{{/each}}
</script>​
update, please use this link for the updated code http://jsfiddle.net/Wjtcj/28/. The code below no more applies
App = Ember.Application.create();
Ember.computed.and = function(dependentKey, otherKey) {
return Ember.computed(dependentKey, otherKey, function(key) {
return get(this, dependentKey) && get(this, otherKey);
});
};
Ember.computed.or = function(dependentKey, otherKey) {
return Ember.computed(dependentKey, otherKey, function(key) {
return get(this, dependentKey) || get(this, otherKey);
});
};
App.ApplicationController = Em.Controller.extend();
App.ApplicationView = Ember.View.extend({
templateName: 'application'
});
App.ObjView = Em.View.extend({
templateName: 'obj'
});
App.ObjController = Ember.ArrayController.extend({
content: [],
user: Ember.Object.create({isAdmin: false, isOwner: false}),
isSelected: false,
isSaveEnabled: false,
canRead: false,
isSaveEnabledBinding: Ember.computed.and('user.isAdmin', 'isSelected'),
canReadBinding: Ember.computed.or('user.isAdmin', 'user.isOwner'),
setSync: function(property, value) {
this.set(property, value);
Ember.run.sync(); // synchronize bindings
this.pushObject('isSaveEnabled = %# ; canRead = %#'.fmt(this.get('isSaveEnabled'), this.get('canRead')));
}
});
App.ObjController.setSync('isSelected', false);
App.ObjController.setSync('user', Ember.Object.create({isAdmin: true, isOwner: false}));
App.ObjController.setSync('isSelected', true);
App.ObjController.setSync('user', Ember.Object.create({isAdmin: false, isOwner: true}));
App.ObjController.setSync('user', Ember.Object.create({isAdmin: false, isOwner: false}));
App.Router = Ember.Router.extend({
enableLogging: true,
location: 'hash',
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('application');
}
}),
obj: Ember.Route.extend({
route: '/obj',
enter: function(router) {
console.log("The obj sub-state was entered.");
},
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router, context) {
router.get('applicationController').connectOutlet( 'obj');
}
})
})
})
});
​
Thanks for any suggestions or fix.
Lots of things going wrong in your example that I'm not sure this will be all that illustrative, but I think this is what you're trying to accomplish:
http://jsfiddle.net/machty/Wjtcj/31/
Important points
It's rare that you ever need to manually call Ember.run.sync() unless you're doing test cases or some other unusual circumstance.
You were trying to cram too many things in ObjController. The intended purpose is to display a list of Users and their privileges; I employed the common pattern of using an ArrayController to manage the list of Users, and then displayed each one with a UserView.
Your original <error> was due to trying to connect applicationController's outlet to... applicationController, hence the recursion and stack overflow
There's a difference between bindings and computed properties. If you're using computed properties, don't put 'Binding' at end of your property
So instead of this:
isSaveEnabledBinding: Ember.computed.and('user.isAdmin', 'isSelected'),
canReadBinding: Ember.computed.or('user.isAdmin', 'user.isOwner'),
Do this
isSaveEnabled: Ember.computed.and('isAdmin', 'isSelected'),
canRead: Ember.computed.or('isAdmin', 'isOwner'),

How to create an action helper that points to a route with a text value using ember.js

I'm writing a simple sort / pagination controller using ember pre 1.0. I want to change the sort property on the controller when the user clicks a column header on the table. I have a simple action helper that points to my routers sortUser method but I can't seem to pass in a raw string that the route can use as the param such as "username" or "id"
Also my url seems broken (getting this url)
http://localhost:8000/#/sort/undefined
instead of
http://localhost:8000/#/sort/username
Thank you in advance
<table width="250px">
<thead>
<th><a {{action sortUsers "id" href=true}}>id</th>
<th><a {{action sortUsers "username" href=true}}>username</th>
<th>update</th>
<th>delete</th>
</thead>
<tbody>
Here is my router (cutting out some complexity but it is a nested route)
PersonApp.Router = Ember.Router.create({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
paginateUsers: Ember.Route.transitionTo('paginated'),
sortUsers: Ember.Route.transitionTo('index.sort.index'),
connectOutlets: function(router, context) {
router.get('applicationController').connectOutlet('person', router.get('store').findAll(PersonApp.Person));
},
index: Ember.Route.extend({
route: '/'
}),
paginated: Ember.Route.extend({
route: '/page/:page_id',
connectOutlets: function(router, context) {
router.get('personController').set('selectedPage', context.page_id);
},
exit: function(router) {
router.get('personController').set('selectedPage', undefined);
}
}),
sort: Ember.Route.extend({
route: '/sort/:column',
serialize: function(router, context) {
if (context) { return { sort: context.sort } }
return {sort: undefined}
},
deserialize: function(router, context) {
return { sort: context.column }
},
connectOutlets: function(router, context) {
router.set('personController.sortProperties', ['username']);
router.get('personController').set('selectedPage', 1);
},
UPDATE
I have a full jsfiddle of this in action now (the sorting along side filter + pagination)
I think you may have a combination of maybe 2 smaller issues contributing to your problem.
Your sortUsers action points to an index route under your sort route. I don't see such a route in the code you include. I only see a sort/:column route.
I had problems getting routes similar to this working yesterday and ended up with something like the following to get the "context" right. I am not at all certain you should have to do that but it got things working for me.
sortUsers: function(router, event) {
// you can now also console.log here as suggested by sly7_7
router.transitionTo('index.sort', event.context);
},
Your serialize/deserialize methods don't look right. They may be fine but from my outside perspective they look broken. Serialize should take whatever you have as the "context" and turn it into an url parameter. Deserialize should do the opposite and return exactly the same as what serialize get as input.
serialize: function(router, context) {
return {
column: context // you are passing a string so let's just put that in the url as is
}
},
deserialize: function(router, urlParams) {
return urlParams.column; // just return the straight string from the url
}
There might be some detail I am missing but that looks like a few changes that might get your transition to run.
Also, make sure you ask the router to log what it is doing... then you can also track your progress better. enableLogging: true

Url contains 'undefined' instead of id after navigating back from 'edit' to 'show'

I read the Ember Application Structure guide and now I trying to create a simple one page application with ember.js.
My home page shows a sidebar containing a list of Post objects. When I click on a list-item, on the right of the sidebar I show a read-only version of the selected item. This version has a 'Edit' button, which makes it possible to edit the item. The edit version has a 'Cancel' link to switch back to the read-only version.
I got this all working, however, when navigating back to the read-only version, the url in the address bar is not updated properly. When navigating back to my read-only version I expect the url to change from 'example.com/#posts/123/edit' to 'example.com/#posts/123', but instead I get ''example.com/#posts/undefined'.
I tried to provide a context when calling transitionTo in the 'cancel' event, but that doesn't work.
How can I navigate back to my read-only from while keeping the url pointing to the proper post (example.com/#posts/123)?
Most of my code is identical to the example in the ember guide, my router and 'edit' related code is shown below:
App.EditPostView = Em.View.extend({
templateName: 'edit_post'
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string'),
published: DS.attr('boolean')
});
App.Router = Em.Router.extend({
enableLogging: true,
location: 'hash',
root: Em.Route.extend({
index: Em.Route.extend({
route: '/',
redirectsTo: 'posts.index'
})
}),
posts: Em.Route.extend({
route: '/posts', # example.com/#posts
showPost: Em.Route.transitionTo('posts.show'),
editPost: Em.Route.transitionTo('posts.edit'),
index: Em.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('posts', App.Post.find());
}
}),
show: Em.Route.extend({
route: '/:post_id', # example.com/#posts/123
connectOutlets: function(router, post) {
router.get('postsController').connectOutlet('post', post);
}
}),
edit: Em.Route.extend({
route: '/:post_id/edit', # example.com/#posts/123/edit
connectOutlets: function(router, post) {
router.get('postsController').connectOutlet({
viewClass: App.EditPostView,
controller: router.get('postController'),
context: post
});
},
}),
cancel: function(router, event) {
router.transitionTo('show'); # Expect this to use 'example.com/#posts/123' but instead it shows 'example.com/#posts/undefined'
}
})
});
# edit_post.handlebars:
<form {{action save on="submit"}}>
...
{{view Em.TextField valueBinding="title"}}
{{view Em.TextArea valueBinding="body"}}
...
<a {{action cancel}} class="btn">Cancel</a>
</form>
You are missing the context in transitionTo calls. You should have something like:
showPost: function (router, event) {
var post = event.context;
Em.Route.transitionTo('posts.show', post);
},
editPost: function (router, event) {
var post = event.context;
Em.Route.transitionTo('posts.edit', post);
},