Implementing Actions and Views

2024-03-28

The LabKey platform includes a generic infrastructure for implementing your own server actions and views.

Definitions

Actions are the "surface area" of the server: everything you invoke on the server, whether a way to display data or a manipulation of data, is some action or set of actions. An Action is implemented using the Model-View-Controller paradigm, where:

  • the Model is implemented as one or more Java classes, such as standard JavaBean classes
  • the View is implemented as JSPs, or other technologies
  • the Controller is implemented as Java action classes
Forms submitted to an action are bound to the JavaBean classes by the Spring framework.

Views are ways of "surfacing" or "viewing" data, typically implemented in parent-child relationships, such that a page is built from a template view that wraps one or more body views. Views often render other views, for example, one view per pane or a series of similar child views. Views are implemented using a variety of different rendering technologies; if you look at the subclasses of HttpView and browse the existing controllers you will see that views can be written using JSP, GWT, out.print() from Java code, etc. (Note that most LabKey developers write JSPs to create new views. The JSP syntax is familiar and supported by all popular IDEs, JSPs perform well, and type checking & compilation increase reliability.)

Action Life Cycle

What happens when you submit to an Action in LabKey Server? The typical life cycle looks like this:

  • ViewServlet receives the request and directs it to the appropriate module.
  • The module passes the request to the appropriate Controller which then invokes the requested action.
  • The action verifies that the user has permission to invoke it in the current folder. (If the user is not assigned an appropriate role in the folder then the action will not be invoked.) Action developers typically declare required permissions via a @RequiresPermission() annotation.
  • The Spring framework instantiates the Form bean associated with the action and "binds" parameter values to it. In other words, it matches URL parameters names to bean property names; for each match, it converts the parameter value to the target data type, performs basic validation, and sets the property on the form by calling the setter.
  • The Controller now has data, typed and validated, that it can work with. It performs the action, and typically redirects to a results page, confirmation page, or back to the same page.

Security and Permissions

Unless you have specific requirements, your action class should derive from org.labkey.api.action.PermissionCheckableAction, which will help ensure that only authorized users can reach your page.

Most actions should use the @RequiresPermission annotation to indicate what permission the current user must have in order to invoke the action. For example, @RequiresPermission(ReadPermission.class) will ensure the user has read access to the current container, and @RequiresPermission(AdminPermission.class) ensures that the user is at least a folder admin.

If the user hasn't authenticated, and is therefore considered a Guest, and Guests don't have the required permission, they will be redirected to the login page. If the user is already logged in, they will receive an error with HTTP status code 401.

Other annotations you can use to control access to your action include:

  • @RequiresAnyOf: Any of the supplied permissions are sufficient to grant access.
  • @RequiresAllOf: The user must have all of the supplied permissions to be allowed to invoke the action.
  • @RequiresSiteAdmin: The user must be a member of the Site Admins group.
  • @RequiresLogin: In addition to whatever other permissions may be required, the user may not be a Guest.
  • @RequiresNoPermission: no permissions are needed to access the action. Use with caution.

Example: Hello World JSP View

Below is a simple example "Hello World" JSP view. Most JSPs extend org.labkey.api.jsp.JspBase; JspBase provides a large number of helper methods for generating and encoding HTML and JSON. JSPs must extend JspBase or JspContext or they won't compile.

LabKey does not allow JSPs to output Strings or arbitrary Objects. You must encode all Strings with h() and use builders that implement SafeToRender.

helloWorld.jsp:

<%@ page extends="org.labkey.api.jsp.JspBase" %>
<%= h("Hello, World!") %>

HelloWorldAction:

The following action displays the "Hello World" JSP shown above.

// If the user does not have Read permissions, the action will not be invoked.
@RequiresPermission(ReadPermission.class)
public class HelloWorldAction extends SimpleViewAction
{
@Override
public ModelAndView getView(Object o, BindException errors) throws Exception
{
JspView view = new JspView("/org/labkey/javatutorial/view/helloWorld.jsp");
view.setTitle("Hello World");
return view;
}

@Override
public void addNavTrail(NavTree root)
{
}
}

The HelloWorld Action is invoked with this URL:

Example: Submitting Forms to an Action

The following action processes a form submitted by the user.

helloSomeone.jsp

This JSP is for submitting posts, and displaying responses, on the same page:

