Repository: DevExpress/devextreme-vue
Pull request #211 (2018-11-29 14:49:44)

Update template components on base component update (#208)


Patch:
diff --git a/example/app.vue b/example/app.vue
index a27797a..a505de0 100644
--- a/example/app.vue
+++ b/example/app.vue
@@ -11,6 +11,8 @@
         <br/>
         <scroll-view-example/>
         <br/>
+        <tab-panel-example />
+        <br/>
         <list-example/>
         <br/>
         <grid-example/>
@@ -39,6 +41,8 @@ import ValidationExample from "./components/validation-example";
 import SchedulerExample from "./components/scheduler-example";
 import ScrollViewExample from "./components/scroll-view-example";
 import ChartExample from "./components/chart-example";
+import TabPanelExample from "./components/tab-panel-example";
+
 
 export default {
     components: {
@@ -53,7 +57,8 @@ export default {
         GridExample,
         SchedulerExample,
         ScrollViewExample,
-        ChartExample
+        ChartExample,
+        TabPanelExample
     },
     name: "app"
 }
diff --git a/example/components/grid-example.vue b/example/components/grid-example.vue
index b45fd4d..1d15ea5 100644
--- a/example/components/grid-example.vue
+++ b/example/components/grid-example.vue
@@ -79,8 +79,6 @@
 </template>
 
 <script>
-import Vue from "vue";
-
 import ExampleBlock from "./example-block";
 import { DxButton, DxCheckBox, DxDataGrid, DxDateBox, DxSwitch } from "../../src";
 import {
diff --git a/example/components/tab-panel-example.vue b/example/components/tab-panel-example.vue
new file mode 100644
index 0000000..d507ff9
--- /dev/null
+++ b/example/components/tab-panel-example.vue
@@ -0,0 +1,38 @@
+<template>
+    <example-block title="dxTabPanel">
+        <dx-tab-panel :items="tabs">
+            <div slot="tab1" slot-scope="_">
+                <h3>First Tab Content</h3>
+            </div>
+            <div slot="tab2" slot-scope="_">
+                <async-component/>
+            </div>
+        </dx-tab-panel>
+    </example-block>
+</template>
+
+<script>
+import Vue from "vue";
+import ExampleBlock from "./example-block";
+import { DxTabPanel } from "../../src";
+
+const SecondTabComponent = Vue.extend({
+    template: "<h3>Second Tab Content</h3>"
+});
+
+export default {
+  components: {
+    ExampleBlock,
+    DxTabPanel,
+    AsyncComponent: () => Promise.resolve(SecondTabComponent)
+  },
+  data: function() {
+    return {
+        tabs: [
+          { title:"Sync Tab", template:"tab1" },
+          { title:"Async Tab", template:"tab2" }
+        ]
+    };
+  }
+};
+</script>
\ No newline at end of file
diff --git a/src/core/component.ts b/src/core/component.ts
index a552435..a5c29b2 100644
--- a/src/core/component.ts
+++ b/src/core/component.ts
@@ -1,5 +1,5 @@
 import * as VueType from "vue";
-import { VNode, VueConstructor } from "vue";
+import IVue, { VNode, VueConstructor } from "vue";
 
 import * as events from "devextreme/events";
 
@@ -13,18 +13,44 @@ interface IWidgetComponent extends IConfigurable {
     $_WidgetClass: any;
 }
 
+interface IEventBusHolder {
+    eventBus: IVue;
+}
+
+interface IBaseComponent extends IVue, IWidgetComponent, IEventBusHolder {
+    $_isExtension: boolean;
+    $_createWidget: (element: any) => void;
+    $_getIntegrationOptions: () => void;
+    $_getExtraIntegrationOptions: () => void;
+    $_getWatchMethod: () => void;
+    $_createEmitters: () => void;
+    $_fillTemplate: () => void;
+}
+
 const Vue = VueType.default || VueType;
 
 const DX_TEMPLATE_WRAPPER_CLASS = "dx-template-wrapper";
 const DX_REMOVE_EVENT = "dxremove";
 
-const BaseComponent: VueConstructor = Vue.extend({
+const BaseComponent: VueConstructor<IBaseComponent> = Vue.extend({
 
     inheritAttrs: false,
 
+    data() {
+        return {
+            eventBus: new Vue()
+        };
+    },
+
+    provide() {
+        return {
+            eventBus: this.eventBus
+        };
+    },
+
     render(createElement: (...args) => VNode): VNode {
         const children: VNode[] = [];
-        pullAllChildren(this.$slots.default, children, (this as any as IWidgetComponent).$_config);
+        pullAllChildren(this.$slots.default, children, this.$_config);
 
         return createElement(
             "div",
@@ -35,8 +61,12 @@ const BaseComponent: VueConstructor = Vue.extend({
         );
     },
 
+    updated() {
+        this.eventBus.$emit("updated");
+    },
+
     beforeDestroy(): void {
-        const instance = (this as any as IWidgetComponent).$_instance;
+        const instance = this.$_instance;
         if (instance) {
             events.triggerHandler(this.$el, DX_REMOVE_EVENT);
             instance.dispose();
@@ -44,27 +74,26 @@ const BaseComponent: VueConstructor = Vue.extend({
     },
 
     created(): void {
-
-        (this as any as IWidgetComponent).$_config = new Configuration(
-            (n: string, v: any) => (this as any as IWidgetComponent).$_instance.option(n, v),
+        (this as IBaseComponent).$_config = new Configuration(
+            (n: string, v: any) => this.$_instance.option(n, v),
             null,
             this.$options.propsData && { ...this.$options.propsData },
-            (this as any as IWidgetComponent).$_expectedChildren
+            this.$_expectedChildren
         );
 
-        (this as any as IWidgetComponent).$_config.init(this.$props && Object.keys(this.$props));
+        this.$_config.init(this.$props && Object.keys(this.$props));
     },
 
     methods: {
         $_createWidget(element: any): void {
-            const config = (this as any as IWidgetComponent).$_config;
+            const config = this.$_config;
             const options: object = {
                 ...this.$_getIntegrationOptions(),
                 ...this.$options.propsData,
                 ...config.getInitialValues()
             };
-            const instance = new (this as any as IWidgetComponent).$_WidgetClass(element, options);
-            (this as any as IWidgetComponent).$_instance = instance;
+            const instance = new this.$_WidgetClass(element, options);
+            (this as IBaseComponent).$_instance = instance;
 
             instance.on("optionChanged", (args) => config.onOptionChanged(args));
             subscribeOnUpdates(config, this);
@@ -122,7 +151,13 @@ const BaseComponent: VueConstructor = Vue.extend({
                 render: (data: any) => {
                     const vm = new Vue({
                         name,
+                        inject: ["eventBus"],
                         parent: this,
+                        created() {
+                            (this as IEventBusHolder).eventBus.$on("updated", () => {
+                                this.$forceUpdate();
+                            });
+                        },
                         render: () => template(data.model)
                     }).$mount();
 
@@ -162,9 +197,9 @@ const DxComponent: VueConstructor = BaseComponent.extend({
     },
 
     mounted(): void {
-        (this as any).$_createWidget(this.$el);
-        (this as any as IWidgetComponent).$_instance.endUpdate();
-        this.$children.forEach((child: any) => {
+        this.$_createWidget(this.$el);
+        this.$_instance.endUpdate();
+        this.$children.forEach((child: IExtension) => {
             if (child.$_isExtension) {
                 child.attachTo(this.$el);
             }
diff --git a/src/core/configuration-component.ts b/src/core/configuration-component.ts
index e09c1f2..dc73966 100644
--- a/src/core/configuration-component.ts
+++ b/src/core/configuration-component.ts
@@ -22,7 +22,7 @@ interface IConfigurable extends IConfigurationOwner {
 const DxConfiguration: VueConstructor = Vue.extend({
 
     beforeMount() {
-        const config = (this.$vnode.componentOptions as any as IConfigurable).$_config;
+        const config = (this.$vnode.componentOptions as IConfigurable).$_config;
 
         config.init(Object.keys(this.$props));
         bindOptionWatchers(config, this);


Test patch:
diff --git a/src/core/component.test.ts b/src/core/component.test.ts
index 0d4931b..5aa36f6 100644
--- a/src/core/component.test.ts
+++ b/src/core/component.test.ts
@@ -912,6 +912,30 @@ describe("template", () => {
         expect(component.$children.length).toBe(1);
     });
 
+    it("updates templates on component updating (check via functional component inside)", () => {
+        expect.assertions(2);
+        const FunctionalComponent = Vue.extend({
+            functional: true,
+            render(h) {
+                expect(true).toBeTruthy();
+                return h("div");
+            }
+        });
+        const vm = new Vue({
+            template: `<test-component ref="component">
+                            <div slot='item' slot-scope='props'><functional-component/></div>
+                        </test-component>`,
+            components: {
+                TestComponent,
+                FunctionalComponent
+            }
+        }).$mount();
+        renderItemTemplate({});
+
+        const component: any = vm.$refs.component;
+        component.$forceUpdate();
+    });
+
     it("unwraps container", () => {
         new Vue({
             template: `<test-component>