How to get a parent routes model from inside controller action? - ember.js

Inside my route for "appointments" below I can reach up and get the model for the parent "day"
App.Router.map(function(match) {
this.resource("day", { path: "/day/:day" }, function() {
this.resource("appointments", { path: "/appointments" }, function() {
this.route("new", { path: "/:appointment_start/new" });
this.route("edit", { path: "/:appointment_id/edit" });
});
});
});
But when I'm deep inside the new or edit routes, how can I reach up (from within the actions handler) and grab the parent "day" model like I did in the route?
App.AppointmentsEditController = Ember.Controller.extend({
actions: {
updateAppointment: function(appointment) {
var day = this.get('??');
}
}
});
Update
The final controller code now looks like this
App.AppointmentsEditController = Ember.Controller.extend({
needs: 'day',
actions: {
updateAppointment: function(appointment) {
var day = this.get('controllers.day');
}
}
});

Toran - sorry to add this as an extra answer, I can't comment yet - yes, it should work for free. You can access controllers.post from within the actions block like this:
var postController = this.get('controllers.post')

There is simple way to do it. In AppointmentsEditController add
needs: "day"
Then you can access to day controller via this.get('controllers.day').
I always use something like this:
App.CommentsControler = Ember.Controller.extend({
needs: "post",
postBinding: "controllers.post",
...
postName: function() {
return this.post.name;
}.property("post.name")
})
Take a look of this article http://emberjs.com/guides/controllers/dependencies-between-controllers/
I hope this help :)

Related

How to render a view in EmberJS 1.13.8 without refreshing the page?

Sorry if this question is too naive,but I am getting confused a lot on rendering views in Ember.
I have a 'Person' route. I am able to do CRUD operations on it.
router.js
this.route('person', function() {
this.route('index', { path: '' });
});
controllers/person/index.js
actions: {
createPerson: function() {
var person = this.get('store').createRecord('person');
this.set('person', person);
this.set('editPersonPane', true);
},
editPerson: function(person) {
this.set('person', person);
this.set('editPersonPane', true);
},
closeEditPerson: function() {
this.get('person').rollback();
this.set('editPersonPane', false);
},
savePerson: function(person) {
var _this = this;
person.save().then(function() {
_this.set('editPersonPane', false);
Ember.get(_this, 'flashMessages').success('person.flash.personUpdateSuccessful');
}, function() {
Ember.get(_this, 'flashMessages').danger('apiFailure');
});
},
deletePerson: function(person) {
var _this = this;
person.destroyRecord().then(function() {
_this.set('editPersonPane', false);
Ember.get(_this, 'flashMessages').success('person.flash.personDeleteSuccessful');
}, function() {
Ember.get(_this, 'flashMessages').danger('apiFailure');
});
}
}
What I want to do now is when I want to create a new person, a form slides in to create it. After filling up the form, I want the list view of persons to be updated immediately, without refreshing the page. Right now, I have been able to add the form and when I add a new person, I get a successful flash message but it's not updated in the view immediately. I have to refresh the page.
It might have to do something with observers but I am still not sure how.
Reloading a saved object will allow you to avoid having to refresh the page:
savePerson: function(person) {
var _this = this;
person.save().then(function(saved) {
saved.reload();
_this.set('editPersonPane', false);
Ember.get(_this, 'flashMessages').success('person.flash.personUpdateSuccessful');
}, function() {
Ember.get(_this, 'flashMessages').danger('apiFailure');
});
}
Also, it's worth noting that if you destructure and use ES6 syntax, you can clean up your code a bit as follows:
//controllers/person/index.js
//at the top of the file
import Ember from 'ember';
const { get, set } = Ember;
//other code
actions: {
//other actions
savePerson(person): {
person.save().then((saved) => {
saved.reload();
set(this, 'editPersonPane', false);
get(this, 'flashMessages').success('person.flash.personUpdateSuccessful');
}, () {
get(this, 'flashMessages').danger('apiFailure');
});
}
}
Which route is displaying your persons list?
Wouldn't something like this work better, so you can display the list and then edit a person within the persons.hbs outlet?
this.route('persons', function() {
this.route('person', { path: 'id' });
});

