Event isn't getting removed - javascript

I have a myDiv.mousedown, window.mousemove, and window.mouseup event. I'm trying to organize my code, and have it clean. So I created a function and returned the 3 event handlers.
The mousedown event adds a mousemove event, and the mouseup event removes the mousemove event.
The problem is, the mousemove event never gets removed. The mouseup event does get fired, but the mousemove event stays. What am I doing wrong, and how can I fix it (in a clean and organized way)?
JSFiddle
var myDiv = document.getElementById('myDiv');
var VeryUsefullClass = (function() {
function VeryUsefullClass(parentElem, options) {
var _this = this;
_this.index = 0;
_this.mouseHandlerBinded = mouseHandler.bind(_this);
myDiv.addEventListener('mousedown', _this.mouseHandlerBinded().mouseDown);
window.addEventListener('mouseup', _this.mouseHandlerBinded().mouseUp);
}
function mouseHandler() {
var _this = this;
var obj = {
mouseDown: function(e) {
console.log('mouseDown');
window.addEventListener('mousemove', _this.mouseHandlerBinded().mouseMove);
},
mouseMove: function(e) {
myDiv.innerHTML = 'mouseMove ' + _this.index++;
},
mouseUp: function(e) {
console.log('mouseUp');
_this.index = 0;
window.removeEventListener('mousemove', _this.mouseHandlerBinded().mouseMove);
}
}
return obj;
}
return VeryUsefullClass;
})();
console.clear();
var hello = new VeryUsefullClass();
#myDiv {
position: absolute;
background-color: orange;
width: 200px;
height: 100px;
}
<div id="myDiv"></div>

_this.mouseHandlerBinded().mouseMove , you will get the function which is a new function , never binded .
var myDiv = document.getElementById('myDiv');
var VeryUsefullClass = (function() {
var mdown= mouseHandler().mouseDown;
var mup = mouseHandler().mouseUp;
var mmove; // unbind with this function will work
function VeryUsefullClass(parentElem, options) {
var _this = this;
_this.index = 0;
mmove = mouseHandler.call(_this).mouseMove;
myDiv.addEventListener('mousedown', mdown);
window.addEventListener('mouseup', mup);
}
function mouseHandler() {
var _this = this;
var obj = {
mouseDown: function(e) {
console.log('mouseDown');
window.addEventListener('mousemove', mmove);
},
mouseMove: function(e) {
myDiv.innerHTML = 'mouseMove ' + (_this.index++);
},
mouseUp: function(e) {
console.log('mouseUp');
_this.index = 0;
window.removeEventListener('mousemove', mmove);
}
}
return obj;
}
return VeryUsefullClass;
})();
console.clear();
var hello = new VeryUsefullClass();

Related

Resume a function after clearInterval

