Add option to show text field inline with the rendering; add option to fill out a default value

This commit is contained in:
pietervdvn 2021-07-11 15:44:17 +02:00
parent 29ea0ac925
commit 6e3c39e475
10 changed files with 123 additions and 23 deletions

View file

@ -26,6 +26,8 @@ export default class TagRenderingConfig {
readonly key: string, readonly key: string,
readonly type: string, readonly type: string,
readonly addExtraTags: TagsFilter[]; readonly addExtraTags: TagsFilter[];
readonly inline: boolean,
readonly default?: string
}; };
readonly multiAnswer: boolean; readonly multiAnswer: boolean;
@ -73,6 +75,8 @@ export default class TagRenderingConfig {
type: json.freeform.type ?? "string", type: json.freeform.type ?? "string",
addExtraTags: json.freeform.addExtraTags?.map((tg, i) => addExtraTags: json.freeform.addExtraTags?.map((tg, i) =>
FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [], FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [],
inline: json.freeform.inline ?? false,
default: json.freeform.default
} }

View file

@ -46,7 +46,19 @@ export interface TagRenderingConfigJson {
**/ **/
addExtraTags?: string[]; addExtraTags?: string[];
/**
* When set, influences the way a question is asked.
* Instead of showing a full-widht text field, the text field will be shown within the rendering of the question.
*
* This combines badly with special input elements, as it'll distort the layout.
*/
inline?: boolean
/**
* default value to enter if no previous tagging is present.
* Normally undefined (aka do not enter anything)
*/
default?: string
}, },
/** /**

View file

@ -0,0 +1,35 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {Translation} from "../i18n/Translation";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
export default class InputElementWrapper<T> extends InputElement<T> {
public readonly IsSelected: UIEventSource<boolean>;
private readonly _inputElement: InputElement<T>;
private readonly _renderElement: BaseUIElement
constructor(inputElement: InputElement<T>, translation: Translation, key: string, tags: UIEventSource<any>) {
super()
this._inputElement = inputElement;
this.IsSelected = inputElement.IsSelected
const mapping = new Map<string, BaseUIElement>()
mapping.set(key, inputElement)
this._renderElement = new SubstitutedTranslation(translation, tags, mapping)
}
GetValue(): UIEventSource<T> {
return this._inputElement.GetValue();
}
IsValid(t: T): boolean {
return this._inputElement.IsValid(t);
}
protected InnerConstructElement(): HTMLElement {
return this._renderElement.ConstructElement();
}
}

View file

@ -36,11 +36,11 @@ export class TextField extends InputElement<string> {
this.SetClass("form-text-field") this.SetClass("form-text-field")
let inputEl: HTMLElement let inputEl: HTMLElement
if (options.htmlType === "area") { if (options.htmlType === "area") {
this.SetClass("w-full box-border max-w-full")
const el = document.createElement("textarea") const el = document.createElement("textarea")
el.placeholder = placeholder el.placeholder = placeholder
el.rows = options.textAreaRows el.rows = options.textAreaRows
el.cols = 50 el.cols = 50
el.style.cssText = "max-width: 100%; width: 100%; box-sizing: border-box"
inputEl = el; inputEl = el;
} else { } else {
const el = document.createElement("input") const el = document.createElement("input")

View file

@ -282,7 +282,7 @@ export default class ValidatedTextField {
}) })
) )
unitDropDown.GetValue().setData(unit.defaultDenom) unitDropDown.GetValue().setData(unit.defaultDenom)
unitDropDown.SetStyle("width: min-content") unitDropDown.SetClass("w-min")
input = new CombinedInputElement( input = new CombinedInputElement(
input, input,

View file

@ -24,6 +24,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils";
import BaseUIElement from "../BaseUIElement"; import BaseUIElement from "../BaseUIElement";
import {DropDown} from "../Input/DropDown"; import {DropDown} from "../Input/DropDown";
import {Unit} from "../../Customizations/JSON/Denomination"; import {Unit} from "../../Customizations/JSON/Denomination";
import InputElementWrapper from "../Input/InputElementWrapper";
/** /**
* Shows the question element. * Shows the question element.
@ -128,7 +129,7 @@ export default class TagRenderingQuestion extends Combine {
} }
return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined: m.ifnot)) return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined: m.ifnot))
} }
const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource.data); const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource);
const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0 const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
if (mappings.length < 8 || configuration.multiAnswer || hasImages) { if (mappings.length < 8 || configuration.multiAnswer || hasImages) {
@ -289,7 +290,7 @@ export default class TagRenderingQuestion extends Combine {
(t0, t1) => t1.isEquivalent(t0)); (t0, t1) => t1.isEquivalent(t0));
} }
private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tagsData: any): InputElement<TagsFilter> { private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource<any>): InputElement<TagsFilter> {
const freeform = configuration.freeform; const freeform = configuration.freeform;
if (freeform === undefined) { if (freeform === undefined) {
return undefined; return undefined;
@ -328,7 +329,8 @@ export default class TagRenderingQuestion extends Combine {
return undefined; return undefined;
} }
let input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, { const tagsData = tags.data;
const input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, {
isValid: (str) => (str.length <= 255), isValid: (str) => (str.length <= 255),
country: () => tagsData._country, country: () => tagsData._country,
location: [tagsData._lat, tagsData._lon], location: [tagsData._lat, tagsData._lon],
@ -336,12 +338,22 @@ export default class TagRenderingQuestion extends Combine {
unit: applicableUnit unit: applicableUnit
}); });
input.GetValue().setData(tagsData[configuration.freeform.key]); input.GetValue().setData(tagsData[freeform.key] ?? freeform.default);
return new InputElementMap( let inputTagsFilter : InputElement<TagsFilter> = new InputElementMap(
input, (a, b) => a === b || (a?.isEquivalent(b) ?? false), input, (a, b) => a === b || (a?.isEquivalent(b) ?? false),
pickString, toString pickString, toString
); );
if(freeform.inline){
inputTagsFilter.SetClass("w-16-imp")
inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags)
inputTagsFilter.SetClass("block")
}
return inputTagsFilter;
} }

View file

@ -379,6 +379,7 @@ export default class SpecialVisualizations {
} }
] ]
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
private static GenHelpMessage() { private static GenHelpMessage() {

View file

@ -7,19 +7,43 @@ import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizatio
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import {VariableUiElement} from "./Base/VariableUIElement"; import {VariableUiElement} from "./Base/VariableUIElement";
import Combine from "./Base/Combine"; import Combine from "./Base/Combine";
import BaseUIElement from "./BaseUIElement";
export class SubstitutedTranslation extends VariableUiElement { export class SubstitutedTranslation extends VariableUiElement {
public constructor( public constructor(
translation: Translation, translation: Translation,
tagsSource: UIEventSource<any>) { tagsSource: UIEventSource<any>,
mapping: Map<string, BaseUIElement> = undefined) {
const extraMappings: SpecialVisualization[] = [];
mapping?.forEach((value, key) => {
console.log("KV:", key, value)
extraMappings.push(
{
funcName: key,
constr: (() => {
return value
}),
docs: "Dynamically injected input element",
args: [],
example: ""
}
)
})
super( super(
Locale.language.map(language => { Locale.language.map(language => {
const txt = translation.textFor(language) let txt = translation.textFor(language);
if (txt === undefined) { if (txt === undefined) {
return undefined return undefined
} }
return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map( mapping?.forEach((_, key) => {
txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`)
})
return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map(
proto => { proto => {
if (proto.fixed !== undefined) { if (proto.fixed !== undefined) {
return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags))); return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
@ -36,30 +60,35 @@ export class SubstitutedTranslation extends VariableUiElement {
}) })
) )
this.SetClass("w-full") this.SetClass("w-full")
} }
public static ExtractSpecialComponents(template: string): { public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): {
fixed?: string, special?: { fixed?: string,
special?: {
func: SpecialVisualization, func: SpecialVisualization,
args: string[], args: string[],
style: string style: string
} }
}[] { }[] {
for (const knownSpecial of SpecialVisualizations.specialVisualizations) { if (extraMappings.length > 0) {
console.log("Extra mappings are", extraMappings)
}
for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) {
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`); const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`);
if (matched != null) { if (matched != null) {
// We found a special component that should be brought to live // We found a special component that should be brought to live
const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1]); const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1], extraMappings);
const argument = matched[2].trim(); const argument = matched[2].trim();
const style = matched[3]?.substring(1) ?? "" const style = matched[3]?.substring(1) ?? ""
const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]); const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings);
const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
if (argument.length > 0) { if (argument.length > 0) {
const realArgs = argument.split(",").map(str => str.trim()); const realArgs = argument.split(",").map(str => str.trim());
@ -73,11 +102,13 @@ export class SubstitutedTranslation extends VariableUiElement {
} }
let element; let element;
element = {special:{ element = {
args: args, special: {
style: style, args: args,
func: knownSpecial style: style,
}} func: knownSpecial
}
}
return [...partBefore, element, ...partAfter] return [...partBefore, element, ...partAfter]
} }
} }

View file

@ -137,7 +137,8 @@
}, },
"freeform": { "freeform": {
"key": "capacity", "key": "capacity",
"type": "nat" "type": "nat",
"inline": true
} }
}, },
{ {

View file

@ -105,6 +105,10 @@ a {
width: min-content; width: min-content;
} }
.w-16-imp {
width: 4rem !important;
}
.space-between{ .space-between{
justify-content: space-between; justify-content: space-between;
} }