How to get <body> in React JS without DOM Manipulation - javascript

I'm rebuilding a project in React JS which i did in Vanilla JS, i came across to add, remove classes from <body> tag, i am also doing something when screen resizes.
I did this, this is a related piece of code, there's actually bunch of code:
...
document.body.onclick = (e) => {
const { lengua } = e.target.dataset
setLenguaOpen(lengua ? true : false) // if the target i or you clicked has data-lengua attr (a button has), a dropdown shows up
switchTheme // in another component i'm toggle 'switchTheme' (it is boolean), and here what i'm doing depending on it
? document.body.classList.add('dark-theme') // in my css, i'm changing values of variables i defined for colors and background-colors if body.dark-theme
: document.body.classList.remove('dark-theme')
}
window.addEventListener('resize', (e) => {
if (e.target.innerWidth >= 850) {
document.body.classList.remove('menu-open')
}
})
...
<FaBars onClick={() => document.body.classList.toggle('menu-open')}/> // a menu is located on 0 top: -60px when screen size < 850, i'm transforming body to y:60px to slide down the menu
It's working fine, but i think DOM Manipulation is preferably not used by most developers in React. I could do it differently to achieve what i wanted, but now i'm curios to learn how good React developers do what is done by DOM M..., especially the above example. If you have a better approach, i would love to hear it.
If you wanna see what exactly i'm talking, see that project: axelreid-store.netlify.app.
The code i shown is related to header section (languages dropdown, theme switcher, and menu toggle)
I'm sorry if this is a weird question!
Thanks!

Avoiding direct DOM manipulation when working with React is a good general principle, however there are quite a few cases when it is the best or only approach for solving a problem.
Accessing elements above the React application's top level element requires direct DOM manipulation. This also — definitely — is not going to clash with React's own DOM updating methods because they only affect elements inside the application.

Related

Intercept click event on the element that is removed from the DOM with JavaScript

I have a little weird situation. I need to perform a click event (intercept), here is my case. By default element (div) are removed from the DOM, that means that div only appears when the user clicks the button, below is an image of that button
When the user clicks the button, content appears, image below:
What am trying to achieve?
Am trying to achieve this, when users click the button I want to add style to one div that appears, I simulate that on the image below:
So far my code looks like this:
var buttons = document.querySelectorAll('button.h5p-image-hotspot');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function(){
var myNodelist = document.querySelectorAll('.h5p-image')
var i;
for (i = 0; i < myNodelist.length; i++) {
myNodelist[i].style.width = "300px";
}
});
}
But this doesn't work, can anybody try to help me with this?
#cloned already gave a good hint IMHO. Overriding the stylesheet would be the cleanest way to achieve this, since you want all images to be affected anyway.
With H5P, you have multiple options now that all have their pros and cons.
Patching
You can patch the code for instance. Then all you'd have to do is to add
.h5p-image-hotspot-popup-body-fraction.h5p-image {
width: 300px;
}
at https://github.com/h5p/h5p-image-hotspots/blob/master/styles/image-hotspots.css#L303 on your system. However, whenever you update the H5P Image Hotspot content type, those changed would have to be re-applied since the official code would override them.
Forking
You could do the same in a fork, meaning you'd have to clone the complete content library files and change the machineName in library.json (https://github.com/h5p/h5p-image-hotspots/blob/master/library.json#L10) and the occurrences in the code itself (it's the class name as well). The downside here is that you'd have to merge updates of the original codes to your fork if you want to benefit from bugfixes or new features.
Hooking
Luckily, H5P offers hooks to add scripts or to alter the stylesheets which is described at https://h5p.org/documentation/for-developers/visual-changes You will have to choose the proper plugin for your host system (e.g. WordPress, Drupal or moodle - the native core version of moodle 3.8+ doesn't feature hooks unfortunately, only the H5P plugin does), implement the alter_styles function to load your CSS file (see examples at https://h5p.org/documentation/for-developers/visual-changes) and then put the aforementioned CSS to that file. Now, whenever H5P loads content, it will use your stylesheet to override its own without the need to touch the original H5P code, so you can benefit from updates while not having to re-apply patches.

Modifying or adding classes to individual components within core wordpress blocks

