Create a height and width attribute for a custom HTML element - javascript

I'm working with creating custom html tags/elements that will then be dynamically added via JavaScript/jQuery. My end goal is to create a custom element like <xt-vertical-layout height="30px" width="50px"></xt-vertical-layout> where height and width will set the rendered size. I have figured out how to create the custom elements and have custom attributes that can hold data, but I cannot seem to find a way to modify the rendering. To be clear, I am not having an issue with CSS height and width not working. My issue is that I could potentially have hundreds of the elements all with varying sizes that are unknown untill being injected into the DOM well after the document has loaded.
I had at first looked into CSS with attribute selectors but it does not seem to be able to carry the value of the attribute down into the CSS rule. Similarly I had looked into the attr() function of CSS but it seems this only works on content. I found a bit of documentation on this function being allowed for all attributes in CSS4 though I then read something saying CSS4 will not be released and instead CSS will begin to be updated per module and so it seems this has not made it into the current standard.
Any light that could be shed on this subject would be greatly appreciated. I feel like there is some way to do this but am not too sure how. To be clear though, I'm not wanting something like parsing through the markup with JavaScript/jQuery and modifying that element with $().css() or something like that, as this then modifies the actual markup. My desire is to have the markup unchanged and the attributes modify the rendering, similar to how the <img/> tag works.
EDIT:
I do not mind doing this with JS/jQuery in the constructor for the custom element if it can be done there without modifying an inline style attribute, leaving the markup unchanged.

You would normally handle this in your CustomElement's class connectedCallback, where you will be able to access the node as this, and its DOM methods like getAttribute() if it inherits from HTMLElement.
class VerticalLayout extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.style.width = this.getAttribute('width');
this.style.height = this.getAttribute('height');
}
}
customElements.define('xt-vertical-layout', VerticalLayout);
// dynamically inserted
const elem = document.createElement('xt-vertical-layout');
elem.setAttribute('width', '25px');
elem.setAttribute('height', '25px');
document.body.append(elem);
xt-vertical-layout {
border: 1px solid;
display: inline-block;
}
<xt-vertical-layout height="30px" width="50px"></xt-vertical-layout>
<xt-vertical-layout height="50px" width="150px"></xt-vertical-layout>
<xt-vertical-layout height="10px" width="50px"></xt-vertical-layout>
And if you really do not want to modify the serialized DOM, then you can append a stylesheet inside the shadowDOM of your elements, and set a rule there. But note that doing so, you will loose support for older browsers as this feature can't be polyfilled.
class VerticalLayout extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'});
const style = document.createElement('style');
style.textContent = `:host {
width: ${ this.getAttribute('width') };
height: ${ this.getAttribute('height') };
}`;
shadow.append(style);
}
}
customElements.define('xt-vertical-layout', VerticalLayout);
// dynamically inserted
const elem = document.createElement('xt-vertical-layout');
elem.setAttribute('width', '25px');
elem.setAttribute('height', '25px');
container.append(elem);
console.log(container.innerHTML);
xt-vertical-layout {
border: 1px solid;
display: inline-block;
}
.as-console-wrapper {max-height: 120px !important;}
<div id="container">
<xt-vertical-layout height="30px" width="50px"></xt-vertical-layout>
<xt-vertical-layout height="50px" width="150px"></xt-vertical-layout>
<xt-vertical-layout height="10px" width="50px"></xt-vertical-layout>
</div>

Custom tags will have a display of inline which cannot have width and height. You need have a display of block or inline block.
addContent('Here is some content', 200, 50);
function addContent(content, width, height) {
const elm = document.createElement('xt-vertical-layout');
elm.setAttribute('style', `width: ${width}px; height: ${height}px;`);
elm.innerText = content;
document.body.append(elm);
}
xt-vertical-layout {
display: inline-block;
color: white;
padding: 5px;
background-color: red;
}

Related

Nested custom element not rendered when parent element is instantiated programmatically

