Why custom elements not support attributes as an object? - javascript

I am trying to pass data attribute in custom element as an object but while receiving inside attachedCallback method getting value "[object object]" in a string form.
So can anyone help me to figure out what is the work around to get the attributes as an object inside custom-element(web component).
code sample
<script>
class myElements extends HTMLElement {
createdCallback() {
this.innerHTML = `<h1>Hello</h1>`;
}
attachedCallback() {
console.log(this.getAttribute('data'));
}
}
document.registerElement('my-element', myElements);
</script>
custom element tag
<script>
var object = { key1: 111, key2: 222, key3: function(){return "hi"}, key4:[1,2,3]};
function changeHandler() {
page('/default', function() {
// some logic to decide which route to redirect to
if (admin) {
page.redirect('/admin');
} else {
page.redirect('/guest');
}
});
}
</script>
<my-element data="object" onchange="changeHandler"></my-element>
Note: suppose that <my-element> is a dropdown which gives user option to choose some value.
Solution: Still no native solution in custom-element specs(v0 and v1).
Since Custom Elements doesn't support data binding so we need a sugaring layer for that (e.g., Polymer or SkateJS) as mention by #tony in the comment.

Try by converting object to JSON string,
var object = { key1: 111, key2: 222};
JSON.stringify(object);
Then when you want to get the value, parse it back to object
JSON.parse(this.getAttribute('data'));

Custom Elements does not modify the standard HTML element attribute behaviour which is always of type string.
Because of that, you shoud instead either:
Send a change Event from your custom element that will trigger the onchange handler.
Register you object/function callback via a custom element method.
Modify a state attribute that will be observed (with Mutation Observer) by its container.
If you want to use attribute anyways you can always use eval().
Example with solution 1 with call to changeHandler():
//define custom element
class DropDown extends HTMLElement
{
connectedCallback ()
{
var span = this.querySelector( 'span' )
//define ul list
var ul = this.querySelector( 'ul' )
ul.onclick = ev => {
if ( this.value != ev.target.textContent )
{
this.value = ev.target.textContent
this.setAttribute( 'value', this.value )
span.textContent = this.value
this.dispatchEvent( new CustomEvent( 'change' ) )
}
}
//show or hide
this.onclick = ev => ul.classList.toggle( 'show' )
}
}
customElements.define( 'drop-down', DropDown )
drop-down {
position: relative ;
cursor: pointer ;
display: inline-block ;
}
drop-down > span {
border: 1px solid #aae ;
padding: 2px 5px ;
}
drop-down > ul {
position: absolute ;
top: 4px ; left: 5px ;
list-style: none ;
outline: 1px solid #aae ;
padding: 0 ;
display: inline-block ;
opacity: 0 ;
transition: all 0.3s ease-out ;
background: white ;
visibility: hidden ;
z-index: 10 ;
}
drop-down > ul.show {
opacity: 1 ;
visibility: visible ;
}
drop-down > ul > li {
padding: 2px 5px ;
}
drop-down > ul > li:hover {
background: lightgoldenrodyellow ;
}
<drop-down onchange="changeHandler()">
<span>select value</span>
<ul>
<li>Chrome</li>
<li>Firefox</li>
<li>Opera</li>
<li>Safari</li>
</ul>
</drop-down>
<script>
function changeHandler ()
{
console.log( 'changeHandler()', event.target.value )
}
</script>

Related

JS Variable Cannot Change Element Properties

