Add back derived properties
This commit is contained in:
parent
17b642d085
commit
cc50beb0a0
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
21
app/client/src/utils/DerivedPropertiesFactory.ts
Normal file
21
app/client/src/utils/DerivedPropertiesFactory.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user