How to extend another module?

This tutorial will show you how to extend an already existing module. This is actually a description how we implemented the Stoppages module.

Use case

First - there should be a possibility to view list of stoppages for single production order by clicking button on details page:

Second - there should be menu which open tab with all stoppages:

Getting started

Before You start - You should check this pages:

Model definition

First we must define the stoppage entity for our plugin. As in the Use case we will need the following elements:

  • order - each stoppage is in relation with one order
  • duration - integer field that shows the stoppage duration in minutes
  • reason - text field in which we give a text description why the stoppage occurred

Each field is required. We will define the entity in the file src/main/resources/stoppage/model/stoppage.xml:

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 the <modules> tag:

<model:model model="stoppage" resource="model/stoppage.xml" />

As we use in our plugin an entity from another module (order entity) we must also add a dependency definition to qcadoo-plugin.xml <plugin> tag:

<dependencies>
	<dependency>
		<plugin>orders</plugin>
		<version>[0.4.1</version>
	</dependency>
</dependencies>

and into pom.xml in the <dependencies> tag:

<dependency>
	<groupId>com.qcadoo.mes</groupId>
	<artifactId>mes-plugins-orders</artifactId>
	<version>0.4.1</version>
</dependency>

We can now define that an order could have many stoppages (we'll use it in views). Back to qcadoo-plugin.xml, go to the <modules> element and 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 the full data model needed for this plugin. The full qcadoo-plugin.xml plugin descriptor should look like this:

<?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 views for the All stoppages window tab. First we must add the following things to the plugin descriptors qcadoo-plugin.xml <modules> element:

  • Stoppages menu item in Production orders menu.
  • Views definition (for grid and edit form).
<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 allStoppages view:

view/allStoppages.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="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>

The gridNewCopyAndRemoveAction template will add a button toolbar buttons with create, copy or remove actions for the grid. In the grid it self we have three columns: order, duration, reason.

Please notice how the production orders name is displayed (using expression). 

We have also define the corresponding view (stoppage/allStoppagesForm). We can set here correspondingViewInModal to true if we want the add/edit form to be in a modal window.

View Definition

For more information check View Definition Overview wiki page.

Now lets  create the add/edit form it self:

view/allStoppagesForm.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="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>

Here we additionally used another template for buttons - navigation. It will add a Back button to our window. Then we have defined components in which we can edit the: order, duration and reason.

Notice that the first component is a lookup - this is a input field which allows you to choose an entity by using a grid or a pop-up that shows as you type.

Views for Stoppages for order

Stoppages for an order should be shown by clicking on the Stoppage button in order details tab. We must add this button using a ribbon extension.
We'll do this in the file src/main/resources/stoppage/view/ribbonExtension/ribbonExtensionDetails.xml:

File 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 created an additional ribbon group stoppage and added a button stoppage with an icon (which is stored in src/main/resources/stoppage/public/css/icons/iconStop24.png). When it will get clicked then it will performe the  showStoppage event which is defined in the element <modules> from qcadoo-plugin.xml:

<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 the showStoppage method from the com.qcadoo.mes.stoppage.StoppageService class.

Now we also have to add a ribbon extension group view in the same file:

<view:view-ribbon-group resource="view/ribbonExtension/ribbonExtensionDetails.xml" />

Lets implement the StoppageService class in the file 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 a modal window with the view stoppage. It will also set the current order id. If we want to open this view in the main application window we can use this code spitte:

viewDefinitionState.redirectTo(url, false, true);

Now we can create a view with the grid which will contain stoppage entities for a selected order in the file src/main/resources/stoppage/view/stoppage.xml:

File 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 the order enity in the orders plugin (not stoppage - this is very important). Afterwards we add a field to the order entity which will make the relationship to stoppages bidirectional.

<model:model-field plugin="orders" model="order">
 <model:hasMany name="stoppages" plugin="stoppage"
 model="stoppage" joinField="order" cascade="delete" />
</model:model-field>

Rest of the grids definition is standard as in allStoppages.xml but the corresponding view will be different: stoppage/stoppageForm.

Wil define it in the file src/main/resources/stoppage/view/stoppageForm.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="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>