From 222d8e5383d1ebaf8fd30b4ec4f9ca5771d29244 Mon Sep 17 00:00:00 2001 From: Shreyas Govinda Raju Date: Thu, 11 Mar 2021 01:27:01 -0800 Subject: [PATCH] feat: Introduced a new optional prop in apl `renderComponent` to customize the APL component returned by a control refactor: Changed the renderAPLComponent API signature. feat: Created new classes on control APL built-ins namespace for few default implementations of APL component mode. refactor: Removed the renderStyle concept in favour of above props and built-ins provided. --- demo/ComponentModeAPL/src/index.ts | 26 +- src/commonControls/listControl/ListControl.ts | 52 ++-- .../listControl/ListControlAPL.ts | 120 +++++++-- .../MultiValueListControl.ts | 52 ++-- .../MultiValueListControlAPL.ts | 177 ++++++++++-- .../numberControl/NumberControl.ts | 61 +++-- .../numberControl/NumberControlAPL.ts | 254 ++++++++++-------- src/controls/Control.ts | 23 +- 8 files changed, 516 insertions(+), 249 deletions(-) diff --git a/demo/ComponentModeAPL/src/index.ts b/demo/ComponentModeAPL/src/index.ts index e9c76c2..8ea546a 100644 --- a/demo/ComponentModeAPL/src/index.ts +++ b/demo/ComponentModeAPL/src/index.ts @@ -1,6 +1,7 @@ import { SkillBuilders } from 'ask-sdk-core'; import { ControlInput, ControlResponseBuilder, ControlResult, NumberControl } from '../../../src'; import { MultiValueListControl } from '../../../src/commonControls/multiValueListControl/MultiValueListControl'; +import { MultiValueListControlComponentAPLBuiltIns } from '../../../src/commonControls/multiValueListControl/MultiValueListControlAPL'; import { ComponentModeControlManager } from '../../../src/controls/ComponentModeControlManager'; import { Control } from '../../../src/controls/Control'; import { ControlHandler } from '../../../src/runtime/ControlHandler'; @@ -60,6 +61,9 @@ export namespace ComponentModeDemo { interactionModel: { targets: ['builtin_choice', 'builtin_it', 'theme'], }, + apl: { + renderComponent: MultiValueListControlComponentAPLBuiltIns.CheckBoxRenderer.default, + }, })), ); @@ -136,13 +140,7 @@ export namespace ComponentModeDemo { left: '50px', width: '200px', height: '100px', - items: [ - this.ageControl.renderAPLComponent( - { renderStyle: 'modalKeypad' }, - input, - controlResponseBuilder, - ), - ], + items: [this.ageControl.renderAPLComponent(input, controlResponseBuilder)], }, { id: 'label2', @@ -164,13 +162,7 @@ export namespace ComponentModeDemo { left: '50px', width: '200px', height: '100px', - items: [ - this.guestsControl.renderAPLComponent( - { renderStyle: 'modalKeypad' }, - input, - controlResponseBuilder, - ), - ], + items: [this.guestsControl.renderAPLComponent(input, controlResponseBuilder)], }, { id: 'label3', @@ -193,11 +185,7 @@ export namespace ComponentModeDemo { width: '700px', height: '360px', items: [ - this.partyThemeControl.renderAPLComponent( - { renderStyle: 'aggregateDuplicates' }, - input, - controlResponseBuilder, - ), + this.partyThemeControl.renderAPLComponent(input, controlResponseBuilder), ], }, diff --git a/src/commonControls/listControl/ListControl.ts b/src/commonControls/listControl/ListControl.ts index bed13aa..47994d3 100644 --- a/src/commonControls/listControl/ListControl.ts +++ b/src/commonControls/listControl/ListControl.ts @@ -17,7 +17,6 @@ import _ from 'lodash'; import { ModelData } from '../..'; import { Strings as $ } from '../../constants/Strings'; import { - APLComponentProps, Control, ControlInputHandler, ControlInputHandlingProps, @@ -62,7 +61,7 @@ import { DeepRequired } from '../../utils/DeepRequired'; import { InputUtil } from '../../utils/InputUtil'; import { defaultIntentToValueMapper } from '../../utils/IntentUtils'; import { falseIfGuardFailed, okIf, StateConsistencyError } from '../../utils/Predicates'; -import { ListControlAPLPropsBuiltIns, ListControlComponentAPLBuiltIns, ListStyles } from './ListControlAPL'; +import { ListControlAPLPropsBuiltIns, ListControlComponentAPLBuiltIns } from './ListControlAPL'; // TODO: feature: support "what are my choices" // TODO: feature: voice pagination of choices. @@ -369,7 +368,12 @@ export class ListControlPromptProps { export type AplContent = { document: { [key: string]: any }; dataSource: { [key: string]: any } }; export type AplContentFunc = (control: ListControl, input: ControlInput) => AplContent; export type AplDocumentPropNewStyle = AplContent | AplContentFunc; - +export type AplRenderComponentFunc = ( + control: ListControl, + props: ListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, +) => { [key: string]: any }; /** * Props associated with the APL produced by ListControl. */ @@ -386,6 +390,23 @@ export class ListControlAPLProps { */ requestValue?: AplDocumentPropNewStyle; requestChangedValue?: AplDocumentPropNewStyle; + + /** + * Determines the APL Component rendering mode. + * + * Usage: + * + * 1) Use pre-defined built-ins under ListControlComponentAPLBuiltIns.* namespace which provides both default + * implementations and customization of props(ListAPLComponentProps) to render an APL component. + * + * e.g renderComponent: ListControlComponentAPLBuiltIns.ImageListRenderer.default --- Default Implementation + * renderComponent: ListControlComponentAPLBuiltIns.ImageListRenderer.of(props: ListAPLComponentProps) --- Override few properties + * + * 2) Provide a custom function which returns an APL component. + * + * Default: ListControlComponentAPLBuiltIns.TextListRenderer.default + */ + renderComponent?: AplRenderComponentFunc; } /** @@ -401,12 +422,7 @@ interface LastInitiativeState { /** * Props to customize ListControl APLComponent rendering. */ -export interface ListAPLComponentProps extends APLComponentProps { - /** - * Defines the render style of APL component produced by the control. - */ - renderStyle: ListStyles; - +export interface ListAPLComponentProps { /** * Boolean to determine to highlight user selected choice from the * list of items. @@ -633,6 +649,7 @@ export class ListControl extends Control implements InteractionModelContributor requestChangedValue: ListControlAPLPropsBuiltIns.defaultSelectValueAPLContent({ valueRenderer: props.valueRenderer ?? ListControl.defaultValueRenderer(), }), + renderComponent: ListControlComponentAPLBuiltIns.TextListRenderer.default, }, }; @@ -1303,17 +1320,12 @@ export class ListControl extends Control implements InteractionModelContributor } } - renderAPLComponent( - props: ListAPLComponentProps, - input: ControlInput, - resultBuilder: ControlResponseBuilder, - ): { [key: string]: any } { - return ListControlComponentAPLBuiltIns.renderComponent( - this, - { ...props, valueRenderer: this.props.valueRenderer }, - input, - resultBuilder, - ); + renderAPLComponent(input: ControlInput, resultBuilder: ControlResponseBuilder): { [key: string]: any } { + const aplRenderFunc = this.props.apl.renderComponent; + const defaultProps: ListAPLComponentProps = { + valueRenderer: this.props.valueRenderer, + }; + return aplRenderFunc.call(this, this, defaultProps, input, resultBuilder); } private evaluateAPLPropNewStyle(prop: AplDocumentPropNewStyle, input: ControlInput): AplContent { diff --git a/src/commonControls/listControl/ListControlAPL.ts b/src/commonControls/listControl/ListControlAPL.ts index 640feea..3a09501 100644 --- a/src/commonControls/listControl/ListControlAPL.ts +++ b/src/commonControls/listControl/ListControlAPL.ts @@ -1,4 +1,5 @@ import i18next from 'i18next'; +import _ from 'lodash'; import { ControlResponseBuilder } from '../..'; import { ControlInput } from '../../controls/ControlInput'; import { AplContent, ListAPLComponentProps, ListControl, ListControlRenderedItem } from './ListControl'; @@ -117,24 +118,18 @@ export namespace ListControlAPLPropsBuiltIns { } } -export type ListStyles = 'textList' | 'imageList'; - +/** + * Namespace which define built-in renderers for APL Component Mode. + */ export namespace ListControlComponentAPLBuiltIns { - export function renderComponent( - control: ListControl, - props: ListAPLComponentProps, - input: ControlInput, - resultBuilder: ControlResponseBuilder, - ) { - if (props.renderStyle === 'textList') { - return renderTextList(control, props, input, resultBuilder); - } else if (props.renderStyle === 'imageList') { - return renderImageList(control, props, input, resultBuilder); - } else { - throw new Error('Invalid render style'); - } - } - + /** + * Function which returns an APL component using ImageListLayout. + * + * @param control - ListControl + * @param props - Control context props e.g valueRenderer + * @param input - Input + * @param resultBuilder - Result builder + */ export function renderImageList( control: ListControl, props: ListAPLComponentProps, @@ -288,6 +283,14 @@ export namespace ListControlComponentAPLBuiltIns { }; } + /** + * Function which returns an APL component using TextListLayout. + * + * @param control - ListControl + * @param props - Control context props e.g valueRenderer + * @param input - Input + * @param resultBuilder - Result builder + */ export function renderTextList( control: ListControl, props: ListAPLComponentProps, @@ -365,4 +368,87 @@ export namespace ListControlComponentAPLBuiltIns { listItems, }; } + + /** + * Defines TextListRenderer for APLComponentMode. + */ + export class TextListRenderer { + /** + * Provides a default implementation of textList with default props. + * + * @param control - ListControl + * @param defaultProps - props + * @param input - Input + * @param resultBuilder - Result builder + */ + static default = ( + control: ListControl, + defaultProps: ListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => renderTextList(control, defaultProps, input, resultBuilder); + + /** + * Provides customization over `renderTextList()` arguments where the input + * props overrides the defaults. + * + * @param props - props + */ + static of(props: ListAPLComponentProps) { + return ( + control: ListControl, + defaultProps: ListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => { + // Merges the user-provided props with the default props. + // Any property defined by the user-provided data overrides the defaults. + const mergedProps = _.merge(defaultProps, props); + return renderTextList(control, mergedProps, input, resultBuilder); + }; + } + } + + /** + * Defines ImageListRenderer for APLComponentMode. + */ + export class ImageListRenderer { + /** + * Provides a default implementation of imageList with default props. + * + * @param control - ListControl + * @param defaultProps - props + * @param input - Input + * @param resultBuilder - Result builder + */ + static default = ( + control: ListControl, + defaultProps: ListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => renderImageList(control, { ...defaultProps, highlightSelected: true }, input, resultBuilder); + + /** + * Provides customization over `renderImageList()` arguments where the input + * props overrides the defaults. + * + * @param props - props + */ + static of(props: ListAPLComponentProps) { + return ( + control: ListControl, + defaultProps: ListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => { + // Assign defaults to props. + defaultProps.highlightSelected = true; + + // Merges the user-provided props with the default props. + // Any property defined by the user-provided data overrides the defaults. + const mergedProps = _.merge(defaultProps, props); + return renderImageList(control, mergedProps, input, resultBuilder); + }; + } + } } diff --git a/src/commonControls/multiValueListControl/MultiValueListControl.ts b/src/commonControls/multiValueListControl/MultiValueListControl.ts index 8ff51af..0c55c26 100644 --- a/src/commonControls/multiValueListControl/MultiValueListControl.ts +++ b/src/commonControls/multiValueListControl/MultiValueListControl.ts @@ -19,7 +19,6 @@ import _ from 'lodash'; import { ModelData, StringOrList } from '../..'; import { Strings as $ } from '../../constants/Strings'; import { - APLComponentProps, Control, ControlInitiativeHandler, ControlInputHandler, @@ -64,9 +63,7 @@ import { falseIfGuardFailed, okIf } from '../../utils/Predicates'; import { MultiValueListControlAPLPropsBuiltIns, MultiValueListControlComponentAPLBuiltIns, - MultiValueListStyles, } from './MultiValueListControlAPL'; - const log = new Logger('AskSdkControls:MultiValueListControl'); export type MultiValueValidationFailure = { @@ -194,6 +191,12 @@ export type SlotValidationFunction = ( export type AplContent = { document: { [key: string]: any }; dataSource: { [key: string]: any } }; export type AplContentFunc = (control: MultiValueListControl, input: ControlInput) => AplContent; export type AplDocumentPropNewStyle = AplContent | AplContentFunc; +export type AplRenderComponentFunc = ( + control: MultiValueListControl, + props: MultiValueListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, +) => { [key: string]: any }; /** * Mapping of action slot values to the capability that this control supports. @@ -381,6 +384,24 @@ export class MultiValueListControlAPLProps { * Custom APL to request value from list of choices. */ requestValue?: AplDocumentPropNewStyle; + + /** + * Determines the APL Component rendering mode. + * + * Usage: + * + * 1) Use pre-defined built-ins under MultiValueListControlComponentAPLBuiltIns.* namespace which provides both default + * implementations and customization of props(MultiValueListAPLComponentProps) to render an APL component. + * + * e.g renderComponent: MultiValueListControlComponentAPLBuiltIns.DualTextListRender.default --- Default Implementation + * renderComponent: MultiValueListControlComponentAPLBuiltIns.DualTextListRender.default.of(props: MultiValueListAPLComponentProps) --- Override few properties + * + * 2) Provide a custom function which returns an APL component. + * + * + * Default: MultiValueListControlComponentAPLBuiltIns.DualTextListRender.default + */ + renderComponent?: AplRenderComponentFunc; } export type MultiValueListStateValue = { @@ -410,12 +431,7 @@ interface LastInitiativeState { valueIds?: string[]; } -export interface MultiValueListAPLComponentProps extends APLComponentProps { - /** - * Defines the render style of APL component produced by the control. - */ - renderStyle: MultiValueListStyles; - +export interface MultiValueListAPLComponentProps { /** * Function that maps the MultiValueListControlState.value to rendered value that * will be presented to the user as a list. @@ -664,6 +680,7 @@ export class MultiValueListControl extends Control implements InteractionModelCo requestValue: MultiValueListControlAPLPropsBuiltIns.defaultSelectValueAPLContent({ valueRenderer: (choice, input) => choice, // TODO: Pass the valueRenderer prop }), + renderComponent: MultiValueListControlComponentAPLBuiltIns.DualTextListRender.default, }, inputHandling: { customHandlingFuncs: [], @@ -1370,17 +1387,12 @@ export class MultiValueListControl extends Control implements InteractionModelCo } } - renderAPLComponent( - props: MultiValueListAPLComponentProps, - input: ControlInput, - resultBuilder: ControlResponseBuilder, - ): { [key: string]: any } { - return MultiValueListControlComponentAPLBuiltIns.renderComponent( - this, - { ...props, valueRenderer: this.props.valueRenderer }, - input, - resultBuilder, - ); + renderAPLComponent(input: ControlInput, resultBuilder: ControlResponseBuilder): { [key: string]: any } { + const aplRenderFunc = this.props.apl.renderComponent; + const defaultProps: MultiValueListAPLComponentProps = { + valueRenderer: this.props.valueRenderer, + }; + return aplRenderFunc.call(this, this, defaultProps, input, resultBuilder); } // tsDoc - see Control diff --git a/src/commonControls/multiValueListControl/MultiValueListControlAPL.ts b/src/commonControls/multiValueListControl/MultiValueListControlAPL.ts index 665677b..1588d1f 100644 --- a/src/commonControls/multiValueListControl/MultiValueListControlAPL.ts +++ b/src/commonControls/multiValueListControl/MultiValueListControlAPL.ts @@ -1,4 +1,5 @@ import i18next from 'i18next'; +import _ from 'lodash'; import { ControlResponseBuilder } from '../..'; import { ControlInput } from '../../controls/ControlInput'; import { assert } from '../../utils/AssertionUtils'; @@ -383,26 +384,18 @@ export namespace MultiValueListControlAPLPropsBuiltIns { } } -export type MultiValueListStyles = 'checkBoxes' | 'dualTextList' | 'aggregateDuplicates'; - +/** + * Namespace which define built-in renderers for APL Component Mode. + */ export namespace MultiValueListControlComponentAPLBuiltIns { - export function renderComponent( - control: MultiValueListControl, - props: MultiValueListAPLComponentProps, - input: ControlInput, - resultBuilder: ControlResponseBuilder, - ) { - if (props.renderStyle === 'checkBoxes') { - return renderCheckBoxComponent(control, props, input, resultBuilder); - } else if (props.renderStyle === 'dualTextList') { - return dualTextListComponent(control, props, input, resultBuilder); - } else if (props.renderStyle === 'aggregateDuplicates') { - return aggregateDuplicatesComponent(control, props, input, resultBuilder); - } else { - throw Error('Invalid renderStyle'); - } - } - + /** + * Function which returns an APL component using AlexaCheckbox Layout. + * + * @param control - MultiValueListControl + * @param props - Control context props e.g valueRenderer + * @param input - Input + * @param resultBuilder - Result builder + */ export function renderCheckBoxComponent( control: MultiValueListControl, props: MultiValueListAPLComponentProps, @@ -536,6 +529,14 @@ export namespace MultiValueListControlComponentAPLBuiltIns { }; } + /** + * Function which returns an APL component using dualTextList Layout. + * + * @param control - MultiValueListControl + * @param props - Control context props e.g valueRenderer + * @param input - Input + * @param resultBuilder - Result builder + */ export function dualTextListComponent( control: MultiValueListControl, props: MultiValueListAPLComponentProps, @@ -717,6 +718,15 @@ export namespace MultiValueListControlComponentAPLBuiltIns { }; } + /** + * Function which returns an APL component using AlexaIconButton Layout + * providing increment/decrement buttons for listItems. + * + * @param control - MultiValueListControl + * @param props - Control context props e.g valueRenderer + * @param input - Input + * @param resultBuilder - Result builder + */ export function aggregateDuplicatesComponent( control: MultiValueListControl, props: MultiValueListAPLComponentProps, @@ -878,4 +888,133 @@ export namespace MultiValueListControlComponentAPLBuiltIns { listItems, }; } + + /** + * Defines DualTextListRenderer for APLComponentMode. + */ + export class DualTextListRender { + /** + * Provides a default implementation of dualTextList with default props. + * + * @param control - ListControl + * @param defaultProps - props + * @param input - Input + * @param resultBuilder - Result builder + */ + static default = ( + control: MultiValueListControl, + defaultProps: MultiValueListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => dualTextListComponent(control, defaultProps, input, resultBuilder); + + /** + * Provides customization over `dualTextListComponent()` arguments where the input + * props overrides the defaults. + * + * @param props - props + */ + static of(props: MultiValueListAPLComponentProps) { + return ( + control: MultiValueListControl, + defaultProps: MultiValueListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => { + // Merges the user-provided props with the default props. + // Any property defined by the user-provided data overrides the defaults. + const mergedProps = _.merge(defaultProps, props); + return dualTextListComponent(control, mergedProps, input, resultBuilder); + }; + } + } + + /** + * Defines CheckBoxRenderer for APLComponentMode. + * + * Provides methods to render APL components using AlexaCheckbox layouts. + */ + export class CheckBoxRenderer { + /** + * Provides a default implementation of CheckBox-textList with default props. + * + * @param control - ListControl + * @param defaultProps - props + * @param input - Input + * @param resultBuilder - Result builder + */ + static default = ( + control: MultiValueListControl, + defaultProps: MultiValueListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => renderCheckBoxComponent(control, defaultProps, input, resultBuilder); + + /** + * Provides customization over `renderCheckBoxComponent()` arguments where the input + * props overrides the defaults. + * + * @param props - props + */ + static of(props: MultiValueListAPLComponentProps) { + return ( + control: MultiValueListControl, + defaultProps: MultiValueListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => { + // Merges the user-provided props with the default props. + // Any property defined by the user-provided data overrides the defaults. + const mergedProps = _.merge(defaultProps, props); + return renderCheckBoxComponent(control, mergedProps, input, resultBuilder); + }; + } + } + + /** + * Defines AggregateDuplicatesRenderer for APLComponentMode. + * + * Provides methods to render APL components using AlexaIconButton layouts. + */ + export class AggregateDuplicatesRenderer { + /** + * Provides a default implementation of aggregateDuplicates-textList with default props. + * + * Usage: + * - This is used to render list items where duplicates counts of item selections + * are allowed. + * - Returns a textList layout with increment and decrement alexaIcons on each listItem. + * + * @param control - ListControl + * @param defaultProps - props + * @param input - Input + * @param resultBuilder - Result builder + */ + static default = ( + control: MultiValueListControl, + defaultProps: MultiValueListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => aggregateDuplicatesComponent(control, defaultProps, input, resultBuilder); + + /** + * Provides customization over `aggregateDuplicatesComponent()` arguments where the input + * props overrides the defaults. + * + * @param props - props + */ + static of(props: MultiValueListAPLComponentProps) { + return ( + control: MultiValueListControl, + defaultProps: MultiValueListAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => { + // Merges the user-provided props with the default props. + // Any property defined by the user-provided data overrides the defaults. + const mergedProps = _.merge(defaultProps, props); + return aggregateDuplicatesComponent(control, mergedProps, input, resultBuilder); + }; + } + } } diff --git a/src/commonControls/numberControl/NumberControl.ts b/src/commonControls/numberControl/NumberControl.ts index e4e994a..b8ca0d0 100644 --- a/src/commonControls/numberControl/NumberControl.ts +++ b/src/commonControls/numberControl/NumberControl.ts @@ -25,13 +25,7 @@ import { ValueControlIntent, } from '../..'; import { Strings as $ } from '../../constants/Strings'; -import { - APLComponentProps, - Control, - ControlInputHandlingProps, - ControlProps, - ControlState, -} from '../../controls/Control'; +import { Control, ControlInputHandlingProps, ControlProps, ControlState } from '../../controls/Control'; import { ControlInput } from '../../controls/ControlInput'; import { ControlResultBuilder } from '../../controls/ControlResult'; import { InteractionModelContributor } from '../../controls/mixins/InteractionModelContributor'; @@ -64,11 +58,7 @@ import { import { SystemAct } from '../../systemActs/SystemAct'; import { StringOrList } from '../../utils/BasicTypes'; import { evaluateInputHandlers, _logIfBothTrue } from '../../utils/ControlUtils'; -import { - NumberControlAPLComponentBuiltIns, - NumberControlAPLComponentStyle, - NumberControlAPLPropsBuiltIns, -} from './NumberControlAPL'; +import { NumberControlAPLPropsBuiltIns } from './NumberControlAPL'; const log = new Logger('AskSdkControls:NumberControl'); @@ -279,6 +269,12 @@ export type AplContentFunc = ( validationFailedMessage: string, ) => AplContent; export type AplPropNewStyle = AplContent | AplContentFunc; +export type AplRenderComponentFunc = ( + control: NumberControl, + props: NumberControlAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, +) => { [key: string]: any }; /** * Props associated with the APL produced by NumberControl. @@ -305,6 +301,23 @@ export class NumberControlAPLProps { * The message to show on screen if validation fails. */ validationFailedMessage?: string | ((value?: number) => string); + + /** + * Determines the APL Component rendering mode. + * + * Usage: + * + * 1) Use pre-defined built-ins under ListControlComponentAPLBuiltIns.* namespace which provides both default + * implementations and customization of props(NumberControlAPLComponentProps) to render an APL component. + * + * e.g renderComponent: NumberControlAPLComponentBuiltIns.ModalKeyPadRender.default --- Default Implementation + * renderComponent: NumberControlAPLComponentBuiltIns.ModalKeyPadRender.default.of(props: NumberControlAPLComponentProps) --- Override few properties + * + * 2) Provide a custom function which returns an APL component. + * + * Default: NumberControlAPLComponentBuiltIns.ModalKeyPadRender.default + */ + renderComponent?: AplRenderComponentFunc; } /** @@ -320,12 +333,7 @@ interface LastInitiativeState { /** * Props to customize NumberControl APLComponent rendering. */ -export interface NumberControlAPLComponentProps extends APLComponentProps { - /** - * Defines the render style of APL component produced by the control. - */ - renderStyle: NumberControlAPLComponentStyle; - +export interface NumberControlAPLComponentProps { /** * Tracks the text to be displayed for invalid input values. */ @@ -660,17 +668,12 @@ export class NumberControl extends Control implements InteractionModelContributo } } - renderAPLComponent( - props: NumberControlAPLComponentProps, - input: ControlInput, - resultBuilder: ControlResponseBuilder, - ): { [key: string]: any } { - return NumberControlAPLComponentBuiltIns.renderComponent( - this, - { ...props, validationFailureText: this.evaluateAPLValidationFailedMessage(this.state.value) }, - input, - resultBuilder, - ); + renderAPLComponent(input: ControlInput, resultBuilder: ControlResponseBuilder): { [key: string]: any } { + const aplRenderFunc = this.props.apl.renderComponent; + const defaultProps: NumberControlAPLComponentProps = { + validationFailureText: this.evaluateAPLValidationFailedMessage(this.state.value), + }; + return aplRenderFunc.call(this, this, defaultProps, input, resultBuilder); } // tsDoc - see Control diff --git a/src/commonControls/numberControl/NumberControlAPL.ts b/src/commonControls/numberControl/NumberControlAPL.ts index 9045d5c..8a3e86f 100644 --- a/src/commonControls/numberControl/NumberControlAPL.ts +++ b/src/commonControls/numberControl/NumberControlAPL.ts @@ -12,10 +12,151 @@ */ import i18next from 'i18next'; +import _ from 'lodash'; import { ControlInput, ControlResponseBuilder } from '../..'; import { DeepRequired } from '../../utils/DeepRequired'; +import { ListAPLComponentProps } from '../listControl/ListControl'; import { NumberControl, NumberControlAPLComponentProps, NumberControlAPLProps } from './NumberControl'; +export namespace NumberControlAPLComponentBuiltIns { + export function renderModalKeypad( + control: NumberControl, + props: NumberControlAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) { + resultBuilder.addAPLDocumentStyle('NumberControlFrameStyle', { + values: [ + { + borderWidth: 2, + borderStrokeWidth: 1, + borderColor: 'darkgrey', + hintColor: 'grey', + fontSize: '40dp', + }, + { + when: '${state.focused}', + // borderColor: 'green', + borderStrokeWidth: 2, + }, + ], + }); + + resultBuilder.addAPLDocumentLayout('ModalKeyPad', { + parameters: [ + { + name: 'controlId', + type: 'string', + }, + { + name: 'validationFailureText', + type: 'string', + }, + ], + items: [ + { + type: 'Container', + style: 'NumberControlFrameStyle', + width: '100%', + height: '100%', + items: [ + { + type: 'Container', + width: '100%', + height: '100%', + direction: 'column', + items: [ + { + type: 'EditText', + id: 'editTextNumber', + style: 'EditStyle', + keyboardType: 'numberPad', + submitKeyType: 'go', + onSubmit: [ + { + type: 'Sequential', + commands: [ + { + type: 'SendEvent', + arguments: ['${controlId}', '${event.source.value}'], + }, + ], + }, + ], + accessibilityLabel: 'Enter a number', + minWidth: '100%', + maxWidth: '100%', + grow: 1, + validCharacters: '-0-9', + text: control.state.value?.toString(), + hint: '[number]', + hintWeight: 'normal', + fontSize: '34px', + }, + { + type: 'Text', + text: control.state.isValidValue ? '' : '${validationFailureMessage}', + minWidth: '100%', + maxWidth: '100%', + height: '30px', + fontSize: '24px', + color: 'red', + }, + ], + }, + ], + }, + ], + }); + + return { + type: 'ModalKeyPad', + controlId: control.id, + validationFailureMessage: props.validationFailureText, //control.evaluateAPLValidationFailedMessage(control.state.value); + }; + } + + /** + * Defines ModalKeyPad Renderer for APLComponentMode. + */ + export class ModalKeyPadRender { + /** + * Provides a default implementation of textList with default props. + * + * @param control - ListControl + * @param defaultProps - props + * @param input - Input + * @param resultBuilder - Result builder + */ + static default = ( + control: NumberControl, + defaultProps: NumberControlAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => renderModalKeypad(control, defaultProps, input, resultBuilder); + + /** + * Provides customization over `renderModalKeypad()` arguments where the input + * props overrides the defaults. + * + * @param props - props + */ + static of(props: ListAPLComponentProps) { + return ( + control: NumberControl, + defaultProps: NumberControlAPLComponentProps, + input: ControlInput, + resultBuilder: ControlResponseBuilder, + ) => { + // Merges the user-provided props with the default props. + // Any property defined by the user-provided data overrides the defaults. + const mergedProps = _.merge(defaultProps, props); + return renderModalKeypad(control, mergedProps, input, resultBuilder); + }; + } + } +} + export namespace NumberControlAPLPropsBuiltIns { /* * Default NumberControl APL props @@ -46,6 +187,7 @@ export namespace NumberControlAPLPropsBuiltIns { }; return aplContent; }, + renderComponent: NumberControlAPLComponentBuiltIns.ModalKeyPadRender.default, }; /** @@ -177,115 +319,3 @@ export namespace NumberControlAPLPropsBuiltIns { } export type NumberControlAPLComponentStyle = 'modalKeypad'; - -export namespace NumberControlAPLComponentBuiltIns { - export function renderComponent( - control: NumberControl, - props: NumberControlAPLComponentProps, - input: ControlInput, - resultBuilder: ControlResponseBuilder, - ) { - if (props.renderStyle === 'modalKeypad') { - return renderModalKeypad(control, props, input, resultBuilder); - } else { - throw new Error('Invalid renderStyle'); - } - } - - export function renderModalKeypad( - control: NumberControl, - props: NumberControlAPLComponentProps, - input: ControlInput, - resultBuilder: ControlResponseBuilder, - ) { - resultBuilder.addAPLDocumentStyle('NumberControlFrameStyle', { - values: [ - { - borderWidth: 2, - borderStrokeWidth: 1, - borderColor: 'darkgrey', - hintColor: 'grey', - fontSize: '40dp', - }, - { - when: '${state.focused}', - // borderColor: 'green', - borderStrokeWidth: 2, - }, - ], - }); - - resultBuilder.addAPLDocumentLayout('ModalKeyPad', { - parameters: [ - { - name: 'controlId', - type: 'string', - }, - { - name: 'validationFailureText', - type: 'string', - }, - ], - items: [ - { - type: 'Container', - style: 'NumberControlFrameStyle', - width: '100%', - height: '100%', - items: [ - { - type: 'Container', - width: '100%', - height: '100%', - direction: 'column', - items: [ - { - type: 'EditText', - id: 'editTextNumber', - style: 'EditStyle', - keyboardType: 'numberPad', - submitKeyType: 'go', - onSubmit: [ - { - type: 'Sequential', - commands: [ - { - type: 'SendEvent', - arguments: ['${controlId}', '${event.source.value}'], - }, - ], - }, - ], - accessibilityLabel: 'Enter a number', - minWidth: '100%', - maxWidth: '100%', - grow: 1, - validCharacters: '-0-9', - text: control.state.value?.toString(), - hint: '[number]', - hintWeight: 'normal', - fontSize: '34px', - }, - { - type: 'Text', - text: control.state.isValidValue ? '' : '${validationFailureMessage}', - minWidth: '100%', - maxWidth: '100%', - height: '30px', - fontSize: '24px', - color: 'red', - }, - ], - }, - ], - }, - ], - }); - - return { - type: 'ModalKeyPad', - controlId: control.id, - validationFailureMessage: props.validationFailureText, //control.evaluateAPLValidationFailedMessage(control.state.value); - }; - } -} diff --git a/src/controls/Control.ts b/src/controls/Control.ts index ca4eeb8..40d8161 100644 --- a/src/controls/Control.ts +++ b/src/controls/Control.ts @@ -32,14 +32,6 @@ export interface ControlProps { */ id: string; } - -export interface APLComponentProps { - /** - * Defines the render style of APL component produced by the control. - */ - renderStyle: string; -} - /** * Defines the mandatory state of a Control. */ @@ -294,11 +286,16 @@ export abstract class Control implements IControl { return act.render(input, responseBuilder); } - renderAPLComponent( - props: APLComponentProps, - input: ControlInput, - responseBuilder: ControlResponseBuilder, - ): { [key: string]: any } { + /** + * Add response APL component by this control. + * + * This is intended to be used to provide APL rendering component for a control to process + * inputs, provide feedback, elicitation etc through touch events on APL screens. + * + * @param input - Input + * @param responseBuilder - Response builder + */ + renderAPLComponent(input: ControlInput, responseBuilder: ControlResponseBuilder): { [key: string]: any } { throw new Error('Not Implemented'); }