From 0b9c04004c70a0933cdd62ac03e7c430de210955 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 26 Aug 2022 15:43:41 -0400 Subject: [PATCH] support objectOf component type. --- dash/dash-renderer/src/TreeContainer.js | 151 +++++++++++++++--- dash/dash-renderer/src/actions/callbacks.ts | 2 +- dash/dash-renderer/src/actions/utils.js | 69 ++++++-- .../renderer/test_component_as_prop.py | 2 +- 4 files changed, 187 insertions(+), 37 deletions(-) diff --git a/dash/dash-renderer/src/TreeContainer.js b/dash/dash-renderer/src/TreeContainer.js index 04864c9db3..7882ef1526 100644 --- a/dash/dash-renderer/src/TreeContainer.js +++ b/dash/dash-renderer/src/TreeContainer.js @@ -20,7 +20,8 @@ import { propOr, path as rpath, pathOr, - type + type, + toPairs } from 'ramda'; import {notifyObservers, updateProps} from './actions'; import isSimpleComponent from './isSimpleComponent'; @@ -249,6 +250,17 @@ class BaseTreeContainer extends Component { for (let i = 0; i < childrenProps.length; i++) { const childrenProp = childrenProps[i]; + + const handleObject = (obj, opath) => { + return keys(obj).reduce((acc, k) => { + const node = acc[k]; + return { + ...acc, + [k]: this.wrapChildrenProp(node, [...opath, k]) + }; + }, obj); + }; + if (childrenProp.includes('.')) { let path = childrenProp.split('.'); let node; @@ -256,17 +268,33 @@ class BaseTreeContainer extends Component { if (childrenProp.includes('[]')) { let frontPath = [], backPath = [], - found = false; + found = false, + hasObject = false; path.forEach(p => { if (!found) { if (p.includes('[]')) { found = true; - frontPath.push(p.replace('[]', '')); + if (p.includes('{}')) { + hasObject = true; + frontPath.push( + p.replace('{}', '').replace('[]', '') + ); + } else { + frontPath.push(p.replace('[]', '')); + } + } else if (p.includes('{}')) { + hasObject = true; + frontPath.push(p.replace('{}', '')); } else { frontPath.push(p); } } else { - backPath.push(p); + if (p.includes('{}')) { + hasObject = true; + backPath.push(p.replace('{}', '')); + } else { + backPath.push(p); + } } }); @@ -278,38 +306,115 @@ class BaseTreeContainer extends Component { if (!firstNode) { continue; } + nodeValue = node.map((element, i) => { const elementPath = concat( frontPath, concat([i], backPath) ); - return assocPath( - backPath, - this.wrapChildrenProp( + let listValue; + if (hasObject) { + listValue = handleObject(element, elementPath); + } else { + listValue = this.wrapChildrenProp( rpath(backPath, element), elementPath - ), - element - ); + ); + } + return assocPath(backPath, listValue, element); }); path = frontPath; } else { - node = rpath(path, props); - if (node === undefined) { - continue; + if (childrenProp.includes('{}')) { + // Only supports one level of nesting. + const front = []; + let dynamic = []; + let hasBack = false; + const backPath = []; + + for (let j = 0; j < path.length; j++) { + const cur = path[j]; + if (cur.includes('{}')) { + dynamic = concat(front, [ + cur.replace('{}', '') + ]); + if (j < path.length - 1) { + hasBack = true; + } + } else { + if (hasBack) { + backPath.push(cur); + } else { + front.push(cur); + } + } + } + + const dynValue = rpath(dynamic, props); + if (dynValue !== undefined) { + nodeValue = toPairs(dynValue).reduce( + (acc, [k, d]) => ({ + ...acc, + [k]: this.wrapChildrenProp( + hasBack ? rpath(backPath, d) : d, + hasBack + ? concat( + dynamic, + concat([k], backPath) + ) + : concat(dynamic, [k]) + ) + }), + {} + ); + path = dynamic; + } + } else { + node = rpath(path, props); + if (node === undefined) { + continue; + } + nodeValue = this.wrapChildrenProp(node, path); } - nodeValue = this.wrapChildrenProp(node, path); } props = assocPath(path, nodeValue, props); - continue; - } - const node = props[childrenProp]; - if (node !== undefined) { - props = assoc( - childrenProp, - this.wrapChildrenProp(node, [childrenProp]), - props - ); + } else { + if (childrenProp.includes('{}')) { + let opath = childrenProp.replace('{}', ''); + const isArray = childrenProp.includes('[]'); + if (isArray) { + opath = opath.replace('[]', ''); + } + const node = props[opath]; + + if (node !== undefined) { + if (isArray) { + for (let j = 0; j < node.length; j++) { + const aPath = concat([opath], [j]); + props = assocPath( + aPath, + handleObject(node[j], aPath), + props + ); + } + } else { + props = assoc( + opath, + handleObject(node, [opath]), + props + ); + } + } + } else { + const node = props[childrenProp]; + if (node !== undefined) { + props = assoc( + childrenProp, + this.wrapChildrenProp(node, [childrenProp]), + props + ); + } + } } } diff --git a/dash/dash-renderer/src/actions/callbacks.ts b/dash/dash-renderer/src/actions/callbacks.ts index 63f4f7b633..964c1df321 100644 --- a/dash/dash-renderer/src/actions/callbacks.ts +++ b/dash/dash-renderer/src/actions/callbacks.ts @@ -160,7 +160,7 @@ function fillVals( inputList.map(({id, property, path: path_}: any) => ({ id, property, - value: (path(path_, layout) as any).props[property] + value: path([...path_, 'props', property], layout) as any })), specs[i], cb.anyVals, diff --git a/dash/dash-renderer/src/actions/utils.js b/dash/dash-renderer/src/actions/utils.js index 621c3421e1..e5eee177fd 100644 --- a/dash/dash-renderer/src/actions/utils.js +++ b/dash/dash-renderer/src/actions/utils.js @@ -65,19 +65,64 @@ export const crawlLayout = ( let [frontPath, backPath] = childrenProp .split('[]') .map(p => p.split('.').filter(e => e)); - const front = concat(['props'], frontPath); - const basePath = concat(currentPath, front); - crawlLayout(path(front, object), func, basePath, backPath); + if (childrenProp.includes('{}')) { + // TODO + } else { + const front = concat(['props'], frontPath); + const basePath = concat(currentPath, front); + crawlLayout(path(front, object), func, basePath, backPath); + } } else { - const newPath = concat(currentPath, [ - 'props', - ...childrenProp.split('.') - ]); - crawlLayout( - path(['props', ...childrenProp.split('.')], object), - func, - newPath - ); + if (childrenProp.includes('{}')) { + const opath = childrenProp.split('.'); + const frontPath = []; + const backPath = []; + let found = false; + + for (let i = 0; i < opath.length; i++) { + const curPath = opath[i]; + if (!found && curPath.includes('{}')) { + found = true; + frontPath.push(curPath.replace('{}', '')); + } else { + if (found) { + backPath.push(curPath); + } else { + frontPath.push(curPath); + } + } + } + const newPath = concat(currentPath, [ + 'props', + ...frontPath + ]); + + const oValue = path(['props', ...frontPath], object); + if (oValue !== undefined) { + Object.keys(oValue).forEach(key => { + const value = oValue[key]; + if (backPath.length) { + crawlLayout( + path(backPath, value), + func, + concat(newPath, [key, ...backPath]) + ); + } else { + crawlLayout(value, func, [...newPath, key]); + } + }); + } + } else { + const newPath = concat(currentPath, [ + 'props', + ...childrenProp.split('.') + ]); + crawlLayout( + path(['props', ...childrenProp.split('.')], object), + func, + newPath + ); + } } }); } diff --git a/tests/integration/renderer/test_component_as_prop.py b/tests/integration/renderer/test_component_as_prop.py index e0c171c532..0cc47110d0 100644 --- a/tests/integration/renderer/test_component_as_prop.py +++ b/tests/integration/renderer/test_component_as_prop.py @@ -235,6 +235,6 @@ def on_click(n_clicks): dash_duo.find_element("#click-dynamic").click() - dash_duo.wait_for_text_to_equal("#dynamic-output", "Clicked 1") + dash_duo.wait_for_text_to_equal("#output-dynamic", "Clicked 1") assert dash_duo.get_logs() == []