Create custom elements v1 in ES5, not ES6 - javascript

Right now, if you follow the exact specifications of v1 of the custom elements spec, it's not possible to use custom elements in browsers that don't support classes.
Is there a way to create v1 custom elements without using the class syntax so that they are fully functional in Chrome, FireFox and IE11. Also, since IE11 doesn't have native support for custom elements, I'm assuming we will probably need to use some pollyfills, so what polyfills or libraries do we need in order to make this work in IE11?
I've messed around with Polymer 2, Polymer 3, and Stencil, but they are all a bit heavy-duty for some of the things we want to create.
This question seems to be on the right track, but I had some trouble getting it to work in IE11, so also how can I use Reflect.construct in IE11 for the purposes of custom elements?

Here's a full example of writing ES5-compatible custom elements using the v1 spec (credit to this comment on github)
<html>
<head>
<!--pollyfill Reflect for "older" browsers-->
<script src="https://cdn.rawgit.com/paulmillr/es6-shim/master/es6-shim.min.js"></script>
<!--pollyfill custom elements for "older" browsers-->
<script src="https://cdn.rawgit.com/webcomponents/custom-elements/master/custom-elements.min.js"></script>
<script type="text/javascript">
function MyEl() {
return Reflect.construct(HTMLElement, [], this.constructor);
}
MyEl.prototype = Object.create(HTMLElement.prototype);
MyEl.prototype.constructor = MyEl;
Object.setPrototypeOf(MyEl, HTMLElement);
MyEl.prototype.connectedCallback = function() {
this.innerHTML = 'Hello world';
};
customElements.define('my-el', MyEl);
</script>
</head>
<body>
<my-el></my-el>
</body>
</html>
Also, for the typescript lovers, here's a way to write custom elements using typescript that will still work when compiled to ES5.
<html>
<head>
<!--pollyfill Reflect for "older" browsers-->
<script src="https://cdn.rawgit.com/paulmillr/es6-shim/master/es6-shim.min.js"></script>
<!--pollyfill custom elements for "older" browsers-->
<script src="https://cdn.rawgit.com/webcomponents/custom-elements/master/custom-elements.min.js"></script>
<script type="text/typescript">
class MyEl extends HTMLElement{
constructor(){
return Reflect.construct(HTMLElement, [], this.constructor);
}
connectedCallback () {
this.innerHTML = 'Hello world';
}
}
customElements.define('my-el', MyEl);
</script>
</head>
<body>
<my-el></my-el>
<!-- include an in-browser typescript compiler just for this example -->
<script src="https://rawgit.com/Microsoft/TypeScript/master/lib/typescriptServices.js"></script>
<script src="https://rawgit.com/basarat/typescript-script/master/transpiler.js"></script>
</body>
</html>

Related

Polymer 1.x: Incompatible with native web-components?

(tl;dr - yes. However Polymer 2.0 is not and is an easy upgrade.)
I am trying to migrate an old front-end code base away from polymer 1.11.3 (webcomponentsjs 0.7.24) to native web-components.
However, I'm running into the following issue:
When Polymer is included in a page, even trivial native web-components are effectively broken. Web-components that have neither custom methods nor properties seem work. But, include a custom method or property and try to access it, and you'll encounter an error indicating that the property or method does not exist.
For example, given the following component:
customElements.define( 'x-hello', class J extends HTMLElement {
hello() { console.log( 'hello called' ) }
})
document.createElement( 'x-hello' ).hello()
If I run this on a page that includes Polymer,
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta name="application-name" content="re">
<script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html">
</head>
<body>
<h1>Here</h1>
</body>
<script>
customElements.define( 'x-hello', class J extends HTMLElement {
hello() { console.log( 'hello called' ) }
})
document.createElement( 'x-hello' ).hello()
</script>
</html>
Then I get the following error:
Uncaught TypeError: document.createElement(...).hello is not a function
<anonymous> debugger eval code:5
However, run it in a page that doesn't include Polymer and it will run just fine.
The few components that I've migrated away from Polymer so far fail in the same way when included in the Polymer-based app I pulled them from. Similarly, running them in a test project that doesn't contain Polymer and they function fine.
I get that both the Polymer lib and webcomponentjs were intended to provide webcomponent support and functionality at a time when browsers didn't support it, but it seems like Polymer should at least be compatible with native web-components.
Is there a work-around for this? Is there some way to incorporate native web-components into an application that is using Polymer 1.x?
I'd really like to be able to migrate components away from Polymer and to native one at a time rather than all at once.

Is it possible to create a web component anyone can use without installation?

