Prototypes on Custom Elements - javascript

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>

Related

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.

how to binding data web component from element

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>

Passing an object to element attribute in Polymer 2

I am playing with Polymer 2.0, and I don't understand how to pass an object as an element attribute.
Here's my code:
<dom-module id="notes-app">
<template>
<style>
:host {
display: block;
}
</style>
<button on-click="loadNotes">Get the notes</button>
<template is="dom-repeat" items="[[notes]]" as="note">
<note recipe='JSON.stringify(note)'></note>
</template>
</template>
<script>
class NotesApp extends Polymer.Element {
static get is() { return 'notes-app'; }
static get properties() {
return {
notes: {
type: Array,
value: []
}
};
}
loadNotes() {
this.notes = [
{"desc":"desc1", "author":"auth1", "type":"type1"},
{"desc":"desc2", "author":"auth2", "type":"type2"},
{"desc":"desc3", "author":"auth3", "type":"type3"}
];
}
}
window.customElements.define(NotesApp.is, NotesApp);
</script>
</dom-module>
simple-note is the element who has a property of type Object:
<dom-module id="note">
<template>
<style>
:host {
display: block;
}
</style>
<div>
<fieldset>
<label>description</label>{{note.desc}}<br>
<label>author</label>{{note.author}}<br>
<label>type</label>{{note.type}}
</fieldset>
</div>
</template>
<script>
class SimpleNote extends Polymer.Element {
static get is() { return 'simple-note' }
static get properties() {
return {
note: {
type: Object,
value: {},
notify: true
}
};
}
}
customElements.define(SimpleNote.is, SimpleNote);
</script>
</dom-module>
As you can see I want note-app to display all the objects in its notes property by passing an object representing a note to every simple-note elements (don't known if it is the right way to make elements interact each other). I want it to happen when I press the notes-app button. How can I pass an object to an element attribute in this case?
Since you're trying to pass the variable as an object, you should use property bindings instead of attribute bindings (which only supports strings).
Polymer data bindings require curly or square brackets ({{twoWayBinding}} or [[oneWayBinding]]). For example, to set the foo property of the <x-child> element to the value of note, the template would look something like this:
<template is="dom-repeat" items="[[notes]]" as="note">
<x-child foo="[[note]]">
</template>
Given that SimpleNote.is equals "simple-note", I assume your usage of <note> and <dom-module id="note"> were only typos in your question. They should be set to simple-note, as the element name must start with a lowercase ASCII letter and must contain a dash.
It looks like you're binding a recipe property, but <simple-note> declares a note property (and no recipe) and binds to note sub-properties in its template. I assume recipe is another typo.
working demo

How to access functions defined in js file inside the template of Polymer element?

I have created a function in global.function.js file as
function getData(flag) {
if (flag === 1) {
return "one";
}
else {
return "not one";
}
}
which then is imported using custom-js-import.html element:
<script src="global.function.js"></script>
When I tried to access the above function in custom-element.html, I am able to access it in the script part but not in the template part.
Is there any way I can access the function inside the HTML element?
<!-- custom-element.html -->
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="custom-js-import.html">
<dom-module id="custom-element">
<template>
<div>
Hello
</div>
<div id="data"></div>
<div>{{getData(1)}}</div><!-- Unable to access this from here -->
<div>{{getLocalData()}}</div>
</template>
<script>
// Define the class for a new element called custom-element
class CustomElement extends Polymer.Element {
static get is() { return "custom-element"; }
constructor() {
super();
}
ready(){
super.ready();
this.$.data.textContent = "I'm a custom-element.";
console.log(getData(1));//can be easily accessed from here
}
getLocalData(){
return "local";
}
}
// Register the new element with the browser
customElements.define(CustomElement.is, CustomElement);
</script>
</dom-module>
Sample Code
Is there any way I can access the function inside the HTML element?
Not really. In order to use data in a template you need to bind it to a property (Polymer calls this data binding).
Polymer's data binding system is designed for binding values to a template. Those values are typically just literals (e.g. strings and numbers) or plain ole javascript objects e.g. {a: 'someval', b: 5}. Polymer's data binding system is not designed to bind functions to a template and you can't just use javascript inside of a template. (If you're really into that idea, check out React as a replacement to polymer).
The polymer way to do what you're trying to do is to use a computed property. Instead of calling a function inside the template, create a computed property that reacts to changes of other variables. When the state of a property changes, the computed property will change too. This state can be thought of as the argument of your function.
I think it's better just to see the code working yeah (tested in chrome)?
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="custom-js-import.html">
<dom-module id="custom-element">
<template>
<div>
Hello
</div>
<label>
<input type="number" value="{{flag::input}}">
</label>
<h1>from flag: [[flag]]</h1>
<div id="data"></div>
<div>{{boundComputedData}}</div><!-- Unable to access this from here -->
<div>{{getLocalData()}}</div>
</template>
<script>
// Define the class for a new element called custom-element
class CustomElement extends Polymer.Element {
static get is() {
return "custom-element";
}
constructor() {
super();
}
getData(flag) {
const flagAsNumber = parseInt(flag);
if (flagAsNumber === 1) {
return "one";
} else {
return "not one";
}
}
ready() {
super.ready();
this.$.data.textContent = "I'm a custom-element.";
console.log(this.getData(1)); //can be easily accessed from here
}
getLocalData() {
return "local";
}
static get properties() {
return {
flag: {
type: Number,
value: 0
},
boundComputedData: {
type: String,
computed: 'getData(flag)'
}
};
}
}
// Register the new element with the browser
customElements.define(CustomElement.is, CustomElement);
</script>
</dom-module>
<custom-element></custom-element>
So What I'm doing here is:
creating a computed property boundComputedData and setting the computed property to 'getData(flag)' which will make it use the class function getData.
Now whenever the state the property flag changes, the computed property will update.
Hope it helps!
Thanks to Rico Kahler for suggesting me to use a mixin. Using mixin solved my problem. You can view the full working sample here.
All the global functions can be defined in the mixin.
<!--custom-mixin.html-->
<script>
const CustomMixin = superclass => class extends superclass {
static get properties() {
return {};
}
connectedCallback() {
super.connectedCallback();
}
getData(flag) {
if (flag === 1) {
return "one(From Mixin)";
} else {
return "not one(From Mixin)";
}
}
};
</script>
And remember to import the mixin file and add that mixin to your element.
<!-- custom-element.html -->
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="custom-mixin.html">
<dom-module id="custom-element">
<template>
<div>
Hello
</div>
<div id="data"></div>
<div>{{getData(1)}}</div>
<!-- Unable to access this from here -->
<div>{{getLocalData()}}</div>
</template>
<script>
// Define the class for a new element called custom-element
class CustomElement extends CustomMixin(Polymer.Element) {
static get is() {
return "custom-element";
}
constructor() {
super();
}
ready() {
super.ready();
this.$.data.textContent = "I'm a custom-element.";
console.log(getData(1)); //can be easily accessed from here
}
getLocalData() {
return "local";
}
}
// Register the new element with the browser
customElements.define(CustomElement.is, CustomElement);
</script>
</dom-module>

Extending HTML elements in Web components

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

Categories