I am having difficulty modifying or adding additional classes to individual components of wordpress blocks. I essentially just want to add a class or classes to some of the pieces that make up some of the core wordpress blocks to do things like add bootstrap styling.
An example of this would be the image gallery block. It works well enough for my purposes, but I may want to do something like add an "img-fluid" or "img-thumbnail" class to the images that are within that gallery.
I have looked through the documentation for block filters and I feel as though my answer is going to lie somewhere in there, I'm just not sure where yet.
I have tried using the blocks.getBlockDefaultClassName filter, but this adds a classname to the entire block as opposed to individual pieces that make up that block (such as the images in my gallery example).
This is the example they give for it in the documentation:
// Our filter function
function setBlockCustomClassName( className, blockName ) {
return blockName === 'core/code' ?
'my-plugin-code' :
className;
}
// Adding the filter
wp.hooks.addFilter(
'blocks.getBlockDefaultClassName',
'my-plugin/set-block-custom-class-name',
setBlockCustomClassName
);
I feel as though blocks.getSaveContent.extraProps might do what I need it to, but I am not sure how to use it in this way. The example they use looks like this:
function addBackgroundColorStyle( props ) {
return lodash.assign( props, { style: { backgroundColor: 'red' } } );
}
wp.hooks.addFilter(
'blocks.getSaveContent.extraProps',
'my-plugin/add-background-color-style',
addBackgroundColorStyle
);
But even if I were to add an extra property like a class name, I would think I would then need to modify the actual save and edit functions (I guess using more block filters) to then use that class name when it displays those pieces (in this case - the images), and I'm also not sure how to do that.
Any thoughts or suggestions about this would be appreciated.
There are two kind of changes one is structural change and the other one is styling change. In your scenario you need styling change but you are actually looking for structural change. You can write your own CSS that can handles your block styling on editor view and on front end.
These are the hooks that you need to enqueue your styles enqueue_block_editor_assets (enqueue your styles only on backend editor) and enqueue_block_assets (enqueue your styles on both backend and frontend).

Polymer `<custom-style>`: which line of code stops appending to DOM multiple imports of the same custom-style template module?

