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

org.devocative.wickomp.grid.WBaseGrid Maven / Gradle / Ivy

package org.devocative.wickomp.grid;

import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.core.util.lang.PropertyResolver;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.IRequestParameters;
import org.devocative.adroit.ObjectUtil;
import org.devocative.wickomp.IExceptionToMessageHandler;
import org.devocative.wickomp.WDefaults;
import org.devocative.wickomp.WJqCallbackComponent;
import org.devocative.wickomp.WebUtil;
import org.devocative.wickomp.data.RObject;
import org.devocative.wickomp.data.RObjectList;
import org.devocative.wickomp.grid.column.OColumn;
import org.devocative.wickomp.grid.column.OColumnList;
import org.devocative.wickomp.grid.column.OPropertyColumn;
import org.devocative.wickomp.grid.column.link.OAjaxLinkColumn;
import org.devocative.wickomp.grid.column.link.OLinkColumn;
import org.devocative.wickomp.grid.toolbar.OAjaxLinkButton;
import org.devocative.wickomp.grid.toolbar.OButton;
import org.devocative.wickomp.grid.toolbar.OLinkButton;
import org.devocative.wickomp.opt.OStyle;
import org.devocative.wickomp.wrcs.FontAwesomeBehavior;
import org.devocative.wickomp.wrcs.HeaderBehavior;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyDescriptor;
import java.util.*;

public abstract class WBaseGrid extends WJqCallbackComponent {
	public static final String URL_PARAM_ID = "$id";
	public static final String URL_PARAM_CLICK_TYPE = "$tp";
	public static final String URL_PARAM_COLUMN_NUMBER = "$cn";
	public static final String URL_PARAM_COLUMN_REORDER = "$cr";

	public static final String CLICK_FROM_CELL = "cl";
	public static final String CLICK_FROM_BUTTON = "bt";

	// ------------------------------

	private static final long serialVersionUID = -2882882330275047801L;
	private static final String TOOLBAR_HTML_CLASS = "w-grid-tbar";

	protected static final Logger logger = LoggerFactory.getLogger(WBaseGrid.class);

	// ------------------------------

	private OBaseGrid options;
	private IExceptionToMessageHandler exceptionMessageHandler = WDefaults.getExceptionToMessageHandler();

	private IDataSource dataSource;
	private IGridDataSource gridDataSource;
	private IGridAsyncDataSource gridAsyncDataSource;
	private IGridFooterDataSource footerDataSource;

	private boolean hideToolbarFirstTime = true;
	private boolean automaticColumns = false;
	private boolean ignoreDataSourceCount = false;

	protected Integer pageSize, pageNum;
	protected List sortFieldList = new ArrayList<>();
	protected Map> pageData = new HashMap<>();

	// ------------------------------ CONSTRUCTORS

	public WBaseGrid(String id, OBaseGrid options, IGridDataSource gridDataSource) {
		super(id, options);

		this.dataSource = gridDataSource;
		this.gridDataSource = gridDataSource;
		this.options = options;
	}

	public WBaseGrid(String id, OBaseGrid options, IGridAsyncDataSource gridAsyncDataSource) {
		super(id, options);

		this.dataSource = gridAsyncDataSource;
		this.gridAsyncDataSource = gridAsyncDataSource;
		this.options = options;
	}

	// ------------------------------ ACCESSORS

	public OBaseGrid getOptions() {
		return options;
	}

	public Integer getPageSize() {
		return pageSize;
	}

	public Integer getPageNum() {
		return pageNum;
	}

	public List getSortFieldList() {
		return sortFieldList;
	}

	public WBaseGrid setExceptionMessageHandler(IExceptionToMessageHandler exceptionMessageHandler) {
		this.exceptionMessageHandler = exceptionMessageHandler;
		return this;
	}

	public WBaseGrid setFooterDataSource(IGridFooterDataSource footerDataSource) {
		this.footerDataSource = footerDataSource;
		options.setShowFooter(true);
		return this;
	}

