So, this question is based on a piece of homework that I'm currently working on. I was provided with an HTML document and two stylesheets. The HTML document is really simple:
<html>
<head>
<title>DOM-scripting: change style</title>
<link rel="stylesheet" href="style/style1.css" id="stylesheet"/>
</head>
<body>
<nav class="chooseStyle">
<input type="button" id="style1Button" value="Style 1"/>
<input type="button" id="style2Button" value="Style 2"/>
</nav>
<article>
<h1>Style change using DOM-scripting</h1>
<p> sample text </p>
<p> sample text </p>
<p> sample text </p>
</article>
<nav class="links">
<ul>
<li><a href="link">school name</a></li>
<li><a href="link">school name</a></li>
</nav>
<script src="scriptToUse.js"></script>
</body>
</html>
style1.css consists of the following:
body {
background-color: #676632;
}
input[type="button"] {
color: #F5F265;
background-color: #9B9302;
}
article {
margin-left: 150px;
margin-right: 150px;
text-indent: 20px;
color: #C9C892;
}
h1 {
color: #EEF1BA;
text-align: center;
}
a {
color: #A1FA56;
text-decoration: none;
font-weight: bold;
}
ul {
padding: 0;
}
li {
list-style-type: none;
}
Style2.css consists of the following:
body {
color: #EEEEAA;
}
input[type="button"] {
color: #9B9302;
background-color: #F5F265;
}
article {
margin-left: auto;
margin-right: auto;
text-align: justify;
color: #A3A163;
}
h1 {
color: #AAAA66;
text-align: left;
}
a {
color: #CDCF9B;
}
ul {
padding: 0;
}
li {
list-style-type: none;
}
The first part of the exercise states that I need to create a javascript file which allows the stylesheet for the webpage to change according to the button that was clicked. The first button refers to the first stylesheet and the second refers to the second stylesheet. I figured this out fairly easy and everything works fine.
However, the second part of the exercise states the following: "Now edit the javascript file so that only the style of the article is changed when the buttons are clicked. DO NOT edit the CSS file." Meaning that the href for the stylesheets should still be changed, like in the first part, but now it may only affect the style of the article element instead of the style of the whole page.
I have been searching for related questions for the past hour but nothing seems to really provide a solution. I have read some questions stating that it is impossible to apply a stylesheet only to a specific element without changing anything in the stylesheet itself. Keep in mind that I am only in my first year of college and have only just begun learning javascript, meaning that a solution should not be that hard to find. I guess?
The Javascript code I wrote so far:
'use strict';
const handleLoad = () => {
let style1 = document.getElementById('style1Button');
let style2 = document.getElementById('style2Button');
style1.addEventListener('click', handleClick);
style2.addEventListener('click', handleClick);
};
const handleClick = (event) => {
if(event.target.value === "Style 1") {
document.getElementById("stylesheet").href = "style/style1.css";
// should only change article style
// currently still referring to the whole document
} else {
document.getElementById("stylesheet").href = "style/style2.css";
// should also only change article style
// also still referring to the whole document
}
};
window.addEventListener('load', handleLoad);
Is there any way I could block the CSS rules for all elements except the article element? Or maybe change the classes or ID's for the elements that should not be affected?
Thanks a lot in advance :)
This is what finally solved it. (code is probably not as compact and correct as it could be but it works so I'm more than happy :D):
'use strict';
const handleLoad = () => {
let style1 = document.getElementById('style1Button');
let style2 = document.getElementById('style2Button');
style1.addEventListener('click', handleClick);
style2.addEventListener('click', handleClick);
makeShadow();
};
const handleClick = (event) => {
let article = document.querySelector('article');
let shadow = article.shadowRoot;
let style = event.target.value === "Style 1" ? "style/style2.css" : "style/style1.css";
for(let node of shadow.childNodes) {
if(node.hasAttribute('href')){
node.href = style;
}
}
};
const makeShadow = () => {
let article = document.querySelector('article');
let articleClone = makeClone(article);
let shadow = article.attachShadow({mode:'open'});
let link = document.createElement('link');
link.rel = "stylesheet";
link.href = 'style/style1.css';
shadow.appendChild(articleClone);
shadow.prepend(link);
};
const makeClone = (article) => {
let clone = article.cloneNode();
clone.innerHTML = article.innerHTML;
return clone;
};
window.addEventListener('load', handleLoad);
In This case we can make use of shadowRoot
as an encapsulation for styles.
The idea is like adding a css file which contains a ton of styles to a page which only have the article
element, obviously the other styles won't have any effect.
We can do the same using a shadowRoot
put the article in it's own little world and add a file which contains more styles.
The code is heavily commented. If you have any question leave a comment :)
First on load we prepare the shadow Element:
const handleLoad = () => {
// Old Code for button handlers still needed
let style1 = document.getElementById('style1Button');
let style2 = document.getElementById('style2Button');
style1.addEventListener('click', handleClick);
style2.addEventListener('click', handleClick);
// New Code Below
// Select the article from the page
const article = document.querySelector('article');
// Create an empty div Element to work as a bucket
const placeholderElement = document.createElement('div');
// set it's id attribute to select it later
placeholderElement.setAttribute('id', 'placeholderElementShadowRot')
// replace the article with our div in the document
// the article element is not completly lose at this point
// we still have in the variable article
article.replaceWith(placeholderElement);
// attache a shadow to the div element
const shadow = placeholderElement.attachShadow({ mode: 'open' });
// sets the shadow's innerHTML equal to article outerHTML
shadow.innerHTML = article.outerHTML;
// create a link
const link = document.createElement('link');
// set it's href attribute to the first style file
link.setAttribute("href", 'style1.css');
// set the rel attribute
link.setAttribute("rel", "stylesheet");
// add the link to the shadow
shadow.appendChild(link)
};
And on button click
const handleClick = (event) => {
// Select our div we created before using the id
const placeholderElement = document.querySelector('#placeholderElementShadowRot');
// since we already attached a shadow
// we can use it through the property .shadowRoot
const shadow = placeholderElement.shadowRoot;
// based on which button we add the relevant style file
if (event.target.value === "Style 1") {
shadow.querySelector('link').setAttribute("href", "style1.css")
} else {
shadow.querySelector('link').setAttribute("href", "style2.css")
}
};
Note: You have a typo in your JS, The handler is called handleclick
lower case click
but you're adding an uppercase Click
handleClick
to addEventListener