Is it possible to create a custom element like : "" and this new element will have all the functionality of an iframe element? (its own document/window objects and such...) ?
Extending <iframe>
To extend a standard HTML element, you must use the { extends: "element" } in the customElements.define() method:
class MyFrame extends HTMLIFrameElement {
constructor () {
super()
console.log( 'created')
}
}
customElements.define( 'my-frame', MyFrame, { extends: 'iframe' } )
Then use the is="custom-element" attribute to create the extended element:
<iframe is='my-frame'></iframe>
You can then use it as a standard <iframe> and add your custom features.
Defining a new HTML tag
Alternaltey, if you want a new tag name, create an autonomous custom element (derived from HTMLElement) with a standard <iframe> element inside :
class NewFrame extends HTMLElement {
constructor() {
super()
console.log( 'created' )
this.attachShadow( { mode: 'open' } )
.innerHTML = "<iframe></iframe>"
}
connectedCallback() {
setTimeout( () =>
this.shadowRoot.querySelector( 'iframe' ).contentDocument.body.innerHTML = this.innerHTML
)
}
}
customElements.define( 'new-frame', NewFrame )
In this example, the custom element will use the content of the <new-frame> to populate the <iframe> document.
<new-frame>Hello world !</new-frame>
The drawback of this solution is that your new element won't act as an <iframe>. You'll have to define some custom methods for all the feature you want to forward to the <iframe> inside.
Related
The documentation over at github.com/Polymer/lit-element describes the lifecycl, if a property of some lit-element is changed. However, I can not seem to find any documentation about a lifecycle if the DOM content of the element is changed.
So assume I have some nested DOM structure and my outermost element should display something based on the DOM content. For sake of simplicity the example below will just display the number of child-elements of the given type.
Now at some point my application inserts a new nested element (click the test button below). At this point I would like to update the shown count.
From my tests it seems that render() is not called again in that case, neither is updated().
Which event do I need to listen or which function do I need to implement for to recognize such a change?
My only current workaround is to use requestUpdate() manually after the DOM update, but I think such changes should be handled by lit-element itself.
document.querySelector( 'button' )
.addEventListener( 'click', () => {
const el = document.querySelector( 'my-element' );
el.insertAdjacentHTML( 'beforeend', '<my-nested-element>new addition</my-nested-element>' );
})
my-element, my-nested-element {
display: block;
}
<script src="https://unpkg.com/#webcomponents/webcomponentsjs#latest/webcomponents-loader.js"></script>
<!-- Works only on browsers that support Javascript modules like Chrome, Safari, Firefox 60, Edge 17 -->
<script type="module">
import {LitElement, html} from 'https://unpkg.com/#polymer/lit-element/lit-element.js?module';
class MyElement extends LitElement {
constructor(){
super();
this.number = this.querySelectorAll( 'my-nested-element' ).length;
}
render() {
return html`<p>number of my-nested-element: ${this.number}</p>
<slot></slot>`;
}
}
customElements.define('my-element', MyElement);
class MyNestedElement extends LitElement {
render() {
return html`<slot></slot>`;
}
}
customElements.define('my-nested-element', MyNestedElement);
</script>
<my-element>
<my-nested-element>first</my-nested-element>
<my-nested-element>second</my-nested-element>
</my-element>
<button>test</button>
In order to detect a new element inserted from the Light DOM through a <slot> element, you can listen to slotchange events on the <slot> element, or on the Shadow DOM root itself.
See the running example below:
document.querySelector('button').onclick = () =>
document.querySelector('my-element').insertAdjacentHTML('beforeend', '<my-nested-element>new addition</my-nested-element>');
my-element,
my-nested-element {
display: block;
}
<script type="module">
import {LitElement, html} from 'https://unpkg.com/#polymer/lit-element/lit-element.js?module';
class MyElement extends LitElement {
firstUpdated() {
var shadow = this.shadowRoot
var nb = shadow.querySelector( 'span#nb' )
shadow.addEventListener( 'slotchange', () =>
nb.textContent = this.querySelectorAll( 'my-nested-element').length
)
}
render() {
return html`<p>number of my-nested-element: <span id="nb"></span></p>
<slot></slot>`;
}
}
customElements.define('my-element', MyElement);
</script>
<my-element>
<my-nested-element>first</my-nested-element>
<my-nested-element>second</my-nested-element>
</my-element>
<button>test</button>
How I can output data to attr value in my web component element? like ngmodel in angularjs
// custom element is :
<custom-ele res="value"></custom-ele>
CSS only
You could use the attr() CSS function combined with the ::after CSS pseudo-class and its content CSS property.
custom-ele::after { content: attr(res) }
<custom-ele res="value"></custom-ele>
Custom Element definition
You can get the attribute value in the connectedCallback() method and inject it in its DOM tree with a template literal variable:
customElements.define( 'custom-ele', class extends HTMLElement {
connectedCallback() {
var res = this.getAttribute( 'res' )
this.innerHTML = `${res}`
}
} )
<custom-ele res="value"></custom-ele>
Using the data-* standard notation
You could access an element attribute by its dataset property if you define it using the data-* notation (i.e.: data-res="value" )
customElements.define( 'custom-ele', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `${this.dataset.res}`
}
} )
<custom-ele data-res="value"></custom-ele>
For an application i want to create Object-Based components in ES6.
On the normal way, you can create Elements as follow:
var element = document.createElement('YourElement');
element.innerHTML = 'Content';
document.querySelector('body').appendChild(element);
How i can create these in ES6 like:
export default class Container extends HTMLDivElement {
constructor() {
super();
this.innerHTML = 'Content';
}
}
With these example?
var container = new Container();
document.querySelector('body').appendChild(container);
My idea is, to create an -only JavaScript- UI Framework, without using "native" HTML snippets...
<div class='body'>
</div>
<script>
class Container extends HTMLElement {
constructor() {
super();
console.log( 'Constructed' )
}
connectedCallback() {
console.log('Callback');
this.innerHTML = "Content";
}
}
customElements.define('my-contain', Container);
let container = new Container();
document.querySelector('.body').appendChild(container);
</script>
You need to register your Component with the CustomElementRegistry below your Class definition and utilize the connectedCallback().
export default class Container extends HTMLDivElement {
constructor() {
super();
this.innerHTML = 'Content'; // Does Nothing
}
connectedCallback() { // Fires when attached
console.log('Callback');
this.innerHTML = "Content";
}
}
customElements.define('my-contain', Container, { extends: "div" });
LIFECYCLE HOOKS OF CUSTOM COMPONENTS
More info on CustomElementRegistry here: MDN CustomElementRegistry
More info on implementation of such here: MDN Using Custom Elements
I have created a Web Component which hosts Wiris. However when the component is rendered the Wiris editor is (very) badly formed:
You can see the issue live here.
The code is as follows:
class WirisComponent extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// Create a shadow root
var shadow = this.attachShadow( { mode: 'open' } );
// Create a div to host the Wiris editor
var div = document.createElement('div');
div.id = 'editorContainer';
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
// Insert the Wiris instance into the div
editor.insertInto(div);
// Append it to the shadow route
shadow.appendChild(div);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
and the HTML mark-up is:
<wiris-component></wiris-component>
Note that I've tried this in Chrome which does have full support for web components.
Any idea what the problem is? Is the problem related to the styling issue found in this issue?
Don't use a Shadow DOM: the styles imported with your library are not working with it.
class WirisComponent extends HTMLElement {
connectedCallback() {
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
editor.insertInto(this);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
<script src="https://www.wiris.net/demo/editor/editor"></script>
<wiris-component></wiris-component>
From the custom elements page, I see that to extend an element you do:
var XFooButtonPrototype = Object.create(HTMLButtonElement.prototype);
XFooButtonPrototype.createdCallback = function() {
this.textContent = "I'm an x-foo button!";
};
var XFooButton = document.registerElement('x-foo-button', {
prototype: XFooButtonPrototype,
extends: 'button'
});
Then later in the guide it says that you can make an element by writing either:
<x-foo></x-foo>
Or:
<button is="x-foo-button"></button>
Questions:
Why is it important to specify extends: 'button' when the element is obviously_ inheriting from HTMLButtonElement (since it has HTMLButtonElement.prototype in its proto chain)
How is the link between button and x-foo-button established? Does x-foo-button become a possible option of button in terms of is="x-foo-button" thanks to that extends: 'button' ? What happens "internally", so to speak?
Why would you pick <button is="x-foo-button"></button> over <x-foo></x-foo>...?
[ADDENDUM]
Polymer saves us from this duplication:
MyInput = Polymer({
is: 'my-input',
extends: 'input',
created: function() {
this.style.border = '1px solid red';
}
});
If extends is there, Polymer will put the right prototype in the chain with Object.getPrototypeOf(document.createElement(tag));.
So, corollary question:
Why the duplication in the first place? If there is an extends, shouldn't the browser automatically do this?
You totally misunderstood how extending web components work.
Create simple elements
First of all, this is how you register a new element:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
To create an element you can do one of these:
<x-foo></x-foo>
var xFoo = new XFoo();
document.body.appendChild(xFoo);
var xFoo = document.createElement( 'x-foo')
document.body.appendChild(xFoo);
Create extended elements
This is how you extend an existing element:
var XFooButton = document.registerElement('x-foo-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
To create one you can do one of these:
<button is="x-foo-button"></button>
var xFooButton = new XFooButton();
document.body.appendChild(xFoo);
var xFooButton = document.createElement('button', 'x-foo-button');
document.body.appendChild(xFooButton);
Note that in case of extended custom elements, when registering them you have to specify both the prototype (set to HTMLButtonElement.prototype rather than HTMLElement.prototype), and the extended tag's name (extends: 'button').
Also, when you create an extended element using markup or createElement(), you need to also specify the basic element (button) and the extended one (x-foo-button),
(Note: I am aware I am answering myself)
I think its Importent to Say here:
WARNING DEPRECATED Browser API METHOD
Here in this Question a .registerElement is Used it got Replaced by .defineElement and the Api has changed
current way to define a element
class AppDrawer extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH</h1>'
}
}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer-noname', class extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH AH</h1>'
}
});
Example - defining a mobile drawer panel, < app - drawer >:
Example usage:
<app-drawer></app-drawer>
<app-drawer-noname></app-drawer-noname>
```