Skip to content

Commit 3905100

Browse files
committed
Add support for component in dictionaries props.
1 parent f42e3a2 commit 3905100

File tree

5 files changed

+76
-9
lines changed

5 files changed

+76
-9
lines changed

@plotly/dash-test-components/src/components/ComponentAsProp.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import PropTypes from 'prop-types';
33

44

55
const ComponentAsProp = (props) => {
6-
const { element, elements, id } = props;
6+
const { element, elements, id, shapeEl } = props;
77
return (
88
<div id={id}>
9+
{shapeEl && shapeEl.header}
910
{elements || element}
11+
{shapeEl && shapeEl.footer}
1012
</div>
1113
)
1214
}
@@ -16,6 +18,11 @@ ComponentAsProp.propTypes = {
1618
element: PropTypes.node,
1719

1820
elements: PropTypes.arrayOf(PropTypes.node),
21+
22+
shapeEl: PropTypes.shape({
23+
header: PropTypes.node,
24+
footer: PropTypes.node,
25+
})
1926
}
2027

2128
export default ComponentAsProp;

dash/dash-renderer/src/TreeContainer.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {propTypeErrorHandler} from './exceptions';
55
import {
66
addIndex,
77
assoc,
8+
assocPath,
89
concat,
910
dissoc,
1011
equals,
@@ -203,6 +204,23 @@ class BaseTreeContainer extends Component {
203204
dissoc('children'),
204205
...childrenProps
205206
.map(childrenProp => {
207+
if (childrenProp.includes('.')) {
208+
const path = childrenProp.split('.');
209+
const node =
210+
_dashprivate_layout.props[path[0]][path[1]];
211+
return assocPath(
212+
path,
213+
this.createContainer(
214+
this.props,
215+
node,
216+
concat(this.props._dashprivate_path, [
217+
'props',
218+
path[0],
219+
path[1]
220+
])
221+
)
222+
);
223+
}
206224
const node = _dashprivate_layout.props[childrenProp];
207225
if (node) {
208226
if (Array.isArray(node)) {

dash/dash-renderer/src/actions/utils.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,15 @@ export const crawlLayout = (object, func, currentPath = []) => {
4343
}
4444
const childrenProps = pathOr([], ['childrenProps'], object);
4545
childrenProps.forEach(childrenProp => {
46-
const newPath = concat(currentPath, ['props', childrenProp]);
47-
crawlLayout(path(['props', childrenProp], object), func, newPath);
46+
const newPath = concat(currentPath, [
47+
'props',
48+
...childrenProp.split('.')
49+
]);
50+
crawlLayout(
51+
path(['props', ...childrenProp.split('.')], object),
52+
func,
53+
newPath
54+
);
4855
});
4956
}
5057
};

dash/development/base_component.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ def __init__(self, **kwargs): # pylint: disable=too-many-branches
137137
if k not in ("children", "id", "style", "className") and not k_in_wildcards:
138138
if isinstance(v, Component):
139139
self._children_props.append(k)
140-
if hasattr(v, "__iter__"):
140+
elif isinstance(v, dict):
141+
for key, value in v.items():
142+
if isinstance(value, Component):
143+
self._children_props.append(k + "." + key)
144+
elif hasattr(v, "__iter__"):
141145
for item in v:
142146
if isinstance(item, Component):
143147
self._children_props.append(k)

tests/integration/renderer/test_component_as_prop.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,16 @@ def test_rdcap001_component_as_prop(dash_duo):
4040
[
4141
Button("click-list", id="to-list"),
4242
Div(id="output-from-list"),
43-
]
43+
Button("click footer", id="to-footer"),
44+
Div(id="from-header"),
45+
],
46+
),
47+
ComponentAsProp(
48+
id="shaped",
49+
shapeEl={
50+
"header": Button("header", id="button-header"),
51+
"footer": Div("initial", id="footer"),
52+
},
4453
),
4554
]
4655
)
@@ -67,10 +76,26 @@ def send_list_output(n_clicks):
6776
def send_to_list(n_clicks):
6877
return f"To list: {n_clicks}"
6978

79+
@app.callback(
80+
Output("from-header", "children"), [Input("button-header", "n_clicks")]
81+
)
82+
def from_header(n_clicks):
83+
return f"From header: {n_clicks}"
84+
85+
@app.callback(Output("footer", "children"), [Input("to-footer", "n_clicks")])
86+
def send_to_footer(n_clicks):
87+
return f"To footer: {n_clicks}"
88+
7089
dash_duo.start_server(app)
7190

91+
assert dash_duo.get_logs() == []
92+
7293
dash_duo.wait_for_text_to_equal("#as-props", "as-props")
7394

95+
elements = dash_duo.find_elements("#elements div")
96+
97+
assert len(elements) == 3
98+
7499
clicker = dash_duo.wait_for_element("#clicker")
75100
clicker.click()
76101
dash_duo.wait_for_text_to_equal("#output-from-prop", "From prop: 1")
@@ -79,14 +104,20 @@ def send_to_list(n_clicks):
79104
nested.click()
80105
dash_duo.wait_for_text_to_equal("#nested-output", "Nested: 1")
81106

82-
elements = dash_duo.find_elements("#elements div")
83-
84-
assert len(elements) == 3
85-
86107
to_list = dash_duo.find_element("#to-list")
87108
to_list.click()
88109
dash_duo.wait_for_text_to_equal("#list-output", "To list: 1")
89110

90111
from_list = dash_duo.find_element("#list-two")
91112
from_list.click()
92113
dash_duo.wait_for_text_to_equal("#output-from-list", "From list: 1")
114+
115+
from_header = dash_duo.find_element("#button-header")
116+
from_header.click()
117+
dash_duo.wait_for_text_to_equal("#from-header", "From header: 1")
118+
119+
to_footer = dash_duo.find_element("#to-footer")
120+
to_footer.click()
121+
dash_duo.wait_for_text_to_equal("#footer", "To footer: 1")
122+
123+
assert dash_duo.get_logs() == []

0 commit comments

Comments
 (0)