Similar to this question:
How to prevent flickering with web components?
But different in that I can't just set the inner HTML to nothing until loaded because there is slotted content, and I don't wish to block rendering the page while it executes the web component JS.
I thought I could add CSS to hide the element, and then the init of the webcomponent unhides itself, but then that CSS snippet needs to included where ever the web component is used, which is not very modular, and prone to be forgotten
I am working on modal component, here's the code (although I don't think its particularly relevant:
<div id="BLUR" part="blur" class="display-none">
<div id="DIALOGUE" part="dialogue">
<div id="CLOSE" part="close">
X
</div>
<slot></slot>
</div>
</div>
const name = "wc-modal";
const template = document.getElementById("TEMPLATE_" + name);
class Component extends HTMLElement {
static get observedAttributes() { return ["open"]; } // prettier-ignore
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
if (this.initialised) return; // Prevent initialising twice is item is moved
this.setupEventListners();
this.init();
this._upgradeProperty("open");
this.initialised = true;
}
init() {}
get(id) {
return this.shadowRoot.getElementById(id);
}
_upgradeProperty(prop) {
/*
Setting a property before the component has loaded will result in the setter being overriden by the value. Delete the property and reinstate the setter.
https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
*/
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
// Setup Event Listeners ___________________________________________________
setupEventListners() {
this.get("CLOSE").addEventListener("click", () => this.removeAttribute("open"));
this.get("BLUR").addEventListener("click", () => this.removeAttribute("open"));
// If the dialogue does not handle click, it propagates up to the blur, and closes the modal
this.get("DIALOGUE").addEventListener("click", (event) => event.stopPropagation());
}
// Attributes _____________________________________________________________
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case "open":
// Disabled is blank string for true, null for false
if (newValue === null) this.hideModal();
else this.showModal();
}
}
// Property Getters/Setters _______________________________________________
get open() { return this.hasAttribute("open"); } // prettier-ignore
set open(value) { value ? this.setAttribute("open", "") : this.removeAttribute("open"); } // prettier-ignore
// Utils & Handlers _______________________________________________________
showModal() {
this.get("BLUR").classList.remove("display-none");
// Disable scrolling of the background
document.body.style.overflow = "hidden";
}
hideModal() {
this.get("BLUR").classList.add("display-none");
// Renable scrolling of the background
document.body.style.overflow = "unset";
}
}
window.customElements.define(name, Component);
Q: How do I hide a web component until the browser knows what to do with it?
A: Here's a solution with outside CSS. Make use of the :defined pseudo class:
class X extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).append(document.createElement('slot'));
}
}
foo.onclick = () => {
customElements.define('ab-cd', X);
foo.disabled = true;
foo.textContent = 'registered!';
}
ab-cd:not(:defined) { display: none; }
<ab-cd>text</ab-cd>
<button id="foo">click to register component</button>
I have tried to see where :defined can cause a FOUC
Only when you apply the display:none too late
<my-element>:not(:defined) { display:none }</my-element>
<style>
my-element:not(:defined) {
border: 2px solid red;
}
my-element:defined {
background: pink;
}
</style>
<style id="STYLE"></style>
<button id="BTN_STYLE">click to style component</button>
<button id="BTN_DEFINE">click to register component</button>
<script>
BTN_STYLE.onclick = () => {
STYLE.innerHTML = `my-element:not(:defined) {display:none}`;
BTN_STYLE.remove();
}
BTN_DEFINE.onclick = () => {
customElements.define('my-element', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).innerHTML = `constructed`;
}
connectedCallback(){
setTimeout(() => this.shadowRoot.innerHTML = `connected after 3s`,3e3);
}
});
BTN_DEFINE.remove();
}
</script>
is there a way to change a background-image on conditions?
Im trying to build a weatherapp and I will change the background of a div.
Like:
Weather api returns:
rainy -> change background image to rain.jpg
snow -> change background image to snow.jpg
sunny -> change background to sunny.jpg
etc.
I've tried multiple ways already but nothing worked.
<template>
<div :class="{background_image}"></div>
</template>
<script>
export default {
// ...
data() {
return {
backgroundImage: 'snow'
},
computed:{
background_image : function () {
switch(this.backgroundImage) {
case 'snow':
return 'bg-snow';
case 'rainy':
return 'bg-rainy';
case 'sunny':
return 'bg-sunny';
default:
return 'bg-default';
}
}
},
methods: {
apiCall() {
//api call update background image according to the response
this.backgroundImage = response ; // response contains sunny text
}
},
// ...
}
</script>
<style>
.bg-sunny{
background-image: url('sunny.jpg');
}
.bg-snow{
background-image: url('snow.jpg');
}
.bg-rainy{
background-image: url('rainy.jpg');
}
.bg-default{
background-image: url('default.jpg');
}
</style>
You can achieve this behavior by looking up the image in an object, where you have defined your key and the corresponding image value. In addition, you need to tell webpack to require that media file. That require tells webpack to treat this file as a request.
<template>
<div :style="{ backgroundImage: `url(${weatherTypes[getWeatherType()]})` }"></div>
</template>
<script>
export default {
// ...
data() {
return {
weatherTypes: {
snow: require('some/path/snow.png'),
sun: require('some/path/sun.png'),
// ...
}
}
},
methods: {
getWeatherType() { return 'snow'; },
},
// ...
}
</script>
Reproduction link
In Vue.js 2, I used to be able to do this call my initialize() method from outside my class like this:
<!DOCTYPE html>
<head>
<script src="https://unpkg.com/vue#next"></script>
<title>test</title>
</head>
<body>
<div id="app">
<div>{{message}}</div>
</div>
<script>
const app = Vue.createApp({
data() {
return {
message: 'initial value'
}
},
methods: {
initialize() {
this.message = 'changed value';
}
}
});
app.mount("#app");
app.initialize();
</script>
</body>
</html>
But the above code in Vue.js 3 gets the error:
Uncaught TypeError: app.initialize is not a function
How can I do this in Vue.js 3?
You need to call the method on the component instance rather than the application instance:
const app = Vue.createApp({
methods: {
initialize() { /* ... */ }
}
});
const vm = app.mount("#app");
vm.initialize();
The object passed to createApp contains the options for the root component. The mount method returns the root component instance.
There's an example of this in the documentation:
https://v3.vuejs.org/guide/data-methods.html#methods
You may also want to take a look at the following page, which explains the difference between the application instance and the root component instance.
https://v3.vuejs.org/guide/instance.html
Hot to use style element in shadowRoot.adoptedStyleSheets?
<style id=style>
h1 { color: red }
</style>
<script>
customElements.define('test-elem', class extends HTMLElement {
constructor () {
super()
const shadow = this.attachShadow({ mode: 'closed' })
shadow.adoptedStyleSheets = [style.sheet]
shadow.append(template.content.cloneNode(true))
}
})
</script>
https://developers.google.com/web/updates/2019/02/constructable-stylesheets
You must create a Constructable Sheet with new CSSStyleSheet()
You can only attach content on an 'open' shadowRoot
<style id="sourcestyle">
h1 {
color: green
}
</style>
<my-element></my-element>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
const sheet = new CSSStyleSheet();
sheet.replace(document.getElementById("sourcestyle").innerHTML);
super() // returns this/element scope
.attachShadow({ mode: 'open' }) // both sets and returns this.shadowRoot
.innerHTML = `<h1>Hello World!</h1>`;
this.shadowRoot.adoptedStyleSheets = [sheet];
}
})
</script>
Maybe better to construct that re-usable stylesheet outside your component,
and without creating a global <style>
const sheet = new CSSStyleSheet();
sheet.replace(`h1{color:green}`);
Without Constructable StyleSheet
(Constructable CSS doesn't work in all Browsers yet)
otherwise you might as well move the <style> with:
(or cloneNode it when you want to re-use it)
this.shadowRoot.append(document.getElementById("sourcestyle"));
Since it is a global sheet, you may want to disable it for the main DOM
<style id="sourcestyle" onload="this.disabled=true">
h1 {
color: green
}
</style>
and then undo the disabled once it is in a shadowDOM:
this.shadowRoot.getElementById("sourcestyle").disabled=false;
How to write this type of code in loop? Actually I don't want to write the same same line again and again, Is their any way to compress this code? can we write this code in loop?
function showCandidates()
{document.getElementById("cand9").style.display="block";
document.getElementById("cand10").style.display="block";
document.getElementById("cand11").style.display="block";
document.getElementById("cand12").style.display="block";
document.getElementById("cand13").style.display="block";
document.getElementById("cand14").style.display="block";
document.getElementById("cand15").style.display="block";
document.getElementById("hide_cand").style.display="block";
document.getElementById("view_cand").style.display="none";
}
function hideCandidates()
{document.getElementById("cand9").style.display="none";
document.getElementById("cand10").style.display="none";
document.getElementById("cand11").style.display="none";
document.getElementById("cand12").style.display="none";
document.getElementById("cand13").style.display="none";
document.getElementById("cand14").style.display="none";
document.getElementById("cand15").style.display="none";
document.getElementById("hide_cand").style.display="none";
document.getElementById("view_cand").style.display="block";
}
I suggest this way:
var show_ids = ["cand9", "cand10", "cand11"] // ... and so on
funciton showCandidates() {
for (var index in show_ids) {
var id = show_ids[index];
document.getElementById(id).style.display="none";
}
}
similar for hideCandidates
You should assign to your html elements a class for example
<div class="hideable" >content </div>
Then either you use JQuery or plain javascript to get all the elements that have the "hideable class attribute:
document.getElementsByClassName('hideable')
or
>$(".hideable")
Since your the two previous methods will return an array, you will have to loop through the array and apply the appropriate style attribute.
Firstly, this can be all encapsulated into one function. The function can take a parameter to assign to the display property. And obviously use some if statement in there to deal with the view_cand elements' display.
I would look into using jquery for this though, it makes selecting DOM elements (especially sets of DOM elements) a damn site easier.
I'd write the code for you here but I don't know anything about the elements you're selecting or the structure to your DOM.
Something like this?
for(i=0;i<candNumber;i++){
id= "cand" + i;
document.getElementById(id).style.display="block";
}
Try this .It'll hide/show ( the wayas you requested) by parameter given to function.
setVisibilityByClass("visible"/"invisible") - shows/hides by changing class
setVisibility("block"/"none") - shows/hides by changing styles directly
CHOOSE ONLY ONE.
css classes:
.vissible{ display: block; } .invissible{ display: none; }
Js functions:
function setVisibility(val) {
var not = new Array;
not["none"] = "block";
not["block"] = "none";
for (i = 9; i <= 15; i++){
document.getElementById("cand" + i).style.display = val;
}
document.getElementById("hide_cand").style.display = val;
document.getElementById("view_cand").style.display = not[val];
}
function setVisibilityByClass(val) {
var not = new Array;
not["invissible"] = "vissible";
not["vissible"] = "invissible";
for (i = 9; i <= 15; i++){
document.getElementById("cand" + i).setAttribute("class", val);
}
document.getElementById("hide_cand").setAttribute("class", val);
document.getElementById("view_cand").setAttribute("class", not[val]);
}
I hope this helps:
(function() {
"use strict";
var candidates = {
idx: 0,
getElement: function(id) { return document.getElementById(id); },
toggle: function(elmnts, obj) {
var idx = candidates.idx,
getElement = function(id) { return candidates.getElement(id); };
if (elmnts.length) {
while ( idx < elmnts.length ) {
getElement(elmnts[idx]).style.display = obj.display;
idx++;
}
}
}
};
var idsToHide = [
"cand9", "cand10", "cand11", "cand12",
"cand13", "cand14", "cand15", "hide_cand"
];
var idsToShow = [
"cand9", "cand10", "cand11", "cand12",
"cand13", "cand14", "cand15", "hide_cand"
];
function showCandidates() {
candidates.toggle(idsToShow, {
display: "block"
});
candidates.toggle(["view_cand"], { display: "none" });
}
function hideCandidates() {
candidates.toggle(idsToHide, {
display: "none"
});
candidates.toggle(["view_cand"], { display: "block" });
}
})();
Easy to do with jQuery:
$(document).ready(function(){
$("#candidates").toggle(function (){
$(this).text('Hide Candidates');
$.each($('.candidate'), function() {
$(this).show();
});
}, function() {
$(this).text('Show Candidates');
$.each($('.candidate'), function() {
$(this).hide();
});
});
});
HTML:
Show Candidates
<div class='candidate' id='1'>
<h1>Hello</h1>
</div>
<div class='candidate' id='2'>
<h1>Hello</h1>
</div>
<div class='candidate' id='3'>
<h1>Hello</h1>
</div>
CSS:
.candidate { display: none }
Here's a JS fiddle: http://jsfiddle.net/vbh5T/
If you don't want to use jQuery then please ignore my answer.
(1) First of all, doing these kinds of lookups is best done with jquery. Apart from being easier (see code below), it also allows you pre-calculate the set of elements to act on. This matters, because lookups by ID scan the whole document tree. Accordingly, the more elements in the page, the slower it is to recalculate the set of elements to act on.
(2) Rather than setting individual properties, it is much better to use a css class.
<style>
.invisible {display:none !important;}
</style>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8"> // <![CDATA[
$(document).ready(function(){
var hide = function(i) {i.addClass('invisible');};
var show = function(i) {i.removeClass('invisible');};
var candidates = $("#cand9, #cand10 /* etc. [...] */");
/* or, if you rejig this to set a class on all candidate elements:
var candidates = $(".candidate"); */
var hide_cand = $("#hide_cand");
var view_cand = $("#view_cand");
function showCandidates()
{
show(candidates);
show(view_cand);
hide(hide_cand);
}
});
// ]]>
</script>
I leave the corresponding hideCandidates as an exercise for the reader.