I'm just getting started with web components, and if I understand correctly, the point is for them to be reusable by anyone. Is it possible to create a component that can be used by anyone simply by adding a piece of html to their static site (similar to how JavaScript widgets are added, simply by copy-pasting a few lines of code), or does it need to be installed by someone? Or is this not an intended use case of web components?
Yes. A Web Component is a kind of "Javascript widget".
Typicially, you define a Web Component in a Javascript file.
You can then include it in any HTML with a <script> element.
Example with a minimal custom element called <hello-world>:
In hello-world.js:
customElements.define( 'hello-world', class extends HTMLElement {
constructor() {
super()
this.attachShadow( {mode: 'open' })
.innerHTML = 'Hello World!'
}
} )
In your main page index.html:
<html>
<head>
<!-- import the web component -->
<script src="hello-world.js">
</head>
<body>
<!-- use the new element -->
<hello-world></hello-world>
</body>
</html>
Note: alternately, one could also copy-paste the Javascript code that defines the custom element to its main HTML page.

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>`;

how to use Polymer polyfills together

I'm trying to use the Polymer polyfills for ShadowDOM and Custom Elements
If I use them individually, they work well, but when I use both at the same time I get errors like this
Uncaught TypeError: Cannot read property 'polymerShadowRoot_' of undefined.....Element.js:69
It depends whether I first include customelement.js or shadowdom.js
Here is my test code:
<!doctype html>
<html>
<head>
<script src="/bower_components/CustomElements/custom-elements.js"></script>
<script src="/bower_components/ShadowDOM/shadowdom.js"></script>
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
console.log('create shadowDOM');
var root = this.createShadowRoot();
root.innerHTML = '<content></content>';
};
document.register('x-foo', {prototype: proto});
</script>
</head>
<body>
<x-foo><span>hallo</span></x-foo>
</body>
</html>
Any suggestions what might go wrong here ?
Whenever using ShadowDOM polyfill, you must load it first because it utterly remaps DOM. Other libraries (generally) can load on top of the remapping, but not underneath.
http://jsbin.com/uBOQIBu/3/edit

Creating javascript objects from different files

I've been trying to do javascript for sometime now, but i want it to be "object-orientated" so I'm trying to create different javascript classes in different files and try to create an object and call it's methods in a different file function, but it doesn't seem to work.
This is what I have so far:
person.js
function Person(name, age, gender)
{
this.age = age;
this.name = name;
this.gender = gender;
this.job;
this.setJob = function(job)
{
this.job = job;
}
this.getAge = function()
{
return this.age;
}
this.getName = function()
{
return this.name;
}
this.getGender = function()
{
return this.gender;
}
}
Job.js
function Job(title)
{
this.title = title;
this.description;
this.setDescription = function(description)
{
this.description = description;
}
}
main.js
function main()
{
var employee = new Person("Richard", 23, male);
document.getElementById("mainBody").innerHTML = employee.getName();
}
index.html
<!DOCTYPE HTML>
<HTML>
<head>
<title>javascript test</title>
<script src="main.js" type="javascript"></script>
</head>
<body>
<p id="mainBody"></p>
</body>
</HTML>
any help or advice would be greatly appreciated.
Many thanks
EDIT:
Many thanks for all the answers and suggestions, however, I've included all my javascript files and still it doesn't work...
Currently JavaScript is not clever enough to find your dependencies without help.
You need:
<!DOCTYPE HTML>
<HTML>
<head>
<title>javascript test</title>
<script src="person.js" type="javascript"></script>
<script src="Job.js" type="javascript"></script>
<script src="main.js" type="javascript"></script>
</head>
<body>
<p id="mainBody"></p>
</body>
</HTML>
Note:
If you want on-demand load of the dependencies then you can use AMD (Asynchronous Module Definition) with requirejs or something else.
Using AMD you can define something like:
define(['Job', 'person'], function (job, person) {
//Define the module value by returning a value.
return function () {};
});
The define method accepts two arguments: dependencies and function which defines the module.
When all dependencies are loaded they are passed as arguments to the function where is the module definition.
One more thing to notice is that Person and Job are not classes. They are just functions (constructor functions) which produces objects based on rules in their definitions.
Files aren't automatically loaded, you need to add each .js file to the document with script tags and in the right order as well, otherwise you will get errors.
You might want to look into requirejs.org for dependency management, it's the hawtest thing lately untill ES6 becomes mainstream anyways.
class methods should be defined via prototype so they receive 'this' reference,
like that:
Person.prototype.getGender = function()
{
return this.gender;
};
you can try to include the 1st and 2nd javascript files first.
like:
<!DOCTYPE HTML>
<HTML>
<head>
<title>javascript test</title>
<script src="person.js" type="javascript"></script>
<script src="Job.js" type="javascript"></script>
<script src="main.js" type="javascript"></script>
</head>
<body>
<p id="mainBody"></p>
</body>
</HTML>
You need to return the object created by Person in order for it to constitute a new instance of the Person prototype.
return(this);
This isn't working because, according to your HTML code, the browser is only loading main.js
<script src="main.js" type="javascript"></script>
Since Javascript runs in the browser, not on the server where the other files are, the browser will try to execute main.js and fail, since it doesn't have access to the classes in the other files. If you include each one of the files (making sure that every file is included after the one it requires), you should have more success.
For example:
<script src="Job.js" type="javascript"></script>
<script src="person.js" type="javascript"></script>
<script src="main.js" type="javascript"></script>
I see three issues with the code.
The page does not import the proper external Javascript files
<head>
<title>javascript test</title>
<script src="job.js" type="javascript"></script>
<script src="person.js" type="javascript"></script>
<script src="main.js" type="javascript"></script>
</head>
Male needs to be a String literal
When the interpreter encounters male within the Person constructor it looks for a variable, which it cannot find.
function main()
{
var employee = new Person("Richard", 23, "male");
document.getElementById("mainBody").innerHTML = employee.getName();
}
The code should call the main function.
Without a call to the main function the code is never kicked off.
function main()
{
var employee = new Person("Richard", 23, "male");
document.getElementById("mainBody").innerHTML = employee.getName();
}
main();
Working Example: http://jsfiddle.net/R7Ybn/
ES6: https://www.sitepoint.com/understanding-es6-modules/
Supported in Safari as of Summer of 2017, but no support in other browsers. In a year or so, it seems like it'll be supported by Edge, Firefox, Chrome, Opera, and safari. So keep it in mind?
https://caniuse.com/#feat=es6-module
I was having a similar issue and the problem for me stemmed from writing
"script src="Person.js" type="javascript"
instead of
"script src="Person.js" type="text/javascript"
in my index.html file
Hope this Helps,

Categories

Resources