| Section | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
Introduction
Use Case
The use case is the management of the parking permissions at area 51.
So this is a quite common task for the public service.
Filing (structure) plan
The general folder structure is as follows:
...
Nevertheless this is an example for everybody and readability is preferred over correctness.
Tutorial
META-INF/MANIFEST
The MANIFEST is the configuration file of the bundle. It must be at the first position in the JAR archive. The explanation is a historical one and quite simple. Just imagine tapes and not random access memory. Now do you want to rewind the whole tape to know what
is stored on it and where?
...
The components are the files which are loaded by Nuxeo. If your archive includes a configuration file which is missing in the MANIFEST then it will be ignored without any error message from Nuxeo.
| Tip | ||
|---|---|---|
| ||
More information about the MANIFEST are available from the How-to create an empty bundle recipe. |
...
So what is your actual/first task? Please replace the makefile with a Maven configuration (and a description) and contribute it back ![]()
Definition of a new folder type
The folders create the general structure.
A folder itself usually does not contain a document.
It contains other folders and documents.
Additionally a folder is described by some meta data.
Nuxeo clearly separates the structural definition and the user interface of a document.
So both parts must be configured separately.
The examples utilize the process.
The process is the management or better only the granting/denial of parking permissions at area 51. First we define a folder which
is only used for the management of the customer requests.
Creating the Process folder
Defining the document type (process-type.xml)
PAP means public administration process. It is used to guarantee the uniqueness of the component names.
Structural configuration
First we need to define the folder type itself. This means which (meta) data is needed in the folder and which features are available for the folder itself. So let's extend org.nuxeo.ecm.core.schema.TypeService with the component org.nuxeo.dev.cookbook.example.pap.process.type.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process.type">
<extension target="org.nuxeo.ecm.core.schema.TypeService" point="doctype">
<doctype name="DevCookbookExamplePapProcess" extends="Folder">
<schema name="common"/>
<schema name="dublincore"/>
</doctype>
</extension>
</component>
|
The new type is only a special folder with most basic stuff.
Configuring the user interface (process-ui-type.xml)
Now, the user interface must be configured for this special folder.
| Tip | ||
|---|---|---|
| ||
Libraries especially in research environments like universities heavily depend on meta data standards. If your content should be usable, searchable and referenceable then it is strongly recommended that you use standards like Dublin Core. Please see here for more informations: |
User interface
The user interface configuration must basically include two things - the layout and the parents.
First the layout stuff defines how a type is displayed. It does not only create a label and a description.
The category defines for example in which section you will see the the document type if you click on the add document button.
You can find a detailed description here Document types.
The parents are defined to give the user interface a hint where the the new document type should be placed.
The example process is only allowed directly at the root of a workspace.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process.ui-type">
<extension target="org.nuxeo.ecm.platform.types.TypeService" point="types">
<type id="DevCookbookExamplePapProcess">
<label>Public Administration Process</label>
<description>This is an example for a basic process in the public administration.</description>
<category>Collaborative</category>
<default-view>view_documents</default-view>
<layouts mode="any">
<layout>heading</layout>
</layouts>
<layouts mode="edit">
<layout>heading</layout>
<layout>dublincore</layout>
</layouts>
<icon>icons/folder.gif</icon>
<bigIcon>icons/folder_100.png</bigIcon>
</type>
<!-- only allow the basic process folder in the root of a workspace -->
<type id="Workspace">
<subtypes>
<type>DevCookbookExamplePapProcess</type>
</subtypes>
</type>
</extension>
</component>
|
The label defines the name of the component in the user interface. The category defines the group of objects in the interface where you can find the component.
So let's test your knowledge with the first tasks:
- Create a new workspace.
- Click on Create a new document.
- Look for the document type Public Administration Process
- Create a new process.
...
Actions
If you create a new type then you usually do this to better control the activities and the workflows inside this type.
The next step is to get some button to perform some actions or operation chains.
The first activity of every good paper tiger is the creation of a good description of the process. Mainly this includes a short name and a description of the content (e.g. parking permissions). Additionally requests are only accepted if there is a description. This means that the button has to do more than only one operation. Et voila, we need an operation chain.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process.actions">
<extension target="org.nuxeo.ecm.platform.actions.ActionService" point="actions">
<!-- first you have to create the description of the process -->
<action id="newDevCookbookExamplePapProcessDescription"
link="#{operationActionBean.doOperation('CreateDevCookbookExamplePapProcessDescription')}"
enabled="true"
order="10"
label="Create a process description"
icon="/icons/action_add.gif">
<category>SUBVIEW_UPPER_LIST</category>
<filter id="newDevCookbookExamplePapProcessDescription">
<rule grant="true">
<permission>AddChildren</permission>
<type>DevCookbookExamplePapProcess</type>
</rule>
</filter>
</action>
</extension>
</component>
|
...
Name | Description |
|---|---|
id | Name |
order | A hint where to place the button if there are several actions. |
label | The displayed name. |
icon | The used basic icon. |
link | The operation function call with the correct parameters. |
The operation is a specialty speciality here because it is the operation to call a chain. The parameter is the name of the chain.
...
The filter is a control that defines when to display an action. If you want to reuse a filter, you can do this by only specifying the matching id. Here we only want to see the button if we have the permission to create new documents in this folder (e.g. a description or a folder with the requests) and if the folder is of type DevCookbookExamplePapProcess.
Next step: define a chain
The description of the process
What is the job of the chain? First create a description, then create the folder for the requests and open the description to fill it with some content.
First we have to define the type and user interface for a special document type, the description. This is not quite different from a folder but it is documented here for completeness. Then the implementation of a chain is demonstrated.
Creating the Description document type
Defining the Description document type (process_description-type.xml)
Okay, let's see what's special here.
...
Training lesson
Before we start with the tests please remember that you can always look into the JAR archive to check your solution.
A request folder will contain the form from the customer, the approval or denial of a parking permission.
The parent of a request folder is a DevCookbookExamplePapProcessRequests folder.
Every customer form will be in a separate request folder.
Please create and fill the following files:
process_request-type.xmlprocess_request-ui-type.xml
Definition of a new file type
A normal file (which is not a folder) usually contains only meta data or additionally a real file.
So if you think in terms of a file system hierarchy then this is the place where to store your data.
If you want to store a text or PDF file in an ECM system then you store some raw data together with some meta data.
A file system knows such meta data too but it is very limited (e.g. ACLs, ownership, some timestamps, etc.).
If you use an ECM system then you can freely define which meta data you need and want to store.
Structural configuration
The structural definition is a little bit more complex here. Some more schemas are used to get additional features like unique identifiers and typical file meta data. The really interesting stuff are the facets.
| Tip | ||
|---|---|---|
| ||
If you look into your server log then you will usually see a warning that an unknown facet is used but this can be ignored (Nuxeo Enterprise Platform 5.5). |
The facets support us with two features.
Publishable makes it possible that the description can be copied to Sections. This area is used only to make documents available. You cannot edit documents in sections. So if you want to publish the description (e.g. to people who work in area 51), then a place in Sections would be right.
Versionable allows you to maintain different versions of the same document. This could be useful if you need to know all old descriptions. The versioning of Nuxeo is described later in this tutorial.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-description.type">
<extension target="org.nuxeo.ecm.core.schema.TypeService" point="doctype">
<doctype name="DevCookbookExamplePapProcessDescription" extends="Document">
<schema name="common"/>
<schema name="uid"/>
<schema name="dublincore"/>
<schema name="file"/>
<facet name="Versionable"/>
<facet name="Publishable"/>
</doctype>
</extension>
</component>
|
The description gets a uid and the regular properties of a file. The really interesting stuff are the facets. If you look into your server log then you will usually see a warning that an unknown facet is used but this can be ignored (Nuxeo Enterprise Platform 5.5).
The facet support us with two features.
Publishable makes it possible that the description can be copied to Sections. This area is used only to make documents available. You cannot edit documents in sections. So if you want to publish the description (e.g. to people who work in area 51), then a place in Sections would be right.
Versionable allows you to maintain different versions of the same document. This could be useful if you need to know all old descriptions.
...
User interface
The specification of the user interface only includes some minor interesting stuff. The special stuff for files is made available
and the descriptions are description is only allowed directly in the root folder of a process.
That's it. KISS.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-description.ui-type">
<extension target="org.nuxeo.ecm.platform.types.TypeService" point="types">
<type id="DevCookbookExamplePapProcessDescription">
<label>Process description</label>
<description>The description of the process.</description>
<category>SimpleDocument</category>
<default-view>view_documents</default-view>
<layouts mode="any">
<layout>heading</layout>
<layout>file</layout>
</layouts>
<layouts mode="edit">
<layout>heading</layout>
<layout>file</layout>
<layout>dublincore</layout>
</layouts>
<icon>icons/file.gif</icon>
<bigIcon>icons/file_100.png</bigIcon>
</type>
<!-- only the process folder itself should contain a process description -->
<type id="DevCookbookExamplePapProcess">
<subtypes>
<type>DevCookbookExamplePapProcessDescription</type>
</subtypes>
</type>
</extension>
</component>
|
Creating the automation chain (process_description-chains.xml)
...
Training lesson
If you want to test your understanding you can create now the following configurations:
- The user request form
process_request_form-type.xmlprocess_request_form-ui-type.xml
- The file which documents the approval of the user request
process_request_approval-type.xmlprocess_request_approval-ui-type.xml
- The file which documents the approval of the user request
- process_request_reject-type.xml
- process_request_reject-ui-type.xml
Please remember you can find all files in the JAR archive.
It is not necessary to do all the stuff by yourself.
Actions
Let's assume there is a customer request but there is no decision. So we need two buttons on the request form: one to approve and one to reject the request.
If you want to configure an action button on a file then this works a little bit different than on folders.
First you need to define a tab and then you can place buttons in this tab.
The example re-use the folder panel as tab.
Let's start with the approval of a request.
The category VIEW_ACTION_LIST defines a new tab in the document.
The XHTML document in the parameter link defines which template is used.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request-form.actions">
<extension target="org.nuxeo.ecm.platform.actions.ActionService" point="actions">
<action id="tabDevCookbookExamplePapProcessRequestDecision"
link="/incl/document_actions.xhtml"
enabled="true"
order="30"
label="Decision"
icon="/icons/file.gif">
<category>VIEW_ACTION_LIST</category>
<filter id="tabDevCookbookExamplePapProcessRequestDecision">
<rule grant="true">
<type>DevCookbookExamplePapProcessRequestForm</type>
<!-- permission:write -->
</rule>
</filter>
</action>
|
The category SUBVIEW_UPPER_LIST is the usual category from the folders.
This works here because the template is the default folder template.
| Code Block | ||
|---|---|---|
| ||
<action id="approveDevCookbookExamplePapProcessRequest"
link="#{operationActionBean.doOperation('ApproveDevCookbookExamplePapProcessRequest')}"
enabled="true"
order="20"
label="Approve the request"
icon="/icons/action_add.gif">
<category>SUBVIEW_UPPER_LIST</category>
<filter id="approveDevCookbookExamplePapProcessRequest">
<rule grant="true">
<type>DevCookbookExamplePapProcessRequestForm</type>
<!-- permission:write -->
</rule>
</filter>
</action>
</extension>
</component>
|
Now there is a tab called Decision with a button Approve request.
| Tip | ||
|---|---|---|
| ||
Please note that you can translate the labels. You can translate in the usual |
Training lesson
Okay, the task to implement the reject button should be very simple.
Please create the reject button (perhaps on another tab).
Operation chains
Operation chains are a very powerful tool. Every operation has an input and an output.
If you want to connect them like a pipe then you just have to ensure that the input and output are compatible.
This sounds complicated but it is very easy because there are not so many allowed input and output types:
- void
- document
- blob
The only problem is that even a list of documents is represented as a blob.
So you cannot directly use it but below you will find a workaround.
Basic stuff
The example shows the operation chain for the creation of the description of the process.
The configured action above defined the name CreateDevCookbookExamplePapProcessDescription for this chain.
What is the job of this chain?
First create a description, second create the folder for the requests and last open the description to fill it with some content.
First things first. Let's create the description. No, just a moment.
There are two very important functions - Seam.AddErrorMeessage and Seam.AddInfoMessage.
Please always add an error message in front of every operation or functional block to ensure that you get a meaningful error message.
If the operation chain ends then please add an info message which reports the success.
This costs some time but it enormous helpful if something fails and the user can give you meaningful error messages because they learned that every operation results in a message.
The following code snip is quite simple. It creates the description and give it the name description.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-description.chains">
<extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent" point="chains">
<chain id="CreateDevCookbookExamplePapProcessDescription">
<!--
Create a fresh description
Create folder requests
Open the description
-->
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">The description could not be created.</param>
</operation>
<operation id="Document.Create">
<param type="string" name="type">DevCookbookExamplePapProcessDescription</param>
<param type="string" name="properties">dc:title=description</param>
</operation>
|
Second Next the resulting document is loaded to the user interface. So if the chain is completed, the user sees it.
| Code Block | ||
|---|---|---|
| ||
...
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot load the description to the UI.</param>
</operation>
<operation id="Seam.NavigateTo">
</operation>
|
Perhaps you will ask now, why you do the last step first and what happens if something fails later?
The document is only available now as an input. I could store it in a variable but this is not necessary.
Operation chains work as a single transaction. So if something fails, nothing happened.
After the description is done, there is still one thing missing - the folder for the requests. So let's create it.
| Code Block | ||
|---|---|---|
| ||
...
<operation id="Document.GetParent">
</operation>
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">The folder for the requests could not be created.</param>
</operation>
<operation id="Document.Create">
<param type="string" name="type">DevCookbookExamplePapProcessRequests</param>
<param type="string" name="properties">dc:title=requests</param>
</operation>
|
Do you see the mistake? Just remember, first things first!
Finally the success message is setup. Please note that you can configure it at any place in the chain. The functions to set an error or an information just copy the input to the output. They are so called void functions.
| Code Block | ||
|---|---|---|
| ||
...
<operation id="Seam.AddInfoMessage">
<param type="string" name="message">The description was successfully created.</param>
</operation>
</chain>
</extension>
</component>
|
Question: Can you already execute the action now?
Next step: a folder for a customer requests
The request
The request folder and the customer form
Before we start with the tests please remember that you can always look into the JAR archive to check your solution.
First you should test your understanding of special folder types.
Second you should test your understanding of special document types.
A request folder will contain the form from the customer, the approval or denial of a parking permission.
The parent of a request folder is a DevCookbookExamplePapProcessRequests folder.
Every customer form will be in a separate request folder.
Please create and fill the following files:
process_request-type.xmlprocess_request-ui-type.xmlprocess_request_form-type.xmlprocess_request_form-ui-type.xml
Create a new request
...
Dynamic names / MVEL
The operation environment implements a very useful scripting engine MVEL.
So let's take a look at a simple example.
The example create a new customer request.
First we need to calculate which request it is. Please remember this is a public administration example.
So the name of 42 the 42nd request is request-42 and not request-41.
The first part includes the following steps:
...
Finally we know which request will be created the next and the parent folder is the input.
This is business as usual.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request.chains">
<extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent" point="chains">
<chain id="CreateDevCookbookExamplePapProcessRequest">
<!--
Input: Called from the folder with all requests
Calculate a name for the new folder
Create folder with title
Create customer_form
Open customer form
-->
<!-- get the folder as input -->
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot load the folder with the requests.</param>
</operation>
<!-- usually unnecessary -->
<operation id="Context.FetchDocument">
</operation>
<!-- determine number of children -->
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot calculate the name of the new file.</param>
</operation>
<operation id="Context.SetInputAsVar">
<param type="string" name="name">requests_folder</param>
</operation>
<operation id="Document.GetChildren">
</operation>
<operation id="Context.SetInputAsVar">
<param type="string" name="name">documents</param>
</operation>
<operation id="Context.SetVar">
<param type="string" name="name">request_count</param>
<param type="object" name="value">expr:@{(Context["documents"].size()+1).toString()}</param>
</operation>
<operation id="Context.RestoreDocumentInput">
<param type="string" name="name">requests_folder</param>
</operation>
|
Okay, I aggree that the determination of request_count is not hundred percent intuitive and the author needed some support from the community liaison too
![]()
Nevertheless it is possible to learn a lot from this one-liner.
Second the request folder will be created. Please note the usage of the dynamically calculated request count.
If you use variable then always use the context. The other stuff is quite intuitive.
| Code Block | ||
|---|---|---|
| ||
<!-- create structure -->
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot create a new request folder.</param>
</operation>
<operation id="Document.Create">
<param type="string" name="type">DevCookbookExamplePapProcessRequest</param>
<param type="string" name="properties">expr:dc:title=request-@{Context["request_count"]}</param>
</operation>
|
...
| Code Block | ||
|---|---|---|
| ||
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot create a new customer form.</param>
</operation>
<operation id="Document.Create">
<param type="string" name="type">DevCookbookExamplePapProcessRequestForm</param>
<param type="string" name="properties">dc:title=customer_form</param>
</operation>
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot load the new customer form.</param>
</operation>
<operation id="Seam.NavigateTo">
</operation>
<operation id="Seam.AddInfoMessage">
<param type="string" name="message">The new request was successfully created.</param>
</operation>
</chain>
</extension>
</component>
|
Next step: buttons on documents
Handle the request
There is now a request but there is no decision. So we need two buttons on the request form.
The important thing is that this works different from folders.
First you need to define a tab and then you can place buttons in this tab.
The example re-use the folder panel as tab.
Let's start with the approval of a request.
Approve a request
The document definition is just another document configuration.
So please check the JAR archive or see it as a training lesson.
There must be three files:
Training lessons
If you want to do some training then you can write some chains for the approval or denial of the customer requests.
You can create the according documents (e.g. a ticket for the car window) or you can send an email to the entrance (or an invoice to planet Mars).
Versioning
If you use a document management system or a similar system then you expect that documents have versions.
If you are a software developer then you are familiar with checkouts and commits.
If you approve a request then the customer form should get a new final version.
The following chain which implements the approval creates such a new major version.
| Code Block | ||||
|---|---|---|---|---|
|
...
|
...
The real work is the configuration of an action button in a document type.
The category VIEW_ACTION_LIST defines a new tab in the document.
The XHTML document in the parameter link defines which template is used.
| Code Block | ||
|---|---|---|
| xml | title | process_request_form-actions
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request-approval.chains">
<extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent" point="chains">
<chain id="ApproveDevCookbookExamplePapProcessRequest">
<!--
Create new major version
Create an approval
Remove write permission on request form
Open the approval
-->
<operation id="Document.CheckOut">
</operation>
<operation id="Document.CheckIn">
<param type="string" name="version">major</param>
<param type="string" name="comment">The request was approved.</param>
</operation>
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">The approval document could not be created.</param>
</operation>
<operation id="Document.GetParent">
</operation>
<operation id="Document.Create">
<param type="string" name="type">DevCookbookExamplePapProcessRequestApproval</param>
<param type="string" name="properties">dc:title=approval</param>
</operation>
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot load the approval to the UI.</param>
</operation>
<operation id="Seam.NavigateTo">
</operation>
<operation id="Seam.AddErrorMessage">
<param type="string" name="message">Cannot remove the write permissions from the customer form.</param>
</operation>
<operation id="Document.GetParent">
</operation>
<!-- this does not work if you work as Administrator ;) -->
<operation id="Document.SetACE">
<param type="string" name="permission">WriteProperties</param>
<param type="string" name="user"></param> <!-- this needs a real user -->
<param type="boolean" name="grant">false</param>
</operation>
<operation id="Seam.AddInfoMessage">
<param type="string" name="message">The approval was successfully created.</param>
</operation>
</chain>
</extension>
</component>
|
The initial check out is necessary to get a working copy of the document.
You can only check in documents which are a working copy.
The check in requires at minimum the parameter version which differs between minor or major check ins.
The final check after an approval is of course a major version.
The comment is optional which is a horror for people who uses revision control systems for software development.
One of the most important rule is: No commits without comments.
Training lesson
Please create the configuration process_request_reject-chains.xml as a test of your understanding.
Management of states (life cycle)
Perhaps you note the title of the section is not Status management.
The reason is simple: Nuxeo enforces a life cycle model which is some kind of a finite state machine.
If you need some kind of state for a folder or a document, then you have to define a life cycle including the allowed state transitions.
The definition is quite straight forward:
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?> <component name="org.nuxeo.dev.cookbook.example.pap.process-request-form.actionslifecycle"> <extension target="org.nuxeo.ecm.platformcore.actionslifecycle.ActionServiceLifeCycleService" point="actionslifecycle"> <action<lifecycle idname="tabDevCookbookExamplePapProcessRequestDecisionDevCookbookExamplePapProcessRequestPolicy" initial="new"> <states> link="/incl/document_actions.xhtml" <state name="new"> <transitions> enabled="true" <transition>approve</transition> order="30" label="Decision"<transition>reject</transition> </transitions> </state> <state name="approved"> </state> icon <state name="/icons/file.gifrejected"> <category>VIEW_ACTION_LIST</category> </state> <filter id="tabDevCookbookExamplePapProcessRequestDecision"</states> <transitions> <transition name="approve" destinationState="approved"> <rule grant="true" <description>Approve the request.</description> </transition> <transition name="reject" destinationState="rejected"> <type>DevCookbookExamplePapProcessRequestForm</type><description>Reject the request.</description> </transition> <!-- permission:write -- </transitions> </lifecycle> </extension> |
The states are only used by Nuxeo to define the allowed transition paths.
The states approved and rejected are only specified because it gives a better overview.
The state after a transition is only taken from destinationState.
If there is a typo in a state then this is a dead end without an error message.
After the definition of the life cycle, you have to apply the life cycle to one or more document types.
You can re-use such life cycles and you can use life cycles to filter actions.
The filter configuration does not support a state filter directly but there is an operation Operation Document.Filter which supports a state filter.
| Code Block | ||
|---|---|---|
| ||
... <extension target="org.nuxeo.ecm.core.lifecycle.LifeCycleService" point="types"> <types> </rule> <type name="DevCookbookExamplePapProcessRequest">DevCookbookExamplePapProcessRequestPolicy</type> </filter>types> </extension> </action>component> |
The category SUBVIEW_UPPER_LIST is the usual category from the folders.
This works here because the template is the default folder templateintegration of the life cycle into the configuration is simple.
The initial state is predefined in the life cycle configuration.
You have only to add a state change in the chain of the approval (Operation Document.SetLifeCycle).
The parameter is the name of the transition.
| Code Block | ||||
|---|---|---|---|---|
| ||||
... <action <operation id="approveDevCookbookExamplePapProcessRequestDocument.GetParent"> </operation> link="#{operationActionBean.doOperation('ApproveDevCookbookExamplePapProcessRequest')}" enabled="true" <!-- Document is now the request folder --> <operation id="Document.SetLifeCycle"> <param type="string" name="value">approve</param> </operation> ... |
Training lesson
If you want to verify your knowledge then you can create a life cycle for the request form itself.
Alternatively you can only add the state change to the reject chain.
Extension of meta data
Sometimes you need more meta informations than usual.
If you want to manage such a special place like area 51 then you need several different kinds of parking places.
You have cars, air planes, x-planes and of course ufos ![]()
Structural configuration
First you need to extend the meta data of a document type.
Such structural changes are done via XML schemas.
So let's introduce a new vehicle type:
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://project.nuxeo.org/schemas/dev/cookbook/example/pap/process-request-form/" xmlns:area51="http://project.nuxeo.org/schemas/dev/cookbook/example/pap/process-request-form/" > <xs:simpleType name="vehicle_type"> <xs:restriction base="xs:string"> order<xs:enumeration value="20Car"/> <xs:enumeration value="Airplane"/> label <xs:enumeration value="Approve the request"X-Plane"/> <xs:enumeration value="Ufo"/> </xs:restriction> </xs:simpleType> <xs:element iconname="/icons/action_add.gif"> parking_area" type="area51:vehicle_type"/> </xs:schema> |
So we have now a schema with exactly one element called parking_area.
Now need to make it available.
This requires a small extensions of the type definition:
| Code Block | ||||
|---|---|---|---|---|
| ||||
<?xml version="1.0"?> <component name="org.nuxeo.dev.cookbook.example.pap.process-request-form.type"> <category>SUBVIEW_UPPER_LIST</category> <extension target="org.nuxeo.ecm.core.schema.TypeService" point="schema"> <schema name="area51" src="schemas/process_request_form.xsd" prefix="area51" /> </extension> <filter<extension idtarget="approveDevCookbookExamplePapProcessRequest"org.nuxeo.ecm.core.schema.TypeService" point="doctype"> <doctype name="DevCookbookExamplePapProcessRequestForm" extends="Document"> <rule grant <schema name="truecommon"/> <schema name="uid"/> <type>DevCookbookExamplePapProcessRequestForm</type> <schema name="dublincore"/> <!-- permission:write -- <schema name="file"/> </rule><schema name="area51"/> </filter><facet name="Versionable"/> </action>doctype> </extension> </component> |
Next step: Create your own button
Reject a request
Okay, the final tasks should be very simple. Please create the following files:
- process_request_reject-type.xml
- process_request_reject-ui-type.xml
- process_request_form-actions.xml
- process_request_reject-chains.xml
The change is minimal. You need to import the schema via the according extension point
and you must add one line to the document type definition. That's it.
User interface
Second you must modify the user interface. This is much more tricky in this case.
You need in this case a new database table, a new widget and a new layout.
Finally you have to extend the type for the user interface itself.
Configure a new database table
The data source is called directory in Nuxeo.
Such directories can be LDAP directories or databases.
If you want to import some list data then you must store them in a CSV file and import it.
| Code Block | ||
|---|---|---|
| ||
id,label,obsolete
"Car","Car","0"
"X-Plane","X-Plane","0"
"Airplane","Airplane","0"
"Ufo","Ufo","0"
|
The import is quite simple:
| Code Block | ||||
|---|---|---|---|---|
| ||||
<extension target="org.nuxeo.ecm.directory.sql.SQLDirectoryFactory" point="directories">
<directory name="vehicle_type">
<schema>vocabulary</schema>
<dataSource>java:/nxsqldirectory</dataSource>
<cacheTimeout>3600</cacheTimeout>
<cacheMaxSize>1000</cacheMaxSize>
<table>area51_vehicle_type</table>
<idField>id</idField>
<autoincrementIdField>false</autoincrementIdField>
<dataFile>directories/vehicle_type.csv</dataFile>
<createTablePolicy>on_missing_columns</createTablePolicy>
</directory>
</extension>
|
Configure a new widget
After you have now a new directory which is filled with the data from a CSV file you need to create a widget.
A widget includes the form field or the field value depending on the situation.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<extension target="org.nuxeo.ecm.platform.forms.layout.WebLayoutManager" point="widgets">
<widget name="type_of_vehicle" type="selectOneDirectory">
<labels>
<label mode="any">Vehicle type</label>
</labels>
<translated>true</translated>
<fields>
<field>area51:parking_area</field>
</fields>
<properties widgetMode="any">
<property name="directoryName">vehicle_type</property>
<property name="localize">true</property>
<property name="ordering">label</property>
</properties>
</widget>
|
The widget defines some usual stuff like name, label and meta data field id.
The interesting stuff here is the widget type.
You can find the available basic widget types at the Standard widget types.
selectOneDirectory implements a select field. The options are taken from the directory called vehicle_type.
This directory was defined above. localize means that the label is translatable.
Configure a new layout
The new wigdet is data only. It is necessary to layout the data.
This is done with the creation of a new layout:
| Code Block | ||||
|---|---|---|---|---|
| ||||
<require>org.nuxeo.ecm.platform.forms.layouts.webapp</require>
<extension target="org.nuxeo.ecm.platform.forms.layout.WebLayoutManager" point="layouts">
<layout name="area51">
<templates>
<template mode="any">/layouts/layout_default_template.xhtml</template>
</templates>
<rows>
<row>
<widget>type_of_vehicle</widget>
</row>
</rows>
</layout>
</extension>
|
Now we have a new layout area51 which uses the widget type_of_vehicle.
Use the new layout
The new layout area51 must be used by the user interface itself.
This is quite simple. The layout must be added to every layouts section.
| Code Block | ||||
|---|---|---|---|---|
| ||||
<layouts mode="edit">
<layout>heading</layout>
<layout>file</layout>
<layout>dublincore</layout>
<layout>area51</layout>
</layouts>
|
Training lesson
If you want to test your knowledge and understanding then simply add a new field.
Just use your fantasy.
| Tip | ||
|---|---|---|
| ||
If you are looking for an example of another feature then please feel free to comment or change the TODO list. |