I am trying to figure out a way to detect that a TextArea is resized so i can prevent it's onClick event from being fired. Reason being that, when resizing the TextArea it also fires the onClick event. However, the onClick event handler has some logic i need to skip if it's a resize.
I tried using the ResizeObserver API to detect the resize event. However, can't seem to locate the 'event' so i can do a preventDefault on it to skip the onClick event.
Not sure if ResizeObserver can be used this way.
Anyone has had this type of requirement met using REsizeObserver or any other approach?
Unfortunately there is no resize event on elements except for on the window, but I would say that this is a solid usecase to use the ResizeObserver API.
Down here I've made an attempt to build something that uses the API and creates a custom event whenever a resize has been made. Because the observer will continuously trigger on every resized pixel, I've added a debounce function in the callback. This will fire the event after 200ms of inactivity.
Then it will fire an event with the data from the resize observer which your field can listen to.
I'm not saying that you should do it like this, but it demonstrates how you could make your own resize event using this API.
const field = document.querySelector('.field');
field.addEventListener('click', event => {
event.preventDefault();
});
field.addEventListener('textarearesize', event => {
console.log('Resize triggered');
const { contentRect } = event.detail;
const { width, height } = contentRect;
field.value = `width: ${Math.floor(width)}, height: ${Math.floor(height)}`;
});
const onResizeCallback = (() => {
let initial = true;
let timeout;
return entries => {
if (initial) {
initial = false;
return;
}
clearTimeout(timeout)
timeout = setTimeout(() => {
for (const entry of entries) {
const event = new CustomEvent('textarearesize', {
detail: entry
});
entry.target.dispatchEvent(event);
}
}, 200);
}
})()
const observer = new ResizeObserver(onResizeCallback);
observer.observe(field);
<textarea class="field" name="description">Hello, I'm a textarea</textarea>
Alternative you could check if the field has a changed in either height or width whenever you release the mouse. This does not require a ResizeObserver and is less complex to create and has more predictable behavior than the example above.
const field = document.querySelector('.field');
const watchField = field => {
let width = field.offsetWidth;
let height = field.offsetHeight;
const areDimensionsChanged = (newWidth, newHeight) =>
width !== newWidth || height !== newHeight;
field.addEventListener('mouseup', event => {
const { target } = event;
const { offsetWidth, offsetHeight } = target;
if (areDimensionsChanged(offsetWidth, offsetHeight)) {
width = offsetWidth;
height = offsetHeight;
const event = new CustomEvent('textarearesize');
target.dispatchEvent(event);
}
});
}
field.addEventListener('textarearesize', event => {
console.log('Resize triggered');
});
watchField(field);
<textarea class="field" name="description">Hello, I'm a textarea</textarea>
Related
I got this Code:
const odleglosc = parseFloat(document.getElementsByClassName("dystans")[0].innerText);
const daleko = "za daleko";
if (odleglosc > 200) {
alert(daleko);
}
<span data-pafe-form-builder-live-preview="lechnarlok" class="dystans" id="dystans">500</span>
It runs fine, because at starting point there is number higher than 200 in it.
But when i change it, alert don't trigger again..
How can i solve that? :(
Im not sure about how the span value will change, so this example works with an input. The same idea could also be applied to a span tho.
<input onchange="theFunction()" data-pafe-form-builder-live-preview="lechnarlok" class="dystans" id="dystans" value="500"></input>
<script>
function theFunction() {
var odleglosc = parseFloat(document.getElementsByClassName("dystans")[0].value);
var daleko = "za daleko";
if (odleglosc > 200)
{
alert(daleko);
}
}
</script>
Here, onChange calls the function whenever the value in the input field changes.
Do You want to show alert after each change of the value? If yes, use event listener for input (not for span).
Update:
Use MutationObserver for this case.
let span = document.getElementById('dystans');
function updateValue(value) {
var daleko = "za daleko";
if (parseFloat(value) > 200)
{
alert(daleko);
}
}
// create a new instance of 'MutationObserver' named 'observer',
// passing it a callback function
observer = new MutationObserver(function(mutationsList, observer) {
let value = mutationsList.filter(x => x.target.id =='dystans')[0].target.innerHTML;
updateValue(value);
});
// call 'observe' on that MutationObserver instance,
// passing it the element to observe, and the options object
observer.observe(span, {characterData: true, childList: true, attributes: true});
span.innerHTML = '3000';
<span data-pafe-form-builder-live-preview="lechnarlok" class="dystans" id="dystans">500</span>
Source:
Detect changes in the DOM
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
I am trying to work with the Intersection Observer API. I have a function which works in my first iteration. The basic logic is that if the user scrolls down and adds or removes items from a basket, once the basket is in view again (as it is at the top of the document) then I fire an API call.
The issue is that it will not fire the function before scrolling, I want to trigger it if the item is visible or becomes visible again after scrolling (the second part is working)
Here is original js:
var observerTargets = document.querySelectorAll('[id^="mini-trolley"]');
var observerOptions = {
root: null, // null means root is viewport
rootMargin: '0px',
threshold: 0.01 // trigger callback when 1% of the element is visible
}
var activeClass = 'active';
var trigger = $('button');
var isCartItemClicked = false;
trigger.on('click', function() {
isCartItemClicked = true;
});
function observerCallback(entries, observer) {
entries.forEach(entry => {
if(entry.isIntersecting && isCartItemClicked){
$(observerTargets).removeClass(activeClass);
$(entry.target).addClass(activeClass);
isCartItemClicked = false;
console.log('isCartItemClicked and in view');
// do my api call function here
} else {
$(entry.target).removeClass(activeClass);
}
});
}
var observer = new IntersectionObserver(observerCallback, observerOptions);
[...observerTargets].forEach(target => observer.observe(target));
I have updated this so it now checks if the item is visible. so I have updated:
if(entry.isIntersecting && isCartItemClicked)
to
if((entry.isVisible || entry.isIntersecting) && isCartItemClicked)
The issue as I understand is that the observer is only triggered on scroll, but the entry.isVisible is part of the observer callback function.
I have made a JSFIDDLE here (which has HTML and CSS markup).
Is it possible to modify the code. Weirdly the MDN page does not mention the isVisible property, but it is clearly part of the function.
This one is a little tricky but can be done by creating a someObserverEntriesVisible parameter that is set by the observerCallback. With that in place we can define how the button triggers should be handled separately from the observer callback for each intersecting entry.
const observerTargets = document.querySelectorAll('[id^="mini-trolley"]');
const observerOptions = {
root: null, // null means root is viewport
rootMargin: '0px',
threshold: 0.01 // trigger callback when 1% of the element is visible
};
const activeClass = 'active';
const trigger = $('button');
let isCartItemClicked = false;
let someObserverEntriesVisible = null;
let observerEntries = [];
trigger.on('click', () => {
isCartItemClicked = true;
if (someObserverEntriesVisible) {
console.log('fired from button');
observerCallback(observerEntries, observer, false);
}
});
function observerCallback(entries, observer, resetCartItemClicked = true) {
observerEntries = entries;
someObserverEntriesVisible = false;
entries.forEach(entry => {
someObserverEntriesVisible ||= entry.isIntersecting;
if (entry.isIntersecting && isCartItemClicked) {
$(entry.target).addClass(activeClass);
// add API call here
if (resetCartItemClicked) {
isCartItemClicked = false;
console.log('fired from observer');
}
} else {
$(entry.target).removeClass(activeClass);
}
});
}
const observer = new IntersectionObserver(observerCallback, observerOptions);
[...observerTargets].forEach(target => observer.observe(target));
#content {
height: 500px;
}
.active {
background-color: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="mini-trolley">Observer target1</div>
<button>Top button</button>
<div id="content"></div>
<div id="mini-trolley">Observer target2</div>
<button>Bottom button</button>
I am writing a pdf reading software using pdftron. I know that docViewer.on('mouseLeftDown', e => {} can get an event, but onClick can’t seem to get a mouse event. Is there any good solution? Thank you.
WebViewer(
{
path,
initialDoc: "",
},
viewer.value
).then(function (instance) {
const { Annotations, annotManager, docViewer } = instance;
instance.contextMenuPopup.add({
type: "actionButton",
label: "MD",
onClick: () => {
const freeText = new Annotations.FreeTextAnnotation();
freeText.PageNumber = docViewer.getCurrentPage();
freeText.X=?;// I don't know how to set the freeText.X at the location of the mouse
freeText.Y=?;
freeText.Width = 150;
freeText.Height = 50;
create-annotation-free-text
converting-between-mouse-locations-and-window-coordinates
I think one way you could do it is to listen to the contextmenu event on the iframe document and then store the last mouse event from there that you can then use in the onClick of your button.
For example
let lastContextMenuEvent;
instance.iframeWindow.document.addEventListener('contextmenu', (e) => {
lastContextMenuEvent = e;
});
instance.contextMenuPopup.add({
onClick: () => {
// use lastContextMenuEvent here
}
});
I have a exam application using React. I need to run this application in IE11. In this exam app I have added the onblur event that will run when the user switches away from the tab, and when this event is triggered the user is alerted with a popup and the user's lockCount in DB is incremented. The user's exam will be blocked if the LockCount exceeds the limit defined for the exam.
The problem is that the onblur event is triggered when the page is momentarily frozen. Usually this freezing problem occurs when it takes a long time to rerender the page or call any API service. It is working without any problem in Chrome.
I also tried the onBlur event with Mouseleave event, but when the page freezes the mouseleave event also triggers.
How can I prevent the onBlur event from triggering when the page freezes in IE11?
Code for the onBlur and onFocus events:
const onFocus = () => {
setIsOnblur(false);
};
const onBlur = () => {
increaseCount();
setIsOnblur(true);
};
useEffect(() => {
if (props.location.pathname.includes("/Exam/")) {
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("focus", onFocus);
window.removeEventListener("blur", onBlur);
};
}
}, []);
It seems the issue is that the blur listener is sometimes firing before the page is completely loaded. We can be sure the page is fully loaded via the load event.
From MDN:
The load event is fired when the whole page has loaded, including all
dependent resources such as stylesheets and images.
I would therefore make the addEventListeners dependent on the window being fully loaded. Something like this should work:
useEffect(() => {
window.addEventListener("load", () => {
if (props.location.pathname.includes("/Exam/")) {
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
}
return () => {
window.removeEventListener("focus", onFocus);
window.removeEventListener("blur", onBlur);
};
});
}, []);
I solved the problem with this(https://stackoverflow.com/a/9502074/9938582) answer. This answer tells that you can use 3 different methods to detect that user lost focus from the webpage.
Page Visibility API
Focus/Blur Event
User Activities
In my case, I solved the problem with user's mouse activity and timeout.
First case: It works when user changes the screen completely from webpage to another page or something. Page Visibility API allows us to detect when page is hidden to the user. It doesn't catch the lost focus when the page is minimized but the page is not hidden completely. It doesn't count.
Second case: Focus-Blur events are working perfectly in normal conditions. But Internet Explorer issue misleads that.
Third case: Mouse events(mouseout,mousein,mouseover) don't work due to this issue above. But if I use all events and especially mouse events with timeout, onBlur event doesn't trigger when the page freezes.
Here is the code:
useEffect(() => {
if (props.location.pathname.includes("/Exam/")) {
var doc = document as any;
// register to the W3C Page Visibility API
var hidden: any = null;
var visibilityChange: any = null;
if (typeof doc.mozHidden !== "undefined") {
hidden = "mozHidden";
visibilityChange = "mozvisibilitychange";
} else if (typeof doc.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof doc.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
// } else if (typeof document.hidden !== "hidden") {
} else if (doc.hidden) {
hidden = "hidden";
visibilityChange = "visibilitychange";
}
if (hidden != null && visibilityChange != null) {
addEvent(doc, visibilityChange, function (event: any) {
if (doc[hidden]) {
onBlur();
}
});
}
// register to the potential page visibility change
addEvent(doc, "potentialvisilitychange", function (event: any) {
if (doc.potentialHidden && !doc[hidden]) {
onBlur();
}
});
var potentialPageVisibility = {
pageVisibilityChangeThreshold: 3 * 3600, // in seconds
init: function () {
var lastActionDate: any = null;
var hasFocusLocal: any = true;
var hasMouseOver: any = true;
doc.potentialHidden = false;
doc.potentiallyHiddenSince = 0;
var timeoutHandler: any = null;
function setAsNotHidden() {
var dispatchEventRequired = doc.potentialHidden;
doc.potentialHidden = false;
doc.potentiallyHiddenSince = 0;
if (dispatchEventRequired) dispatchPageVisibilityChangeEvent();
}
function initPotentiallyHiddenDetection() {
if (!hasFocusLocal) {
// the window does not has the focus => check for user activity in the window
lastActionDate = new Date();
if (timeoutHandler != null) {
clearTimeout(timeoutHandler);
}
timeoutHandler = setTimeout(checkPageVisibility, potentialPageVisibility.pageVisibilityChangeThreshold * 1000 + 100); // +100 ms to avoid rounding issues under Firefox
}
}
function dispatchPageVisibilityChangeEvent() {
var evt = doc.createEvent("Event");
evt.initEvent("potentialvisilitychange", true, true);
doc.dispatchEvent(evt);
}
function checkPageVisibility() {
var potentialHiddenDuration = (hasFocusLocal || lastActionDate == null ? 0 : Math.floor((new Date().getTime() - lastActionDate.getTime()) / 1000));
doc.potentiallyHiddenSince = potentialHiddenDuration;
if (potentialHiddenDuration >= potentialPageVisibility.pageVisibilityChangeThreshold && !doc.potentialHidden) {
// page visibility change threshold raiched => raise the even
doc.potentialHidden = true;
dispatchPageVisibilityChangeEvent();
}
}
addEvent(doc, "mousemove", function (event: any) {
lastActionDate = new Date();
});
addEvent(doc, "mouseover", function (event: any) {
hasMouseOver = true;
setAsNotHidden();
});
addEvent(doc, "mouseout", function (event: any) {
hasMouseOver = false;
initPotentiallyHiddenDetection();
});
addEvent(window, "blur", function (event: any) {
hasFocusLocal = false;
initPotentiallyHiddenDetection();
});
addEvent(window, "focus", function (event: any) {
hasFocusLocal = true;
setAsNotHidden();
});
setAsNotHidden();
}
}
potentialPageVisibility.pageVisibilityChangeThreshold = 1; // 4 seconds for testing
potentialPageVisibility.init();
}
}, []);
Is there a way to detect when the parent of an element changes (namely when changing from null to !null -- i.e., when the element is initially added to the DOM) using a MutationObserver? I can't find any documentation that shows how this could be achieved.
I am programmatically creating elements with document.createElement(). I return the created element from a function, but want to create a listener from within the function to react when the element is eventually added to the DOM, without knowing where or which parent it will be added to.
I'm not quite sure how else to phrase this, honestly.
const elem = document.createElement('div');
let added = false;
elem.addEventListener('added-to-dom', () => { added = true; });
// ^ how do I achieve this?
assert(added == false);
document.body.addChild(elem);
assert(added == true);
I don't see what's so hard about understanding this or why it was closed.
An easy but inelegant way is to monkeypatch Node.prototype.appendChild (and, if necessary, Element.prototype.append, Element.prototype.insertAdjacentElement, and Node.prototype.insertBefore) to watch for when an element is added to the DOM:
const elementsToWatch = new Set();
const { appendChild } = Node.prototype;
Node.prototype.appendChild = function(childToAppend) {
if (elementsToWatch.has(childToAppend)) {
console.log('Watched child appended!');
elementsToWatch.delete(childToAppend);
}
return appendChild.call(this, childToAppend);
};
button.addEventListener('click', () => {
console.log('Element created...');
const div = document.createElement('div');
elementsToWatch.add(div);
setTimeout(() => {
console.log('About to append element...');
container.appendChild(div);
}, 1000);
});
<button id="button">Append something after 1000ms</button>
<div id="container"></div>
Mutating built-in prototypes generally isn't a good idea, though.
Another option would be to use a MutationObserver for the whole document, but this may well result in lots of activated callbacks for a large page with frequent mutations, which may not be desirable:
const elementsToWatch = [];
new MutationObserver(() => {
// instead of the below, another option is to iterate over elements
// observed by the MutationObserver
// which could be more efficient, depending on how often
// other elements are added to the page
const root = document.documentElement; // returns the <html> element
const indexOfElementThatWasJustAdded = elementsToWatch.findIndex(
elm => root.contains(elm)
);
// instead of the above, could also use `elm.isConnected()` on newer browsers
// if an appended node, if it has a parent,
// will always be in the DOM,
// instead of `root.contains(elm)`, can use `elm.parentElement`
if (indexOfElementThatWasJustAdded === -1) {
return;
}
elementsToWatch.splice(indexOfElementThatWasJustAdded, 1);
console.log('Observed an appended element!');
}).observe(document.body, { childList: true, subtree: true });
button.addEventListener('click', () => {
console.log('Element created...');
const div = document.createElement('div');
div.textContent = 'foo';
elementsToWatch.push(div);
setTimeout(() => {
console.log('About to append element...');
container.appendChild(div);
}, 1000);
});
<button id="button">Append something after 1000ms</button>
<div id="container"></div>
You could listen for the DOMNodeInserted-event and compare the elements id.
Notice: This event is marked as Depricated and will probably stop working in modern modern browsers at some point in the near
future.
let container = document.getElementById('container');
let button = document.getElementById('button');
document.body.addEventListener('DOMNodeInserted', function(event) {
if (event.originalTarget.id == button.id) {
console.log('Parent changed to: ' + event.originalTarget.parentElement.id);
}
});
button.addEventListener('click', function(event) {
container.appendChild(button);
});
#container {
width: 140px;
height: 24px;
margin: 10px;
border: 2px dashed #c0a;
}
<div id="container"></div>
<button id="button">append to container</button>