Convert Onclick to addEventListener - javascript

for(var i=1;i<=s;i++){if(e[r]<0&&n<=0)
{n=Math.abs(e[r])-1;r++}
else if(n>0){n--}else{t=e[r];r++}
var o=document.createElement("div");
o.style.height=t+"px";o.className="thumbnail";
o.id="thumb"+i;
o.setAttribute("onclick","Viewer.goToPage("+i+");");
I'm trying to convert onclick into addEventListener due to CSP restrictions in Firefox OS but not getting success.

for (var i = 1; i <= s; i++) {
if (e[r] < 0 && n <= 0) {
n = Math.abs(e[r]) - 1;
r++
} else if (n > 0) {
n--
} else {
t = e[r];
r++
}
var o = document.createElement("div");
o.style.height = t + "px";
o.className = "thumbnail";
o.id = "thumb" + i;
(function(j, elem) {
elem.addEventListener('click', function() {
Viewer.goToPage(j);
}, false);
}(i, o));
}
You'll have to capture the value of the iterator in an IIFE to make it persist in the callback for addEventListener

You can use addEventListener for this, however I don't see any benefit over direct property assignment (based on adeneo's code):
o.onclick = (function(pageNum) {
return function() {
Viewer.goToPage(pageNum);
};
}(i));
// Presumably o is added to the document before the next iteration
One benefit of this approach is that you can remove the listener later by assigning a new value to the element's onclick property, or add additional listeners using addEventListener (or equivalent).
If addEventListener is used to add an "anonymous" function, there's no easy way to remove it later.
And it's good to avoid setAttribute for attaching handlers as it's inconsistent across browsers. Directly setting the property is supported by every browser back to IE and NN version 3 or earlier.

Related

addEventListener (and removeEventListener) function that need param

