Dynamically compile a HTMLBars template at runtime in Ember - templates

I want to dynamically compile (and then render) a HTMLBars template at runtime, on the client in Ember. How can I do this?

This answer is now out of date. Please see #poohoka's answer which I've accepted above.
Building off of Kingpin2K's answer to Compile template client side in ember using HTMLbars:
For some background, it might be useful to refer back to Compiling Templates with Ember 1.10. We'll still need to load ember-template-compiler.js. Add
app.import('bower_components/ember/ember-template-compiler.js');
to your ember-cli-build.js.
Then you can write a Component like this:
import Ember from 'ember';
export default Ember.Component.extend({
layout: Ember.computed(function() {
return Ember.HTMLBars.compile(
'{{foo-bar}} <span>' + 'hello' + '</span>'
);
}),
});
This solution will likely break in future relases of Ember, depending on how the Ember Template compilation process changes with the advent of Glimmer 2.

Since Ember 2.10 is now using Glimmer, things might be a bit tricky here. In order to compile a template, you need to include ember-template-compiler.js to your application. I'd recommend using ember-browserify and ember-source.
In your controller, import the compiler as the following.
import Ember from 'ember';
import Compiler from 'npm:ember-source/dist/ember-template-compiler';
export default Ember.Controller.extend({
compileContent() {
const template = Compiler.compile(this.get('dynamicContent'));
Ember.TEMPLATES[`YOUR_TEMPLATE_NAME`] = template;
},
// we observe content changes here
contentDidUpdate: Ember.observer('dynamicContent', function() {
this.compileContent();
}),
});
As tested, your content can contain anything from Ember helpers to your custom components, even your action bindings.
e.g.
<ul>
<li>{{#link-to 'index'}}Home{{/link-to}}</li>
</ul>
<div {{action 'yourCustomAction'}}>
{{your-custom-component params=yourCustomParams model=model flag=true}}
</div>
Now, let's do the magic in your template by using {{partial}} helper.
...
{{partial 'YOUR_TEMPLATE_NAME'}}
...
This method works in Ember 2.13 without deprecation warnings, it should work in future updates. Please note that Ember.TEMPLATES is global variable and the engine seems to cache it somehow, so do not reassign new values to the existing one.

Since Ember 2.13+ (without bower by default) you need to add in your ember-cli-build.js:
app.import('vendor/ember/ember-template-compiler.js');
For Ember version prior to 2.10 you need to include it via bower (also on ember-cli-build.js)
app.import('bower_components/ember/ember-template-compiler.js');
And on the code you need to:
Ember.TEMPLATES['mycompiledcode'] = Ember.HTMLBars.compile('{{foo-bar}} <span>' + 'hello' + '</span>');
In the hbs file call:
{{partial 'mycompiledcode'}}
Or you can make a component like this:
import Ember from 'ember';
export default Ember.Component.extend({
layout: Ember.computed(function() {
return Ember.HTMLBars.compile(
'{{foo-bar}} <span>' + 'hello' + '</span>'
);
}),
});
Based on solution of another answer https://stackoverflow.com/a/37345099/6505594

I'm currently on Ember-2.9.x and I brought in the latest handlebars with my bower.json:
"handlebars": "^4.0.0"
And then added it via my ember-cli-build.js file:
app.import('bower_components/handlebars/handlebars.js');
This has worked for my typeahead component and I don't see any reason why this won't work when upgrading to Ember-2.10 with Glimmer2.

Related

Creating alias to a dynamic property now that Ember.Binding is deprecated

How can I add alias or observer to a property that's name I only know when the component is initialized?
I need this for a generic use component where the property name depends on what data was passed to the component. In previous Ember versions I could just create the binding on init:
binding = Ember.Binding.from("model.settings." + this.get('type')).to("setting");
binding.connect(this);
Then use the "setting" wherever it is needed and everything gets correctly updated when or if the property changes. The "type" property is passed to the component from the outside and is different for every instance of the component so I can't hard code the property name in the component itself.
Now Ember.Binding was deprecated in Ember 2.7 and will be removed in Ember 3.0.
I can't figure out how to achieve this without Ember.Binding. And no, there isn't a good way to pass the value from elsewhere or manage this without a binding as far as I can tell. The actual component is a bit more complicated than what I described above but the problem remains same.
You need to use defineProperty to create computed property for dynamic dependant key.. ember-twiddle
controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
actions:{
changeType(){
this.set('model.settings.admin','changed-adminsettings');
}
}
});
templates/application.hbs
{{my-component model=model type='admin' }}
<button {{action 'changeType'}}> ChangeType</button>
my-component.hbs
{{setting}}
{{yield}}
my-component.js
import Ember from 'ember';
export default Ember.Component.extend({
init(){
this._super(...arguments);
var type= this.get('type');
Ember.defineProperty(this,'setting',Ember.computed('model.settings.' + type,function(){
return this.get('model.settings.'+type);
}));
}
});
Reference: https://github.com/emberjs/ember.js/issues/13912

Adding component dynamically in Ember