For a Single Page Application I have registered some custom elements to the customElementRegistry which are rendered from string literals via insertAdjacentHTML(). Nesting these elements is no problem when done in html. But when I try to instantiate a parent custom element programmatically starting from customElements.get('entry-page') or document.createElement('div', {is: 'entry-page'}), the nested custom elements do not render as if not defined at all. Even customElements.whenDefined('nested-element').then(...) doesn't help.
function registerCustomElement(name, tpl) {
class cls extends HTMLDivElement {
connectedCallback() {
this.insertAdjacentHTML('afterbegin', tpl)
}
};
customElements.define(name, cls, {
extends: 'div'
})
}
let tpl1 = '<p class="box">inner HTML</p>'
let tpl2 = '<article class="outer">outer HTML<inner-html></inner-html></article>'
let tpl3 = '<article class="outer">revised outer HTML<div is="inner-html"></div></article>'
registerCustomElement('inner-html', tpl1)
registerCustomElement('outer-html', tpl2)
registerCustomElement('outer-html-revised', tpl3)
document.querySelector('main')
.insertAdjacentHTML('beforeend', '<div is="outer-html"></div>')
document.querySelector('main')
.insertAdjacentHTML('beforeend', '<div is="outer-html-revised"></div>')
body {
font-family: 'sans serif';
font-size: 0.8em
}
.box {
padding: 1em;
color: white;
background-color: olive;
}
.outer {
padding: 0.5em;
margin-bottom: 0.5em;
border: 2px solid darkorange
}
<h1>Embedded in html</h1>
<h2>version with 'inner-html' element</h2>
<outer-html></outer-html>
<h2>version with 'div is inner-html' element</h2>
<outer-html-revised></outer-html-revised>
<h1>Embedded programmatically</h1>
<main></main>
Each element is defined separately as a ES6 Module. I am currently not using shadow DOM.
Why a custom element does fully render including all nested elements when embedded in HTML, but not when implemented in the DOM programmatically?
Edit As Danny Engelman stated, the only cross-browser option is to extend HTMLElement.
While in HTML the custom elements may be referenced by there names even in the string literals, the custom elements are based upon, this seems not to be true, when they are generated programmatically. In this case the spec requires something like <div is="custom-element-name">, but it wouldn't be cross-browser, as Apple only supports HTMLElement.

getComputedStyle() returns nothing, but getComputedStyle().getPropertyValue() returns as expected

