Excel is no doubt the most commonly used business application in the world. It is so easy that almost anyone can pick it up without learning. It is powerful in the sense that anyone can design a template and style it nicely; anyone can create custom reports or forms with its powerful built-in features like sorting, filtering, charting and conditional formatting; it even comes with advanced formula functions for professionals.
However, Excel Sheets are scattered among each user and require frequent uploading/downloading/emailing when traveling among systems and users.
What if we can seamlessly integrate these Excel Sheets into our workflow allowing the Sheets to always connect to the latest data from the database; be shared among multiple users in real-time; or automated with other systems? Spreadsheet users can continue working with the familiar Excel interface, while the IT department can have better control over the data. Business departments can even create their own forms and UI with Excel so that they can actually participate deeper in the application development with their domain know-how. Whenever a new form or report is needed, they can design and create the custom report or form just the way they do in Excel, without having to ask the IT department to create another program from scratch.
This article demonstrates how you can import a typical Excel template and turn it into a workflow application using Keikai.
To better understand this article and its code example, I assume that you already have the basic knowledge of HTML and Java EE web components (JSP and servlet) and that you have read Keikai Getting Started. We are also using the ZK Framework to provide the application context, but knowledge of ZK is not necessary to follow this article.
Keikai is especially useful when building applications involving form filling or formula calculation. In this article, I will demonstrate how you can create a Form Submission Workflow Application with Keikai.
First, I'd like to share with you some background of this example. There are 2 roles in this application:
The whole workflow is as illustrated as below:
Assuming we already have an Application for Leave and a Business Travel Request form designed in Excel, and that we have already thought about the workflow and defined the roles. In this scenario, there should be 4 kinds of actions in each form: submit, cancel, approve, and reject:
leave form in Excel
Now we are ready to turn these templates into a Web application. First, we will quickly take a look at the architecture.
From a Java implementation perspective, the architecture is:
ZK Framework
: handle HTTP request/responseWorkflow Composer
: A ZK composer (*), which implement the whole workflow logicWorkflowDao
: query, insert, and update a form submission record in the database(*) Composers are ZK objects that control part of a ZK page UI. They are used to listen to user action and to update the page based on user's choices.
We start by building the UI. In this application, I build every page with a sheet which contains clickable cells for actions.
Keikai supports binding mouse clicks events on a sheet. You can treat a sheet as a page and put background colors on cells, a larger text as a title, and a data validation control for drop-down selection. Anyone who can use Excel can build a page, no technical skills are required.
First, I build a sheet to represent each workflow stage: the main sheet, form list sheet, and submission list sheet. Then, when a user moves among stages, I just need to switch to and display the corresponding sheets accordingly.
For example, when a user enters the application as an "Employee", the workflow application just activates (displays) "form list" sheet to the user. This is how we do page navigation easily.
As mentioned in the previous section, anyone can build a UI page with a sheet easily. Here are some tips to keep in mind since these sheets will be used as part of the application.
Normally, users are free to edit any cell in a worksheet. But if I take a sheet as an application UI, I need to enable proper protection on the sheet to avoid them breaking the application. For example, if users delete a sheet or a role, then they can not enter or use the workflow anymore. This sounds funny but it could create unexpected issues in a business usecase.
So I need to define what users are allowed to do, to avoid such a disaster.
Hiding sheet tabs can avoid users performing any sheet operation such as switching sheets. With this, the application can fully control a user's stage transition without running into undesired surprises.
This can be done by set showSheetbar to false on the spreadsheet.
spreadsheet.setShowSheetbar(false);
Both the form list and the submission list are just a read-only list for selection, I don't want users to change these lists. Hence, I can easily make them read-only by sheet protection with the following API.
private static final SheetProtection READ_ONLY = SheetProtection.Builder.create().withSelectLockedCellsAllowed(true).build();
...
Ranges.range(spreadsheet.getSelectedSheet()).protectSheet(READ_ONLY);
If anyone tries to edit a cell, Keikai will pop up a warning message:
Note that even when the sheet is read-only, it is still possible to allow some user actions like selecting locked/unlocked cells, so that users can see a selection box when clicking a cell. Developers and designers can decide to turn on/off these actions depending on the actual use cases. For available user actions, please check the protectSheet JavaDoc and the SheetProtection JavaDoc.
Since I don't want users to edit cells in these sheets, I decided to hide the toolbar to avoid confusing users. This can be done directly on the spreadsheet object as shown below:
spreadsheet.setShowToolbar(false);
In order to guide users going to the next stage(sheet) of the workflow, I need to add "button" clicking listeners and show the corresponding sheet to a user by activating the sheet.
When an Employee clicks the "Enter" button, I activate the "form list" sheet and it will lead him to that sheet:
The first step is to listen to click events from the spreadsheet object. In this case, I've used the ZK @Listen annotation on my onClick event listener.
When we loaded the spreadsheet, the active workbook received the name "MAIN". We will use this name to trigger the workflow handleClickMain(Event event);
add a listener for the spreadsheet ON_CELL_CLICK event
@Listen(Events.ON_CELL_CLICK + "= #spreadsheet")
public void onClick(CellMouseEvent event) {
String sheetName = event.getSheet().getSheetName();
...
}
Identify the current worksheet, and trigger the handleClickMain(Event event) method
switch (sheetName) {
case MAIN:
handleClickMain(event);
break;
...
from the main page, check for the user role, and open the relevant workbook
private void navigateToList() {
switch (currentRole) {
case SUPERVISOR:
navigateTo(SUBMISSION_LIST);
break;
case EMPLOYEE:
navigateTo(FORM_LIST);
break;
}
}
Change the view to a different sheet in the workbook
private void navigateTo(String sheetName) {
spreadsheet.setSelectedSheet(sheetName);
if (MAIN.equals(sheetName)) {
spreadsheet.focusTo(roleCell.getRow(), roleCell.getColumn());
}
}
spreadsheet.setActiveWorksheet(SHEET_FORM)
to select a sheet by its name.The same technique can be applied for "Cancel", "Approve", and "Reject" buttons, and when users click on these buttons I will show the corresponding sheets. Please check navigateTo(String sheetName)
in WorkflowComposer.java
.
After activating the "form list" sheet, I populate form file list with Range setCellValue():
for (int index = 0; index < submissionList.size(); index++) {
Submission s = submissionList.get(index);
Ranges.range(sheet, START_ROW + index, 2).setCellValue(s.getId());
When an Employee selects a file in the form list by clicking a specific cell, I want to show him the corresponding form. To implement this, I need to listen to mouse click events on cells and check the value of the target cell to identify the form.
The event itself is retrived from the ON_CELL_CLICK listener declared above. Since our active page will be the form list, the listener will apply the method associated to this workbook.
From the spreadsheet ON_CELL_CLICK event, trigger the handleClickFormList(Event event) method
@Listen(Events.ON_CELL_CLICK + "= #spreadsheet")
public void onClick(CellMouseEvent event) {
String sheetName = event.getSheet().getSheetName();
switch (sheetName) {
...
case FORM_LIST:
handleClickFormList(event);
break;
}
Retrieve the matching excel file from storage, and import it as the current workbook
int index = getClickedCell(event).getRow() - START_ROW;
...
File selectedForm = formList.get(index);
try {
Book book = Importers.getImporter().imports(selectedForm, selectedForm.getName());
spreadsheet.setBook(book);
};
There are in total 4 possible buttons in the sheet. However since an Employee can only Submit or Cancel a form, I have to hide some buttons and show only the relevant buttons upon their role. This can be done by renaming the 2 actual buttons dynamically while loading the form:
private void addButtons(Sheet sheet) {
Range button1 = Ranges.rangeByName(sheet, BUTTON1);
Range button2 = Ranges.rangeByName(sheet, BUTTON2);
switch (currentRole) {
case SUPERVISOR:
button1.setCellValue(REJECT);
button2.setCellValue(APPROVE);
break;
case EMPLOYEE:
button1.setCellValue(CANCEL);
button2.setCellValue(SUBMIT);
break;
}
}
The buttons button1 and button2 can then be used in a click listener. I can read they current name and trigger the associated method in the composer:
private void handleClickForm(CellMouseEvent event) {
Range button1 = Ranges.rangeByName(event.getSheet(), BUTTON1);
Range button2 = Ranges.rangeByName(event.getSheet(), BUTTON2);
Range clickedCell = getClickedCell(event);
if (clickedCell.asString().equals(button1.asString())
|| clickedCell.asString().equals(button2.asString())) {
String label = clickedCell.getCellValue().toString();
switch (label) {
case APPROVE:
approve();
break;
case REJECT:
reject();
break;
case SUBMIT:
submitForm();
break;
}
exitForm();
}
}
I also want to prevent users from modifying some part of the form, so I protect the whole sheet and set editable cells unlocked in Excel. Then users can only edit these unlocked fields: From, To, and Reason for Leave:
You can also lock and unlock cells with API:
Apply locked cells on top of existing style properties
for (String cell : cells){
Range range = Ranges.range(spreadsheet.getSelectedSheet(), cell);
CellStyle oldStyle = range.getCellStyle();
EditableCellStyle newStyle = range.getCellStyleHelper().createCellStyle(oldStyle);
newStyle.setLocked(true);
range.setCellStyle(newStyle);
}
When an Employee submits a form, I save the whole book content into the database so that a Supervisor can review later. Keikai supports exporting the whole workbook as a byte array, and I can store the workbook as a BLOB in a table.
private void submitForm() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
lockCells();
Exporters.getExporter().export(spreadsheet.getBook(), outputStream);
Submission submission = new Submission();
submission.setForm(outputStream);
submission.setFormName(spreadsheet.getBook().getBookName());
submission.setOwner(this.currentRole);
workflowDao.insert(submission);
submissionChange = true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Submission
object and stores form content as a ByteArrayOutputStream
. Then it inserts Submission
into a table.Once this is done, I can import the byte array and show the submitted form content with Keikai when needed.
Instead of exporting the whole content, it is also possible to extract and store the cell values you care (e.g. From, To, Reason for Leave) into the database. When a supervisor reviews a form, just import the form template and populate these stored cell values from the database to corresponding cells.
In the previous sections I have mentioned 2 approaches to store a workbook's content:
One of the advantages to export the whole workbook is that it will not be affected by any future form layout change because there is no code to extract specific cell value, like spreadsheeet.getRange("B2").getValue()
. Therefore, if someone adds more rows or changes the position of "To" field in "Leave Application" form, I don't need to modify the Java code.
Also in the case where users are allowed to change the sheet layout and format, or in the case that you need to keep the change history of a file for a security reason, it is better to export the content and store it as a whole. The down-side of this approach is that it is not possible to query the content of the stored byte array by SQL, and it takes up more space.
If you take the 2nd approach and store only the cell values instead of the whole content, the advantage is that it separates the data and the file template clearly and saves storing spaces. Also, you can query data when needed. However, if the form is being changed later, you will need to modify the Java code about getting cell values of "From".
The best advice I can give is: choose the way that suits your requirement the most.
You can also populate a Submission list queried from a database with the Range
API:
When a Supervisor clicks a submission in WAITING state, we should import the submitted byte array into Keikai spreadsheet. You can implement this with an event listener and a button action as I mentioned in the previous sections.
I have demonstrated how you can build a workflow application easily using Keikai. To wrap up, the benefits are:
You can create all UI pages with spreadsheets, and no need to worry about dealing with any web UI frameworks. Anyone who knows how to use a Spreadsheet can build a page, and the resulting web page will just look like what you designed in the spreadsheet or Excel.
Since the end users are normally those who know the best about their business needs and process, it's better to let them create forms and pages by themselves instead of letting developers do the work. This can reduce the communication cost/loss between end users and the technical team.
With Keikai Range
API, you can populate business data from the database into sheets. It also allows you to retrieve what user input into the sheet in your preferred way.
By adding event listeners on cells, you can apply your business rules implemented by Java to the whole workflow.
I have demonstrated how you can build a workflow application with Keikai. Feel free to tell us what other applications we can show you.