{
"name": "czechidm-example",
"version": "7.3.0",
"description": "Example module for CzechIdM devstack. This module can be duplicated and rename for create new optional CzechIdM module.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"CzechIdM",
"example",
"IdM"
],
"author": "BCV solutions s.r.o",
"license": "MIT"
}
==== module-descriptor.js ====
module.exports = {
'id': 'example',
'npmName': 'czechidm-example',
'backendId': 'example',
'name': 'Example module for CzechIdM devstack.',
'description': 'Example module for CzechIdM devstack. This module can be duplicated and renamed for create new optional CzechIdM module.'
};
Module is built together with czechidm-app module using ''gulp''. See frontend installation [[https://github.com/bcvsolutions/CzechIdMng/tree/develop/Realization/frontend|guide]].
The simplest way to create new module is to copy example module skeleton.
==== Routes ====
Each module could expose new urls with contents. We need to register ''routes.js'' descriptor in module descriptor:
module.exports = {
'id': 'example',
...
'mainRouteFile': 'routes.js',
...
};
Then we can add routes descriptor ''routes.js'' into the same folder as module descriptor with new route definition:
module.exports = {
module: 'example',
childRoutes: [
{
path: '/example/content',
component: require('./src/content/ExampleContent')
}
]
};
==== Content ====
After route is definied, then we can add new content to path ''
import React from 'react';
import Helmet from 'react-helmet';
//
import { Basic } from 'czechidm-core';
/**
* Example content (page)
*/
export default class ExampleContent extends Basic.AbstractContent {
constructor(props, context) {
super(props, context);
}
/**
* "Shorcut" for localization
*/
getContentKey() {
return 'example:content.example';
}
render() {
return (
{' '}
{this.i18n('header')}
{ this.i18n('text') }
);
}
}
This content will be available on url ''
module.exports = {
'id': 'example',
...
'mainLocalePath': 'src/locales/',
...
};
We can create localization json file with name ''en.json'':
{
"module": {
"name": "Example module",
"author": "BCV solutions s.r.o."
},
"content": {
"example": {
"header": "Example content",
"label": "Example content",
"title": "Example content available from navigation, added by czechidm-example module.",
"text": "New example content."
}
}
}
See ''getContentKey()'' method above in ''ExampleContent'' (page) and the json structure - it fits together.
==== Navigation ====
Now is localized content available from url. We can add item with link to navigation - add navigation item into module descriptor:
module.exports = {
'id': 'example',
...
'navigation': {
'items': [
{
'id': 'example-main-menu',
'labelKey': 'example:content.examples.label',
'titleKey': 'example:content.examples.title',
'icon': 'gift',
'iconColor': '#FF8A80',
'order': 9
}
]
}
...
};
==== Services (rest) ====
Service consumes rest endpoints. This is the only place when url of rest endpoint can be defined. Services simply make calls to rest api - [[https://github.com/matthew-andrews/isomorphic-fetch|isomorphic-fetch]] is used.
=== Base services ===
* ''RestApiService'' - calls http methods to given endpoint and wraps authentication tokens (cidmst).
* ''AbstractService'' - basic CRUD method, whid are commons for all endpoints
* Other services define concrete rest endpoint and add custom endpoint methods
=== Service example ===
Service with example product CRUD operations.
import { Services } from 'czechidm-core';
import { Domain } from 'czechidm-core';
/**
* Example products
*/
export default class ExampleProductService extends Services.AbstractService {
getApiPath() {
return '/example-products';
}
getNiceLabel(entity) {
if (!entity) {
return '';
}
return `${entity.name} (${entity.code})`;
}
/**
* Agenda supports authorization policies
*/
supportsAuthorization() {
return true;
}
/**
* Group permission - all base permissions (`READ`, `WRITE`, ...) will be evaluated under this group
*/
getGroupPermission() {
return 'EXAMPLEPRODUCT';
}
/**
* Almost all dtos doesn§t support rest `patch` method
*/
supportsPatch() {
return false;
}
/**
* Returns default searchParameters for current entity type
*
* @return {object} searchParameters
*/
getDefaultSearchParameters() {
return super.getDefaultSearchParameters().setName(Domain.SearchParameters.NAME_QUICK).clearSort().setSort('name');
}
}
==== Managers (redux) ====
The business logic is implemented inside manager. Manager use underlying service and adds [[http://redux.js.org/docs/introduction/|redux]] state usage - holds application state and provide actions (encapsulated to managers) to change application state.
=== Manager example ===
/**
* Example product manager
*/
export default class ExampleProductManager extends Managers.EntityManager {
constructor() {
super();
this.service = new ExampleProductService();
}
getModule() {
return 'example';
}
getService() {
return this.service;
}
/**
* Controlled entity
*/
getEntityType() {
return 'ExampleProduct';
}
/**
* Collection name in search / find response
*/
getCollectionType() {
return 'exampleProducts';
}
}
We can register services and managers in ''index.js'' files placed in folders, when services and managers are defined - it simpler to import services from one file:
...
import { ExampleProductService } from '../services';
...
where relative path leeds to module service's ''index.js'':
import ExampleProductService from './ExampleProductService';
const ServiceRoot = {
ExampleProductService
};
ServiceRoot.version = '0.1.0';
module.exports = ServiceRoot;
Created manager can be used together with ''Basic.AbstractTableContent'' [[https://github.com/bcvsolutions/CzechIdMng/tree/develop/Realization/frontend/czechidm-core/src/components/advanced/Content|component]] to provide CRUD methods for persisted entity - see [[https://github.com/bcvsolutions/CzechIdMng/tree/develop/Realization/frontend/czechidm-core/src/components/advanced/Content|ExampleProductTable]].
==== Security ====
Authorities and permissions are primarily evaluated on backend, but if we need to hide some navigation items, routes and contents on frontend, then ''SecurityManager'' can be used for this purpose.
=== Secure route ===
''SecurityManager'' is not called directly, but each route can have ''access'' property, which is controlled by ''SecurityManager#checkAccess'' method on the backgroud:
module.exports = {
module: 'example',
childRoutes: [
...
{
path: '/example/products',
component: require('./src/content/example-product/ExampleProducts'),
access: [ { 'type': 'HAS_ANY_AUTHORITY', 'authorities': ['EXAMPLEPRODUCT_READ'] } ]
},
...
]
};
Url ''
module.exports = {
'id': 'example',
...
'navigation': {
'items': [
{
'id': 'example-main-menu',
'labelKey': 'example:content.examples.label',
'titleKey': 'example:content.examples.title',
'icon': 'gift',
'iconColor': '#FF8A80',
'order': 9,
'items': [
{
'id': 'example-products',
'type': 'DYNAMIC',
'section': 'main',
'icon': 'gift',
'labelKey': 'example:content.example-products.label',
'titleKey': 'example:content.example-products.title',
'order': 20,
'path': '/example/products',
'access': [ { 'type': 'HAS_ANY_AUTHORITY', 'authorities': ['EXAMPLEPRODUCT_READ'] } ]
}
]
},
...
]
}
};
=== Secure components ===
''SecurityManager'' can be used direcly for hide some component, e.g. button on some content:
...
{ this.i18n('button.save') }
...
Button will be shown only for logged identity with authority ''ROLECATALOGUE_UPDATE''.
=== Secure form ===
When agenda supports **authorization policies = permissions for data**, then form can be secured using manager's ''canRead'', ''canSave'', ''canDelete'' methods. Permissions, what currently logged identity can do with selected record, has to be loaded at first. Permission are stored in redux store and are loaded together with entity by default (see ''EntityManager#fetchEntity'' method) - [[https://github.com/bcvsolutions/CzechIdMng/blob/develop/Realization/frontend/czechidm-example/src/content/example-product/ExampleProductDetail.js|full example]].
==== Error codes ====
Backend returns error code response, when some [[https://wiki.czechidm.com/7.3/dev/quickstart/backend#errors|error occurs]]. We can localize this error on frontend by adding ''error'' section into localization file:
...
"error": {
"EXAMPLE_SERVER_ERROR": {
"title": "Example server error",
"message": "Example server with parameter [{{parameter}}]."
},
"EXAMPLE_CLIENT_ERROR": {
"title": "Example client error",
"message": "Example client error, bad value given [{{parameter}}]."
}
},
...
==== Registrable components ====
Component descriptor can be used for register component, which can be added into existing contents or which can override some existing component (e.g. when different behavior is needed).
We need to register ''component.js'' descriptor in module descriptor:
module.exports = {
'id': 'example',
...
'mainComponentDescriptorFile': 'component-descriptor.js',
...
=== Example - dashboard component ===
Then we can add ''component-descriptor.js'' into the same folder as module descriptor with new component definition:
module.exports = {
'id': 'example',
'name': 'Example',
'description': 'Components for Example module',
'components': [
{
'id': 'exampleDashboard',
'type': 'dashboard',
'span': '4',
'order': '4',
'component': require('./src/content/dashboards/ExampleDashboard')
}
]
};
And then we can add new content to path ''
/**
* Example dashbord panel
*/
export default class ExampleDashboard extends Basic.AbstractContent {
/**
* "Shorcut" for localization
*/
getContentKey() {
return 'example:content.dashboard.exampleDashboard';
}
render() {
return (
{ this.i18n('title') }
{ this.i18n('text') }
);
}
}
==== Theme ====
Custom theme can be created in module. Theme contains less files and images. Core less variables can be overriden.
We start with copy [[https://github.com/bcvsolutions/CzechIdMng/tree/develop/Realization/frontend/czechidm-core/themes/default|core default theme]] into new module's theme folder and rename theme folder from ''default'' to ''example''. Then we select new theme in [[https://github.com/bcvsolutions/CzechIdMng/tree/develop/Realization/frontend/czechidm-app/config|frontend configuration]] under our state and profile (e.g. ''development.json''):
{
"env": "development",
...
"theme": "czechidm-example/themes/example",
...
}
Then we can change some colors and logo in module's theme ''
@brand-success: #A13432;
@idm-base-color: #A13432;
@idm-link-color: #A13432;
header {
.home {
background-image: url("../images/logo.png");
}
}
and replace logo image on path ''