diff --git a/.jshintrc b/.jshintrc
index 8fe58ca6..a9a9e7ee 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -4,7 +4,7 @@
"esnext": true,
"bitwise": true,
"camelcase": true,
- "curly": true,
+ "curly": false,
"eqeqeq": true,
"immed": true,
"indent": 2,
@@ -14,7 +14,7 @@
"regexp": true,
"undef": true,
"unused": true,
- "strict": true,
+ "strict": false,
"trailing": true,
"smarttabs": true,
"globals": {
diff --git a/doc/Configuration-reference.md b/doc/Configuration-reference.md
index ba12ab07..282ccc2f 100644
--- a/doc/Configuration-reference.md
+++ b/doc/Configuration-reference.md
@@ -379,6 +379,7 @@ A field is the representation of a property of an entity.
* [`file` Field Type](#file-field-type)
* [`reference` Field Type](#reference-field-type)
* [`referenced_list` Field Type](#referenced_list-field-type)
+* [`embedded_list` Field Type](#embedded_list-field-type)
* [`reference_many` Field Type](#reference_many-field-type)
### General Field Settings
@@ -608,42 +609,73 @@ Some other properties are allowed, see https://github.com/danialfarid/ng-file-up
### `reference` Field Type
-The `reference` type also defines `label`, `order`, `map`, `list` & `validation` options like the `Field` type.
+The `reference` type maps a many-to-one relationship where the entity contains a foreign key to another entity. For instance, if the REST API behaves as follows:
+
+```
+GET /comments/123
+{
+ "id": 123,
+ "body": "Lorem ipsum sic dolor amet...",
+ "post_id": 456 // foreign key to post of id 456
+}
+
+GET /posts/456
+{
+ "id": "456",
+ "title": "Consectetur adipisicing elit",
+ "body": "Sed do eiusmod..."
+}
+```
+
+Then mapping the `post_id` property of the `comment` entity to a `reference` will tell ng-admin to fetch the related `post` entity, and to display the `targetField`.
+
+```js
+var post = nga.entity('posts');
+var comment = nga.entity('comments');
+comment.listView().fields([
+ nga.field('post_id', 'reference')
+ .targetEntity(post) // Select a target Entity
+ .targetField(nga.field('title')) // Select the field to be displayed
+]);
+```
+
+In a read context (`listView` and `showView`), `reference` fields render the targetField as text. In a write context (`creationView` and `editionView`), ` reference` fields render as a dropdown, allowing to select the related entity among a list using the ` targetField` as a representation. In that case, ng-admin fetches the possible values on the related entity, so a `reference` field makes an additional query in a write context:
+
+```
+GET /comments/123 <= get the main entity
+{ "id": 123, "post_id": 456, ... },
+GET /posts/456 <= get the referenced entity itself
+GET /posts?_perPage=30 <= get the possible values for the referenced entity
+```
+
+The `reference` type specializes the `Field` type, so it supports the same `label`, `order`, `map`, `list` & `validation` options. Additional options are:
* `targetEntity(Entity)`
Define the referenced entity.
* `targetField(string)`
-Define the target field name used to retrieve the label of the referenced element.
+Define the target field used to retrieve the label of the referenced element.
myView.fields([
nga.field('post_id', 'reference')
- .label('Post title')
- .map(truncate) // Allows to truncate values in the select
- .targetEntity(post) // Select a target Entity
- .targetField(nga.field('title')) // Select a label Field
+ .label('Post content')
+ .targetEntity(post)
+ .targetField(nga.field('body')) // display the body instead of the title
+ .map(truncate) // truncate the long body
]);
-* `singleApiCall(function(entityIds) {}`
-Define a function that returns parameters for filtering API calls. You can use it if you API support filter for multiple values.
-
- // Will call /posts?post_id[]=1&post_id[]=2&post_id%[]=5...
- commentList.fields([
- nga.field('post_id', 'reference')
- .singleApiCall(function (postIds) {
- return { 'post_id[]': postIds };
- })
- ]);
+* `perPage(integer)`
+Define the maximum number of related entities fetched and displayed in the dropdown of possible values in a write context. Defaults to 30.
* `sortField(String)`
-Set the default field for list sorting. Defaults to 'id'
+Set the field used to sort the list displayed in the dropdown in a write context. Defaults to 'id'.
* `sortDir(String)`
-Set the default direction for list sorting. Defaults to 'DESC'
+Set the direction used to sort the list displayed in the dropdown in a write context. Defaults to 'DESC'.
* `remoteComplete([true|false], options = {})`
-Enable autocompletion by fetching remote results (disabled by default). When enabled, the `reference` widget fetches the results matching the string typed in the autocomplete input from the REST API.
-If set to false, all references (in the limit of `perPage` parameter) would be retrieved at view initialization.
+In write context, enable autocompletion by fetching remote results as the user types (disabled by default). When enabled, the `reference` widget fetches the results matching the string typed in the autocomplete input from the REST API.
+When set to false, all references (in the limit of `perPage` parameter) are retrieved at view initialization.
comments.editionView().fields([
nga.field('id'),
@@ -656,7 +688,7 @@ If set to false, all references (in the limit of `perPage` parameter) would be r
Available options are:
* `refreshDelay`: minimal delay between two API calls in milliseconds. By default: 500.
- * `searchQuery`: a function returning the parameters to add to the query string basd on the input string.
+ * `searchQuery`: a function returning the parameters to add to the query string based on the input string.
comments.editionView().fields([
nga.field('id'),
@@ -671,8 +703,56 @@ If set to false, all references (in the limit of `perPage` parameter) would be r
.perPage(10) // limit the number of results to 10
]);
+* `singleApiCall(function(entityIds) {}`
+Group queries for the related entities in a `listView`. If that option isn't defined, adding a `reference` field in a `listView` triggers a query to the API for the related entity *for each row*.
+
+ commentList.fields([
+ nga.field('post_id', 'reference')
+ .targetEntity(post)
+ .targetField(nga.field('title'))
+ ]);
+
+ // will trigger the following queries
+ GET /comments?_page=1
+ [
+ { "id": 123, "post_id": 456, ... },
+ { "id": 124, "post_id": 457, ... },
+ { "id": 125, "post_id": 458, ... },
+ ]
+ GET /posts/456
+ { "id": 456, ... }
+ GET /posts/457
+ { "id": 457, ... }
+ GET /posts/458
+ { "id": 458, ... }
+
+ On most configurations, multiplying the requests to the REST API like that will slow down the rendering of the list a great deal. To speed things up, you can group the calls to the related entities - provided the REST API supports filters for multiple values ("WHERE ... IN"). To do so, use `singleApiCall()` to format the request based on an array of ids.
+
+ commentList.fields([
+ nga.field('post_id', 'reference')
+ .targetEntity(post)
+ .targetField(nga.field('title'))
+ .singleApiCall(function (postIds) {
+ return { 'post_id[]': postIds };
+ })
+ ]);
+
+ // will trigger the following queries
+ GET /comments?_page=1
+ [
+ { "id": 123, "post_id": 456, ... },
+ { "id": 124, "post_id": 457, ... },
+ { "id": 125, "post_id": 458, ... },
+ ]
+ GET /posts?post_id[]=456&post_id[]=457&post_id[]=458
+ [
+ { "id": 456, ... },
+ { "id": 457, ... },
+ { "id": 458, ... },
+ ]
+
* `permanentFilters({ field1: value, field2: value, ...})`
-Add filters to the referenced results list. This can be very useful to restrict the list of possible values displayed in a dropdown list:
+Add filters to the referenced results list. This can be very useful to restrict the list of possible values displayed in a dropdown list. As such, it is only used in write context.
comments.editionView().fields([
nga.field('id'),
@@ -680,16 +760,72 @@ Add filters to the referenced results list. This can be very useful to restrict
.targetEntity(post)
.targetField(nga.field('title'))
.permanentFilters({
- published: true
+ published: true // display only the published posts
});
]);
-* `perPage(integer)`
-Define the maximum number of elements fetched and displayed in the list.
+ // will trigger the following queries
+ GET /comments/123 <= get the main entity
+ { "id": 123, "post_id": 456, ... }
+ GET /posts/456 <= get the referenced entity itself
+ GET /posts?_filters={"published":true} <= get the possible values for the referenced entity
+ [
+ { "id": 456, ... },
+ { "id": 458, ... },
+ ]
### `referenced_list` Field Type
-The `referenced_list` type also defines `label`, `order`, `map`, `list` & `validation` options like the `Field` type.
+The `referenced_list` type maps a one-to-many relationship where the foreign key is located in another entity. For instance, if the REST API behaves as follows:
+
+```
+GET /posts/456
+{
+ "id": "456",
+ "title": "Consectetur adipisicing elit",
+ "body": "Sed do eiusmod..."
+}
+
+GET /comments/123
+{
+ "id": 123,
+ "author": "Alice",
+ "body": "Lorem ipsum sic dolor amet...",
+ "post_id": 456 // foreign key to post of id 456
+}
+GET /comments/124
+{
+ "id": 124,
+ "author": "Bob",
+ "body": "Lorem ipsum sic dolor amet...",
+ "post_id": 456 // foreign key to post of id 456
+}
+```
+
+Then mapping a `comments` property of the `post` entity to a `referenced_list` will tell ng-admin to fetch the related `comment` entities, and to display the result in a datagrid.
+
+```js
+var post = nga.entity('posts');
+var comment = nga.entity('comments');
+post.editionView().fields([
+ nga.field('comments', 'referenced_list') // Define a 1-N relationship with the comment entity
+ .targetEntity(comment) // Target the comment Entity
+ .targetReferenceField('post_id') // Each comment with post_id = post.id (the identifier) will be displayed
+ .targetFields([ // which comment fields to display in the datagrid
+ nga.field('id').label('ID'),
+ nga.field('body').label('Comment')
+ ])
+]);
+```
+
+As such, a `referenced_lists` field is the opposite of a `reference` field. `referenced_lists` fields are not editable (because the relationship is the other entity's responsibility), so they render the same in all contexts: as a datagrid. However, they are only useful in `showView` and `editionView` (you can't display a datagrid in a datagrid, so this excludes the `listView`, and you can't fetch related entities to a non-existent entity, so this excludes the `creationView`). For that field, ng-admin fetches the related entities in a single query with a filter:
+
+```
+GET /posts/456 <= get the main entity
+GET /comments?_filters={"post_id":456}&_page=1 <= get the values for the referenced entity
+```
+
+The `referenced_list` type specializes the `Field` type, so it supports the same `label`, `order`, `map`, `list` & `validation` options. Additional options are:
* `targetEntity(Entity)`
Define the referenced entity.
@@ -698,33 +834,182 @@ Define the referenced entity.
Define the field name used to link the referenced entity.
* `targetFields(Array(Field))`
-Define an array of fields that will be displayed in the list of the form.
+Define the list of fields of the target entity to be displayed in the datagrid.
myEditionView.fields([
- nga.field('comments', 'referenced_list') // Define a N-1 relationship with the comment entity
- .label('Comments')
- .targetEntity(comment) // Target the comment Entity
- .targetReferenceField('post_id') // Each comment with post_id = post.id (the identifier) will be displayed
- .targetFields([ // Display comment field to display
+ nga.field('comments', 'referenced_list')
+ .targetEntity(comment)
+ .targetReferenceField('post_id')
+ .targetFields([ // choose another set of fields
+ nga.field('author'),
+ nga.field('body')
+ ])
+ ]);
+
+* `perPage(integer)`
+Define the maximum number of related entities fetched and displayed in the datagrid. Defaults to 30.
+
+* `sortField(String)`
+Set the field used to sort the datagrid. Defaults to 'id'.
+
+* `sortDir(String)`
+Set the direction used to sort the datagrid. Defaults to 'DESC'.
+
+* `permanentFilters({ field1: value, field2: value, ...})`
+Filter the list of referenced entities list. This can be very useful to restrict the list of possible values displayed in the datagrid:
+
+ post.editionView().fields([
+ nga.field('comments', 'referenced_list')
+ .targetEntity(comment)
+ .targetReferenceField('post_id')
+ .targetFields([
nga.field('id').label('ID'),
nga.field('body').label('Comment')
])
+ .permanentFilters({
+ published: true // display only the published comments
+ })
+ ]);
+
+ // will trigger the following queries
+ GET /post/456 <= get the main entity
+ { "id": 456, ... }
+ GET /posts?_filters={"post_id":456,"published":true}&_page=1 <= get the possible values for the referenced entity
+ [
+ { "id": 123, "post_id": 456, ... },
+ { "id": 124, "post_id": 456, ... },
+ ]
+
+### `embedded_list` Field Type
+
+The `embedded_list` type maps a one-to-many relationship where the related entities are embedded in the main response. For instance, if the REST API behaves as follows:
+
+```
+GET /posts/1
+{
+ "id": "1",
+ "title": "Consectetur adipisicing elit",
+ "body": "Sed do eiusmod...",
+ "comments": [
+ {
+ "author": "Alice",
+ "body": "Lorem ipsum sic dolor amet...",
+ },
+ {
+ "author": "Bob",
+ "body": "Lorem ipsum sic dolor amet...",
+ }
+ ]
+}
+```
+
+Then mapping a `comments` property of the `post` entity to an `embedded_list` will tell ng-admin to use the embedded `comment` entities.
+
+```js
+post.showView().fields([
+ nga.field('comments', 'embedded_list') // Define a 1-N relationship with the (embedded) comment entity
+ .targetFields([ // which comment fields to display in the datagrid / form
+ nga.field('body')
+ ])
+]);
+```
+
+Ng-admin renders`embedded_list` fields as a datagrid in read context (`showView`), and as a list of embedded forms in write context (`creationView` and `editionView`). This won't issue any additional query to the REST API, since the related entities are already embedded.
+
+The `embedded_list` type specializes the `Field` type, so it supports the same `label`, `order`, `map`, `list` & `validation` options. Additional options are:
+
+* `targetFields(Array(Field))`
+Define the list of fields of the target entity to be displayed in the datagrid.
+
+ myEditionView.fields([
+ nga.field('comments', 'embedded_list')
+ .targetFields([ // choose another set of fields
+ nga.field('author'),
+ nga.field('body')
+ ])
]);
* `sortField(String)`
-Set the default field for list sorting. Defaults to 'id'
+Set the field used to sort the datagrid. Defaults to 'id'.
* `sortDir(String)`
-Set the default direction for list sorting. Defaults to 'DESC'
+Set the direction used to sort the datagrid. Defaults to 'DESC'.
+
+* `targetEntity(Entity)`
+Define the referenced entity (optional). When set, if the embedded entities have an identifier field, the `embedded_list` datagrid will be able to display links to the detail view of the entity. It is not used in write context.
+
+ var post = nga.entity('posts');
+ var comment = nga.entity('comments');
+ post.showView().fields([
+ nga.field('comments', 'embedded_list') // Define a 1-N relationship with the (embedded) comment entity
+ .targetEntity(comment)
+ .targetFields([ // which comment fields to display in the datagrid / form
+ nga.field('id') // will have a link to comment edition view
+ nga.field('body')
+ ])
+ ]);
* `permanentFilters({ field1: value, field2: value, ...})`
-Add filters to the referenced results list.
+Filter the list of referenced entities list. This can be very useful to restrict the list of possible values displayed in the datagrid:
-* `perPage(integer)`
-Define the maximum number of elements fetched and displayed in the list.
+ post.editionView().fields([
+ nga.field('comments', 'embedded_list')
+ .targetFields([
+ nga.field('body')
+ ])
+ .permanentFilters({
+ published: true // display only the published comments
+ })
+ ]);
### `reference_many` Field Type
+The `reference_many` type maps a one-to-many relationship where the identifiers of the related entities are embedded in the main response. For instance, if the REST API behaves as follows:
+
+```
+GET /posts/456
+{
+ "id": "456",
+ "title": "Consectetur adipisicing elit",
+ "body": "Sed do eiusmod...",
+ "comments": [123, 124]
+}
+
+GET /comments/123
+{
+ "id": 123,
+ "author": "Alice",
+ "body": "Lorem ipsum sic dolor amet...",
+}
+GET /comments/124
+{
+ "id": 124,
+ "author": "Bob",
+ "body": "Lorem ipsum sic dolor amet...",
+}
+```
+
+Then mapping a `comments` property of the `post` entity to a `reference_many` will tell ng-admin to fetch the related `comment` entities.
+
+```js
+var post = nga.entity('posts');
+var comment = nga.entity('comments');
+post.editionView().fields([
+ nga.field('comments', 'reference_many') // Define a 1-N relationship with the comment entity
+ .targetEntity(comment) // Target the comment Entity
+ .targetFieldField('body') // the field of the comment entity to use as representation
+]);
+```
+
+` reference_many` fields render as a list of labels in rerad context (`listView` and `showView`), and as a select multiple in write context (`creationView` and `editionView`). For that field, ng-admin fetches the related entities one by one:
+
+```
+GET /posts/456 <= get the main entity
+{ "id": "456", "comments": [123, 124], ... }
+GET /comments/123
+GET /comments/124
+```
+
The `reference_many` field type also defines `label`, `order`, `map` & `validation` options like the `Field` type.
* `targetEntity(Entity)`
@@ -742,7 +1027,7 @@ Define the field name used to link the referenced entity.
])
* `singleApiCall(function(entityIds) {}`
-Define a function that returns parameters for filtering API calls. You can use it if you API support filter for multiple values.
+Define a function that returns parameters for filtering API calls. You can use it if you API supports filter for multiple values.
// Will call /tags?tag_id[]=1&tag_id[]=2&tag_id%[]=5...
postList.fields([
diff --git a/examples/blog/config.js b/examples/blog/config.js
index 11488953..da501180 100644
--- a/examples/blog/config.js
+++ b/examples/blog/config.js
@@ -97,6 +97,10 @@
.cssClasses('hidden-xs'),
nga.field('views', 'number')
.cssClasses('hidden-xs'),
+ nga.field('backlinks', 'embedded_list') // display list of related comments
+ .label('Links')
+ .map(links => links ? links.length : '')
+ .template('{{ value }}'),
nga.field('tags', 'reference_many') // a Reference is a particular type of field that references another entity
.targetEntity(tag) // the tag entity is defined later in this file
.targetField(nga.field('name')) // the field to be displayed in this list
@@ -152,6 +156,14 @@
.cssClasses('col-sm-4'),
nga.field('average_note', 'float')
.cssClasses('col-sm-4'),
+ nga.field('backlinks', 'embedded_list') // display embedded list
+ .targetFields([
+ nga.field('date', 'datetime'),
+ nga.field('url')
+ .cssClasses('col-lg-10')
+ ])
+ .sortField('date')
+ .sortDir('DESC'),
nga.field('comments', 'referenced_list') // display list of related comments
.targetEntity(nga.entity('comments'))
.targetReferenceField('post_id')
@@ -170,7 +182,39 @@
post.showView() // a showView displays one entry in full page - allows to display more data than in a a list
.fields([
nga.field('id'),
- post.editionView().fields(), // reuse fields from another view in another order
+ nga.field('category', 'choice') // a choice field is rendered as a dropdown in the edition view
+ .choices([ // List the choice as object literals
+ { label: 'Tech', value: 'tech' },
+ { label: 'Lifestyle', value: 'lifestyle' }
+ ]),
+ nga.field('subcategory', 'choice')
+ .choices(subCategories),
+ nga.field('tags', 'reference_many') // ReferenceMany translates to a select multiple
+ .targetEntity(tag)
+ .targetField(nga.field('name')),
+ nga.field('pictures', 'json'),
+ nga.field('views', 'number'),
+ nga.field('average_note', 'float'),
+ nga.field('backlinks', 'embedded_list') // display embedded list
+ .targetFields([
+ nga.field('date', 'datetime'),
+ nga.field('url')
+ ])
+ .sortField('date')
+ .sortDir('DESC'),
+ nga.field('comments', 'referenced_list') // display list of related comments
+ .targetEntity(nga.entity('comments'))
+ .targetReferenceField('post_id')
+ .targetFields([
+ nga.field('id').isDetailLink(true),
+ nga.field('created_at').label('Posted'),
+ nga.field('body').label('Comment')
+ ])
+ .sortField('created_at')
+ .sortDir('DESC')
+ .listActions(['edit']),
+ nga.field('').label('')
+ .template('