addEventListener mousemove on document ready - addeventlistener

Newbie-question I suppose.
The following code is part of a function that I call on document ready. It is intended to permanently return the values of the current mouse position whenever the mouse is moved.
The odd thing that is happening: Moving the mouse on document ready doesn't log anything to the console. I know the mouse_monitor-function works though because I use this function in another "mousedown"-eventlistener and it then logs the current mouse-position to the console.
//Mouse Monitor
canvas.addEventListener('mousemove', mouse_monitor, false);
//Mouse Monitor Request
var mouse = new Array();
var mouse_monitor = function(e) {
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
mouse.x = e.pageX - offsetX;
mouse.y = e.pageY - offsetY;
return mouse;
console.log(mouse);
}

return mouse;
Any statements after that line won't be executed.
Okay, then get something working and add to it/modify it incrementally:
var mouse_monitor = function(e) {
var x = e.pageX;
var y = e.pageY;
console.log(x, y);
}
window.onload = function() {
this.addEventListener('mousemove', mouse_monitor);
}
But you mentioned "document ready", so if you are using jquery you should avoid using addEventListener() because it's not cross browser:
var mouse_monitor = function(e) {
var x = e.pageX;
var y = e.pageY;
console.log(x, y);
}
$(document).ready( function() {
$(this).on('mousemove', mouse_monitor);
});
Another approach is to console.log() all variables and their values leading up to the failed code to determine which values are not as they should be.

Related

Textarea functions don't work with contenteditable elements that are not textareas

