Best Practices for List View to Detail View


#1

I apologize for the general, simplistic Topic title. I really didn’t know how to boil down my inquiries into a single succinct title. But I am looking for the best way to accomplish what I’m about to describe. :slight_smile:

First, Ember is not my “native language”, as it were, so any guidance will be greatly appreciated. Additionally links to online resources will be super helpful.

Second, I’m trying to write an application that has 2 ways into an editor view: 1) via a “create new widget” button, and 2) via a list of widgets where a person can select one to load up the editor.

There is one caveat: the editor needs some data from an API to populate some drop-down lists.

What’s the best way for me to accomplish this?

Thanks, in advance!


#2

Hi Mel, as you mentioned it’s a pretty broad question and depends somewhat on exactly what you want to accomplish but I’ll try to give you some broad strokes for design considerations.

First you may want to determine your routing structure. It sounds like you’ll have two basic views: create/edit widget, and list widgets.

The first question you may want to ask yourself to figure this out is “do I want any of the UI to be nested?”. For example if you wanted the list view to remain rendered when the detail view is rendered (like list view to left, detail to right) you’d probably want to nest your routes. If not you would probably want to keep those routes at the same level in the router.

One thing I would suggest considering is actually NOT reusing the route for the create/edit. It’s not uncommon or super weird to do that, however it’s a bit cleaner in terms of the router if you have a “detail” route, and then a separate “new” route. Both routes can render the same “widget editor” component though, so you can still reuse most of the code.

So let’s say you go with three routes, no nesting. You could do something like this in your router.js:

// this is the widgets list
this.route('widgets');
// this is the widget detail
this.route('widget', { path: '/widgets/:id' });
// this is the create widget view
this.route('createwidget', { page: '/widgets/new' });

The model hook for the “widgets” route would probably return a findAll(‘widget’), the model hook for the detail route (“widget”) would probably return a findRecord(‘widget’, params.id) and the model hook for the “createwidget” route would probably return a store.createRecord(‘widget’). The detail and create routes could both render the same widget editor component which edits the widget, and then when you successfully save the record in the createwidget route you could transition to the widget detail with the id of the newly saved widget.

You also mentioned that the editor needs to fetch some other data to populate some dropdowns. This could either be done in the routes (and you could return an rsvp.hash from the routes OR the more controversial and more advanced approach would be to load the data in the editor component with ember concurrency. If you’re starting out with ember I’d recommend just doing it in the routes.

Anyway, sorry that was a little rambly. That’s just one approach, there are multiple considerations to make when choosing your route structure, but generally if you nail that part the rest should be pretty straightforward. Feel free to post any follow up questions if you’d like help with the specifics.

I’d also highly recommend reading through the ember guides if you haven’t, and maybe check out Balint Erdi’s Rock’n’Roll with Ember book. It’s a great resources for learning the framework while creating a real-world-type app and there would probably be a lot of good conventions and tips you could pick up.


#3

Thank you very much for your “rambly”, yet detailed response. I hadn’t thought of splitting the create and edit routes into 2 routes. So that was spot on.

I can google the rsvp.hash technique too. However, from the description, does that mean ‘model’ becomes a Hash? and that I should do something like the following:

{ 
  widget_to_edit => findRecord('widget', params.id),
  list_of_options => findAll('widget_color')
}

(or something like that) And then, presumably, in the view, instead of using just ‘model’ it would be ‘model.widget_to_edit’.

Do I have the gist of it?


#4

Yup I think you’ve got it. Basically it’s a way to jam multiple promises together with names. My personal preference is to just use it every time because I almost always have multiple models in any real-world situation.

So, as an example let’s say you had a route that needed to load grab some state from the application route (like the logged in user model) and also a widget detail, in the route you’d do something like:

import { hash } from 'rsvp';
...

export default Route.extend({
  ...
  model(params) {
    // grabs the fictional "my logged-in user" from the application route model
    let me = this.modelFor('application').me;

    return hash({
      me,
      widgetToEdit: this.store.findRecord('widget', params.id)
    });
  },
  ...

Then in the template you would (as you mentioned above) reference this data via {{model.me.name}} or {{model.widgetToEdit.type}}, etc. I really like referencing things this way because it makes it very explicit in the template what is coming from the model vs other properties or computed properties defined on the controller.

Anyway, sounds like you’ve got the hang of it!