}
{!viewModel.contentRender && isIconLeft && icon}
{!viewModel.contentRender && 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 babce2bda059..fc37e7830b8a 100644
--- a/testing/jest/button.tests.tsx
+++ b/testing/jest/button.tests.tsx
@@ -218,15 +218,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(
);
+
+ expect(button.exists(contentRender)).toBe(false);
+
+ button.setProps({ contentRender });
+ expect(button.exists(contentRender)).toBe(true);
+
+ button.setProps({ contentRender: undefined });
+ expect(button.exists(contentRender)).toBe(false);
+ });
+
+ it('should change properties in runtime', () => {
+ const button = mount(
);
+ let buttonContent = button.find(contentRender);
+
+ expect(buttonContent.props().model.text).toBe('My button');
+ expect(buttonContent.text()).toBe('My button123');
+
+ button.setProps({ text: 'New value' });
+ buttonContent = button.find(contentRender);
- expect(buttonContentChildren.props().text).toBe('My button');
- expect(buttonContentChildren.render().text()).toBe('My button123');
+ expect(buttonContent.props().model.text).toBe('New value');
+ expect(buttonContent.text()).toBe('New value123');
});
});
diff --git a/testing/tests/Renovation/button.tests.js b/testing/tests/Renovation/button.tests.js
new file mode 100644
index 000000000000..16cfcbbb0cdd
--- /dev/null
+++ b/testing/tests/Renovation/button.tests.js
@@ -0,0 +1,121 @@
+import $ from 'jquery';
+import 'renovation/dist/button.j';
+import { isRenderer } from 'core/utils/type';
+import config from 'core/config';
+
+QUnit.testStart(function() {
+ $('#qunit-fixture').html(`
+
+
+ `);
+});
+
+const buttonConfig = {
+ beforeEach: function(module) {
+ // it needs for Preact timers https://github.com/preactjs/preact/blob/master/hooks/src/index.js#L273
+ this.clock = sinon.useFakeTimers();
+ },
+ afterEach: function() {
+ this.clock.tick(100);
+ this.clock.restore();
+ }
+};
+
+QUnit.module('Props: template', buttonConfig);
+
+QUnit.test('should render button with default template', function(assert) {
+ const $element = $('#component');
+ $element.Button({ text: 'test', icon: 'check' });
+ const $contentElements = $element.find('.dx-button-content').children();
+
+ assert.strictEqual($element.Button('instance').option('template'), '', 'default template value');
+ assert.ok($contentElements.eq(0).hasClass('dx-icon'), 'render icon');
+ assert.ok($contentElements.eq(1).hasClass('dx-button-text'), 'render test');
+});
+
+QUnit.test('should pass correct container', function(assert) {
+ const $element = $('#component');
+
+ $element.Button({
+ template: function(data, container) {
+ assert.strictEqual(isRenderer(container), !!config().useJQuery, 'container is correct');
+ return $('
');
+ }
+ });
+});
+
+QUnit.test('should pass correct data', function(assert) {
+ const $element = $('#component');
+
+ $element.Button({
+ text: 'My button',
+ icon: 'test',
+ template: function(data, container) {
+ assert.strictEqual(data.text, 'My button', 'text is correct');
+ assert.strictEqual(data.icon, 'test', 'icon is correct');
+ const $template = $('
');
+ $template.text(`${data.text}123`);
+ return $template;
+ }
+ });
+
+ assert.strictEqual($element.text(), 'My button123', 'render correct text');
+});
+
+QUnit.test('should render jQuery', function(assert) {
+ const $element = $('#component');
+
+ $element.Button({
+ template: (data, container) => $('
'),
+ });
+ assert.strictEqual($element.find('.dx-button-content').length, 1, 'render content');
+ assert.strictEqual($element.find('#custom-template').length, 1, 'render custom template');
+});
+
+QUnit.test('should render dom node', function(assert) {
+ const $element = $('#component');
+
+ $element.Button({
+ template: (data, container) => $('
').get(0),
+ });
+ assert.strictEqual($element.find('.dx-button-content').length, 1, 'render content');
+ assert.strictEqual($element.find('#custom-template').length, 1, 'render custom template');
+});
+
+QUnit.test('should replace content if has "dx-template-wrapper" class', function(assert) {
+ const $element = $('#component');
+
+ $element.Button({
+ template: (data, container) => {
+ const $element = $('
')
+ .addClass('dx-template-wrapper');
+
+ return $element.get(0);
+ },
+ });
+ assert.ok($element.find('.dx-button-content').hasClass('dx-template-wrapper'), 'template has "dx-button-content" class');
+});
+
+QUnit.test('should rerender template in runtime', function(assert) {
+ const template = (data, container) => $('');
+ const templateNew = (data, container) => $('
');
+ const $element = $('#component');
+
+ $element.Button({ template: template });
+ assert.strictEqual($element.find('#custom-template').length, 1, 'render custom template');
+
+ $element.Button('instance').option('template', templateNew);
+ assert.strictEqual($element.find('#custom-template').length, 0, 'not render old template');
+ assert.strictEqual($element.find('#new-template').length, 1, 'render new template');
+});
+
+QUnit.test('should render submit input with custom template', function(assert) {
+ const $element = $('#component');
+
+ $element.Button({
+ useSubmitBehavior: true,
+ template: (data, container) => $(''),
+ });
+
+ assert.strictEqual($element.find('.dx-button-submit-input').length, 1, 'render submit input');
+});