I'm trying to make a scrollbar stay down with this function (Tampermonkey, on the website: 'https://dictation.io/speech'):
setInterval(function() {
document.getElementsByClassName('ql-editor').scrollTop = document.getElementsByClassName('ql-editor').scrollHeight;
}, 500);
It worked before on another website.
I've fixed the height of the text box, so this scrollbar appears when there is enough of text:
div.notepad {
height : 771px;
}
I've tried doing this:
setInterval(function() {
document.getElementById("speech").scrollTop = document.getElementById("speech").scrollHeight;
}, 500);
and this (to make it read only, but it also doesn't work):
document.getElementById("speech").readOnly = true;
document.getElementsByClassName("ql-editor").readOnly = true;
I'm simply trying to keep the scrollbar always down. And I tried all possible ids and classnames. It worked very well on another website (the textbox was such: <textarea class="-metrika-nokeys" name="docel" id="docel" style="width: 100%;" cols="80" rows="20" spellcheck="true"></textarea>). But nothing has any effect on the text box on this website.
Thank you for any help in advance!
P.S. The problem is universal. This code (and when it's ".ql-editor" instead of '#speech') also doesn't work:
var input = document.querySelector('#speech');
var textarea = document.querySelector('#speech');
var reset = function(e) {
var context = this;
setTimeout(function() {
var len = context.value.length;
context.setSelectionRange(len, len);
}, 100);
};
input.addEventListener('copy', reset, false);
textarea.addEventListener('copy', reset, false);
I was able to solve it (probably not the best solution) by creating a textarea, copying text there from the div, and applying all those functions to that textarea.
Here is the code:
Creating a textarea:
var div = document.getElementById("speech");
var input = document.createElement("textarea");
input.setAttribute("id", "normaltext");
input.name = "post";
input.cols = "80";
input.rows = "2";
div.appendChild(input); //appendChild
Copying everything from the div to the textarea:
setInterval(function copyText() {
$("#normaltext").val($(".ql-editor").text());
}, 100);
Applying functions:
var input1 = document.querySelector('#normaltext');
var textarea1 = document.querySelector('#normaltext');
var reset = function(e) {
var context = this;
setTimeout(function() {
var len = context.value.length;
context.setSelectionRange(len, len);
}, 100);
};
input1.addEventListener('copy', reset, false);
textarea1.addEventListener('copy', reset, false);
setInterval(function() {
document.getElementById("normaltext").scrollTop = document.getElementById("normaltext").scrollHeight;
}, 500);
That works for me, but maybe someone will come up with a better solution.

Drag and Drop limitation in Konva js

I recently began to learn Konva-JS... please help me :)
<script>
var width = window.innerWidth;
var height = window.innerHeight;
function loadImages(sources, callback) {
var assetDir = '/assets/';
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = assetDir + sources[src];
}
}
function isNearOutline(animal, outline) {
var a = animal;
var o = outline;
var ax = a.getX();
var ay = a.getY();
if(ax > o.x - 20 && ax < o.x + 20 && ay > o.y - 20 && ay < o.y + 20) {
return true;
}
else {
return false;
}
}
function drawBackground(background, beachImg, text) {
var context = background.getContext();
context.drawImage(beachImg, 0, 0);
context.setAttr('font', '20pt Calibri');
context.setAttr('textAlign', 'center');
context.setAttr('fillStyle', 'white');
context.fillText(text, background.getStage().getWidth() / 2, 40);
}
function initStage(images) {
var stage = new Konva.Stage({
container: 'container',
width: 578,
height: 530
});
var background = new Konva.Layer();
var animalLayer = new Konva.Layer();
var animalShapes = [];
var score = 0;
// image positions
var animals = {
snake: {
x: 10,
y: 70
},
giraffe: {
x: 90,
y: 70
},
monkey: {
x: 275,
y: 70
},
lion: {
x: 400,
y: 70
}
};
var outlines = {
snake_black: {
x: 275,
y: 350
},
giraffe_black: {
x: 390,
y: 250
},
monkey_black: {
x: 300,
y: 420
},
lion_black: {
x: 100,
y: 390
}
};
// create draggable animals
for(var key in animals) {
// anonymous function to induce scope
(function() {
var privKey = key;
var anim = animals[key];
var animal = new Konva.Image({
image: images[key],
x: anim.x,
y: anim.y,
draggable: true
});
animal.on('dragstart', function() {
this.moveToTop();
animalLayer.draw();
});
/*
* check if animal is in the right spot and
* snap into place if it is
*/
animal.on('dragend', function() {
var outline = outlines[privKey + '_black'];
if(!animal.inRightPlace && isNearOutline(animal, outline)) {
animal.position({
x : outline.x,
y : outline.y
});
animalLayer.draw();
animal.inRightPlace = true;
if(++score >= 4) {
var text = 'You win! Enjoy your booty!';
drawBackground(background, images.beach, text);
}
// disable drag and drop
setTimeout(function() {
animal.draggable(false);
}, 50);
}
});
// make animal glow on mouseover
animal.on('mouseover', function() {
animal.image(images[privKey + '_glow']);
animalLayer.draw();
document.body.style.cursor = 'pointer';
});
// return animal on mouseout
animal.on('mouseout', function() {
animal.image(images[privKey]);
animalLayer.draw();
document.body.style.cursor = 'default';
});
animal.on('dragmove', function() {
document.body.style.cursor = 'pointer';
});
animalLayer.add(animal);
animalShapes.push(animal);
})();
}
// create animal outlines
for(var key in outlines) {
// anonymous function to induce scope
(function() {
var imageObj = images[key];
var out = outlines[key];
var outline = new Konva.Image({
image: imageObj,
x: out.x,
y: out.y
});
animalLayer.add(outline);
})();
}
stage.add(background);
stage.add(animalLayer);
drawBackground(background, images.beach, 'Ahoy! Put the animals on the beach!');
}
var sources = {
beach: 'beach.png',
snake: 'snake.png',
snake_glow: 'snake-glow.png',
snake_black: 'snake-black.png',
lion: 'lion.png',
lion_glow: 'lion-glow.png',
lion_black: 'lion-black.png',
monkey: 'monkey.png',
monkey_glow: 'monkey-glow.png',
monkey_black: 'monkey-black.png',
giraffe: 'giraffe.png',
giraffe_glow: 'giraffe-glow.png',
giraffe_black: 'giraffe-black.png'
};
loadImages(sources, initStage);
</script>
as we can see in this example Animals_on_the_Beach_Game the animal's images are drag-able and can be drop ever where.... but I want to change it in the way that it just can drop on the specific place ... what can I do ?
thank you :)
This is more of a design question, as letting go of the mouse button isn't something you can prevent. It would also be non-intuitive to keep the image attached to the mouse position as you would then need a new mouse event to associate with dropping it. What I've done for a drag and drop UI was to either (1) destroy the dropped shape, or if that wasn't an option, (2) animate the shape back (i.e. snap back) to its original position. Alternatively, you might (3) find the closest likely valid drop target and snap to that location.
First you define lionOrigin, that maybe you already have.
You have to implement the call on the dragend event of the object dragged, so let's say the lion. You have to check position of the lion in relation to the end desired position, let's call it lionDestiny. That can be done with a simple grometry: calculate the distance between to point. We do that with distanceA2B() function.
Now you can establish an offset inside wich you can snap the object, as it is close enough. If the minimal offset is not achieved, then you place the lion back on lionOrigin.
Al last, in konvajs you can use .x() and .y() to easily get or set position to lion.
Something like this:
var lionOrigin = [50,50];
var lionDestiny = [200,200];
var offset = 20;
distanceA2B(a,b) {
return Math.sqrt( ((a[0]-b[0])*(a[0]-b[0])) + ((a[1]-b[1])*(a[1]-b[1])) );
}
lion.on('dragend', (e) => {
var d = distanceA2B([lion.x(),lion.y()],lionDestiny);
if(d<offset){
lion.x(lionDestiny[0]);
lion.y(lionDestiny[1]);
}else{
lion.x(lionOrigin[0]);
lion.y(lionOrigin[1]);
}
});
Hope this helps!
It would have been better if you could explain your question more when you say you want to move any shape to a specific position. Though konva.js provides you with various events through which you can do this. For example, suppose you want to interchange the location of two shapes when you drag and move the first shape to the second and drop it there. In this case, you can use dragend event of konva. So when you move the target element to another element and drop it there, check if they are intersecting each other or not and then interchange their coordinates with each other.
Here is the function to find the intersection between two elements:
haveIntersection(r1, r2) {
return !(
r2.x > r1.x + r1.width ||
r2.x + r2.width < r1.x ||
r2.y > r1.y + r1.height ||
r2.y + r2.height < r1.y
);
}
And from here, you can try to understand the functionality. Though it's in nuxt.js but the events and scripts would be almost same if you are using only javascript. You can find sample code with an explanation for replacing the location of two shapes with each other. So even if you don't want to replace the locations but you want to move your target element to any position this will make you understand how to do this.

