package com.psddev.cms.db; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyTagSupport; import javax.servlet.jsp.tagext.DynamicAttributes; import javax.servlet.jsp.tagext.TryCatchFinally; import com.psddev.dari.util.HtmlGrid; import com.psddev.dari.util.HtmlObject; import com.psddev.dari.util.HtmlWriter; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.TypeReference; public class LayoutTag extends BodyTagSupport implements DynamicAttributes, TryCatchFinally { private static final long serialVersionUID = 1L; private static final String ATTRIBUTE_PREFIX = LayoutTag.class.getName() + "."; private static final String GRID_CSS_WRITTEN_ATTRIBUTE = ATTRIBUTE_PREFIX + "gridCssWritten"; private static final String GRID_CSS_CLASS_WRITTEN_ATTRIBUTE_PREFIX = ATTRIBUTE_PREFIX + "gridCssClassWritten."; private static final String GRID_CSS_COMMON_WRITTEN_ATTRIBUTE = ATTRIBUTE_PREFIX + "gridCssCommonWritten"; private static final String GRID_JAVASCRIPT_WRITTEN_ATTRIBUTE = ATTRIBUTE_PREFIX + "gridJavaScriptWritten"; private static final String GRIDS_ATTRIBUTE = ATTRIBUTE_PREFIX + "grids"; private final Map<String, Object> attributes = new LinkedHashMap<String, Object>(); private transient HtmlWriter writer; private transient List<CssClassHtmlGrid> cssGrids; private transient List<String> cssClasses; private transient String extraCss; private transient Map<String, Object> areas; public Map<String, Object> getAreas() { return areas; } // --- DynamicAttributes support --- @Override public void setDynamicAttribute(String uri, String localName, Object value) { if (value != null) { attributes.put(localName, value); } } // --- TryCatchFinally support --- @Override public void doCatch(Throwable error) throws Throwable { throw error; } @Override public void doFinally() { attributes.clear(); writer = null; cssGrids = null; cssClasses = null; extraCss = null; areas = null; } // --- TagSupport support --- protected HtmlGrid getGrid(HttpServletRequest request, Object area) { if (area instanceof Integer) { int areaInt = (Integer) area; int gridOffset = 0; for (CssClassHtmlGrid entry : cssGrids) { HtmlGrid grid = entry.grid; int gridAreaSize = entry.grid.getAreas().size(); if (areaInt < gridOffset + gridAreaSize) { return grid; } else { gridOffset += gridAreaSize; } } } else if (cssGrids != null && !cssGrids.isEmpty()) { return cssGrids.iterator().next().grid; } return null; } public Object getAreaName(HttpServletRequest request, Object area) { if (area instanceof Integer) { int areaInt = (Integer) area; int gridOffset = 0; for (CssClassHtmlGrid entry : cssGrids) { int gridAreaSize = entry.grid.getAreas().size(); if (areaInt < gridOffset + gridAreaSize) { return areaInt - gridOffset; } else { gridOffset += gridAreaSize; } } } return area; } @Override public int doStartTag() throws JspException { ServletContext context = pageContext.getServletContext(); HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); try { writer = new HtmlWriter(pageContext.getOut()); cssGrids = new ArrayList<CssClassHtmlGrid>(); cssClasses = ObjectUtils.to(new TypeReference<List<String>>() { }, attributes.remove("class")); Set<String> gridCssClasses = new HashSet<String>(); extraCss = ObjectUtils.to(String.class, attributes.remove("extraClass")); if (cssClasses != null) { for (Object cssClassObject : cssClasses) { if (cssClassObject != null) { String cssClassString = cssClassObject.toString(); for (String cssClass : cssClassString.split(" ")) { cssClass = cssClass.trim(); @SuppressWarnings("unchecked") Map<String, HtmlGrid> grids = (Map<String, HtmlGrid>) request.getAttribute(GRIDS_ATTRIBUTE); if (grids == null) { grids = HtmlGrid.Static.findAll(context, request); request.setAttribute(GRIDS_ATTRIBUTE, grids); } HtmlGrid grid = grids.get("." + cssClass); if (grid != null) { cssGrids.add(new CssClassHtmlGrid(cssClassString, grid)); gridCssClasses.add(cssClass); } } } } } if (cssGrids.isEmpty()) { areas = null; writer.writeStart("div", attributes, "class", cssClasses != null && !cssClasses.isEmpty() ? cssClasses.get(0) + " " + extraCss : extraCss); } else { areas = new LinkedHashMap<String, Object>(); LayoutTag.Static.writeGridCssByClasses(writer, context, request, gridCssClasses); LayoutTag.Static.writeGridJavaScript(writer, context, request); } } catch (IOException error) { throw new JspException(error); } return areas != null ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE; } @Override public int doEndTag() throws JspException { try { if (cssGrids.isEmpty()) { writer.writeEnd(); } else { List<Object> areasList = new ArrayList<Object>(areas.values()); int areaSize = areasList.size(); int gridOffset = 0; for (CssClassHtmlGrid cssGrid : cssGrids) { String cssClass = cssGrid.cssClass; HtmlGrid grid = cssGrid.grid; Map<String, GridItem> items = new LinkedHashMap<String, GridItem>(); int gridAreaSize = grid.getAreas().size(); for (Map.Entry<String, Object> areaEntry : areas.entrySet()) { items.put(areaEntry.getKey(), new GridItem(areaEntry.getValue())); } int i = 0; for (; i < gridAreaSize && gridOffset + i < areaSize; ++ i) { items.put(String.valueOf(i), new GridItem(areasList.get(gridOffset + i))); } for (; i < gridAreaSize; ++ i) { items.put(String.valueOf(i), null); } gridOffset += gridAreaSize; if (extraCss != null) { cssClass += " " + extraCss; } writer.writeStart("div", attributes, "class", cssClass); writer.writeGrid(items, grid); writer.writeEnd(); } } } catch (IOException error) { throw new JspException(error); } return EVAL_PAGE; } /** {@link LayoutTag} utility methods. */ public static final class Static { private Static() { } /** * Writes all grid CSS found in the given {@code context} to the * given {@code writer} unless it's already been written within the * given {@code request}. * * @param writer Can't be {@code null}. * @param context Can't be {@code null}. * @param request Can't be {@code null}. */ public static void writeGridCss(HtmlWriter writer, ServletContext context, HttpServletRequest request) throws IOException { if (request.getAttribute(GRID_CSS_WRITTEN_ATTRIBUTE) == null) { writer.writeStart("style", "type", "text/css"); writer.writeAllGridCss(context, request); writer.writeEnd(); request.setAttribute(GRID_CSS_WRITTEN_ATTRIBUTE, Boolean.TRUE); } } /** * Writes all grid CSS related to the given {@code cssClasses found * in the given {@code context} to the given {@code writer} unless * it's already been written within the given {@code request}. * * @param writer Can't be {@code null}. * @param context Can't be {@code null}. * @param request Can't be {@code null}. * @param cssClasses May be {@code null}. */ public static void writeGridCssByClasses( HtmlWriter writer, ServletContext context, HttpServletRequest request, Collection<String> cssClasses) throws IOException { if (request.getAttribute(GRID_CSS_WRITTEN_ATTRIBUTE) != null) { return; } boolean styleStarted = false; if (request.getAttribute(GRID_CSS_COMMON_WRITTEN_ATTRIBUTE) == null) { request.setAttribute(GRID_CSS_COMMON_WRITTEN_ATTRIBUTE, Boolean.TRUE); if (!styleStarted) { styleStarted = true; writer.writeStart("style", "type", "text/css"); } writer.writeCommonGridCss(); } if (cssClasses != null) { Map<String, HtmlGrid> grids = HtmlGrid.Static.findAll(context, request); for (String cssClass : cssClasses) { for (Map.Entry<String, HtmlGrid> entry : grids.entrySet()) { String selector = entry.getKey(); if (selector.contains(cssClass)) { String attr = GRID_CSS_CLASS_WRITTEN_ATTRIBUTE_PREFIX + selector; if (request.getAttribute(attr) == null) { request.setAttribute(attr, Boolean.TRUE); if (!styleStarted) { styleStarted = true; writer.writeStart("style", "type", "text/css"); } writer.writeGridCss(selector, entry.getValue()); } } } } } if (styleStarted) { writer.writeEnd(); } } /** * Writes all grid JavaScript found in the given {@code context} to the * given {@code writer} unless it's already been written within the * given {@code request}. * * @param writer Can't be {@code null}. * @param context Can't be {@code null}. * @param request Can't be {@code null}. */ public static void writeGridJavaScript(HtmlWriter writer, ServletContext context, HttpServletRequest request) throws IOException { if (request.getAttribute(GRID_JAVASCRIPT_WRITTEN_ATTRIBUTE) == null) { writer.writeStart("script", "type", "text/javascript"); writer.writeAllGridJavaScript(context, request); writer.writeEnd(); request.setAttribute(GRID_JAVASCRIPT_WRITTEN_ATTRIBUTE, Boolean.TRUE); } } } private static class CssClassHtmlGrid { public final String cssClass; public final HtmlGrid grid; public CssClassHtmlGrid(String cssClass, HtmlGrid grid) { this.cssClass = cssClass; this.grid = grid; } } private class GridItem implements HtmlObject { private final Object item; public GridItem(Object item) { this.item = item; } @Override public void format(HtmlWriter writer) throws IOException { if (item != null) { writer.write(item.toString()); } } } }