My project's upstream web components theme is implemented as <custom-style> elements link
I want to implement my document level overrides as a JS module (as in, avoid hardcoding into app index.html or equivalent), which on surface looks simple:
import '#vaadin/vaadin-lumo-styles/color.js';
const $template = document.createElement('template');
$template.innerHTML = `
<custom-style>
<style>
html,
:host {
--lumo-primary-color: red;
}
</style>
</custom-style>`;
document.head.appendChild($template.content);
QUESTION
Some web components used in the document also import original theme via import '#vaadin/vaadin-lumo-styles/color.js'.
I want my overrides to always cascade last (without !important hacks).
Do multiple later import '#vaadin/vaadin-lumo-styles/color.js'; calls have any potential to revert my CSS custom property overrides cascade?
Think:
original: --lumo-primary-color: hsl(214, 90%, 52%);
me: import original, override --lumo-primary-color: red;
later: can a later import of original "reset" cascade back to --lumo-primary-color: hsl(214, 90%, 52%);)?
ES6 import a file in multiple place, why the file loads once? seems to imply maybe not, but I'm struggling finding any documentation that explicitly states something one way or another about <custom-style>.
Perhaps https://github.com/Polymer/polymer/blob/v3.2.0/lib/elements/custom-style.js#L80 is the key?
GLITCH
https://glitch.com/edit/#!/roan-pizza?path=src/index.js seems to confirm repeated imports don't seem to cause a problem, but why? Is it purely due to ES6 module load caching, or is there something else to it?
EDIT drag-n-dropping <custom-style> elements around in browser inspector definitely has an effect on the cascade (colors change based on tag order), so at least loading order is confirmed to matter.
Tips: remove all for edit.
difference tags (of <custom-style>) by #id => <custom-style id="id01">.
save <custom-style> to edit => save(#id).
red (in "--lumo-primary-color") is a variable => var color = red.
remove all content of a <custom-style id="id01"> => remove_all(#id).
add a new content you need => as you did above.
I just hope that you will solve your problem. You are better than me so I will not write code.

Using addEventListener on multiple elements, avoid TypeError when particular element not found

I'm using two simple addEventListener mouseenter and mouseleave functions respectively to play and stop animations (Bodymovin/SVG animations, though I suspect that fact is irrelevant).
So, the following works fine:
document.getElementById('animationDiv').addEventListener('mouseenter', function(){
animation.play();
})
(The HTML couldn't be simpler: The relevant part is just an empty div placeholder filled by script - i.e., <div id="animationDiv"></div>.
I can place that in the same file as the one that operationalizes the animation code, or I can place it in a separate "trigger" file, with both files (and other others necessary to processing) loaded in the site footer.
The problem arises when I need to be able to set triggers for any of multiple similar animations that may or may not appear on a given page.
If only one of two animatable elements are present on a page, then one of two sets of triggers will throw an error. If the first of two such triggers is not present, then the second one will not be processed, meaning that the animation will fail. Or at least that's what it looks like to me is happening.
So, just to be clear, if I add the following two triggers for the same page, and the first of the following two elements is present, then the animation will play on mouseenter. If only the second is present, its animation won't be triggered, apparently because of the error thrown on the first.
document.getElementById('firstAnimationDiv').addEventListener('mouseenter', function(){
firstAnimation.play();
})
document.getElementById('secondAnimationDiv').addEventListener('mouseenter', function(){
secondAnimation.play();
})
At present I can work around the problem by creating multiple trigger files, one for each animation, and setting them to load only when I know that the animatable element will be present, but this approach would get increasingly inefficient when I am using multiple animations per page, on pages whose content may be altered.
I've looked at try/catch approaches and also at event delegation approaches, but so far they seem a bit complicated for handling this simple problem, if appropriate at all.
Is there an efficient and flexible standard method for preventing or properly handling an error for an element not found, in such a way that subsequent functions can still be processed? Or am I missing something else or somehow misreading the error and the function failure I've been encountering?
WHY I PICKED THE ANSWER THAT I DID (PLUS WORKING CODE)
I was easily able to make the simple, directly responsive answer by Baoo work.
I was unable to make the answers below by Patrick Roberts and Crazy Train work, though no doubt my undeveloped js skills are entirely at fault. When I have the time, or when the issue next comes up for me in a more complex implementation (possibly soon!), I'll take another look at their solutions, and see if I can either make them work or if I can formulate a better question with fully fledged coding examples to be worked through.
Finally, just to make things clear for people who might be looking for an answer on Bodymovin animations, and whose js is even weaker than mine, the following is working code, all added to the same single file in which a larger set of Bodymovin animations are constructed, relieving me of any need to create separate trigger files, and preventing TypeErrors and impaired functionality.
//There are three "lets_talk" animations that can play - "home," "snug," and "fixed"
//and three types of buttons needing enter and leave play and stop triggers
let home = document.getElementById('myBtn_bm_home');
if (home) home.addEventListener('mouseenter', function() {
lets_talk_home.play();
});
if (home) home.addEventListener('mouseleave', function() {
lets_talk_home.stop();
});
let snug = document.getElementById('myBtn_bm_snug');
if (snug) snug.addEventListener('mouseenter', function() {
lets_talk_snug.play();
});
if (snug) snug.addEventListener('mouseleave', function() {
lets_talk_snug.stop();
});
let fixed = document.getElementById('myBtn_bm_fixed');
if (fixed) fixed.addEventListener('mouseenter', function() {
lets_talk_fixed.play();
});
if (fixed) fixed.addEventListener('mouseleave', function() {
lets_talk_fixed.stop();
});
At typical piece of underlying HTML (it's generated by a PHP function taking into account other conditions, so not identical for each button), looks like this at the moment - although I'll be paring away the data-attribute and class, since I'm not currently using either. I provide it on the off-chance that someone sees something significant or useful there.
<div id="letsTalk" class="lets-talk">
<a id="myBtn" href="#"><!-- a default-prevented link to a pop-up modal -->
<div class="bm-button" id="myBtn_bm_snug" data-animation="snug"></div><!-- "snug" (vs "fixed" or "home" is in both instances added by PHP -->
</a>
</div>
Obviously, a more parsimonious and flexible answer could be - and probably should be - written. On that note, correctly combining both the play and stop listeners within a single conditional would be an obvious first step, but I'm too much of a js plodder even to get that right on a first or second try. Maybe later/next time!
Thanks again to everyone who provided an answer. I won't ask you to try to squeeze the working solution into your suggested framework - but I won't ask you not to either...
Just write your code so that it won't throw an error if the element isn't present, by simply checking if the element exists.
let first = document.getElementById('firstAnimationDiv');
if (first) first.addEventListener('mouseenter', function() {firstAnimation.play();});
You could approach this slightly differently using delegated event handling. mouseover, unlike mouseenter, bubbles to its ancestor elements, so you could add a single event listener to an ancestor element where every #animationDiv is contained, and switch on event.target.id to call the correct play() method:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
switch (event.target.id) {
case 'firstAnimationDiv':
return firstAnimation.play();
case 'secondAnimationDiv':
return secondAnimation.play();
// and so on
}
});
You could also avoid using id and use a more semantically correct attribute like data-animation as a compromise between this approach and #CrazyTrain's:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
// assuming <div data-animation="...">
// instead of <div id="...">
switch (event.target.dataset.animation) {
case 'first':
return firstAnimation.play();
case 'second':
return secondAnimation.play();
// and so on
}
});
First, refactor your HTML to add a common class to all of the placeholder divs instead of using unique IDs. Also add a data-animation attribute to reference the desired animation.
<div class="animation" data-animation="first"></div>
<div class="animation" data-animation="second"></div>
The data- attribute should have a value that targets the appropriate animation.
(As #PatrickRobers noted, the DOM selection can be based on the data-animation attribute, so the class isn't really needed.)
Since your animations are held as global variables, you can use the value of data-animation to look up that variable. However, it would be better if they weren't global, but were rather in a common object.
const animations = {
first: null, // your first animation
second: null, // your second animation
};
Then select the placeholder elements by class, and use the data attribute to see if the animation exists, and if so, play it.
const divs = document.querySelectorAll("div.animation");
divs.forEach(div => {
const anim = animations[div.dataset.animation];
if (anim) {
anim.play(); // Found the animation for this div, so play it
}
});
This way you're guaranteed only to work with placeholder divs that exist and animations that exist.
(And as noted above, selection using the data attribute can be done const divs = document.querySelectorAll("div[data-animation]"); so the class becomes unnecessary.)

jquery ui.slider: add click event to display/hide handle

I am completely new to javascript and jquery. My programming knowledge is... nonexistent, I just started some days ago with some simple tasks like replacing a CSS class or toggling a div. So I want to apologize if I'm treading on someones toes by asking newbie-questions in here. But I hope that someone can help me and solve my problem.
I need to implement some sort of visual analog scale for a survey; ui.slider is perfect for that one. But I need the handle to be hidden by default. When the user clicks on the slider, the handle shall appear in the proper position. That should be fairly simple - at least I hope so - by just hiding the handle with CSS and changing it by a click event on the slider.
I use the following piece of code to wrap a normal div (a div is needed in my understanding to apply the jquery slider.js) to my input elements (they should be - at least visually - replaced by the slider) and pass the value of the slider to the input elements (needed for passing the values to a form). that works properly. (I do that instead of just putting a div in my DOM by default because I cannot influence some PHP scripts that will generate form elements of the survey and so on)
$(function () {
$.each($('.slider'),
function () {
obj = $(this);
obj.wrap('<div></div>');
obj.parent().slider({
change: function (event, ui) {
$('input', this).val(ui.value);
}
});
});
});
Hiding the slider-handle can be done by CSS as described above by changing style properties of a.ui-slider-handle. but when I add a normal click event to the slider (.ui-slider) that changes CSS properties of the handle, nothing happens. As far as my basic knowledge goes it should have something to do with the click event not working on generated DOM elements. Am I right with that one? And if yes: how can I solve this problem? Could someone provide me a piece of code for my function and explain it so I might comprehend what's exactly going on?
I read a tutorial about events on learningjquery.com but I have not made enough progresses the last few days since I started working with JS/jquery to comprehend the steps and translate it into my example/problem. And I am running out of time (I need this for a survey I have to make asap, that's why I hope someone could give me a hint so I can solve this little issue somehow).
Any reason you can't just include the show on the change event rather than a click? It's a bit cleaner code-wise rather than including a whole new event.
$(function() {
$('.slider').wrap('<div></div>').parent().slider({
change: function(event, ui) {
$('input', this).val(ui.value);
$('.ui-slider-handle').show();
}
});
});
Also, there was a bit of redundancy in the code - most jQuery functions return the object itself, so you can chain them. And you don't need that each function, since most jQuery functions also, when applied to a collection, run on all of them :)

Categories