I am trying to move an element from the light DOM to the shadow DOM, but when I do so the styling isn't copying over. I tried to fix this by setting the newElement.style = window.getComputedStyle(elem), but this hasn't seemed to work. The styles should be:
.card {
color: #ff0;
font-size: 3rem;
font-weight: 600;
border: 3px solid blueviolet;
background-color: greenyellow;
}
but the styles don't apply and when I print the getComputedStyle() to console what I see is:
all the values are empty
However, when I loop through the properties of getComputedStyle() with .getPropertyValue() like so:
for(let property of style){
console.log(`property: ${property}, value: ${style.getPropertyValue(property)}`);
}
what I get in the console is:
the correct values
So I'm confused as to why getComputedStyle() doesn't contain the values, but using getComputedStyle().getPropertyValue() returns the correct values. I'm sure I'm missing something obvious, as I couldn't find another post about this anywhere.
Any help would be greatly appreciated, thanks in advance.
EDIT: I've taken the code provided by Danny below and modified it to better show the issue I'm facing:
<style>
.card {
color: yellow;
background: green;
}
</style>
<my-element>
<div class="card">lightDOM reflected to shadowDOM</div>
</my-element>
<script>
customElements.define("my-element", class extends HTMLElement {
constructor(){
super().attachShadow({mode:"open"}).innerHTML = ``;
}
connectedCallback() {
setTimeout(() => { // wait till innerHTML is parsed
let card = this.children[0]; // Get the light DOM Card element
this.shadowRoot.appendChild(card.cloneNode(true)); // Append it to the shadowDOM
let style = window.getComputedStyle(card); // Get style of the Light DOM Card
this.shadowRoot.querySelector('.card').style = style; // Set the ShadowDOM card style equal to the Light DOM Style
console.log(style);
console.log(style.color); // yellow = rgb:255,255,0
console.log(style.background); // green = rgb:0,128,0
card.remove(); // Remove the card from the Light DOM to prevent duplication
})
}
})
</script>
Notice that the styling above doesn't apply even though it seems to be exactly as the docs specify:
"The returned object is the same CSSStyleDeclaration type as the object returned from the element's style property. However, the two objects have different purposes:
The object from getComputedStyle is read-only, and should be used to inspect the element's style — including those set by a element or an external stylesheet.
The element.style object should be used to set styles on that element, or inspect styles directly added to it from JavaScript manipulation or the global style attribute."
https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle#description
From MDN Documentation:
The Window.getComputedStyle() method returns an object containing the values of all CSS properties of an element, after applying active stylesheets and resolving any basic computation those values may contain. Individual CSS property values are accessed through APIs provided by the object, or by indexing with CSS property names.
It's stated that you need to use API functions, such as getPropertyValue() to get the value of it.
Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
If you want to print all of the CSS styles from a specific element you may just iterate all the attributes like this:
function dumpCSSText(element){
var s = '';
var o = getComputedStyle(element);
for(var i = 0; i < o.length; i++){
s+=o[i] + ': ' + o.getPropertyValue(o[i])+';\n';
}
return s;
}
var e = document.querySelector('.card');
console.log(dumpCSSText(e));
.card {
color: #ff0;
font-size: 3rem;
font-weight: 600;
border: 3px solid blueviolet;
background-color: greenyellow;
}
<div class="card"></div>
property style is read-only so you can't assign anything to it;
(I stand corrected per comments; you can assign a value, but it
will override all values)
The innerHTML of Custom Elements is not parsed yet when the connectedCallback fires. So getting styles of its children with getComputedStyle is an operation on non-existing elements.
If you reflect the lightDOM contents to a <slot> in shadowDOM, there is no need to copy styles as the styling from lightDOM is reflected
<style>
.card {
color: yellow;
background: green;
}
</style>
<my-element>
<div class="card">lightDOM reflected to shadowDOM</div>
</my-element>
<script>
customElements.define("my-element", class extends HTMLElement {
constructor(){
super().attachShadow({mode:"open"}).innerHTML = `<slot></slot>`
}
connectedCallback() {
setTimeout(() => { // wait till innerHTML is parsed
let card = this.querySelector(".card"); // in lightDOM!
let style = window.getComputedStyle(card);
console.log(style.color); // yellow = rgb:255,255,0
console.log(style.background); // green = rgb:0,128,0
})
}
})
</script>
More reading:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
::slotted CSS selector for nested children in shadowDOM slot
wait for Element Upgrade in connectedCallback: FireFox and Chromium differences

How do I change an attribute or class using only Javascript?