Google Maps API, JS global variables and Geolocation.watchPosition()

I have been struggling with this piece of javascript for some time now. I have read different, and similar, posts on the subject but I can't find anything that seems to lead me in the right direction of solving my problem.
I need to call the value of the variables from the watchPosition (and getCurrentPosition) method , set them as global and then call them inside of the function initMap().
The code is working but watchPosition reloads the Google map (this appears to happen when i change the browser/switch between tabs). I can't get the global variables to catch the value from the methods below (inside updateMarker).
How do I set the values from:
mon_lat = +position.coords.latitude;
mon_long = +position.coords.longitude;
to become global?
My main question is, more or less: how can i load the script without updating function initMap()? I would like it so that the navigator.geolocation.watchPosition() method updates automatically.
$(document).ready(function() {
updateMarker();
});
var mon_lat = null;
var mon_long = null;
var start_lat = null;
var start_long = null;
function updateMarker() {
// Get positions
if (navigator.geolocation) {
// Get current position
navigator.geolocation.watchPosition(
function (position) {
mon_lat = +position.coords.latitude;
mon_long = +position.coords.longitude;
initMap(mon_lat, mon_long);
}
);
// Get starting position
navigator.geolocation.getCurrentPosition(
function (position) {
start_lat = +position.coords.latitude;
start_long = +position.coords.longitude;
initMap(start_lat, start_long);
}
);
}
}
function initMap() {
// Display the map
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: mon_lat, lng: mon_long},
zoom: 10,
mapTypeControl:false
});
}

addEventListener error in IE8