I need to add some listeners to 8 object (palms).
These object are identical but the behaviour have to change basing to their position.
I have the follow (ugly) code:
root.palmsStatus = ["B","B","B","B","B","B","B","B"];
if (root.palmsStatus[0] !== "N")
root.game.palms.palm1.addEventListener("click", palmHandler = function(){ palmShakeHandler(1); });
if (root.palmsStatus[1] !== "N")
root.game.palms.palm2.addEventListener("click", palmHandler = function(){ palmShakeHandler(2); });
if (root.palmsStatus[2] !== "N")
root.game.palms.palm3.addEventListener("click", function(){ palmShakeHandler(3); });
if (root.palmsStatus[3] !== "N")
root.game.palms.palm4.addEventListener("click", function(){ palmShakeHandler(4); });
if (root.palmsStatus[4] !== "N")
root.game.palms.palm5.addEventListener("click", function(){ palmShakeHandler(5); });
if (root.palmsStatus[5] !== "N")
root.game.palms.palm6.addEventListener("click", function(){ palmShakeHandler(6); });
if (root.palmsStatus[6] !== "N")
root.game.palms.palm7.addEventListener("click", function(){ palmShakeHandler(7); });
if (root.palmsStatus[7] !== "N")
root.game.palms.palm8.addEventListener("click", function(){ palmShakeHandler(8); });
I have two needs:
1) doesn't use an anonymous function on click event.
I wrote this code, but it doesn't work
root.game.palms.palm8.addEventListener("click", palmShakeHandler(8));
So this one works fine
root.game.palms.palm8.addEventListener("click", function(){ palmShakeHandler(8); });
But I didn't understand how remove the event listener.
I try this solution, but it doesn't work
root.game.palms.palm8.addEventListener("click", palmHandler = function(){ palmShakeHandler(8); });
root.game.palms.palm8.removeEventListener("click", palmHandler);
2) add and remove listener in a for cycle
I wrote the follow code but the behaviour is not correct.
for (i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", function(){ palmShakeHandler(i); });
}
}
the listeners was added but the value of the parameter passed to the palmShakeHandler is always 8.
Nobody could help me to fix these issues?
There is a actually, a perfect way to do that in JavaScript using the Function.prototype.bind method.
bind let you define extra parameters that will be passed, as arguments, of the function.
You should also keep in mind that bind creates a new function and doesn't modify the initial function.
Here is what it looks like:
function palmHandler(number) {
// your code working with `number`
}
var palmHandler8 = palmHandler.bind(null, 8)
// the palmHandler8 is now tied to the value 8.
// the first argument (here null) define what `this` is bound to in this function
This should fix your problem, and you will be able to remove handlers easily :)
Your code will look like this:
for (i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", palmShakeHandler.bind(null, i));
}
}
To be able to remove the handler afterward, you need to keep a reference to the function you create with bind. This would be the way to do this.
var boundHandler = handler.bind(null, i);
element.addEventListener(boundHandler);
element.removeEventListener(bounderHander);
If you want to know more about the awesome bind method in JavaScript, the MDN is your friend :) https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
BTW, the problem with you function always returning 8 is a very common question in JavaScript. This thread will explain everything (spoiler, it's a matter of scoping :) ) https://stackoverflow.com/a/750506/2745879
So in case your array of »palms« is very huge, it is basically a bad Idea to add a single event listener to each of them, because that causes performance flaws. So I would suggest a different approach:
var handlers = [function (e) {}, …, function (e) {}];
root.game.palms.forEach(functiion (palm, idx) {
palm.setAttribute('data-idx', idx);
});
<palmsparent>.addEventListener('click', function (e) {
var c = e.target, idx = -1;
while (c) {
if (c.hasAttribute && c.hasAttribute('data-idx')) {
idx = parseInt(c.getAttribute('data-idx'));
break;
}
c = c.parentNode;
}
//here you also check for the »palm status«
if (idx >= 0) {
handlers[idx](c);
}
})
One event listener for all, much easier to remove and better for performance.
In your last solution you are pasing the same var to every function and that is what make al the functions work with 8 because is the last value of the variable.
To work arround that you can use "let" ( please at least use var, otherside that "i" is global and can be changed every where in the code) but since I dont know wich browser you target I propose other solution.
for (var i=1; i <= root.palmsStatus.length; i++){
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click", (function(index)
(return function(){
palmShakeHandler(index);
}))(i);
}
}
Since its look like You are targeting modern browsers I will use let.https://kangax.github.io/compat-table/es6/
for (var i=1; i <= root.palmsStatus.length; i++){
let index = i;
let intermediateFunction = function(){palmShakeHandler(index);};
if (root.palmsStatus[i-1] !== "N"){
root.game.palms["palm" + i].addEventListener("click",intermediateFunction);
root.game.palms["palm" + i].removeHandShake = function(){this.removeEventListener("click",intermediateFunction)};
}
}
So now you just need to call "removeHandShake" and will remove the listener,
I have code this right here so it ease some minor errors to pop

Override jQuery cycle via <a> link