I have this code:
jQuery(function($) { // DOM is ready
var $el = $("header tr"),
tot = $el.length,
c = 0;
var timer = setInterval(function() {
$el.removeClass("current").eq(++c % tot).addClass("current");
}, 3000);
$el.first().addClass("current");
$el.on({
mouseenter: function(e) {
clearInterval(timer);
}
});
$el.mouseout({
timer;
});
});
I want to suspend the function on mouseover and resume it on mouse out but I cant get the latter right. How can I resume it?
Thank you.
There are two ways:
Set a flag that the function being called by the interval checks, and have the function not do anything if it's "suspended."
Start the interval again via a new setInterval call. Note that the old timer value cannot be used for this, you need to pass in the code again.
Example of #1:
jQuery(function($) { // DOM is ready
var $el = $("header tr"),
tot = $el.length,
c = 0,
suspend = false; // The flag
var timer = setInterval(function() {
if (!suspend) { // Check it
$el.removeClass("current").eq(++c % tot).addClass("current");
}
}, 3000);
$el.first().addClass("current");
$el.on({
mouseenter: function(e) {
suspend = true; // Set it
},
mouseleave: function(e) {
suspend = false; // Clear it
}
});
});
Example of #2:
jQuery(function($) { // DOM is ready
var $el = $("header tr"),
tot = $el.length,
c = 0,
timer = 0;
// Move this to a reusable function
var intervalHandler = function() {
$el.removeClass("current").eq(++c % tot).addClass("current");
};
// Probably best to encapsulate the logic for starting it rather
// than repeating that logic
var startInterval = function() {
timer = setInterval(intervalHandler, 3000);
};
// Initial timer
startInterval();
$el.first().addClass("current");
$el.on({
mouseenter: function(e) {
clearInterval(timer); // Stop it
}
mouseleave: function(e) {
startInterval(); // Start it
}
});
});
Checkout these prototypes:
//Initializable
function Initializable(params) {
this.initialize = function(key, def, private) {
if (def !== undefined) {
(!!private ? params : this)[key] = (params[key] !== undefined) ? params[key] : def;
}
};
}
function PeriodicJobHandler(params) {
Initializable.call(this, params);
this.initialize("timeout", 1000, true);
var getTimeout = function() {
return params.timeout;
};
var jobs = [];
function Job(params) {
//expects params.job() function
Initializable.call(this, params);
this.initialize("timeout", getTimeout(), true);
this.initialize("instant", false);
var intervalID = undefined;
this.start = function() {
if (intervalID !== undefined) {
return;
}
if (this.instant) {
params.job(true);
}
intervalID = setInterval(function() {
params.job(false);
}, params.timeout);
};
this.stop = function() {
clearInterval(intervalID);
intervalID = undefined;
};
}
this.addJob = function(params) {
jobs.push(new Job(params));
return jobs.length - 1;
};
this.removeJob = function(index) {
jobs[index].stop();
jobs.splice(index, 1);
};
this.startJob = function(index) {
jobs[index].start();
};
this.stopJob = function(index) {
jobs[index].stop();
};
}
Initializable simplifies member initialization, while PeriodicJobHandler is able to manage jobs in a periodic fashion. Now, let's use it practically:
var pjh = new PeriodicJobHandler({});
//It will run once/second. If you want to change the interval time, just define the timeout property in the object passed to addJob
var jobIndex = pjh.addJob({
instant: true,
job: function() {
$el.removeClass("current").eq(++c % tot).addClass("current");
}
});
jQuery(function($) { // DOM is ready
var $el = $("header tr"),
tot = $el.length,
c = 0;
var timer = setInterval(function() {
$el.removeClass("current").eq(++c % tot).addClass("current");
}, 3000);
$el.first().addClass("current");
$el.on({
mouseenter: function(e) {
jobIndex.stop();
}
});
$el.mouseout({
jobIndex.start();
});
});
With Javascript, it is much easy and efficient.
You can change the interval in setInterval function.
It is checking whether suspend variable is false or true, here suspend variable is setting to true, if mouseEnter function is called and set to false if mouseLeave function is called.
var displayMsg = document.getElementById('msg').innerHTML;
var i = 0;
var suspend = false;
var sequence = setInterval(update, 100);
function update() {
if (suspend == false) {
var dispalyedMsg = '';
dispalyedMsg = displayMsg.substring(i, displayMsg.length);
dispalyedMsg += ' ';
dispalyedMsg += displayMsg.substring(0, i);
document.getElementById('msg').innerHTML = dispalyedMsg;
i++;
if (i > displayMsg.length) {
i = 0;
}
}
}
document.getElementById('msg').addEventListener('mouseenter', mouseEnter);
document.getElementById('msg').addEventListener('mouseleave', mouseLeave);
function mouseEnter() {
suspend = true;
}
function mouseLeave() {
suspend = false;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
#msg {
width: 680px;
height: 100px;
border: 1px solid #ccc;
padding-left: 15px;
}
</style>
</head>
<body>
<div id="msg">
Today is only 15% discount. Hurry up to grab. Sale will end sooooooooooooon!!!!
</div>
<div id="output"></div>
<script src="marquee.js"></script>
</body>
</html>

Click outside to hide container with button

