Complete frontend extension sample
This sample shows how to quickly extend OpenKM features with JSP, HTML, CSS and JavaScript technologies using OpenKM Java API and how to use JavaScript functions of the exposed frontend Java methods.
Download the extension sample code complete_extension_sample.zip.
Description
The sample is shown in a new Workspace tab.
The main screen is shown as a toolbar with three buttons:
- The first button goes to the main screen of the sample.
- The second button shows sample 1.
- The second button shows sample 2.
Project structure:
Installation
- Stop the application.
- Open the contents of the downloaded file into the $TOMCAT_HOME/webapps/openkm folder (or better into openkm.war)
- Delete $TOMCAT_HOME/work/catalina/localhost
- Start the application.
- Check the URL http://localhost:8080/openkm/sample.
The screenshots were taken in combination with "ExtraTab" frontend extension feature; you can also use TabWorkspaceExtension.
When you change the contents of openkm.war, the file is deployed as a new version of the application and all the contents in $TOMCAT_HOME/webapps/openkm will be refreshed, and you will lose any changes you made directly in the webapps/openkm folder.
Implementation details
index.jsp
Index.jsp controls the iframe height with JavaScript code.
Another important fact is that, for example after uploading a document (Sample 2), the parameter urlToOpen is used to indicate which URL must be opened.
HttpSessionManager httpSessionManager = ContextWrapper.getContext().getBean(HttpSessionManager.class);
OKMAuth okmAuth = ContextWrapper.getContext().getBean(OKMAuth.class);
WebUtils webUtils = ContextWrapper.getContext().getBean(WebUtils.class);
httpSessionManager.add(request);
okmAuth.login();
String urlToOpen = URLDecoder.decode(webUtils.getString(request, "urlToOpen"), "UTF-8");
if (urlToOpen.equals("")) {
urlToOpen = "home.jsp";
}
menu.jsp
Contains the menu URL definitions.
home.jsp
Sets the main home screen.
Sample 1
Is composed of 4 files:
- action.jsp (the main controller where the logic is set).
- distributor.jsp (the main layer with two columns at 50%).
- full_list.jsp (shows full list with no special JavaScript control).
- filtered_list.jsp (shows filtered list with no special JavaScript control).
action.jsp
The main controller.
<%@page import="java.util.Map"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%@ page import="java.text.SimpleDateFormat"%>
<%@ page import="java.io.IOException"%>
<%@ page import="java.net.URLDecoder"%>
<%@ page import="org.slf4j.Logger"%>
<%@ page import="org.slf4j.LoggerFactory"%>
<%@ page import="com.openkm.bean.Document"%>
<%@ page import="com.openkm.util.WebUtils"%>
<%@ page import="com.openkm.bean.CommonUser"%>
<%@ page import="com.openkm.api.OKMAuth"%>
<%@ page import="com.openkm.api.OKMRepository"%>
<%@ page import="com.openkm.api.OKMDocument"%>
<%@ page import="com.openkm.util.ContextWrapper"%>
<%@ page extends="com.openkm.extension.servlet.BaseServlet"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%!
private static Logger log = LoggerFactory.getLogger("com.openkm.sample");
private static SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
private WebUtils webUtils = ContextWrapper.getContext().getBean(WebUtils.class);
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
String action = webUtils.getString(request, "action");
try {
list(request, response);
} catch (Exception e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request, response, e);
}
}
// Used by fileupload return
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
String action = webUtils.getString(request, "action");
try {
if (action.equals("filter")) {
list(request, response);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request, response, e);
}
}
private void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, Exception {
OKMAuth okmAuth = ContextWrapper.getContext().getBean(OKMAuth.class);
String nameFilter = webUtils.getString(request, "nameFilter");
String userFilter = webUtils.getString(request, "userFilter");
// Getting lists
List<Map<String, String>> leftDocsMaps = leftList(""); // Pending documents for all users
List<Map<String, String>> rightDocsMaps = rightList(userFilter, nameFilter);
List<String> users = new ArrayList<String>();
// UserList
for (CommonUser user : okmAuth.getUsers(null)) {
users.add(user.getId());
}
ServletContext sc = getServletContext();
sc.setAttribute("nameFilter", nameFilter);
sc.setAttribute("leftDocsMaps", leftDocsMaps);
sc.setAttribute("rightDocsMaps", rightDocsMaps);
sc.setAttribute("users", users);
sc.setAttribute("nameFilter", nameFilter);
sc.setAttribute("userFilter", userFilter);
sc.getRequestDispatcher("/sample/sample1/distributor.jsp").forward(request, response);
}
private List<Map<String, String>> leftList(String user) throws Exception {
OKMDocument okmDocument = ContextWrapper.getContext().getBean(OKMDocument.class);
OKMRepository okmRepository = ContextWrapper.getContext().getBean(OKMRepository.class);
String uuid = okmRepository.getNodeUuid(null, "/okm:root");
List<Map<String, String>> documentsMap = new ArrayList<Map<String, String>>();
// To change
List<Document> pendingDocuments = new ArrayList<Document>();
for (Document doc : okmDocument.getChildren(null, uuid)) {
Map<String, String> docMap = new HashMap<String, String>();
docMap.put("author", doc.getAuthor());
docMap.put("name", doc.getPath().substring(doc.getPath().lastIndexOf("/") + 1));
docMap.put("uuid", doc.getUuid());
docMap.put("path", doc.getPath());
docMap.put("mimeType", doc.getMimeType());
docMap.put("lastModified", df.format(doc.getLastModified().getTime()));
documentsMap.add(docMap);
}
return documentsMap;
}
private List<Map<String, String>> rightList(String user, String nameFilter) throws Exception {
OKMDocument okmDocument = ContextWrapper.getContext().getBean(OKMDocument.class);
OKMRepository okmRepository = ContextWrapper.getContext().getBean(OKMRepository.class);
String uuid = okmRepository.getNodeUuid(null, "/okm:root");
List<Map<String, String>> documentsMap = new ArrayList<Map<String, String>>();
for (Document doc : okmDocument.getChildren(null, uuid)) {
boolean found = true;
if (!user.equals("") && !user.equals(doc.getAuthor())) {
found = false;
}
if (!nameFilter.equals("") && !doc.getPath().contains(nameFilter)) {
found = false;
}
if (found) {
Map<String, String> docMap = new HashMap<String, String>();
docMap.put("author", doc.getAuthor());
docMap.put("name", doc.getPath().substring(doc.getPath().lastIndexOf("/") + 1));
docMap.put("uuid", doc.getUuid());
docMap.put("path", doc.getPath());
docMap.put("mimeType", doc.getMimeType());
docMap.put("lastModified", df.format(doc.getLastModified().getTime()));
documentsMap.add(docMap);
}
}
return documentsMap;
}
%>
distributor.jsp
The main layer.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath() %>/sample/css/style.css" />
<script type="text/javascript" src="<%=request.getContextPath() %>/js/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="<%=request.getContextPath() %>/js/vanadium-min.js" ></script>
</head>
<body>
<table width="100%">
<tbody>
<tr>
<td valign="top" width="50%">
<jsp:include page="/sample/sample1/full_list.jsp"></jsp:include>
</td>
<td valign="top" width="10"></td>
<td valign="top" width="50%">
<jsp:include page="/sample/sample1/filtered_list.jsp"></jsp:include>
</td>
</tr>
</tbody>
</table>
</body>
</html>
full_list.jsp
Shows the document list.
Note the JavaScript function "go", which calls parent.parent.jsOpenPathByUuid(uuid). The "jsOpenPathByUuid" function is a GWT public function used to jump to a specific document, folder, etc. The Exposed frontend JavaScript API linked here provides a complete list of JavaScript functions.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<script type="text/javascript">
// Note the parent.parent call because iframe is into other iframe
function go(uuid) {
alert('Document UUID:'+uuid);
parent.parent.jsOpenPathByUuid(uuid);
}
</script>
<h1>Full list of /okm:root documents</h1>
<table class="results" style="white-space: nowrap;" cellpadding="3" width="100%">
<thead>
<tr>
<th></th><th>Author</th><th>Document</th><th>Date</th><th></th>
</tr>
</thead>
<tbody>
<c:forEach var="document" items="${leftDocsMaps}" varStatus="row">
<tr>
<td valign="center"><img src="<%=request.getContextPath() %>/mime/${document.mimeType}"/></td>
<td>${document.author}</td>
<td>${document.name}</td>
<td>${document.lastModified}</td>
<td align="center">
<a href="javascript:go('${document.uuid}')"><img src="<%=request.getContextPath() %>/sample/img/action/goto_document.gif" alt="Show document" title="Show document"/></a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
Screenshot of sample1 view
Sample 2
Is composed of 4 files:
- action.jsp (the main controller where the logic is set).
- add_form.jsp (form to upload a new document)
- distributor.jsp (the main layer with two columns at 50% with JavaScript).
- full_list.jsp (shows full list with special JavaScript control for table height).
- pending.jsp (static content).
action.jsp
The main controller.
<%@page import="java.util.Map"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%@ page import="java.text.SimpleDateFormat"%>
<%@ page import="java.io.IOException"%>
<%@ page import="org.slf4j.Logger"%>
<%@ page import="org.slf4j.LoggerFactory"%>
<%@ page import="com.openkm.bean.Document"%>
<%@ page import="com.openkm.util.WebUtils"%>
<%@ page import="com.openkm.api.OKMDocument"%>
<%@ page import="com.openkm.api.OKMRepository"%>
<%@ page import="com.openkm.util.ContextWrapper"%>
<%@ page extends="com.openkm.extension.servlet.BaseServlet"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%!
private static Logger log = LoggerFactory.getLogger("com.openkm.sample");
private WebUtils webUtils = ContextWrapper.getContext().getBean(WebUtils.class);
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String action = webUtils.getString(request, "action");
try {
list(request, response);
} catch (Exception e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request, response, e);
}
}
// Used by fileupload return and filter
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String action = webUtils.getString(request, "action");
try {
list(request, response);
} catch (Exception e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request, response, e);
}
}
private void list(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, Exception {
OKMDocument okmDocument = ContextWrapper.getContext().getBean(OKMDocument.class);
OKMRepository okmRepository = ContextWrapper.getContext().getBean(OKMRepository.class);
String uuid = okmRepository.getNodeUuid(null, "/okm:root");
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
String docPath = (String) request.getAttribute("docPath");
String nameFilter = webUtils.getString(request, "nameFilter");
// Pending docs
List<Map<String, String>> pendingDocMaps = new ArrayList<Map<String, String>>();
for (Document doc : okmDocument.getChildren(null, uuid)) {
Map<String, String> docMap = new HashMap<String, String>();
docMap.put("name", doc.getPath().substring(doc.getPath().lastIndexOf("/") + 1));
docMap.put("uuid", doc.getUuid());
docMap.put("path", doc.getPath());
docMap.put("mimeType", doc.getMimeType());
docMap.put("lastModified", df.format(doc.getLastModified().getTime()));
pendingDocMaps.add(docMap);
}
ServletContext sc = getServletContext();
sc.setAttribute("docPath", docPath);
sc.setAttribute("nameFilter", nameFilter);
sc.setAttribute("pendingDocuments", pendingDocMaps);
sc.getRequestDispatcher("/sample/sample2/distributor.jsp").forward(request, response);
}
%>
distributor.jsp
The main layer.
Here, JavaScript is used to control the height and width of the layer.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath() %>/sample/css/style.css" />
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath() %>/css/jquery.tablescroll.css"/>
<script type="text/javascript" src="<%=request.getContextPath() %>/js/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="<%=request.getContextPath() %>/js/vanadium-min.js" ></script>
<script type="text/javascript" src="<%=request.getContextPath() %>/js/jquery.tablescroll.js"></script>
</head>
<body>
<div id="leftPanel" style="float: left;">
<jsp:include page="/sample/sample2/add_form.jsp"></jsp:include>
<jsp:include page="/sample/sample2/full_list.jsp"></jsp:include>
</div>
<div id="centerPanel" style="float: left; width: 12px;">
<div id="verticalSeparator" class="verticalSeparator"></div>
</div>
<div id="rightPanel" style="float: left;">
<jsp:include page="/sample/sample2/pending.jsp"></jsp:include>
</div>
<script type="text/javascript">
var width = $(window).width()-12; // Deleting middle panel
var height = $(window).height();
// Setting height
$('#verticalSeparator').height(height);
$('#leftPanel').height(height);
$('#rightPanel').height(height);
// Setting width
$('#leftPanel').width(parseInt(width/2));
$('#centerPanel').width(12);
$('#rightPanel').width((width-parseInt((width/2))));
</script>
</body>
</html>
add_form.jsp
Form to upload a new document.
<%@ page import="com.openkm.frontend.client.constants.ui.UIFileUploadConstants" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<h2 id="headerTitle_new_register">Upload new file</h2>
<form enctype="multipart/form-data" method="post" action="<%=request.getContextPath() %>/frontend/FileUpload">
<input type="hidden" name="path" value="/okm:root">
<input type="hidden" name="action" value="<%=UIFileUploadConstants.ACTION_INSERT%>">
<input type="hidden" name="message" value="">
<input type="hidden" name="comment" value="">
<input type="hidden" name="redirect" value="/sample/sample2/action.jsp">
<table class="form">
<tbody>
<c:if test="${docPath != null && docPath != ''}">
<tr>
<td><div class="ok">Document sended</div></td>
</tr>
</c:if>
<tr>
<td>
<div id="file_error" style="display:none; color : red;">File is mandatory.</div>
<input type="file" name="file" class=":required;;file_error :only_on_blur">
</td>
</tr>
<tr>
<td align="center"><input type="submit" value="Send"></td>
</tr>
</tbody>
</table>
</form>
full_list.jsp
Shows a list of documents.
Take a look at the end, where JavaScript is used to control the table's scrolling.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<script type="text/javascript">
// Note the parent.parent call because iframe is into other iframe
function go(uuid) {
alert('Document UUID:'+uuid);
parent.parent.jsOpenPathByUuid(uuid);
}
</script>
<h2 class="top">Full document list from /okm:root</h2>
<div align="center" style="padding: 0px 20px 0px 5px">
<div class="tablescroll">
<table id="docsPendingTable" width="100%" cellspacing="0" style="white-space: nowrap;">
<thead>
<tr>
<td>Id</td><td>Document</td><td>Date</td><td></td>
</tr>
</thead>
<tbody>
<c:set var="first" value="true"></c:set>
<c:forEach var="document" items="${pendingDocuments}" varStatus="row">
<c:choose>
<c:when test="${first==true}">
<tr class="first">
</c:when>
<c:otherwise>
<tr>
</c:otherwise>
</c:choose>
<td valign="center"><img src="<%=request.getContextPath() %>/mime/${document.mimeType}"/></td>
<td>${document.name}</td>
<td>${document.lastModified}</td>
<td align="center"><a href="javascript:go('${document.uuid}')"><img src="<%=request.getContextPath() %>/sample/img/action/goto_document.gif" alt="Show document" title="Show document"/></a></td>
</tr>
<c:set var="first" value="false"></c:set>
</c:forEach>
</tbody>
</table>
</div>
</div>
<script type="text/javascript">
//Calculating table height removing from total other elements height
var pendingTableHeight = $(window).height() - (parseInt($('#docsPendingTable').offset().top) + 35);
/*<![CDATA[*/
jQuery(document).ready(function($)
{
$('#docsPendingTable').tableScroll({height:pendingTableHeight});
});
/*]]>*/
</script>
Screenshot of sample2 view