Is it possible to access the jQuery cycle via a link to override the i variable?
I've seen examples that do this, but not in a case like this where cycle() function is inside a variable:
$(document).ready(function() {
$('div[id^="content-"]').hide();
});
$(window).load(function() {
var divs = $('div[id^="content-"]').hide(),
i = 0;
(function cycle() {
divs.eq(i).fadeIn(400)
.delay(5000)
.fadeOut(400, cycle);
if (typeof window["Stage" + i] != 'undefined')
{
window["Stage" + i].destroy();
}
i = ++i % divs.length; // increment i,
// and reset to 0 when it equals divs.length
if (i == 1)
{
window["Stage" + i] = new swiffy.Stage(document.getElementById('graph_lines'), swiffyobject);
}
else if (i === 0)
{
window["Stage" + i] = new swiffy.Stage(document.getElementById('circle_lines'), circleobject);
}
window["Stage" + i].start();
})();
});
Demo Link
If you're asking whether cycle can be globally accessed, then the short answer is
No
Your cycle function is part of a private function closure. You'll have to make it global for it to work that way. But it's highly recommended that we don't pollute global scope so you should apply this function to your links within that closure:
$(function(){
var divs = $('div[id^="content-"]').hide();
var cycle = function() {
// do the thing although you're using globals etc.
}
$("a").click(cycle);
});
Anyway this is the way to do this, but you will have to clean up your code removing globals unless they're really needed. And learn about jQuery and how it works and don't do same things twice. When you'll learn a little you'll know what I'm talking about.
Note: I'm not sure what you mean by override via a link. I've attached to it to click event in my example, but that may not be what you want. You'll have to be much more specific.
Running swiffy on link click
I suggest you make your life easy on yourself and add links like:
Start swiffy 0
And have this code instead:
$(function() {
var divs = $('div[id^="content-"]').hide();
divs.each(function(index) {
$(this).fadeIn(400)
.delay(5000)
.fadeOut(400);
// replaces first if statement
window["Stage" + index] && window["Stage" + index].destroy();
if (index === 0)
{
window["Stage1"] = new swiffy.Stage(document.getElementById('graph_lines'), swiffyobject);
}
if (index === divs.length - 1)
{
window["Stage0"] = new swiffy.Stage(document.getElementById('circle_lines'), circleobject);
}
window["Stage" + ((index + 1) % divs.length)].start();
});
$("a.start-swiffy").click(function(evt) {
evt.preventDefault();
window["Stage" + $(this).data("swiffy")].start();
});
});
Even though I still don't understand why do you do all those div fades and removing stages and running them in offset sequence by one so they start from the second and the first one starts last? That is the main part that could be improved I guess...

Event delegation, why is this not working, selecting target by id

Edit #2:
The problem does not exist anymore. It was some kind of scoping problem. But now it's working.I am really sorry for the inconvenience.
In some part of my script there's an event delegation and specifying target by id. But when i run it. the line if(evt.target.id == ("subtaskSubmit" + subtaskCounter)) seems not to work at all. Here's my code :
var subtaskCounter = 0;
aWholeTask.addEventListener("click", function(evt){
//other event targets specifications ..
if(evt.target.id == "divide"){
var subtaskInput = document.createElement("input"),
subtaskSubmit = document.createElements.button("submit"), // this is a special function i've made, don't bother about it. I've used it all over the place and it's working normally.
subtaskContainer = document.createElement("p");
subtaskContainer.style.marginLeft = "40px";
subtaskInput.id = "subtaskInput" + (++subtaskCounter);
subtaskInput.type = "text";
subtaskSubmit.id = "subtaskSubmit" + subtaskCounter;
subtaskContainer.appendChildren(subtaskInput,subtaskSubmit);
aWholeTask.appendChild(subtaskContainer);
}
if(evt.target.id == ("subtaskSubmit" + subtaskCounter)){
//this event is the one that not working when i press on that element
alert("hello");
}
});
Edit:
I've made some changes to debug the code and the result is strange :
var subtaskCounter = 0;
aWholeTask.addEventListener("click", function(evt){
//other event targets specifications ..
if(evt.target.id == "divide"){
var subtaskInput = document.createElement("input"),
subtaskSubmit = document.createElements.button("submit"), // this is a special function i've made, don't bother about it. I've used it all over the place and it's working normally.
subtaskContainer = document.createElement("p");
subtaskContainer.style.marginLeft = "40px";
subtaskInput.id = "subtaskInput" + (++subtaskCounter);
subtaskInput.type = "text";
subtaskSubmit.id = "subtaskSubmit" + subtaskCounter;
subtaskContainer.appendChildren(subtaskInput,subtaskSubmit);
aWholeTask.appendChild(subtaskContainer);
}
if(evt.target.innerHTML == "Submit"){
alert(evt.target.id == ("subtaskSubmit" + subtaskCounter));
//And this surprisingly returns false !
}
});
So why does evt.target.id == ("subtaskSubmit" + subtaskCounter) returns false ?
It looks like the problem is because you're sharing subtaskCounter by a number of handlers. Is there a loop around the call to aWholeTask.addEventListener("click", ...) ?
If there is, then by the time the click handler is called, subTaskCounter will always point to the value that caused it to drop out of the loop.
Example
var subtaskCounter = 0;
while (subCounter < 5) {
aWholeTask.addEventListener("click", function(evt){
console.log(subTaskCounter);
});
subTaskCounter ++;
}
In the above example, you'll notice that subTaskCounter will always be 5 when the click handler is called (the value that cause the loop to end). It's the same variable shared by all the handlers. If this is indeed your problem, you need to freeze your closures. Here's one way to do it, using self calling anonymous functions.
var subtaskCounter = 0;
while (subCounter < 5) {
aWholeTask.addEventListener("click", (function(frozenSubTaskCounter) {
return function(evt){
console.log(frozenSubTaskCounter);
};
})(subTaskCounter));
// The line above creates a separate closure `frozenSubTaskCounter`
// for each of the handlers
subTaskCounter ++;
}
In the above example, you'll see that console.log() will output the value that you intended.

