diff --git a/js/renovation/button.tsx b/js/renovation/button.tsx index a2b9963a1cc3..98a5120b3995 100644 --- a/js/renovation/button.tsx +++ b/js/renovation/button.tsx @@ -100,7 +100,13 @@ export const viewFunction = (viewModel: ButtonViewModel) => { >
{viewModel.contentRender && - } + } {!viewModel.contentRender && isIconLeft && icon} {!viewModel.contentRender && viewModel.text && {viewModel.text} @@ -125,6 +131,7 @@ export class ButtonInput extends WidgetInput { @OneWay() onSubmit?: (e: any) => any = (() => undefined); @OneWay() pressed?: boolean; @OneWay() stylingMode?: 'outlined' | 'text' | 'contained'; + @OneWay() template?: any = ''; @OneWay() text?: string = ''; @OneWay() type?: string; @OneWay() useInkRipple: boolean = false; diff --git a/js/renovation/dist/button.j.jsx b/js/renovation/dist/button.j.jsx index 6af720629526..75a55355978b 100644 --- a/js/renovation/dist/button.j.jsx +++ b/js/renovation/dist/button.j.jsx @@ -1,7 +1,15 @@ + +import $ from '../../core/renderer'; +import * as Preact from 'preact'; import registerComponent from '../../core/component_registrator'; -import Widget from '../preact_wrapper'; +import Widget from '../preact-wrapper/component'; import { extend } from '../../core/utils/extend'; import ButtonView from '../button.p'; +import { wrapElement } from '../preact-wrapper/utils'; +import { useLayoutEffect } from 'preact/hooks'; +import { getPublicElement } from '../../core/utils/dom'; + +const TEMPLATE_WRAPPER_CLASS = 'dx-template-wrapper'; class Button extends Widget { getView() { @@ -10,22 +18,33 @@ class Button extends Widget { getProps(isFirstRender) { const props = super.getProps(isFirstRender); - if(props.contentRender) { - props.contentRender = (data) => { - const template = this._getTemplate(props.contentRender); - - return (
{ - if(element && element.parentElement) { - const parent = element.parentElement; - while(parent.firstChild) { - parent.removeChild(parent.firstChild); - } - template.render({ model: data, container: parent }); - parent.appendChild(element); + + if(props.template) { + const template = this._getTemplate(props.template); + + // TODO: rename 'contentRender' => 'template' after fix generator bug + // (renames 'template' => 'render' in declaration) + props.contentRender = ({ parentRef, ...restProps }) => { + useLayoutEffect(() => { + const $parent = $(parentRef.current); + let $template = template.render({ + container: getPublicElement($parent), + ...restProps, + }); + + if($template.hasClass(TEMPLATE_WRAPPER_CLASS)) { + $template = wrapElement($parent, $template); } - }}/>); + + return () => { + $template.remove(); + }; + }, Object.keys(props).map(key => props[key])); + + return (); }; } + props.onClick = this._createActionByOption('onClick', { excludeValidators: ['readOnly'], afterExecute: () => { @@ -34,6 +53,7 @@ class Button extends Widget { useSubmitBehavior && setTimeout(() => this._submitInput().click()); } }); + return props; } @@ -44,6 +64,7 @@ class Button extends Widget { hoverStateEnabled: true, icon: '', iconPosition: 'left', + template: '', text: '', }); } diff --git a/js/renovation/dist/widget.j.jsx b/js/renovation/dist/widget.j.jsx index 2eebc9111096..a26c4ad4d28c 100644 --- a/js/renovation/dist/widget.j.jsx +++ b/js/renovation/dist/widget.j.jsx @@ -1,5 +1,5 @@ import registerComponent from '../../core/component_registrator'; -import WidgetBase from '../preact_wrapper'; +import WidgetBase from '../preact-wrapper/component'; import { extend } from '../../core/utils/extend'; import WidgetView from '../widget.p'; diff --git a/js/renovation/preact_wrapper.js b/js/renovation/preact-wrapper/component.js similarity index 76% rename from js/renovation/preact_wrapper.js rename to js/renovation/preact-wrapper/component.js index 300c12113cd7..d1a294967838 100644 --- a/js/renovation/preact_wrapper.js +++ b/js/renovation/preact-wrapper/component.js @@ -1,6 +1,6 @@ -import Widget from '../ui/widget/ui.widget'; +import Widget from '../../ui/widget/ui.widget'; import * as Preact from 'preact'; -import { extend } from '../core/utils/extend'; +import { extend } from '../../core/utils/extend'; export default class PreactWrapper extends Widget { getInstance() { @@ -23,15 +23,18 @@ export default class PreactWrapper extends Widget { getProps(isFirstRender) { const options = extend({}, this.option()); + const attributes = this.$element()[0].attributes; if(isFirstRender) { - const attributes = this.$element()[0].attributes; options.elementAttr = extend(Object.keys(attributes).reduce((a, key) => { if(attributes[key].specified) { a[attributes[key].name] = attributes[key].value; } return a; }, {}), options.elementAttr); + } else if(attributes.id) { + // NOTE: workaround to save container id + options.elementAttr = extend({ [attributes.id.name]: attributes.id.value }, options.elementAttr); } return options; diff --git a/js/renovation/preact-wrapper/utils.js b/js/renovation/preact-wrapper/utils.js new file mode 100644 index 000000000000..689610443213 --- /dev/null +++ b/js/renovation/preact-wrapper/utils.js @@ -0,0 +1,16 @@ +// NOTE: function for jQuery templates +export const wrapElement = ($element, $wrapper) => { + const attributes = [...$wrapper.get(0).attributes]; + attributes.forEach(({ name, value }) => { + if(name === 'class') { + $element.addClass(value); + } else { + $element.attr(name, value); + } + }); + + const children = $wrapper.contents(); + $wrapper.replaceWith(children); + + return children; +}; diff --git a/styles/widgets/common/button.less b/styles/widgets/common/button.less index e288ce4abd65..20afe036199f 100644 --- a/styles/widgets/common/button.less +++ b/styles/widgets/common/button.less @@ -58,6 +58,7 @@ width: 0; font-size: 0; opacity: 0; + display: none; } .dx-state-disabled { diff --git a/testing/jest/button.tests.tsx b/testing/jest/button.tests.tsx index 620a24ab7875..c2219b5fc802 100644 --- a/testing/jest/button.tests.tsx +++ b/testing/jest/button.tests.tsx @@ -210,15 +210,46 @@ describe('Button', () => { }); describe('contentRender', () => { - it('should render template', () => { + const contentRender = ({ model: { text } }) =>
{`${text}123`}
; + + it('should render contentRender', () => { const button = render({ text: 'My button', - contentRender: ({ text }) =>
{text + 123}
, + contentRender, }); - const buttonContentChildren = button.find('.dx-button-content').children(); + const customRender = button.find(contentRender); + + expect(customRender.exists()).toBe(true); + expect(customRender.exists('.custom-content')).toBe(true); + + expect(customRender.props().model.text).toBe('My button'); + expect(customRender.text()).toBe('My button123'); + }); + + it('should rerender contentRender in runtime', () => { + const button = mount(