	public WBaseGrid setHideToolbarFirstTime(boolean hideToolbarFirstTime) {
		this.hideToolbarFirstTime = hideToolbarFirstTime;
		return this;
	}

	public WBaseGrid setAutomaticColumns(boolean automaticColumns) {
		this.automaticColumns = automaticColumns;
		return this;
	}

	public WBaseGrid setIgnoreDataSourceCount(boolean ignoreDataSourceCount) {
		this.ignoreDataSourceCount = ignoreDataSourceCount;
		return this;
	}

	// ------------------------------ METHODS

	public WBaseGrid loadData(AjaxRequestTarget target) {
		if (isEnabledInHierarchy()) {
			if (options.getUrl() == null) {
				options.setUrl(getCallbackURL());
				target.appendJavaScript(String.format("$('#%s').%s('updateUrl',\"%s\");",
					getMarkupId(), getJQueryFunction(), getCallbackURL()));
			}

			target.appendJavaScript(String.format("$('#%s').%s('resetData');",
				getMarkupId(), getJQueryFunction()));
		} else {
			throw new WicketRuntimeException("WBaseGrid is disabled: " + getId());
		}
		return this;
	}

	public WBaseGrid makeVisible(AjaxRequestTarget target) {
		if (!isVisible()) {
			setVisible(true);
			target.add(this);
		}
		return this;
	}

	public WBaseGrid pushData(IPartialPageRequestHandler handler, List list) {
		return pushData(handler, list, -1, null);
	}

	public WBaseGrid pushData(IPartialPageRequestHandler handler, List list, long count) {
		return pushData(handler, list, count, null);
	}

	public WBaseGrid pushData(IPartialPageRequestHandler handler, List list, long count, List footer) {
		if (isEnabledInHierarchy()) {
			if (ignoreDataSourceCount) {
				count = (pageNum - 1) * pageSize + list.size();
				if (list.size() == pageSize) {
					count++;
				}
			}

			RGridPage gridPage = getGridPage(list, count);

			if (options.hasFooter()) {
				if (footer != null) {
					gridPage.setFooter(getGridFooter(footer));
				} else {
					gridPage.setFooter(new ArrayList<>());
				}
			}

			String script = createClientScript(gridPage);

			logger.debug("WBaseGrid.pushData(): {}", script);

			handler.appendJavaScript(script);
		} else {
			throw new WicketRuntimeException("WBaseGrid is disabled: " + getId());
		}

		return this;
	}

	public WBaseGrid pushError(IPartialPageRequestHandler handler, Exception e) {
		if (isEnabledInHierarchy()) {
			RGridPage gridPage = new RGridPage();
			gridPage.setTotal((long) pageNum * pageSize);
			gridPage.setRows(new RObjectList());
			gridPage.setError(exceptionMessageHandler.handleMessage(this, e));

			String script = createClientScript(gridPage);

			logger.debug("WBaseGrid.pushError(): {}", script);

			handler.appendJavaScript(script);
		} else {
			throw new WicketRuntimeException("WBaseGrid is disabled: " + getId());
		}

		return this;
	}

	// ------------------------------ INTERNAL METHODS

	@Override
	protected void onInitialize() {
		super.onInitialize();

		if (options.getColumns() == null) {
			options.setColumns(new OColumnList<>());
		}

		int i = 0;
		for (OColumn column : options.getColumns().getAllColumns()) {
			if (column.getField() == null) {
				column
					.setField("f" + (i++))
					.setDummyField(true);
			}
		}

		options.getColumns().validate();
		pageNum = 1;
		pageSize = options.getPageSize();

		add(new FontAwesomeBehavior());
		add(new HeaderBehavior("main/wGrid.js").setNeedEasyUI(true));

		if (gridDataSource == null && gridAsyncDataSource == null) {
			throw new WicketRuntimeException("WBaseGrid without datasource: " + getId());
		}

		if (gridDataSource != null && gridAsyncDataSource != null) {
			throw new WicketRuntimeException("WBaseGrid has both dataSource & asyncDataSource: " + getId());
		}
	}