javascript error is null or not an object

I am trying to open a window from a php script. But when I click I get this error
Line: 389
Error: 'surgExpressBuildDefaultButton' is null or not an object
Line 389 code is as follow
function setupLayout(i)
{
document.body.onselectstart = function()
{
if (event.srcElement.tagName.search(/input|textarea/i)) return false;
}
setupButtons();
if(window.parent.opener.parent.frames[0].surgExpressBuildDefaultButton)
{
buttonClick(window.parent.opener.parent.frames[0].surgExpressBuildDefaultButton);
}
else
{
layout.buttons.commonButton.fixSelected();
}
for(i = 0; i < da.imgPlus.length; i++)
{
da.imgPlus[i].onclick = clickPlus;
da.imgMinus[i].onclick = clickMinus;
}
for(i = 0; i < da.spnName.length; i++)
{
da.spnName[i].selected = 0;
da.spnName[i].title = da.spnName[i].innerText;
da.spnName[i].onclick = function(){selectCommonProcedure(this);}
da.spnName[i].ondblclick = function(){addCommonProcedure(this);}
da.spnName[i].onmouseout = function(){this.className = (this.selected ? "nSelected" : "nOut");}
da.spnName[i].onmousedown = function(){this.className = "nDown";}
da.spnName[i].onmouseover = da.spnName[i].onmouseup = function(){this.className = "nOver";}
}
da.inpSearch.onkeydown = function(){if(event.keyCode == 13) updateProcedureList();}
da.btnSearch.onclick = da.selSpecialty.onchange = updateProcedureList;
da.btnClose.onclick = function(){window.close();}
da.btnAdd.disable = da.btnSave.disable = CC.Disable;
da.btnAdd.disable(1);
da.btnAdd.onclick = addCommonProcedure;
da.btnSave.disable(1);
da.btnSave.onclick = saveExpress;
}
what could be the problem. Any idea?
It's hard to tell without knowing which of those four dozen lines is line 489, but this jumped out at me:
function setupLayout(i)
{
document.body.onselectstart = function()
{
if (event.srcElement.tagName.search(/input|textarea/i)) return false;
^-- here
You're using event without having declared it (at least, not in the code you quoted). On IE, that'll work because IE makes the event object a property of window (and unless they're shadowed by other declarations, you can access window properties without explicitly qualifying them), but on most other browsers the event object is an argument to the event handler function – and so that code tries to use an undefined value as an object reference, which triggers exactly the error message you're seeing.
The usual idiom for dealing with this discrepancy in browsers (other than using a library like jQuery or Prototype that handle it for you) is this:
document.body.onselectstart = function(event)
{
event = event || window.event;
// ...
};
That idiom declares event it as an argument to the function, but then has it fall back to looking on the window (to support IE).

Weird Event Listening in ActionScript3

I have a weird quirk in ActionScript. I need to pass the index to a callback function.
Here is my code
for (var i:Number = 0; ((i < arrayQueue.length) && uploading); i++)
{
var lid:ListItemData=ListItemData(arrayQueue[i]);
var localI:Number= new Number(i); // to copy?
var errorCallback:Function = function():void { OnUploadError(localI); };
var progressCallback:Function = function(e:ProgressEvent):void { lid.progress = e; OnUploadProgress(localI); };
var completeCallback:Function = function():void { Alert.show('callback'+localI.toString()); OnUploadComplete(localI); }; // localI == arrayQueue.length - 1 (when called)
Alert.show(localI.toString()); // shows current i as expected
lid.fileRef.addEventListener(Event.COMPLETE, completeCallback);
lid.fileRef.addEventListener(ProgressEvent.PROGRESS, progressCallback);
lid.fileRef.addEventListener(HTTPStatusEvent.HTTP_STATUS, errorCallback);
lid.fileRef.addEventListener(IOErrorEvent.IO_ERROR, errorCallback);
lid.fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorCallback);
lid.fileRef.upload(url, 'File');
}
Any idea on how to pass in the index to my callbacks? .upload does not block.
Passing additional parameters for your callbacks is possible via some kind of delegate function or closure. However it is often considered a bad practice. You may use event target property instead to determine your index based on FileReference.
Edit: here is a sample of using closures:
function getTimerClosure(ind : int) : Function {
return function(event : TimerEvent) {
trace(ind);
};
}
for (var i = 0; i < 10; i++) {
var tm : Timer = new Timer(100*i+1, 1);
tm.addEventListener(TimerEvent.TIMER, getTimerClosure(i));
tm.start();
}
This will continuously trace numbers from 0 to 9.
Edit2: here is a sample of creating a delegate based on a function closure:
function timerHandler(event : Event, ...rest) : void {
trace(event, rest);
}
function Delegate(scope : Object, func : Function, ...rest) : Function {
return function(...args) : void {
func.apply(scope, args.concat(rest));
}
}
var tm : Timer = new Timer(1000, 1);
tm.addEventListener(TimerEvent.TIMER, Delegate(this, this.timerHandler, 1, 2, 3));
tm.start();
However this is a bad approach since unsubscribing for such a listener is a hell pain. This in turn will probably cause some memory leakages, which will decrease overall performance of your application. So, use with caution!
Bottom line: if you know how to work with closures, use them - it is a wonderful thing! If you don't care about your application performance in a long perspective, use closures - it's simple!
But if you are unsure about closures, use a more conventional approach. E.g. in your case you could create a Dictionary that matches your FileReference objects to appropriate indices. Something like that:
var frToInd : Dictionary = new Dictionary(false);
// false here wouldn't prevent garbage collection of FileReference objects
for (var i : int = 0; i < 10; i++) {
// blah-blah stuff with `lib` objects
frToInd[lib.fileRef] = i;
// another weird stuff and subscription
}
function eventListener(event : Event) : void {
// in the event listener just look up target in the dictionary
if (frToInd[event.target]) {
var ind : int = frToInd[event.target];
} else {
// Shouldn't happen since all FileReferences should be in
// the Dictionary. But if this happens - it's an error.
}
}
-- Happy coding!
I have a weird quirk in ActionScript
It's not a quirk, it's variable scope. You should read this article: http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f9d.html#WS5b3ccc516d4fbf351e63e3d118a9b90204-7f8c
And you really shouldn't use anonymous, it just makes everything more confusing. You're actually making multiple copies of the same object.
If the arrayQueue is in scope, you can use this code to get the index:
GetArrayIndex(e.currentTarget);
function GetArrayIndex(object:Object):Number
{
for(var i:Number = 0; 0 < arrayQueue.length; i++)
{
if(object === arrayQueue[i])
return i;
}
}
You should consider using an uint for the index.

Categories

Resources