In our codebase we use handlebars out of an API so people can change the content of the handlebars. We also use these handlebars to change the text of meta
-tags, because the texts are depending on the user of the platform. For example we want to say Robert Jackson's page with his 5 products
. The handlebars in the API will return something like {{user.name}}'s page with his {{products.length}} products
(simplified for now).
How so solve this type of problem in Ember?
What I created now is a big helper function which worked in Ember.js 3.0.0 but does not anymore in Ember 3.1.3.
import { getOwner, setOwner } from '@ember/application';
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import Helper from '@ember/component/helper';
import Ember from 'ember';
const { HTMLBars: { compile } } = Ember;
// Here we store the keys with the corresponding content-element output
const stored = {};
// In Ember you can only render templates in a real component, so we do that here
export default Helper.extend({
store: service(),
createDomElement(key, format, template, properties) {
// The following logic create a real DOM element
const owner = getOwner(this);
const component = Component.create({
classNames: 'hide',
layout: compile(template),
renderer: owner.lookup('renderer:-dom')
});
setOwner(component, owner);
component.setProperties(properties);
// We wait for the component to render and than grab the innerHTML from it
component.on('didRender', () => {
// We add the result in the global `stored` object
const html = component.element.innerHTML;
stored[key] = html.replace(/\s\s+/g, ' ');
component.destroy();
// Ember helpers don't support Promises, so we recompute the helper which
// will detect the element in the global `stored` object and returns that one.
this.recompute();
});
// Here we append it to the body
component.append();
},
// The compute function is the function Ember runs automatically
compute(params, hash) {
const key = hash.key;
const format = hash.format;
const offer = hash.offer;
if (!offer) return;
// We check if the key is already stored
const storedKey = stored[key];
if (storedKey) {
stored[key] = null
return storedKey;
}
// Here we get our template (content-element) with a certain key
const template = this.store.peekAll('content-element').filterBy('key', key).get('firstObject.value');
if (!template) return;
this.createDomElement(key, format, template, {
offer,
product: offer.get('product'),
project: offer.get('project')
});
}
});
We want to use this as a attribute of a link:
<a class="button" href={{social-url format=facebookUrl key='share_message.facebook' offer=offer}} {{action 'sendEvent' preventDefault=false}}>{{t 'share_facebook.open'}}</a>
And we want to use this in the body of our page:
{{{social-url
key='meta.title'
project=offer.product.project
product=offer.product
offer=offer}}}
And in our meta tags:
<meta name="twitter:description" content={{social-url
key='meta.description'
project=model.project
product=model.offer.product
offer=model.offer}}>
There error I get when running our current code in Ember.js 3.1.3:
Uncaught TypeError: Cannot read property 'fullName' of undefined
at new RootComponentDefinition (vendor.js:44666)
at InteractiveRenderer.appendTo (vendor.js:44883)
at Class.exports.default._emberMetal.Mixin.create._Mixin$create.appendTo (vendor.js:70569)
at Class.exports.default._emberMetal.Mixin.create._Mixin$create.append (vendor.js:70573)
at Class.createDomElement (frontend.js:3976)
at Class.compute (frontend.js:4023)
at ClassBasedHelperReference.compute (vendor.js:40727)
at ClassBasedHelperReference.value (vendor.js:40382)
at Object.evaluate (vendor.js:29592)
at AppendOpcodes.evaluate (vendor.js:28822)
RootComponentDefinition @ vendor.js:44666
appendTo @ vendor.js:44883
exports.default._emberMetal.Mixin.create._Mixin$create.appendTo @ vendor.js:70569
exports.default._emberMetal.Mixin.create._Mixin$create.append @ vendor.js:70573
createDomElement @ frontend.js:3976
compute @ frontend.js:4023
compute @ vendor.js:40727
value @ vendor.js:40382
(anonymous) @ vendor.js:29592
...