	@Override
	protected void onBeforeRender() {
		super.onBeforeRender();

		// It should be called in onBeforeRender, not worked in onInitialize, causing StalePageException
		if (!isEnabledInHierarchy()) {
			options.setUrl(null);
		}
		/*
		The following is set by IHtmlId & ICallbackUrl

		else {
			options.setUrl(getCallbackURL());
		}
		options.setHtmlId(getMarkupId());
		*/

		for (int i = 0; i < options.getToolbarButtons().size(); i++) {
			OButton button = options.getToolbarButtons().get(i);
			button.init(getCallbackURL(), i, options.getHtmlId(), options.getColumns());
		}
	}

	@Override
	protected void onRequest() {
		if (!isEnabledInHierarchy()) {
			return;
		}

		IRequestParameters getParams = getRequest().getQueryParameters();
		IRequestParameters postParams = getRequest().getPostParameters();

		/*
		NOTE: EasyUI POST params = rows, page, id, sort, order
		 */

		pageSize = postParams.getParameterValue("rows").toInt(options.getPageSize());
		pageNum = postParams.getParameterValue("page").toInt(1);

		/*
		NOTE: idByPost vs idByGet
		idByPost: sent by EasyUI TreeGrid for expanded node
		idByGet:  sent by a custom link in the cell
		 */
		String idByGet = getParams.getParameterValue(URL_PARAM_ID).toOptionalString();
		String idByPost = postParams.getParameterValue("id").toOptionalString();

		String sortList = postParams.getParameterValue("sort").toOptionalString();
		String orderList = postParams.getParameterValue("order").toOptionalString();

		String clickType = getParams.getParameterValue(URL_PARAM_CLICK_TYPE).toString();
		Integer colNo = getParams.getParameterValue(URL_PARAM_COLUMN_NUMBER).toOptionalInteger();
		String columnReorder = getParams.getParameterValue(URL_PARAM_COLUMN_REORDER).toOptionalString();

		logger.debug("WBaseGrid.onRequest:\n\tpageSize=[{}], pageNum=[{}], sort=[{}], order=[{}], idByPost=[{}]\n\tclickType=[{}] idByGet=[{}] colNo=[{}]",
			pageSize, pageNum, sortList, orderList, idByPost, clickType, idByGet, colNo);

		if (CLICK_FROM_CELL.equals(clickType)) {// click from cell (per row)
			if (idByGet == null) {
				throw new RuntimeException("Null id parameter!");
			}
			if (colNo == null) {
				throw new RuntimeException("Null colNo parameter!");
			}
			handleCellLinkClick(idByGet, colNo);
		} else if (CLICK_FROM_BUTTON.equals(clickType)) {// click from button in toolbar
			if (colNo == null) {
				throw new RuntimeException("Null button index parameter!");
			}
			handleToolbarButtonClick(colNo);
		} else if (columnReorder != null) {
			logger.debug("Column Reorder: {}", columnReorder);
			String[] columns = columnReorder.split("[,]");
			onColumnReorder(Arrays.asList(columns));
			sendEmptyResponse();
		} else if (idByPost != null && idByPost.length() > 0) {
			handleRowsById(idByPost);
		} else {
			if (sortList != null && orderList != null) {
				updateSortFieldList(sortList.split(","), orderList.split(","));
			} else {
				sortFieldList.clear();
			}

			logger.debug("WBaseGrid: SortFields = {}", sortFieldList);

			RGridPage result = getGridPage();
			if (gridDataSource != null) {
				sendJSONResponse(WebUtil.toJson(result));
			} else {
				sendJSONResponse("");
			}
		}
	}

