Binding to strings containing custom components in Ember.js - javascript

I'm trying to display a string, pulled from my model, that contains ember custom components. They don't seem to get compiled though -- see (1) and (2) in the output. If I replace the custom components with standard html elements and use the {{{-}}} syntax for binding, things look right (see (3) and (4) in the output), but this is not sufficient for the application I have in mind, though. How can I get ember to compile the custom components before displaying them?
app.js:
App = Ember.Application.create();
var g1 = "{{#my-bold}}Yo{{/my-bold}}, {{#my-italic}}dude{{/my-italic}}!";
var g2 = "<b>Yo</b>, <i>dude</i>!";
App.IndexRoute = Ember.Route.extend({
model: function() {
return {greeting1: g1, greeting2: g2}
}
});
App.MyBoldComponent = Ember.Component.extend({tagName: "span"});
App.MyItalicComponent = Ember.Component.extend({tagName: "span"});
index.html
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" id="components/my-bold"><b>{{yield}}</b></script>
<script type="text/x-handlebars" id="components/my-italic"><i>{{yield}}</i></script>
<script type="text/x-handlebars" id="index">
<ol>
<li>{{model.greeting1}}</li>
<li>{{{model.greeting1}}}</li>
<li>{{{model.greeting2}}}</li>
<li>{{#my-bold}}Yo,{{/my-bold}} {{#my-italic}}dude!{{/my-italic}}</li>
</ol>
</script>
output:
{{#my-bold}}Yo{{/my-bold}}, {{#my-italic}}dude{{/my-italic}}!
{{#my-bold}}Yo{{/my-bold}}, {{#my-italic}}dude{{/my-italic}}!
Yo, dude!
Yo, dude!

From the Ember.js issue tracker https://github.com/emberjs/ember.js/issues/11649 on dynamically inserting components:
This isn't something we support, I also suspect we won't as it would require all of the htmlbars compiler client side and would likely be pretty slow. If you wish to add components dynamically. The component helper may be your best bet.
On that issue they are proposing to use the {{component}} helper, but that doesn't work with your code since you actually want to produce two component.
They are also talking about the RFC for contextual components: https://github.com/emberjs/rfcs/pull/64#issuecomment-111761176 which depending on their implementation would work similar to what you're doing. So what can you do?
As far as I can tell it's simply not possible to do with 1.13 (I tried quite a number of things) because rerender doesn't work, but they are going to fix it. If you downgrade to 1.12 you can do:
App.RenderTemplateComponent = Ember.Component.extend({
layout: function(){
return Ember.Handlebars.compile(this.get('templateString'));
}.property('templateString')
});
Then in your template something like:
{{render-template templateString="test {{x-foo}} {{x-foo}}"}}
Take a look at this JSFiddle:
http://emberjs.jsbin.com/jazayiyufi/1/edit?html,css,js,output

Related

I have some questions about Sapper/Svelte

I just started using Sapper (https://sapper.svelte.technology) for the first time. I really like it so far. One of the things I need it to do is show a list of the components available in my application and show information about them. Ideally have a way to change the way the component looks based on dynamic bindings on the page.
I have a few questions about using the framework.
First, I'll provide a snippet of my code, and then a screenshot:
[slug].html
-----------
<:Head>
<title>{{info.title}}</title>
</:Head>
<Layout page="{{slug}}">
<h1>{{info.title}}</h1>
<div class="content">
<TopBar :organization_name />
<br>
<h3>Attributes</h3>
{{#each Object.keys(info.attributes) as attribute}}
<p>{{info.attributes[attribute].description}} <input type="text" on:keyup="updateComponent(this.value)" value="Org Name" /></p>
{{/each}}
</div>
</Layout>
<script>
import Layout from '../_components/components/Layout.html';
import TopBar from '../../_components/header/TopBar.html';
let COMPONENTS = require('../_config/components.json');
export default {
components: {
Layout, TopBar
},
methods: {
updateComponent(value) {
this.set({organization_name: value});
}
},
data() {
return {
organization_name: 'Org Name'
}
},
preload({ params, query }) {
params['info'] = COMPONENTS.components[params.slug];
return params;
}
};
</script>
Now my questions:
I notice I can't #each through my object. I have to loop through its keys. Would be nice if I could do something like this:
{{#each info.attributes as attribute }}
{{attribute.description}}
{{/each}}
Before Sapper, I would use Angular-translate module that could do translations on strings based on a given JSON file. Does anyone know if a Sapper/Svelte equivalent exists, or is that something I might need to come up with on my own?
I'm not used to doing imports. I'm more use to dependency injection in Angular which looks a bit cleaner (no paths). Is there some way I can create a COMPONENTS constant that could be used throughout my files, or will I need to import a JSON file in every occurence that I need access to its data?
As a follow-up to #3, I wonder if there is a way to better include files instead of having to rely on using ../.. to navigate through my folder structure? If I were to change the path of one of my files, my Terminal will complain and give errors which is nice, but still, I wonder if there is a better way to import my files.
I know there has got to be a better way to implement what I implemented in my example. Basically, you see an input box beside an attribute, and if I make changes there, I am calling an updateComponent function which then does a this.set() in the current scope to override the binding. This works, but I was wondering if there was some way to avoid the function. I figured it's possible that you can bind the value of the input and have it automatically update my <TopBar> component binding... maybe?
The preload method gives me access to params. What I want to know if there is some way for me to get access to params.slug without the preload function.
What would be really cool is to have some expert rewrite what I've done in the best possible way, possibly addressing some of my questions.
Svelte will only iterate over array-like objects, because it's not possible to guarantee consistent behaviour with objects — it throws up various edge cases that are best solved at an app level. You can do this sort of thing, just using standard JavaScript idioms:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} ...</p>
{{/each}}
<!-- or, if you need the key as well -->
{{#each Object.entries(info.attributes) as [key, value]}}
<p>{{attr.description}} ...</p>
{{/each}}
Not aware of a direct angular-translate equivalent, but a straightforward i18n solution is to fetch some JSON in preload:
preload({ params, query }) {
return fetch(`/i18n/${locale}.json`)
.then(r => r.json())
.then(dict => {
return { dict };
});
}
Then, you can reference things like {{dict["hello"]}} in your template. A more sophisticated solution would only load the strings necessary for the current page, and would cache everything etc, but the basic idea is the same.
I guess you could do this:
// app/client.js (assuming Sapper >= 0.7)
import COMPONENTS from './config/components.json';
window.COMPONENTS = COMPONENTS;
// app/server.js
import COMPONENTS from './config/components.json';
global.COMPONENTS = COMPONENTS;
Importing isn't that bad though! It's good for a module's dependencies to be explicit.
You can use the resolve.modules field in your webpack configs: https://webpack.js.org/configuration/resolve/#resolve-modules
This would be a good place to use two-way binding:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} <input bind:value=organization_name /></p>
{{/each}}
Yep, the params object is always available in your pages (not nested components, unless you pass the prop down, but all your top-level components like routes/whatever/[slug].html) — so you can reference it in templates as {{params.slug}}, or inside lifecycle hooks and methods as this.get('params').slug, whether or not a given component uses preload.

Handlebars RegisterHelper with Ember Error

I'm pretty lost after trying numerous different things. I am building an Ember site and inside of one of my Handlebars templates, I want to display a link only if a condition is true. Here is my template.
<script type="text/x-handlebars" data-template-name="project">
<div class="project-container">
{{is_external model.url model.title}}
</div>
</script>
Now, my registerHelper in my js file:
Handlebars.registerHelper('is_external', function(url, title, options) {
if (url.indexOf("codepen") < 0) {
return "<p class='view-external-link'>"+
"<a href='{{url}}'' aria-label='View {{title}}'' target='_blank'>Visit Site</a>"+
"</p>";
}
});
The error I keep receiving however, is:
Uncaught TypeError: Cannot read property 'isHelperFactory' of undefined coming from my ember.js file.
Ideally, I would prefer to just return true or false from the helper to keep the Html out of my helper function but first I just need some help getting it working.
I am including ember, ember_compiler, and handlebars on my page as well.
My understanding of Ember is that since even 1.x, Ember included its own instance of Handlebars, so your external instance might be interfering.
The Ember v2.2.0 documentation on templates says to use Ember.Helper.helper, as does v1.13.0. Maybe give that a try and see if it helps.
What is the version of your Ember?
When i generate helper Ember uses syntax like this
import Ember from "ember";
// http://emberjs.com/deprecations/v1.x/#toc_ember-select
export function checkEquality([leftSide, rightSide]) {
return leftSide === rightSide;
}
export default Ember.Helper.helper(checkEquality);
In the template
{{is-equal item.selection selection}}
And if you want to use html within helper
you need to return
yourHtml.htmlSafe();

How to inject 3rd party javascript in Ember.js template?

I'm trying to utilize two 3rd party widgets on a website however cannot quite figure out how to get Ember.js to cooperate. I found lots on Views and have found that they're deprecated now and Components seem the way to go however I'm not sure how to make this work...
I have various city-based templates that require:
<script type="text/javascript" src="http://www.topix.net/iframe/city/atco-nj?js=1"></script>
and one other that looks like this:
<script>document.write('<script src="... + Math.random() + ..."></script>');</script>
How would I do this with Components or a better alternative!?
For this you don't really need a component, you could just create a template and inject it wherever you need it. However I'm not 100% what are city based templates but just to output html you can just use a template template / helper:
using a template (known as partial):
run (if using ember cli , if not just create the template file somewhere, again assuming you have some way you're compiling templates on the server)..
ember g partial inject_city
then:
//inject_city.hbs
<script type="text/javascript" src="http://www.topix.net/iframe/city/atco-nj?js=1"></script>
then in your main template:
{{partial 'inject_city'}}
Further reading: http://guides.emberjs.com/v1.10.0/templates/writing-helpers/
using a helper (notice to return html you must use the safestring)
Ember.Handlebars.helper('injectScript', function(value, options) {
return new Ember.Handlebars.SafeString("<script>document.write('<script src="... + Math.random() + ..."></script>');</script> );
});
In version 1.13.0 and above the syntax is different:
import Ember from "ember";
export default Ember.Helper.helper(function(params) {
return Ember.String.htmlSafe(`<b>${params[0]}</b>`);
});
(Notice you should generate a helper, wrap it with Helper.helper and return Ember.String.htmlSafe).
further reading: http://guides.emberjs.com/v1.10.0/templates/writing-helpers/
However the best way is to include libraries in your ember build / build your own component from by using the building blocks, and not just include a whole script..The ember documentation explains about components pretty well and ember-cli docs explain how to include third party libs..
Best of luck!
I got this to work by making a component. I had the same sort of problem, I wanted to draw some pie charts at load time of the page using charts.js
SO i defined the charts and ran the js to create them in a component.
heres the component 'js-charts':
export default Ember.Component.extend({
didInsertElement: function() { insert script and or methods to run }
});
This will always trigger because of the didInsertElement.
and in the template of the page your rendering just add {{js-charts}} component

Can I load partials from another file in Mustache.js?

I'm trying to load in partials from a separate file while using mustache.js but it's proven difficult. The PHP implementation makes this very easy, but not so much in the JS side.
I've gone through the docs and can't find anything related to this, only using an object as a partial instead of a file.
Here's some sort of psuedocode to explain what I'm trying to do.
$.Mustache.load('/mustaches.php', function () {
var item = { thing: 'one' };
var partial = 'partials/actions.mustache';
$('.container').mustache(
'main_mustache',
{item: item},
partial
);
});
So, I'd like to be able to load a secondary file as a partial an include it with the template to be used so I don't have to duplicate code across all the different templates I use.
With something like Blaze I can import a template with {{> templateName}} but it doesn't seem to be quite so easy with Mustache.js.
If this isn't possible with Mustache, what other libraries would you recommend?
You could load both templates with mustache, and then pass one of them (the partial) to the one that should render the partial. Be aware that in order to make it work, in the mustache where you want to render the partial, the variable name should be enclosed in triple curly bracers in order to tell mustache that it should be html:
<div>
<h1>This is my title</h1>
{{{partial}}}
</div>
and then:
$('.container').mustache(
'main_mustache',
{item: item, partial: partial},
);

ContainerView: limitations when dynamically adding views

ContainerView.pushObject() does not automatically wire-up dynamically added views with a Container object.
The lack of an auto-wired container causes a render failure when a view renders a template that contains a handlebars render helper.
SIMPLE CASE THAT WORKS (KIND OF)
View:
App.DynamicView = Em.View.extend({
templateName: 'dynamic',
didInsertElement: function() {
var control = this.get('controller');
control.send( 'view_inserted', this.templateName, control._debugContainerKey);
control.send('callDynamicController');
}
});
Template:
<script type="text/x-handlebars" data-template-name="dynamic">
dynamic
</script>
Controller (only used when manually assigned):
App.DynamicController = Em.ObjectController.extend({
className: 'App.DynamicWithRenderController',
callDynamicController: function() {
console.log('DynamicController.callDynamicController()');
}
});
Index Controller:
App.IndexController = Em.ObjectController.extend({
view_inserted: function(aview, acontroller) {
console.log('view inserted!', aview, acontroller);
}
})
Instantiation code:
var acontainer = App.DynamicController.create({});
var aview = App.DynamicView.create({ controller: acontroller })
acontainerView.pushObject(aview);
These classes render & behave as expected, but if you interogate them, lack some of the Ember-wiring (e.g. no _debugContainerKey & container properties IIRC):
MORE ADVANCED CASE THAT BREAKS
If we introduce a handlebars template that uses rendering helpers, it breaks rendering. The dynamically added view currently lacks some properties the rendering helper assumes
<script type="text/x-handlebars" data-template-name="dynamic-with-render">
dynamic w/render:
{{render knob}}
</script>
and make knob look like this:
<script type="text/x-handlebars" data-template-name="knob">
{{render knob}}
</script>
The (failing) dynamic view instantiation code:
var acontainer = App.DynamicController.create({});
var aview = App.DynamicView.create({
controller: acontroller,
template:'dynamic-with-render' })
acontainerView.pushObject(aview);
CODE EXAMPLE
A fuller example with some notes can be seen here:
http://jsfiddle.net/AshCoolman/KyJ2U/6/embedded/result/
NOTE: My tests include a custom handlebars helper based of the control helper called controlWithVars
THE PROBLEM
It looks like I need to write something that does the Ember-wiring, in either:
the more native ContainerView (getting into the Ember guts), OR
a more de-coupled new render helper possibly inelegant)
I'm not sure how to proceed. It would be great if someone has already come up with an elegant solution, or at least could give me some helpful tips.
EDIT: So it looks like creating and assigning a container, which includes the views dependencies might be a solution. Thoughts anyone?
HELPFUL READING
https://github.com/emberjs/ember.js/issues/2108
What is the purpose of the Ember.Container
http://mcdowall.info/posts/ember-application-initializers/
The raison d'être of Ember.ContainerView is for dynamically adding and removing views, so I'm pretty confident you can do all the things you want to with it.
One thing I noticed in your examples is that you are creating your child views with View.create(attrs). It is important to use containerView.createChildView(viewClassName, attrs) to create views that get the container, parent view hierarchy, and more. See the implementation more details:
https://github.com/emberjs/ember.js/blob/master/packages/ember-views/lib/views/view.js#L2072

Categories

Resources