<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %>
<%@ page import="org.labkey.api.view.HttpView"%>
<%@ page import="org.labkey.javatutorial.JavaTutorialController" %>
<%@ page import="org.labkey.javatutorial.HelloSomeoneForm" %>
<%@ page extends="org.labkey.api.jsp.JspBase" %>
<%
HelloSomeoneForm form = (HelloSomeoneForm) HttpView.currentModel();
%>
<labkey:errors />
<labkey:form method="POST" action="<%=urlFor(JavaTutorialController.HelloSomeoneAction.class)%>">
<h2>Hello, <%=h(form.getName()) %>!</h2>
<table width="100%">
<tr>
<td class="labkey-form-label">Who do you want to say 'Hello' to next?: </td>
<td><input name="name" value="<%=h(form.getName())%>"></td>
</tr>
<tr>
<td><labkey:button text="Go" /></td>
</tr>
</table>
</labkey:form>

Action for handling posts:

// If the user does not have Read permissions, the action will not be invoked.
@RequiresPermission(ReadPermission.class)
public class HelloSomeoneAction extends FormViewAction<HelloSomeoneForm>
{
@Override
public void validateCommand(HelloSomeoneForm form, Errors errors)
{
// Do some error handling here
}

@Override
public ModelAndView getView(HelloSomeoneForm form, boolean reshow, BindException errors) throws Exception
{
return new JspView<>("/org/labkey/javatutorial/view/helloSomeone.jsp", form, errors);
}

@Override
public boolean handlePost(HelloSomeoneForm form, BindException errors) throws Exception
{
return true;
}

@Override
public ActionURL getSuccessURL(HelloSomeoneForm form)
{
// Redirect back to the same action, adding the submitted value to the URL.
ActionURL url = new ActionURL(HelloSomeoneAction.class, getContainer());
url.addParameter("name", form.getName());

return url;
}

@Override
public void addNavTrail(NavTree root)
{
root.addChild("Say Hello To Someone");
}
}

Below is the form used to convey the URL parameter value to the Action class. Note that the form follows a standard JavaBean format. The Spring framework attempts to match URL parameter names to property names in the form. If it finds matches, it interprets the URL parameters according to the data types it finds in the Bean property and performs basic data validation on the values provided on the URL:

package org.labkey.javatutorial;

public class HelloSomeoneForm
{
public String _name = "World";

public void setName(String name)
{
_name = name;
}

public String getName()
{
return _name;
}
}

URL that invokes the action in the home project:

Example: Export as Script Action

This action exports a query as a re-usable script, either as JavaScript, R, Perl, or SAS. (The action is surfaced in the user interface on a data grid, at Export > Script.)

public static class ExportScriptForm extends QueryForm
{
private String _type;

public String getScriptType()
{
return _type;
}

public void setScriptType(String type)
{
_type = type;
}
}


@RequiresPermission(ReadPermission.class)
public class ExportScriptAction extends SimpleViewAction<ExportScriptForm>
{
@Override
public ModelAndView getView(ExportScriptForm form, BindException errors) throws Exception
{
ensureQueryExists(form);

return ExportScriptModel.getExportScriptView(QueryView.create(form, errors),
form.getScriptType(), getPageConfig(), getViewContext().getResponse());
}

@Override
public void addNavTrail(NavTree root)
{
}
}

Example: Delete Cohort

The following action deletes a cohort category from a study (provided it is an empty cohort). It then redirects the user back to the Manage Cohorts page.

@RequiresPermission(AdminPermission.class)
public class DeleteCohortAction extends SimpleRedirectAction<CohortIdForm>
{
@Override
public ActionURL getRedirectURL(CohortIdForm form) throws Exception
{
CohortImpl cohort = StudyManager.getInstance().getCohortForRowId(getContainer(), getUser(), form.getRowId());
if (cohort != null && !cohort.isInUse())
StudyManager.getInstance().deleteCohort(cohort);

return new ActionURL(CohortController.ManageCohortsAction.class, getContainer());
}
}

Packaging JSPs

JSPs can be placed anywhere in the src directory, but by convention they are often placed in the view directory, as shown below:

mymodule
├───lib
├───resources
└───src
└───org
└───labkey
└───javatutorial
│ HelloSomeoneForm.java
│ JavaTutorialController.java
│ JavaTutorialModule.java
└───view
helloSomeone.jsp
helloWorld.jsp

Related Topics