Add back derived properties

This commit is contained in:
Hetu Nandu 2020-01-17 09:28:26 +00:00
parent 17b642d085
commit cc50beb0a0
18 changed files with 410 additions and 104 deletions

View File

@ -82,6 +82,7 @@
"source-map-explorer": "^2.1.1",
"styled-components": "^4.1.3",
"tinycolor2": "^1.4.1",
"toposort": "^2.0.2",
"ts-loader": "^6.0.4",
"typescript": "^3.6.3",
"unescape-js": "^1.1.4"
@ -123,6 +124,7 @@
"@types/react-select": "^3.0.5",
"@types/react-tabs": "^2.3.1",
"@types/redux-form": "^8.1.9",
"@types/toposort": "^2.0.3",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"babel-loader": "^8.0.6",

View File

@ -1,6 +1,6 @@
/* eslint-disable no-useless-escape */
// TODO (hetu): Remove useless escapes and re-enable the above lint rule
export type NamePathBindingMap = Record<string, string>;
export const DATA_BIND_REGEX = /(.*?){{(\s*(.*?)\s*)}}(.*?)/g;
export const DATA_BIND_REGEX = /{{([\s\S]*?)}}/g;
export const AUTOCOMPLETE_MATCH_REGEX = /{{\s*.*?\s*}}/g;
/* eslint-enable no-useless-escape */

View File

@ -44,7 +44,7 @@ export default class RealmExecutor implements JSExecutor {
try {
result = this.rootRealm.evaluate(sourceText, safeData);
} catch (e) {
//TODO(Satbir): Return an object with an error message.
console.error(`Error: "${e.message}" when evaluating {{${sourceText}}}`);
}
return this.convertToMainScope(result);
}

View File

