Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] All fields have template #713

Merged
merged 15 commits into from
Oct 1, 2015
26 changes: 26 additions & 0 deletions UPGRADE-0.9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Upgrade to 0.9

## All fields have `.template()`

All field types now support the `template()` method, which makes it easy to customize the look and feel of a particular field, withut sacrificing the native features.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without


For instance, if you want to customize the appearance of a `NumberField` according to its value:

```js
listview.fields([
nga.field('amount', 'number')
.format('$0,000.00')
.template('<span ng-class="{ \'red\': value < 0 }"><ma-number-column field="::field" value="::entry.values[field.name()]"></ma-number-column></span>')
]);
```

This removes the need for [custom types](doc/Custom-types.md) in a vast majority of cases.

The `template` *field type* is deprecated and will be removed in future versions. Simply use the default field type together with the `template()` method instead:

```js
// before
nga.field('name', 'template').template('{{ entry.values.name }}')
// now
nga.field('name').template('{{ entry.values.name }}')
```
32 changes: 23 additions & 9 deletions doc/Configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ application
|-transform
|-attributes
|-cssClasses
|-template
|-validation
|-editable
```
Expand Down Expand Up @@ -283,10 +284,10 @@ Add filters to the list. Each field maps a property in the API endpoint result.
nga.field('q').label('Search').pinned(true)
]);

Filter fields can be of any type, including `reference` and `template`. This allows to define custom filters with ease.
Filter fields can be of any type, including `reference`. This allows to define custom filters with ease.

listView.filters([
nga.field('q', 'template').label('')
nga.field('q').label('')
.template('<div class="input-group"><input type="text" ng-model="value" placeholder="Search" class="form-control"></input><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span></div>'),
]);

Expand Down Expand Up @@ -379,12 +380,11 @@ A field is the representation of a property of an entity.
* [`reference` Field Type](#reference-field-type)
* [`referenced_list` Field Type](#referenced_list-field-type)
* [`reference_many` Field Type](#reference_many-field-type)
* [`template` Field Type](#template-field-type)

### General Field Settings

* `nga.field(name, type)`
Create a new field of the given type. Default type is 'string', so you can omit it. Bundled types include `string`, `text`, `wysiwyg`, `password`, `email`, `date`, `datetime`, `number`, `float`, `boolean`, `choice`, `choices`, `json`, `file`, `reference`, `reference_list`, `reference_many`, and `template`.
Create a new field of the given type. Default type is 'string', so you can omit it. Bundled types include `string`, `text`, `wysiwyg`, `password`, `email`, `date`, `datetime`, `number`, `float`, `boolean`, `choice`, `choices`, `json`, `file`, `reference`, `reference_list`, and `reference_many`.

* `label(string label)`
Define the label of the field. Defaults to the uppercased field name.
Expand Down Expand Up @@ -466,6 +466,25 @@ A list of CSS classes to be added to the corresponding field. If you provide a f
return 'my-custom-css-class-for-list-header';
});

* `template(String|Function)`
All field types support the `template()` method, which makes it easy to customize the look and feel of a particular field, withut sacrificing the native features.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without


For instance, if you want to customize the appearance of a `NumberField` according to its value:

listview.fields([
nga.field('amount', 'number')
.format('$0,000.00')
.template('<span ng-class="{ \'red\': value < 0 }"><ma-number-column field="::field" value="::entry.values[field.name()]"></ma-number-column></span>')
]);

The template scope exposes the following variables:

- `value`, `field`, `entry`, `entity`, and `datastore` in `listView` and `showView`
- `value`, `field`, `values`, and `datastore` in filters
- `value`, `field`, `entry`, `entity`, `form`, and `datastore` in `editionView` and `creationView`

Most of the time, `template()` is used to customize the existing ng-admin directives (like `ma-number-column>` in the previous example), for instance by decorating them. If you want to learn about these native directives, explore the [column](../src/javascripts/ng-admin/crud/column), [field](../src/javascripts/ng-admin/crud/field), and [fieldView](../src/javascripts/ng-admin/crud/fieldView) directories in ng-admin source.

* `defaultValue(*)`
Define the default value of the field in the creation form.

Expand Down Expand Up @@ -765,8 +784,3 @@ If set to false, all references (in the limit of `perPage` parameter) would be r
})
.perPage(10) // limit the number of results to 10
]);

### `template` Field Type

* `template(*)`
Define the template to be displayed for fields of type `template` (can be a string or a function).
4 changes: 2 additions & 2 deletions doc/Custom-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ myApp.directive('sendEmail', ['$location', function ($location) {

## Including the Custom Directive in a Template Field

Now the new directive is ready to be used inside a ng-admin field of type 'template':
Now the new directive is ready to be used inside a ng-admin field using 'template()':

```js
post.showView().fields([
// ...
nga.field('custom_action', 'template')
nga.field('custom_action')
.label('')
.template('<send-email post="entry"></send-email>')
]);
Expand Down
6 changes: 3 additions & 3 deletions doc/Getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,10 @@ Browse to the posts list, and you will see the full-text filter displayed on the

