Namespacing, sharing models, reusing controller in Ember - ember.js

I have an Ember 1.10 application with api support for 2 models, Students and Parents, and basic CRUD setup for each model, as well as the ability to read and write messages to/from Students and Parents when visiting their respective /show pages.
I must create an Inbox in which the messages as well as all the other data on Students and Parents is available and must do so without the ability to change or add to the api (i.e. I can' create a Messages endpoint).
Basically, I need to keep the Student and Parent models and controllers as they are, but need to be able to access them from routes namespaced under the Inbox like so:
Students
-inbox
-index
-edit
-show
Parents
-inbox
Inbox
StudentInbox <-- should use StudentsRoute and StudentsController
-index
-show
-edit
ParentInbox
-index
-show
-edit
So that visiting /inbox/students/123 will be using the model defined in the StudentRoute and the actions and properties defined on the StudentController. The InboxStudentsShowRoute should similarly have access to the StudentsShowRoute and Controller.
Instead of copying and pasting the existing code from the Student and Parent routes and controllers, is there a way to proxy actions fired from templates under the Inbox namespace to the relevant models and controllers and maintain the ability to pass in params.
For example, passing in params like ?unread=true while on the InboxStudentsShow route would pass these to the StudentsShowRoute and Controller (and bubble appropriately to it's parent route and controller) or editing a Student from student/inbox/123/edit template would work just as it does on the student/123/edit template?
New to Ember and know there has to be a better way to reuse existing code and proxy actions from a namespace. Any advice would be greatly appreciated!

I'd go here with creating a Mixin which holds same code for Route and specify controllerName, templateName in InboxStudentsShowRoute. Then, you extend from this Mixin in both routes. Model, template, actions, properties will be the same.
For example, passing in params like ?unread=true while on the
InboxStudentsShow route would pass these to the StudentsShowRoute and
Controller (and bubble appropriately to it's parent route and
controller)
I'm not sure what do you mean here by pass these to the StudentsShowRoute and Controller, but CONTROLLER properties changed in one route should remain the same in another route, because controllers are singletons in Ember. I don't know what do you mean by passing query param to another route, because if you transition to other part of your app you change URL and you can't have 2 urls at the same time, so how could query params be passed to another route?
In Ember 1.10 query params are defined in Controllers so I guess behaviour will remain the same.
Example of InboxStudentsShowRoute(move code from StudentsShowRoute to StudentsShowMixin, define controller and template name):
import Ember from 'ember';
import StudentsShowMixin from '../mixins/students-show';
export default Ember.Route.extend(StudentsShowMixin, {
controllerName: 'students/show',
templateName: 'students/show'
});

Related

EmberJS "detail" page fetches the model automatically - is this normal?

Long time reader - first time questioner here.
I am developing a medium sized application in EmberJS which is a framework that I have been using for a while.
Today I have realised that it fetches model data from the server without me writing any code.
I have a main route called "students". Then there is a "list" sub route where the model() function of this route calls the store to fetch all the students and lists them on a table.
On each row of this table I link to another sub route called "detail" where it accepts the ID of each student as an argument. However inside the route.js file for this route there is no model() function querying any information about the specific student from the server.
Ember does this automatically somehow as I can see the appropriate network request being made using chrome dev tools.
How is this happening and is it normal?
Thank you in advance.
The Ember router will automatically load a model if it detects a dynamic segment that ends in _id.
Ember follows the convention of :model-name_id for two reasons. The first reason is that Routes know how to fetch the right model by default, if you follow the convention
You mentioned that your api route is /api/student/details/:student_id and I would expect that your ember route is fairly similar.
It detected _id, and called store.find('student', params.student_id) automatically for you when you navigated to that route.
This is completely normal, and is one of the ways Ember encourages you to follow convention - If you do, you don't have to create as much boilerplate.
If you want to avoid the second request, possibly because the list route pulls all relevant data, you can pass the student model instead of the student id.

Multiple component with multiple models

So, I'm building an EmberJS application and I have a route, containing multiple components (e.g. messages and questions).
Each component is extended with a dyn-widget mixin that allows it to be dragged by the user across the screen, as they are presented to the user as windows on a canvas.
To store the information about each dyn-widget window, I use three Ember Data models: a dyn-widget that stores values shared by any widget (position, size...), and a dyn-widget/questions and a dyn-widget/chat that contain additional information about how the widget is set up and a one-to-one relationship to the 'parent' dyn-widget model.
The route has a corresponding controller that allows the user to instianciate new windows. The controller then creates a dyn-widget/whatever model along with the associated dyn-widget model, and everything is stored in the local storage.
However, when I try to implement the model() function into the route, I've found that I can't really return multiple, different models without hacky solutions. I thought of putting the models directly into my components but that breaks the following pattern:
Components are isolated entities that consume data through their
interface, react to data changes that flow via data binding, and
possibly send up named actions.
So how should I approach this? What would the a non-hacky, Ember way to this issue?
Thanks!
It is not true that model in ember by convection cannot return multiple models for a route. If you look at the documentation
https://guides.emberjs.com/v2.6.0/routing/specifying-a-routes-model/#toc_multiple-models
you can use Ember.RSVP.hash to resolve multiple resources
sample router code:
model(params) {
return Ember.RSVP.hash({
messages: this.store.findAll('message'),
questions: this.store.findAll('question')
});
},
In this way you can feed your components with data from multiple models directly from router - so the pattern is that components gets the data and send the actions to route or controller.
This is a standard way of doing things and it was in ember from 1.x release
I hope this will help you.

Reuse ember controller for two routes, but pre populate part of model on one of the routes

Lets say we were modelling repositories in an app. We would want to be able to see all of our repo's at /repos/.
We can create a repo at /repos/create. In our model a repo has a owner (user), going down this route I would want my first form field in my create.hbs template to be selecting a user.
But it may also make sense to create a repo through viewing a user at /users/userId/repos, and then perhaps the route being /users/userId/repos/create. The difference here would be that the first field of my form would have the user pre populated.
I know that I can reuse the same controller by specifying this in my route:
controllerName: 'repos/create'
(what would the ember generate statement/ the place to put this route in my folder structure be, for this complex route?)
But how would I pass in the user to the repos/create controller?
The comment by #Artych provided the best way to do this currently, so to expand on this.
In the end I created a repo-create component that may or may not be passed a user object. The logic for handling what to pre-populate can then be set in the logic of the component itself.
After this is was simply a case of having a one line template at both /repos/create.hbs and /user/repos/create.hbs, with the component.
I then followed the approach of using my user as my model in the routes/user/repos/create.js route and passing it to the component in my template as
{{repo-create user=model}}
With this approach I avoided doing anything explicitly on controllers, and moving up to Ember 2.x will be alot less painful.

request process in emberjs

I am new guy to emberjs, I want to know flow of request in emberjs.
There are some query related to emberjs:
what is specific role of controller in emberjs, wherever I have seen we can create action in template.
There is standard naming convention and association between controller, routes and view, but how can to associate a controller, routes and views.
how to flow control when a request process?
a routes handover control to a controller or
a controller handover control to routes.
if we want to associate a controller and a routes manually then how to associate.
what is specific role of controller in emberjs, wherever I have seen
we can create action in template.
Controller is connecting your model with view like in MVC pattern. In Ember.JS you can use controller to keep your logic that will be used on one particular module, manage dependencies or store conditions. In templates you can use only simple conditions (without and/or), so whenever you need some more complex condition you should put in inside controller. For instance
App.PersonController = Ember.ObjectController.extend({
isPersonRich: function() {
return #get('person.money') > 1000000 && #get('person.isReal')
}.property('person.money', 'person.isReal')
});
So I person that is not fictional and have more 1 000 000 assets is rich.
{{#if isPersonRich}}
<p>This person is rich!</p>
{{/if}}
There is standard naming convention and association between
controller, routes and view, but how can to associate a controller,
routes and views.
Route usually fetch data from your backend.
App.PersonRoute = Ember.Route.extend({
model: function(params) {
this.store.find('person', params.person_id);
}
});
Each time when you enter the persons route ember is going to make call to your api (using ember data in this case) to find given person. Moreover, it's going to display loading route in this case and give you some fallback after failure.
PersonView would be the place where you can put your jQuery code that is going to be executed after template would be successfully rendered.
App.PersonView = Ember.View.extend({
didInsertElement: function() {
this.$().find('*[data-toggle=tooltip]').tooltip();
}
});
In this example I am adding bootstrap tooltip to template.
how to flow control when a request process?
Route is procceded before controller, you have even setupController method inside each route that set model to the controller by default.
if we want to associate a controller and a routes manually then how to
associate.
You can overwrite setupController method and eventually renderTemplate. There is nothing more to do. I advice you to stick to ember name conventions.
Additionaly take a look that if your controller is not going to handle fired action it is going to propagate to your route.

Why does ember use the same controller instance on separate visits to a route?

Problem:
I have a conceptual problem with re-using an old controller instance when the user re-enters the route. In many examples, the controller stores the state of user interaction in instance variables. When the user re-enters the controller those should be reset, but there is no clear mechanism for doing so.
So, there must be a good reason to use a single controller instance. What is it?
How I arrived at this issue:
I ran into a bug in out Ember app, where the controller keeps local state that got out of sync based on the user actions elsewhere. The controller maintains a state whether the user is editing the "name" of a "case". When the controller is instantiated, that is set to "true", but when the user cancels, it's set to "false". Then the user goes away to a different route and comes back to the same route. I get the same controller instance that already has "false" for editing the name. I would expect to have "true" instead.
Ember v.s. Rails:
I'm coming from Rails perspective where controller is instantiated for every request. There is no shared controller state between requests. All instance variables are local to the request, so they can be used safely.
Back to the question:
Why does ember use the same controller instance on separate visits to a route?
The role of a controller in ember and in rails are very different. In rails, as you know, the controller is used as a conduit for the request to prepare the data for presentation. If you consider the use case in ember, this is a role that is primarily played by the router.
In your example you mention that the edit state has gotten out of sync. You can solve this problem in a variety of ways, for example you could move the edit state to the model, so instead of isEditing, you'd call model.isEditing (or just isEditing if you are using an ObjectController). Another option would be to reset the state of the controller when entering the route.
Another example which should illustrate this difference is a save button on a form. Initially you might think "Oh, I'm going to put a save action on my controller". This is logical, when you're thinking like a rails developer, but saving a model is the responsibility of the router (especially if it results in changing the current route as this is something that's intentionally hard to do from a controller).
In ember the role of a controller is as a conduit between the model and the view. The fact that controller instances are re-used is a little confusing, but really isn't that important. When you change or return to a route the model/context of the controller is different, and that is where the state of the view should live... in the model.
Although controllers are usually singleton, when you use {{render}} with a specified model you will get a newly instantiated controller. See rc2 release notes under "Using Render with Multiple Models". I'm new to Ember and this difference confused the heck out of me.
Using a singleton controller when trying to keep per-model-instance user interaction state seems tricky. As jonnii said, you can put the state on the model, and that works but it cruds up the data model. The other thing I have tried is to put a map in the controller (keyed by model, value is an object of user interaction state) but this seems like a clumsy reimplementation of something I'd expect the framework to provide. Still working on this...