All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.magicwerk.brownies.html.content.HtmlTableFormatter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012 by Thomas Mauch
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id$
 */
package org.magicwerk.brownies.html.content;

import org.jdom2.Element;
import org.magicwerk.brownies.core.CheckTools;
import org.magicwerk.brownies.core.ObjectTools;
import org.magicwerk.brownies.core.collections.GridSelection;
import org.magicwerk.brownies.core.collections.IGrid;
import org.magicwerk.brownies.core.collections.SpanCell;
import org.magicwerk.brownies.core.collections.SpanGrid;
import org.magicwerk.brownies.core.print.PrintTools;
import org.magicwerk.brownies.core.strings.StringFormatter;
import org.magicwerk.brownies.core.types.Type;
import org.magicwerk.brownies.core.values.ITableModel;
import org.magicwerk.brownies.html.HtmlConst;
import org.magicwerk.brownies.html.HtmlElement;
import org.magicwerk.brownies.html.HtmlFlow;
import org.magicwerk.brownies.html.HtmlResource;
import org.magicwerk.brownies.html.HtmlResources;
import org.magicwerk.brownies.html.HtmlTable;
import org.magicwerk.brownies.html.HtmlTbody;
import org.magicwerk.brownies.html.HtmlThead;
import org.magicwerk.brownies.html.HtmlTools;
import org.magicwerk.brownies.html.HtmlTr;
import org.magicwerk.brownies.html.content.HtmlFormatters.Context;
import org.magicwerk.brownies.html.content.HtmlFormatters.HtmlFormatter;

/**
 * Class {@link HtmlTableFormatter} formats a {@link IGrid} into a HTML table.
 */
public class HtmlTableFormatter {

	static class HtmlRegionFormatter {
		GridSelection region;
		HtmlFormatter formatter;

		HtmlRegionFormatter(GridSelection region, HtmlFormatter formatter) {
			this.region = region;
			this.formatter = formatter;
		}

		GridSelection getRegion() {
			return region;
		}

		HtmlFormatter getFormatter() {
			return formatter;
		}
	}

	/** CSS class which marks a table as begin active supporting sorting and filtering */
	public static final String CLASS_ACTIVE_TABLE = "activeTable";
	/** CSS class which marks a table as having a fixed, i.e. always visible header if it is scrolled */
	public static final String CLASS_FIXED_HEADER = "fixedHeader";

	// See https://adrianroselli.com/2020/01/fixed-table-headers.html
	// @formatter:off
	static final String CSS_FIXED_HEADER = 
			  "table.fixedHeader th {\r\n"
			+ "  position: -webkit-sticky;\r\n"
			+ "  position: sticky;\r\n"
			+ "  top: 0;\r\n"
			+ "  z-index: 2;\r\n"
			+ "}\r\n"
			+ "\r\n"
			+ "table.fixedHeader th[scope=row] {\r\n"
			+ "  position: -webkit-sticky;\r\n"
			+ "  position: sticky;\r\n"
			+ "  left: 0;\r\n"
			+ "  z-index: 1;\r\n"
			+ "}\r\n"
			+ "";
	// @formatter:on

	// Members

	/** True to make this an active table supporting sorting and filtering */
	boolean active;
	/** True to fix the table header if the table is scrolled (only used if a table is rendered) */
	boolean fixedHeader;
	/** Controls whether the header row is shown if a table is rendered (ignored if a grid is rendered) */
	boolean showHeader = true;
	/** Controls the number of header rows if a grid rendered (ignored if a table is rendered) */
	int headerRows;
	/** Controls the number of header cols if a grid rendered (ignored if a table is rendered) */
	int headerCols;
	/** True to use colspan/rowspan for display. Only used if the input grid is of type SpanGrid. */
	boolean useSpans = true;
	/** Formatter */
	HtmlFormatters formatters = new HtmlFormatters();

	/**
	 * Returns static HTML resources needed for formatting tables.
	 */
	public static HtmlResources getHtmlResources() {
		HtmlResource res = new HtmlResource("HtmlTableFormatter");
		res.getHead().addStyleCss("table.activeTable th { cursor: pointer; }");
		res.getHead().addStyleCss("table.activeTable th .filter { background-color: #ffffcc; font-weight: normal; }");
		res.getHead().addStyleCss(CSS_FIXED_HEADER);
		res.getHead().addScriptJs(HtmlTools.normalizeJs(HtmlJs.getHtmlTableFormatterJs()));
		res.getHead().addScriptJs(HtmlTools.normalizeJs(HtmlJs.getDialogJs()));

		HtmlResources rs = new HtmlResources();
		rs.add(HtmlJs.getJqueryResource());
		rs.add(HtmlJs.getJqueryUiResource());
		rs.add(res);
		return rs;
	}

	/**
	 * Returns JS code to enable dynamic display options for the specified table (sorting, filtering).
	 */
	public static String getJsToActivateTable(HtmlTable tab) {
		String id = HtmlTools.getOrSetId(tab);
		String js = StringFormatter.format("activateTable($('#{}'));", id);
		return js;
	}

	// Methods

	/** Setter for {@link #formatters} */
	public HtmlTableFormatter setFormatters(HtmlFormatters formatters) {
		this.formatters = formatters;
		return this;
	}

	/** Setter for {@link #active} */
	public HtmlTableFormatter setActive(boolean active) {
		this.active = active;
		return this;
	}

