
This guide explains how to render dynamic content in Magento Page Builder using Blocks or Widgets without
relying on the native "HTML Code" content type.
This guide is based on a custom content type generated
usingcommerce-docs/pbmodules.
Requirements
- A pre-configured Custom Page Builder or anew installation.
- Block Class and .phtml template file
- (Optional) A
widget.xml
file if you choose to use the widget setup. If using a block-based implementation, thewidget.xml
file is not required.
this tutorial as created based on the custom content type that was generated by
thehttps://github.com/commerce-docs/pbmodules
1. Update Dependency Injection (DI) Configuration
Create or update your module'setc/di.xml
, add the following code.
<typename="Magento\PageBuilder\Model\Stage\RendererPool"><arguments><argumentname="renderers"xsi:type="array"><itemname="CONTENT_TYPE_NAME"xsi:type="object"> Magento\PageBuilder\Model\Stage\Renderer\WidgetDirective</item></argument></arguments></type>
Note: Replace
CONTENT_TYPE_NAME
with your content type name.
2. Content Type XML Configuration
Create or update the file:view/adminhtml/pagebuilder/content_type/CONTENT_TYPE_NAME.xml
Within the section:
Add an HTML attribute (inside or any other element as needed):<htmlname="html"preview_converter="Magento_PageBuilder/js/converter/attribute/preview/store-id"/>
After the
</elements>
:
Add the converters configuration.<converters><convertercomponent="Vendor_Module/js/content-type/CONTENT_TYPE_FOLDER/mass-converter/widget-directive"name="widget_directive"><config><itemname="html_variable"value="html"/></config></converter></converters>
Note: Replace
CONTENT_TYPE_FOLDER
with your content type folder name.
3. Create the Mass Converter JavaScript
Create the file:view/adminhtml/web/js/content-type/CONTENT_TYPE_FOLDER/mass-converter/widget-directive.js
Add the following code:
Note: Replace
CONTENT_TYPE_FOLDER
with your content type folder name.
define(['Magento_PageBuilder/js/mass-converter/widget-directive-abstract','Magento_PageBuilder/js/utils/object'],function(widgetDirective,dataObject){'use strict';classWidgetDirectiveextendswidgetDirective{/** * Convert value to internal format * * @param {object} data * @param {object} config * @returns {object} */fromDom(data,config){varattributes=super.fromDom(data,config);returndata;}toDom(data,config){constattributes={type:'Devgfnl\\WidgetBlockPageBuilder\\Block\\Info',template:'Devgfnl_WidgetBlockPageBuilder::info.phtml',type_name:'Widget/Block with PageBuilder',my_field:data.my_field// ... other attributes to be passed to the block/widget};dataObject.set(data,config.html_variable,this.buildDirective(attributes));returndata;}}returnWidgetDirective;});
Using a Block Instead of a Widget
If you are not using the widget setup, modify the toDom function as follows:
toDom(data, config) { const attributes = {- type: 'Devgfnl\\WidgetBlockPageBuilder\\Block\\Info',+ class: 'Devgfnl\\WidgetBlockPageBuilder\\Block\\Info', template: 'Devgfnl_WidgetBlockPageBuilder::info.phtml', type_name: 'Widget/Block with PageBuilder', my_field: data.my_field };- dataObject.set(data, config.html_variable, this.buildDirective(attributes));+ dataObject.set(data, config.html_variable, this.buildBlockDirective(attributes)); return data; }+ buildBlockDirective(attributes) {+ return '{{block ' + this.createAttributesString(attributes) + '}}';+ }
Note: When using a widget, ensure that you have the proper widget.xml setup; otherwise, the Page Builder will not
render the PHTML content. The block-based approach is recommended if you want to avoid creating an extra widget.xml
file.
4. Update the Preview JavaScript
Create or update the fileview/adminhtml/web/js/content-type/CONTENT_TYPE_FOLDER/preview.js
file, update it with the
following code.
Note: Replace
CONTENT_TYPE_FOLDER
with your content type folder name.
define(['jquery','mage/translate','knockout','underscore','Magento_PageBuilder/js/config','Magento_PageBuilder/js/content-type/preview'],function($,$t,ko,_,Config,PreviewBase){'use strict';var$super;/** * Quote content type preview class * * @param parent * @param config * @param stageId * @constructor */functionPreview(parent,config,stageId){PreviewBase.call(this,parent,config,stageId);this.displayPreview=ko.observable(false);this.previewElement=$.Deferred();this.loading=ko.observable(false);this.widgetUnsanitizedHtml=ko.observable();this.element=null;this.messages={EMPTY:$t('Empty...'),NO_RESULTS:$t('No result were found.'),LOADING:$t('Loading...'),UNKNOWN_ERROR:$t('An unknown error occurred. Please try again.')};this.placeholderText=ko.observable(this.messages.EMPTY);}Preview.prototype=Object.create(PreviewBase.prototype);$super=PreviewBase.prototype;/** * Modify the options returned by the content type * * @returns {*} */Preview.prototype.retrieveOptions=function(){varoptions=$super.retrieveOptions.call(this,arguments);// Customize options herereturnoptions;};/** * On afterRender callback. * * @param {Element} element */Preview.prototype.onAfterRender=function(element){this.element=element;this.previewElement.resolve(element);};/** * @inheritdoc */Preview.prototype.afterObservablesUpdated=function(){$super.afterObservablesUpdated.call(this);constdata=this.contentType.dataStore.getState();if(this.hasDataChanged(this.previousData,data)){this.displayPreview(false);if(!this.shouldDisplay(data)){this.placeholderText(this.messages.EMPTY);return;}consturl=Config.getConfig('preview_url'),requestConfig={// Prevent cachingmethod:'POST',data:{role:this.config.name,directive:this.data.main.html()}};this.placeholderText(this.messages.LOADING);$.ajax(url,requestConfig).done((response)=>{if(typeofresponse.data!=='object'||!response.data.content){this.placeholderText(this.messages.NO_RESULTS);return;}if(response.data.error){this.widgetUnsanitizedHtml(response.data.error);}else{this.widgetUnsanitizedHtml(response.data.content);this.displayPreview(true);}this.previewElement.done(()=>{$(this.element).trigger('contentUpdated');});}).fail(()=>{this.placeholderText(this.messages.UNKNOWN_ERROR);});}this.previousData=Object.assign({},data);};/** * Determine if the preview should be displayed * * @param data * @returns {boolean} */Preview.prototype.shouldDisplay=function(data){constmyField=data.my_field;return!!myField;};/** * Determine if the data has changed, whilst ignoring certain keys which don't require a rebuild * * @param {object} previousData * @param {object} newData * @returns {boolean} */Preview.prototype.hasDataChanged=function(previousData,newData){previousData=_.omit(previousData,this.ignoredKeysForBuild);newData=_.omit(newData,this.ignoredKeysForBuild);return!_.isEqual(previousData,newData);};returnPreview;});
5. Create the Preview Template
Create or update the file preview template file:view/adminhtml/web/template/content-type/CONTENT_TYPE_FOLDER/default/preview.html
, add
the following code:
Note: Replace
CONTENT_TYPE_FOLDER
with your content type folder name.
<divclass="pagebuilder-content-type"attr="data.main.attributes"ko-style="data.main.style"css="data.main.css"event="{ mouseover: onMouseOver, mouseout: onMouseOut }, mouseoverBubble: false"><divclass="my-class"data-bind="liveEdit: { field: 'my_field', placeholder: $t('Your custom content type!') }"></div><divif="displayPreview"class="rendered-content"html="widgetUnsanitizedHtml"afterRender="onAfterRender"></div><divifnot="displayPreview"class="pagebuilder-products-placeholder"><spanclass="placeholder-text"text="placeholderText"></span></div><renderargs="getOptions().template"></render></div>
6. Create the Master Template
Create or update the file master template file:view/adminhtml/web/template/content-type/CONTENT_TYPE_FOLDER/default/master.html
, add the
following code:
Note: Replace
CONTENT_TYPE_FOLDER
with your content type folder name.
<divhtml="data.main.html"attr="data.main.attributes"css="data.main.css"ko-style="data.main.style"></div>
Results
Observations
- KnockoutJS Rendering: In tests, a PHTML file incorporating KnockoutJS rendered correctly on the frontend. However,KnockoutJS may not render as expected in the admin area.
- Widget vs. Block: If you use the widget setup, ensure that the corresponding
widget.xml
is configured properly;otherwise, the PHTML content may not be rendered. The block-based approach is recommended for simplicity, as it doesnot require an extra XML configuration file. - BlockInterface: If you use the block and widget approach, ensure that the Block class implements the
BlockInterface
to avoid any issues with the Page Builder rendering the content.
Known Issues & Solutions
- Issue: The PHTML content is not rendered in the admin area.
- Solution:
- If you are using the widget setup, ensure that the
widget.xml
file is configured properly. - Ensure that the Block class implements the
BlockInterface
.
- If you are using the widget setup, ensure that the
- Solution:
Code Reference
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse