<%-- ADOBE CONFIDENTIAL ___________________ Copyright 2015 Adobe All Rights Reserved. NOTICE: All information contained herein is, and remains the property of Adobe and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to Adobe and its suppliers and are protected by all applicable intellectual property laws, including trade secret and copyright laws. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from Adobe. --%><% %><%@ include file="/libs/granite/ui/global.jsp" %><% %><%@ include file="/libs/granite/ui/components/coral/foundation/datasource/datasourceUtils.jsp" %><% %><%@ page import="java.util.ArrayList, java.util.Iterator, javax.servlet.http.HttpServletRequest, org.apache.jackrabbit.util.Text, org.apache.sling.commons.json.io.JSONStringer, org.slf4j.Logger, org.slf4j.LoggerFactory, com.adobe.granite.ui.components.AttrBuilder, com.adobe.granite.ui.components.ComponentHelper.Options, com.adobe.granite.ui.components.Config, com.adobe.granite.ui.components.ds.DataSource, com.adobe.granite.ui.components.ExpressionHelper, org.apache.commons.lang3.StringUtils, com.adobe.granite.ui.components.Tag" %><%--### ColumnView ========== .. granite:servercomponent:: /libs/granite/ui/components/coral/foundation/columnview A Miller columns component. It implements :doc:`/jcr_root/libs/granite/ui/components/coral/foundation/clientlibs/foundation/vocabulary/selections` and :doc:`/jcr_root/libs/granite/ui/components/coral/foundation/clientlibs/foundation/vocabulary/mode` vocabulary. Note that this component's size is maximized to its parent OOTB. Hence it doesn't make sense if this component has a sibling component, as it will take the whole parent's space. It has the following content structure: .. gnd:gnd:: [granite:ColumnView] > granite:commonAttrs, granite:renderCondition, granite:container /** * ``true`` to enable the selection of item; ``false`` otherwise. */ - selectionMode (BooleanEL) = 'true' /** * The count of item to be selected (when the ``selectionMode`` is enabled): * * single * Only maximum single selection allowed * multiple * Zero or more selection allowed */ - selectionCount (StringEL) = 'multiple' /** * The URI Template that is returning the HTML response of the column view. * It is used when the client needs to load data dynamically, such as for pagination. * * Note that only the items of the current column are rendered using the provided item datasource. * The items of the ancestors columns are loaded lazily in different requests. * * If it is not specified, the feature is disabled. * * It supports the following variables: * * offset * The item offset of the current request. * limit * The item limit of the pagination(actual+placeholder). * size * The actual number items to be included(excluding placeholder) * id * The path of the target column. * sortName * The value on which layout needs to be sorted. * This is optional variable that may not be passed when resolving the URI Template. * sortDir * The direction of the sorting: ``asc`` or ``desc``. * This is optional variable that may not be passed when resolving the URI Template. * * e.g. ``/a/b/c{.offset,limit,size}.html{+id}{?sortName,sortDir}` */ - src (StringEL) /** * The path of the current column. It will act as the value of ``[data-foundation-collection-id]``. * * e.g. ``${requestPathInfo.suffix}`` */ - path (StringEL) /** * ``true`` to also load the ancestors of the current columns up to the ``rootPath``; ``false`` otherwise. */ - loadAncestors (Boolean) /** * ``true`` to also render a special column for the root resource when ``path == rootPath``; ``false`` otherwise. * This special column is used to allow the user to select the root path. * * Say you set ``path = /``, this component by default will render a column containing the items of that path (e.g. ``/apps``, ``/etc``). * When this property is ``true``, when ``path == rootPath``, this component will also render a column containing a single item representing the root. * In other words it will render two columns; first, the special root column; second, the normal column. * * It can be illustrated with the following visualization:: * * When showRoot = false, path = /, rootPath = /: * * +------+ * | apps | * | etc | * | home | * | libs | * +------+ * * When showRoot = true, path = /, rootPath = /: * * +-----+------+ * | / | apps | * | | etc | * | | home | * | | libs | * +-----+------+ * * When rendering the root resource for the item in the special column, this component will also honor the ``itemResourceType`` property. */ - showRoot (Boolean) /** * The path of the root column. */ - rootPath (StringEL) = '/' /** * The resource type for each item of the column. */ - itemResourceType (String) /** * The item limit of the pagination. */ - limit (Long) = '40' /** * Indicates the size of the items from datasource to be included. * If this property is not specified, then all items from datasource will be included, and the next page will be fetched accordingly. * * This is meant to be a performance optimization to avoid fetching the next page unnecessarily. * For example given the fact that the ``size`` is set to 20, and the datasource is configured to fetch ``size + 1``, which is 21, * the implementation can check if the datasource actually has more item or not just by checking its size. * * .. warning:: When ``size`` is set, you have to make sure your datasource is configured to fetch more than the value of ``size``! * * ========= ============== ========= * ``size`` Actual DS Size Has More? * ========= ============== ========= * 20 < 20 ``false`` * 20 = 20 ``false`` * 20 > 20 ``true`` * (not set) n/a ``true`` * ========= ============== ========= */ - size (IntegerEL) /** * The URI Template that is returning the HTML response for the preview of an item. * * It supports the following variables: * * id * The id/path of the item. */ - previewSrc (StringEL) /** * ``true`` to render the preview column maximized to the available space; ``false`` otherwise. * * Note that if you enable this, it generally works well visually only when you have one and only one column (beside the preview column) in the column view. */ - previewMaximized (Boolean) /** * The resource type to render :doc:`.foundation-collection-meta `. * * The resource specified at ``path`` will be included using this resource type, where it can be processed accordingly. */ - metaResourceType (String) /** * The value of :doc:`[data-foundation-mode-group] `. */ - modeGroup (String) /** * The value of :doc:`[data-foundation-mode-group] ` * this component participates. * * The component supports the ``default`` or ``selection`` mode. * * When there is a selection, it will trigger the ``foundation-mode-change`` event with mode = ``selection``, * while triggering the event with mode = ``default`` when there is no selection. * * When other component triggers the event with mode = ``default``, it will react by clearing the selection. */ - modeGroup (String) [granite:ColumnViewDatasource] /** * The value on which layout needs to be sorted.. */ - sortName (StringEL) /** * The direction of the sorting: ``asc`` or ``desc`` * * asc * layout items to be sorted in ascending order * desc * layout items to be sorted in descending order */ - sortDir (StringEL) = "asc" < "asc", "desc" Example:: + mycolumnview - sling:resourceType = "granite/ui/components/coral/foundation/columnview" - src = "/a/b/c{.offset,limit,size}.html{+id}{?sortName,sortDir}" - previewSrc = "/a/b/c.preview.html{+id}" - path = "${requestPathInfo.suffix}" - loadAncestors = true - rootPath = "/content" + datasource - sling:resourceType = "my/datasource" ###--%><% final long DEFAULT_PAGINATION_LIMIT = 1000; final Logger logger = LoggerFactory.getLogger("libs.granite.ui.components.coral.foundation.columnview"); if (!cmp.getRenderCondition(resource, false).check()) { return; } Config cfg = cmp.getConfig(); ExpressionHelper ex = cmp.getExpressionHelper(); String src = ex.getString(cfg.get("src", String.class)); if (src != null && src.startsWith("/")) { src = request.getContextPath() + src; } String previewSrc = ex.getString(cfg.get("previewSrc", String.class)); if (previewSrc != null && previewSrc.startsWith("/")) { previewSrc = request.getContextPath() + previewSrc; } String layoutName = "foundation-layout-columnview"; String rootPath = ex.getString(cfg.get("rootPath", "/")); String path = ex.getString(cfg.get("path", String.class)); if (resourceResolver.getResource(path) == null) { path = rootPath; } boolean isSelectionMode = ex.getBoolean(cfg.get("selectionMode", "true")); String selectionCount = ex.getString(cfg.get("selectionCount", "multiple")); String itemResourceType = cfg.get("itemResourceType", String.class); Resource datasource = resource.getChild("datasource"); Integer size = ex.get(cfg.get("size", String.class), Integer.class); Long limit = ex.get(cfg.get("limit", String.class), Long.class); long offset = datasource != null ? ex.get(datasource.getValueMap().get("offset", "0"), long.class) : 0; //Below statement for BC long totalSize = limit != null && limit >= DEFAULT_PAGINATION_LIMIT ? limit : DEFAULT_PAGINATION_LIMIT; totalSize = size != null && size >= totalSize ? size : totalSize; final Resource currentRes = resourceResolver.getResource(path); String sortBy = datasource != null ? StringUtils.trimToNull(ex.getString(datasource.getValueMap() .get("sortName", String.class))) : null; String sortOrder = datasource != null ? StringUtils.trimToNull(ex.getString(datasource.getValueMap() .get("sortDir", String.class))) : null; DataSource ds; if (size == null || size < 20 || size >= totalSize || datasource == null) { ds = cmp.getItemDataSource(); if (size != null) { totalSize = size; } } else { try { Resource datasourceWrapper = new LimitIncreaseDatasourceWrapper(datasource, ex, totalSize - size + 1); Resource resourceWrapper = new DatasourceOverrideWrapper(resource, datasourceWrapper); ds = cmp.asDataSource(datasourceWrapper, resourceWrapper); } catch(Exception e) { logger.debug("Failed to wrap datasource for lookahead {}", e); logger.info("Fallback to non-lookahead datasource"); ds = cmp.getItemDataSource(); if (size != null) { totalSize = size; } } } Iterator items = ds.iterator(); Boolean hasMore = null; if (size != null) { ArrayList list = new ArrayList(); while (items.hasNext() && list.size() < totalSize) { list.add(items.next()); } hasMore = items.hasNext(); items = list.iterator(); } Tag tag = cmp.consumeTag(); AttrBuilder attrs = tag.getAttrs(); cmp.populateCommonAttrs(attrs); attrs.addClass("foundation-collection"); attrs.add("data-foundation-collection-id", path); attrs.add("data-foundation-collection-src", src); attrs.add("data-foundation-selections-mode", selectionCount); attrs.add("data-foundation-mode-group", cfg.get("modeGroup", String.class)); attrs.add("data-foundation-collection-sortby", sortBy); attrs.add("data-foundation-collection-sortorder", sortOrder); String layoutJson = new JSONStringer() .object() .key("name").value(layoutName) .key("limit").value(limit != null ? limit : 40) .key("size").value(size) .key("previewSrc").value(previewSrc) .key("previewMaximized").value(cfg.get("previewMaximized", false)) .key("layoutId").value(resource.getName()) // This is used as an id to identify the layout when there are multiple layouts to represent the same collection. .key("trackingFeature").value(cfg.get("trackingFeature", String.class)) .key("trackingElement").value(cfg.get("trackingElement", String.class)) .endObject() .toString(); attrs.addClass(layoutName); attrs.add("data-foundation-layout", layoutJson); attrs.add("selectionmode", isSelectionMode ? selectionCount : "none"); %>><% if (cfg.get("loadAncestors", false)) { final Resource rootRes = resourceResolver.getResource(rootPath); ArrayList ancestors = getAncestors(currentRes, rootRes); for (int i = 0; i < ancestors.size(); i++) { Resource r = ancestors.get(i); String activeId; if (i < ancestors.size() - 1) { activeId = ancestors.get(i + 1).getPath(); } else { activeId = currentRes.getPath(); } AttrBuilder parentColumnAttrs = new AttrBuilder(request, xssAPI); parentColumnAttrs.add("data-foundation-layout-columnview-columnid", r.getPath()); parentColumnAttrs.add("data-foundation-layout-columnview-lazy", true); parentColumnAttrs.add("data-foundation-layout-columnview-activeitem", activeId); %>> <% } } if (cfg.get("showRoot", false) && path.equals(rootPath)) { // Put a special path for columnId to avoid having the same columnId with the next column to avoid breaking the contract of columnId. // The contract of columnId is that it should be a path of the current column, i.e. the path should be a path representing a parent. // e.g. When columnId = "/", then the column will show the children of this path, such as "/a", "/b". // So for showRoot scenario, if we want to show the item with path = "/", we need to generate the column having a columnId with value of the parent of "/". // Since the cannot have a parent of "/", then we decide to just use a special convention ("parentof:") to indicate this. // Other component (e.g. `.granite-collection-navigator`) reading the columnId can then understand this convention and handle it accordingly. String columnId = "parentof:" + path; AttrBuilder rootColumnAttrs = new AttrBuilder(request, xssAPI); rootColumnAttrs.add("data-foundation-layout-columnview-columnid", columnId); rootColumnAttrs.add("data-foundation-layout-columnview-hasmore", false); AttrBuilder rootItemAttrs = new AttrBuilder(request, xssAPI); rootItemAttrs.addClass("foundation-collection-item"); rootItemAttrs.add("data-foundation-collection-item-id", rootPath); rootItemAttrs.addBoolean("active", true); %>> <% cmp.include(currentRes, itemResourceType, new Options().tag(new Tag(rootItemAttrs))); %> <% } AttrBuilder columnAttrs = new AttrBuilder(request, xssAPI); if (currentRes != null) { columnAttrs.add("data-foundation-layout-columnview-columnid", currentRes.getPath()); } columnAttrs.add("data-foundation-layout-columnview-hasmore", hasMore); %>><% for (long index = 0; items.hasNext(); index++) { Resource item = items.next(); AttrBuilder itemAttrs = new AttrBuilder(request, xssAPI); itemAttrs.addClass("foundation-collection-item"); itemAttrs.add("data-foundation-collection-item-id", item.getPath()); itemAttrs.add("data-granite-collection-item-id", item.getPath()); itemAttrs.add("data-datasource-index", "" + (index + offset)); if(size != null && size <= index) { itemAttrs.addClass("is-lazyLoaded"); itemAttrs.addClass("foundation-layout-columnview-item-placeholder"); // add an empty coral-columnview-itemselect to avoid creation of coral-checkbox %>>
<% } else { cmp.include(item, itemResourceType, new Options().tag(new Tag(itemAttrs))); } } // Put meta element here instead of under , // as somehow Coral is moving all the elements under to be under // even though is already given. String metaRT = cfg.get("metaResourceType", String.class); if (currentRes != null && metaRT != null) { %><% } %>
<%= xssAPI.encodeForHTML(i18n.get("Loading more items")) %>
<%= xssAPI.encodeForHTML(i18n.get("Loading")) %>
<%! /** * Returns the ancestors of the current resource (inclusive) up to the root. * The result is ordered with the root as the first item. */ private ArrayList getAncestors(Resource current, Resource root) { ArrayList results = new ArrayList(); if (current == null || root == null || current.getPath().equals(root.getPath())) { return results; } Resource r = current.getParent(); while (r != null) { results.add(0, r); if (r.getPath().equals(root.getPath())) { break; } r = r.getParent(); } return results; } %>