I'm sending a nested model as JSON to a Marionette app. It looks something like this:
{
"Course1": [
{
"id": 51,
"title": "Assignment1",
"created_at": "2013-09-01T08:47:37.908+09:00",
"updated_at": "2013-09-09T20:53:00.193+09:00",
},
{
"id": 52,
"title": "Assignment2",
"created_at": "2013-09-01T09:11:40.547+09:00",
"updated_at": "2013-09-09T20:52:37.630+09:00",
}
],
"Course2": [
{
"id": 19,
"title": "Assignment1",
"created_at": "2013-08-08T22:49:26.621+09:00",
"updated_at": "2013-09-09T20:48:20.015+09:00",
},
{
"id": 20,
"title": "Assignment2",
"created_at": "2013-08-08T23:03:58.131+09:00",
"updated_at": "2013-09-09T20:47:53.997+09:00",
}
],
"Course3": [
{
"id": 29,
"title": "Assignment1",
"created_at": "2013-08-18T09:22:32.299+09:00",
"updated_at": "2013-09-09T20:47:32.971+09:00",
},
{
"id": 30,
"title": "Assignment2",
"created_at": "2013-08-18T09:33:16.882+09:00",
"updated_at": "2013-09-09T20:02:08.731+09:00",
}
]
}
I'm wondering if there is some way to display each "course" and the data nested within the courses as a table in a Marionette view. I don't know how many courses I'll be sending to Marionette on any given request.
Is there some way to iterate over the data above (as a collection in the Marionette app) and dynamically make a new CompositeView for each course?
You could use Underscore each function. Something like this:
var data = <your JSON data here>;
_.each(data, function(value, key, list) {
var compView = new <yourCustomCompositeView>({ collection: value });
// render the view in whatever region you need to
});
That would create a new CompositeView for each entry in your JSON. Here's the documenation for that function: http://underscorejs.org/#each
Update
Take a look at http://jsfiddle.net/craigjennings11/D2qAC/ on a way to solve this. It might not be the cleanest, but it gets the job done. Note that I specify a custom region that overloads the open method for the layout, and then call open to attach the views to the region instead of show. show closes the region before calling open again, which would remove your previous view.
I realize now how foolish I was! What I was trying to do is part of the core Backbone.Marionette functionality.
I needed only to nest the List.Assignments CompositeView inside the List.Courses CompositeView.
#ControlPanel.module "AssignmentsApp.List", (List, App, Backbone, Marionette, $, _) ->
class List.Controller extends Marionette.Controller
initialize: ->
# This is a collection of courses with nested assignments
# as described in the original question.
App.request "courses:entities", (courses) =>
#coursesView = #getCoursesView courses
App.mainRegion.show #coursesView
getCoursesView: (courses) ->
new List.Courses
collection: courses
class List.Assignment extends Marionette.ItemView
template: "assignments/list/templates/assignment"
tagName: "li"
class List.Assignments extends Marionette.CompositeView
template: "assignments/list/templates/assignments"
tagName: "li"
itemView: List.Assignment
itemViewContainer: "ul"
# This is the important part...
initialize: ->
# Get the nested assignments for this particular course
assignments = #model.get("assignments")
# At this point assignments is a regular Javascript object.
# For this to work assignments must be a valid Backbone collection
#collection = new Backbone.collection assignments
class List.Courses extends Marionette.CompositeView
template: "assignments/list/templates/courses"
itemView: List.Assignments
itemViewContainer: "ul"
Related
I have a table with an one-to-one self relationship to represent an hierarchical structure. Below are the diagram of the Table and a few sample data:
This is the Location model code for the relationship:
class Location extends Model
{
public function location()
{
return $this->hasOne('App\Location');
}
}
I would like to query the Locations table and send a JSON response like the example below but I'm not sure how to approach the Query:
{
"id": 1,
"name": "Country",
"location_id": null
"location": {
"id": 2,
"name": "State",
"location_id": 1
"location": {
"id": 3,
"name": "City",
"location_id": 2
}
}
}
To always eager load the location relation and not worry about depth, you could add the $with property to your location model:
protected $with = ['location'];
Now when you return a location, it will have all nested sub locations loaded up:
return App\Location::find(1);
Laravel also handles returning this as JSON if it's returned from a controller method.
as pointed out by Kyslik, this presents a potential N+1 problem, potential depth along with query size and the rest of the environment should be considered when deciding to use a solution like this in production, for something like locations, it's probably not an issue and well worth the simlpicity
By nested json I mean something that keeps address data in its own "address" array:
{
"user": {
"id": 999,
"username": "xxxx",
"email": "xxxx#example.org",
"address": {
"street": "13th avenue",
"place": 12
}
}
}
instead of flat one
{
"user": {
"id": 999,
"username": "xxxx",
"email": "xxxx#example.org",
"street": "13th avenue",
"place": 12
}
}
Flat one is processed fine there using User entity and it's properties: "id", "username" and "email". It is nicely validated using symfony form feature:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username');
$builder->add('email', 'email');
$builder->add('password', 'password');
$builder->add('street');
$builder->add('place');
}
I want to have both "street" and "place" as properties in User entity, to store it all in one user table in the database, using doctrine. But the json I get comes from third party, so I can not modify it.
Is there any way of constructing the form so it can validate the json with "address" field correctly, still being able to keep all the user data in one table?
This is a pretty good question. One solution that comes to mind is making an unmapped form and binding data manually using a form event, for example:
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Make a "nested" address form to resemble JSON structure
$addressBuilder = $builder->create('address', 'form')
->add('place')
->add('street');
$builder->add('username');
$builder->add('email', 'email');
$builder->add('password', 'password');
// add that subform to main form, and make it unmapped
// this will tell symfony not to bind any data to user object
$builder->add($addressBuilder, null, ['mapped' => false]);
// Once form is submitted, get data from address form
// and set it on user object manually
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$user = $event->getData();
$addressData = $event->getForm()->get('address')->getData();
$user->setStreet($addressData['street']);
$user->setPlace($addressData['place']);
})
}
Lets say I have a simple django model:
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
When I display info from it as JSON through the django web framework I get this:
[{"id": 1, "title": "hello"}, {"id": 2, "title": "world"}]
How would I add an array title to the generated JSON? Like so:
["books" :{"id": 1, "title": "hello"}, {"id": 2, "title": "world"}]
So your client API requires the JSON to be an object instead of an array (there was a security rationale for it when using the browser built-in javascript parser to parse JSON but I forgot the reason)...
If your client API does not mind the extra fields added by PaginationSerializer, you can do:
class BookSerializer(pagination.BasePaginationSerializer):
results_field = "books"
class BookListView(generics.ListAPIView):
model = Book
pagination_serializer_class = BookSerializer
paginate_by = 9999
This will result:
{
'count': 2,
'next': null,
'previous': null,
'books': [
{"id": 1, "title": "hello"},
{"id": 2, "title": "world"}
]
}
[update]
The security reason for avoiding an array as the JSON root is JSON Hijacking. Basically, a clever hacker could override the array constructor in order to do nasty things. Only relevant if your API are answering GET requests and using cookies for authentication.
I have two simple serializers:
class ZoneSerializer(serializers.ModelSerializer):
class Meta:
model = Zone
fields = ('id', 'name')
class CitySerializer(serializers.ModelSerializer):
zone = ZoneSerializer(source='zone')
class Meta:
model = City
fields = ('id', 'name', 'zone')
So client-side receives JSON objects like:
{
"id": 11,
"name": "City1",
"zone": {
"id": 2,
"name": "Zone 2"
}
}
Now, when I receive JSON from client-side as...
{
"name": "NewCity",
"zone": {
"id": 2,
"name": "Zone 2"
}
}
... and want to POST (create) it as a new "city", I want my ModelSerializer to know that "zone" in JSON is a foreign key to Zone model, and it shouldn't be inserted to db as a new "zone".
Is there a way to achieve that? Or must I use RelatedField instead, although I want to pass and receive full detailed zone rather than just primary keys?
According to this answer by the author of django-rest-framework, the answer is no, it is not possible ("nested serializers do not currently support write operations") as of January 2013.
I've been lurking on Stack Overflow for quite some time now, and have found quite a number of very helpful answers. Many thanks to the community! I hope to be able to contribute my own helpful answers before too long.
In the meantime, I have another issue I can't figure out. I am using Sencha Touch to create a Web-based phone app and I'm having trouble using a nested loop to iterate through some JSON. I can grab the first level of data, but not the items nested within that first level. There is a somewhat related ExtJS thread, but I decided to create my own since ExtJS and Touch diverge in subtle yet important ways. Anyway, here is some code to show where I am:
JSON (truncated - the JSON is PHP/MYSQL-generated, and there are currently actually three sub levels with "title", all of which I can access. It's the sub level "items" through which I can't iterate):
{
"lists": [
{
"title": "Groceries",
"id": "1",
"items": [
{
"text": "contact solution - COUPON",
"listId": "1",
"id": "4",
"leaf": "true"
},
{
"text": "Falafel (bulk)",
"listId": "1",
"id": "161",
"leaf": "true"
},
{
"text": "brita filters",
"listId": "1",
"id": "166",
"leaf": "true"
}
]
}
]
}
Store:
var storeItms = new Ext.data.Store({
model: 'Lists',
proxy: {
type: 'ajax',
method: 'post',
url : LIST_SRC,
extraParams: {action: 'gtLstItms'},
reader: {
type: 'json',
root: 'lists'
}
}
});
Working Loop:
storeItms.on('load', function(){
var lstArr = new Array();
storeItms.each(function(i) {
var title = i.data.title;
lstArr.push(i.data.title);
});
console.log(lstArr);
});
Non-working Nested Loop:
storeItms.on('load', function(){
var lstArr = new Array();
storeItms.each(function(i) {
var title = i.data.title;
var id = i.data.id;
title.items.each(function(l) {
lstArr.push(l.data.text);
});
});
console.log(lstArr);
});
The non-working nested loop gives me the error "Cannot call method 'each' of undefined", in reference to 'title.items.each...'
I suspect this is because I've not set title to be a key to set up a key:value pair, so it just sees a list of strings...but I'm kind of at a loss.
I should mention that the store is populated via two Models that have been associated with one another. I know that the Store can access everything because I am able to do nested iterating via an XTemplate.
Any help will be much appreciated and hopefully returned to the community in kind before too long!'
-Eric
Eric, why the loop?
If your models are associated in the same way that the JSON is nested, then you should just be able to set autoLoad:true on the store, sit back and enjoy.
Anyway, on the assumption that you are needing these arrays for some other unrelated reason, the problem is that you are trying .each on
i.data.title.items
Surely you should be iterating through
i.data.items
Also, if the object is a model, you can use .get() instead of the data object:
var title = i.get('title);
Using new sencha touch 2 framework, you can create associations within the models exactly the same way how your json is returned.
Check Sencha Touch 2 Model Document which tells you the various config options on Model.
You may refer to this example of ST2 Nested List .
Hope this helps.
"title" is not a enumerable object, its a string. To iterate a string you'll need to split it to convert it into an array.
Also, instead of using Ext.each try a simple for (var x in obj) {} or for (var xc in obj.prop) {} If that works then the ext.each method should work as well but if ext cannot iterate the object it will just quietly fail.