8
votes

I am trying to figure out what the correct Ember.js way to model this project would be, eg. what models, routes and controllers would be needed. I have started a jsBin to work from.

My requirements can be safely reduced down to:

Items & their Options

  • Items have a collection of options
  • Options have their own properties
  • Items have other properties (beside the options) that the dashboard will use

Dashboard

  • The dashboard does not have any data of it's own
  • The dashboard needs to observe all Items and Options, and update an analysis of their properties

Navigation

  • Virtually none
  • This will appear on one 'page', but a small number of pages/popups may be added in the future
  • I want to be able to save and repopulate a given state (eg, a list of selected Option ids)

Data

  • The data will be loaded once with a single json call
  • Application logic will be done solely clientside within Ember - no ajax for the business logic
  • The only subsequent contact w/ the server will be if/when the user saves the state

So how would this be structured in Ember?

I've tried to do this once on my own, but it was my first try and I ended up with a pretty ugly setup. I would like to see how someone with Ember experience would approach this:

jsBin Mockup (link)

I've created a series of handlebar templates, but have not taken a stab at what models should exist and what controllers are needed.. Screenshot of jsBin mockup

Json

{
  "Items" : [
    {
      "Item" : {
        "nid" : "3",
        "title" : "Hydro",
        "image" : "http://bpf.vm/sites/default/files/bpf_things/hydro.jpg",
        "properties" : "Baseload, Intermittent",
                "values" : {
                    "Cost" : {
                        "price" : "6",
                        "quantity" : null
                    },
                    "Percent of Portfolio" : {
                        "price" : null,
                        "quantity" : "56"
                    }
                },
                "options" : {
                    "1" : {
                        "price" : "1512",
                        "quantity" : "10000"
                    },
                    "12" : {
                        "price" : "825",
                        "quantity" : "20000"
                    },
                    "11" : {
                        "price" : "550",
                        "quantity" : "50000"
                    }
                }
      }
    },
    {
      "Item" : {
        "nid" : "4",
        "title" : "Nuclear",
        "image" : "http://bpf.vm/sites/default/files/bpf_things/nuclear.jpg",
        "id" : "",
        "properties" : "Baseload, Predictable",
                "values" : {
                    "Cost" : {
                        "price" : "8",
                        "quantity" : null
                    },
                    "Percent of Portfolio" : {
                        "price" : null,
                        "quantity" : "21"
                    }
                },
                "options" : {
                    "4" : {
                        "price" : "825",
                        "quantity" : "10000"
                    },
                    "13" : {
                        "price" : "411",
                        "quantity" : "15000"
                    }
                }
      }
    },
    {
      "Item" : {
        "nid" : "5",
        "title" : "Natural Gas",
        "image" : "http://bpf.vm/sites/default/files/bpf_things/gas.jpg",
        "id" : "9",
        "properties" : "Baseload, Predictable",
                "values" : {
                    "Cost" : {
                        "price" : "5",
                        "quantity" : null
                    },
                    "Percent of Portfolio" : {
                        "price" : null,
                        "quantity" : "24"
                    }
                },
                "options" : {
                    "7" : {
                        "price" : "400",
                        "quantity" : "50000"
                    },
                    "10" : {
                        "price" : "600",
                        "quantity" : "100000"
                    }
                }
      }
    }
  ]
}
2
Can you be more specific as to what you mean by modeling this? You mean how you'd design the controllers, urls, etc? Or do you mean data models? If the former, seeing your 'ugly' setup would be helpful.EmptyArsenal
Updated post w/ more details, links to jsBin (templates only - having trouble porting my current work), and updated screenshot.doub1ejack
very helpful conversation on how to structure this app with Conrad: chat.stackoverflow.com/rooms/41721/…doub1ejack

2 Answers

3
votes

I put up a small JSBin http://jsbin.com/IdAXuMar/5/edit

Okay, so after having a chat and a bit of a longer look at it, here are my thoughts on how to simplify this:

You only have one URL, therefore i would go with only one Route and one Controller for now.

The datamodel is quite simple because it is completely hierarchical:

a Display has many Items, an Item has many Options

And because you only ever look at one Display at a time, you do not need the Display as a model at all. If your application evolves and you have multiple Displays at once, it makes sense to implement a Display model, though, and do all JSON requests through that model.