Quick question here, I encountered this problem today while practicing some JS. I wanted to create a basic prototype to loop through a "div" background-color array on click, but I realized that assigning the element property to a variable (instead of using the event target) impedes me to change the actual values.
This is the JS code:
let colors = ["blue", "yellow", "orange", "red"]
let n = 1;
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
let classes = e.target.className;
classes = colors[n];
n++;
console.log(classes);
if (n >= 4) {n = 0;}
});
});
So, changing the actual e.target.className works just fine, but trying to change the assigned "classes" variable does nothing. I feel like this may be a matter of specificity, or JS being unable to access the actual property values, or some akin beginner mistake.
e.target.className passes by value when you have let classes = e.target.className, so classes contains a copy of its data. Changing classes just changes the copy, rather than what's stored in e.target.classname.
Actually, you are not changing the value of e.target.className. What you do, is assigning the value of e.target.className to the variable/let-binding classes. To assign one of the color values to the className property, the assignment has to be the other way around:
e.target.className = colors[n];
let classes = e.target.className will assign the current string value of className to classes. And while you can assign a new colour value to classes that won't assign the new colour value to the className property of the element. For that you want to explicitly assign it: e.target.className = colors[i].
You may also want to remove the need to add a event listener to all the elements. Event delegation allows you to add one listener to a parent element which captures events from its child elements as they "bubble up" the DOM.
Here's an over-wrought example:
const colors = ['blue', 'yellow', 'orange', 'red'];
// Cache the elements first, and add a listener to
// the container
const counter = document.querySelector('.index');
const container = document.querySelector('.container');
container.addEventListener('click', handleClick);
let count = 0;
function handleClick(e) {
// Check to see if the element that was clicked
// was a div element
if (e.target.matches('.container div')) {
// Update the counter element, the `className` value,
// and the `textContent of the "box", and then update
// the count value
counter.textContent = `Color index: ${count}`;
e.target.className = colors[count];
e.target.textContent = colors[count];
count = count < colors.length - 1 ? ++count : 0;
}
}
.container { display: grid; gap: 0.4em; grid-template-columns: repeat(2, 50px); }
.container div { display: flex; justify-content: center; align-items: center; height: 50px; width: 50px; border: 1px solid lightgray; }
.container div:hover { cursor: pointer; border: 1px solid darkgray; }
.blue { background-color: lightblue; }
.yellow { background-color: #ffff00; }
.orange { background-color: darkorange; }
.red { background-color: red; }
.index { margin-top: 0.5em; }
<div class="container">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="index">Color index:</div>

Changing div width with a button

i am trying to learn JS, React and Node.js. Now i am trying to change the width of a div with a button but i'm getting always the same error, that it "Cannot read property 'style' of null
Here is my JS
var sideNav = document.getElementById("side-nav");
var linksSideNavTop = document.getElementById("links-side-nav-top");
var linksSideNavBottom = document.getElementById("links-side-nav-bottom");
function openMenuTablet() {
if (sideNav.style.width === '70px') {
sideNav.style.width = '300px';
linksSideNavTop.style.display = "block";
linksSideNavBottom.style.display = "block";
} else {
sideNav.style.width = '70px';
linksSideNavTop.style.display = "none";
linksSideNavBottom.style.display = "none";
};
}
Here the CSS
#side-nav {
position: fixed;
background-color: #454545;
height: 100%;
width: 70px;
}
Have a look at this working example:
function changeWidth() {
document.getElementById('myContainer').style.width = '500px'
}
#myContainer {
width: 250px;
height: 50px;
background: #aaa;
}
<div id="myContainer"></div>
<button onclick="changeWidth()">Change width</button>
Above answers Are correct, also I can show my example how to do that by .classList.toggle()
const box = document.querySelector(".box")
const button = document.querySelector("button")
button.addEventListener("click", () => {
box.classList.toggle("toggle")
})
.box{
width: 100px;
height: 100px;
background: red;
margin-bottom: 10px;
transition: 0.5s
}
.box.toggle{
width: 200px;
}
<div class="box"></div>
<button>Toggle Class List</button>
if your class list has toggle your div width increases and and in Javascript .toggle method removes and adds your passed classList which is "toggle" in our case
Are you sure you have declared the items with the id´s of links-side-nav-top, links-side-nav-bottom and side-nav?
You can try to console.log(linksSideNavTop); to check existence of id.
The error "Cannot read property 'style' of null" means that the code to get the element (document.getElementById()) isn't retrieving (founding) an element with that particular ID.
I would suggest for you to double check that you don't have duplicated ID with the same exact name and also to move the document.getElementById() inside your function.
function openMenuTablet() {
var sideNav = document.getElementById("side-nav");
var linksSideNavTop = document.getElementById("links-side-nav-top");
var linksSideNavBottom = document.getElementById("links-side-nav-bottom");
if (sideNav.style.width === '70px') {
// ...
} else {
// ...
};
}
Also a good alternative to document.getElementById() would be document.querySelector():
document.querySelector("h1"); // Search html tag "h1"
document.querySelector(".rect"); // Search class name "rect"
document.querySelector("#square"); // Searcd id name "square"
For last, to know if you are really founding and retrieving the intended element you can for example console.log() the var element:
var sideNav = document.getElementById("side-nav");
console.log(sideNav);
// ...

