I'm a newbie in JS. I'm trying to move a photo from one div to another #mediaquery with JS. It is moving nevertheless but is not coming back when am changing the dimension of the browser at more then 767px. Probably my thinking is bad at else statement, but I don't know how to do it!
if I delete the else completely is not working anymore...
any help is highly appreciated it! Thank you!
function myFunction(x) {
if (x.matches) { // If media query matches
document.getElementById("img_move").append(document.getElementById("img_dest"));
} else{
document.getElementById("img_dest").append(document.getElementById("img_move"));
}
}
var x = window.matchMedia("max-width: 767px")
myFunction(x) // Call listener function at run time
x.addListener(myFunction) // Attach listener function on state changes
It couldn't be the exact answer of the question but it's other resolution. Without deleting but it's just making other id's img element. And then hiding original one on the other hand showing other id's img element. Both have same image but displaying is different on condition.
function myFunction(x) {
if (x.matches) { // If media query matches
document.getElementById("img_dest").style.display = 'none'
document.getElementById("img_dest2").style.display = 'block'
} else{
document.getElementById("img_dest").style.display = 'block';
document.getElementById("img_dest2").style.display = 'none'
}
}
var x = window.matchMedia("(max-width: 767px)")
myFunction(x) // Call listener function at run time
x.addListener(myFunction) // Attach listener function on state changes
<div id='img_move'> dd</div>
<div id='img_dest'> over 767px</div>
<div id='img_dest2'> under 767px</div>
Your code has several errors which mean that it never reaches the myFunction on a media state change. I have put them in comments here. A good place to look to understand JS functions and methods is MDN, this link gives basic info. on sensing media state changes: https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener
Once those are mended you will see in your browser's console log that your attempt at moving an image fails as you alter the viewport width. I am not at all clear what you actually want to do here - swap the src in the two img elements perhaps?
<script>
function myFunction(event) { //on a state change you get myFunction called but with parameter event, not x
if (event.matches) { // If media query matches
console.log('media query matches');
document.getElementById("img_move").append(document.getElementById("img_dest"));
// you will see an error here: on Firefox: media query matches test14.html:11:13
//HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy
//on Edge
//Uncaught DOMException: Failed to execute 'append' on 'Element': The new child element contains the parent.
// at MediaQueryList.myFunction (file:///C:/Users/annet/Documents/test14.html:1
} else{
console.log('media query does not match');
document.getElementById("img_dest").append(document.getElementById("img_move"));
}
}
var x = window.matchMedia("(max-width: 767px)");//brackets were missing so never saw media width changing
//myFunction(x); // Call listener function at run time - this function needs an event to match up the media tests with
x.addListener(myFunction); // Attach listener function on state changes
</script>
Related
Essentially I'm creating a custom video control bar for an html5 video element. Everything works fine except for the seek bar for which I'm using a range input element.
Now for each element in the control bar, I'm assigning it to a variable in a js function which is called upon window load and adding event listeners. Like so:
function handleWindowLoad() {
var video = document.getElementById ("video");
var playPause = document.getElementById ("playPause");
var muteUnmute = document.getElementById ("muteUnmute");
var fullScreen = document.getElementById ("toggleFullscreen");
var scrubSlider = document.getElementById ("seekBar");
playPause.addEventListener ("click", togglePlay);
muteUnmute.addEventListener("click", toggleMute);
fullScreen.addEventListener("click", toggleFullscreen);
scrubSlider.addEventListener("change", scrubVideo);
}
All the event handlers work without a hitch.
e.g:
function togglePlay() {
if (video.paused) {
video.play();
playPause.innerHTML = "Pause";
}
else {
video.pause();
playPause.innerHTML = "Play";
}
}
works just fine.
However the scrubSlider event handler, scrubVideo():
function scrubVideo() {
var scrubTime = video.duration * (scrubSlider.value/100);
video.currentTime = scrubTime;
}
throws a "scrubSlider undefined" error.
The event listener works fine as scrubVideo() is called when expected but outside of the handleWindowLoad() function I am unable to access scrubSlider. The only ways around this error I've tried are using document.getElementById instead of the var or by declaring scrubSlider globally.
I find this interesting as the only differences between scrubSlider and the rest of the elements is that the element in question is an <input> and not a <button> and the event being listened for is a "change" instead of a "click".
Is this error a result of some sort of inherent functionality of the HTML element or something way more trivial which I've simply overlooked?
Here's a JSFiddle
If I see well, you define scrubSlider in the handleWindowLoad function (which makes it only available in that scope) and you try to access it in scrubVideo. To make it work do something like this:
var scrubSlider;
function handleWindowLoad() {
scrubSlider = document.getElementById ("seekBar");
...
}
EDIT:
Funny thing that I didn't know: apparently you reach the html elements by their id in javascript, if you subscribe to the DOMContentLoaded event. https://jsfiddle.net/22oqpcLu/1/
So, this is the reason that you don't get undefined while accessing the video variable. I guess you could access scrubSlider as seekBar.
This is the first time I get my hands on with automation instruments in xcode The script works well for all button taps but the one making server connection. I don't know the reason
Here is the script I tried so far
var target = UIATarget.localTarget();
target.pushTimeout(4);
target.popTimeout();
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
appScroll.logElementTree();
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
The above script works up to showing the UIActivityIndicator instead of moving to next controller after success
I know There must be a very simple point I am missing. So help me out
UIAutomation attempts to make things "easy" for the developer, but in doing so it can make things very confusing. It sounds like you're getting a reference to window, waiting for a button to appear, then executing .tap() on that button.
I see that you've already considered messing with target.pushTimeout(), which is related to your issue. The timeout system lets you do something that would be impossible in any sane system: get a reference to an element before it exists. I suspect that behind-the-scenes, UIAutomation repeatedly attempts to get the reference you want -- as long as the timeout will allow.
So, in the example you've posted, it's possible for this "feature" to actually hurt you.
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
What if the view changes during the 2-second delay? Your reference to target.frontMostApp().mainWindow.scrollViews()[0] may be invalid, or it may not point to the object you think you're pointing at.
We got around this in our Illuminator framework by forgetting about the timeout system altogether, and just manually re-evaluating a given reference until it actually returns something. We called it waitForChildExistence, but the functionality is basically as follows:
var myTimeout = 3; // how long we want to wait
// this function selects an element
// relative to a parent element (target) that we will pass in
var selectorFn = function (myTarget) {
var ret = myTarget.frontMostApp().mainWindow.scrollViews()[0];
// assert that ret exists, is visible, etc
return ret;
}
// re-evaluate our selector until we get something
var element = null;
var later = get_current_time() + myTimeout;
while (element === null && get_current_time() < later) {
try {
element = selectorFn(target);
} catch (e) {
// must not have worked
}
}
// check whether element is still null
// do something with element
For cases where there is a temporary progress dialog, this code will simply wait for it to disappear before successfully returning the element you want.
An external JavaScript adds a DOM element to a page.
Using JavaScript or JQuery how do I determine when this DOM element is added to the page?
You basically have three choices, one of which isn't available on all browsers:
Get a notification from the other script that it's added an element (if it provides one)
Poll (I'd use setTimeout and reschedule each following check on purpose) to see if the element appears. E.g.:
var CHECK_INTERVAL = 100; 100 = 1/10th second, choose appropriate
function checkForElement() {
/* ...check for the element... */
if (/*...you found the element...*/) {
// Do something with it
}
else {
// Check again after a brief pause
setTimeout(checkForElement, CHECK_INTERVAL);
}
}
setTimeout(checkForElement, CHECK_INTERVAL);
Use a mutation observer, which is the replacement for the broken old mutation events. This lets you register a callback to be called on certain mutation events. Support is reasonable but — quelle shock — IE didn't get them until IE11.
If you have access to that external javascript file, you can just call a function in your page from that file just after you add an element. If you don't have this facility or there is not event triggering in between, you can keep on checking existence of such element in a certain interval using javascript setInterval() method.
var chekElement = function (){
// this will rerun function every second
var chekElemRecursiveTimer = setTimeout(chekElement, 1000);
var someElement = $('.someElement');
if (someElement){
alert("its on page!");
clearTimeout(chekElemRecursiveTimer);
}
}
// run function in page or from external js file
chekElement();
another way to checked this
var chekForElement = function (){
var chekElementTimer = setTimeout(chekForElement, 1000);
if ($('.chcekedElementclass').length > 0){
alert("found in page");
clearTimeout(chekElemRecursiveTimer);
}
}
// run function in page or from external js file
chekForElement();
I need to accurately measure the dimensions of text within my web app, which I am achieving by creating an element (with relevant CSS classes), setting its innerHTML then adding it to the container using appendChild.
After doing this, there is a wait before the element has been rendered and its offsetWidth can be read to find out how wide the text is.
Currently, I'm using setTimeout(processText, 100) to wait until the render is complete.
Is there any callback I can listen to, or a more reliable way of telling when an element I have created has been rendered?
The accepted answer is from 2014 and is now outdated. A setTimeout may work, but it's not the cleanest and it doesn't necessarily guarantee that the element has been added to the DOM.
As of 2018, a MutationObserver is what you should use to detect when an element has been added to the DOM. MutationObservers are now widely supported across all modern browsers (Chrome 26+, Firefox 14+, IE11, Edge, Opera 15+, etc).
When an element has been added to the DOM, you will be able to retrieve its actual dimensions.
Here's a simple example of how you can use a MutationObserver to listen for when an element is added to the DOM.
For brevity, I'm using jQuery syntax to build the node and insert it into the DOM.
var myElement = $("<div>hello world</div>")[0];
var observer = new MutationObserver(function(mutations) {
if (document.contains(myElement)) {
console.log("It's in the DOM!");
observer.disconnect();
}
});
observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
$("body").append(myElement); // console.log: It's in the DOM!
The observer event handler will trigger whenever any node is added or removed from the document. Inside the handler, we then perform a contains check to determine if myElement is now in the document.
You don't need to iterate over each MutationRecord stored in mutations because you can perform the document.contains check directly upon myElement.
To improve performance, replace document with the specific element that will contain myElement in the DOM.
There is currently no DOM event indicating that an element has been fully rendered (eg. attached CSS applied and drawn). This can make some DOM manipulation code return wrong or random results (like getting the height of an element).
Using setTimeout to give the browser some overhead for rendering is the simplest way. Using
setTimeout(function(){}, 0)
is perhaps the most practically accurate, as it puts your code at the end of the active browser event queue without any more delay - in other words your code is queued right after the render operation (and all other operations happening at the time).
This blog post By Swizec Teller, suggests using requestAnimationFrame, and checking for the size of the element.
function try_do_some_stuff() {
if (!$("#element").size()) {
window.requestAnimationFrame(try_do_some_stuff);
} else {
$("#element").do_some_stuff();
}
};
in practice it only ever retries once. Because no matter what, by the next render frame, whether it comes in a 60th of a second, or a minute, the element will have been rendered.
You actually need to wait yet a bit after to get the after render time. requestAnimationFrame fires before the next paint. So requestAnimationFrame(()=>setTimeout(onrender, 0)) is right after the element has been rendered.
In my case solutions like setTimeout or MutationObserver weren't totaly realiable.
Instead I used the ResizeObserver. According to MDN:
Implementations should, if they follow the specification, invoke
resize events before paint and after layout.
So basically the observer always fires after layout, thus we should be able to get the correct dimensions of the observed element.
As a bonus the observer already returns the dimensions of the element. Therefore we don't even need to call something like offsetWidth (even though it should work too)
const myElement = document.createElement("div");
myElement.textContent = "test string";
const resizeObserver = new ResizeObserver(entries => {
const lastEntry = entries.pop();
// alternatively use contentBoxSize here
// Note: older versions of Firefox (<= 91) provided a single size object instead of an array of sizes
// https://bugzilla.mozilla.org/show_bug.cgi?id=1689645
const width = lastEntry.borderBoxSize?.inlineSize ?? lastEntry.borderBoxSize[0].inlineSize;
const height = lastEntry.borderBoxSize?.blockSize ?? lastEntry.borderBoxSize[0].blockSize;
resizeObserver.disconnect();
console.log("width:", width, "height:", height);
});
resizeObserver.observe(myElement);
document.body.append(myElement);
This can also we wrapped in a handy async function like this:
function appendAwaitLayout(parent, element) {
return new Promise((resolve, reject) => {
const resizeObserver = new ResizeObserver((entries) => {
resizeObserver.disconnect();
resolve(entries);
});
resizeObserver.observe(element);
parent.append(element);
});
}
// call it like this
appendAwaitLayout(document.body, document.createElement("div")).then((entries) => {
console.log(entries)
// do stuff here ...
});
The MutationObserver is probably the best approach, but here's a simple alternative that may work
I had some javascript that built the HTML for a large table and set the innerHTML of a div to the generated HTML. If I fetched Date() immediately after setting the innerHTML, I found that the timestamp was for a time prior to the table being completely rendered. I wanted to know how long the rendering was taking (meaning I needed to check Date() after the rendering was done). I found I could do this by setting the innerHTML of the div and then (in the same script) calling the click method of some button on the page. The click handler would get executed only after the HTML was fully rendered, not just after the innerHTML property of div got set. I verified this by comparing the Date() value generated by the click handler to the Date() value retrieved by the script that was setting the innerHTML property of the div.
Hope someone finds this useful
suppose your element has classname class="test"
The following function continue test if change has occured
if it does, run the function
function addResizeListener(elem, fun) {
let id;
let style = getComputedStyle(elem);
let wid = style.width;
let hei = style.height;
id = requestAnimationFrame(test)
function test() {
let newStyle = getComputedStyle(elem);
if (wid !== newStyle.width ||
hei !== newStyle.height) {
fun();
wid = newStyle.width;
hei = newStyle.height;
}
id = requestAnimationFrame(test);
}
}
let test = document.querySelector('.test');
addResizeListener(test,function () {
console.log("I changed!!")
});
when you make for example
var clonedForm = $('#empty_form_to_clone').clone(true)[0];
var newForm = $(clonedForm).html().replace(/__prefix__/g, next_index_id_form);
// next_index_id_form is just a integer
What am I doing here?
I clone a element already rendered and change the html to be rendered.
Next i append that text to a container.
$('#container_id').append(newForm);
The problem comes when i want to add a event handler to a button inside newForm, WELL, just use ready event.
$(clonedForm).ready(function(event){
addEventHandlerToFormButton();
})
I hope this help you.
PS: Sorry for my English.
According to #Elliot B.'s answer, I made a plan that suits me.
const callback = () => {
const el = document.querySelector('#a');
if (el) {
observer.disconnect();
el.addEventListener('click', () => {});
}
};
const observer = new MutationObserver(callback);
observer.observe(document.body, { subtree: true, childList: true });
EDIT:
I've made the changes Matthew and Yossi suggested and it still doesn't seem to work. Those changes I've edited in the post below too.
It now works!
I have a question for a particular problem I can't solve. If you know this question has been answered please send me the link as an answer. I'm trying not to use a framework in this case, but can use jQuery if necessary.
I have found answers on how to attach listeners via functions but I need something so as I wouldn't have to refactor all the code I already have. I'm a freelancer and am working on somebody else's code.
What happens is that I want to detect a touch event for a touch device. This code should work for a PC too so I need to detect clicks. There's this DIV which is created programatically to which I need to add the click or touch, depending on the device. Originally the function was called from an onmousedown event like this:
arrDivAnswers[c].onmousedown = onQuestionDown;
And this is the function it calls:
function onQuestionDown(e)
{
if(!itemSelected)
{
if(this.getAttribute('data-isCorrect') == 'true')
setStyleQCorrect(this, true);
else
setStyleQIncorrect(this);
this.querySelector('.answerText').style.color = '#ffffff';
this.querySelector('.isCorrect').style.visibility = 'visible';
}
itemSelected = true;
}
This was working fine. Now I've made this one which would try and select the correct event for a click or touch (I need a function because I have to use this more than once - and the isTouchDevice is working fine. I use that on some other apps so that code is pretty short and has been tested):
function detectEventClickOrTouch(element, functionToCall){
//detectEventClickOrTouch(arrDivAnswers[c], 'onQuestionDown');
if(isTouchDevice()){
element.addEventListener("touchend", functionToCall, false);
} else{
element.addEventListener("click", functionToCall, false);
}
}
The DIV element gets created like this on some loop:
arrDivAnswers[c] = document.createElement('div');
console.log( "Answer object #" + c + " = " + arrDivAnswers[c] );
arrDivAnswers[c].className = 'autosize';
arrDivAnswers[c].style.textAlign = 'left';
arrDivAnswers[c].setAttribute('data-isCorrect',false);
arrDivAnswers[c].setAttribute('data-isSelected',false);
divAnswerContainer.appendChild(arrDivAnswers[c]);
And then the events get attached to it like this (the older method has been commented out):
for(c;c < arrQuestions[index].arrAnswers.length;c++)
{
var curAnswer = arrQuestions[index].arrAnswers[c];
arrDivAnswers[c].onmouseover = function (e){setStyleQHover(e.currentTarget)};
arrDivAnswers[c].onmouseout = function (e){setStyleQUp(e.currentTarget)};
// Detect touch here *************************
detectEventClickOrTouch(arrDivAnswers[c], onQuestionDown);
//arrDivAnswers[c].onmousedown = onQuestionDown;
// Detect touch here *************************
arrDivAnswers[c].style.visibility = 'visible';
arrDivAnswers[c].querySelector('.answerText').innerHTML = curAnswer.strAnswer;
arrDivAnswers[c].setAttribute('data-isCorrect',curAnswer.isCorrect);
if(curAnswer.isCorrect)
{
//arrDivAnswers[c].classList.add("correctAnswer");
arrDivAnswers[c].className = "correctAnswer";
}
else
{
//arrDivAnswers[c].classList.remove("correctAnswer");
arrDivAnswers[c].className = "autosize";
}
arrDivAnswers[c].setAttribute('data-isSelected',false);
setStyleQUp(arrDivAnswers[c]);
itemSelected = false;
}
[...]
The debugger is throwing this error:
Uncaught TypeError: Object [object DOMWindow] has no method 'getAttribute'
I'm sure I'm messing up the "this" because I'm not calling the function properly.
I agree the "this" variable is getting messed up. The problem is that you are attaching an anonymous function as the callback that then calls eval on another method. This seems unnecessary.
Could you just do this:
function detectEventClickOrTouch(element, functionToCall){
//detectEventClickOrTouch(arrDivAnswers[c], 'onQuestionDown');
if(isTouchDevice()){
element.addEventListener("touchend", functionToCall, false);
} else{
element.addEventListener("click", functionToCall, false);
}
}
And then when you attach the event just do:
detectEventClickOrTouch(arrDivAnswers[c], onQuestionDown);
Since you now call the onQuestionDown function indirectly by the eval the this context seen by the onQuestionDown is the global namespace and not the the element which fired the event.
You don't need the eval anyway... you can pass the function it self
detectEventClickOrTouch(arrDivAnswers[c], onQuestionDown);
and:
element.addEventListener("touchend", functionToCall, false);