I would implement a single Route and Controller:

App.Router.map(function() {
     this.resource('display', path: { 'display/:id' });
});

App.DisplayRoute = Ember.Route.extend({
    model: function(params) {
        return App.Item.find(params._id);
    }
});

the DisplayController has full access on all it's items, as they are set as it's model.

I think You only need one template for now, you can split these into multiple partials later if it's growing out of control.

<script type="text/x-handlebars" data-template-name="display">    
  {{#each model}}
    <!-- access on every item here -->


    {{#each option}}
      {{#if isSelected}}
        this option is selected
      {{/if}}
      <!-- access on every option here -->

      <a {{action selectOption this}} href=''> Select this option</a>

    {{/each}}
  {{/each}}
</script>

Note the selectOption action: when calling this and passing the option, you can then set the selected state directly on the option itself, which will immediately reflect in the view.

App.DisplayController = Ember.ArrayController.extend({
    // add computed properties here..

    actions: {
        selectOption: function(option) {
            option.set('isSelected', true);
        }
    }

});

To get the items from the server, you could call App.Item.find() and then pass the display's id. That is not a 100% conventional, as you would be expected to pass the Item's id here, but i think for this purpose it's okay. so this method would look something like

App.Item = Ember.Option.extend({
    selected: false
    // add computed properties here
});

App.Item.reopenClass({

    // retrieves the items from the server

    find: function(displayId) {

        var url = "/game/json" + displayId;

        var items = new Ember.A();

        Ember.$.getJSON().success(function(data) {
            data.items.forEach(function(jsonItem) {

                var item = Ember.Item.create({
                    nid: jsonItem.nid,
                    title: jsonItem.title,
                    image: jsonItem.image
                });

                item.set('options', new Ember.A());

                jsonItem.options.forEach(function(option) {
                    var option = Ember.Option.create({
                        // set option properties
                    });
                    emberItem.get('options').pushObject(option);
                })

            })

        });

        return items;
    }
});

I hope that helps you get started and perhaps makes it a little easier to transfer your concept to Ember. If you have questions regarding for example how to save everything back to the server, shoot :)

2
votes

Here's the beginnings of an answer:

Models

I think I only need three models here. The dashboard is a major player in this app, but it does not have any data of it's own.

  • Item Model - holds all info for an item
  • Option Model - holds all info for an option
  • Display Model - holds a set of selected option ids which can either be sent to the server & saved, or can be used to return the app to a specific state

Controllers

Earlier I was totally missing the concept of ArrayControllers. Generally, anything that is a collection will need an ArrayController to represent it, rather than a plain ember ObjectController. My 'Items' will need one, but I don't think 'Options' will because Options are children of Item, and can use Item/Items as a proxy.

  • Dashboard - I'm guessing this is going to be the beefy one since the controller needs to process all items & collections
  • Items - since there are many items, we will need an ArrayController for them
  • Item - the item needs to do some simple analysis of it's options when their state changes
  • Option - options will, at a minimum, need to respond to click actions

Templates

The indentation here represents templates that render other templates. For instance my Display template contains {{render dashboard}} and {{render items}}.

  • Application - technically the app root, it redirects to the Display (which is probably not necessary)
    • Display - essentially the root of my app.
      • Dashboard - the area that provides visual analysis of Items/Options
      • Items - renders each item
        • Options - renders the options for each item

Routes

This is still very hazy. Routes seem to play many roles (mapping urls to models, setting models for controllers, maybe other stuff??). At the moment the only url I can think of needing is:

  • Display - since my 'display' represents an application snapshot (eg, a saved version), it needs to be specified in App.Router.map

Other routes:

  • ApplicationRoute
    • setupController: sets the controller to a blank/saved display
  • IndexRoute
    • redirect: just redirects to the display route (essentially the root of the app)
  • DisplayRoute
    • model: sets a given display as the model
    • afterModel: load up the items specified by the display

I think that's all. This is a simple app and once I have the Items loaded for a Display then the app just changes the display for the screen. There are user selections, but they are boolean flags (eg setting isSelected on an Item should alter the data shown by the dashboard) - these selections don't require any navigation.