how to binding data web component from element - javascript

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>

Related

Prototypes on Custom Elements

Suppose I have a custom HTML element tag <custom-table> with an attribute tableVal="10".
I would like to easily fetch the tableVal without using .getAttribute() (since I'm creating a public API that is easy-to-use).
Here's what I'm trying to do:
var customElement = document.querySelector('custom-table');
console.log(customElement.val)
Output: undefined
Expected Output: 10
This is my current try:
Object.setPrototypeOf(customElements.prototype, val.prototype)
function val(){
return this.getAttribute("tableVal")
}
Any idea or approaches through which I can achieve this?
There are a couple of ways to do this (see the MDN documentation on writing custom elements), but one is to create a class for your custom element with an accessor val property, and use that class when registering your custom element:
class CustomTable extends HTMLElement {
get val() {
return this.getAttribute("tableVal");
}
}
customElements.define("custom-table", CustomTable);
Live Example:
<!-- Defining the custom element -->
<script>
class CustomTable extends HTMLElement {
get val() {
return this.getAttribute("tableVal");
}
}
customElements.define("custom-table", CustomTable);
</script>
<!-- Using it in HTML -->
<custom-table tableVal="10"></custom-table>
<!-- Testing the property -->
<script>
const table = document.querySelector("custom-table");
console.log(table.val); // 10
</script>

HTML web component does not use shadow DOM style

I have created a vanilla web component or HTML element. It just displays two links.
To encapsulate the thing, I use shadow DOM. However it does not seem to be encapsulated. In the DOM tree it's inside #shadow-root which is good.
Why does the web component use the global style instead of the style I provided in the template for my web component?
The text is red and I expected it to be green.
class MyEl extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
connectedCallback() {
const template = `
<style>
a {
color: green;
}
</style>
<slot></slot>`;
this.shadow.innerHTML = template;
}
}
window.customElements.define("my-el", MyEl);
a {
color: red
}
<my-el>
Item1
Item2
</my-el>
While this question already has an accepted answer, moving a slot's children to the shadowRoot isn't desirable for most use cases.
What you probably want to do is to use the ::slotted() selector.
Just bear in mind that styles applied to a slot's children through the ::slotted() selector only act as "default" styles and can still be overridden by using styles in light DOM.
For example, check this edited version of your snippet:
As you can see, this time my-el tries to apply both a color and a text-decoration style to anchor (<a>) children in any of it's slots.
However, in light dom, we have a a.special selector that overrides the color, so the <a class="special"> will be red, not green
class MyEl extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
connectedCallback() {
const template = `
<style>
::slotted(a) {
color: green;
text-decoration: none;
}
</style>
<slot></slot>`;
this.shadow.innerHTML = template;
}
}
window.customElements.define("my-el", MyEl);
a.special {
color: red
}
<my-el>
Item1
<a class="special" href="example.com">Item2</a>
</my-el>
The full, detailed explanation is in: ::slotted CSS selector for nested children in shadowDOM slot
TL;DR
Your links are in lightDOM and thus styled by its DOM (in your code the document DOM)
Moving the nodes from lightDOM to shadowDOM is one "solution"; but you are not using slots then.
FYI, your code can be compacted to:
class MyEl extends HTMLElement {
constructor() {
super().attachShadow({ mode: "open" })
.innerHTML = `<style>a{color:green}</style><slot></slot>`;
}
}
window.customElements.define("my-el", MyEl);
More SLOT related answers can be found with StackOverflow Search: Custom Elements SLOTs
observe this line, you have to move/copy elements to shadow for example with:
this.shadow.innerHTML = this.innerHTML + template;
I've added this to demonstrate that only inline style will be applied to shadow dom elements .. so copied links in SD are using your style :)
so red will be GLOBAL, green will be SHADOW elements
class MyEl extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.shadow = this.attachShadow({ mode: "open" });
const template = `
<style>
a {
color: green;
}
</style>
<slot></slot>`;
this.shadow.innerHTML = this.innerHTML + template;
}
}
window.customElements.define("my-el", MyEl);
a {
color: red
}
<my-el>
Item1
Item2
</my-el>

How to get class of the element in stimulus.js

I want to toggle elements and I need a class names for that.
How can I get a class name of the nested element in stimulus.js and change it?
F.I, I need to toggle the "ul" element that is initially hidden.
div data-controller="my_controller"
a data-action="click->my_controller#toggle_my_elements"
| Click
ul.is-hidden id="my-id" data-target="my_controller.mytext"
li
| Text to be toggled.
and in stimulus controller I have:
import { Controller } from 'stimulus'
export default class extends Controller {
static targets = ["mytext"]
toggle_my_elements(){
console.log("debuggin") //Ok
const class_name = this.mytextTarget.className
}
}
I tried to call a js function className but it seems js functions don't work in the way they used to.
I just can't figure out how to get it.
As StimulusJS target is a HTML element, you can use its classList property
this.mytextTarget.classList.remove('is-hidden')
You could do the following to get the ul class:
static targets = [ "mytext" ]
connect() {
this.mytextClass = this.data.get("class") || "is-hidden"
}
Then use the following action descriptor to toggle your ul element
toggle(event) {
event.preventDefault();
this.mytextTargets.forEach(target => {
target.classList.toggle(this.mytextClass)
})
}
Have you tried element[:class]?
That's how I access the class of the html element from a Stimulus Reflex in ruby since element.class returns the class of the element (a StimulusReflex::Element) instead of the "btn btn-primary" String I was expecting.

Creating custom node to extend HTMLIFrameElement

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.

Lit-Element: which event to use for DOM updates?

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>

Categories