How do you get the style of focussed HTML element using JavaScript?

I would like to get the computed style of an element when it is focussed. I am using puppeteer and I would like to compare the CSS before focus to the CSS after focus. I can successfully get focus on an element using element.focus() and can confirm it's focussed by checking the active element (document.activeElement). However, the computed style (getComputedStyle()) for this element is the same as before focus, which is not what I expected.
Is there a way to get the CSS of the element after focus?
To be clear, I am writing a JavaScript tool which can scrape any website to check if the outline or border of an element under focus has sufficient contrast with the background on the page (such that it conforms with accessibility guidelines https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html). To do this I need to be able to get the colour of the outline/border when the element is under focus. Normally a focus outline would be defined in the CSS under :focus { ... } so this is what I wish to retrieve for any element.
you need to pay attention to the order of code in the script. if you change the style before the getComputedStyle() you'll get the change.
exmple:
document.querySelector("input").addEventListener("focus", (e) => {
console.log(window.getComputedStyle(e.target, null).color);
e.target.style.color = "red";
console.log(window.getComputedStyle(e.target, null).color);
e.target.style.color = "blue";
})
<input type="text" style="color: blue;">
As getComputedStyle returns a live CSSStyleDeclaration, you have to record the CSS styles by yourself manually to keep the CSS before focus.
Here's a working example:
let allInputs = document.querySelectorAll('input')
let beforeFocusStyles = Array.from(allInputs).reduce(function(final, elem) {
final[elem.dataset.identifier] = (function() {
let liveStyle = window.getComputedStyle(elem)
let value = {}
for (let key in liveStyle) {
value[key] = liveStyle[key]
}
return value
})()
return final
}, {})
Array.from(allInputs).forEach(elem => {
elem.onfocus = function() {
let afterFocusStyle = window.getComputedStyle(elem)
let differenceInStyle = (function() {
let beforeFocusStyle = beforeFocusStyles[elem.dataset.identifier]
let differences = []
for (let key in afterFocusStyle) {
if (beforeFocusStyle[key] !== afterFocusStyle[key]) {
differences.push([key, beforeFocusStyle[key], afterFocusStyle[key]])
}
}
return differences
})()
differenceInStyle.forEach(difference => {
console.log(difference[0], difference[1], difference[2])
})
}
})
.type1 {
outline: 1px solid black;
border: none;
}
.type1:focus {
outline: 2px solid red;
border: none;
}
.type2 {
outline: 1px solid blue;
border: none;
}
.type2:focus {
outline: 2px solid green;
border: none;
}
<input type="text" class="type1" data-identifier="1">
<input type="text" class="type2" data-identifier="2">

Find specific div in forEach