The full-text search isn't looking very good. Usually, a full-text filter widget has no label, a placeholder simply saying "Search", and a magnifying glass icon. How can you turn the current full-text input into that UI?

Fortunately, ng-admin fields can benefit from the power of Angular.Js directives. Using the 'template' field type, you can specify the HTML template to use for rendering the field. And you can use any directive already registered in that HTML. Update the `nga.field('q')` definition as follows:
Fortunately, ng-admin fields can benefit from the power of Angular.js directives. Using the `template()` field method, you can specify the HTML template to use for rendering the field. And you can use any directive already registered in that HTML. Update the `nga.field('q')` definition as follows:

```js
nga.field('q', 'template')
nga.field('q')
.label('')
.pinned(true)
.template('<div class="input-group"><input type="text" ng-model="value" placeholder="Search" class="form-control"></input><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span></div>'),
Expand Down Expand Up @@ -653,7 +653,7 @@ myApp.config(['NgAdminConfigurationProvider', function (nga) {
.listActions(['show'])
.batchActions([])
.filters([
nga.field('q', 'template')
nga.field('q')
.label('')
.pinned(true)
.template('<div class="input-group"><input type="text" ng-model="value" placeholder="Search" class="form-control"></input><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span></div>'),
Expand Down
24 changes: 23 additions & 1 deletion doc/Theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,31 @@ myEntity.listView().fields([
]);
```

## Customizing The Template For A Given Field

All field types support the `template()` method, which makes it easy to customize the look and feel of a particular field, withut sacrificing the native features.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without


For instance, if you want to customize the appearance of a `NumberField` according to its value:

```js
listview.fields([
nga.field('amount', 'number')
.format('$0,000.00')
.template('<span ng-class="{ \'red\': value < 0 }"><ma-number-column field="::field" value="::entry.values[field.name()]"></ma-number-column></span>')
]);
```

The template scope exposes the following variables:

* `value`, `field`, `entry`, `entity`, and `datastore` in `listView` and `showView`
* `value`, `field`, `values`, and `datastore` in filters
* `value`, `field`, `entry`, `entity`, `form`, and `datastore` in `editionView` and `creationView`

Most of the time, `template()` is used to customize the existing ng-admin directives (like `ma-number-column>` in the previous example), for instance by decorating them. If you want to learn about these native directives, explore the [column](../src/javascripts/ng-admin/crud/column), [field](../src/javascripts/ng-admin/crud/field), and [fieldView](../src/javascripts/ng-admin/crud/fieldView) directories in ng-admin source.

## Customizing Directives Templates

