This article will show you how to extend already existing modules. This is actually a description how we implemented the Stoppages module.
Use case
The use case is little different than on page Stoppage and makes plugin more extended. First - there should be a possibility to view list of stoppages for single production order by clicking button on details page:
Getting started
Before You start - You should check this pages:
- Tutorial - to get some knowledge about qcadoo plugin structure
- Setup a new plugin - page shows how to generate empty project
Model definition
First we must define model for our plugin. As in Use case we need following elements:
- order - each stoppage is in relation with some order
- duration - integer field for stoppage duration
- reason - text field for reason
Each field should be required. We created following model in file src/main/resources/stoppage/model/stoppage.xml:
<?xml version="1.0" encoding="UTF-8"?> <model name="stoppage" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/model" xsi:schemaLocation="http://schema.qcadoo.org/model http://schema.qcadoo.org/model.xsd"> <fields> <belongsTo name="order" model="order" plugin="orders" required="true" /> <integer name="duration" required="true" /> <text name="reason" required="true" /> </fields> <hooks> </hooks> </model>
Model Fields
For more information check Model Fields wiki page.
Now we must add information about model in src/main/resources/qcadoo-plugin.xml into <modules> tag:
<model:model model="stoppage" resource="model/stoppage.xml" />
As we use in our plugin other module(orders) we must also add dependency definition to qcadoo-plugin.xml <plugin> tag:
<dependencies> <dependency> <plugin>orders</plugin> <version>[0.4.1</version> </dependency> </dependencies>
and to pom.xml <dependencies> tag:
<dependency> <groupId>com.qcadoo.mes</groupId> <artifactId>mes-plugins-orders</artifactId> <version>0.4.1</version> </dependency>
We can here now define that order could have many stoppages (we'll use it in views). Back to qcadoo-plugin.xml and go to <modules> section, then add:
<model:model-field plugin="orders" model="order"> <model:hasMany name="stoppages" plugin="stoppage" model="stoppage" joinField="order" cascade="delete" /> </model:model-field>
Now we have model properly defined. One look for our qcadoo-plugin.xml file:
<?xml version="1.0" encoding="UTF-8"?> <plugin plugin="stoppage" version="0.4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/plugin" xmlns:model="http://schema.qcadoo.org/modules/model" xmlns:view="http://schema.qcadoo.org/modules/view" xmlns:menu="http://schema.qcadoo.org/modules/menu" xmlns:localization="http://schema.qcadoo.org/modules/localization" xsi:schemaLocation=" http://schema.qcadoo.org/plugin http://schema.qcadoo.org/plugin.xsd http://schema.qcadoo.org/modules/model http://schema.qcadoo.org/modules/model.xsd http://schema.qcadoo.org/modules/view http://schema.qcadoo.org/modules/view.xsd http://schema.qcadoo.org/modules/menu http://schema.qcadoo.org/modules/menu.xsd http://schema.qcadoo.org/modules/localization http://schema.qcadoo.org/modules/localization.xsd"> <information> <name>MES - Stoppage</name> <vendor> <name>Qcadoo Limited</name> <url>http://qcadoo.com/</url> </vendor> </information> <dependencies> <dependency> <plugin>orders</plugin> <version>[0.4.1</version> </dependency> </dependencies> <modules> <localization:translation path="locales" /> <model:model model="stoppage" resource="model/stoppage.xml" /> <model:model-field plugin="orders" model="order"> <model:hasMany name="stoppages" plugin="stoppage" model="stoppage" joinField="order" cascade="delete" /> </model:model-field> <!-- We will add views definitions here soon --> <view:resource uri="public/**/*" /> </modules> </plugin>
Views for All Stoppages
Now we will create little easier views for All stoppages window tab. First we must add to qcadoo-plugin.xml into <modules>:
- Stoppages menu item in Production orders menu.
- Views definition (for grid and edit form).
Check following lines:
<menu:menu-category name="orders" /> <menu:menu-item name="stoppages" category="orders" view="allStoppages" /> <view:view resource="view/allStoppages.xml" /> <view:view resource="view/allStoppagesForm.xml" />
We placed stoppage menu item in orders category and bound it with allStoppages view.
Now we must create view allStoppages:
<?xml version="1.0" encoding="UTF-8"?> <view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/view" xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd" name="allStoppages" modelName="stoppage" modelPlugin="stoppage"> <component type="window" name="window"> <ribbon> <group template="gridNewCopyAndRemoveAction" /> </ribbon> <component type="grid" name="stoppage" reference="grid"> <option type="column" name="order" fields="order" expression="#order['name']" link="true" width="40" /> <option type="column" name="duration" fields="duration" link="true" width="20" /> <option type="column" name="reason" fields="reason" link="true" /> <option type="correspondingView" value="stoppage/allStoppagesForm" /> <option type="correspondingComponent" value="form" /> <option type="correspondingViewInModal" value="false" /> <option type="searchable" value="reason, duration" /> <option type="orderable" value="reason, duration" /> <option type="fullscreen" value="true" /> <option type="multiselect" value="true" /> <option type="order" column="duration" direction="desc" /> </component> <option type="fixedHeight" value="true" /> <option type="header" value="false" /> </component> </view>
In view tag we have defined name of the model(allStoppages), model which we use(stoppage) and model plugin(stoppage).
gridNewCopyAndRemoveAction template will add to button toolbar buttons for create, copy or remove entry from grid. Next we have grid defined. Three columns (order, duration, reason). Check how production order name is displayed(using expression).
We have also define corresponding view (stoppage/allStoppagesForm). It will be add/edit form for grid. We can set here correspondingViewInModal to true if we want add/edit form in modal window.
View Definition
For more information check View Definition Overview wiki page.
Then we create add/edit form view:
<?xml version="1.0" encoding="UTF-8"?> <view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/view" xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd" name="allStoppagesForm" modelName="stoppage"> <component type="window" name="window"> <ribbon> <group template="navigation" /> <group template="formSaveCopyAndRemoveActions" /> </ribbon> <component type="form" name="form" reference="form"> <component type="lookup" name="order" field="order"> <option type="column" name="name" fields="name" link="true"/> <option type="searchable" value="name,number" /> <option type="orderable" value="name,number" /> <option type="fullScreen" value="true" /> <option type="expression" value="#name" /> <option type="fieldCode" value="number" /> </component> <component type="input" name="duration" field="duration" /> <component type="textarea" name="reason" field="reason" /> <option type="expression" value="#reason + ' (' + #duration +' min)'" /> <option type="header" value="true" /> </component> </component> </view>
We use here additionally one other template for buttons - navigation. It will add button Back for our window(modal or not). Then we have definition of form component with three elements - order, duration and reason. Second and third input are normal text inputs (for reason it is textarea). First is lookup - this is input field with in-line search and grid search module (after clicking on magnifier icon new window will be opened with production orders grid).
Important things here are:
- lookup definition
- expression tag for component form
Views for Stoppages for order
Stoppages for order should be shown by clicking on Stoppage button in order details tab. We must add button using ribbon extension.
Create file src/main/resources/stoppage/view/ribbonExtension/ribbonExtensionDetails.xml:
<?xml version="1.0" encoding="UTF-8"?> <ribbonExtension xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/modules/ribbonExtension" xsi:schemaLocation="http://schema.qcadoo.org/modules/ribbonExtension http://schema.qcadoo.org/modules/ribbonExtension.xsd" plugin="orders" view="orderDetails"> <group name="stoppage"> <bigButton name="stoppage" action="#{form}.fireEvent(showStoppage);" icon="../../../../../stoppage/public/css/icons/iconStop24.png" disabled="true" /> </group> </ribbonExtension>
We create additional ribbon group stoppage and add button stoppage with icon (which is stored in src/main/resources/stoppage/public/css/icons/iconStop24.png). Attribute action performs event showStoppage which is defined in qcadoo-plugin.xml <modules>:
<view:view-listener plugin="orders" view="orderDetails" component="form" event="showStoppage" class="com.qcadoo.mes.stoppage.StoppageService" method="showStoppage" />
As we can see it refers to showStoppage method from com.qcadoo.mes.stoppage.StoppageService class.
Then we add also ribbon extension group view in the same file:
<view:view-ribbon-group resource="view/ribbonExtension/ribbonExtensionDetails.xml" />
StoppageService class
Create new class in src/main/java/com/qcadoo/mes/stoppage/StoppageService.java:
package com.qcadoo.mes.stoppage; import java.math.BigDecimal; import java.util.Date; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.qcadoo.localization.api.TranslationService; import com.qcadoo.view.api.ComponentState.MessageType; import com.qcadoo.view.api.components.FormComponent; import com.qcadoo.model.api.DataDefinition; import com.qcadoo.model.api.DataDefinitionService; import com.qcadoo.model.api.Entity; import com.qcadoo.model.api.Entity; import com.qcadoo.mes.orders.constants.OrdersConstants; import com.qcadoo.security.api.SecurityService; import com.qcadoo.view.api.ComponentState; import com.qcadoo.view.api.ViewDefinitionState; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; @Service public class StoppageService { public void showStoppage(final ViewDefinitionState viewDefinitionState, final ComponentState triggerState, final String[] args){ Long orderId = (Long) triggerState.getFieldValue(); if(orderId != null){ String url = "../page/stoppage/stoppage.html?context={\"order.id\":\""+orderId+"\"}"; viewDefinitionState.openModal(url); } } }
showStoppage will open modal window with view stoppage, and also set current order id (for empty form component which I'll descrie later) . If we want to open this view in main application window we can use here:
viewDefinitionState.redirectTo(url, false, true);
Now we can create view with grid which will contains stoppage entries for selected stoppage in file src/main/resources/stoppage/view/stoppage.xml:
<?xml version="1.0" encoding="UTF-8"?> <view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/view" xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd" name="stoppage" modelName="order" modelPlugin="orders"> <component type="window" name="window"> <ribbon> <group template="navigation" /> <group template="gridNewCopyAndRemoveAction" /> </ribbon> <component type="form" name="order" reference="order"> </component> <component type="grid" name="stoppage" reference="grid" source="#{order}.stoppages"> <option type="column" name="duration" fields="duration" link="true" width="20" /> <option type="column" name="reason" fields="reason" link="true" /> <option type="correspondingView" value="stoppage/stoppageForm" /> <option type="correspondingComponent" value="form" /> <option type="correspondingViewInModal" value="false" /> <option type="searchable" value="reason, duration" /> <option type="orderable" value="reason, duration" /> <option type="fullscreen" value="true" /> <option type="multiselect" value="true" /> <option type="order" column="duration" direction="desc" /> </component> <option type="fixedHeight" value="true" /> <option type="header" value="false" /> </component> </view>
Notice that modelName and modelPlugin refers to order model and orders plugin (not stoppage - this is very important). Then we create empty form component order with reference to order. As we defined
<model:model-field plugin="orders" model="order"> <model:hasMany name="stoppages" plugin="stoppage" model="stoppage" joinField="order" cascade="delete" /> </model:model-field>
before - we can now fill grid using as it source: #{order}.stoppages. Rest of grid definition is standard as in allStoppages.xml. There is reference to add/edit form view stoppage/stoppageForm.
Now we create stoppage add/edit form view - file src/main/resources/stoppage/view/stoppageForm.xml contains following view definition:
<?xml version="1.0" encoding="UTF-8"?> <view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/view" xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd" name="stoppageForm" modelName="stoppage"> <component type="window" name="window"> <ribbon> <group template="navigation" /> <group template="formSaveCopyAndRemoveActions" /> </ribbon> <component type="form" name="form" reference="form"> <component type="input" name="duration" field="duration" /> <component type="textarea" name="reason" field="reason" /> <option type="expression" value="#reason + ' (' + #duration +' min)'" /> <option type="header" value="true" /> </component> </component> </view>
Model used here is stoppage, because we refer here to single record from model stoppage, not from order stoppages.
Translation for labels and other elements
You can of course edit src/main/resources/stoppage/locales/stoppage_XX.properties during component developing process (XX is two-letter language code). But if You don't know how labels are named a good idea is to use your plugin (check all windows etc.) and then check qcadoo-path/logs/translation.log for Missing translation entries. And then of course add all of them to stoppage_XX.properties and fill with translations.
Now - Stoppage module is complete. To compile it run mvn install in module main directory.