I want to document once and for all for myself (and keep a permanent record!) of the relationship between parents and children and the ideal json structure, along with basic examples of routes and controllers.
As I (hopefully) get answers and comments I will update the question to reflect best practice.
So, I have my ember models :
App.Customer = DS.Model.extend({
name: DS.attr('string' ),
orders: DS.hasMany("order")
});
App.Order = DS.Model.extend({
carrier: DS.attr('string' ),
customer: DS.belongsTo("customer")
orderlines: DS.hasMany("orderline")
});
App.Orderline = DS.Model.extend({
order: DS.belongsTo("order"),
item: DS.belongsTo("item"),
price: DS.attr('number'),
qty: DS.attr('number'),
});
App.Item = DS.Model.extend({
name: DS.attr('string'),
orderlines: DS.hasMany("orderline")
});
Question 1: are these model definitions correct ?
There are several approaches that I could take to viewing this data:
Customer Tab | Orders Tab | Order lines tab
Customer page -> Orders Page -> Order lines page
Treeview
Can anyone suggest any other JS widgets that could display this hierarchical data ?
Question 2:
what are the router / controllers that are required for each of these ?
I have option 2) working as far as displaying the customer, but when I click on the orders link for the customer I am getting all orders. It could be that my json is wrong .. or more likely I don't know how to display all the orders of the selected customer. Or both :(
I currently have :
{"customers":[
{"name":"foobar inc","id":"0x181","orders":["0x386","0x3a4"]},
{"name":"barfoo ltd","id":"0x182","orders":["0x3de","0x3fd"]} ],
"orders":[
{"carrier":"Standard Mail","id":"0x386","customer_id":"0x181"},
{"carrier":"FlyByNight Courier","id":"0x3a4","customer_id":"0x181"},
{"carrier":"Standard Mail","id":"0x3de","customer_id":"0x182"},
{"carrier":"FlyByNight Courier","id":"0x3fd","customer_id":"0x182"} ]}
Question 3: is this correct ? (I can make the json come out in any format, so it's probably best to create the json structure that best suits ember-data).
Question 4: should I be including related data at this point (so all customers, all orders, all orderlines)? Or would it be better not to include the child data, but get these on demand from the server ?
I'll leave it for now - hopefully I can start to make sense of the nested data soon ! thanks.
Question 1 Those model definitions look good to me.
Question 2 You may want to end up with a mixture of your options #1 and #2. Tabs to let you see the entire list of each model, but with the ability to drill down hierarchically, on the /customers page, for instance. The exact routes that you need depend on the exact URLs that you want to have available in your app which will correspond to the screens/views that you want to show.
Let's say that you wanted these URLs/screens
/customers - The list of all customers
/customers/1 - The details about customer #1
/customers/1/orders - All orders for customer #1
/customers/1/orders/1 - The details for order #1 (including OrderLines)
Then your routes would be:
App.Router.map(function() {
this.resource('customers');
this.resource('customer', { path: '/customers/:customer_id' }, function(){
this.resource('orders');
this.resource('order', { path: '/orders/:order_id'});
});
});
Question 3 Yes, that JSON looks correct.
Question 4 That depends on the needs of your app. Probably you don't want to include the entire tree of data in a single request (Customers -> Orders -> OrderLines -> Items). You probably want to progressively load things as the user goes down a tree.
For instance you'd want to load just list list of customers at first, and then when the user clicks on a customer, you'd want to fire a request to get all of the orders for that customer. And so on, down the tree.
Here's a JSBin showing the general idea : http://jsbin.com/ucanam/1074/edit
Note that the hasMany relationships are defined with {async:true}. This allows them to be looked up on demand, instead of loaded with the parent model.
If you switched to the RESTAdapter, when it tries to load the list of orders for a customer it would make a request like :
/orders?ids[]=0x3de&ids[]=0x3fd
[UPDATE] : In response to the comments.
So, for the url structure that you requested : > >List of customers > -> customer details > -> list of orders > -> order details > -> list of order lines > -> order line details
You are very close with your JSBin. The thing that's tripping you up is the way that nesting of templates works.
The 'order' template, if it exists, is rendered for any and all routes that match /orders/xxx or /orders/xxx/*. If there are other parts of the route like /orderlines those templates get rendered into the 'order' template. But, since your 'order' template doesn't have an {{outlet}} there is nothing for the 'orderlines' template to render into.
Here's a slightly modified JSBin : http://jsbin.com/iKIsisO/3/edit
The only change there is the addition of the {{outlet}} to the bottom of the 'order' template.
Now, rendering orderlines below, or otherwise inside of the main order detail may not be what you want. Most likely you want the orderlines to replace the other order info. In this case you can rename the 'order' template to be 'order/index'. (You can also remove the OrderRoute and the needs from the OrderController).
Another JSBin, with the template renamed : http://jsbin.com/iDiMOCO/1/edit
So, what's happening here?
Using the Order model as an example, when you visit the /orders route, and any other routes that apply to the collection (/orders/new, /orders/some_custom_batch_thing), the main 'orders' template is rendered if it exists, then the sub path templates are rendered into 'orders'. If the 'orders' template does not exist, then the sub-path templates render into the {{outlet}} that is immediately up the chain. The /orders route is kind of a special case in that the sub-template for it is implicitly assumed to be orders/index. Similarly, with /orders/xxx and any other routes that apply to a single Order, the 'order' template is rendered first (if it exists), then the sub-path templates are rendered, either into 'order', or into the most immediate parent {{outlet}}. Also with /orders/xxx the 'order/index' template is the implicit sub-path template.
/orders :: 'orders' -> 'orders/index'
/orders/new :: 'orders' -> 'orders/new'
/order/xxx :: 'order' -> 'order/index'
/orders/xxx/edit :: 'order' -> 'order/edit'
So, the 'orders' and 'order' templates are really kind of like per-model-layout templates that can be used to decorate the sub-paths in a consistent way.
Final JSBin with template names rendered in the template and a few extra templates added to act as "model layouts": http://jsbin.com/iKIsisO/2/edit
I hope that all makes sense.
Related
Is there currently (in the latest builds) a way of specifying a URL on a model-by-model basis? in Ember Data 1.0 beta? I have found some questions on SO and issues on Github around this, but most are out-dated.
For example, I have a model that's called App.PaymentSearchResult and rather than having the request go to /payment_search_results I would like it to go to /payments/search. Where would I override the URL used for a given model (rather than overriding buildURL on the RESTAdapter)?
You can override the the find adapter
but it's kind of hackish, i think however i would take another approach. Idealy you want your Ember models to reflect your backend's models, so why would you need a PaymentSearchResult? When you probably already have a Payment model?
If you need to search in your payment records, why not handle it using query params?
http://emberjs.com/guides/models/finding-records/#toc_querying-for-records
this.store.find('payment', { total: "22" });
Then you want to answer accordingly on the server.
If you want to do a search which returns multiple models, you do this with a manual ajax request.
var self = this;
$.get( "/search", { name: "John", time: "2pm" }, function(result) {
self.store.pushMany(result);
});
PushMany assumes a sane JSON structure.
http://emberjs.com/api/data/classes/DS.Store.html#method_pushMany
i'm trying to implement the very common list/detail pattern, like the tables in the peepcode example or the blog posts in the recent tom dale screencast. Only in my case the first item should be selected and the details shown when you enter the common /items route. So when you go to /items, it should automatically change the url to /items/1 and display the list as well as the details.
This is what i tried:
App.ItemsRoute = Ember.Route.extend({
model: function () {
return App.Item.find();
},
redirect: function () {
if(Ember.isEmpty(this.modelFor('item'))) {
firstItem = this.model().get('firstObject');
this.replaceWith('item', firstItem);
}
}
});
Complete example:
http://jsfiddle.net/ralph/zeKH9/5/
Problem is, when you add the redirect to the items route, the list of items is not displayed any more, only the item details (try removing the redirect part in the ItemsRoute to see what I mean).
So, what's the best way to achieve this?
When you redirect, you need to redirect from one route to a different route.
item is nested inside items. So I guess Ember.js is getting confused, because transitioning to item does not mean leaving items.
Instead of redirecting from items to item, you should redirect from items.index to item.
item is included in items which means items -> item does not make much sense.
item is not included in items.index which means items.index -> item is a different route, and so should work.
Updated fiddle
It seams simple, but I'm stuck on this one. I have a single controller, and single view dedicated to order model, which has nested client. I create empty records on setupController:
route:
ShowroomApp.OrdersRoute = Ember.Route.extend
model: ->
ShowroomApp.Order.createRecord()
setupController: (controller,model) ->
model.set("client", ShowroomApp.Client.createRecord() )
controller.set("content", model )
controller:
save: ->
#content.store.commit()
On OrdersController i have save action to commit changes made in the form. It results in two separate POST requests each one for each model, but the association doesn't build itself. Orders model saves first, and client_id is obviously null because client doesn't exists yet. Later goes Client post and saves the client, but Order model doesn't know about it and stays without client.
Is there any solution to that?
Thanks,
J
This is due to an outstanding ember-data issue - RESTAdapter: Allow new parent, child to be saved at once
Check out Tom Dale's Comment for a possible workaround. It involves manually adding support for saving both records at once.
As an alternative, you might consider adding a onCreate callback to the parent record, then creating the child in the callback. If you are ok with having a second commit() this will get the job done.
I'm exploring the possibility of using MVC for my next e-commerce site. One thing I can't seem to figure out is whether or not I can use the same URL convention I normally use. Currently, the URL for any product could be one of the following:
Category/SubCategory/Product1.html
Category/SubCategory/SubSubCategory/Product2.html
Category/SubCategory/SubSubCategory/Product3.html
Category/SubCategory/SubSubCategory/SubSubSubCategory/Product4.html
etc.
The issue I'm having is with the nested category structure. So far the only thing I've come up with is as follows:
routes.MapRoute(
"Products",
"{categories}/{productname}",
new { controller = "Product", action = "Details", productname = UrlParameter.Optional },
new { categories = #"\w+/\w+" }
);
I was hoping that {categories} could be matched with any one of the following which I could process to identify the right category that the product belongs to:
Sport/Tennis/Rackets/ProductA
Sport/Badminton/Rackets/ProductB
But the route shown above doesn't work correctly.
Does anyone know how this can be achieved, or if it can't be done?
The routing system allows you to define catchall parameters, which ignore slashes and capture
everything up to the end of a URL. Designate a parameter as being catchall by prefixing it with an
asterisk (*).
routes.MapRoute(null, "Articles/{*articlePath}",
new { controller = "Articles", action = "Show" }
);
You can only have one catchall parameter in a URL pattern, and it must be the last (i.e.,
rightmost) thing in the URL, since it captures the entire URL path from that point onward.
One Caveat though, it doesn’t capture anything from the query string as route objects only look at the
path portion of a URL.
Catchall parameters are useful if you’re letting visitors navigate through some kind of arbitrary
depth hierarchy, such as in a content management system (CMS).
You can use the RouteData object to extract information about the route. For your needs, you would probably create a custom route handler that parses the route data and calls the correct controller methods.
You need access to the individual segments of the URL so you need to divide the category segment into two segments. That would make it much easier.
Let's say we call Tennis and Badminton categories and Rackets within those categories as a product class
You need a way to access the category, productClass and productName parameters. Supposing that "Sport" is fixed in this case, I will do it like this:
routes.MapRoute(
"Products",
"sport/{category}/{productClass}/{productName}",
new { controller = "Product", action = "Details", productClass = UrlParameter.Optional, productName = UrlParameter.Optional }
);
Your action method will be something like this
public ActionResult Details(string category, string productClass, string productName){
//Do whatever you need to do in order to get the specified product
}
You could use Areas in MVC2
So it would read:
Area/Controller/View/id
So in your case it would end up being:
Sport being the area,
Tennis The controller,
Rackets the view,
ProductA being an ID or querystring,
http://www.asp.net/mvc/videos/aspnet-mvc-2-areas
Hope this makes sense.
I have a fairly complex relationship that I am trying to make work with the Django admin site. I have spent quite some time trying to get this right and it just seems like I am not getting the philosophy behind the Django models.
There is a list of Groups. Each Group has multiple departments. There are also Employees. Each Employee belongs to a single group, but some employees also belong to a single Department within a Group. (Some employees might belong to only a Group but no Department, but no Employee will belong only to a Department).
Here is a simplified version of what I currently have:
class Group:
name = models.CharField(max_length=128)
class Department
group = models.ForeignKey(Group)
class Employee
department = models.ForeignKey(Department)
group = models.ForeignKey(Group)
The problem with this is that the Department select box on the Employees page must display all Departments, because a group has not yet been set. I tried to rectify this by making an EmployeeInline for the GroupAdmin page, but it is not good to have 500+ employees on a non-paginated inline. I must be able to use the models.ModelAdmin page for Employees (unless there is a way to search, sort, collapse and perform actions on inlines).
If I make EmployeeInline an inline of DepartmentAdmin (instead of having a DepartmentInline in GroupAdmin), then things are even worse, because it is not possible to have an Employee that does not belong to a Group.
Given my description of the relationships, am I missing out on some part of the Django ORM that will allow me to structure this relationship the way it 'should be' instead of hacking around and trying to make things come together?
Thanks a lot.
It sounds like what you want is for the Department options to only be those that are ForeignKey'ed to Group? The standard answer is that the admin site is only for simple CRUD operations.
But doing what you're supposed to do is boring.
You could probably overcome this limitation with some ninja javascript and JSON.
So first of all, we need an API that can let us know which departments are available for each group.
def api_departments_from_group(request, group_id):
departments = Department.objects.filter(group__id=group_id)
return json(departments) # Note: serialize, however
Once the API is in place we can add some javascript to change the <option>'s on the department select...
$(function() {
// On page load...
if ($('#id_group')) {
// Trap when the group box is changed
$('#id_group').bind('blur', function() {
$.getJSON('/api/get-departments/' + $('#id_group').val() + '/', function(data) {
// Clear existing options
$('#id_department').children().remove();
// Parse JSON and turn into <option> tags
$.each(data, function(i, item) {
$('#id_department').append('<option>' + item.name + '</option>');
});
});
});
}
});
Save that to admin-ninja.js. Then you can include it on the admin model itself...
class EmployeeAdmin(models.ModelAdmin):
# ...
class Media:
js = ('/media/admin-ninja.js',)
Yeah, so I didn't test a drop of this, but you can get some ideas hopefully. Also, I didn't get fancy with anything, for example the javascript doesn't account for an option already already being selected (and then re-select it).