I'm getting an error message in ie8: Object doesn't support property or method 'addEventListener'. How can I fix this? I've seen adding an else statementand changing addEventListener to attachEvent. However, I am a bit green in the land of js and not sure where that should go, I did try a few ways.
$(document).ready(function() {
// Off canvas menu
var $slider = document.querySelector('#slider');
var $toggle = document.querySelector('.toggle-nav');
var $toggle2 = document.querySelector('nav .toggle-nav');
// var $link = document.querySelector('.link > a');
$toggle.addEventListener('click', function() {
var isOpen = $slider.classList.contains('slide-in');
$slider.setAttribute('class', isOpen ? 'slide-out' : 'slide-in');
$('#slider').animate({'right': '100%'}, 400);
});
$toggle2.addEventListener('click', function() {
var isOpen = $slider.classList.contains('slide-in');
$slider.setAttribute('class', isOpen ? 'slide-out' : 'slide-in');
$('#slider').animate({'right': '0'}, 300);
});
var toggleDataAttr = function(parentElem, toggleElem, opt1, opt2, dataAttr) {
var toggleElem = parentElem.querySelector(toggleElem);
toggleElem.setAttribute(dataAttr, toggleElem.getAttribute(dataAttr) === opt1 ? opt2 : opt1);
};
var toggle_li = document.querySelectorAll('li');
for (var i = 0; i < toggle_li.length; i++) {
toggle_li[i].onclick = function() {
toggleDataAttr(this, '.toggleContent', 'closed', 'open', 'data-state');
toggleDataAttr(this, '.toggleIcon', 'down', 'up', 'data-icon');
};
}
});
addEventListener() is not supported in IE8 and lower (more info here: http://www.w3schools.com/jsref/met_document_addeventlistener.asp), instead you need to use attachEvent() in these browsers.
You can use it like this:
if(element.addEventListener()) {
element.addEventListener('click', myFunction(), true);
} else if(element.attachEvent()) {
element.attachEvent('click', myFunction());
}
This way to do it makes it multi-browser compatible.

Latency issue with Primefaces overlayPanel - loads to lazy

I am using Primefaces 3.2 with jsf 2 and glassfish 3.1.2.
I have a p:dataTable of users containing avatars of the user. Whenever the user moves the mouse over the avatar a p:overlayPanel appears with more information (lazy loaded) on the user, and disappears when the user moves the cursor away - like this:
<p:overlayPanel for="avatar" dynamic="true" showEvent="mouseover" hideEvent="mouseout" ...>
This works very well - as long as the user is "slowhanded". Whenever an user moves the cursor fast above many avatars many of the overlayPanels stay visible.
For example when the user has the cursor over the position where user avatars are displayed and uses the scroll wheel of his mouse to scroll the usertable down or up.
I believe that the overlaypanel starts to load the information dynamically (dynamic="true") from the server when showEvent="mouseover" is dispatched and displays the overlaypanel after the response from the server arrives.
This way it is not possible to detect whether the cursor is already away when the overlaypanel becomes visible - so the hideEvent="mouseout" is never dispatched.
Is there a way to make the primefaces overlaypanel appear directly on mousover, showing a loading gif and update the content into the overlaypanel when the response comes from the server.
Is this a good appraoch or does anyone know any other way to solve this nasty problem?
Thanks Pete
As my first answer is already very long and contains valid information, I decided to open a new answer presenting my final approach.
Im now using Primefaces inheritance pattern making the code alot cleaner. Also I noticed that replacing/overwriting the whole bindEvents function isnt necessary, as we can remove the old event handlers. Finally this code fixs the latest issue experienced: A hide event before ajax arrival.
PrimeFaces.widget.OverlayPanel = PrimeFaces.widget.OverlayPanel
.extend({
bindEvents : function() {
this._super();
var showEvent = this.cfg.showEvent + '.ui-overlay', hideEvent = this.cfg.hideEvent
+ '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(
showEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.timer = setTimeout(function() {
_self.hidden = false;
_self.show();
}, 300);
}).on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hidden = true;
_self.hide();
});
},
_show : function() {
if (!this.cfg.dynamic || !this.hidden) {
this._super();
}
}
});
Im sorry for the poor formatting: Eclipses fault ;)
Wow, finally after a long debuging session and testing various approaches i recognized that the problem isnt the ajax request but the event handlers itself:
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
if(_self.isVisible()) {
_self.hide();
}
});
As you can see, the widget is just hidden if its visible before. If your moving your mouse out too fast, now two things can happen:
The widget isnt visible at all
The animation is still going on
In this case the event is discarded and the panel stays visible. As animations are queued, one simply has to remove the if statement to fix the issue. I did this by replacing the whole bindEvents method:
PrimeFaces.widget.OverlayPanel.prototype.bindEvents = function() {
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent == this.cfg.hideEvent) {
var event = this.cfg.showEvent;
$(document).off(event, this.targetId).on(event, this.targetId, this, function(e) {
e.data.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlay',
hideEvent = this.cfg.hideEvent + '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
if(!_self.isVisible()) {
_self.show();
}
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.hide();
});
}
//enter key support for mousedown event
this.bindKeyEvents();
var _self = this;
//hide overlay when mousedown is at outside of overlay
$(document.body).bind('mousedown.ui-overlay', function (e) {
if(_self.jq.hasClass('ui-overlay-hidden')) {
return;
}
//do nothing on target mousedown
var target = $(e.target);
if(_self.target.is(target)||_self.target.has(target).length > 0) {
return;
}
//hide overlay if mousedown is on outside
var offset = _self.jq.offset();
if(e.pageX < offset.left ||
e.pageX > offset.left + _self.jq.outerWidth() ||
e.pageY < offset.top ||
e.pageY > offset.top + _self.jq.outerHeight()) {
_self.hide();
}
});
//Hide overlay on resize
var resizeNS = 'resize.' + this.id;
$(window).unbind(resizeNS).bind(resizeNS, function() {
if(_self.jq.hasClass('ui-overlay-visible')) {
_self.hide();
}
});
};
Execute this code on load and the issue should be gone.
As your replacing the js code nevertheless, you can use this oppurtunity to implement quite a nice feature. By using timeouts in the event handlers one can easily implement a little delay not just improving usability (no more thousands of popups appear) but also reducing network traffic:
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.timer = setTimeout( function(){
if(!_self.isVisible()) {
_self.show();
}
}, 300);
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hide();
});
Ofcourse you can use a global variable to control the delay time. If you want a more flexible approach youll have to overwrite the encodeScript method in the OverlayPanelRender to transmit an additional property. You could access it then with _self.cfg.delay. Notice though that youll have to replace the component model OverlayPanel too providing it with an extra attribute.
At the same time I thank you for this brilliant solution I take the opportunity to update it for Primefaces 5.2. In our application the code broke after that upgrade.
Follows the updated code for Primefaces 5.2:
PrimeFaces.widget.OverlayPanel.prototype.bindTargetEvents = function() {
var $this = this;
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent === this.cfg.hideEvent) {
var event = this.cfg.showEvent;
this.target.on(event, function(e) {
$this.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlaypanel',
hideEvent = this.cfg.hideEvent + '.ui-overlaypanel';
this.target
.off(showEvent + ' ' + hideEvent)
.on(showEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
$('.ui-overlaypanel').hide();
$this.hidden = false;
$this.show();
}, 500);
})
.on(hideEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
// don't hide if hovering overlay
if(! $this.jq.is(":hover")) {
$this.hide();
}
}, 100);
});
}
$this.target.off('keydown.ui-overlaypanel keyup.ui-overlaypanel').on('keydown.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
e.preventDefault();
}
})
.on('keyup.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
$this.toggle();
e.preventDefault();
}
});
};
I also added an extra feature which allows the user to move the mouse over the overlay without hiding it. It should hide when you move the mouse out of it then which I accomplished through:
<p:overlayPanel .... onShow="onShowOverlayPanel(this)" ...>
function onShowOverlayPanel(ovr) {
ovr.jq.on("mouseleave", function(e) {
ovr.jq.hide();
});
}
Hope you enjoy!
It's been a long time, but in case anyone bumps into this problem, a showDelay attribute was added to the overlayPanel to solve this problem starting from Primefaces 6.2. However, it is not in the official documentation for some reason.

Resources