Play CMS is shipped with DefaultParty as a default implementation of party, DefaultPartyRole as a default implementation of role, DefaultPartyManage, DefaultRoleManager and default user interface for their management.
Each of the elements can be extended and customized in main project.
The Party objects are managed by the PartyService which can be overridden to support customized Party implementation. The overridden PartyService can be enabled by providing a custom Dependency Injection Module binding (see play.api.inject.Module#Module) or by defining a custom play.inject.guice.GuiceApplicationLoader with overridden bindings.
Example:
// CustomPartyService.java
public class CustomPartyManager extends ch.insign.playauth.party.PartyService<User> {
@Override
public Class<User> getPartyClass() {
return User.class;
}
}
// CustomApplicationLoader.java
public class CustomApplicationLoader extends GuiceApplicationLoader {
@Override
public GuiceApplicationBuilder builder(Context context) {
return super.builder(context)
.overrides(
bind(PartyService.class).to(CustomPartyService.class));
}
}
# Take control of how the application is initialized
play.application.loader = "CustomApplicationLoader"
A list of users is rendered by datatable. You can find a detailed guide about play-cms integration with datatable in the Datatable integration section. The user searching functionality is already implemented in PartyDatatableService.
Let's look on example where new fields are added to the list:
// imports ommited
public class UserDatatableService extends PartyDatatableService<User> {
@Inject
public UserDatatableService(CMSApi cmsApi, PlayAuthApi playAuthApi, JPAApi jpaApi) {
super(cmsApi, playAuthApi, jpaApi);
}
@Override
protected Class<User> getEntityClass() {
return User.class;
}
@Override
protected List<String> getSortableFields() {
return Arrays.asList("name", "email", "roleName", "lastLogin", "loginCount", "");
}
@Override
protected List<Function<User, String>> getTableFields() {
return Arrays.asList(
getPartyName(),
getPartyEmail(),
getPartyRoles(),
getUserLastLogin(),
getUserLoginCount(),
getPartyActions()
);
}
private Function<User, String> getUserLastLogin() {
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy HH:mm:ss");
return user -> Optional.ofNullable(user.getLastLogin()).map(dateFormat::format).orElse("-");
}
private Function<User, String> getUserLoginCount() {
return user -> String.valueOf(user.getLoginCount());
}
}
PartyDatatableService
already provides number of helper methods that helps to obtain various fields from party like name, email, roles or available actions. UserDatatableService
extends list of such helpers by adding new methods to receive last login date and login counter. It also overrides getSortableFields
method in order to bring ability to sort a table by newly added fields. You may note that last column in list of sortable columns doesn't have name. This is because last column in getTableFields
was set to getPartyActions
that doesn't have any representation in db and couldn't be reordered. Yet, in the datatable it is required that the number of sortable columns should match the actual number of columns.
Once service is done, template should be updated accordingly:
@()
@import ch.insign.cms.views.html.admin.shared.layout
@import ch.insign.cms.models.CMS
@pageFooter = {
<script src="@{ch.insign.cms.views.html.tags._asset("backend/js/user_list.js")}" type="text/javascript"></script>
}
@layout(new ch.insign.cms.views.admin.utils.AdminContext, "", "", null, pageFooter) {
<div class="modal fade" id="deletePartyModal"></div>
<input type="hidden" id="datatableUrl" value="@ch.insign.cms.controllers.routes.PartyController.datatable()" name="datatableUrl"/>
<div class="row">
<div class="col-md-12">
<div class="portlet">
<div class="portlet-title">
<div class="caption">
<i class="fa fa-user"></i> @Messages("backend.user.customer.title")
</div>
<div class="actions">
<a href="@CMS.getRouteResolver.createParty()" class="btn red">
<i class="fa fa-plus"></i> @Messages("backend.newEntry.button")
</a>
</div>
</div>
<div class="portlet-body">
<table class="table table-striped table-bordered table-hover" id="user_list">
<thead>
<tr>
<th class="col-xs-3">@Messages("backend.user.name")</th>
<th class="col-xs-3">@Messages("backend.user.email")</th>
<th class="col-xs-2">@Messages("backend.user.roles")</th>
<th class="col-xs-2">@Messages("backend.user.lastLogin")</th>
<th class="col-xs-1">@Messages("backend.user.loginCount")</th>
<th class="col-xs-1">@Messages("backend.user.actions")</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
}
There are just two additional table headers in this template if compared to the default implementation. The additional headers are added to match the number of columns received from the server, because otherwise the datatable will generate an exception.
This template should be injected into a project by implementing PartyListView
.
Create class which extends PartyFormManager and implement it's methods:
Register new class in registerPartyFormManager() method of a setup class
Example:
public class DefaultPartyFormManager implements PartyFormManager {
@Override
public Html editForm(Form editForm, Party party) {
return editForm.render(new AdminContext(), editForm, (User) party);
}
@Override
public Html createForm(Form form) {
return createForm.render(form);
}
}
Note: create and edit templates can use default party's templates as a base template and only extend it with new fields.
Example:
@ch.insign.cms.views.html.admin.party.editForm(a, userForm, user) {
<div class="form-group">
@inputText(
userForm("lastName"),
'class -> "form-control",
'placeholder -> Messages.get("backend.user.lastName"),
'_label -> Messages.get("backend.user.lastName")
)
</div>
<div class="form-group">
@inputText(
userForm("phone"),
'class -> "form-control",
'placeholder -> Messages.get("backend.user.phone"),
'_label -> Messages.get("backend.user.phone")
)
</div>
}
play-cms provides build-in validation constrains for various typical actions. :
ValidateRepeatedPassword
- checks password and password verification entered by user matches.ValidateExisitngEmail
- checks that the email entered by user doesnt exists in DB or linked to Party that was edited.ValidateNewEmail
- checks that email doesn't present in DB.All constraints listed above are enabled for default Party implementation.
At different lifecycle events party could be validated with own set of constraint. To add own constraint on one of the events we provide predefined constraint groups:
OnCreate
- triggers constrains on user creationOnPasswordChange
- triggers constrains on password changeOnUpdate
- triggers constrains on party updateclass User extends DefaultParty {
....
// Phone field wouldn't be mandatory on registration, but would be required on profile update
@Constraints.Required(groups = PartyConstrains.OnUpdate.class)
private String phone;
....
}
Note, that constraints without groups defiend would be always triggered on create and on update events.
Create class which extends PartyEvents and implement it's methods:
Register new class in registerPartyEventHandler()
method of a setup class.
Example:
public class DefaultPartyHandler implements PartyEvents {
@Override
public void onCreate(Form form, Party party) {
User user = (User) party;
User userForm = ((Form<User>) form).get();
user.setLastName(userForm.getLastName());
user.setPhone(userForm.getPhone());
}
@Override
public void onUpdate(Form form, Party party) {
}
@Override
public void onDelete(Party party) {
}
}
Customizing party's controllers actions is not really necessary but can be easily done by creating new class, that extends from RouteResolver.
Register new class in registerResolver() method of a setup class and implement it's methods.
Each of it's method should return the call to appropriate action of your own Party Controller.
Example:
public class DefaultRouteResolver implements RouteResolver {
@Override
public Call login() {
return routes.AuthController.login();
};
@Override
public Call logout(String backURL) {
return routes.AuthController.logout(backURL);
}
@Override
public Call listParties() {
return routes.PartyController.list();
}
@Override
public Call editParty(String id) {
return routes.PartyController.editParty(id);
}
//...
}
Note: Default RouteResolver is already implemented in play-cms module.