Using Angular's [`$provide`](https://docs.angularjs.org/api/auto/service/$provide) service, and the ability to decorate another provider, you can customize the templates of all the directives used by ng-admin. Here is an example of customization of the 'text' input field customization:
Using Angular's [`$provide`](https://docs.angularjs.org/api/auto/service/$provide) service, and the ability to decorate another provider, you can customize the template of any directive used by ng-admin for your entire application. Here is an example of customization of the 'text' input field:

```js
var myApp = angular.module('myApp', ['ng-admin']);
Expand Down
18 changes: 9 additions & 9 deletions examples/blog/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,15 @@
.sortField('created_at')
.sortDir('DESC')
.listActions(['edit']),
nga.field('', 'template').label('')
nga.field('').label('')
.template('<span class="pull-right"><ma-filtered-list-button entity-name="comments" filter="{ post_id: entry.values.id }" size="sm"></ma-filtered-list-button><ma-create-button entity-name="comments" size="sm" label="Create related comment" default-values="{ post_id: entry.values.id }"></ma-create-button></span>')
]);

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('custom_action', 'template')
.label('')
nga.field('custom_action').label('')
.template('<send-email post="entry"></send-email>')
]);

Expand All @@ -197,7 +196,7 @@
.singleApiCall(ids => { return {'id': ids }})
])
.filters([
nga.field('q', 'template')
nga.field('q')
.label('')
.pinned(true)
.template('<div class="input-group"><input type="text" ng-model="value" placeholder="Search" class="form-control"></input><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span></div>'),
Expand Down Expand Up @@ -238,9 +237,9 @@

comment.editionView()
.fields(comment.creationView().fields())
.fields([nga.field(null, 'template')
.label('')
.template('<post-link entry="entry"></post-link>') // template() can take a function or a string
.fields([
nga.field('').label('')
.template('<post-link entry="entry"></post-link>') // template() can take a function or a string
]);

comment.deletionView()
Expand All @@ -260,14 +259,15 @@
return 'bg-warning text-center';
}
}),
nga.field('custom', 'template')
nga.field('custom')
.label('Upper name')
.template('{{ entry.values.name.toUpperCase() }}')
.cssClasses('hidden-xs')
])
.filters([
nga.field('published', 'template')
nga.field('published')
.label('Not yet published')
.template(' ')
.defaultValue(false)
])
.batchActions([]) // disable checkbox column and batch delete
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"url": "git://github.com/marmelab/ng-admin.git"
},
"devDependencies": {
"admin-config": "^0.3.2",
"admin-config": "^0.4.0",
"angular": "~1.3.15",
"angular-bootstrap": "^0.12.0",
"angular-mocks": "1.3.14",
Expand Down
1 change: 1 addition & 0 deletions src/javascripts/ng-admin/Crud/CrudModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ CrudModule.directive('maDatagridPagination', require('./list/maDatagridPaginatio
CrudModule.directive('maDatagridInfinitePagination', require('./list/maDatagridInfinitePagination'));
CrudModule.directive('maDatagridItemSelector', require('./list/maDatagridItemSelector'));
CrudModule.directive('maDatagridMultiSelector', require('./list/maDatagridMultiSelector'));
CrudModule.directive('maFilterForm', require('./filter/maFilterForm'));
CrudModule.directive('maFilter', require('./filter/maFilter'));
CrudModule.directive('maFilterButton', require('./filter/maFilterButton'));

Expand Down
122 changes: 61 additions & 61 deletions src/javascripts/ng-admin/Crud/column/maColumn.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,77 @@
/*global define*/
function maColumn($state, $anchorScroll, $compile, Configuration, FieldViewConfiguration) {

define(function (require) {
'use strict';

function maColumn($state, $anchorScroll, $compile, Configuration, FieldViewConfiguration) {

function getDetailLinkRouteName(field, entity) {
if (entity.isReadOnly) {
return entity.showView().enabled ? 'show' : false;
}
if (field.detailLinkRoute() == 'edit' && entity.editionView().enabled) {
return 'edit';
}
function getDetailLinkRouteName(field, entity) {
if (entity.isReadOnly) {
return entity.showView().enabled ? 'show' : false;
}
if (field.detailLinkRoute() == 'edit' && entity.editionView().enabled) {
return 'edit';
}
return entity.showView().enabled ? 'show' : false;
}

function isDetailLink(field, entity) {
if (field.isDetailLink() === false) {
function isDetailLink(field, entity) {
if (field.isDetailLink() === false) {
return false;
}
if (field.type() == 'reference' || field.type() == 'reference_many') {
var relatedEntity = Configuration().getEntity(field.targetEntity().name());
if (!relatedEntity) {
return false;
}
if (field.type() == 'reference' || field.type() == 'reference_many') {
var relatedEntity = Configuration().getEntity(field.targetEntity().name());
if (!relatedEntity) {
return false;
}
return getDetailLinkRouteName(field, relatedEntity) !== false;
}
return getDetailLinkRouteName(field, entity) !== false;
return getDetailLinkRouteName(field, relatedEntity) !== false;
}
return getDetailLinkRouteName(field, entity) !== false;
}

return {
restrict: 'E',
scope: {
field: '&',
entry: '&',
entity: '&',
datastore: '&'
},
link: function(scope, element) {
scope.datastore = scope.datastore();
scope.field = scope.field();
scope.entry = scope.entry();
scope.entity = scope.entity();
var type = scope.field.type();
return {
restrict: 'E',
scope: {
field: '&',
entry: '&',
entity: '&',
datastore: '&'
},
link: function(scope, element) {
scope.datastore = scope.datastore();
scope.field = scope.field();
scope.entry = scope.entry();
scope.value = scope.entry.values[scope.field.name()];
scope.entity = scope.entity();
let customTemplate = scope.field.getTemplateValue(scope.entry);
if (customTemplate) {
element.append(customTemplate);
} else {
let type = scope.field.type();
if (isDetailLink(scope.field, scope.entity)) {
element.append(FieldViewConfiguration[type].getLinkWidget());
} else {
element.append(FieldViewConfiguration[type].getReadWidget());
}
$compile(element.contents())(scope);
scope.gotoDetail = function () {
var route = getDetailLinkRouteName(scope.field, scope.entity);
$state.go($state.get(route),
angular.extend({}, $state.params, {
entity: scope.entry.entityName,
id: scope.entry.identifierValue
}));
};
scope.gotoReference = function () {
var referenceEntity = scope.field.targetEntity().name();
var relatedEntity = Configuration().getEntity(referenceEntity);
var referenceId = scope.entry.values[scope.field.name()];
var route = getDetailLinkRouteName(scope.field, relatedEntity);
$state.go($state.get(route), {
entity: referenceEntity,
id: referenceId
});
};
}
};
}
$compile(element.contents())(scope);
scope.gotoDetail = function () {
var route = getDetailLinkRouteName(scope.field, scope.entity);
$state.go($state.get(route),
angular.extend({}, $state.params, {
entity: scope.entry.entityName,
id: scope.entry.identifierValue
}));
};
scope.gotoReference = function () {
var referenceEntity = scope.field.targetEntity().name();
var relatedEntity = Configuration().getEntity(referenceEntity);
var referenceId = scope.entry.values[scope.field.name()];
var route = getDetailLinkRouteName(scope.field, relatedEntity);
$state.go($state.get(route), {
entity: referenceEntity,
id: referenceId
});
};
}
};
}

maColumn.$inject = ['$state', '$anchorScroll', '$compile', 'NgAdminConfiguration', 'FieldViewConfiguration'];
maColumn.$inject = ['$state', '$anchorScroll', '$compile', 'NgAdminConfiguration', 'FieldViewConfiguration'];

return maColumn;
});
module.exports = maColumn;
Loading