Render a component from a dynamic variable with Handlebars - javascript

I'd like to render a component that I will define within a variable.
I have a variable containing content like that:
page.js
Ember.Controller.extend({
c: {
'pageTitle': 'This is the title with a component {{my-component}}'
}
});
page.hbs
<div>
{{c.pageTitle}}
</div>
The c object is populated from an API call, from a content server.
I would like to provide the capability to inject components from what is defined in the content.
Basically what I need would be to render 2 times, the first time to replace my {{pageTitle}} with the string, and the second time to replace {{my-component}} with the component.
What would be the best solution to do such a thing?
Thanks

You could render the component using the new {{component}} helper (in the latest version of Ember)
{{component 'my-component'}}
{{component c.myPageTitleComponent}}
For the text to be present, you could do two things:
In your Template
You could implement the text directly in your template
{{c.myPageTitleText}} {{component c.myPageTitleComponent}}
Using a Parent Component
You could implement a parent component:
{{my-parent-component text=c.myPageTitleText componentName=c.myPageTitleComponent}}
Then in 'my-parent-component', it would look like this:
{{text}} {{component componentName}}
This doesn't really make sense though unless you needed some custom logic in my-parent-component.

Related

Ember passing an action closure through an outlet

I am building a simple Ember app, but I have run into difficulty passing an action closure to a child component when that component is rendered in the {{outlet}} of a navigable container.
For context, here is a quick look at the aesthetically-astonishing app I have been building:
I have a roles/role path that displays a component (the yellow section above) with the following markup. Note that the model for this component is an instance of a Role:
// file: app/components/role.hbs
<p>{{#role.name}}</p>
<div>
{{sel-nav-tabs items=this.tabConfig}}
<div class='route-content'>{{outlet}}</div>
</div>
(Where "sel" stands for "someone else's library".)
this.tabConfig is defines in the corresponding class:
// file: app/components.role.js
import Component from '#glimmer/component';
export default class RoleComponent extends Component {
get tabConfig() {
return [
{ label: 'Users', route: 'roles.role.users' },
{ label: 'Privileges', route: 'roles.role.privileges' },
];
}
}
Into the outlet in role.hbs will be rendered the appropriate list component, either users or privileges.
The users list is rendered by the following component. Note that the model is the list of User instances associated with the Role from its parent:
// file: app/components/role/user-list.hbs
<ul>
{{#each #users as |user|}}
<li>
{{user.name}}
{{#sel-button type="toolbar" onActivate=this.removeUser}}
{{sel-icon/remove-circle}}
{{/sel-button}}
</li>
{{/each}}
</ul>
and when the button is clicked it calls an action defined in the RoleUserListComponent class:
// file: app/components/role/user-list.js
import Component from '#glimmer/component';
import { action } from "#ember/object";
export default class RoleUserListComponent extends Component {
#action removeUser(user) {
// remove the user model from the role... but which role?
}
}
The catch is that the relationship between users and roles is many-to-many, so I can't simply unset the user's owner and let Ember Data take care of things. The obvious answer seemed like passing an action closure from the role component to its child user-list component.
Except, there seems to be no way to pass the action closure through the {{outlet}}. What I was hoping for was something like:
{{outlet onActivate=(action removeUser #role)}}
which would pass the closure to any component that was rendered there. I tried instead to use {{yield user}} in the child to let the parent render the delete button and give it the appropriate action, but that also hit the outlet wall.
I also tried to use controllers, which aren't documented that well, probably since their role seems to have been evolving dramatically over Ember's maturation. But while this brief explanation does mention passing down actions, it doesn't go into details, and the few up-to-date examples I found all seem to break when an outlet joins the party.
I'm suspecting that {{outlet}} just plain isn't closure-friendly.
While defining a service would probably work, that doesn't seem to be what services are intended for, and I'd be cluttering up my global space to solve a local problem.
What is the best practice (or, really, any practice) for dealing with getting messages through outlets? I looked for ways to query the earlier parts of the path, but I didn't find any that were defined in the relevant classes.
EDIT to add more detail:
The route template for /roles/role is simply:
// file app/templates/roles/role
{{role role=#model}}
Where the Role component is in the first listing above. (I also added the role.js file contents above.) My reasoning for doing that was that by making a component I created a logical place to put the config (rather than inline helper functions) and it just gave me a sense of tidiness to have all ui elements be in components.
If a refactor can be the anchor to a good solution (essentially copying the entire Role component into the route template), however, I'll happily do it.
{{outlet}} only supports one optional string argument for a named outlet and nothing else, so you won't be able to achieve this through the use of {{outlet}}!

[Ember.js]Pass template helpers created by template helpers as a attribute to a component in order to use it in the components template?

I am using paper-data-table which is a extension to ember-paper.
Both use a technique I did not see before which I would describe as "template helpers create template helpers".
Here is a simple example of the ember-paper toolbar component
{{#paper-toolbar as |toolbar|}}
{{#toolbar.tools}}
{{#paper-button}}
Go Back
{{/paper-button}}
<h2>Toolbar with Standard Buttons</h2>
<span class="flex"></span>
{{#paper-button raised=true}}
Learn More
{{/paper-button}}
{{#paper-button mini=true aria-label="Favorite"}}
{{paper-icon "favorite"}}
{{/paper-button}}
{{/toolbar.tools}}
{{/paper-toolbar}}
There is a new template helper created {{#paper-toolbar as |toolbar|}}.
In my use-case I want to pass the row template helper which is created by the paper-data-table template helper(/component?) down to another component to encapsulate the logic inside it.
I tried to pass it down as a argument:
{{#paper-data-table
sortProp='sort'
sortDir='asc'
as |table|
}}
{{#table.body as |body|}}
{{#each questions as |question index|}}
{{question-row
row=body.row
}}
{{/each}}
{{/table.body}}
{{/paper-data-table}}
But when trying to use the helper(/component) in the template of the question-row component
{{#row as |row|}}{{/row}}
I get the following error:
Assertion Failed: A component or helper named "row" could not be found Error
So I wanted to ask if thats possible and how that would work.
This method is called contextual components and I was able to solve it with the following code in my question-row component:
{{#component row as |row|}}
{{#row.cell}}
HALLO
{{/row.cell}}
{{/component}}

Ember template convention

I'm trying to understand EmberJS template convention in Discourse.
Here's a snippet from app/assets/javascripts/discourse/templates/discovery/categories.hbs
{{#discovery-categories refresh="refresh"}}
{{component controller.categoryPageStyle
categories=model.categories
latestTopicOnly=controller.latestTopicOnly
topics=model.topics}}
{{!-- my-template --}}
{{/discovery-categories}}
What is the meaning of discovery-categories and component?
For example I want to insert my-template to extend categories.hbs, what is the convention I should use to create file with my template?
discovery-categories is the name of the component which is
called statically using the name of the component.
Whereas in the second line 'component' is a template helper which loads the component dynamically using the name specified via property controller.categoryPageStyle.
3.my-template is the yield block , where you can have context of the component discovery-categories if its yield.
for eg. if discovery-categories has a property foo you can write something like
{{#discovery-categories refresh="refresh" foo="Some Text"}}
{{component controller.categoryPageStyle
categories=model.categories
latestTopicOnly=controller.latestTopicOnly
topics=model.topics}}
{{foo}}
{{/discovery-categories}}

Meteor.js how can I use templates with the same name, multiple times

How can I use the same template name multiple times across different files? I have the same template naming pattern for every page, and the problem is when for example <template name="defaultContent"></template> was already used on another page, it can't be used again.
example URLs:
/home
/home/first
/home/second
/home/third
homePage.html
/template: homePage
template: default
template: first
template: second
template: third
userPage.html
/template: userPage
template: default
template: first
template: second
template: third
Iron router code:
// mainpage
Router.route('/home', function () {
this.render('homePage');
this.render('default', {to: 'content'});
});
// subpages
Router.route('/home/:content', function () {
this.render('homePage');
var templateName = this.params.content;
if(Template[templateName]){
this.render(templateName, {to: 'content'});
};
});
[update] By the way, that's how Meteor kitchen solved this problem:
<template name="CoolPageSubPageBSubPageB1LoremIpsum">
You can't define multiple templates with the same name.
Meteor defines your templates in a global object, Template (for example, Template.homePage). An object can't have multiple times the same field, so defining multiple times a single template would lead to errors (possibly silent ones).
Plus, how could the router know which defaultContent you are talking about?
How could you?
Instead of shadowing templates, you could simply define multiple templates (Template.homeDefault, Template.userDefault) which would allow you to debug them and refer to them easily.
You need to wrap it in one parent template per page. You add then your template inside the parent template in its HTML using {{> defaultContent}} if the template you want to reuse is called "defaultContent".

Load knockoutjs component using javascript

What's the best way to load a ko component with JavaScript code instead of defining a custom element in html? I tried with ko.components.defaultLoader.load but my component constructor does not hit.
I double checked and the component appears to be registered.
I believe what you are looking for is function ko.components.get(componentName, callback). What this method does is ask the component loaders to resolve the component name until it finds one. If it doesn't find one, it will call callback(null). If it does fine one, it will call callback(componentDefinition), where componentDefinition is the object used to register the component, like { viewmodel: ..., template: ...}.
As far as I can tell, there isn't a ready made function which returns a "working" component. What you have to do after getting the componentDefinition object is something like:
convert the template into a DOM element
instantiate the viewmodel (if defined)
bind the viewmodel to the DOM element
Note that this is not straight away because templates and view models can be defined in several ways.
I recommend looking at https://github.com/knockout/knockout/blob/master/src/components/componentBinding.js and see how it's done here (from line 38).
I hope this works for you, otherwise you could consider other options, like dynamically creating a div element in code with a component binding where the component name and parameters are bound to properties of a view model. Then bind this view model to the div element you just created. This should work "code only" which much less code than the other route.

Categories

Resources