using
Ember : 1.13.11,
Ember Data : 1.13.8,
ember-cli : 1.13.12
I want to add a component dynamically to webpage - this webpage is template of another component don't think that it will make any difference-. Here is my code snippet in which I try to add a component named LyricsEditorLine to <div> tag, somehow like this
agenda-alpha/components/lyrics-editor.js
import Ember from 'ember';
import LyricsEditorLine from 'agenda-alpha/components/lyrics-editor-line';
export default Ember.Component.extend({
afterRenderEvent:function(){
LyricsEditorLine.create().appendTo($("#in"));
},
init:function(){
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
this._super();
}
});
agenda-alpha/templates/components/lyrics-editor.hbs
<div id='in'> </div>
every time this gives me
'Uncaught Error: Assertion Failed: You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead'
Looked for ContainerViewhere found that it is deprecated
Most of the answers that I found are not using ember-cli and being a beginner makes it harder to understand
I want to be able to add components as much as the user needs
I think you probably want the {{component}} helper which allows to dynamically render a component.
{{component "componentName" param1=paramValue param2=anotherParamValue}}
Which means you can have (made up example)
{{component "lyrics-editor-line" line=line}}
One the best things is that componentName can be a bound property
{{component componentName line=line}}
And in your controller/component:
componentName: Ember.computed('prop1','prop2', function() {
if (this.get('prop1') === 'A') {
return 'my-component-a';
}
return 'default-component';
}),
line: computed('prop3', function() {
return this.get('prop2');
})
Also, you can have the component helper inside an each loop (example taken from the Ember documentation)
{{#each model as |post|}}
{{!-- either foo-component or bar-component --}}
{{component post.componentName post=post}}
{{/each}}

Ember-cli, Masonry, Isotope, Packery. How to use?

I need to use them in my ember-cli project.
How to start?
I writed in terminal:
bower install isotope --save
then in my ember-cli-build.js I added app.import ecc..., but then I don't know what to do.
Where to put my intialization script, like this:
$('.grid').isotope({
// options
itemSelector: '.grid-item',
layoutMode: 'fitRows'
});
If I put it in application.hbs it give to me an error and when i change route with {{#link-to}} it doesn't work anymore.
What to do?
In the web there aren't many resources about this.
You should create a component:
ember g component isotope-grid
Then, in component's didInsertElement hook you should call isotope on component's jQuery element:
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['grid'],
didInsertElement() {
this.$().isotope({
// options
itemSelector: '.grid-item',
layoutMode: 'fitRows'
});
}
})
Then, instead of using <div class="grid"></div>, use:
{{#isotope-grid}}
... HTML goes here
{{/isotope-grid}}

In Ember, why does my template want {{model.key}}, not just {{key}}?

I am trying out Ember, and finding a discrepancy with the docs. I used the Ember CLI to ember generate template index and ember generate route index. Then I set up a trivial model in index.js:
model: function () {
return {name: "Joe"};
}
From my reading of the docs and examples, I expected to be able to access this value simply with {{name}} in my index.hbs template, but instead I only get the value with {{model.name}}. Why?
Before Ember 1.11 you could use ObjectController, that works like a proxy to corresponding route model, and you could write {{name}} for model.name.
ObjectController was deprecated in Ember 1.11, details here:
http://emberjs.com/deprecations/v1.x/#toc_objectcontroller. So in last Ember versions you should use Controller class instead ObjectController, that doesn't work as proxy of model. You could think of it as of Ember Object with model property from corresponding route. So {{name}} means property of Controller, {{model.name}} - property of model.
For example:
//route
model: function () {
return {name: "Joe"};
}
//controller
import Ember from 'ember';
export default Ember.Controller.extend({
name: 'Marry'
});
//template
{{name}} //=> Marry
{{model.name}} //=> Joe
I think this might be a thing about explicitness but I'm not 100% sure - you can also have data sent to the template on a property other than model so it might be about allowing that to be more easily understood - model is a poor property name IMO anyway
You could use the with helper if the syntax is too verbose for you:
{{#with story}}
<div class="intro">{{{intro}}}</div>
<div class="body">{{{body}}}</div>
{{/with}}

How do I add classNames to the first element of the page without using a view?

I want to add a wrapper class to the first div element in my page. I used to do this with a view. So, it seems that Ember 2.0 won't support Views anymore. So how can I do that now?
view/application.js:
import Ember from 'ember';
export default Ember.View.extend({
classNames: ['wrapper'],
});
Resulting in the following page:
<body class="ember-application">
<div id="ember573" class="ember-view wrapper">
the rest of my page in this div
</div>
</body>
How is this done now that views are deprecated?
I used css to solve this problem:
body > .ember-view {
padding-left: 240px; //styles for container goes here
}
I don't have a neat solution, but subclassing Ember.Component from inside applications/view.js works.
https://ember-twiddle.com/b15411266f996191605c
Like the others said, the only way to add a class using Ember 2.0 is to use a component on your page. The component has the same properties that the view had. Your page will have a component-only call in the template, like the following:
your-page.hbs
{{your-page-component}}
If you really don't want to have a component on your page, my advice to you would be to add manually a class name in your template:
your-page.hbs
<div class="your-page">
{{outlet}}
</div>
Views are deprecated in ember 2.0. The way to do things from now on is using component and route. You can specify which class name is applied to your component by doing:
export default Ember.Component.extend({
/* Wrap your component in primary class*/
classNames: ['primary'],
/*defined class binding*/
classNameBindings: ['isUrgent'],
isUrgent: true
});
All information regarding on how to customize your component can be found in the ember documentation(click here to find out more)
You're not supposed to use Views now since they're deprecated. Use components instead, example:
import Ember from 'ember';
export default Ember.Component.extend({
classNameBindings: ['functionName'],
functionName: Ember.computed(function() {
// function logic
})
});