Adding a new route to an existing Ember.js app, need input

I have an existing Ember app which works great. I need to add a new subroute to the app to allow users to view additional information. My current route looks like this:
Social.Router.map(function() {
this.resource('accounts', { path: '/accounts' }, function(){
this.resource('account', { path: ':account_id'});
});
});
with the following URL
#/accounts/56/
The route I'd like to add is this:
#/accounts/56/interactions
So I added a nested route like so:
Social.Router.map(function() {
this.resource('accounts', { path: '/accounts' }, function(){
this.resource('account', { path: ':account_id'}, function(){
this.route('interactions', { path: '/interactions'});
});
});
});
But when that route is accessed I get the following error:
Uncaught Error: assertion failed: The route interactions was not found core.libs.js:2236
Uncaught Error: You cannot modify child views while in the inBuffer state core.libs.js:19298
So I also added an empty InteractionsRoute but that didn't resolve it:
Social.InteractionsRoute = Ember.Route.extend();
Does anyone have input on what might be going wrong?
In addition I'm trying to add a button to the interface which looks like this:
{{#linkTo "interactions"}}# Interactions{{/linkTo}}
Social.Router.map(function() {
this.resource('accounts', { path: '/accounts' }, function(){
this.resource('account', { path: ':account_id'}, function(){
this.route('interactions', { path: '/interactions'});
});
});
});
Like this the URL to interactions is #/interactions
But you wanted this: #/accounts/56/interactions
Therefore you need to remove the preceding slash in the path hook of interactions, otherwise you'll indicate that this route will be accessed from the root.
Social.Router.map(function() {
this.resource('accounts', { path: '/accounts' }, function(){
this.resource('account', { path: ':account_id'}, function(){
this.route('interactions', { path: 'interactions'});
});
});
});
By the way, if you don't declare a path hook the url will be the same as the route name. So you can also use this:
Social.Router.map(function() {
this.resource('accounts', function(){
this.resource('account', { path: ':account_id'}, function(){
this.route('interactions');
});
});
});
Try splitting out your list from the individual record view.
Social.Router.map(function() {
this.resource('accounts');
this.resource('account', { path: '/accounts/:account_id' }, function() {
this.route('interactions');
});
});
Your route name for interactions should be like this:
Social.AccountInteractionsRoute = Ember.Route.extend();
From the table on http://emberjs.com/guides/routing/defining-your-routes/
If all else fails you could just avoid the nested resources and define the paths for each route.
Social.Router.map(function() {
this.resource('accounts');
this.resource('account', { path: '/accounts/:account_id' });
this.resource('account-interactions', { path: '/accounts/:account_id/interactions' });
});

Ember Router transitionTo nested route with params

App.Router.map(function() {
this.resource('documents', { path: '/documents' }, function() {
this.route('edit', { path: ':document_id/edit' });
});
this.resource('documentsFiltered', { path: '/documents/:type_id' }, function() {
this.route('edit', { path: ':document_id/edit' });
this.route('new');
});
});
And this controller with a subview event that basically transitions to a filtered document
App.DocumentsController = Ember.ArrayController.extend({
subview: function(context) {
Ember.run.next(this, function() {
//window.location.hash = '#/documents/'+context.id;
return this.transitionTo('documentsFiltered', context);
});
},
});
My problem is that this code works fine when Hash of page is changed.
But when I run the above code NOT w/ the location.hash bit and w/ the Ember native transitionTo I get a cryptic
Uncaught TypeError: Object [object Object] has no method 'slice'
Any clues?
Thanks
UPDATE:
App.DocumentsFilteredRoute = Ember.Route.extend({
model: function(params) {
return App.Document.find({type_id: params.type_id});
},
});
{{#collection contentBinding="documents" tagName="ul" class="content-nav"}}
<li {{action subview this}}>{{this.nameOfType}}</li>
{{/collection}}
The problem is that your model hook is returning an array, while in your transitionTo you are using a single object. As a rule of thumb your calls to transitionTo should pass the same data structure that is returned by your model hook. Following this rule of thumb i would recommend to do the following:
App.DocumentsController = Ember.ArrayController.extend({
subview: function(document) {
var documents = App.Document.find({type_id: document.get("typeId")});
Ember.run.next(this, function() {
return this.transitionTo('documentsFiltered', documents);
});
}
});
Note: I assume that the type_id is stored in the attribute typeId. Maybe you need to adapt it according to your needs.

Can a nested ember.js route use a different model and still retain controller context?

I have a basic person object
PersonApp.Person = DS.Model.extend({
username: DS.attr('string')
});
I have a route to find all people
PersonApp.Router.map(function(match) {
this.resource("person", { path: "/" }, function() {
this.route("page", { path: "/page/:page_id" });
this.route("search", { path: "/search/:page_term" });
});
});
In my route I'm looking at the params coming in
PersonApp.PersonRoute = Ember.Route.extend({
selectedPage: 1,
filterBy: '',
model: function(params) {
if (get(params, 'page_id') !== undefined) {
this.selectedPage = get(params, 'page_id');
} else {
this.selectedPage = 1;
}
if (get(params, 'page_term') !== undefined) {
this.filterBy = get(params, 'page_term');
} else {
this.filterBy = '';
}
console.log(this.selectedPage);
console.log(this.filterBy);
return PersonApp.Person.find();
}
});
My nested routes are using a different model (not person directly) as they contain data that isn't persisted (and really only let me flip a bit on the controller)
Yet when I manually put something on the url or click a link that does a full blown transition the "params" coming into my model hook above are always empty.
Here is the basic page model I'm using (w/ search support)
PersonApp.Page = Ember.Object.extend({
term: ''
});
When a user does a search I have a view that invokes transitionTo
PersonApp.SearchField = Ember.TextField.extend({
keyUp: function(e) {
var model = PersonApp.Page.create({term: this.get('value')});
this.get('controller.target').transitionTo('person.search', model);
}
});
Any way I can pass this "page" model to a nested view and still retain the basic "person" controller context (ie- so I can manipulate the view around this array of model objects)

Hashbang URLs using Ember.js

I am trying to set up my Router to use "hashbang" URLs (#!).
I tried this, but obviously it doesn't work:
App.Router.map(function() {
this.route("index", { path: "!/" });
this.route("otherState", { path: "!/otherState" });
});
Is this possible to do in Ember?
Teddy Zeenny's answer is mostly correct, and registerImplementation seems to be a clean way to implement this. I tried to just edit his answer to make it fully answer the question, but my edit got rejected.
Anyway here is the full code to make Ember use hashbang URLs:
(function() {
var get = Ember.get, set = Ember.set;
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
getURL: function() {
return get(this, 'location').hash.substr(2);
},
setURL: function(path) {
get(this, 'location').hash = "!"+path;
set(this, 'lastSetURL', "!"+path);
},
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
Ember.run(function() {
var path = location.hash.substr(2);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(location.hash.substr(2));
});
});
},
formatURL: function(url) {
return '#!'+url;
}
}));
})();
Then once you create your app you need to change the router to utilize the "hashbang" location implementation:
App.Router.reopen({
location: 'hashbang'
})
Extending Ember.HashLocation would be the way to go.
For a clean implementation, you can do the following.
Ember.Location.registerImplementation('hashbang', Ember.HashLocation.extend({
// overwrite what you need, for example:
formatURL: function(url) {
return '#!' + url;
}
// you'll also need to overwrite setURL, getURL, onUpdateURL...
})
Then instruct your App Router to use your custom implementation for location management:
App.Router.reopen({
location: 'hashbang'
})