
com.viaoa.web.html.oa.OAHtmlTable Maven / Gradle / Ivy
package com.viaoa.web.html.oa;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.viaoa.hub.Hub;
import com.viaoa.object.OAObject;
import com.viaoa.uicontroller.OAUISelectController;
import com.viaoa.util.OAConv;
import com.viaoa.util.OAStr;
import com.viaoa.web.html.HtmlCol;
import com.viaoa.web.html.HtmlColGroup;
import com.viaoa.web.html.HtmlElement;
import com.viaoa.web.html.HtmlFormElement;
import com.viaoa.web.html.HtmlTD;
import com.viaoa.web.html.HtmlTH;
import com.viaoa.web.html.HtmlTR;
import com.viaoa.web.html.HtmlTable;
import com.viaoa.web.html.form.OAForm;
import com.viaoa.web.html.form.OAFormSubmitEvent;
/**
* Creates a table listing. Columns are added by adding HtmlElements.
* Supports keyboard navigation and editors.
*
* Notes:
* see styles in oaweb.css
* adds style "table-layout: fixed"
* Can be wrapped in a div that scroll css, and has sticky column header.
*/
public class OAHtmlTable extends HtmlTable implements OAHtmlComponentInterface {
private final OAUISelectController oaUiControl;
private final List alColumn = new ArrayList<>();
private static class LastRefresh {
Hub hubUsed;
OAObject objSelected;
// if hub is linked, then this is the current object that it's linked to and changing.
OAObject objLinkedTo;
}
private final LastRefresh lastRefresh = new LastRefresh();
private int submitRow=-1, submitCol=-1;
private String submitKeys;
public OAHtmlTable(String id, Hub hub) {
super(id);
// used to interact between component with hub.
oaUiControl = new OAUISelectController(hub) {
@Override
protected void onCompleted(String completedMessage, String title) {
OAForm form = getForm();
if (form != null) {
form.addMessage(completedMessage);
form.addConsoleMessage(title + " - " + completedMessage);
}
}
@Override
protected void onError(String errorMessage, String detailMessage) {
OAForm form = getForm();
if (form != null) {
form.addError(errorMessage);
form.addConsoleMessage(errorMessage + " - " + detailMessage);
}
}
};
setAjaxSubmit(true);
getOAHtmlComponent().addClass("oatable table table-bordered table-hover table-striped");
// addStyle("table-layout", "fixed");
}
public static class Column {
HtmlCol htmlCol;
HtmlTH htmlTh;
OAHtmlTableComponentInterface comp;
public Column(HtmlCol htmlCol, HtmlTH htmlTh, OAHtmlTableComponentInterface comp) {
this.htmlCol = htmlCol;
this.htmlTh = htmlTh;
this.comp = comp;
}
}
public void addColumn(String title, OAHtmlTableComponentInterface comp) {
addColumn(title, comp, -1);
}
/**
* Add a new column using an HTML Element.
* @param title heading title
* @param comp OAWeb component
* @param width width of the column, using "ch" as the units.
*/
public void addColumn(String title, OAHtmlTableComponentInterface comp, int width) {
if (comp instanceof HtmlElement) {
add((HtmlElement) comp);
}
HtmlCol htmlCol = new HtmlCol();
if (width >= 0) {
htmlCol.setWidth(width+"ch");
}
HtmlTH htmlTh = new HtmlTH();
htmlTh.setInnerHtml(title == null ? "" : title);
addColumn(htmlCol, htmlTh, comp);
}
public Column getColumn(int pos) {
if (pos < 0 || pos >= this.alColumn.size()) return null;
return this.alColumn.get(pos);
}
public void addColumn(HtmlCol htmlCol, HtmlTH htmlTh, OAHtmlTableComponentInterface comp) {
this.alColumn.add(new Column(htmlCol, htmlTh, comp));
HtmlColGroup cg = getColGroup();
if (cg == null) {
cg = new HtmlColGroup("");
setColGroup(cg);
}
cg.addCol(htmlCol);
List al = getTHeadRows();
HtmlTR tr;
if (al.size() == 0) {
tr = new HtmlTR("");
addTHeadRow(tr);
}
else tr = al.get(0);
tr.addTableData(htmlTh);
htmlTh.addClass("table-primary");
}
public void addCounterColumn() {
addCounterColumn("#");
}
/**
* Creates a column for row count, that uses class "oatableColumnCount".
*/
public void addCounterColumn(String title) {
HtmlCol htmlCol = new HtmlCol();
htmlCol.addClass("oatableColumnCount");
htmlCol.addStyle("width", "3ch");
HtmlTH htmlTh = new HtmlTH();
htmlTh.setInnerHtml(title == null ? "" : title);
htmlTh.addClass("oatableColumnCount");
addCounterColumn(htmlCol, htmlTh);
}
public void addCounterColumn(HtmlCol htmlCol, HtmlTH htmlTh) {
OAHtmlTableComponentInterface comp = new OAHtmlTableComponentInterface() {
@Override
public String getTableCellRenderer(HtmlTD td, int row) {
String s = OAConv.toString(row+1, "#,###");
return s;
}
@Override
public String getTableCellEditor(HtmlTD td, int row, boolean bHasFocus) {
String s = getTableCellRenderer(td, row);
return s;
}
};
addColumn(htmlCol, htmlTh, comp);
}
public boolean getEnabled() {
return htmlComponent.getEnabled();
}
public boolean isEnabled() {
return htmlComponent.getEnabled();
}
public void setEnabled(boolean b) {
htmlComponent.setEnabled(b);
}
@Override
protected void onSubmitBeforeLoadValues(OAFormSubmitEvent formSubmitEvent) {
submitCol = submitRow = -1;
String[] ids = formSubmitEvent.getNameValueMap().get("oacommand");
if (ids != null && ids.length == 1) {
String id = ids[0];
if (id.startsWith(getId()+"_")) {
formSubmitEvent.setSubmitOAHtmlComponent(getOAHtmlComponent());
formSubmitEvent.setSubmitHtmlElement(this);
int dcnt = OAStr.dcount(id, "_");
submitRow = OAConv.toInt(OAStr.field(id, "_", dcnt-1));
submitCol = OAConv.toInt(OAStr.field(id, "_", dcnt));
}
}
submitKeys = null;
String[] params = formSubmitEvent.getNameValueMap().get("oaparam");
if (params != null && params.length == 1) {
submitKeys = params[0];
}
}
@Override
protected void onSubmitAfterLoadValues(OAFormSubmitEvent formSubmitEvent) {
// verify that hubs "have not moved", when using detailHub, linkHub, etc
if (getHub().getRealHub() != lastRefresh.hubUsed) {
if (lastRefresh.objLinkedTo == null) return; // it was not linked, so dont change AO
}
if (lastRefresh.hubUsed != getHub().getRealHub()) {
formSubmitEvent.addSyncError("OAHtmlTable list changed");
}
else {
Hub h = getHub().getLinkHub(true);
if (h != null) {
if (lastRefresh.objLinkedTo != h.getAO()) {
formSubmitEvent.addSyncError("OAHtmlTable link to changed");
}
}
}
}
@Override
protected void onSubmit(OAFormSubmitEvent formSubmitEvent) {
if (formSubmitEvent.getSubmitHtmlElement() != this) return;
if (submitRow >=0 && submitRow >= 0 && OAStr.isNotEmpty(submitKeys)) {
if (submitKeys.indexOf(OAForm.Key_UP) >= 0) submitRow--;
else if (submitKeys.indexOf(OAForm.Key_DOWN) >= 0) submitRow++;
else if (submitKeys.indexOf(OAForm.Key_LEFT) >= 0) submitCol--;
else if (submitKeys.indexOf(OAForm.Key_RIGHT) >= 0) submitCol++;
}
Object obj = getHub().get(submitRow);
oaUiControl.onAOChange(lastRefresh.objLinkedTo, lastRefresh.objSelected, obj);
}
@Override
protected String getInitializeScript() {
// addClass("oatable");
StringBuilder sb = new StringBuilder();
String js = super.getInitializeScript();
if (js != null) sb.append(js);
if (getAjaxSubmit()) {
sb.append("$('#"+getId()+"').on('focus', 'tbody tr td', function(event) {\n");
sb.append(" if ($(this).parent().hasClass('oatableSelected')) return true;\n");
sb.append(" $('#oacommand').val($(this).attr('id'));\n");
sb.append(" ajaxSubmit();\n");
sb.append(" $('#oacommand').val('');\n");
sb.append(" return true;\n");
sb.append("});\n");
}
// add nav keys event
sb.append("$('#" + getId() + "').on('keydown', 'tr td', function(event) {\n");
sb.append(" var ss = $(this).attr('id').split('_');\n");
sb.append(" if (ss == undefined) return true;\n");
sb.append(" var row = ss[ss.length -2];\n");
sb.append(" var col = ss[ss.length -1];\n");
sb.append(" const totalRows = event.delegateTarget.tBodies[0].rows.length;\n");
sb.append(" const totalCols = event.delegateTarget.tBodies[0].rows[0].cells.length;\n");
sb.append(" var keys = '';\n");
sb.append(" if (event.shiftKey) keys += '[SHIFT]';\n");
sb.append(" if (event.ctrlKey) keys += '[CTRL]';\n");
sb.append(" if (event.altKey) keys += '[ALT]';\n");
sb.append(" if (keys.length > 0) {\n");
sb.append(" keys = 'KEYS='+keys;\n");
sb.append(" }\n");
sb.append(" var bEnd = true;\n");
sb.append(" var bBeg = true;\n");
sb.append(" if (event.target.tagName === 'INPUT' && event.target.type === 'text') {\n");
sb.append(" var p1 = event.target.selectionStart;\n");
sb.append(" var p2 = event.target.selectionEnd;\n");
sb.append(" if (p2 < p1) {\n");
sb.append(" var p3 = p1;\n");
sb.append(" p1 = p2;\n");
sb.append(" p2 = p3;\n");
sb.append(" }\n");
sb.append(" bBeg = p1 == 0 && p2 == 0;\n");
sb.append(" bEnd = p2 == event.target.value.length && p1 == p2;\n");
sb.append(" }\n");
sb.append(" switch (event.which) {\n");
sb.append(" case 38: keys += '[UP]';\n");
sb.append(" if (row == 0) return false;\n");
sb.append(" row--;\n");
sb.append(" break\n");
sb.append(" case 33: keys += '[PGUP]';\n");
sb.append(" if (row == 0) return false;\n");
sb.append(" row=0;\n");
sb.append(" break;\n");
sb.append(" case 40: keys += '[DOWN]';\n");
sb.append(" if (row+1 == totalRows) return false;\n");
sb.append(" row++;\n");
sb.append(" break;\n");
sb.append(" case 34: keys += '[PGDN]';\n");
sb.append(" if (row+1 == totalRows) return false;\n");
sb.append(" row = totalRows-1;\n");
sb.append(" break;\n");
sb.append(" case 39: keys += '[RIGHT]';\n");
sb.append(" if (col+1 == totalCols) return true;\n");
sb.append(" if (!bEnd) return true;\n");
sb.append(" col++;\n");
sb.append(" break;\n");
sb.append(" case 35: keys += '[END]';\n");
sb.append(" if (col+1 == totalCols) return true;\n");
sb.append(" if (!bEnd) return true;\n");
sb.append(" col = totalCols-1;\n");
sb.append(" break;\n");
sb.append(" case 36: keys += '[HOME]';\n");
sb.append(" if (col == 0) return false;\n");
sb.append(" if (!bBeg) return true;\n");
sb.append(" col = 0;\n");
sb.append(" break;\n");
sb.append(" case 37: keys += '[LEFT]';\n");
sb.append(" if (col == 0) return false;\n");
sb.append(" if (!bBeg) return true;\n");
sb.append(" col--;\n");
sb.append(" break;\n");
sb.append(" default:\n");
sb.append(" return true;\n");
sb.append(" } \n");
/*
sb.append(" case 27: keys += '[ESC]';break;\n");
sb.append(" case 8: keys += '[BACK]';break;\n");
sb.append(" case 45: keys += '[INS]';break;\n");
sb.append(" case 46: keys += '[DEL]';break;\n");
sb.append(" default: keys += String.fromCharCode(event.which);break;\n");
*/
sb.append(" $('#"+getId()+"_'+row+'_'+col).focus();\n");
sb.append(" return false;\n");
sb.append("});\n");
// need to send before cell editor components are initialized
js = createTableScript();
sb.append(js);
return sb.toString();
}
public Hub getHub() {
return oaUiControl.getHub();
}
public OAUISelectController getController() {
return oaUiControl;
}
@Override
protected void beforeGetScript() {
setVisible(oaUiControl.isVisible());
setEnabled(oaUiControl.isEnabled());
lastRefresh.hubUsed = getHub().getRealHub();
lastRefresh.objSelected = (OAObject) getHub().getAO();
final int pos = getHub().getPos();
Hub h = getHub().getLinkHub(true);
if (h != null) {
lastRefresh.objLinkedTo = (OAObject) h.getAO();
}
else lastRefresh.objLinkedTo = null;
//rebuild the body rows
getTBodyRows().clear();
int row = 0;
for (Object obj : lastRefresh.hubUsed) {
final int r = row++;
HtmlTR tr = new HtmlTR(getId()+"_"+r);
addTBodyRow(tr);
if (r == pos) {
tr.addClass("oatableSelected");
tr.addClass("table-success");
}
int col = 0;
for (Column column : alColumn) {
final int c = col++;
HtmlTD td = new HtmlTD(getId() + "_" + r + "_" + c);
for (String s : column.htmlCol.getClasses()) td.addClass(s);
//qqqqqqqqqqqqqqqqqqqq
//dont use style:overflow for bsDT components qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
//qqqqqqq this is needed by date components popup .....
// needs to be put on div ??
//td.addStyle("position", "relative");
td.setTabIndex(0);
String s;
if (r == pos) {
if (column.comp instanceof HtmlElement) {
((HtmlElement) column.comp).getOAHtmlComponent().setNeedsRefreshed(true); // since it will be removed, and then re-added to the page/dom.
}
s = column.comp.getTableCellEditor(getHub(), td, r, (r == submitRow && c == submitCol));
if (r == submitRow && c == submitCol && (column.comp instanceof HtmlFormElement)) {
((HtmlFormElement) column.comp).setFocus();
}
}
else if (!column.htmlCol.getVisible() || column.htmlCol.getHidden()) s = "";
else s = column.comp.getTableCellRenderer(getHub(), td, r);
if (s == null) s = "";
td.setInnerHtml(s);
tr.addTableData(td);
}
}
if (pos < 0) {
final int r = row;
HtmlTR tr = new HtmlTR(getId()+"_"+r);
tr.addStyle("display", "none");
tr.setHidden(true);
addTBodyRow(tr);
int col = 0;
for (Column column : alColumn) {
final int c = col++;
HtmlTD td = new HtmlTD(getId() + "_" + r + "_" + c);
if (column.comp instanceof HtmlElement) {
((HtmlElement) column.comp).getOAHtmlComponent().setNeedsRefreshed(true); // since it will be removed, and then re-added to the page/dom.
}
String s = column.comp.getTableCellEditor(getHub(), td, r, (r == submitRow && c == submitCol));
if (s == null) s = "";
td.setInnerHtml(s);
tr.addTableData(td);
}
}
super.beforeGetScript();
}
@Override
protected String getAjaxScript(final boolean bIsInitializing) {
if (bIsInitializing) return null; // getInitializeScript() already calls createTableScript
return createTableScript();
}
@Override
protected String createTableScript() {
String width = getWidth();
boolean bHadWidth = OAStr.isNotEmpty(width);
if (!bHadWidth) { // calculate width
int w = 0;
String units = null;
boolean bBadUnits = false;
String regex = "^(\\d+)(.*)";
// Create a Pattern object from the regex
Pattern pattern = Pattern.compile(regex);
// Create a Matcher object to find the number prefix in the input string
for (Column column : alColumn) {
String s = column.htmlCol.getWidth();
if (OAStr.isEmpty(s)) {
bBadUnits = true;
break;
}
Matcher matcher = pattern.matcher(s);
// Check if the regex pattern matches the input string
if (matcher.matches()) {
// Group 1 captures the number prefix, and Group 2 captures the remaining text
s = matcher.group(1);
w += OAConv.toInt(s);
String s2 = matcher.group(2);
if (units == null) units = s2;
else if (!units.equalsIgnoreCase(s2)) {
bBadUnits = true;
break;
}
}
}
if (!bBadUnits && units != null) {
addStyle("width", w+units);
}
}
StringBuilder sb = new StringBuilder();
String js = super.createTableScript();
if (js != null) sb.append(js);
if (submitRow >= 0 && submitCol >= 0) {
sb.append("$('#"+ getId() +"_" + submitRow + "_"+ submitCol + "').focus();\n");
}
submitRow = submitCol = -1;
return sb.toString();
}
private static Set hsSupported = new HashSet(); // lowercase
static {
hsSupported.add("enabled");
}
@Override
public boolean isSupported(String name) {
if (name == null) return false;
return super.isSupported(name) || hsSupported.contains(name.toLowerCase());
}
}