I am using the following Angular directive to hide div elements when the user clicks or touches outside.
https://github.com/TheSharpieOne/angular-off-click
It is working as expected but when you click outside of a div on a button that toggles the div the 'angular-off-click' callback is fired to hide the container but then the toggle function attached to the button is called, reopening the div.
The 'off-click-filter' solves this by adding exceptions which use css selectors to check before the hide function is called. However this was removed as we did not want the extra bloat of css class exceptions in the html markup.
The desired function is for the toggle button not to fire its handler when you click outside of the container
Update
It is only a problem on touch devices where there is a 300ms delay by default. So this means that the callback is fired to hide the container then the toggle functions runs after 300ms, reopening the container. On desktop, with a mouse click, the toggle function fires first and then the callback
// Angular App Code
var app = angular.module('myApp', ['offClick']);
app.controller('myAppController', ['$scope', '$timeout', function($scope,$timeout) {
$scope.showContainer = false;
$scope.toggleContainer = function() {
$timeout(function() {
$scope.showContainer = !$scope.showContainer;
}, 300);
};
$scope.hideContainer = function(scope, event, p) {
$scope.showContainer = false;
console.log('event: ', event);
console.log('scope: ', scope);
console.log(p);
};
}]);
// Off Click Directive Code
angular.module('offClick', [])
.directive('offClick', ['$rootScope', '$parse', function ($rootScope, $parse) {
var id = 0;
var listeners = {};
// add variable to detect touch users moving..
var touchMove = false;
// Add event listeners to handle various events. Destop will ignore touch events
document.addEventListener("touchmove", offClickEventHandler, true);
document.addEventListener("touchend", offClickEventHandler, true);
document.addEventListener('click', offClickEventHandler, true);
function targetInFilter(target, elms) {
if (!target || !elms) return false;
var elmsLen = elms.length;
for (var i = 0; i < elmsLen; ++i) {
var currentElem = elms[i];
var containsTarget = false;
try {
containsTarget = currentElem.contains(target);
} catch (e) {
// If the node is not an Element (e.g., an SVGElement) node.contains() throws Exception in IE,
// see https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
// In this case we use compareDocumentPosition() instead.
if (typeof currentElem.compareDocumentPosition !== 'undefined') {
containsTarget = currentElem === target || Boolean(currentElem.compareDocumentPosition(target) & 16);
}
}
if (containsTarget) {
return true;
}
}
return false;
}
function offClickEventHandler(event) {
// If event is a touchmove adjust touchMove state
if( event.type === 'touchmove' ){
touchMove = true;
// And end function
return false;
}
// This will always fire on the touchend after the touchmove runs...
if( touchMove ){
// Reset touchmove to false
touchMove = false;
// And end function
return false;
}
var target = event.target || event.srcElement;
angular.forEach(listeners, function (listener, i) {
if (!(listener.elm.contains(target) || targetInFilter(target, listener.offClickFilter))) {
$rootScope.$evalAsync(function () {
listener.cb(listener.scope, {
$event: event
});
});
}
});
}
return {
restrict: 'A',
compile: function ($element, attr) {
var fn = $parse(attr.offClick);
return function (scope, element) {
var elmId = id++;
var offClickFilter;
var removeWatcher;
offClickFilter = document.querySelectorAll(scope.$eval(attr.offClickFilter));
if (attr.offClickIf) {
removeWatcher = $rootScope.$watch(function () {
return $parse(attr.offClickIf)(scope);
}, function (newVal) {
if (newVal) {
on();
} else if (!newVal) {
off();
}
});
} else {
on();
}
attr.$observe('offClickFilter', function (value) {
offClickFilter = document.querySelectorAll(scope.$eval(value));
});
scope.$on('$destroy', function () {
off();
if (removeWatcher) {
removeWatcher();
}
element = null;
});
function on() {
listeners[elmId] = {
elm: element[0],
cb: fn,
scope: scope,
offClickFilter: offClickFilter
};
}
function off() {
listeners[elmId] = null;
delete listeners[elmId];
}
};
}
};
}]);
/* Styles go here */
.container {
background: blue;
color: #fff;
height: 300px;
width: 300px;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://code.angularjs.org/1.4.0/angular.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body data-ng-app="myApp">
<h1>Hello Plunker!</h1>
<div data-ng-controller="myAppController">
<button data-ng-click="toggleContainer()">Toggle Container</button>
<div class="container" data-ng-show="showContainer" data-off-click="hideContainer()" data-off-click-if="showContainer">
This is the container
</div>
</div>
</body>
</html>
http://jsbin.com/hibovu
The problem is that when you click on the button the both of the functions are firing:
hideContainer from the directive.
toggleContainer from the click event (that showing the div again).
The solution
Add event.stopPropagation(); before you evaluate the hide callback.
How do you do this?
Pass the event to the function data-off-click="hideContainer($event)".
Add the $event param in the definition of the hideContainer function in the $scope like this: $scope.hideContainer = function($event)
And the full code:
// Angular App Code
var app = angular.module('myApp', ['offClick']);
app.controller('myAppController', ['$scope', '$timeout', function($scope,$timeout) {
$scope.showContainer = false;
$scope.toggleContainer = function() {
$timeout(function() {
$scope.showContainer = !$scope.showContainer;
}, 300);
};
$scope.hideContainer = function($event) {
$event.stopPropagation();
$timeout(function(){
$scope.showContainer = false;
});
};
}]);
// Off Click Directive Code
angular.module('offClick', [])
.directive('offClick', ['$rootScope', '$parse', function ($rootScope, $parse) {
var id = 0;
var listeners = {};
// add variable to detect touch users moving..
var touchMove = false;
// Add event listeners to handle various events. Destop will ignore touch events
document.addEventListener("touchmove", offClickEventHandler, true);
document.addEventListener("touchend", offClickEventHandler, true);
document.addEventListener('click', offClickEventHandler, true);
function targetInFilter(target, elms) {
if (!target || !elms) return false;
var elmsLen = elms.length;
for (var i = 0; i < elmsLen; ++i) {
var currentElem = elms[i];
var containsTarget = false;
try {
containsTarget = currentElem.contains(target);
} catch (e) {
// If the node is not an Element (e.g., an SVGElement) node.contains() throws Exception in IE,
// see https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
// In this case we use compareDocumentPosition() instead.
if (typeof currentElem.compareDocumentPosition !== 'undefined') {
containsTarget = currentElem === target || Boolean(currentElem.compareDocumentPosition(target) & 16);
}
}
if (containsTarget) {
return true;
}
}
return false;
}
function offClickEventHandler(event) {
// If event is a touchmove adjust touchMove state
if( event.type === 'touchmove' ){
touchMove = true;
// And end function
return false;
}
// This will always fire on the touchend after the touchmove runs...
if( touchMove ){
// Reset touchmove to false
touchMove = false;
// And end function
return false;
}
var target = event.target || event.srcElement;
angular.forEach(listeners, function (listener, i) {
if (!(listener.elm.contains(target) || targetInFilter(target, listener.offClickFilter))) {
//$rootScope.$evalAsync(function () {
listener.cb(listener.scope, {
$event: event
});
//});
}
});
}
return {
restrict: 'A',
compile: function ($element, attr) {
var fn = $parse(attr.offClick);
return function (scope, element) {
var elmId = id++;
var offClickFilter;
var removeWatcher;
offClickFilter = document.querySelectorAll(scope.$eval(attr.offClickFilter));
if (attr.offClickIf) {
removeWatcher = $rootScope.$watch(function () {
return $parse(attr.offClickIf)(scope);
}, function (newVal) {
if (newVal) {
on();
} else if (!newVal) {
off();
}
});
} else {
on();
}
attr.$observe('offClickFilter', function (value) {
offClickFilter = document.querySelectorAll(scope.$eval(value));
});
scope.$on('$destroy', function () {
off();
if (removeWatcher) {
removeWatcher();
}
element = null;
});
function on() {
listeners[elmId] = {
elm: element[0],
cb: fn,
scope: scope,
offClickFilter: offClickFilter
};
}
function off() {
listeners[elmId] = null;
delete listeners[elmId];
}
};
}
};
}]);
.container {
background: blue;
color: #fff;
height: 300px;
width: 300px;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body data-ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<h1>Hello Plunker!</h1>
<div data-ng-controller="myAppController">
<button data-ng-click="toggleContainer()">Toggle Container</button>
<div class="container" data-ng-show="showContainer" data-off-click="hideContainer($event)" data-off-click-if="showContainer">
This is the container
</div>
</div>
</body>
</html>
http://jsbin.com/hibovu/3/edit?html,css,js

How do I call a Javascript function outside of another Javascript function?

I have a javascript function that's supposed to toggle an animation when clicked by calling another function outside of it.
function MyFunction(id) {
var target = document.getElementById(id);
var on = true;
this.startMe = function() {
//animation code
on = true;
}
this.stopMe = function() {
//animation code
on = false;
}
this.toggleMe = function() {
if (on) this.stopMe();
else this.startMe();
}
target.addEventListener('click', function() {
this.toggleMe();
}, false);
}
The problem lies in the toggleMe and addEventListener functions. "this" refers to the function itself and not the one containing it, which is what I need it to reference. How can I work around this?
The easy fix is to use a closure variable as given below
function MyFunction(id) {
var self = this;
var target = document.getElementById(id);
var on = true;
this.startMe = function () {
//animation code
on = true;
}
this.stopMe = function () {
/animation code
on = false;
}
this.toggleMe = function() {
if (on) this.stopMe();
else this.startMe();
}
target.addEventListener('click', function() {
//this refers to the element here not the instance of MyFunction
//use a closure variable
self.toggleMe();
}, false);
}
Another solution is to pass a custom execution context to the callback using $.proxy() - you can use Function.bind() also but not supported in IE < 9
function MyFunction(id) {
var target = document.getElementById(id);
var on = true;
this.startMe = function () {
//animation code
on = true;
}
this.stopMe = function () {
//animation code
on = false;
}
this.toggleMe = function () {
if (on) this.stopMe();
else this.startMe();
}
//use Function.bind() to pass a custom execution context to
target.addEventListener('click', jQuery.proxy(function () {
// this refers to the element here not the instance of MyFunction
//use a closure variable
this.toggleMe();
}, this), false);
}
Also use .click()/on('click') to register the click handler instead of addEventListener
$(target).on('click', jQuery.proxy(function () {
// this refers to the element here not the instance of MyFunction
//use a closure variable
this.toggleMe();
}, this), false);
Simply add another variable with a reference to this but with a different name; then you can use that in your functions.
function MyFunction(id) {
var self = this;
var target = document.getElementById(id);
var on = true;
this.startMe = function() {
on = true;
}
this.stopMe = function() {
on = false;
}
this.toggleMe = function() {
if (on) self.stopMe();
else self.startMe();
}
target.addEventListener('click', function() {
self.toggleMe();
}, false);
}
My personal preference is to take it even one step further and continue to use self everywhere that makes sense:
function MyFunction(id) {
var self = this;
var target = document.getElementById(id);
var on = true;
self.startMe = function() {
on = true;
}
self.stopMe = function() {
on = false;
}
self.toggleMe = function() {
if (on) self.stopMe();
else self.startMe();
}
target.addEventListener('click', function() {
self.toggleMe();
}, false);
}

Change an image and change it back with JavaScript click events

I have the following code:
window.onload = function () {
'use strict';
var alcohol = document.getElementById('alcohol');
var changeImage = function (event) {
alcohol.src = "image/alcohol3.jpg";
};
var changeBack = function (event) {
alcohol.src = "image/alcohol.jpg";
};
addHandler(alcohol, "click", changeImage);
// addHandler(alcohol, "mouseout", changeBack);
How can I change the image back with a second click event? At the moment, it's not working.
Change your changeImage function to:
var changeImage = function (event) {
if (alcohol.src != "image/alcohol3.jpg"){
alcohol.src = "image/alcohol3.jpg";
}
else
{
alcohol.src = "image/alcohol.jpg";
}
};
You could use a closure like that to do so :
var changeImage = function() {
function a(event) {
alcohol.src = "image/alcohol3.jpg";
event.srcElement.onclick = b;
}
function b(event) {
alcohol.src = "image/alcohol.jpg";
event.srcElement.onclick = a;
}
return a;
}
addHandleaddHandler(alcohol, "click", changeImage());
First it is the a function that is assigned as the handler and each the event is raised the handler is toggled between a and b.
You can use toggle:
$("#alcohol").toggle(
changeImage() ,
changeBack()
);

Cant' change an object variable from an outer event

I was playing around with making game in JS. And hit a brick wall which is inability to change a variable from an event in the main html file. Namely speaking offSetX. Why it doesn't change?
var game = new Game();
window.addEventListener("keyup", game.input);
game.start('myCanvas');
The game object looks like this:
function Game() {
this.offSetX = 0;
this.init = function (id) {
this.canvas = document.getElementById(id);
this.context = this.canvas.getContext('2d');
this.blocks = [];
this.blocks.push(new block());
};
this.logic = function () {
for (var i in this.blocks) {
this.blocks[i].update(this.offSetX);
}
};
this.draw = function () {
for (var i in this.blocks) {
this.blocks[i].draw(this.context);
}
};
this.main = function () {
this.logic();
this.draw();
console.log(this.offSetX);
};
this.input = function (key) {
if (key.keyCode == 37) {
this.offSetX--;
console.log(this.offSetX);
}
if (key.keyCode == 39) {
this.offSetX++;
console.log(this.offSetX);
}
};
this.start = function (id) {
var _this = this;
this.init(id);
this.interval = setInterval(function () {
_this.canvas.width = _this.canvas.width;
_this.main();
}, 30);
}
};
Try this:
window.addEventListener("keyup", function(key){
game.input.apply(game,[key]);
});
The problem was by window.addEventListener("keyup", game.input) line , you are adding handler for window object, that's why in input method , "this" is window object(which does not have any "offSetX" method), not the game object.

Categories

Resources