	@Override
	protected void onAfterRender() {
		super.onAfterRender();

		if (isVisible()) {
			createToolbar();
		}
	}

	// ---------------

	protected final RGridPage getGridPage() {
		RGridPage result = null;
		try {
			if (gridDataSource != null) {
				List data = gridDataSource.list(pageNum, pageSize, sortFieldList);

				long count;
				if (data.size() < pageSize || ignoreDataSourceCount) {
					count = (pageNum - 1) * pageSize + data.size();
					//TODO the plus-one must be detected by requesting one more item from data source!
					if (ignoreDataSourceCount && data.size() == pageSize) {
						count++;
					}
				} else {
					count = gridDataSource.count();
				}

				result = getGridPage(data, count);

				if (options.hasFooter() && footerDataSource != null) {
					result.setFooter(getGridFooter(footerDataSource.footer(data)));
				}
			} else {
				gridAsyncDataSource.asyncList(pageNum, pageSize, sortFieldList);
			}
		} catch (Exception e) {
			logger.warn("Grid.DataSource: id=" + getId(), e);
			result = new RGridPage();
			result.setTotal((long) pageNum * pageSize);
			result.setError(exceptionMessageHandler.handleMessage(this, e));
		}
		return result;
	}

	protected final RGridPage getGridPage(List data, long count) {
		RGridPage result = new RGridPage();
		if (data != null) {
			result.setRows(createRObjectList(data));
		} else {
			result.setRows(new RObjectList());
		}
		result.setTotal(count);
		return result;
	}

	protected RObjectList createRObjectList(List data) {
		pageData.clear();

		RObjectList objectList = new RObjectList();
		convertBeansToRObjects(data, objectList);
		return objectList;
	}

	protected List getGridFooter(List footerData) {
		List footer = new ArrayList<>();

		if (footerData != null) {
			List> columns = options.getColumns().getAllColumns();
			for (Object bean : footerData) {
				RObject rObject = new RObject();
				for (int colNo = 0; colNo < columns.size(); colNo++) {
					OColumn column = columns.get(colNo);
					if (column.isHasFooter() && column.isVisible()) {
						String url = String.format("%s&%s=%s&%s=%s", getCallbackURL(), URL_PARAM_COLUMN_NUMBER, colNo,
							URL_PARAM_CLICK_TYPE, CLICK_FROM_CELL);
						rObject.addProperty(column.getField(), column.footerCellValue(bean, colNo, url));
					}
				}
				footer.add(rObject);
			}
		}

		return footer;
	}

	protected void handleRowsById(String id) {
	}

	protected void onAfterBeanToRObject(T bean, RObject rObject) {
	}

	protected void onColumnReorder(List columns) {
	}

	protected final void convertBeansToRObjects(List list, RObjectList page) {
		if (automaticColumns && !list.isEmpty()) {
			publishDynamicColumns(list.get(0));
		}

		List> columns = options.getColumns().getAllColumns();

		for (int rowNo = 0; rowNo < list.size(); rowNo++) {
			T bean = list.get(rowNo);
			RObject rObject = new RObject();

			String id = String.valueOf(rowNo);
			if (options.getIdField() != null) {
				Object idValue = PropertyResolver.getValue(options.getIdField(), bean);
				if (idValue == null) {
					throw new RuntimeException(String.format("Null value for id: idField=[%s] bean=[%s]",
						options.getIdField(), bean));
				}
				id = idValue.toString();
				rObject.addProperty(options.getIdField(), id);
			}
			pageData.put(id, dataSource.model(bean));

			if (options.getRowStyler() != null) {
				OStyle rowStyle = options.getRowStyler().doStyle(bean, id);
				rObject.addProperty("$style", rowStyle);
			}

			for (int colNo = 0; colNo < columns.size(); colNo++) {
				OColumn column = columns.get(colNo);
				if (column.onCellRender(bean, id)) {
					String url = String.format("%s&%s=%s&%s=%s&%s=%s", getCallbackURL(), URL_PARAM_ID, id,
						URL_PARAM_COLUMN_NUMBER, colNo, URL_PARAM_CLICK_TYPE, CLICK_FROM_CELL);
					rObject.addProperty(column.getField(), column.cellValue(bean, id, colNo, url));

					if (column.getCellStyler() != null) {
						OStyle cellStyle = column.getCellStyler().doStyle(bean, id);
						rObject.addProperty(column.getField() + "$style", cellStyle);
					}
				}
			}
			onAfterBeanToRObject(bean, rObject);
			page.addRObject(id, rObject);
		}
	}

