Inject html in polymer including data binding - javascript

I have a trusted html file with data-bindings, which I want to include to a web component.I tried multiple ways to include the html file, but the data-binding doesn't work. I know polymer won't stamp the html, because it becomes a vulnerability for XSS attacks from untrusted sources, but I have a trusted source.
I'm already aware of 1 and 2 and tried out juicy-html, iron-ajax with inner-h-t-m-l and also the injectBoundHTML function.
Is there other way than binding everything by myself?
The file I want to include contains input fields and it is a predefined form.

You can use the Templatizer by creating a <template> manually and setting its content. The important part is that you can't just set the innerHTML
Polymer({
is: 'my-elem',
behaviors: [ Polymer.Templatizer ],
ready: function() {
var template = document.createElement('template');
// you must prepare the template content first (with bindings)
var templateContent = document.createElement('div');
templateContent.innerHTML = 'First: <span style="color: red">[[person.first]]</span> <br> Last: <span style="color: green">[[person.last]]</span>';
// and cannot simply set template's innerHTML
template.content.appendChild(templateContent);
// this will process your bindings
this.templatize(template);
var person = {
first: 'Tomasz',
last: 'Pluskiewicz'
};
var itemNode = this.stamp({ person: person });
Polymer.dom(this.root).appendChild(itemNode.root);
}
});
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.19/webcomponents.min.js"></script>
<link rel="import" href="https://raw.githubusercontent.com/Polymer/polymer/master/polymer.html" />
</head>
<body>
<my-elem>
</my-elem>
<dom-module id="my-elem">
<template>
</template>
</dom-module>
</body>
</html>

Related

DOM doesn't update upon child tags that are appended with JQuery

