You can create your own selection view if an Item Selector doesn’t contain the one you need. The steps here show you how. For more information on custom selection views and the Item Selector API, see the Item Selector introduction.
Configuring Your Selection View’s OSGi Module
First, you must configure your selection view’s OSGi module:
-
Add these dependencies to your module’s
build.gradle
:dependencies { compileOnly group: "com.liferay", name: "com.liferay.item.selector.api", version: "2.0.0" compileOnly group: "com.liferay", name: "com.liferay.item.selector.criteria.api", version: "2.0.0" compileOnly group: "com.liferay.portal", name: "com.liferay.portal.impl", version: "2.0.0" compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0" compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0" compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0" compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1" compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0" }
-
Add your module’s information to the
bnd.bnd
file. For example, this configuration adds the information for a module calledMy Custom View
:Bundle-Name: My Custom View Bundle-SymbolicName: com.liferay.docs.my.custom.view Bundle-Version: 1.0.0
-
Add a
Web-ContextPath
to yourbnd.bnd
to point to your module’s resources:Include-Resource:\ META-INF/resources=src/main/resources/META-INF/resources Web-ContextPath: /my-custom-view
If you don’t have a
Web-ContextPath
, your module won’t know where your resources are. TheInclude-Resource
header points to the relative path for the module’s resources.
Implementing Your Selection View’s Class
Follow these steps to implement your selection view’s class:
-
Create an
ItemSelectorView
component class that implementsItemSelectorView
with the criterion as a type argument. In the@Component
annotation, set theitem.selector.view.order
property to the order you want the view to appear in when displayed alongside other selector views (lower values get higher priority).This example selector view class is for images, so it implements
ItemSelectorView
withImageItemSelectorCriterion
as a type argument. The@Component
annotation sets theitem.selector.view.order
property to200
and registers the class as anItemSelectorView
service:@Component( property = {"item.selector.view.order:Integer=200"}, service = ItemSelectorView.class ) public class SampleItemSelectorView implements ItemSelectorView<ImageItemSelectorCriterion> {...
-
Create getter methods for the criterion class, servlet context, and return types. Do this by implementing the methods
getItemSelectorCriterionClass()
,getServletContext()
, andgetSupportedItemSelectorReturnTypes()
, respectively:@Override public Class<ImageItemSelectorCriterion> getItemSelectorCriterionClass() { return ImageItemSelectorCriterion.class; } @Override public ServletContext getServletContext() { return _servletContext; } @Override public List<ItemSelectorReturnType> getSupportedItemSelectorReturnTypes() { return _supportedItemSelectorReturnTypes; }
-
Configure the selection view’s title, search options, and visibility settings. Here’s an example configuration for the
Sample Selector
selection view:@Override public String getTitle(Locale locale) { return "Sample Selector"; } @Override public boolean isShowSearch() { return false; } @Override public boolean isVisible(ThemeDisplay themeDisplay) { return true; }
See The Selection View’s Class for more information on these methods.
-
Implement the
renderHTML
method to set your view’s render settings and render its markup.Here’s an example implementation of a
renderHTML
method that points to a JSP file (sample.jsp
) to render the view. Note thatitemSelectedEventName
is passed as a request attribute so it can be used in the view markup. The view markup is specified via theServletContext
methodgetRequestDispatcher
. Although this example uses a JSP, you can render the markup in another language such as FreeMarker.@Override public void renderHTML( ServletRequest request, ServletResponse response, ImageItemSelectorCriterion itemSelectorCriterion, PortletURL portletURL, String itemSelectedEventName, boolean search ) throws IOException, ServletException { request.setAttribute(_ITEM_SELECTED_EVENT_NAME, itemSelectedEventName); ServletContext servletContext = getServletContext(); RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/sample.jsp"); requestDispatcher.include(request, response); }
-
Use the
@Reference
annotation to reference your module’s class for thesetServletContext
method. In the annotation, use thetarget
parameter to specify the available services for the servlet context. This example uses theosgi.web.symbolicname
property to specify thecom.liferay.selector.sample.web
class as the default value. You should also use theunbind = _
parameter to specify that there’s no unbind method for this module. In the method body, set the servlet context variable:@Reference( target = "(osgi.web.symbolicname=com.liferay.item.selector.sample.web)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { _servletContext = servletContext; }
-
Define the
_supportedItemSelectorReturnTypes
list with the return types that this view supports (you referenced this list in step two). This example addsURLItemSelectorReturnType
andFileEntryItemSelectorReturnType
to the list of supported return types (you can use more if needed). More return types means that the view is more reusable. Also note that this example defines its servlet context variable at the bottom of the file:private static final List<ItemSelectorReturnType> _supportedItemSelectorReturnTypes = Collections.unmodifiableList( ListUtil.fromArray( new ItemSelectorReturnType[] { new FileEntryItemSelectorReturnType(), new URLItemSelectorReturnType() })); private ServletContext _servletContext;
For a real-world example of a view class, see
SiteNavigationMenuItemItemSelectorView
.
Writing Your View Markup
You can write your view markup however you wish—there’s no typical or average case. You can write it with taglibs, AUI components, or even pure HTML and JavaScript. The markup must do two key things:
- Render the entities for the user to select.
- When an entity is selected, pass the return type information via a JavaScript event.
The example view class in the previous section passes the JavaScript event name
as a request attribute in the renderHTML
method. You can therefore use this
event name in the markup:
Liferay.fire(
`<%= {ITEM_SELECTED_EVENT_NAME} %>',
{
data:{
the-data-your-client-needs-according-to-the-return-type
}
}
);
For a complete, real-world example, see
layouts.jsp
for the module
com.liferay.layout.item.selector.web
.
Even though this example is for a previous version of Liferay DXP, it still
applies to Liferay DXP 7.2. Here’s a walkthrough of this layouts.jsp
file:
-
First, some variables are defined. Note that
LayoutItemSelectorViewDisplayContext
is an optional class that contains additional information about the criteria and view:<% LayoutItemSelectorViewDisplayContext layoutItemSelectorViewDisplayContext = (LayoutItemSelectorViewDisplayContext)request.getAttribute( BaseLayoutsItemSelectorView.LAYOUT_ITEM_SELECTOR_VIEW_DISPLAY_CONTEXT); LayoutItemSelectorCriterion layoutItemSelectorCriterion = layoutItemSelectorViewDisplayContext.getLayoutItemSelectorCriterion(); Portlet portlet = PortletLocalServiceUtil.getPortletById(company.getCompanyId(), portletDisplay.getId()); %>
-
This snippet imports a CSS file for styling and places it in the
<head>
of the page:<liferay-util:html-top> <link href="<%= PortalUtil.getStaticResourceURL( request, application.getContextPath() + "/css/main.css", portlet.getTimestamp()) %>" rel="stylesheet" type="text/css" /> </liferay-util:html-top>
You can learn more about using the
liferay-util
taglibs in Using the Liferay Util Taglib. -
This snippet creates the UI to display the layout entities. It uses the
liferay-layout:layouts-tree
taglib along with the Lexicon design language to create cards:<div class="container-fluid-1280 layouts-selector"> <div class="card-horizontal main-content-card"> <div class="card-row card-row-padded"> <liferay-layout:layouts-tree checkContentDisplayPage="<%= layoutItemSelectorCriterion.isCheckDisplayPage() %>" draggableTree="<%= false %>" expandFirstNode="<%= true %>" groupId="<%= scopeGroupId %>" portletURL="<%= layoutItemSelectorViewDisplayContext.getEditLayoutURL() %>" privateLayout="<%= layoutItemSelectorViewDisplayContext.isPrivateLayout() %>" rootNodeName="<%= layoutItemSelectorViewDisplayContext.getRootNodeName() %>" saveState="<%= false %>" selectedLayoutIds="<%= layoutItemSelectorViewDisplayContext.getSelectedLayoutIds() %>" selPlid="<%= layoutItemSelectorViewDisplayContext.getSelPlid() %>" treeId="treeContainer" /> </div> </div> </div>
This renders the following UI:
-
This portion of the
aui:script
returns the path for the page:<aui:script use="aui-base"> var LString = A.Lang.String; var getChosenPagePath = function(node) { var buffer = []; if (A.instanceOf(node, A.TreeNode)) { var labelText = LString.escapeHTML(node.get('labelEl').text()); buffer.push(labelText); node.eachParent( function(treeNode) { var labelEl = treeNode.get('labelEl'); if (labelEl) { labelText = LString.escapeHTML(labelEl.text()); buffer.unshift(labelText); } } ); } return buffer.join(' > '); };
-
The following snippet passes the return type data when the layout (entity) is selected. Note the
url
anduuid
variables retrieve the URL or UUID for the layout:var setSelectedPage = function(event) { var disabled = true; var messageText = '<%= UnicodeLanguageUtil.get(request, "there-is-no-selected-page") %>'; var lastSelectedNode = event.newVal; var labelEl = lastSelectedNode.get('labelEl'); var link = labelEl.one('a'); var url = link.attr('data-url'); var uuid = link.attr('data-uuid'); var data = {}; if (link && url) { disabled = false; data.layoutpath = getChosenPagePath(lastSelectedNode);
-
This checks if the return type information is a URL or a UUID. It then sets the value for the JSON object’s
data
attribute accordingly. The last line adds theCKEditorFuncNum
for the editor to the JSON object’sdata
attribute:<c:choose> <c:when test="<%= Objects.equals(layoutItemSelectorViewDisplayContext.getItemSelectorReturnTypeName(), URLItemSelectorReturnType.class.getName()) %>"> data.value = url; </c:when> <c:when test="<%= Objects.equals(layoutItemSelectorViewDisplayContext.getItemSelectorReturnTypeName(), UUIDItemSelectorReturnType.class.getName()) %>"> data.value = uuid; </c:when> </c:choose> } <c:if test="<%= Validator.isNotNull(layoutItemSelectorViewDisplayContext.getCkEditorFuncNum()) %>"> data.ckeditorfuncnum: <%= layoutItemSelectorViewDisplayContext.getCkEditorFuncNum() %>; </c:if>
The
data-url
anddata-uuid
attributes are in the HTML for the Layouts Item Selector. The HTML for an instance of the Layouts Item Selector is shown here: -
The JavaScript trigger event specified in the Item Selector return type is fired, passing the data JSON object with the required return type information:
Liferay.Util.getOpener().Liferay.fire( '<%= layoutItemSelectorViewDisplayContext.getItemSelectedEventName() %>', { data: data } ); };
-
Finally, the layout is set to the selected page:
var container = A.one('#<portlet:namespace />treeContainerOutput'); if (container) { container.swallowEvent('click', true); var tree = container.getData('tree-view'); tree.after('lastSelectedChange', setSelectedPage); } </aui:script>
Your new selection view is automatically rendered by the Item Selector in every app that uses the criterion and return types you defined, without modifying anything in those apps.