	/** Setter for {@link #fixedHeader} */
	public HtmlTableFormatter setFixedHeader(boolean fixedHeader) {
		this.fixedHeader = fixedHeader;
		return this;
	}

	/** Setter for {@link #showHeader} */
	public HtmlTableFormatter setShowHeader(boolean showHeader) {
		this.showHeader = showHeader;
		return this;
	}

	/** Setter for {@link #headerRows} */
	public HtmlTableFormatter setHeaderRows(int headerRows) {
		this.headerRows = headerRows;
		return this;
	}

	/** Setter for {@link #headerCols} */
	public HtmlTableFormatter setHeaderCols(int headerCols) {
		this.headerCols = headerCols;
		return this;
	}

	/** Setter for {@link #useSpans} */
	public HtmlTableFormatter setUseSpans(boolean useSpans) {
		this.useSpans = useSpans;
		return this;
	}

	HtmlTable createTable() {
		HtmlTable t = new HtmlTable();
		if (active) {
			t.addClass(CLASS_ACTIVE_TABLE);
		}
		if (fixedHeader) {
			t.addClass(CLASS_FIXED_HEADER);
		}
		return t;
	}

	public HtmlTable format(IGrid grid) {
		if (grid instanceof ITableModel) {
			return formatTable((ITableModel) grid);
		} else {
			return formatGrid(grid);
		}
	}

	/**
	 * Convert content stored in a table into a HTML table.
	 * If showHeader is set, a header row is inserted before the values.
	 *
	 * @param table			table to convert
	 * @return				HTML table with content of table
	 */
	HtmlTable formatTable(ITableModel table) {
		CheckTools.checkNonNull(table, "table");
		formatters.checkFormatters(table.getNumRows(), table.getNumCols());

		Context ctx = new Context();
		ctx.source = table;

		HtmlTable t = createTable();

		// Header
		if (showHeader) {
			HtmlThead th = t.newThead();
			ctx.row = -1;
			HtmlTr tr = th.newTr();
			for (int c = 0; c < table.getNumCols(); c++) {
				ctx.col = c;
				ctx.val = table.getColName(c);
				ctx.str = (String) ctx.val;
				HtmlFlow cell = tr.newTh();
				formatters.formatElem(cell, ctx);
			}
		}

		// Body
		HtmlTbody tb = t.newTbody();
		for (int r = 0; r < table.getNumRows(); r++) {
			HtmlTr tr = tb.newTr();
			ctx.row = r;
			for (int c = 0; c < table.getNumCols(); c++) {
				ctx.col = c;
				ctx.val = table.getElem(r, c);
				@SuppressWarnings("unchecked")
				Type type = (Type) table.getColType(c);
				ctx.str = type.format(ctx.val);
				HtmlFlow cell = tr.newTd();
				formatters.formatElem(cell, ctx);
			}
		}
		return t;
	}

	/**
	 * Convert content stored in a grid into a HTML table.
	 * The grid can contain objects which are converted to string for display, or pre-formatted HTML content as as Element or HtmlElement.
	 *
	 * @param grid			grid to convert
	 * @return				HTML table with content of grid
	 */
	HtmlTable formatGrid(IGrid grid) {
		CheckTools.checkNonNull(grid, "grid");
		formatters.checkFormatters(grid.getNumRows(), grid.getNumCols());

		Context ctx = new Context();
		ctx.source = grid;

		HtmlTable t = createTable();
		for (int r = 0; r < grid.getNumRows(); r++) {
			HtmlTr tr = t.newTr();
			ctx.row = r;
			for (int c = 0; c < grid.getNumCols(); c++) {
				ctx.col = c;
				SpanCell spanCell = null;
				if (useSpans && grid instanceof SpanGrid) {
					spanCell = ((SpanGrid) grid).getCell(c, r);
				}
				Object obj = grid.getElem(r, c);

				boolean isThTd = false;
				Element elem = null;
				if (obj instanceof HtmlElement) {
					HtmlElement htmlElem = (HtmlElement) obj;
					obj = htmlElem.getElement();
				}
				if (obj instanceof Element) {
					elem = (Element) obj;
					isThTd = (ObjectTools.isOneOf(elem.getName(), HtmlConst.ELEM_TH, HtmlConst.ELEM_TD));
				}

				HtmlFlow cell;
				if (isThTd) {
					// TODO how should it work together with spans?
					cell = (HtmlFlow) new HtmlFlow().attach(elem);
				} else {

					if (spanCell != null) {
						if (spanCell.getCol() != c || spanCell.getRow() != r) {
							continue;
						}
					}

					if (r < headerRows || c < headerCols) {
						cell = tr.newTh();
					} else {
						cell = tr.newTd();
					}
					if (spanCell != null) {
						if (spanCell.getColSpan() != 1) {
							cell.setAttribute("colspan", "" + spanCell.getColSpan());
						}
						if (spanCell.getRowSpan() != 1) {
							cell.setAttribute("rowspan", "" + spanCell.getRowSpan());
						}
					}

					if (elem != null) {
						cell.getElement().addContent(elem);
						ctx.val = obj;
						ctx.str = null;
					} else {
						ctx.val = obj;
						ctx.str = PrintTools.toString(obj, "");
					}
				}
				formatters.formatElem(cell, ctx);
			}
		}
		return t;
	}

}