I'm wondering how to select a child when the callback of a forEach does not directly refer to the desired element.
const $searchField = document.querySelectorAll('.js-search');
if ($searchField.length > 0) {
$searchField.forEach(function($el) {
// $el = .js-search
$el.addEventListener('click', function(event) {
$el.classList.add('is-active');
});
// Here, i need to target the input, not .js-search
$el.addEventListener('keyup', function(event) {
if ($el.value.length > 0) {
$el.classList.add('is-active');
} else {
$el.classList.remove('is-active')
}
//});
});
}
Here, $el refers to the parent .js-search so obviously the keyup can not work. I would like to make sure to select the input, but I'm not sure how to do it properly.
Demo is available on Codepen!
The goal is to keep the state is-active when the search is completed (has at least 1 character).
Thanks
Just select the child input's .value, instead of the $el's .value:
const $searchField = document.querySelectorAll('.js-search');
$searchField.forEach(function($el) {
// $el = .js-search
$el.addEventListener('click', function() {
$el.classList.add('is-active');
});
const input = $el.querySelector('input');
input.addEventListener('keyup', function() {
if (input.value.length > 0) {
$el.classList.add('is-active');
} else {
$el.classList.remove('is-active')
}
});
});
Note that there's no need for the if check if you don't want - calling forEach on an empty collection won't throw an error, it just won't iterate over anything.
You also might consider including a polyfill for NodeList.prototype.forEach (if you aren't already), since older browsers don't support it. (Alternatively, transform the collection into an array, or use Array.prototype.forEach.call)
A bit opinion-based, but there's no need to prefix variable names with $ - this isn't PHP. Often, a $ prefix is done when indicating that something is a jQuery ($) collection. If you're doing DOM manipulation, probably best not to use a variable name that starts with $, to avoid confusion for future readers of your code.
You can use this.querySelector('input'); where this will the parent element and querySelector will get the first input child element
const $searchField = document.querySelectorAll('.js-search');
if ($searchField.length > 0) {
$searchField.forEach(function($el) {
$el.addEventListener('click', function(event) {
$el.classList.add('is-active');
let $input = this.querySelector('input');
$input.addEventListener('keyup', function(et) {
console.log(et.target.value)
});
});
});
document.addEventListener('click', function(event) {
if (!$(event.target).closest('.js-search').length) {
closeSearchs();
}
$('.js-search.is-active').not($(event.target).closest('.js-search')).removeClass('is-active');
});
}
function closeSearchs() {
$searchField.forEach(function($el) {
$el.classList.toggle('is-active');
});
}
.field-search {
position: relative;
&.is-active {
.search-input {
background-color: rgba(0, 0, 0, .35);
color: white;
}
}
.search-input {
background: none;
box-shadow: none;
border-radius: 4px;
color: black;
font-size: 1.2rem;
font-weight: 400;
height: 100%;
outline: none;
padding-left: 4.5rem;
transition: background-color .2s;
width: 365px;
&::placeholder {
color: black;
font-size: 1.2rem;
font-weight: 400;
}
&.is-active {
background-color: rgba(255, 255, 255, .1);
}
}
}
<div class="field-search js-search">
<input type="search" class="search-input" placeholder="Search...">
</div>

Show if hidden jQuery

I have a div that I show if the mouse is hovering over a different div, then when the mouse leaves the div is hidden.
In my mousemove callback function I say
$('#divToShow').show(), however I'm wondering if this is inefficient as mousemove events are fired very frequently, and if that div is already shown then it's calling show() for no reason.
Would it be more efficient to check if the div is hidden, and only show it then? Like this:
if ($('#divToShow').is(":hidden")){
$('#divToShow').show();
}
Or another solution would be to have a boolean variable that is set to true the first time the div is shown, then set to false on mouseleave.
Does anyone have any information on an efficient way to show a div in a mousemove function?
Since you're using Jquery, a more efficient way to do what you want is using a .hover(handlerIn, handlerOut) callback, so you don't need to worry about creating a flag or something like that.
The handlerIn will be triggered only one time, when the mouse enter on the target div (as you can see on the console.log('show') call). The handlerOut will also be executed only one time, when the mouse leaves the target div.
On the example below, when the mouse hover #div-b the #div-a content will be visible and when it leaves, the content will be hidden:
$(function() {
$('#div-b').hover(
function() {
console.log('show');
$('#div-a').show();
},
function() {
console.log('hide');
$('#div-a').hide();
}
);
});
#div-a {
display: none;
padding: 20px;
}
.wrapper {
margin: auto;
background-color: darkcyan;
color: white;
height: 100px;
margin-bottom: 10px;
}
#div-b {
padding: 20px;
height: 50px;
background-color: black;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div id="div-a">
I'm visible!
</div>
</div>
<div id="div-b">
Hover me
</div>
First of All , i let you inspect source code of $.fn.show, then , see , at the end , my answer :
show: function() {
return showHide( this, true );
}
And showHide source code is :
function showHide( elements, show ) {
var display, elem, hidden,
values = [],
index = 0,
length = elements.length;
for ( ; index < length; index++ ) {
elem = elements[ index ];
if ( !elem.style ) {
continue;
}
values[ index ] = dataPriv.get( elem, "olddisplay" );
display = elem.style.display;
if ( show ) {
// Reset the inline display of this element to learn if it is
// being hidden by cascaded rules or not
if ( !values[ index ] && display === "none" ) {
elem.style.display = "";
}
// Set elements which have been overridden with display: none
// in a stylesheet to whatever the default browser style is
// for such an element
if ( elem.style.display === "" && isHidden( elem ) ) {
values[ index ] = dataPriv.access(
elem,
"olddisplay",
defaultDisplay( elem.nodeName )
);
}
} else {
hidden = isHidden( elem );
if ( display !== "none" || !hidden ) {
dataPriv.set(
elem,
"olddisplay",
hidden ? display : jQuery.css( elem, "display" )
);
}
}
}
// Set the display of most of the elements in a second loop
// to avoid the constant reflow
for ( index = 0; index < length; index++ ) {
elem = elements[ index ];
if ( !elem.style ) {
continue;
}
if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
elem.style.display = show ? values[ index ] || "" : "none";
}
}
return elements;
}
So :
You don't need to check if it is hidden or not $().is(':hidden') because it is already checked in show function (See : if ( elem.style.display === "" && isHidden( elem ) ) { ....)
Use mouseEnter to set a flag (or just show) and on mouseLeave set the flag to the opposite value (or just hide). mouseMove may not be best event since it may be fired more than desired. You may not even keep track of a flag if you just want to show and hide an element.
Can't you use .hover() and .toggle() functions? is mousemove must?
https://jsfiddle.net/tvwpdxum/1/
A better solution would be to use a mouseEnter and mouseLeave like this snippet below:
This is in javaScript but will give you a better idea. If you want it in JQuery and having problems with it I can write it on Pluncker for you. Hope this will help
function show_hide(id) {
var e = document.getElementById(id);
if (e == null){
} else {
if (!e.classList.contains('showClass'))
e.className += " showClass";
else
e.className = "myclass";
}
}
.myclass {
opacity: 0;
margin-top: 25px;
font-size: 21px;
text-align: center;
-webkit-transition: opacity 1s ease-in;
-moz-transition: opacity 1s ease-in;
-o-transition: opacity 1s ease-in;
-ms-transition: opacity 1s ease-in;
transition: opacity 1s ease-in;
}
.showClass{ opacity: 1 }
<div onmouseover="show_hide('deletebutton')" onmouseout="show_hide('deletebutton')">
// image
<div class="myclass" id="deletebutton">DELETE</div>
</div>
Updated
function show() {
$("#deletebutton").show();
}
function hide() {
$("#deletebutton").hide();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div onmouseover="show()" onmouseout="hide()">
// image
<div class="myclass" id="deletebutton" style="display:none;">DELETE</div>
</div>

Categories