	// ---------------

	private void createToolbar() {
		List> toolbarButtons = options.getToolbarButtons();
		if (!toolbarButtons.isEmpty()) {
			StringBuilder builder = new StringBuilder();
			builder
				.append(String.format("
"); for (OButton button : toolbarButtons) { builder.append(""); } builder .append("
").append(button.getHTMLContent()).append("
"); getResponse().write(builder.toString()); } } private void handleToolbarButtonClick(Integer colNo) { OButton button = options.getToolbarButtons().get(colNo); if (button instanceof OLinkButton) { OLinkButton linkButton = (OLinkButton) button; linkButton.onClick(); } else if (button instanceof OAjaxLinkButton) { AjaxRequestTarget target = createAjaxResponse(); OAjaxLinkButton ajaxLinkButton = (OAjaxLinkButton) button; ajaxLinkButton.onClick(target); } else { throw new RuntimeException("Invalid request from button click"); } } private void handleCellLinkClick(String id, Integer colNo) { IModel rowModel = pageData.get(id); OColumn column = options.getColumns().getAllColumns().get(colNo); if (column instanceof OLinkColumn) { OLinkColumn linkColumn = (OLinkColumn) column; linkColumn.onClick(rowModel); } else if (column instanceof OAjaxLinkColumn) { AjaxRequestTarget target = createAjaxResponse(); OAjaxLinkColumn ajaxLinkColumn = (OAjaxLinkColumn) column; try { ajaxLinkColumn.onClick(target, rowModel); } catch (Exception e) { ajaxLinkColumn.onException(target, e, rowModel); } } else { throw new RuntimeException("Invalid request from cell click: " + column.getField()); } } private void updateSortFieldList(String[] sortList, String[] orderList) { sortFieldList.clear(); for (int i = 0; i < sortList.length; i++) { sortFieldList.add(new WSortField(sortList[i], orderList[i])); } } private String createClientScript(RGridPage gridPage) { StringBuilder result = new StringBuilder(); if (automaticColumns) { result.append(String.format("$('#%s').%s('updateColumns', {columns:%s});", getMarkupId(), getJQueryFunction(), WebUtil.toJson(options.getColumns()))); } result.append(String.format("$('#%s').%s('loadData', %s);", getMarkupId(), getJQueryFunction(), WebUtil.toJson(gridPage))); // NOTE: setting URL must be set after loadData, otherwise it sends a request by loadData and fetches data twice if (options.getUrl() == null) { options.setUrl(getCallbackURL()); result.append(String.format("$('#%s').%s('updateUrl', \"%s\");", getMarkupId(), getJQueryFunction(), getCallbackURL())); } return result.toString(); } private void publishDynamicColumns(T sample) { OColumnList columns = options.getColumns(); columns.clear(); if (sample instanceof Map) { Map map = (Map) sample; for (Object key : map.keySet()) { String prop = key.toString(); columns.add(new OPropertyColumn<>(new Model<>(prop), prop)); } } else { PropertyDescriptor[] descriptors = ObjectUtil.getPropertyDescriptors(sample, false); for (PropertyDescriptor pd : descriptors) { String prop = pd.getName(); if ("class".equals(prop)) { continue; } columns.add(new OPropertyColumn<>(new Model<>(prop), prop)); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy