2019-09-12 08:11:25 +00:00
|
|
|
import React from "react";
|
2019-09-13 10:45:49 +00:00
|
|
|
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
|
2019-11-13 07:00:25 +00:00
|
|
|
import { WidgetType } from "constants/WidgetConstants";
|
2020-03-06 09:45:21 +00:00
|
|
|
import InputComponent, {
|
|
|
|
|
InputComponentProps,
|
|
|
|
|
} from "components/designSystems/blueprint/InputComponent";
|
2021-03-30 05:29:03 +00:00
|
|
|
import {
|
|
|
|
|
EventType,
|
|
|
|
|
ExecutionResult,
|
|
|
|
|
} from "constants/AppsmithActionConstants/ActionConstants";
|
2019-11-22 13:12:39 +00:00
|
|
|
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
2021-03-13 14:24:45 +00:00
|
|
|
import { createMessage, FIELD_REQUIRED_ERROR } from "constants/messages";
|
2021-02-25 14:00:02 +00:00
|
|
|
import { DerivedPropertiesMap } from "utils/WidgetFactory";
|
2020-08-28 17:23:07 +00:00
|
|
|
import * as Sentry from "@sentry/react";
|
2020-10-06 09:01:51 +00:00
|
|
|
import withMeta, { WithMeta } from "./MetaHOC";
|
2021-05-18 18:29:39 +00:00
|
|
|
import { GRID_DENSITY_MIGRATION_V1 } from "mockResponses/WidgetConfigResponse";
|
2019-09-12 08:11:25 +00:00
|
|
|
|
2020-10-06 09:01:51 +00:00
|
|
|
class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
2021-02-16 10:29:08 +00:00
|
|
|
constructor(props: InputWidgetProps) {
|
|
|
|
|
super(props);
|
|
|
|
|
this.state = {
|
|
|
|
|
text: props.text,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static getPropertyPaneConfig() {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
sectionName: "General",
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
helpText: "Changes the type of data captured in the input",
|
|
|
|
|
propertyName: "inputType",
|
|
|
|
|
label: "Data Type",
|
|
|
|
|
controlType: "DROP_DOWN",
|
|
|
|
|
options: [
|
|
|
|
|
{
|
|
|
|
|
label: "Text",
|
|
|
|
|
value: "TEXT",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: "Number",
|
|
|
|
|
value: "NUMBER",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: "Password",
|
|
|
|
|
value: "PASSWORD",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: "Email",
|
|
|
|
|
value: "EMAIL",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
isBindProperty: false,
|
|
|
|
|
isTriggerProperty: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText:
|
|
|
|
|
"Sets the default text of the widget. The text is updated if the default text changes",
|
|
|
|
|
propertyName: "defaultText",
|
|
|
|
|
label: "Default Text",
|
|
|
|
|
controlType: "INPUT_TEXT",
|
|
|
|
|
placeholderText: "Enter default text",
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.TEXT,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText: "Sets a placeholder text for the input",
|
|
|
|
|
propertyName: "placeholderText",
|
|
|
|
|
label: "Placeholder",
|
|
|
|
|
controlType: "INPUT_TEXT",
|
|
|
|
|
placeholderText: "Enter placeholder text",
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.TEXT,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText:
|
|
|
|
|
"Adds a validation to the input which displays an error on failure",
|
|
|
|
|
propertyName: "regex",
|
|
|
|
|
label: "Regex",
|
|
|
|
|
controlType: "INPUT_TEXT",
|
|
|
|
|
placeholderText: "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$",
|
|
|
|
|
inputType: "TEXT",
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.REGEX,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText:
|
|
|
|
|
"Displays the error message if the regex validation fails",
|
|
|
|
|
propertyName: "errorMessage",
|
|
|
|
|
label: "Error Message",
|
|
|
|
|
controlType: "INPUT_TEXT",
|
|
|
|
|
placeholderText: "Enter error message",
|
|
|
|
|
inputType: "TEXT",
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.TEXT,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
propertyName: "isRequired",
|
|
|
|
|
label: "Required",
|
|
|
|
|
helpText: "Makes input to the widget mandatory",
|
|
|
|
|
controlType: "SWITCH",
|
|
|
|
|
isJSConvertible: true,
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.BOOLEAN,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText: "Controls the visibility of the widget",
|
|
|
|
|
propertyName: "isVisible",
|
|
|
|
|
label: "Visible",
|
|
|
|
|
controlType: "SWITCH",
|
|
|
|
|
isJSConvertible: true,
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.BOOLEAN,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText: "Disables input to this widget",
|
|
|
|
|
propertyName: "isDisabled",
|
|
|
|
|
label: "Disabled",
|
|
|
|
|
controlType: "SWITCH",
|
|
|
|
|
isJSConvertible: true,
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.BOOLEAN,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText: "Clears the input value after submit",
|
|
|
|
|
propertyName: "resetOnSubmit",
|
|
|
|
|
label: "Reset on submit",
|
|
|
|
|
controlType: "SWITCH",
|
|
|
|
|
isJSConvertible: true,
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: false,
|
2021-04-21 14:34:25 +00:00
|
|
|
validation: VALIDATION_TYPES.BOOLEAN,
|
2021-02-16 10:29:08 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
sectionName: "Actions",
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
helpText: "Triggers an action when the text is changed",
|
|
|
|
|
propertyName: "onTextChanged",
|
|
|
|
|
label: "onTextChanged",
|
|
|
|
|
controlType: "ACTION_SELECTOR",
|
|
|
|
|
isJSConvertible: true,
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
helpText:
|
|
|
|
|
"Triggers an action on submit (when the enter key is pressed)",
|
|
|
|
|
propertyName: "onSubmit",
|
|
|
|
|
label: "onSubmit",
|
|
|
|
|
controlType: "ACTION_SELECTOR",
|
|
|
|
|
isJSConvertible: true,
|
|
|
|
|
isBindProperty: true,
|
|
|
|
|
isTriggerProperty: true,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
}
|
2019-10-31 05:28:11 +00:00
|
|
|
|
2020-03-06 09:45:21 +00:00
|
|
|
static getDerivedPropertiesMap(): DerivedPropertiesMap {
|
|
|
|
|
return {
|
2020-08-17 14:23:57 +00:00
|
|
|
isValid: `{{
|
|
|
|
|
function(){
|
2020-10-07 06:33:53 +00:00
|
|
|
let parsedRegex = null;
|
|
|
|
|
if (this.regex) {
|
2020-10-11 13:32:02 +00:00
|
|
|
/*
|
|
|
|
|
* break up the regexp pattern into 4 parts: given regex, regex prefix , regex pattern, regex flags
|
2021-01-04 10:16:08 +00:00
|
|
|
* Example /test/i will be split into ["/test/gi", "/", "test", "gi"]
|
2020-10-11 13:32:02 +00:00
|
|
|
*/
|
2020-10-07 06:33:53 +00:00
|
|
|
const regexParts = this.regex.match(/(\\/?)(.+)\\1([a-z]*)/i);
|
2020-12-15 05:04:30 +00:00
|
|
|
|
2020-11-04 14:19:46 +00:00
|
|
|
if (!regexParts) {
|
2020-10-07 06:33:53 +00:00
|
|
|
parsedRegex = new RegExp(this.regex);
|
2020-11-04 14:19:46 +00:00
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* if we don't have a regex flags (gmisuy), convert provided string into regexp directly
|
|
|
|
|
/*
|
|
|
|
|
if (regexParts[3] && !/^(?!.*?(.).*?\\1)[gmisuy]+$/.test(regexParts[3])) {
|
|
|
|
|
parsedRegex = RegExp(this.regex);
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* if we have a regex flags, use it to form regexp
|
|
|
|
|
*/
|
|
|
|
|
parsedRegex = new RegExp(regexParts[2], regexParts[3]);
|
2020-10-07 06:33:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-10-05 07:06:24 +00:00
|
|
|
if (this.inputType === "EMAIL") {
|
2020-08-17 14:23:57 +00:00
|
|
|
const emailRegex = new RegExp(/^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$/);
|
|
|
|
|
return emailRegex.test(this.text);
|
2020-10-05 07:06:24 +00:00
|
|
|
}
|
|
|
|
|
else if (this.inputType === "NUMBER") {
|
2020-12-15 05:04:30 +00:00
|
|
|
if (parsedRegex) {
|
|
|
|
|
return parsedRegex.test(this.text);
|
|
|
|
|
}
|
2021-04-22 08:28:16 +00:00
|
|
|
if (this.isRequired) {
|
|
|
|
|
return !(this.text === '' || isNaN(this.text));
|
|
|
|
|
}
|
2020-12-15 05:04:30 +00:00
|
|
|
|
2021-04-22 08:28:16 +00:00
|
|
|
return (this.text === '' || !isNaN(this.text || ''));
|
2020-10-05 07:06:24 +00:00
|
|
|
}
|
|
|
|
|
else if (this.isRequired) {
|
|
|
|
|
if(this.text && this.text.length) {
|
2020-10-07 06:33:53 +00:00
|
|
|
if (parsedRegex) {
|
|
|
|
|
return parsedRegex.test(this.text)
|
2020-08-17 14:23:57 +00:00
|
|
|
} else {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-10-07 06:33:53 +00:00
|
|
|
} if (parsedRegex) {
|
|
|
|
|
return parsedRegex.test(this.text)
|
2020-08-17 14:23:57 +00:00
|
|
|
} else {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}}`,
|
2020-06-09 12:04:38 +00:00
|
|
|
value: `{{this.text}}`,
|
2020-03-06 09:45:21 +00:00
|
|
|
};
|
2019-10-31 05:28:11 +00:00
|
|
|
}
|
|
|
|
|
|
2020-04-17 16:15:09 +00:00
|
|
|
static getDefaultPropertiesMap(): Record<string, string> {
|
|
|
|
|
return {
|
|
|
|
|
text: "defaultText",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static getMetaPropertiesMap(): Record<string, any> {
|
|
|
|
|
return {
|
|
|
|
|
text: undefined,
|
|
|
|
|
isFocused: false,
|
|
|
|
|
isDirty: false,
|
|
|
|
|
};
|
2019-10-31 05:28:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onValueChange = (value: string) => {
|
2020-10-06 16:47:16 +00:00
|
|
|
this.props.updateWidgetMetaProperty("text", value, {
|
2021-04-23 13:50:55 +00:00
|
|
|
triggerPropertyName: "onTextChanged",
|
2020-10-06 16:47:16 +00:00
|
|
|
dynamicString: this.props.onTextChanged,
|
|
|
|
|
event: {
|
|
|
|
|
type: EventType.ON_TEXT_CHANGE,
|
|
|
|
|
},
|
|
|
|
|
});
|
2020-03-06 09:45:21 +00:00
|
|
|
if (!this.props.isDirty) {
|
2020-10-06 09:01:51 +00:00
|
|
|
this.props.updateWidgetMetaProperty("isDirty", true);
|
2020-03-06 09:45:21 +00:00
|
|
|
}
|
2020-10-06 09:01:51 +00:00
|
|
|
};
|
2019-10-31 05:28:11 +00:00
|
|
|
|
2020-03-06 09:45:21 +00:00
|
|
|
handleFocusChange = (focusState: boolean) => {
|
2020-10-06 09:01:51 +00:00
|
|
|
this.props.updateWidgetMetaProperty("isFocused", focusState);
|
2020-03-06 09:45:21 +00:00
|
|
|
};
|
|
|
|
|
|
2021-02-04 05:51:16 +00:00
|
|
|
onSubmitSuccess = (result: ExecutionResult) => {
|
|
|
|
|
if (result.success && this.props.resetOnSubmit) {
|
|
|
|
|
this.props.updateWidgetMetaProperty("text", "", {
|
2021-04-23 13:50:55 +00:00
|
|
|
triggerPropertyName: "onSubmit",
|
2021-02-04 05:51:16 +00:00
|
|
|
dynamicString: this.props.onTextChanged,
|
|
|
|
|
event: {
|
|
|
|
|
type: EventType.ON_TEXT_CHANGE,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-28 11:15:14 +00:00
|
|
|
handleKeyDown = (
|
|
|
|
|
e:
|
|
|
|
|
| React.KeyboardEvent<HTMLTextAreaElement>
|
|
|
|
|
| React.KeyboardEvent<HTMLInputElement>,
|
|
|
|
|
) => {
|
2021-02-10 07:43:41 +00:00
|
|
|
const { isValid, onSubmit } = this.props;
|
2021-01-28 11:15:14 +00:00
|
|
|
const isEnterKey = e.key === "Enter" || e.keyCode === 13;
|
2021-02-10 07:43:41 +00:00
|
|
|
if (isEnterKey && onSubmit && isValid) {
|
2021-01-28 11:15:14 +00:00
|
|
|
super.executeAction({
|
2021-04-23 13:50:55 +00:00
|
|
|
triggerPropertyName: "onSubmit",
|
2021-02-10 07:43:41 +00:00
|
|
|
dynamicString: onSubmit,
|
2021-01-28 11:15:14 +00:00
|
|
|
event: {
|
|
|
|
|
type: EventType.ON_SUBMIT,
|
2021-02-04 05:51:16 +00:00
|
|
|
callback: this.onSubmitSuccess,
|
2021-01-28 11:15:14 +00:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-12 08:11:25 +00:00
|
|
|
getPageView() {
|
2020-10-06 09:01:51 +00:00
|
|
|
const value = this.props.text || "";
|
2020-03-06 14:29:10 +00:00
|
|
|
const isInvalid =
|
|
|
|
|
"isValid" in this.props && !this.props.isValid && !!this.props.isDirty;
|
2020-08-17 14:23:57 +00:00
|
|
|
|
2020-03-06 09:45:21 +00:00
|
|
|
const conditionalProps: Partial<InputComponentProps> = {};
|
|
|
|
|
conditionalProps.errorMessage = this.props.errorMessage;
|
|
|
|
|
if (this.props.isRequired && value.length === 0) {
|
2021-03-13 14:24:45 +00:00
|
|
|
conditionalProps.errorMessage = createMessage(FIELD_REQUIRED_ERROR);
|
2020-03-06 09:45:21 +00:00
|
|
|
}
|
|
|
|
|
if (this.props.maxChars) conditionalProps.maxChars = this.props.maxChars;
|
|
|
|
|
if (this.props.maxNum) conditionalProps.maxNum = this.props.maxNum;
|
|
|
|
|
if (this.props.minNum) conditionalProps.minNum = this.props.minNum;
|
2021-01-28 11:15:14 +00:00
|
|
|
|
2019-10-30 10:23:20 +00:00
|
|
|
return (
|
|
|
|
|
<InputComponent
|
|
|
|
|
defaultValue={this.props.defaultText}
|
2021-04-28 10:28:39 +00:00
|
|
|
disableNewLineOnPressEnterKey={!!this.props.onSubmit}
|
|
|
|
|
disabled={this.props.isDisabled}
|
|
|
|
|
inputType={this.props.inputType}
|
|
|
|
|
isInvalid={isInvalid}
|
2019-12-03 04:41:10 +00:00
|
|
|
isLoading={this.props.isLoading}
|
2021-04-28 10:28:39 +00:00
|
|
|
label={this.props.label}
|
2020-02-13 09:32:24 +00:00
|
|
|
multiline={
|
2021-05-18 18:29:39 +00:00
|
|
|
// GRID_DENSITY_MIGRATION_V1 used to adjust code as per new scaled canvas.
|
|
|
|
|
(this.props.bottomRow - this.props.topRow) /
|
|
|
|
|
GRID_DENSITY_MIGRATION_V1 >
|
|
|
|
|
1 && this.props.inputType === "TEXT"
|
2020-02-06 07:01:25 +00:00
|
|
|
}
|
2020-03-06 09:45:21 +00:00
|
|
|
onFocusChange={this.handleFocusChange}
|
2021-01-28 11:15:14 +00:00
|
|
|
onKeyDown={this.handleKeyDown}
|
2021-04-28 10:28:39 +00:00
|
|
|
onValueChange={this.onValueChange}
|
|
|
|
|
placeholder={this.props.placeholderText}
|
|
|
|
|
showError={!!this.props.isFocused}
|
|
|
|
|
stepSize={1}
|
|
|
|
|
value={value}
|
|
|
|
|
widgetId={this.props.widgetId}
|
2020-03-06 09:45:21 +00:00
|
|
|
{...conditionalProps}
|
2019-10-30 10:23:20 +00:00
|
|
|
/>
|
|
|
|
|
);
|
2019-09-12 08:11:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getWidgetType(): WidgetType {
|
|
|
|
|
return "INPUT_WIDGET";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 05:35:16 +00:00
|
|
|
export const InputTypes: { [key: string]: string } = {
|
|
|
|
|
TEXT: "TEXT",
|
|
|
|
|
NUMBER: "NUMBER",
|
|
|
|
|
INTEGER: "INTEGER",
|
|
|
|
|
PHONE_NUMBER: "PHONE_NUMBER",
|
|
|
|
|
EMAIL: "EMAIL",
|
|
|
|
|
PASSWORD: "PASSWORD",
|
|
|
|
|
CURRENCY: "CURRENCY",
|
|
|
|
|
SEARCH: "SEARCH",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type InputType = typeof InputTypes[keyof typeof InputTypes];
|
2019-09-12 08:11:25 +00:00
|
|
|
|
2019-09-19 11:29:24 +00:00
|
|
|
export interface InputValidator {
|
|
|
|
|
validationRegex: string;
|
|
|
|
|
errorMessage: string;
|
|
|
|
|
}
|
2020-10-06 09:01:51 +00:00
|
|
|
export interface InputWidgetProps extends WidgetProps, WithMeta {
|
2019-09-12 08:11:25 +00:00
|
|
|
inputType: InputType;
|
|
|
|
|
defaultText?: string;
|
2019-10-30 10:23:20 +00:00
|
|
|
isDisabled?: boolean;
|
2020-04-17 16:15:09 +00:00
|
|
|
text: string;
|
2019-10-31 05:28:11 +00:00
|
|
|
regex?: string;
|
|
|
|
|
errorMessage?: string;
|
2019-09-19 11:29:24 +00:00
|
|
|
placeholderText?: string;
|
|
|
|
|
maxChars?: number;
|
2019-10-30 10:23:20 +00:00
|
|
|
minNum?: number;
|
|
|
|
|
maxNum?: number;
|
2020-02-18 10:41:52 +00:00
|
|
|
onTextChanged?: string;
|
2019-09-12 08:11:25 +00:00
|
|
|
label: string;
|
2019-09-19 11:29:24 +00:00
|
|
|
inputValidators: InputValidator[];
|
2020-03-06 09:45:21 +00:00
|
|
|
isValid: boolean;
|
2019-09-13 10:45:49 +00:00
|
|
|
focusIndex?: number;
|
2019-09-19 11:29:24 +00:00
|
|
|
isAutoFocusEnabled?: boolean;
|
2020-03-06 09:45:21 +00:00
|
|
|
isRequired?: boolean;
|
|
|
|
|
isFocused?: boolean;
|
|
|
|
|
isDirty?: boolean;
|
2021-01-28 11:15:14 +00:00
|
|
|
onSubmit?: string;
|
2019-09-12 08:11:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default InputWidget;
|
2020-10-06 09:01:51 +00:00
|
|
|
export const ProfiledInputWidget = Sentry.withProfiler(withMeta(InputWidget));
|