So I'm trying to set up some references of some DOM elements in my JS. However, for some reason, they return the correct elements, but then return null shortly after. So i tried wrapping them in an if check to make sure the elements aren't null to prevent this. however, it still seems to be happening.
resizer.js
// Define DOM elements
const resizer = document.getElementById('resizer');
let btnMob;
let btnTablet;
let btnLaptop;
let btnDesktop;
// Check buttons exist and asign to variables if they do
if (document.getElementById('resizer-mob') !== null) {
btnMob = document.getElementById('resizer-mob');
}
if (document.getElementById('resizer-tablet') !== null) {
btnTablet = document.getElementById('resizer-tablet');
}
if (document.getElementById('resizer-laptop') !== null) {
btnLaptop = document.getElementById('resizer-laptop');
}
if (document.getElementById('resizer-desktop') !== null) {
btnDesktop = document.getElementById('resizer-desktop');
}
// Define string constants
const DESKTOP_CLASS = 'resizer--desktop';
const LAPTOP_CLASS = 'resizer--laptop';
const TABLET_CLASS = 'resizer--tablet';
const MOB_CLASS = 'resizer--mob';
// Check elements have been asign correctly
console.log('mob is ', btnMob);
console.log('tablet is ', btnTablet);
console.log('laptop is ', btnLaptop);
console.log('desktop is ', btnDesktop);
// Update resizer to display desktop
btnDesktop.addEventListener('click', () => {
console.log("DESKTOP");
resizer.classList.remove(LAPTOP_CLASS);
resizer.classList.remove(TABLET_CLASS);
resizer.classList.remove(MOB_CLASS);
resizer.classList.add(DESKTOP_CLASS);
});
// Update resizer to display laptop
btnLaptop.addEventListener('click', () => {
console.log("LAPTOP");
resizer.classList.remove(DESKTOP_CLASS);
resizer.classList.remove(TABLET_CLASS);
resizer.classList.remove(MOB_CLASS);
resizer.classList.add(LAPTOP_CLASS);
});
// Update resizer to display tablet
btnTablet.addEventListener('click', () => {
console.log("TABLET");
resizer.classList.remove(DESKTOP_CLASS);
resizer.classList.remove(LAPTOP_CLASS);
resizer.classList.remove(MOB_CLASS);
resizer.classList.add(TABLET_CLASS);
});
// Update resizer to display mobile
btnMob.addEventListener('click', () => {
console.log("MOB");
resizer.classList.remove(DESKTOP_CLASS);
resizer.classList.remove(LAPTOP_CLASS);
resizer.classList.remove(TABLET_CLASS);
resizer.classList.add(MOB_CLASS);
});
Have you tried != instead of !==?
And it returns null or undefined?
If it's undefined you need to use:
typeof(element) != 'undefined'
The code is behaving as expected. The first time you run the code everything works as you haven't deleted any elements. The second time you have, and so some elements return null because they don't exist anymore.
Related
I have two states defined like so:
const [productProperties, setProductProperties] = useState<
PropertyGroup[] | null
>(null);
const [originalProductProperties, setOriginalProductProperties] = useState<
PropertyGroup[] | null
>(null);
The first one is supposed to be updated through user input and the second one is used later for a comparison so that only the PropertyGroup's that have changed values will be submitted via API to be updated.
I have done this a thousand times before, but for some reason when I change the name value for a PropertyGroup and update the state for 'productProperties' like so:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i].name = {
...update[i].name,
[selectedLocale]: e.currentTarget.value,
};
setProductProperties([...update]);
}
}
The state of originalProductProperties also updates. Why? setOriginalProductProperties is never called here, I am also not mutating any state directly and I use the spread operator to be sure to create new references. I am lost.
Preface: It sounds like the two arrays are sharing the same objects. That's fine provided you handle updates correctly.
Although you're copying the array, you're modifying the object in the array directly. That's breaking the main rule of state: Do Not Modify State Directly
Instead, make a copy of the object as well:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update); // (No need to *re*copy the array here, you've already done it at the top of the function)
}
}
Or, since you have that i !== -1 check there, we could copy the array later so we don't copy it if we don't find the group matching itemId:
(e, itemId) => {
const i = productProperties.findIndex((group) => group.id === itemId);
if (i !== -1) {
const update = [...productProperties];
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update);
}
}
FWIW, in cases where you know there will be a match, map is good for this (but probably not in this case, since you seem to indicate the group may not be there):
(e, itemId) => {
const update = productProperties.map((group) => {
if (group.id === itemId) {
// It's the one we want, create the replacement
group = {
...group,
[selectedLocale]: e.currentTarget.value,
};
}
return group;
});
setProductProperties(update);
}
Or sometimes you see it written with a conditional operator:
(e, itemId) => {
const update = productProperties.map((group) =>
group.id === itemId
? { // It's the one we want, create a replacement
...group,
[selectedLocale]: e.currentTarget.value,
}
: group
);
setProductProperties(update);
}
I am having trouble accessing a object in my scene. I've figured out the reason I wasn't able to access it was because the Raytracer was loading before the world had finished loading. It works fine for objects added by code, but it doesn't work when looking for objects imported by GLTFLoader.
I have set up something to trigger an event when all the objects have been loaded. But I am unable to access the variable inside of the check in my update function.
setRayCaster()
{
this.rayCaster = new THREE.Raycaster()
this.foundButton = this.main.scene.children.find(child => child.name === 'Test')
this.objectsToTest = [this.main.scene.children.find(child => child.name === 'Test')]
this.resources.on('ready', () =>
{
this.UNABLETOACCESSTHIS = this.scene.getObjectByName('Trackpad')
})
}
update()
{
this.rayCaster.setFromCamera(this.cursor, this.camera.view)
this.intersects = this.rayCaster.intersectObject(this.foundButton)
UNABLETOACCESSTHIS.here
for(const object of this.objectsToTest)
{
object.material.color.set('#ff0000')
}
for(const intersect of this.intersects)
{
intersect.object.material.color.set('#0000ff')
}
// console.log(this.intersects)
}
}
Intertwining Functions
I've been trying to make functions different but both have have the same original function.
Here are my selectors:
const popControl = document.getElementById("pop");
const popHeader = document.getElementById("header");
const popButton = document.getElementById("close");
const popTitle = document.getElementById("title");
const popBody = document.getElementById("body");
const popControl2 = document.getElementById("pop2");
const popHeader2 = document.getElementById("header2");
const popButton2 = document.getElementById("close2");
const popTitle2 = document.getElementById("title2");
const popBody2 = document.getElementById("body2");`
With all my id's selected, I create my first function, called verifyPosition:
function verifyPosition(circleLeftPosition, circleRightPosition, moveBy) {
console.log(circleLeftPosition, circleRightPosition);
if (circleLeftPosition == "550px" && circleRightPosition == "75px") {
popButton.addEventListener("click", closePosition);
openPosition();
}
}
Now, I must create the other, malfunctioning func, verifyPosition2:
function verifyPosition2(circleLeftPosition, circleRightPosition, moveBy) {
console.log(circleLeftPosition, circleRightPosition);
if (circleLeftPosition == "550px" && circleRightPosition == "75px") {
popButton2.addEventListener("click", closePosition2);
openPosition2();
}
}
For some reason, my verifyPosition2 does not work, and it does not matter what you put in it.
This brings me to my final questions:
Why is it not working?
And how can I make it work?
Thanks and thanks all!
Hint: [!ch.edit] tag means I have edited this question.
You may try the below approach
function verifyPosition(circleLeftPosition, circleRightPosition, moveBy, selector_extension) {
if (circleLeftPosition == "550px" && circleRightPosition == "75px") {
const popButton = document.getElementById("close" + selector_extension);
// Checkout the below code, if verifyPosition is called multiple times on same element, there is chance of setting multiple click events on popButton
popButton.addEventListener("click", function (e) { return closePosition(e, selector_extension); });
openPosition(selector_extension);
}
}
function closePosition(e, selector_extension) {
// access the required elements using `selector_extension`
}
function openPosition(selector_extension) {
// access the required elements using `selector_extension`
}
I am getting this error while using tab visibility . In which 'msHidden' and 'webkitHidden' does not exist on type 'Document'
const TabVisibilityComponent = () => {
let hidden = null;
let visibilityChange = '';
const [action, setAction] = useState('show');
if (typeof document.hidden !== 'undefined') {
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
const handleVisibilityChange = () => {
if (document.hidden) {
setAction('hide');
} else {
setAction('show');
}
};
useEffect(() => {
document.addEventListener('visibilitychange', handleVisibilityChange, false);
window.addEventListener(
'focus',
function() {
setAction('show');
},
false,
);
window.addEventListener('blur', function() {
setAction('hide');
}, false);
}, [visibilityChange]);
return <p></p>;
};
export default TabVisibilityComponent;
I have tried adding 'readonly msHidden: boolean' in the Document interface in 'lib.dom.d.ts' file, because hidden works fine and it is declared as readonly boolean in that file so tried the same for msHidden and webkitHidden but it is not working. I have seen this code used by many but don't know what's the mistake with mine. Would be much happy if anyone got a solution for this !
The code comes straight from the MDN docs so you're right that the code itself is fine. The issues are:
The lib.dom definitions do include browser-specific properties.
One of the ways to get around this is to pass the document as an argument to a function which defines those properties as optional.
const hiddenPropName = (document: Document & {msHidden?: boolean; webkitHidden?: boolean}) => {
if (typeof document.hidden !== "undefined" ) {
return "hidden";
} else if (typeof document.msHidden !== "undefined") {
return "msHidden";
} else if (typeof document.webkitHidden !== "undefined") {
return "webkitHidden";
}
// not sure how to handle when none exist
}
But as far as Typescript is concerned, document.hidden always exists.
It looks like there is hardly any difference between the numbers for prefixed and un-prefixed support on caniuse. So You could just ditch the msHidden and webkitHidden.
You are ignoring the hidden and visibilityChange variables that you set.
The first section of the code in the example is just setting the variables hidden and visibilityChange to the names of the properties so that they can access the correct properties later on. The example uses document[hidden] where hidden is a variable, but you used document.hidden which always gets the property "hidden" and never "msHidden" or "webkitHidden".
They are also using a variable for the event in the document.addEventListener.
If you want to support the prefixes then you need to use variable property and event names. If not, just delete that whole first code block.
You are not adapting the code properly to React.
You want to make sure that your useEffect hook has the correct dependencies and you ideally should use a cleanup function in your useEffect to remove the event listeners on unmount.
The hardest part here is making sure that you aren't creating any stale closures that will use outdated values of document when evaluating. I'm actually not sure without playing with it if this is correct:
const TabVisibilityComponent = () => {
const [action, setAction] = useState("show");
useEffect(() => {
const hide = () => setAction("hide");
const show = () => setAction("show");
const handleVisibilityChange = () => {
document.hidden ? hide() : show();
};
document.addEventListener(
"visibilitychange",
handleVisibilityChange,
false
);
window.addEventListener("focus", show, false);
window.addEventListener("blur", hide, false);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
window.removeEventListener("focus", show);
window.removeEventListener("blur", hide);
};
}, [setAction]);
return <p></p>;
};
I need to capture last focused input and paste something in it later.
I've already managed to catch last focused HTML input field (using jQuery on focusin event) or CKEDITOR editor (using CKEDITOR API on focus event). Because I store this last object in one var lastFocusedInput (jQuery object or CKEDITOR editor object), now I need to determine if it is CKEDITOR or jQuery object, due to they have different methods to paste data in it.
Any ideas how to do this in a more sophisticated way than testing it like that:
function isjQueryObject(o)
{
return (o && (o instanceof jQuery || o.constructor.prototype.jquery));
}
function isCKEditorObject(o)
{
return (o && (typeof CKEDITOR !== undefined) && (typeof o.insertHtml !== undefined));
}
EDIT on 2018-03-29
In the meantime I've ended up with type testing as below due to the need of reuse in other areas of the code.
function TypeTester()
{
var result = function (test)
{
return test ? true : false;
};
// jQuery [object Function]
this.jQuery = function (o)
{
return result(o
&& (o instanceof jQuery || o.constructor.prototype.jquery)
);
};
// CKEDITOR [object Object]
this.CKEDITOR =
{
object: function (o)
{
return result(o
&& o.replaceClass === 'ckeditor'
);
},
instance: function (o)
{
return result(o
&& o.insertHtml !== undefined
&& o.insertText !== undefined
);
},
};
};
var isTypeOf = new TypeTester();
var lastFocusedInput = new Object(
{
object: null,
insert: function (content)
{
if (!this.object) return;
switch (true)
{
case isTypeOf.jQuery(this.object) :
this.object.insertAtCaret(content);
break;
case isTypeOf.CKEDITOR.instance(this.object) :
this.object.insertHtml(content);
break;
}
},
});
As you know the typeof object while storing then store it like
var lastFocusedInput= { type:'jQuery', theObject: theObjectToStore};
And access it like so
if(lastFocusedInput.type == 'jQuery'){
//get jquery object -> lastFocusedInput.theObject
}else{
//get CKEDITOR object -> lastFocusedInput.theObject
}
Or use two containers
If object to store is jQuery
var $lastFocusedInput = theObjectToStore;
var CKElastFocusedInput= null;
or vice versa
while accessing
if($lastFocusedInput){// use jquery API on object }
else{ // use CKEDITOR API on object }