Repository: DevExpress/devextreme-react
Pull request #258 (2018-12-10 07:57:20)

Support children in Template


Patch:
diff --git a/example/data-grid-example.tsx b/example/data-grid-example.tsx
index 7c481e2..70eeb8e 100644
--- a/example/data-grid-example.tsx
+++ b/example/data-grid-example.tsx
@@ -34,10 +34,6 @@ const RegionComponent = (props: any) => {
     return <b>{props.displayValue}</b>;
 };
 
-const ToolbarItemRender = () => {
-    return <b>Collapse or Expand Groups:</b>;
-};
-
 export default class extends React.Component<any, { expandAll: boolean, pageSize: number }> {
 
     constructor(props: any) {
@@ -94,7 +90,9 @@ export default class extends React.Component<any, { expandAll: boolean, pageSize
                     />
                     <MasterDetail enabled={true} component={DetailComponent} />
 
-                    <Template name={"toolbarLabel"} render={ToolbarItemRender} />
+                    <Template name={"toolbarLabel"}>
+                        {this.state.expandAll ? <b>Collapse Groups:</b> : <b>Expand Groups:</b>}
+                    </Template>
                 </DataGrid>
             </Example>
         );
diff --git a/src/core/template-host.ts b/src/core/template-host.ts
index db9183e..e2cc35b 100644
--- a/src/core/template-host.ts
+++ b/src/core/template-host.ts
@@ -1,4 +1,4 @@
-import { ITemplateMeta } from "./template";
+import { ITemplateMeta, ITemplateProps } from "./template";
 
 import * as React from "react";
 
@@ -42,6 +42,7 @@ class TemplateHost {
     private _nestedTemplateProps: Record<string, {
         render: any;
         component: any;
+        children: any;
     }> = {};
 
     constructor(stateUpdater: StateUpdater) {
@@ -83,21 +84,18 @@ class TemplateHost {
         };
     }
 
-    public addNested(props: {
-        name: string;
-        render: any;
-        component: any;
-    }): void {
+    public addNested(props: ITemplateProps): void {
         const name: string = props.name;
         this._nestedTemplateProps[name] = {
             component: props.component,
-            render: props.render
+            render: props.render,
+            children: () => props.children
         };
         const propsGetter = (prop) => this._nestedTemplateProps[name][prop];
 
         const templateDescr: ITemplateDescr = {
             name,
-            propName: !!props.component ? "component" : "render",
+            propName: !!props.component ? "component" : !!props.render ? "render" : "children",
             isComponent: !!props.component,
             propsGetter
         };
diff --git a/src/core/template.ts b/src/core/template.ts
index 18e3e64..df87dd0 100644
--- a/src/core/template.ts
+++ b/src/core/template.ts
@@ -11,6 +11,7 @@ interface ITemplateProps {
     name: string;
     component?: any;
     render?: any;
+    children?: any;
 }
 
 class Template extends React.PureComponent<ITemplateProps, any> {
@@ -20,7 +21,7 @@ class Template extends React.PureComponent<ITemplateProps, any> {
 }
 
 const requiredPropsCheck = (props: Record<string, any>) => {
-    if (!props.component && !props.render) {
+    if (!props.component && !props.render && !props.children) {
         return new Error("The Template component requires 'component' or 'render' property");
     }
     return null;
@@ -29,22 +30,25 @@ const requiredPropsCheck = (props: Record<string, any>) => {
 (Template as any).propTypes = {
     name: PropTypes.string.isRequired,
     component: requiredPropsCheck,
-    render: requiredPropsCheck
+    render: requiredPropsCheck,
+    children: requiredPropsCheck
 };
 
-function findProps(child: React.ReactElement<any>): { name: string, render: any, component: any } | undefined {
+function findProps(child: React.ReactElement<any>): ITemplateProps | undefined {
     if (child.type !== Template) {
         return;
     }
     return {
         name: child.props.name,
         render: child.props.render,
-        component: child.props.component
+        component: child.props.component,
+        children: child.props.children
     };
 }
 
 export {
     ITemplateMeta,
+    ITemplateProps,
     Template,
     findProps
 };


Test patch:
diff --git a/src/core/__tests__/template.test.tsx b/src/core/__tests__/template.test.tsx
index 1641de9..9bab76c 100644
--- a/src/core/__tests__/template.test.tsx
+++ b/src/core/__tests__/template.test.tsx
@@ -244,6 +244,9 @@ describe("nested template", () => {
             <ComponentWithTemplates>
                 <Template name={"item1"} render={ItemTemplate} />
                 <Template name={"item2"} component={ItemTemplate} />
+                <Template name={"item3"}>
+                    <ItemTemplate/>
+                </Template>
             </ComponentWithTemplates >
         );
 
@@ -261,6 +264,9 @@ describe("nested template", () => {
 
         expect(integrationOptions.templates.item2).toBeDefined();
         expect(typeof integrationOptions.templates.item2.render).toBe("function");
+
+        expect(integrationOptions.templates.item3).toBeDefined();
+        expect(typeof integrationOptions.templates.item3.render).toBe("function");
     });
 
     it("renders nested templates", () => {
@@ -275,6 +281,19 @@ describe("nested template", () => {
         expect(component.find(".template").html()).toBe('<div class="template">Template</div>');
     });
 
+    it("renders children of nested template", () => {
+        const component = mount(
+            <ComponentWithTemplates>
+                <Template name={"item1"}>
+                    <div className={"template"}>Template</div>
+                </Template>
+            </ComponentWithTemplates >
+        );
+        renderTemplate("item1");
+        component.update();
+        expect(component.find(".template").html()).toBe('<div class="template">Template</div>');
+    });
+
     it("renders new templates after component change", () => {
         const FirstTemplate = () => <div className={"template"}>First Template</div>;
         const component = mount(
@@ -294,6 +313,29 @@ describe("nested template", () => {
         expect(component.find(".template").html()).toBe('<div class="template">Second Template</div>');
     });
 
+    it("renders new templates after children change", () => {
+        const component = mount(
+            <ComponentWithTemplates>
+                <Template name={"item1"}>
+                    <div className={"template"}>First Template</div>
+                </Template>
+            </ComponentWithTemplates >
+        );
+        renderTemplate("item1");
+        component.update();
+        expect(component.find(".template").html()).toBe('<div class="template">First Template</div>');
+
+        component.setProps({
+            children: (
+                <Template name={"item1"}>
+                    <div className={"template"}>Second Template</div>
+                </Template>
+            )
+        });
+        component.update();
+        expect(component.find(".template").html()).toBe('<div class="template">Second Template</div>');
+    });
+
 });
 
 describe("component/render in nested options", () => {