Dynamic Content
Sometimes you want to work with dynamic content to change it in your CMS, and it is automatically updated through the whole site. One example is related articles after the content of your post.
This guide will implement the Setka Editor's custom component, which can help you achieve dynamic content behavior within the Setka Editor post.
The main issues with the dynamic content is that:
- Setka Editor does not know how to receive and transform your data to HTML;
- Setka Editor operates with HTML, which is not dynamic by design.
Shortcodes Approach
We recommend the "shortcodes" approach to address these issues, which is a common way to handle dynamic content by various CMS-s. You can do it in two steps:
- A specific string is placed in the static HTML during editing. For example:
[shortcode]
or{{widget}}
; - When outputting HTML to the client, the CMS replaces these shortcodes with dynamic content. Dynamic content is inserted server-side, so this way works well with SEO.
To implement the "shortcodes" approach, you need:
- Create a custom Content Element component, which contains info about the dynamic content;
- Create a custom Tool, which inserts and configures a Content Element;
- Configure your CMS to handle dynamic content;
- [Optional] Configure Setka Editor's in editing and preview modes to show dynamic content.
Let's implement an example with the latest articles after the post:
Implement Custom Content Element with dynamic content
// Replaces default jsx parser to our own createElement function
/** @jsx SetkaEditor.lib.React.createElement */
const RelatedArticles = {};
// Extending from Setka Editor React component
RelatedArticles.edit = class RelatedArticles extends SetkaEditor.lib.React.Component {
render() {
const { element } = this.props;
return <div {...element.attributes}>{`{{article ${element.attributes['data-id']}}}`}</div>;
}
};
RelatedArticles.view = ({ element }) => (
<div {...element.attributes}>{`{{article ${element.attributes['data-id']}}`}</div>
);
SetkaEditor.registerElement({
type: 'RelatedArticles',
typeName: {
en: (n = 1) => (n > 1 ? 'Related Articles' : 'Related Articles'),
},
component: {
edit: RelatedArticles.edit,
view: RelatedArticles.view,
},
options: {
basic: [],
advanced: [],
},
mapStateToProps: (state, props) => ({
element: SetkaEditor.selectors.getElementById(state, props.id),
}),
});
Implement Custom Tool with dynamic content
The next step is to create a Tool component, which will select the necessary id for the article.
const RelatedArticlesToolElement = ({ selectedElement, isDisabled, onClick }) => (
<SetkaEditor.UI.Button kind="icon" disabled={isDisabled} onClick={() => onClick(selectedElement)}>
📃
</SetkaEditor.UI.Button>
);
const mapStateToProps = (state) => ({
selectedElement: SetkaEditor.selectors.getSelectedElements(state)[0],
isDisabled: !SetkaEditor.selectors.selectedElementsCan(state, 'insertAfter'),
});
const mapDispatchToProps = (dispatch) => ({
onClick: (selectedElement) => {
dispatch(
SetkaEditor.actions.insertElement({
// In this example random post id is used.
// You must use your post id, retrieved from your CMS
element: new SetkaEditor.Classes.RelatedArticles({
attributes: { 'data-id': Math.floor(Math.random() * 1000) },
}),
after: selectedElement.id,
}),
);
},
});
const RelatedArticlesTool = SetkaEditor.lib.ReactRedux.connect(
mapStateToProps,
mapDispatchToProps,
)(RelatedArticlesToolElement);
SetkaEditor.registerTool({ component: RelatedArticlesTool });
After the steps above you can find LatestArticleTool
in the right tool panel:
When the tool is clicked the shortcut is placed inside the Setka Editor's post:
Configure CMS to replace shortcuts
After implementing the custom dynamic content component and tool, the work with Setka Editor is finished. Now you need to implement a replacing mechanism inside your CMS. For example, it can be like this:
- Find all shortcuts in the post w/ regexp;
- Retrieve ids of each shortcut;
- Select the related articles from database;
- Replace the shortcut with HTML and data from database:
{{article 961}}
-><section><article>Article 1</article><article>Article 2</article></section>
etc.
[Optional] Add styles to Setka Editor's edit and preview modes
Let's add articles preview implementation to the previous code and put it all together so you can run the demo locally.
Implement Custom Content Element with dynamic content
First, mock API endpoint to get article data from.
// widget-mocked-api.js
// Mock real API with the cute Pokeapi one
window.WidgetAPI = {
random: function() {
const pokemonCount = 898;
return Math.floor(Math.random() * pokemonCount) + 1;
},
render: async function (id) {
const response = await fetch('https://pokeapi.co/api/v2/pokemon-form/' + id);
const pokemon = await response.json();
const url = pokemon.sprites.front_default;
const name = pokemon.name;
const title = 'Meet ' + name.charAt(0).toUpperCase() + name.slice(1) + ', the Pokémon of the day!'
return `<img src='${url}' alt='${name}'/><span>${title}</span>`;
}
}
Add preview implementation to the previously written code
// setka-custom-components.js
// Replaces default jsx parser to our own createElement function
/** @jsx SetkaEditor.lib.React.createElement */
// Prepare some data for the further preview implementation
const APIEndpoint = window.WidgetAPI;
const loader = '<span class="loader">Fetching...</span>';
// 1. Custom Content Element with dynamic content
const RelatedArticles = {};
RelatedArticles.edit = class RelatedArticles extends SetkaEditor.lib.React.Component {
constructor(props) {
super(props);
this.state = {
widget: loader // Initial widget content
};
}
render() {
const { element } = this.props;
const attributes = {
...element.attributes,
className: SetkaEditor.lib.cx(element.attributes.className, 'widget-related-articles'),
dangerouslySetInnerHTML: { __html: this.state.widget },
};
return <div {...attributes}/>;
}
// Trigger actual data rendering after inserting component
componentDidMount() {
this.fetchWidgetContent();
}
// Get articles data from API
fetchWidgetContent() {
const { element } = this.props;
APIEndpoint.render(element.attributes['data-id']).then((result) => {
this.setState({...this.state, widget: result});
});
}
};
RelatedArticles.view = ({ element }) => (
<div {...element.attributes}>{`{{article ${element.attributes['data-id']}}`}</div>
);
SetkaEditor.registerElement({
type: 'RelatedArticles',
typeName: {
en: (n = 1) => (n > 1 ? 'Related Articles' : 'Related Articles'),
},
component: {
edit: RelatedArticles.edit,
view: RelatedArticles.view,
},
options: {
basic: [],
advanced: [],
},
mapStateToProps: (state, props) => ({
element: SetkaEditor.selectors.getElementById(state, props.id),
}),
});
// 2. Implement Custom Tool with dynamic content
const RelatedArticlesToolElement = ({ selectedElement, isDisabled, onClick }) => (
<SetkaEditor.UI.Button kind="icon" disabled={isDisabled} onClick={() => onClick(selectedElement)}>
📃
</SetkaEditor.UI.Button>
);
const mapStateToProps = (state) => ({
selectedElement: SetkaEditor.selectors.getSelectedElements(state)[0],
isDisabled: !SetkaEditor.selectors.selectedElementsCan(state, 'insertAfter'),
});
const mapDispatchToProps = (dispatch) => ({
onClick: (selectedElement) => {
dispatch(
SetkaEditor.actions.insertElement({
element: new SetkaEditor.Classes.RelatedArticles({
attributes: { 'data-id': APIEndpoint.random() },
}),
after: selectedElement.id,
}),
);
// Replace empty paragraph with the inserted widget
if (selectedElement.type === 'Paragraph' && !selectedElement.content) {
SetkaEditor.editor.actions.removeElement({
id: selectedElement.id,
selectNext: false,
});
}
},
});
const RelatedArticlesTool = SetkaEditor.lib.ReactRedux.connect(
mapStateToProps,
mapDispatchToProps,
)(RelatedArticlesToolElement);
SetkaEditor.registerTool({ component: RelatedArticlesTool });
Implement widget preview renderer
Implement a function that will render articles widget in Setka Editor preview. This file (and mocked API connector) should be passed to Setka on the initialization.
// widget-preview.js
// Widget render function
const renderWidgets = function () {
const widgetType = 'relatedarticles';
const APIEndpoint = window.WidgetAPI;
document.querySelectorAll(`[data-ce-tag=${widgetType}]`).forEach(function (widget) {
const widgetId = widget.dataset.id;
APIEndpoint.render(widgetId).then(widgetContent => {
widget.innerHTML = widgetContent;
});
});
};
// Create an mutation observer instance with the callback function
const observer = new MutationObserver(function() {
renderWidgets();
});
// Start observing the preview document for configured mutations
observer.observe(document.querySelector('body'), { childList: true });
Adding styles
/* widget.css */
[data-ce-tag="relatedarticles"]:not(#stk):not(#stk) {
display: flex;
align-items: center;
justify-content: start;
background: linear-gradient(45deg, lightblue, pink);
font-family: monospace;
padding: 1em 2em;
font-size: 1.2rem;
}
[data-ce-tag="relatedarticles"]:not(#stk):not(#stk) .loader {
font-size: 1rem;
opacity: .5;
}
Putting it all together
Replace links and public key with your actual data. See Setka Post Editor initialization Guide for details.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<!-- 1. Main Editor's JS file (REPLACE WITH YOUR URL) -->
<script src="https://ceditor.setka.io/path/to/editor.v.1.2.3.min.js"></script>
<!-- 2. Editor's interface styles (REPLACE WITH YOUR URL) -->
<link rel="stylesheet" href="https://ceditor.setka.io/path/to/editor.v.1.2.3.min.css">
<!-- 3. Your company's styles (REPLACE WITH YOUR URL) -->
<link rel="stylesheet" href="https://ceditor.setka.io/path/to/company_name.v.1.2.3.min.css">
<!-- 4. Mock widgets API -->
<script src="widget-mocked-api.js"></script>
<!-- 5. Widget styles -->
<link rel="stylesheet" href="widget.css">
<!-- 6. Babel to parse JSX in browser (for demonstration purposes only, not recommended for production) -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<!-- 7. Register custom components -->
<script src="setka-custom-components.js" type="text/babel"></script>
</head>
<body>
<!-- 8. Container for Setka Editor to render into -->
<div class="stk-editor" id="setka-editor"></div>
<!-- 9. Init Setka -->
<script>
// Loading your JSON file with meta information (REPLACE WITH YOUR URL)
fetch('https://ceditor.setka.io/path/to/company_name.v.1.2.3.json')
.then(response => response.json())
.then(response => {
const config = response.config;
const assets = response.assets;
// Manually passing public_token to config (REPLACE WITH YOUR URL)
config.public_token = 'my_public_token_123456';
config.token = 'my_public_token_123456';
// Custom component preview
config.previewJS = [
'widget-mocked-api.js',
'widget-preview.js'
];
config.previewCSS = [
'widget.css'
];
// Start Setka Editor
SetkaEditor.start(config, assets);
}).catch(ex => console.error(ex)); // Handle initialization errors
</script>
</body>
</html>