When we use web component techniques to create a custom element, sometimes the implementation of a custom element involves the use of attributes present on the resulting element in the main document. As in the following example:
main doc:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="import" href="web-components/my-portrait.html">
</head>
<body>
<my-portrait src="images/pic1.jpg" />
</body>
</html>
my-portrait.html:
<template id="my-portrait">
<img src="" alt="portait">
</template>
<script>
(function() {
var importDoc = document.currentScript.ownerDocument;
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var t = importDoc.querySelector("#my-portrait");
var clone = document.importNode(t.content, true);
var img = clone.querySelector("img");
img.src = this.getAttribute("src");
this.createShadowRoot().appendChild(clone);
}
}
});
document.registerElement("my-portrait", {prototype: proto});
})();
</script>
In the createdCallback, we use this.getAttribute("src") to get the src attribute defined on the portrait element.
However, this way of obtaining the attribute can be only used when the element is instantiated by element tag declaration. But what if the element is created using JavaScript: document.createElement("my-portrait")? When this statement is done being executed, the createdCallback has already been called and this.getAttribute("src") will return null since the element has no src attribute instantly when it is created.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="import" href="web-components/my-portrait.html">
</head>
<body>
<!--<my-portrait src="images/pic1.jpg" />-->
<script>
var myPortrait = document.createElement("my-portrait");
myPortrait.src = "images/pic2.jpg"; // too late
document.getElementsByTagName("body")[0].appendChild(myPortrait);
</script>
</body>
</html>
So how do we pass attribute to createdCallback when we instantiate a custom element using JavaScript? If there were a beforeAttach callback, we can set attributes there, but there are no such callback.
You may implement the lifecycle callback called attributeChangedCallback
:
my-portrait.html:
proto.attributeChangedCallback = function ( name, old, value )
{
if ( name == "src" )
this.querySelector( "img" ).src = value
//...
}
name
is the name of the modified (or added) attribute, old
is the old value of the attribute, or undefined
if it was just created,value
is the new value of the attribute (of type string
).For the callback to be called, use the setAttribute
method against your custom element.
main doc:
<script>
var myPortrait = document.createElement( "my-portrait" )
myPortrait.setAttribute( "src", "images/pic2.jpg" ) // OK
document.body.appendChild( myPortrait )
</script>
example:
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
this.innerHTML = document.querySelector("template").innerHTML
var path = this.getAttribute("src")
if (path)
this.load(path)
}
},
attributeChangedCallback: {
value: function(name, old, value) {
if (name == "src")
this.load(value)
}
},
load: {
value: function(path) {
this.querySelector("img").src = path
}
}
})
document.registerElement("image-test", { prototype: proto })
function add() {
var el = document.createElement("image-test")
el.setAttribute("src", "https://placehold.it/100x50")
document.body.appendChild(el)
}
<image-test src="https://placehold.it/100x100">
</image-test>
<template>
<img title="portrait" />
</template>
<button onclick="add()">
add
</button>