Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[basicui] Use sitemap input hint v2 #1923

Merged
merged 25 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
31bdda4
number, date and time input using hints
mherwege Feb 23, 2023
9d31349
fix rebase issue
mherwege Apr 10, 2023
c6acf99
Rebase mistake fixes
mherwege Apr 10, 2023
4f96a58
review adjustments, force datetime inputHint for dateTime type
mherwege May 7, 2023
bfd2d3c
cut seconds
mherwege May 8, 2023
356ac08
fix datetime from UNDEF handling
mherwege May 8, 2023
85a7fe2
fix js error setting date to UNDEF
mherwege May 8, 2023
0124c95
fix undefValue when no state description
mherwege May 8, 2023
a943a3a
fix undef colors
mherwege May 8, 2023
2239e92
correct undef color at start
mherwege May 8, 2023
b2a9883
fix build
mherwege May 8, 2023
b32effc
warnings and unit fix
mherwege May 9, 2023
bb0a33b
fix build error
mherwege May 9, 2023
31ec5d2
default interpret single comma in number string as decimal separator
mherwege May 9, 2023
6e91bfe
improvements
mherwege May 22, 2023
8934eab
fix build error
mherwege May 22, 2023
55fd19b
support extended state descriptions
mherwege May 23, 2023
df5cb4e
postfix identification improvement
mherwege May 23, 2023
9467e84
enter data in field active color
mherwege May 24, 2023
41bc388
support scientific notation for numbers
mherwege May 25, 2023
5bea81a
fix single character units
mherwege May 25, 2023
3d6aa48
rework on refactored ItemUIRegistry
mherwege May 31, 2023
b363e09
review feedback
mherwege Jul 2, 2023
97e042d
parseNumber (JavaScript): avoid adding a trailing space if no unit
lolodomo Jul 11, 2023
35f777c
setValuePrivate (JS): update lastItemState only if itemState paramete…
lolodomo Jul 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@
*/
package org.openhab.ui.basic.internal.render;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.model.sitemap.sitemap.Input;
import org.openhab.core.model.sitemap.sitemap.Widget;
import org.openhab.core.types.State;
Expand All @@ -43,6 +52,11 @@
@NonNullByDefault
public class InputRenderer extends AbstractWidgetRenderer {

private static final Pattern NUMBER_PATTERN = Pattern.compile("^(\\+|-)?[0-9\\.,]+((e|E)(\\+|-)?[0-9]+)?");
private static final Pattern COMMA_SEPARATOR_PATTERN = Pattern
.compile("^(\\+|-)?(([1-9][0-9]{0,2}(\\.[0-9]{3})*)|([0-9]*))?(,[0-9]+)?((e|E)(\\+|-)?[0-9]+)?$");
private static final Pattern FORMAT_PATTERN = Pattern.compile("^([^%]*)( ?%\\S+)+( [^%]*)?");

private final Logger logger = LoggerFactory.getLogger(InputRenderer.class);

@Activate
Expand All @@ -58,38 +72,225 @@ public boolean canRender(Widget w) {

@Override
public EList<Widget> renderWidget(Widget w, StringBuilder sb, String sitemap) throws RenderException {
String snippet = getSnippet("input");
Input input = (Input) w;

String snippet = getSnippet("input");
snippet = preprocessSnippet(snippet, w);

String dataState = getValue(w);
State state = itemUIRegistry.getState(w);
if (state == null || state instanceof UnDefType) {
snippet = snippet.replace("%undef_state%", dataState);
snippet = snippet.replace("%data_state%", "");
} else {
snippet = snippet.replace("%undef_state%", "");
snippet = snippet.replace("%data_state%", dataState);
}

Item item = null;
try {
item = itemUIRegistry.getItem(w.getItem());
} catch (ItemNotFoundException e) {
logger.debug("Failed to retrieve item during widget rendering: {}", e.getMessage());
return ECollections.emptyEList();
}

String dataType;
if (item != null && item.getAcceptedDataTypes().stream().anyMatch(o -> Number.class.isAssignableFrom(o))) {
dataType = "Number";
if (item.getAcceptedCommandTypes().stream()
.anyMatch(o -> (!StringType.class.isAssignableFrom(o) && Number.class.isAssignableFrom(o)))) {
dataType = "number";
} else if (item.getAcceptedCommandTypes().stream()
.anyMatch(o -> (!StringType.class.isAssignableFrom(o) && DateTimeType.class.isAssignableFrom(o)))) {
dataType = "datetime";
} else {
dataType = "Text";
dataType = "text";
}
snippet = snippet.replace("%data_type%", dataType);

String inputHint = input.getInputHint();
List<Class<? extends State>> dataTypes = item.getAcceptedDataTypes();
if (("number".equals(inputHint)
&& !(dataTypes.contains(DecimalType.class) || dataTypes.contains(PercentType.class)))
|| (("date".equals(inputHint) || "time".equals(inputHint) || "datetime".equals(inputHint))
&& !dataTypes.contains(DateTimeType.class))) {
logger.warn("Invalid inputHint {} for item {} of type {}, set to default", inputHint, item.getName(),
item.getType());
inputHint = null;
}
if ("datetime".equals(dataType) && ((inputHint == null) || inputHint.isEmpty() || "text".equals(inputHint))) {
inputHint = "datetime";
}
snippet = snippet.replace("%input_hint%", inputHint == null ? "" : inputHint);

String inputType = "text";
String inputPattern = "";
if ("number".equals(inputHint)) {
inputType = "number";
inputPattern = "pattern=\"[0-9]*([\\.|,][0-9]*)?\"";
} else if ("date".equals(inputHint)) {
inputType = "date";
inputPattern = "pattern=\"[0-9]{4}-[0-9]d{2}-[0-9]d{2}\"";
} else if ("time".equals(inputHint)) {
inputType = "time";
inputPattern = "pattern=\"[0-9]{2}:[0-9]{2}?\"";
} else if ("datetime".equals(inputHint)) {
inputType = "datetime-local";
inputPattern = "pattern=\"[0-9]{4}-[0-9]d{2}-[0-9]d{2} [0-9]{2}:[0-9]{2}\"";
}
snippet = snippet.replace("%input_type%", inputType);
snippet = snippet.replace("%input_pattern%", inputPattern);

String displayState = getValue(w, item);
State state = itemUIRegistry.getState(w);

String prefix = getPrefix(w);
boolean hasUnit = item instanceof NumberItem ? (((NumberItem) item).getDimension() != null) : false;
String postfix = hasUnit ? "" : getPostfix(w);
String prefixSnippet = !prefix.isBlank()
? "<span %valuestyle% class=\"mdl-form__input-prefix\">" + prefix + "</span>"
: "";
String postfixSnippet = !postfix.isBlank()
? "<span %valuestyle% class=\"mdl-form__input-postfix\">" + postfix + "</span>"
: "";
snippet = snippet.replace("%prefix_snippet%", prefixSnippet);
snippet = snippet.replace("%postfix_snippet%", postfixSnippet);

String undefState = "";
if (state == null || state instanceof UnDefType) {
if ("number".equals(inputHint)) {
String[] stateArray = displayState.split(" ");
undefState = stateArray.length > 0 ? stateArray[0] : undefState;
} else if (!("date".equals(inputHint) || "time".equals(inputHint) || "datetime".equals(inputHint))) {
undefState = displayState;
}
}
snippet = snippet.replace("%undef_state%", undefState);

String dataState = "";
String itemState = "";
if ((state == null) || (state instanceof UnDefType)) {
itemState = "";
} else {
itemState = state.toString();
dataState = displayState;
if ("number".equals(inputHint)) {
String[] stateArray = dataState.trim().split(" ");
dataState = parseNumber(stateArray[0]);
} else if (state instanceof DateTimeType) {
if ("date".equals(inputHint)) {
dataState = ((DateTimeType) state).format("%1$tY-%1$tm-%1$td");
} else if ("time".equals(inputHint)) {
dataState = ((DateTimeType) state).format("%1$tR");
} else if ("datetime".equals(inputHint)) {
dataState = ((DateTimeType) state).format("%1$tY-%1$tm-%1$tdT%1$tR");
}
}
}
snippet = snippet.replace("%data_state%", dataState);
snippet = snippet.replace("%item_state%", itemState);

String unitSnippet = "";
String unit = "";
if (item instanceof NumberItem) {
NumberItem numberItem = (NumberItem) item;
if (numberItem.getDimension() != null) {
unit = getUnit(w, numberItem);
if ("number".equals(inputHint)) {
unitSnippet = "<span %valuestyle% class=\"mdl-form__input-unit\">" + unit + "</span>";
}
}
}
snippet = snippet.replace("%item_unit%", unit);
snippet = snippet.replace("%unit_snippet%", unitSnippet);

// Process the color tags
snippet = processColor(w, snippet);

sb.append(snippet);

return ECollections.emptyEList();
}

private String parseNumber(String value) {
String newValue = value.trim();
Matcher numberMatcher = NUMBER_PATTERN.matcher(newValue);
if (numberMatcher.find()) {
String numberValue = numberMatcher.group(0);
String unitValue = newValue.substring(numberValue.length()).trim();
newValue = numberValue.replace("/^\\+/", "");
if (COMMA_SEPARATOR_PATTERN.matcher(newValue).find()) {
newValue = newValue.replace("/\\./g", "").replace(",", ".");
}
if (unitValue.length() > 0) {
newValue = newValue + " " + unitValue;
}
return newValue;
} else {
return value;
}
}

private String getValue(Widget w, Item item) {
String value = cleanValue(getValue(w), w, item);
if (value.isBlank()) {
State state = itemUIRegistry.getState(w);
if (state != null && !(state instanceof UnDefType)) {
value = state.toString();
} else {
value = "-";
}
}
if (item instanceof NumberItem) {
NumberItem numberItem = (NumberItem) item;
if (numberItem.getDimension() != null) {
String[] stateArray = value.split(" ");
if (stateArray.length <= 1) {
String unit = getUnit(w, numberItem);
value = (stateArray.length > 0 ? stateArray[0] : value) + (!unit.isBlank() ? " " + unit : "");
}
}
}
return value;
}

private String cleanValue(String value, Widget w, Item item) {
String prefix = getPrefix(w);
boolean hasUnit = item instanceof NumberItem ? (((NumberItem) item).getDimension() != null) : false;
String postfix = hasUnit ? "" : getPostfix(w);
String newValue = value.startsWith(prefix) ? value.substring(prefix.length()) : value;
newValue = value.endsWith(postfix) ? newValue.substring(0, newValue.lastIndexOf(postfix)) : newValue;
return newValue.trim();
}

private String getUnit(Widget w, NumberItem numberItem) {
String unit;
unit = itemUIRegistry.getUnitForWidget(w);
if (unit == null) {
unit = numberItem.getUnitSymbol();
}
unit = (unit == null) ? "" : unit;
return unit;
}

private String getPrefix(Widget w) {
String pattern = itemUIRegistry.getFormatPattern(w);
if (pattern == null) {
return "";
}

Matcher m = FORMAT_PATTERN.matcher(pattern);
if (m.matches()) {
String prefix = m.group(1);
if (prefix != null && !prefix.isBlank()) {
return prefix;
}
}
return "";
}

private String getPostfix(Widget w) {
String pattern = itemUIRegistry.getFormatPattern(w);
if (pattern == null) {
return "";
}

Matcher m = FORMAT_PATTERN.matcher(pattern);
if (m.matches()) {
String postfix = m.group(3);
if (postfix != null && !postfix.isBlank()) {
return postfix;
}
}
return "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,33 @@
%label%
</span>
<div
class="mdl-form__control mdl-form__input mdl-textfield mdl-js-textfield"
data-control-type="input"
data-item="%item%"
data-widget-id="%widget_id%"
data-item-type=%data_type%
for="oh-input-%item%"
class="mdl-form__input"
>
<input
%valuestyle% type="text"
id="oh-input-%item%"
class="mdl-textfield__input"
value="%data_state%"
/>
<label for="oh-input-%item%" class="mdl-textfield__label">
%undef_state%
</label>
%prefix_snippet%
<div
class="mdl-form__control mdl-form__input-container mdl-textfield mdl-js-textfield"
data-control-type="input"
data-item="%item%"
data-widget-id="%widget_id%"
data-item-type="%data_type%"
data-input-hint="%input_hint%"
data-item-state="%item_state%"
data-item-unit="%item_unit%"
for="oh-input-%item%"
>
<input
%valuestyle% type="%input_type%" %input_pattern%
autocomplete="off"
step="any"
id="oh-input-%item%"
class="mdl-textfield__input"
value="%data_state%"
/>
<label for="oh-input-%item%" class="mdl-textfield__label">
%undef_state%
</label>
</div>
%unit_snippet%
%postfix_snippet%
</div>
</div>
Loading