attach event in loop, javascript - javascript

I know this is a "classic" and I already tried to read different explanatory articles on this subject, but I still manage to do it wrong somehow. I am talking about adding event handlers and functions in a javascript loop.
Here is my code with problems (it's a suggest-box / auto complete)
function autoCompleteCB(results) {
document.getElementById('autocom').innerHTML = '';
if (results.length == 0) {
document.getElementById('autocom').style.display = 'none';
} else {
document.getElementById('autocom').style.display = 'block';
var divholders = [];
for (var i = 0; i < results.length; i++) {
divholders[i] = document.createElement('div');
divholders[i].style.width = '350px';
var divrestext = document.createElement('div');
divrestext.className = 'autocom0';
divrestext.innerHTML = results[i][0];
divholders[i].appendChild(divrestext);
var divrestype = document.createElement('div');
divrestype.className = 'autocom1' + results[i][1];
divrestype.innerHTML = results[i][1];
divholders[i].appendChild(divrestype);
divholders[i].attachEvent('onmouseover', (function(i) { return function() { divholders[i].style.backgroundColor='#266699'; }; })(i));
divholders[i].attachEvent('onmouseout', (function (i) { return function() { divholders[i].style.backgroundColor = '#F5F5F5'; }; })(i));
document.getElementById('autocom').appendChild(divholders[i]);
}
}
}
It is (of course) the attachevent lines that do not work. This part of javascript is so weird/tricky :) Can a kind expert help me fix those two lines?
This is a half-way fix (I think(:
function bindEvent(element, type, listener) {
if (element.addEventListener) {
element.addEventListener(type, listener, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, listener);
}
}
function autoCompleteCB(results) {
document.getElementById('autocom').innerHTML = '';
if (results.length == 0) {
document.getElementById('autocom').style.display = 'none';
} else {
document.getElementById('autocom').style.display = 'block';
var divholders = [];
for (var i = 0; i < results.length; i++) {
divholders[i] = document.createElement('div');
divholders[i].style.width = '350px';
var divrestext = document.createElement('div');
divrestext.className = 'autocom0';
divrestext.innerHTML = results[i][0];
divholders[i].appendChild(divrestext);
var divrestype = document.createElement('div');
divrestype.className = 'autocom1' + results[i][1];
divrestype.innerHTML = results[i][1];
// BIND THE EVENTS
divholders[i].appendChild(divrestype);
document.getElementById('autocom').appendChild(divholders[i]);
}
}
}
It looks like this now, but still no "action"
function autoComplete() {
var ss = document.getElementById('txbkeyword').value;
if (ss.length > 0) { CSearch.SearchAutoComplete(ss, 3, autoCompleteCB); }
else { document.getElementById('autocom').style.display = 'none'; }
}
function bindEvent(element, type, listener) {
if (element.addEventListener) {
element.addEventListener(type, listener, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, listener);
}
}
function autoCompleteCB(results) {
document.getElementById('autocom').innerHTML = '';
if (results.length == 0) {
document.getElementById('autocom').style.display = 'none';
} else {
document.getElementById('autocom').style.display = 'block';
var divholders = [];
for (var i = 0; i < results.length; i++) {
divholders[i] = document.createElement('div');
divholders[i].style.width = '350px';
var divrestext = document.createElement('div');
divrestext.className = 'autocom0';
divrestext.innerHTML = results[i][0];
divholders[i].appendChild(divrestext);
var divrestype = document.createElement('div');
divrestype.className = 'autocom1' + results[i][1];
divrestype.innerHTML = results[i][1];
(function (i) {
bindEvent(divholders[i], 'mouseover', function () {
divholders[i].style.backgroundColor = '#266699';
});
bindEvent(divholders[i], 'mouseout', function () {
divholders[i].style.backgroundColor = '#F5F5F5';
});
})(i);
divholders[i].appendChild(divrestype);
document.getElementById('autocom').appendChild(divholders[i]);
}
}
}

One possibility is because attachEvent is IE-specific. You'll have to use attachEventListener in many other browsers.
And, to use the "proper" method for the current browser, you'll need to feature-detect them (snippet from MDN):
if (el.addEventListener){
el.addEventListener('click', modifyText, false);
} else if (el.attachEvent){
el.attachEvent('onclick', modifyText);
}
You can also create a function to aid in this:
function bindEvent(element, type, listener) {
if (element.addEventListener) {
element.addEventListener(type, listener, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, listener);
}
}
Then, in place of these 2 lines:
divholders[i].attachEvent('onmouseover', (function(i) { return function() { divholders[i].style.backgroundColor='#266699'; }; })(i));
divholders[i].attachEvent('onmouseout', (function (i) { return function() { divholders[i].style.backgroundColor = '#F5F5F5'; }; })(i));
...use the function to bind your handlers (skipping the on in the event type argument):
(function (i) {
bindEvent(divholders[i], 'mouseover', function () {
divholders[i].style.backgroundColor = '#266699';
});
bindEvent(divholders[i], 'mouseout', function () {
divholders[i].style.backgroundColor = '#F5F5F5';
});
})(i);
You could also just enclose the <div>:
(function (div, i) {
bindEvent(div, 'mouseover', function () {
div.style.backgroundColor = '#266699';
});
bindEvent(div, 'mouseout', function () {
div.style.backgroundColor = '#F5F5F5';
});
})(divholders[i], i);

Try this:
divholders[i].attachEvent('onmouseover', this.style.backgroundColor='#266699');

Related

How to check if a function has been called before executing another function everytime

I have a onMouseDownEssence() and onMouseUpEssence() function for an HTML element, how to check if onMouseDownEssence() is called every time before calling onMouseUpEssence() to ensure I get the correct mouse down position?
Here is mousedown function:
var mouseDownIndex = -1;
function onMouseDownEssence(downIndex, e, className) {
dragTarget = e.target;
holdStarter = new Date().valueOf();
mouseDownIndex = downIndex;
}
Here is mouseup function:
function onMouseUpEssence(upIndex, e, className) {
var el = e.target;
var holdActive = (new Date().valueOf() - holdStarter) > holdDelay;
if (holdActive) {
var thisUpTargetIndex = el.getAttribute("name");
if (lastUpTargetIndex != null && thisUpTargetIndex != lastUpTargetIndex) {
// console.log("double drag done");
el.removeAttribute(dbl);
lastUpTargetIndex = null;
var selectedText = clickDragAutoExpand(mouseDownIndex, upIndex,
className);
} else {
// console.log("drag done");
var selectedText = clickDragAutoExpand(mouseDownIndex, upIndex,
className);
}
holdActive = false;
} else if (el.getAttribute(dbl) == null) {
el.setAttribute(dbl, 1);
setTimeout(
function() {
if (el.getAttribute(dbl) == 1 && !dragTarget) {
if (e.button === 0) {
// console.log("single clicked ");
el.removeAttribute(dbl);
var selectedText = clickAutoExpand(upIndex,
className);
}
} else {
if (el.getAttribute(dbl) != null)
lastUpTargetIndex = el.getAttribute("name");
}
}, dblDelay);
} else {
// console.log("double clicked");
el.removeAttribute(dbl);
var selectedText = clickAutoExpand(upIndex, className);
}
dragTarget = null;
}
My approach would be to keep a track of whether mouseDownEssence() was called. And if not, call it before proceeding further. This approach would work somewhat as below. It would work differently for asynchronous functions but mouseDownEssence() seems to be a synchronous function.
let isMouseDownEssenceCalled = false;
function mouseDownEssence() {
isMouseDownEssenceCalled = true;
...
}
function mouseUpEssence() {
if (!isMouseDownEssenceCalled) {
mouseDownEssence()
}
...
isMouseDownEssenceCalled = false;
}

Javascript hover with querySelectorAll

How do such a thing to work:
function getElements(attrib) {
return document.querySelectorAll('[' + attrib + ']');
}
$(window).load(function () {
$(".b1").hover(function () {
$(this).className = 'x';
var elements = getElements('code');
for (var i = 0; i < elements.length; i++) {
if (elements[i] == 'wow') {
elements[i].className = 'blue';
} else {
elements[i].className = 'red';
}
}
}, function () {
$(this).className = 'y';
});
});
http://jsfiddle.net/rc6Pq/10/
I would like hover to "BUTTON HOVER" and then show this elements with atributes "code" in different colors for "wow" and "lol".
Regards and thanks in advance!
What about this version:
function getElements(attrib) {
return $('[' + attrib + ']');
}
$(window).load(function () {
$(".b1").hover(function () {
$(this).className = 'x';
var elements = getElements('code');
getElements('code').addClass('red').filter('[code="wow"]')
.removeClass('red').addClass('blue');
}, function () {
$(this).className = 'y';
});
});
http://jsfiddle.net/rc6Pq/11/
or even better:
var elements = getElements('code'),
wow = getElements('code').filter('[code="wow"]').addClass('blue');
elements.not(wow).addClass('red');
http://jsfiddle.net/rc6Pq/12/

… is not a function in javascript {custom code}

please, could somebody tell me, what he heck I am doing wrong in my syntax?
The problem starts in the statement this.form.onsubmit, where I get this.initData is not a function.
Thanks.
var Contact_Form = function(element){
this.form = element;
this.errors = new Array();
this.invalid = new Array();
this.inSent = false;
this.name = new String();
this.email = new String();
this.message = new String();
this.initData = function()
{
this.name = this.getElementValue('contact-name');
this.email = this.getElementValue('contact-email');
this.message = this.getElementValue('contact-message');
}
this.form.onsubmit = function(event)
{
event.preventDefault();
this.initData();
if(this.verifyData())
this.send();
}
this.verifyData = function()
{
if(!this.isNameLength())
this.setError('name', 'Zadejte, prosím, jméno dlouhé maximálně 30 znaků.');
if(this.isProperEmail())
{
if(!this.isEmailLength())
this.setError('email', 'Váš e-mail smí obsahovat maximálně 50 znaků.');
}
else
this.setError('email', 'Zadejte, prosím, email v korektním formátu.');
if(!this.isMessageLength())
this.setError('name', 'Zadejte, prosím, zprávu v rozsahu 1-999 znaků.');
this.doInvalidFields();
if(0 == this.errors.length)
return true;
return false;
}
this.doInvalidFields = function()
{
if(this.invalid.length > 0)
{
for(var invalid in this.invalid)
this.getElement(invalid).setAttribute('aria-invalid', true);
}
}
this.setError = function(field, message)
{
this.errors.push(message);
this.invalid.push(field);
}
this.getElementValue = function(element) {
return this.getElement(element).value;
}
this.getElement = function(element) {
return document.getElementById(element);
}
this.getElementName = function() {
return this.getElement('contact-name');
}
this.getElementEmail = function() {
return this.getElement('contact-email');
}
this.getElementMessage = function() {
return this.getElement('contact-message');
}
this.isNameLength = function(){
return this.isLength(this.name, 1, 30);
}
this.isEmailLength = function(){
return this.isLength(this.email, 1, 50);
}
this.isMessageLength = function(){
return this.isLength(this.email, 1, 999);
}
this.isProperEmail = function() {
return this.email.match(/^(?:\w){1,100}#{1}(?:\w){1,100}(?:.){1}(?:\w){1,10}$/ig);
}
this.isLength = function isLength(string, _min, _max) {
if(string.length >= _min && string.length <= _max)
return true;
return false;
}
}
window.onload = function()
{
new Contact_Form(document.forms[0]);
}
The problem is that this is not inherited, and has a different value inside each function.
Then, use
var Contact_Form = function(element){
/* ... */
var that = this;
/* Here this===that */
this.form.onsubmit = function(event)
{
/* Here this===that.form */
event.preventDefault();
that.initData();
if(that.verifyData())
this.send();
}
/* ... */
}
this is referring to the form in the onsubmit handler. You could assign this to a local variable, or bind the handler to the correct this with Function.prototype.bind, ie:
this.form.onsubmit = function(event) {
event.preventDefault();
this.initData();
if(this.verifyData())
this.send();
}.bind(this)
or with jQuery.proxy
this.form.onsubmit = $.proxy(function(event) {
event.preventDefault();
this.initData();
if(this.verifyData())
this.send();
}, this);
Both examples are forcing the this context of the function to be the instance of a Contact_Form whenever the handler is called

Javascript callback managment

I'm having trouble with designing a class which exposes its actions through callbacks. Yes my approach works for me but also seems too complex.
To illustrate the problem I've drawn the following picture. I hope it is useful for you to understand the class/model.
In my approach, I use some arrays holding user defined callback functions.
....
rocket.prototype.on = function(eventName, userFunction) {
this.callbacks[eventName].push(userFunction);
}
rocket.prototype.beforeLunch = function(){
userFunctions = this.callbacks['beforeLunch']
for(var i in userFunctions)
userFunctions[i](); // calling the user function
}
rocket.prototype.lunch = function() {
this.beforeLunch();
...
}
....
var myRocket = new Rocket();
myRocket.on('beforeLunch', function() {
// do some work
console.log('the newspaper guys are taking pictures of the rocket');
});
myRocket.on('beforeLunch', function() {
// do some work
console.log('some engineers are making last checks ');
});
I'm wondering what the most used approach is. I guess I could use promises or other libraries to make this implementation more understandable. In this slide using callbacks is considered evil. http://www.slideshare.net/TrevorBurnham/sane-async-patterns
So, should I use a library such as promise or continue and enhance my approach?
var Rocket = function () {
this.timer = null;
this.velocity = 200;
this.heightMoon = 5000;
this.goingToMoon = true;
this.rocketStatus = {
velocity: null,
height: 0,
status: null
};
this.listener = {
};
}
Rocket.prototype.report = function () {
for (var i in this.rocketStatus) {
console.log(this.rocketStatus[i]);
};
};
Rocket.prototype.on = function (name,cb) {
if (this.listener[name]){
this.listener[name].push(cb);
}else{
this.listener[name] = new Array(cb);
}
};
Rocket.prototype.initListener = function (name) {
if (this.listener[name]) {
for (var i = 0; i < this.listener[name].length; i++) {
this.listener[name][i]();
}
return true;
}else{
return false;
};
}
Rocket.prototype.launch = function () {
this.initListener("beforeLaunch");
this.rocketStatus.status = "Launching";
this.move();
this.initListener("afterLaunch");
}
Rocket.prototype.move = function () {
var that = this;
that.initListener("beforeMove");
if (that.goingToMoon) {
that.rocketStatus.height += that.velocity;
}else{
that.rocketStatus.height -= that.velocity;
};
that.rocketStatus.velocity = that.velocity;
if (that.velocity != 0) {
that.rocketStatus.status = "moving";
}else{
that.rocketStatus.status = "not moving";
};
if (that.velocity >= 600){
that.crash();
return;
}
if (that.rocketStatus.height == 2000 && that.goingToMoon)
that.leaveModules();
if (that.rocketStatus.height == that.heightMoon)
that.landToMoon();
if (that.rocketStatus.height == 0 && !that.goingToMoon){
that.landToEarth();
return;
}
that.report();
that.initListener("afterMove");
that.timer = setTimeout(function () {
that.move();
},1000)
}
Rocket.prototype.stop = function () {
clearTimeout(this.timer);
this.initListener("beforeStop");
this.velocity = 0;
this.rocketStatus.status = "Stopped";
console.log(this.rocketStatus.status)
this.initListener("afterStop");
return true;
}
Rocket.prototype.crash = function () {
this.initListener("beforeCrash");
this.rocketStatus.status = "Crashed!";
this.report();
this.stop();
this.initListener("afterCrash");
}
Rocket.prototype.leaveModules = function () {
this.initListener("beforeModules");
this.rocketStatus.status = "Leaving Modules";
this.initListener("afterModules");
}
Rocket.prototype.landToMoon = function () {
this.initListener("beforeLandToMoon");
this.rocketStatus.status = "Landing to Moon";
this.goingToMoon = false;
this.initListener("afterLandToMoon");
}
Rocket.prototype.landToEarth = function () {
this.initListener("beforeLandToEarth");
this.stop();
this.rocketStatus.status = "Landing to Earth";
this.initListener("afterLandToEarth");
}
Rocket.prototype.relaunch = function () {
this.initListener("beforeRelaunch");
this.timer = null;
this.velocity = 200;
this.heightMoon = 5000;
this.goingToMoon = true;
this.rocketStatus = {
velocity: 200,
height: 0,
status: "relaunch"
};
this.launch();
this.initListener("afterRelaunch");
}
init;
var rocket = new Rocket();
rocket.on("afterLaunch", function () {console.log("launch1")})
rocket.on("afterLandToMoon", function () {console.log("land1")})
rocket.on("beforeLandToEarth", function () {console.log("land2")})
rocket.on("afterMove", function () {console.log("move1")})
rocket.on("beforeLaunch", function () {console.log("launch2")})
rocket.launch();
You can add any function before or after any event.
This is my solution for this kinda problem. I am not using any special methods anything. I was just wonder is there any good practise for this like problems. I dig some promise,deferred but i just can't able to to this. Any ideas ?

Can anyone explain why this JavaScript causes memory leaks in IE7?

The code is rather long yet simple:
100 leaky JavaScript objects are created.
10 leaky elements are created from the JS objects.
1 element is removed and 1 is added 10000 times.
I assume that the detachEvent call is not functioning properly.
Also, if you change this.eventParams from an array to a simple variable, the leak goes away. Why?
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Memory Leak With Fix</title>
<style type="text/css">
.leakyEle
{
border: solid 1px red;
background-color: Gray;
}
</style>
<script type="text/javascript">
/******************************* MAIN ********************************/
var leakObjArray = new Array();
AddEvent(window, 'load', Startup, false);
function Startup() {
for(var i=0; i<100; i++) {
leakObjArray.push(new LeakyObj(i));
}
for(var j=0; j<10; j++) {
leakObjArray[j].CreateLeakyEle();
}
var container = document.getElementById('Container');
AddEvent(container, 'click', Run, false);
alert('Close this dialog and click the document to continue.');
}
function Run() {
var k = 0;
var l = 10;
for(var m = 0; m<10000; m++) {
leakObjArray[k].DestroyLeakyEle();
leakObjArray[l].CreateLeakyEle();
if(k<leakObjArray.length - 1) {
k++;
} else {
k = 0;
}
if(l<leakObjArray.length - 1) {
l++;
} else {
l = 0;
}
}
for(var i=0; i<leakObjArray.length; i++) {
leakObjArray[i].DestroyLeakyEle();
}
alert('Test Complete.');
}
/******************************* END MAIN ********************************/
/******************************* LEAKY OBJECT ********************************/
function LeakyObj(id) {
this.id = id;
this.leakyEle = null;
this.containerEle = document.getElementById('Container');
this.clicked = false;
this.eventParams = new Array();
}
LeakyObj.prototype.CreateLeakyEle = function() {
var leakyEle = document.createElement('div');
leakyEle.id = 'leakyEle' + this.id;
leakyEle.className = 'leakyEle';
leakyEle.innerHTML = this.id + ' --- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' +
'<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' +
'<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' +
'<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' +
'<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
this.leakyEle = leakyEle;
var _self = this;
this.eventParams.push(AddEventWithReturnParams(this.leakyEle, 'click', function() { _self.EventHandler(); }, false));
this.containerEle.appendChild(leakyEle);
}
LeakyObj.prototype.DestroyLeakyEle = function() {
if(this.leakyEle != null) {
this.containerEle.removeChild(this.leakyEle);
for(var i=0; i<this.eventParams.length; i++) {
RemoveEventOverload(this.eventParams[i]);
}
this.leakyEle = null;
}
}
LeakyObj.prototype.EventHandler = function() {
this.leakyEle.style.display = 'none';
this.clicked = true;
}
/******************************* END LEAKY OBJECT ********************************/
/******************************* GENERAL FUNCS ********************************/
function AddEvent(elm, evType, fn, useCapture){
var success = false;
if(elm.addEventListener) {
if(evType == 'mousewheel') evType = 'DOMMouseScroll';
elm.addEventListener(evType, fn, useCapture);
success = true;
} else if(elm.attachEvent) {
if(evType == 'mousewheel') {
window.onmousewheel = document.onmousewheel = fn;
success = true;
} else {
var r = elm.attachEvent('on' + evType, fn);
success = r;
}
} else {
success = false;
}
elm = null;
return success;
}
function AddEventWithReturnParams(elm, evType, fn, useCapture) {
var eventParams = new EventParams(elm, evType, fn, useCapture);
AddEvent(elm, evType, fn, useCapture);
return eventParams;
}
function RemoveEvent(elm, evType, fn, useCapture) {
if(elm) {
if(elm.removeEventListener) {
elm.removeEventListener(evType, fn, useCapture);
return true;
} else if(elm.detachEvent) {
var r = elm.detachEvent('on' + evType, fn);
return r;
} else {
debugger;
}
}
}
function RemoveEventOverload(eventParams) {
if(eventParams) {
return RemoveEvent(eventParams.element, eventParams.eventType, eventParams.handler, eventParams.capture);
}
}
function EventParams(elm, evType, fn, useCapture) {
return {
element: elm,
eventType: evType,
handler: fn,
capture: useCapture
}
}
/******************************* END GENERAL FUNCS ********************************/
</script>
</head>
<body>
<div id="Container"></div>
</body>
</html>
If you take a look at your code, you should notice that in each eventParams object stored in your eventParams array, you have references to the objects, but you never empty out your array. Try clearing out your array...
looks like you're pushing stuff onto the eventParams array inside CreateLeakyEle, but never removing it? Is that right?

Categories

Resources