package com.psddev.cms.tool.widget; import java.io.IOException; import javax.servlet.ServletException; import com.google.common.collect.ImmutableMap; import com.psddev.cms.db.Content; import com.psddev.cms.tool.Dashboard; import com.psddev.cms.tool.DashboardWidget; import com.psddev.cms.tool.ToolPageContext; import com.psddev.dari.db.Query; import com.psddev.dari.db.Record; import com.psddev.dari.util.PaginatedResult; /** * Provides an extensible base implementation of a {@link DashboardWidget} displaying a {@link PaginatedResult}. * * Minimal implementation requires overriding {@link #getQuery(ToolPageContext)} and {@link #getTitle(ToolPageContext)}. For further * customization, optionally override {@link #writeFiltersHtml(ToolPageContext)}, {@link #writeResultsHtml(ToolPageContext, PaginatedResult)}, or * {@link #writeResultsItemHtml(ToolPageContext, Record)}. * * @param <T> type of {@link PaginatedResult} to be rendered. */ public abstract class AbstractPaginatedResultWidget<T extends Record> extends DashboardWidget { private static final int[] LIMITS = {10, 20, 50}; public static final String OFFSET_PARAMETER = "offset"; public static final String LIMIT_PARAMETER = "limit"; /** * It is recommended that implementations of this method produce * a localized title. * * @param page Used to write {@link DashboardWidget} HTML. * @return the title * @throws IOException */ public abstract String getTitle(ToolPageContext page) throws IOException; /** * Implementations should produce a Query for the type * to be displayed. * * @param page Used for access to request parameters. * @return the {@link Query} to produce the {@link PaginatedResult}. */ public abstract Query<T> getQuery(ToolPageContext page); /** * Optionally override for more control over the creation of * the {@link PaginatedResult}. * * @param page Used for access to request parameters. * @return the {@link PaginatedResult} to be displayed by the widget. */ public PaginatedResult<T> getPaginatedResult(ToolPageContext page) { return getQuery(page).select(page.param(long.class, OFFSET_PARAMETER), page.paramOrDefault(int.class, LIMIT_PARAMETER, LIMITS[0])); } /** * Optionally override to provide filters for the PaginatedResult displayed * by the widget. * * @param page Used to write {@link DashboardWidget} HTML. * @throws IOException */ public void writeFiltersHtml(ToolPageContext page) throws IOException { // Default implementation has no filters } /** * Optionally override to customize the container of the results. * * @param page Used to write {@link DashboardWidget} HTML. * @param result A {@link PaginatedResult} from {@link #getQuery(ToolPageContext)} * @throws IOException */ public void writeResultsHtml(ToolPageContext page, PaginatedResult<T> result) throws IOException { page.writeStart("table", "class", "links table-striped pageThumbnails").writeStart("tbody"); for (T record : result.getItems()) { writeResultsItemHtml(page, record); } page.writeEnd().writeEnd(); } public void writeEmptyHtml(ToolPageContext page) throws IOException { page.writeStart("div", "class", "message message-info"); page.writeStart("p"); page.writeHtml(page.localize(AbstractPaginatedResultWidget.class, "message.noResults")); page.writeEnd(); page.writeEnd(); } /** * Optionally override to customize appearance of a row in the table. * * @param page Used to write {@link DashboardWidget} HTML. * @param record A {@link Record} from the items produced by the {@link PaginatedResult} used by {@link #writeResultsHtml(ToolPageContext, PaginatedResult)} * @throws IOException */ public void writeResultsItemHtml(ToolPageContext page, T record) throws IOException { page.writeStart("tr"); page.writeStart("td"); page.writeTypeLabel(record); page.writeEnd(); page.writeStart("td"); if (record instanceof Content) { page.writeStart("a", "target", "_top", "href", page.objectUrl("/content/edit.jsp", record)); } page.writeObjectLabel(record); if (record instanceof Content) { page.writeEnd(); } page.writeEnd(); page.writeEnd(); } @Override public void writeHtml(ToolPageContext page, Dashboard dashboard) throws IOException, ServletException { page.writeStart("div", "class", "widget"); page.writeStart("h1"); page.writeHtml(getTitle(page)); page.writeEnd(); page.writeStart("div", "class", "widget-filters"); page.writeStart("form", "method", "get", "action", page.url(null)); writeFiltersHtml(page); page.writeEnd(); page.writeEnd(); PaginatedResult<T> result = getPaginatedResult(page); writePaginationHtml(page, result, page.paramOrDefault(int.class, LIMIT_PARAMETER, LIMITS[0])); if (result.hasPages()) { writeResultsHtml(page, result); } else { writeEmptyHtml(page); } page.writeEnd(); } private void writePaginationHtml(ToolPageContext page, PaginatedResult<T> result, int limit) throws IOException { // Pagination page.writeStart("ul", "class", "pagination"); if (result.hasPrevious()) { page.writeStart("li", "class", "first"); page.writeStart("a", "href", page.url("", OFFSET_PARAMETER, result.getFirstOffset())); page.writeHtml(page.localize(AbstractPaginatedResultWidget.class, "pagination.newest")); page.writeEnd(); page.writeEnd(); page.writeStart("li", "class", "previous"); page.writeStart("a", "href", page.url("", OFFSET_PARAMETER, result.getPreviousOffset())); page.writeHtml(page.localize(ImmutableMap.of("count", limit), "pagination.newerCount")); page.writeEnd(); page.writeEnd(); } if (result.getOffset() > 0 || result.hasNext() || result.getItems().size() > LIMITS[0]) { page.writeStart("li"); page.writeStart("form", "data-bsp-autosubmit", "", "method", "get", "action", page.url(null)); page.writeStart("select", "name", LIMIT_PARAMETER); for (int l : LIMITS) { page.writeStart("option", "value", l, "selected", limit == l ? "selected" : null); page.writeHtml(page.localize(AbstractPaginatedResultWidget.class, ImmutableMap.of("count", l), "option.showCount")); page.writeEnd(); } page.writeEnd(); page.writeEnd(); page.writeEnd(); } if (result.hasNext()) { page.writeStart("li", "class", "next"); page.writeStart("a", "href", page.url("", "offset", result.getNextOffset())); page.writeHtml(page.localize(AbstractPaginatedResultWidget.class, ImmutableMap.of("count", limit), "pagination.olderCount")); page.writeEnd(); page.writeEnd(); } page.writeEnd(); } }