@ -52,10 +52,7 @@ import { getFormData } from "selectors/formSelectors";
import { API_EDITOR_FORM_NAME } from "constants/forms";
import { executeAction } from "actions/widgetActions";
import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton";
import {
getNameBindingsWithData,
NameBindingsWithData,
} from "selectors/nameBindingsWithDataSelector";
import { getParsedDataTree } from "selectors/nameBindingsWithDataSelector";
import { transformRestAction } from "transformers/RestActionTransformer";
export const getAction = (
@ -83,8 +80,8 @@ const createActionErrorResponse = (
});
export function* evaluateDynamicBoundValueSaga(path: string): any {
const nameBindingsWithData = yield select(getNameBindingsWithData);
return getDynamicValue(`{{${path}}}`, nameBindingsWithData);
const tree = yield select(getParsedDataTree);
return getDynamicValue(`{{${path}}}`, tree);
}
export function* getActionParams(jsonPathKeys: string[] | undefined) {
@ -106,12 +103,10 @@ export function* getActionParams(jsonPathKeys: string[] | undefined) {
}
function* executeJSActionSaga(jsAction: ExecuteJSActionPayload) {
const nameBindingsWithData: NameBindingsWithData = yield select(
getNameBindingsWithData,
);
const tree = yield select(getParsedDataTree);
const result = JSExecutionManagerSingleton.evaluateSync(
jsAction.jsFunction,
nameBindingsWithData,
tree,
);
yield put({

View File

@ -7,7 +7,7 @@ import { WidgetConfigReducerState } from "reducers/entityReducers/widgetConfigRe
import { WidgetCardProps } from "widgets/BaseWidget";
import { WidgetSidebarReduxState } from "reducers/uiReducers/widgetSidebarReducer";
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
import { enhanceWithDynamicValuesAndValidations } from "utils/DynamicBindingUtils";
import { getEvaluatedDataTree, getParsedTree } from "utils/DynamicBindingUtils";
import { getDataTree } from "./entitiesSelector";
import {
FlattenedWidgetProps,
@ -18,9 +18,11 @@ import { PageListReduxState } from "reducers/entityReducers/pageListReducer";
import { OccupiedSpace } from "constants/editorConstants";
import { WidgetTypes } from "constants/WidgetConstants";
import {
getNameBindingsWithData,
NameBindingsWithData,
getNameBindingsWithData,
getParsedDataTree,
} from "./nameBindingsWithDataSelector";
import _ from "lodash";
const getEditorState = (state: AppState) => state.ui.editor;
const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig;
@ -116,15 +118,14 @@ export const getWidgetCards = createSelector(
export const getValidatedDynamicProps = createSelector(
getDataTree,
getNameBindingsWithData,
(entities: DataTree, nameBindingsWithData: NameBindingsWithData) => {
getParsedDataTree,
(entities: DataTree, tree) => {
const widgets = { ...entities.canvasWidgets };
Object.keys(widgets).forEach(widgetKey => {
widgets[widgetKey] = enhanceWithDynamicValuesAndValidations(
widgets[widgetKey],
nameBindingsWithData,
true,
);
const evaluatedWidget = _.find(tree, { widgetId: widgetKey });
if (evaluatedWidget) {
widgets[widgetKey] = evaluatedWidget;
}
});
return widgets;
},

View File

@ -4,37 +4,42 @@ import { createSelector } from "reselect";
import { getActions, getDataTree } from "./entitiesSelector";
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import createCachedSelector from "re-reselect";
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
export type NameBindingsWithData = Record<string, object>;
export const getNameBindingsWithData = createSelector(
getDataTree,
(dataTree: DataTree): NameBindingsWithData => {
const nameBindingsWithData: Record<string, object> = {};
Object.keys(dataTree.nameBindings).forEach(key => {
const nameBindings = dataTree.nameBindings[key];
const evaluatedValue = JSONPath({
nameBindingsWithData[key] = JSONPath({
path: nameBindings,
json: dataTree,
})[0];
if (evaluatedValue && key !== "undefined") {
nameBindingsWithData[key] = evaluatedValue;
}
});
return nameBindingsWithData;
},
);
export const getParsedDataTree = createSelector(
getNameBindingsWithData,
(namedBindings: NameBindingsWithData) => {
return getEvaluatedDataTree(namedBindings, true);
},
);
// For autocomplete. Use actions cached responses if
// there isn't a response already
export const getNameBindingsForAutocomplete = createCachedSelector(
getNameBindingsWithData,
getParsedDataTree,
getActions,
(namedBindings: NameBindingsWithData, actions: ActionDataState["data"]) => {
(dataTree: NameBindingsWithData, actions: ActionDataState["data"]) => {
const cachedResponses: Record<string, any> = {};
if (actions && actions.length) {
actions.forEach(action => {
if (!(action.name in namedBindings) && action.cacheResponse) {
if (!(action.name in dataTree) && action.cacheResponse) {
try {
cachedResponses[action.name] = JSON.parse(action.cacheResponse);
} catch (e) {
@ -43,6 +48,6 @@ export const getNameBindingsForAutocomplete = createCachedSelector(
}
});
}
return { ...namedBindings, ...cachedResponses };
return { ...dataTree, ...cachedResponses };
},
)((state: AppState) => state.entities.actions.data.length);

View File

@ -4,12 +4,16 @@ import { PropertyPaneReduxState } from "reducers/uiReducers/propertyPaneReducer"
import { PropertyPaneConfigState } from "reducers/entityReducers/propertyPaneConfigReducer";
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { PropertySection } from "reducers/entityReducers/propertyPaneConfigReducer";
import { enhanceWithDynamicValuesAndValidations } from "utils/DynamicBindingUtils";
import {
enhanceWidgetWithValidations,
getEvaluatedDataTree,
} from "utils/DynamicBindingUtils";
import { WidgetProps } from "widgets/BaseWidget";
import {
getNameBindingsWithData,
NameBindingsWithData,
getNameBindingsWithData,
} from "./nameBindingsWithDataSelector";
import _ from "lodash";
const getPropertyPaneState = (state: AppState): PropertyPaneReduxState =>
state.ui.propertyPane;
@ -41,14 +45,23 @@ export const getWidgetPropsWithValidations = createSelector(
getNameBindingsWithData,
(
widget: WidgetProps | undefined,
nameBindigsWithData: NameBindingsWithData,
nameBindingsWithData: NameBindingsWithData,
) => {
if (!widget) return undefined;
return enhanceWithDynamicValuesAndValidations(
widget,
nameBindigsWithData,
false,
const tree = getEvaluatedDataTree(nameBindingsWithData, false);
const evaluatedWidget = _.find(tree, { widgetId: widget.widgetId });
const validations = enhanceWidgetWithValidations(
evaluatedWidget as WidgetProps,
);
if (validations) {
const { invalidProps, validationMessages } = validations;
return {
...widget,
invalidProps,
validationMessages,
};
}
return widget;
},
);

View File

@ -0,0 +1,21 @@
import WidgetFactory from "./WidgetFactory";
import { WidgetType } from "constants/WidgetConstants";
export class DerivedPropFactory {
static getDerivedPropertiesOfWidgetType(
widgetType: WidgetType,
widgetName: string,
): any {
const derivedPropertyMap = WidgetFactory.getWidgetDerivedPropertiesMap(
widgetType,
);
const derivedProps: any = {};
Object.keys(derivedPropertyMap).forEach(propertyName => {
derivedProps[propertyName] = derivedPropertyMap[propertyName].replace(
/this./g,
`${widgetName}.`,
);
});
return derivedProps;
}
}

View File

@ -3,8 +3,9 @@ import { WidgetProps } from "widgets/BaseWidget";
import { DATA_BIND_REGEX } from "constants/BindingsConstants";
import ValidationFactory from "./ValidationFactory";
import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton";
import { NameBindingsWithData } from "selectors/nameBindingsWithDataSelector";
import unescapeJS from "unescape-js";
import { NameBindingsWithData } from "selectors/nameBindingsWithDataSelector";
import toposort from "toposort";
export const isDynamicValue = (value: string): boolean =>
DATA_BIND_REGEX.test(value);
@ -51,11 +52,34 @@ export function parseDynamicString(dynamicString: string): string[] {
return parsedDynamicValues;
}
const getAllPaths = (
tree: Record<string, any>,
prefix = "",
): Record<string, true> => {
return Object.keys(tree).reduce((res: Record<string, true>, el): Record<
string,
true
> => {
if (Array.isArray(tree[el])) {
const key = `${prefix}${el}`;
return { ...res, [key]: true };
} else if (typeof tree[el] === "object" && tree[el] !== null) {
const key = `${prefix}${el}`;
return { ...res, [key]: true, ...getAllPaths(tree[el], `${key}.`) };
} else {
const key = `${prefix}${el}`;
return { ...res, [key]: true };
}
}, {});
};
export const getDynamicBindings = (
dynamicString: string,
): { bindings: string[]; paths: string[] } => {
if (!dynamicString) return { bindings: [], paths: [] };
const sanitisedString = dynamicString.trim();
// Get the {{binding}} bound values
const bindings = parseDynamicString(dynamicString);
const bindings = parseDynamicString(sanitisedString);
// Get the "binding" path values
const paths = bindings.map(binding => {
const length = binding.length;
@ -105,22 +129,7 @@ export const getDynamicValue = (
// Get the Data Tree value of those "binding "paths
const values = paths.map((p, i) => {
if (p) {
const value = evaluateDynamicBoundValue(data, p);
// Check if the result is a dynamic value, if so get the value again
if (isDynamicValue(value)) {
// Check for the paths of this dynamic value
const { paths } = getDynamicBindings(value);
// If it is the same as it came in, log an error
// and return the same value back
if (paths.length === 1 && paths[0] === p) {
console.error("Binding not correct");
return value;
}
// Evaluate the value again
return getDynamicValue(value, data);
} else {
return value;
}
return evaluateDynamicBoundValue(data, p);
} else {
return bindings[i];
}
@ -134,34 +143,161 @@ export const getDynamicValue = (
return undefined;
};
export const enhanceWithDynamicValuesAndValidations = (
export const enhanceWidgetWithValidations = (
widget: WidgetProps,
nameBindingsWithData: NameBindingsWithData,
replaceWithParsed: boolean,
): WidgetProps => {
if (!widget) return widget;
const properties = { ...widget };
const invalidProps: Record<string, boolean> = {};
const validationMessages: Record<string, string> = {};
Object.keys(widget).forEach((property: string) => {
let value = widget[property];
// Check for dynamic bindings
if (widget.dynamicBindings && property in widget.dynamicBindings) {
value = getDynamicValue(value, nameBindingsWithData);
}
Object.keys(properties).forEach((property: string) => {
const value = properties[property];
// Pass it through validation and parse
const {
isValid,
parsed,
message,
} = ValidationFactory.validateWidgetProperty(widget.type, property, value);
const { isValid, message } = ValidationFactory.validateWidgetProperty(
widget.type,
property,
value,
);
// Store all invalid props
if (!isValid) invalidProps[property] = true;
// Store validation Messages
if (message) validationMessages[property] = message;
// Replace if flag is turned on
if (replaceWithParsed) properties[property] = parsed;
});
return { ...properties, invalidProps, validationMessages };
return {
...properties,
invalidProps,
validationMessages,
};
};
export const getParsedTree = (tree: any) => {
return Object.keys(tree).reduce((tree, entityKey: string) => {
const entity = tree[entityKey];
if (entity && entity.type) {
const parsedEntity = { ...entity };
Object.keys(entity).forEach((property: string) => {
const value = entity[property];
// Pass it through parse
const { parsed } = ValidationFactory.validateWidgetProperty(
entity.type,
property,
value,
);
parsedEntity[property] = parsed;
});
return { ...tree, [entityKey]: parsedEntity };
}
return tree;
}, tree);
};
export const getEvaluatedDataTree = (
dataTree: NameBindingsWithData,
parseValues: boolean,
) => {
const dynamicDependencyMap = createDependencyTree(dataTree);
const evaluatedTree = dependencySortedEvaluateDataTree(
dataTree,
dynamicDependencyMap,
parseValues,
);
if (parseValues) {
return getParsedTree(evaluatedTree);
} else {
return evaluatedTree;
}
};
type DynamicDependencyMap = Record<string, Array<string>>;
export const createDependencyTree = (
dataTree: NameBindingsWithData,
): Array<[string, string]> => {
const dependencyMap: DynamicDependencyMap = {};
const allKeys = getAllPaths(dataTree);
Object.keys(dataTree).forEach(entityKey => {
const entity = dataTree[entityKey] as WidgetProps;
if (entity && entity.dynamicBindings) {
Object.keys(entity.dynamicBindings).forEach(prop => {
const { paths } = getDynamicBindings(entity[prop]);
dependencyMap[`${entityKey}.${prop}`] = paths.filter(p => !!p);
});
}
});
Object.keys(dependencyMap).forEach(key => {
dependencyMap[key] = _.flatten(
dependencyMap[key].map(path => calculateSubDependencies(path, allKeys)),
);
});
const dependencyTree: Array<[string, string]> = [];
Object.keys(dependencyMap).forEach((key: string) => {
dependencyMap[key].forEach(dep => dependencyTree.push([key, dep]));
});
return dependencyTree;
};
const calculateSubDependencies = (
path: string,
all: Record<string, true>,
): Array<string> => {
const subDeps: Array<string> = [];
const identifiers = path.match(/[a-zA-Z_$][a-zA-Z_$0-9.]*/g) || [path];
identifiers.forEach((identifier: string) => {
if (identifier in all) {
subDeps.push(identifier);
} else {
const subIdentifiers =
identifier.match(/[a-zA-Z_$][a-zA-Z_$0-9]*/g) || [];
let current = "";
for (let i = 0; i < subIdentifiers.length; i++) {
const key = `${current}${current ? "." : ""}${subIdentifiers[i]}`;
if (key in all) {
current = key;
} else {
break;
}
}
if (current) subDeps.push(current);
}
});
return subDeps;
};
export function dependencySortedEvaluateDataTree(
dataTree: NameBindingsWithData,
dependencyTree: Array<[string, string]>,
parseValues: boolean,
) {
const tree = JSON.parse(JSON.stringify(dataTree));
try {
// sort dependencies
const sortedDependencies = toposort(dependencyTree).reverse();
// evaluate and replace values
return sortedDependencies.reduce(
(currentTree: NameBindingsWithData, path: string) => {
const binding = _.get(currentTree as any, path);
const widgetType = _.get(
currentTree as any,
`${path.split(".")[0]}.type`,
null,
);
let result = binding;
if (isDynamicValue(binding)) {
result = getDynamicValue(binding, currentTree);
}
if (widgetType && parseValues) {
const { parsed } = ValidationFactory.validateWidgetProperty(
widgetType,
`${path.split(".")[1]}`,
result,
);
result = parsed;
}
return _.set(currentTree, path, result);
},
tree,
);
} catch (e) {
console.error(e);
return tree;
}
}

View File

@ -8,7 +8,11 @@ jest.mock("jsExecution/RealmExecutor", () => {
return { execute: mockExecute, registerLibrary: mockRegisterLibrary };
});
});
import { getDynamicValue, parseDynamicString } from "./DynamicBindingUtils";
import {
dependencySortedEvaluateDataTree,
getDynamicValue,
parseDynamicString,
} from "./DynamicBindingUtils";
import { getNameBindingsWithData } from "selectors/nameBindingsWithDataSelector";
import { AppState, DataTree } from "reducers";
@ -121,3 +125,43 @@ it("Parse the dynamic string", () => {
expect(value).toEqual(actualValue);
});
it("evaluates the data tree", () => {
const input = {
widget1: {
displayValue: "{{widget2.computedProperty}}",
},
widget2: {
computedProperty: "{{ widget2.data[widget2.index] }}",
data: "{{ apiData.node }}",
index: 2,
},
apiData: {
node: ["wrong value", "still wrong", "correct"],
},
};
const dynamicBindings = [
["widget1.displayValue", "widget2.computedProperty"],
["widget2.computedProperty", "widget2.data"],
["widget2.computedProperty", "widget2.index"],
["widget2.data", "apiData.node"],
];
const output = {
widget1: {
displayValue: "correct",
},
widget2: {
computedProperty: "correct",
data: ["wrong value", "still wrong", "correct"],
index: 2,
},
apiData: {
node: ["wrong value", "still wrong", "correct"],
},
};
const result = dependencySortedEvaluateDataTree(input, dynamicBindings);
expect(result).toEqual(output);
});

View File

@ -6,20 +6,33 @@ import {
} from "widgets/BaseWidget";
import { WidgetPropertyValidationType } from "./ValidationFactory";
type WidgetDerivedPropertyType = any;
export type DerivedPropertiesMap = Record<string, string>;
class WidgetFactory {
static widgetMap: Map<WidgetType, WidgetBuilder<WidgetProps>> = new Map();
static widgetPropValidationMap: Map<
WidgetType,
WidgetPropertyValidationType
> = new Map();
static widgetDerivedPropertiesGetterMap: Map<
WidgetType,
WidgetDerivedPropertyType
> = new Map();
static derivedPropertiesMap: Map<
WidgetType,
DerivedPropertiesMap
> = new Map();
static registerWidgetBuilder(
widgetType: WidgetType,
widgetBuilder: WidgetBuilder<WidgetProps>,
widgetPropertyValidation: WidgetPropertyValidationType,
derivedPropertiesMap: DerivedPropertiesMap,
) {
this.widgetMap.set(widgetType, widgetBuilder);
this.widgetPropValidationMap.set(widgetType, widgetPropertyValidation);
this.derivedPropertiesMap.set(widgetType, derivedPropertiesMap);
}
static createWidget(
@ -60,6 +73,17 @@ class WidgetFactory {
}
return map;
}
static getWidgetDerivedPropertiesMap(
widgetType: WidgetType,
): DerivedPropertiesMap {
const map = this.derivedPropertiesMap.get(widgetType);
if (!map) {
console.error("Widget type validation is not defined");
return {};
}
return map;
}
}
export interface WidgetCreationException {

View File

@ -14,7 +14,7 @@ import {
WidgetOperations,
WidgetOperation,
} from "widgets/BaseWidget";
import { WidgetType, RenderModes } from "constants/WidgetConstants";
import { WidgetType } from "constants/WidgetConstants";
import { generateReactKey } from "utils/generators";
import {
GridDefaults,
@ -25,6 +25,7 @@ import {
} from "constants/WidgetConstants";
import { snapToGrid } from "./helpers";
import { OccupiedSpace } from "constants/editorConstants";
import { DerivedPropFactory } from "utils/DerivedPropertiesFactory";
export type WidgetOperationParams = {
operation: WidgetOperation;
@ -289,17 +290,26 @@ export const generateWidgetProps = (
children: [],
};
}
const derivedProperties = DerivedPropFactory.getDerivedPropertiesOfWidgetType(
type,
widgetName,
);
const dynamicBindings: Record<string, true> = {};
Object.keys(derivedProperties).forEach(prop => {
dynamicBindings[prop] = true;
});
return {
...widgetConfig,
type,
widgetName: widgetName,
widgetName,
isVisible: true,
isLoading: false,
parentColumnSpace,
parentRowSpace,
renderMode: RenderModes.CANVAS,
dynamicBindings,
...sizes,
...others,
...derivedProperties,
};
} else {
if (parent) {

View File

@ -32,6 +32,7 @@ class WidgetBuilderRegistry {
},
},
ContainerWidget.getPropertyValidationMap(),
ContainerWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -42,6 +43,7 @@ class WidgetBuilderRegistry {
},
},
TextWidget.getPropertyValidationMap(),
TextWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -52,6 +54,7 @@ class WidgetBuilderRegistry {
},
},
ButtonWidget.getPropertyValidationMap(),
ButtonWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -62,6 +65,7 @@ class WidgetBuilderRegistry {
},
},
SpinnerWidget.getPropertyValidationMap(),
SpinnerWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -72,6 +76,7 @@ class WidgetBuilderRegistry {
},
},
InputWidget.getPropertyValidationMap(),
InputWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -82,6 +87,7 @@ class WidgetBuilderRegistry {
},
},
CheckboxWidget.getPropertyValidationMap(),
CheckboxWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -92,6 +98,7 @@ class WidgetBuilderRegistry {
},
},
DropdownWidget.getPropertyValidationMap(),
DropdownWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -102,6 +109,7 @@ class WidgetBuilderRegistry {
},
},
RadioGroupWidget.getPropertyValidationMap(),
RadioGroupWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
@ -112,6 +120,7 @@ class WidgetBuilderRegistry {
},
},
ImageWidget.getPropertyValidationMap(),
ImageWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
"TABLE_WIDGET",
@ -121,6 +130,7 @@ class WidgetBuilderRegistry {
},
},
TableWidget.getPropertyValidationMap(),
TableWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
"FILE_PICKER_WIDGET",
@ -130,6 +140,7 @@ class WidgetBuilderRegistry {
},
},
FilePickerWidget.getPropertyValidationMap(),
FilePickerWidget.getDerivedPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
"DATE_PICKER_WIDGET",
@ -139,6 +150,7 @@ class WidgetBuilderRegistry {
},
},
DatePickerWidget.getPropertyValidationMap(),
DatePickerWidget.getDerivedPropertiesMap(),
);
}
}

View File

@ -28,6 +28,7 @@ import { PositionTypes } from "constants/WidgetConstants";
import ErrorBoundary from "components/editorComponents/ErrorBoundry";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { DerivedPropertiesMap } from "utils/WidgetFactory";
/***
* BaseWidget
*
@ -63,6 +64,10 @@ abstract class BaseWidget<
return {};
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {};
}
/**
* Widget abstraction to register the widget type
* ```javascript

View File

@ -7,6 +7,10 @@ import _ from "lodash";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
export interface DropDownDerivedProps {
selectedOption?: DropdownOption;
selectedOptionArr?: DropdownOption[];
}
class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
@ -18,6 +22,23 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
selectedIndexArr: VALIDATION_TYPES.ARRAY,
};
}
static getDerivedPropertiesMap() {
return {
selectedOption: `{{
this.selectionType === 'SINGLE_SELECT'
? this.options[this.selectedIndex]
: undefined
}}`,
selectedOptionArr: `{{
const options = this.options || [];
this.selectionType === "MULTI_SELECT"
? options.filter((opt, index) =>
_.includes(this.selectedIndexArr, index),
)
: undefined
}}`,
};
}
getPageView() {
return (
<DropDownComponent

View File

@ -14,6 +14,12 @@ class RadioGroupWidget extends BaseWidget<RadioGroupWidgetProps, WidgetState> {
selectedOptionValue: VALIDATION_TYPES.TEXT,
};
}
static getDerivedPropertiesMap() {
return {
selectedOption:
"{{_.find(this.options, { value: this.selectedOptionValue })}}",
};
}
getPageView() {
return (
<RadioGroupComponent

View File

@ -2,7 +2,7 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { ActionPayload, TableAction } from "constants/ActionConstants";
import _, { forIn } from "lodash";
import { forIn } from "lodash";
import TableComponent from "components/designSystems/syncfusion/TableComponent";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
@ -32,7 +32,12 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
nextPageKey: VALIDATION_TYPES.TEXT,
prevPageKey: VALIDATION_TYPES.TEXT,
label: VALIDATION_TYPES.TEXT,
selectedRow: VALIDATION_TYPES.OBJECT,
selectedRowIndex: VALIDATION_TYPES.NUMBER,
};
}
static getDerivedPropertiesMap() {
return {
selectedRow: "{{this.tableData[this.selectedRowIndex]}}",
};
}
@ -46,40 +51,36 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
isLoading={this.props.isLoading}
height={this.state.componentHeight}
width={this.state.componentWidth}
selectedRowIndex={
this.props.selectedRow && this.props.selectedRow.rowIndex
}
selectedRowIndex={this.props.selectedRowIndex}
disableDrag={(disable: boolean) => {
this.disableDrag(disable);
}}
onRowClick={(rowData: object, index: number) => {
const { onRowSelected } = this.props;
this.updateSelectedRowProperty(rowData, index);
this.updateSelectedRowProperty(index);
super.executeAction(onRowSelected);
}}
></TableComponent>
/>
);
}
componentDidUpdate(prevProps: TableWidgetProps) {
super.componentDidUpdate(prevProps);
if (
!_.isEqual(prevProps.tableData, this.props.tableData) &&
prevProps.selectedRow
) {
this.updateSelectedRowProperty(
this.props.tableData[prevProps.selectedRow.rowIndex],
prevProps.selectedRow.rowIndex,
);
}
}
// componentDidUpdate(prevProps: TableWidgetProps) {
// super.componentDidUpdate(prevProps);
// if (
// !_.isEqual(prevProps.tableData, this.props.tableData) &&
// prevProps.selectedRow
// ) {
// this.updateSelectedRowProperty(
// this.props.tableData[prevProps.selectedRow.rowIndex],
// prevProps.selectedRow.rowIndex,
// );
// }
// }
updateSelectedRowProperty(rowData: object, index: number) {
updateSelectedRowProperty(index: number) {
const { widgetId } = this.props;
this.updateWidgetProperty(widgetId, "selectedRow", {
...rowData,
rowIndex: index,
});
this.updateWidgetProperty(widgetId, "selectedRowIndex", index);
}
getWidgetType(): WidgetType {
@ -102,7 +103,7 @@ export interface TableWidgetProps extends WidgetProps {
recordActions?: TableAction[];
onPageChange?: ActionPayload[];
onRowSelected?: ActionPayload[];
selectedRow?: SelectedRow;
selectedRowIndex?: number;
}
export default TableWidget;

View File

@ -2569,6 +2569,11 @@
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf"
integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==
"@types/toposort@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/toposort/-/toposort-2.0.3.tgz#dc490842b77c3e910c8d727ff0bdb2fb124cb41b"
integrity sha512-jRtyvEu0Na/sy0oIxBW0f6wPQjidgVqlmCTJVHEGTNEUdL1f0YSvdPzHY7nX7MUWAZS6zcAa0KkqofHjy/xDZQ==
"@types/uglify-js@*":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
@ -14649,6 +14654,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"