I'm trying to dynamically append child riot.js tags depending on the results of an API call. Whenever I try to append these tags using jquery's .append() function, the DOM does not update. I tried the following method described on this github thread (which doesn't work for me):
https://github.com/riot/riot/issues/2279
var myTag = document.createElement('my-tag')
$('#container').append(myTag)
riot.mount(myTag)
Here's a simplified example of what I'm trying to do (code listed below as well): https://jsfiddle.net/7m2z7cus/12/
<html>
<head>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://rawgit.com/riot/riot/master/riot.min.js"></script>
</head>
<body>
<foo></foo>
<script>
riot.tag('bar', '<h1>hello</h1>', '', '', function(opts) { });
riot.tag('foo', '<div id="bars"></div>', '', '', function(opts) {
var bar = document.createElement('bar');
$('#bars').append(bar);
riot.mount(bar);
});
riot.mount('foo');
</script>
</body>
</html>
I expect the #bars div to have a bar tag appended to it displaying "Hello" on the screen but it's not there. The page is blank. How should I go about dynamically appending nested tags like in the example above?
What you are trying to do is totally possible and your implementation is really close to working.
The only thing your missing is that the tag foo needs to be fully mounted before you can reference DOM nodes inside the tag, i.e. trying to reference $('#bars') won't reference anything if foo isn't fully mounted.
So, in order to get this to work, you will need to create and append the tag bar after foo has mounted which is done by utilizing the 'mount' event
for the tag foo.
<html>
<head>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://rawgit.com/riot/riot/master/riot.min.js"></script>
</head>
<body>
<foo></foo>
<script>
riot.tag('bar', '<h1>hello</h1>', '', '', function(opts) { });
riot.tag('foo', '<div id="bars"></div>', '', '', function(opts) {
this.on('mount', function() {
// foo has fully mounted. DOM Nodes are accessible inside this callback
var bar = document.createElement('bar');
$('#bars').append(bar);
riot.mount('bar');
})
});
riot.mount('foo');
</script>
</body>
Here's the JSFiddle: https://jsfiddle.net/ypwwma2s/

Create and Use a Custom HTML Component?

I have the following local html:
<html>
<head>
<link rel="import" href="https://mygithub.github.io/webcomponent/">
</head>
<body>
<!-- This is the custom html component I attempted to create -->
<img-slider></img-slider>
</body>
</html>
and the following attempt at a template:
<template>
<style>
.redColor{
background-color:red;
}
</style>
<div class = "redColor">The sky is blue</div>
</template>
<script>
// Grab our template full of slider markup and styles
var tmpl = document.querySelector('template');
// Create a prototype for a new element that extends HTMLElement
var ImgSliderProto = Object.create(HTMLElement.prototype);
// Setup our Shadow DOM and clone the template
ImgSliderProto.createdCallback = function() {
var root = this.createShadowRoot();
root.appendChild(document.importNode(tmpl.content, true));
};
// Register our new element
var ImgSlider = document.registerElement('img-slider', {
prototype: ImgSliderProto
});
</script>
As described in this article. When I run the code, I get:
Uncaught TypeError: Cannot read property 'content' of null
at HTMLElement.ImgSliderProto.createdCallback ((index):20)
In other words document.querySelector('template'); returns null. What gives?
My goal is to create custom html element and display it on the website that links the template code. I am 100% sure I am pulling the remote template code correctly (obviously, since I get the error in that code).
P.S. I am using latest Chrome, so I shouldn't need polyfills.
Try this:
var tmpl = (document.currentScript||document._currentScript).ownerDocument.querySelector('template');
The problem you ran into is that the template isn't really part of document but it is part of the currentScript. Due to polyfills and browser differences you need to check for currentScript and _currentScript to work correctly.
Also be aware that HTML Imports will never be fully cross browser. Most web components are moving to JavaScript based code and will be loaded using ES6 module loading.
There are things that help create templates in JS files. Using the backtick (`) is a reasonable way:
var tmpl = document.createElement('template');
tmpl.innerHTML = `<style>
.redColor{
background-color:red;
}
</style>
<div class = "redColor">The sky is blue</div>`;

Knockout Template is not working in IE 8 using RequireJS and Require Text

I have a problem rendering a tree object in JavaScript when using IE8. In order to write a hierarchy in an HTML page, I need to use a knockout template to recursively render the tree object.
I am using knockout components to render the final result using requirejs and require.text
The component is working fine in all browsers except for IE8. I know I should not be using IE8, but this is an intranet site for a company that cannot upgrade all browsers easily right now.
I simplified the code to only render a list of numbers in a plain list using the template.
Here is the ViewModel in test.vm.js:
define(['knockout'], function(ko) {
function TestViewModel() {
var self = this;
self.Title = "Example";
self.List = [
{ Name: "1" },
{ Name: "2" },
{ Name: "3" },
{ Name: "4" }
];
}
return TestViewModel;
});
Here is the View in test.view.js:
<script type="text/html" id="testTemplate">
<li data-bind="text: Name"></li>
</script>
<div>
<div data-bind="text: Title"></div>
<ul data-bind="template: {name: 'testTemplate', foreach: List}">
</ul>
</div>
And finally, the HTML page calling the component:
<html>
<head>
<meta charset="utf-8" />
<title>App</title>
<script src="Scripts/jquery-1.10.2.js"></script>
<script src="Scripts/require.js"></script>
</head>
<body>
<script type="text/javascript">
require.config({
baseUrl: "/",
paths: {
'knockout': 'Scripts/knockout-3.2.0',
'text': 'Scripts/require.text'
}
});
$(document).ready(function () {
require(['knockout'], function (ko) {
ko.applyBindings();
});
});
require(['knockout'], function (ko) {
ko.components.register('TestComponent', {
viewModel: { require: 'Scripts/test.vm' },
template: { require: 'text!Scripts/test.view.js' }
});
});
</script>
<!-- ko component: { name: "TestComponent" } -->
<!-- /ko -->
</body>
</html>
The main difference I can see browsing the DOM is that the template is rendered in other browsers completely
<script id="testTemplate" type="text/html">
<li data-bind="text: Name"></li>
</script>
while in IE8 is empty
<SCRIPT id=testTemplate type=text/html __ko__1453323521948="ko12"></SCRIPT>
If I put the template "testTemplate" outside the view file and directly in the HTML page, the component start to work. The component is not working just because the template is empty. Placing the "testTemplate" in the HTML is a partial solution but I need to find why it is not working when is placed inside the view file.
UPDATE:
I simplified the scenario. Apparently is some bug in the "template" parameter in component registration. If the template is enclosed in script tags, the content is ignored and not rendered in the page. If I decide to change it to use <template> tags, component tries to resolve data bindings inside the template and shows an error. Template tags are supposed to be ignored in bindings but the component does not ignore the tags. I tried with a temporal hack using the last tag I read is configured to be ignored by templates, by enclosing the template in textarea tags and placing display:none. Now the contents are rendered in all browsers but I don't like this solution.
I work for a company that still uses Windows XP so I'm limited to IE8 too.
We are successfully using knockout component templates with require.js and the text plugin.
Re. "If the template is enclosed in script tags, the content is ignored and not rendered in the page":
We have our templates inside HTML files, and use the !strip suffix to remove the wrapping HTML & body tags. This successfully loads the templates and also gives better editor support as the file extension is correct for editing markup.

Providing data to Polymer elements

I want to have a single source provide all of my data. A model if you will, and I want my elements to be able to utilize that data, but never change it (one way data-binding). How can I go about this? Should I add the data as a behavior?
I tried doing this in my document:
<script type="text/javascript" src="/data.js"></script> <!-- defines a global object named DATA -->
<my-element data="{{DATA}}"></my-element>
And this inside my-element.html
<dom-module id="my-element">
<template></template>
<script>
Polymer({
is: 'my-element',
properties: {
data: Object
},
ready: function () {
console.log(this.data);
}
});
</script>
</my-element>
but it doesn't seem to work, the value of this.data is literally "{{data}}".
I am looking for a better solution than wrapping the element declaration inside a dom-bind template
To use data binding, you either need to use it inside a polymer element, or inside a dom-bind element. See the explanation here. If you use dom-bind, it's only a case of using the js to set DATA to a property on the dom-bind template element, 'data' maybe, which would be little code.
Essentially, you can't set a global and expect data binding to know about it. You need to either tell dom-bind about it, or the element about it, by setting a property on the element, perhaps using behaviour, as you suggested, or using Mowzer's approach.
An example of using a behaviour would be:
<link rel="import" href="databehaviour.html">
<link rel="import" href="bower_components/polymer/polymer.html">
<dom-module id="an-ele">
<style>
</style>
<template>
<div>{{data.sth}}</div>
</template>
<script>
Polymer({
is: "an-ele",
behaviors: [DataBehaviour]
});
</script>
</dom-module>
With the behaviour being:
<script>
DataBehaviour = {
ready: function() {
this.data = {'sth':'A thing! A glorious thing!'};
}
}
</script>
But in your case, this.data would be set to your DATA global.
Use <iron-meta> [link] or <iron-localstorage>] [link] to share variables between elements or the main document.

Meteor - Ckeditor Integration

I'm not sure if there has been a change in the way Meteor loads items, or the way it handles jquery, but I'm having an awful lot of trouble getting ckeditor to come up.
Main Template (Iron-router):
<template name="layout">
<head>
<script type="text/javascript" src="js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="js/ckeditor/adapters/jquery.js"></script>
</head>
.....
</template>
Independent Editor Template:
<template name="editor">
<div class="editor_container">
<textarea class="editor"></textarea>
</div>
</template>
Ckeditor located at public/js/ckeditor, any time I try to do the Template.editor.rendered() technique, or even just trying to type $('.editor').ckeditor(); into the console, I get an error of:
$('.editor').ckeditor();
VM48825:2 Uncaught TypeError: undefined is not a function
Any ideas?
Try taking the <head> section out of the layout template. Reading here I believe the <head> section is treated specially be meteor (see: http://docs.meteor.com/#/full/structuringyourapp) and that it being inside a template may be causing the JS to actually not be loaded. Just a guess though.
<head>
<script type="text/javascript" src="js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="js/ckeditor/adapters/jquery.js"></script>
</head>
<template name="layout">
.....
</template>
You can use IRLibLoader from iron:router into the onBeforeAction like this.
Router.route('/editor', {
name: 'editor',
template: 'layout',
onBeforeAction: function () {
var ckEditor = IRLibLoader.load('/js/ckeditor/ckeditor.js');
var adapter = IRLibLoader.load('/js/ckeditor/adapters/jquery.js');
if(ckEditor.ready() && adapter.ready()){
console.log('The 2 JS just finish load');
this.next(); // Render the editor page
if(Meteor.isClient){
Template.editor.rendered = function(){
$('.editor').ckeditor();
console.log("loading coeditor when template fully rendered");
}
}
}
}
});
Alternative on the main layout you can use this.
<head>
<script type="text/javascript" src="js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="js/ckeditor/adapters/jquery.js"></script>
</head>
<template name="layout">
{{> yield}}
</template>
<template name="editor">
<div class="editor_container">
<textarea class="editor"></textarea>
</div>
</template>
And do the same rendered function
Template.editor.rendered = function(){
$('.editor').ckeditor();
//or make a little delay (1sec)
Meteor.setTiemout(function(){
$('.editor').ckeditor();
},100)
}
There are several problems with your code :
You can't put <head> sections inside another template, it must be done outside all templates.
The path to your JS files are broken, you must prepend a slash to them to reference files in the public directory.
Loading scripts in <head> sections is not a good idea because they will be loaded when your app first loads for every user, even if they never use the editor.
Here is a solution where we load every scripts asynchronously using jQuery promises when the editor template is rendered, and only then initialize the CKEditor.
Template.editor.rendered=function(){
var template=this;
$.when(
$.getScript("/js/ckeditor/ckeditor.js"),
$.getScript("/js/ckeditor/adapters/jquery.js")
).done(function(){
template.$(".editor").ckeditor();
});
};

Categories

Resources