I would like to change the styling attribute values of all elements that have the class "post-feature" and contain an attribute value of "http"
So the div element will look like the following:
<div class="post-feature" style="backgroundimage:url(http://local.test.com/test_image.jpg);">
So far the http check works. But I am not able to set the attribute value.
I have the following code
var features = document.getElementsByClassName(".post-feature")
[0].getAttribute("style");
if (features.includes("http")) {
features.setAttribute("background-color", "orange");
} else {
alert('no change');
}
You can use querySelectorAll('.post-feature[style*="http"]') to find those elements.
Then simply iterate through them and i.e. set their background color with
element.style.backgroundColor = 'orange';
Now, if you want to make sure you only target elements having a background-image and http, you can use this selector:
querySelectorAll('.post-feature[style*="http"][style*="background-image"]')
Also, by adding an i (or I) just before the end bracket [style*="http"i], the value will be compared case-insensitively.
window.addEventListener('load', function() {
var elements = document.querySelectorAll('.post-feature[style*="http"]');
for (var i = 0; i < elements.length; i++) {
elements[i].style.backgroundColor = 'orange'; /* add propert value */
/* replace class
elements[i].className = 'myClass';
*/
/* add a class
elements[i].classList.add('myClass');
*/
}
/* temp log */
console.log('Found ', elements.length,' element(s)');
})
div {
height: 40px;
background-color: gray;
}
div + div {
margin-top: 10px;
}
<div class="post-feature" style="background-image:url(http://local.test.com/test_image.jpg);"></div>
<div class="post-feature"></div>
<div class="post-feature" style="background-image:url(http://local.test.com/test_image.jpg);"></div>
<div class="post-feature"></div>
Updated
To only change styling, like colors etc., you don't even need a script, you can use CSS alone
div {
height: 40px;
background-color: gray;
}
div + div {
margin-top: 10px;
}
/* for elements that contain "http" and "background-image" */
.post-feature[style*="http"i][style*="background-image"i] {
background-color: orange;
}
<div class="post-feature" style="background-image:url(http://local.test.com/test_image.jpg);"></div>
<div class="post-feature"></div>
<div class="post-feature" style="background-image:url(HTTP://local.test.com/test_image.jpg);"></div>
<div class="post-feature"></div>
As a note, and as discussed in a few comments, if to make sure it is the background-image property that also contain the http in its url(), you can adjust the selector to this, which as well can be used without any script, as a CSS rule
.post-feature[style*="background-image:url(http"i] {
background-color: orange;
}
The above selector can of course also be used in the first sample, like this
querySelectorAll('.post-feature[style*="background-image:url(http"i]')
First, you can use querySelctorAll() with a CSS query that selects the elements with the class you desire and, in most cases, you should use this instead of getElementsByClassName() as that returns a "live node list" that causes the DOM to be re-scanned every time you access it.
Next, setAttribute() is for setting HTML element attributes. You are asking to change the value of a CSS property. While that could be accomplished with setAttribute('style', value), it is very "old-school" and not the best approach, nor is getAttribute('style') the best way to read a CSS property value (it won't work if the CSS was set from a style sheet).
Also, your code is trying to access: backgroundimage, but the property is accessed as background-image when working in CSS and backgroundImage when accessing it via JavaScript.
To access the inline styles applied to an HTML element, just access the style property of that element, followed by the name of the CSS property you are interested in. For example:
var bColor = element.style.backgroundColor;
If the style has been applied to the element from an internal style sheet or an external style sheet, the above approach won't work for you and you'll need to get it another way, via window.getComputedStyle():
var bColor = window.getComputedStyle(element, null).backgroundColor;
But, note that getComputedStyle() doesn't always return the same value that you set - - it's the value after the browser has computed all factors. In this case, even paths that you wrote as relative references (without the "http") will be returned as absolute paths (with the http).
So, here is a modern approach that correctly checks only the background-image CSS property for the presence of http.
NOTE: This solution tests for http specifically in the background-image property. Unlike most of the other answers given, this code will correctly ignore http in other CSS properties besides background-image. Examine the CSS of the last div to see this in action.
// querySelectorAll() is more efficient than getElementsByClassName()
var features = document.querySelectorAll(".post-feature");
// Loop over the list
for(var i = 0; i < features.length; i++){
// Get access to the background-image property (called backgroundImage from JavaScript) value,
// convert that value to lower case and check to see if "http" is in that value
if(features[i].style.backgroundImage.toLowerCase().indexOf("http") > -1){
// Set the CSS background-color property (called "backgroundColor" in JavaScript) to orange:
features[i].style.backgroundColor = "orange";
// Just for testing:
features[i].textContent = features[i].style.backgroundImage;
} else {
alert("No change");
}
}
.post-feature { width:100%; height:50px; border:1px solid black; background-color:gray; color:yellow; }
<!-- The correct CSS property is "background-image", not "backgroundimage" -->
<div class="post-feature" style="background-image:url(http://local.test.com/test_image.jpg);"></div>
<div class="post-feature" style="background-image:url(test_image.jpg);"></div>
<div class="post-feature" style="background-image:url(http://local.test.com/test_image.jpg);"></div>
<div class="post-feature"
style="border-image: url('http:///images/border.png') 30 30 repeat;background-image:url(test_image.jpg);">I have "http" in one of my CSS properties, but not "background-image", so I shouldn't be orange.</div>
i think some wrong in your code, try this code
element.setAttribute("style", "background-color: orange;"); // bad
or
element.style.backgroundColor = "orange"; // good
Use element.style.backgroundColor and indexOf
var features = document.getElementsByClassName(".post-feature")[0].getAttribute("style");
if (features.indexOf("http") > -1) {
features.style.backgroundColor = "orange";
} else {
alert('no change');
}
check this fiddle
https://jsfiddle.net/vywk72j8/2/
<div class="post-feature" style="background-image:url(http://local.test.com/test_image.jpg);">
tt</div>
var feature = document.getElementsByClassName("post-feature")[0];
if (feature.style.backgroundImage.indexOf("http") !== -1) {
feature.style.backgroundColor = "orange";
} else {
alert('no change');
}
In your code, you are fetching the attribute value in features
var features = document.getElementsByClassName(".post-feature")
[0].getAttribute("style");
Here features is a string containing attribute value, not an element so you cannot use it to set value.

Achieve "::-moz-focus-inner {border:0;}" in javascript

I'd like to remove the ugly focus outline on an input button in firefox. I've tried adding ::-moz-focus-inner {border:0;} as style in my html, which works initially, but not when button elements are re-created via javascript.
I've tried:
cell.style.mozFocusInner.border = "0";
cell.style["-moz-focus-inner"] = "{border:0}";
cell.style["-moz-focus-inner"]["border"] = "0";
etc.
In general, how do I "map" css to javascript?
According to the CSS property to IDL attribute algorithm, a -moz-focus-inner would be camelCased to MozFocusInner. So you could use one of
element.style.MozFocusInner = value;
element.style.setPropertyValue('-moz-focus-inner', value);
element.style.setProperty('-moz-focus-inner', value);
element.style.setProperty('-moz-focus-inner', value, '!important');
But there is a big problem: -moz-focus-inner is not a CSS property, is a pseudo-element.
Given an element, you can read the computed styles of its pseudo-elements via getComputedStyle:
getComputedStyle(element, '::-moz-focus-inner').borderTopWidth; // 1px
However, you can't set them directly. If you want to do that, you can:
Conditionally set the desired styles in a stylesheet, and use JS to trigger that condition whenever you want. For example, add a class.
document.getElementById('enable').addEventListener('click', function() {
document.getElementById('target').classList.remove('no-focus-inner');
});
document.getElementById('disable').addEventListener('click', function() {
document.getElementById('target').classList.add('no-focus-inner');
});
.no-focus-inner::-moz-focus-inner {
border: none;
}
<ol>
<li><button id="enable">Enable inner outline</button> or <button id="disable">Disable inner outline</button></li>
<li>Press Tab key</li>
<li><button id="target">Focus me to check if I have inner outline</button></li>
</ol>
Create a new stylesheet with the desired rulesets, and append it to the document.
var styleSheet = document.createElement('style');
styleSheet.textContent = '#target::-moz-focus-inner { border: none; }';
document.getElementById('enable').addEventListener('click', function() {
if(styleSheet.parentNode) document.head.removeChild(styleSheet);
});
document.getElementById('disable').addEventListener('click', function() {
document.head.appendChild(styleSheet);
});
<ol>
<li><button id="enable">Enable inner outline</button> or <button id="disable">Disable inner outline</button></li>
<li>Press Tab key</li>
<li><button id="target">Focus me to check if I have inner outline</button></li>
</ol>
Maybe this works without javascript: https://css-tricks.com/forums/topic/button-padding-issue/
::moz-focus-inner is a pseudo-element. In this link are several ways how to modify pseudo-elements dynamically (with javascript) http://pankajparashar.com/posts/modify-pseudo-elements-css/
cited from http://pankajparashar.com/posts/modify-pseudo-elements-css/ :
<p class="red">Hi, this is a plain-old, sad-looking paragraph tag.</p>
.red::before {
content: 'red';
color: red;
}
Method 1
Write separate classes attached with pseudo element for each style and then using JavaScript or jQuery toggle between these classes.
.green::before {
content: 'green';
color: green;
}
$('p').removeClass('red').addClass('green');
...
Common css-styles (not pseudo-elements) can be modified using javascript like this:
cited from https://www.kirupa.com/html5/setting_css_styles_using_javascript.htm :
Every HTML element that you access via JavaScript has a style object. This object allows you to specify a CSS property and set its value. For example, this is what setting the background color of an HTML element whose id value is superman looks like:
var myElement = document.querySelector("#superman");
myElement.style.backgroundColor = "#D93600";
To affect many elements, you can do something as follows:
var myElements = document.querySelectorAll(".bar");
for (var i = 0; i < myElements.length; i++) {
myElements[i].style.opacity = 0;
}
In a nutshell, to style elements directly using JavaScript, the first step is to access the element. I am using the querySelector method to make that happen. The second step is just to find the CSS property you care about and give it a value. Remember, many values in CSS are actually strings. Also remember that many values require a unit of measurement like px or em or something like that to actually get recognized.

Why can't I use jQuery prop to get the CSS setting?

In the HTML file, I have created a DOM element:
<div id="colorOne"></div>
and I set the attributes in css file:
#colorOne{
position: absolute;
top: 70%;
left: 8%;
width: 36px;
height: 36px;
border-color: black;
border-width: 5px;
border-style: solid;
background-color: white;
}
However, as I command
$('#colorOne').prop('width'); $('#colorOne').prop('height');
If I want to get any attribute of the element colorOne by $.prop, it only shows undefined. But I also noticed that if I change prop to css and I can get what I want.
And if I write
<div id="colorOne" style="width:36px;"></div>
And the $.prop works.
I want to know why is that. How does the browser engine handle these two different writing methods?
(1. inline style 2. setting the attributes in .css file)
If you want to get the final, computed style with jQuery you need to use .css().
This uses getComputedStyle internally so you can use it with styles from several different sources.
Here is what jQuery does internally to do this:
function (elem, name) {
var ret, defaultView, computedStyle, width, style = elem.style;
name = name.replace(rupper, "-$1").toLowerCase();
if ((defaultView = elem.ownerDocument.defaultView) && (computedStyle = defaultView.getComputedStyle(elem, null))) {
ret = computedStyle.getPropertyValue(name);
if (ret === "" && !jQuery.contains(elem.ownerDocument.documentElement, elem)) {
ret = jQuery.style(elem, name);
}
}
// A tribute to the "awesome hack by Dean Edwards"
// WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
// which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
if (!jQuery.support.pixelMargin && computedStyle && rmargin.test(name) && rnumnonpx.test(ret)) {
width = style.width;
style.width = ret;
ret = computedStyle.width;
style.width = width;
}
return ret;
}
When you perform a .attr or .prop call you're reading an HTML attribute/property and not a style. If you read the style attribute you're only getting that and not the actual computed style from all the stylesheets etc.
css is what you want.
Try:
$('#colorOne').css('width');
$('#colorOne').css('height');
$.prop is use to access attributes like name, href, etc.
If you still want to use prop, you will have to set width, height attributes in html elements.
<div id="colorOne" width='36px'></div>
$('#colorOne').prop('width');
The above works because, width is an attribute to the element #colorOne.
If width of the element is changed by js or css(using !important) in anyway, $.prop will give you wrong answer. But $.css will give you the correct one.

Categories