How do I capture an onkeyevent across multiple frames & subframes - javascript

I'm dealing with a ball-of-mudd project that uses frames & iframes to create a "customizable" interface (like people did in 2002).
The application runs from within a hta and kind of emulates a real WPF style app. I need to capture keys so I can selectively change/refresh some of the subframes.
What I'm trying to do is, if there was a sub-sub frame called desktop and that had some frames in it how would I capture an event, safely, across all frames; and refresh a frames subframes?
Any help appreciated; I accept no responsibility for nausia caused by repeating the last paragraph too many times. :)

Answering to get the formatting
arrFrames[i].document.onkeypress = function(){
var evtobj = window.event ? event : e;
evtobj.cancelBubble = true;
if (evtobj.stopPropagation){ evtobj.stopPropagation();}
top.console.log(evtobj.type+' - '+(evtobj.which?evtobj.which:evtobj.keyCode));
};

I don't know anything about HTA, but the question is marked as javascript / jquery / iframe, so i'll guess it isn't a problem...
You can use an object in window.top to manage your events in a centralized place.
In your main window, you use something like:
var getTopObject = function() {
return window.top.topObject;
}
var TopClass = function () {
this.processClick = function (frame) {
//do something...
alert('click in ' + frame.document.location.toString());
var msj = frame.document.getElementById("msj");
msj.innerHTML = "change!";
};
}
window.top.topObject = new TopClass();
And then, on every iframe, you put:
window.onclick = function () { getTopObject().processClick(window); };
That way you get notified of the click event.
Also note that inside the 'processClick' function in the example you can access the iframe document.
Of course, you can do this a lot more complex, but that's the basic idea. You will have to deal with different events in your case.
Hope this helps, and sorry for my english!

Working; digs through the frames in a loop using a function calling itself; I limited it to 8 rather as I know thats the deepest it will get. You can always change that yourself.
var XXX_util_keyboard = function()
{
//"private" variables:
var objTopWindow = top.window.frames,
arrFrames = [],
MaxDepth = 8;
//"private" methods:
var AddToArray = function(obj){
if(typeof obj.document != "undefined") {
arrFrames.push(obj);
return true;
}
return false;
};
var DeleteFromArray = function(obj){
if(typeof obj != "undefined") {
arrFrames.splice(arrFrames.indexOf(obj), 1);
return true;
}
return false;
};
var FrameLoop = function(objFrames){
if(MaxDepth > 0){
if(objFrames !== null)
{
for(var k = 0; k < objFrames.frames.length; k++)
{
var tmp = objFrames.frames[k];
AddToArray( tmp );
FrameLoop( tmp );
}
this.MaxDepth--;
}
}
};
var AttachEvent = function(key, fn) {
for(var i = 0; i < arrFrames.length; i++){
arrFrames[i].document.onkeypress = function(e) {
var evt = e || window.event,
charCode;
if(evt === null){ evt = this.parentWindow.event; /*IE doesnt capture scope correctly*/ }
charCode = evt.keyCode || evt.which;
alert(charCode);
evt.cancelBubble = true;
if (evt.stopPropagation){ evt.stopPropagation();}
};
}
};
return {
init: function()
{
AddToArray(this.getTopWindow()[0]);
FrameLoop(this.getTopWindow()[0]);
},
getFrames: function()
{
if(arrFrames.length < 1){ FrameLoop(objTopWindow[0]); }
return arrFrames;
},
getTopWindow: function()
{
return objTopWindow === undefined ? window.frames : objTopWindow.window.frames;
},
attachEvent: function()
{
if(arrFrames.length < 1){ FrameLoop(objTopWindow[0]); }
AttachEvent();
}
};
}();

Related

object:removed Event Not Fired - Fabric JS

I am facing a weird issue with fabric events. Take a look at this snippet
canvas.on('object:added', function(e) {
console.log(e.target.type);
console.log("Something was Added");
});
canvas.on('object:removed', function(e) {
console.log(e.target.type);
console.log("Something was removed");
});
Given this code base I am experimenting on an undo / redo functionality. Given both undo & redo can add , modify or remove an object I would like to be notified if something was added or removed in the canvas (I am not much worried about object modified at this stage).
But strange enough no matter if an object is added or removed from the canvas using the undo / redo functionality. I always get the output - Something was Added
Undo / Redo Routines:
// Undo Redo Clear
canvas.counter = 0;
var newleft = 0;
canvas.selection = false;
var state = [];
var mods = 0;
canvas.on(
'object:modified', function () {
updateModifications(true);
},
'object:added', function () {
updateModifications(true);
},
'object:removed' , function(e){
updateModifications(true);
console.log('test me');
});
function updateModifications(savehistory) {
if (savehistory === true) {
myjson = JSON.stringify(canvas);
state.push(myjson);
console.log(myjson);
}
}
undo = function undo() {
if (mods < state.length) {
canvas.clear().renderAll();
canvas.loadFromJSON(state[state.length - 1 - mods - 1]);
canvas.renderAll();
mods += 1;
//check_team();
//compare_states(state[state.length - 1 - mods - 1] , state[state.length - 1 - mods + 1])
}
//make_objects_selectable();
}
redo = function redo() {
if (mods > 0) {
canvas.clear().renderAll();
canvas.loadFromJSON(state[state.length - 1 - mods + 1]);
canvas.renderAll();
mods -= 1;
//check_team();
}
//make_objects_selectable();
}
clearcan = function clearcan() {
canvas.clear().renderAll();
newleft = 0;
}
Fabric version:"1.6.0-rc.1"
Update: The Event is working fine in case of a normal delete action. Hence I added the Undo and Redo Routines.
Regards
Both your undo and redo functions do basically the same thing, erase canvas, load a new state and render it. When you clear the canvas, there is no object:removed event, but another event is fired, called canvas:cleared. That is why you never see your object:removed event fired when doing undo/redo. On the other hand, you do see object:added fired on both undo and redo, because I am guessing that canvas.renderAll adds every object on the current state into the canvas (since it was previously removed with canvas.clear()).
EDIT
A better solution is to store every action that happens on canvas, like add, modify or remove, and have each action associated with some object data. For example, you could have an object_added action associated with a serialization of the added object, or an object_removed action associated with a serialization of the removed object. For object_modified you would need two associated object serializations, one prior modification and one after modification. In case of a canvas_cleared action, you would have to store the whole canvas state as associative data.
A simple stack structure can work great for the purpose of action storage.
function SimpleStackException(msg) {
this.message = msg;
this.name = 'SimpleStackException';
}
function SimpleStack() {
var MAX_ENTRIES = 2048;
var self = this;
self.sp = -1; // stack pointer
self.entries = []; // stack heap
self.push = function(newEntry) {
if (self.sp > MAX_ENTRIES - 1) {
throw new SimpleStackException('Can not push on a full stack.');
}
self.sp++;
self.entries[self.sp] = newEntry;
// make sure to clear the "future" stack after a push occurs
self.entries.splice(self.sp + 1, self.entries.length);
};
self.pop = function() {
if (self.sp < 0) {
throw new SimpleStackException('Can not pop from an empty stack.');
}
var entry = self.entries[self.sp];
self.sp--;
return entry;
};
self.reversePop = function() {
self.sp++;
if (!self.entries[self.sp]) {
self.sp--;
throw new SimpleStackException('Can not reverse pop an entry that has never been created.');
}
return self.entries[self.sp];
}
}
Go ahead and create such a structure:
var actionHistory = new SimpleStack();
Another feature you will need for the action-based undo/redo to work, is the ability to "reference" objects in the canvas. In fabric.js you can reference objects from canvas.getObjects(), but that is a plain js array and does not help much. I have added object IDs, in the form of UUID.
Here is a function (taken somewhere in SO, dont have the link now) tha generates UUIDs
var lut = [];
for (var i = 0; i < 256; i++) {
lut[i] = (i < 16 ? '0' : '') + (i).toString(16);
}
function generateUuid() {
var d0 = Math.random() * 0xffffffff | 0;
var d1 = Math.random() * 0xffffffff | 0;
var d2 = Math.random() * 0xffffffff | 0;
var d3 = Math.random() * 0xffffffff | 0;
return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' +
lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' +
lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
}
In order for fabric objects to have a new uuid property you need to add it to the object prototype, and to the object serialization method as well
fabric.Object.prototype.uuid = "";
fabric.Object.prototype.toObject = (function(toObject) {
return function() {
return fabric.util.object.extend(toObject.call(this), {
uuid: this.uuid,
});
};
})(fabric.Object.prototype.toObject);
Finally you need a function to "reference" objects via this uuid property.
function getFabricObjectByUuid(uuid) {
var fabricObject = null;
canvas.getObjects().forEach(function(object) {
if (object.uuid === uuid) {
fabricObject = object;
}
});
return fabricObject;
}
Now you need to listen for events on the canvas, and update the actionHistory accordingly:
canvas.on('path:created', function(path) {
var object = path.path;
object.uuid = generateUuid();
actionHistory.push({
type: 'object_added',
object: JSON.stringify(object)
});
});
canvas.on('object:added', function(e) {
var object = e.target;
// bypass the event for path objects, as they are handled by `path:created`
if (object.type === 'path') {
return;
}
// if the object has not been given an uuid, that means it is a fresh object created by this client
if (!object.uuid) {
object.uuid = generateUuid();
}
if (!object.bypassHistory) {
actionHistory.push({
type: 'object_added',
object: JSON.stringify(object)
});
}
});
canvas.on('object:modified', function(e) {
var object = e.target;
actionHistory.push({
type: 'object_modified',
objectOld: JSON.stringify(latestTouchedObject),
objectNew: JSON.stringify(object)
});
});
canvas.on('text:changed', function(e) {
var object = e.target;
actionHistory.push({
type: 'text_changed',
objectOld: JSON.stringify(latestTouchedObject),
objectNew: JSON.stringify(object)
});
});
canvas.on('object:removed', function(e) {
var object = e.target;
if (!object.bypassHistory) {
actionHistory.push({
type: 'object_removed',
object: JSON.stringify(object)
});
}
});
canvas.on('canvas:cleared', function(e) {
if (!canvas.bypassHistory) {
actionHistory.push({
type: 'canvas_cleared',
canvas: JSON.stringify(canvas)
});
}
});
Check out each event handler carefully to understand the actual data that will be stored on actionHistory. Also be mindful when the uuid property is actually added to an object. There are two things to note about the above snippet.
bypassHistory is a custom property of canvas objects and the canvas itself. You only want to store actions that are willingly performed by a user onto the canvas. If a user hand-draws a line, you want to save that action, and you do so by listening to path:cleared. However, in the case of a programmatically drawn line (eg. when performing a redo), you may not want to store the action. To add this custom property do as follows:
fabric.Object.prototype.bypassHistory = false; // default value false
object_modified is a special action because it needs to store two object representations: before and after modification. While the "after" version is obtained easily via event.target of the object:modified event, the "before" version has to be tracked programmatically. In my solution i have a high level latestTouchedObject variable that keeps track of the latest modified object on the canvas.
canvas.on('mouse:down', function(options) {
if (options.target) {
latestTouchedObject = fabric.util.object.clone(options.target);
}
});
Now that the action storage and all listeners have been setup, it's time to implement the undo and redo functions
function undoAction() {
var action, objectCandidate;
try {
action = actionHistory.pop();
} catch (e) {
console.log(e.message);
return;
}
if (action.type === 'object_added') {
objectCandidate = JSON.parse(action.object);
var object = getFabricObjectByUuid(objectCandidate.uuid);
object.bypassHistory = true;
canvas.remove(object);
} else if (action.type === 'object_removed') {
objectCandidate = JSON.parse(action.object);
fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
actualObjects[0].uuid = objectCandidate.uuid;
var object = actualObjects[0];
object.bypassHistory = true;
canvas.add(object);
object.bypassHistory = false;
});
} else if (action.type === 'object_modified' || action.type === 'text_changed') {
objectCandidate = JSON.parse(action.objectOld);
fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
actualObjects[0].uuid = objectCandidate.uuid;
var object = actualObjects[0];
var existingObject = getFabricObjectByUuid(objectCandidate.uuid);
if (existingObject) {
existingObject.bypassRemoveEvent = true;
existingObject.bypassHistory = true;
canvas.remove(existingObject);
}
object.bypassHistory = true;
canvas.add(object);
object.bypassHistory = false;
});
} else if (action.type === 'canvas_cleared') {
var canvasPresentation = JSON.parse(action.canvas);
canvas.bypassHistory = true;
canvas.loadFromJSON(canvasPresentation);
canvas.renderAll();
canvas.bypassHistory = false;
}
}
function redoAction() {
var action, objectCandidate;
try {
action = actionHistory.reversePop();
} catch (e) {
console.log(e.message);
return;
}
if (action.type === 'object_added') {
objectCandidate = JSON.parse(action.object);
fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
actualObjects[0].uuid = objectCandidate.uuid;
var object = actualObjects[0];
object.bypassHistory = true;
canvas.add(object);
object.bypassHistory = false;
});
} else if (action.type === 'object_removed') {
objectCandidate = JSON.parse(action.object);
var object = getFabricObjectByUuid(objectCandidate.uuid);
object.bypassHistory = true;
canvas.remove(object);
object.bypassHistory = false;
} else if (action.type === 'object_modified' || action.type === 'text_changed') {
objectCandidate = JSON.parse(action.objectNew);
fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
actualObjects[0].uuid = objectCandidate.uuid;
var object = actualObjects[0];
var existingObject = getFabricObjectByUuid(objectCandidate.uuid);
if (existingObject) {
existingObject.bypassRemoveEvent = true;
existingObject.bypassHistory = true;
canvas.remove(existingObject);
}
object.bypassHistory = true;
canvas.add(object);
object.bypassHistory = false;
});
} else if (action.type === 'canvas_cleared') {
canvas.clear();
}
}
I dont know if this solution (and code) fits your needs out-of-the-box. Maybe it is at some degree coupled to my specific application. I hope you manage to understand what i propose and make use of it.

At mobile device too late to use display none, block

At Pc it works well.
but mobile it is too late.
is any other faster method or the others?
function country_change(country,countries)
{
var cls = document.getElementsByClassName("country_events");
if(document.readyState == "loading")
{
alert('not loading.');
} else
{
for (n=0; n < cls.length; n++)
{
var elem = cls[n];
var div_elem = elem.getElementsByTagName('div').length;
for (m=1; m < div_elem; m++)
{
if (elem.getAttribute('name') == country)
{
if (elem.getElementsByTagName('div')[m].style.display == "none")
{
elem.getElementsByTagName('div')[m].style.display="block";
increaseHeight()
}
}
else
{
elem.getElementsByTagName('div')[m].style.display="none";
increaseHeight()
}
}
}
}
}
at pc it works about 1~3 seconds, but mobile it takes almost 10~20 sec.
i think display is not good method, but there is no other way isn't it?
The collection returned by:
var cls = document.getElementsByClassName("country_events");
is live, so any modification to the DOM may require the browser to refresh the collection. That may happen a lot more often that you think (and much more in some browsers than others), so you may want to convert that to an array or use querySelectorAll (which returns a static collection) instead:
var cls = document.querySelectorAll(".country_events");
Then you have:
var elem = cls[n];
var div_elem = elem.getElementsByTagName('div').length;
so it is good to cache elem, but getElementsByTagName also returns a live collection so use querySelectorAll again:
var div_elem = elem.querySelectorAll('div').length;
div_elem seems an inappropriate name, perhaps divCount would be better?
Then:
if (elem.getAttribute('name') == country)
you can save a method call by accessing the property directly:
if (elem.name == country)
And then the awfully wastefull:
if (elem.getElementsByTagName('div')[m].style.display == "none")
{
elem.getElementsByTagName('div')[m].style.display="block";
increaseHeight()
You got the list of divs earlier, so cache it there
var divs = elem.querySelectorAll('div');
var divCount = divs.length;
Now reuse it:
if (divs[m].style.display == 'none') {
divs[m].style.display = '';
} else {
divs[m].style.display = 'none';
}
increaseHeight();
which can be shortened to:
divs[m].style.display == divs[m].style.display == 'none'? '' : 'none';
increaseHeight();
However the conditional operator is usually slower than the equivalent if..else.
So the function becomes:
function country_change(country,countries) {
var cls = document.querySelectorAll('.country_events');
if(document.readyState == "loading") {
alert('not loading.');
} else {
for (var n=0, nLen=cls.length; n<nLen; n++) {
var elem = cls[n];
var divs = elem.querySelectorAll('div');
var divCount = divs.length;
for (var m = 1; m < divCount; m++) {
if (elem.name == country) {
if (divs[m].style.display == 'none') {
divs[m].style.display = '';
} else {
divs[m].style.display = 'none';
}
increaseHeight()
}
}
}
}
}
Note that querySelectorAll is not supported by IE 7 and lower.
Setting element.style.display to '' (empty string) allows it to return to it's default or computed style and means you don't have to know what that is for each different type of element or what is included in CSS rules.

Add space between numbers/digits and letters/characters

I have a code like this
(function($, window, document, undefined) {
$.fn.quicksearch = function (target, opt) {
var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({
delay: 100,
selector: null,
stripeRows: null,
loader: null,
noResults: '',
bind: 'keyup',
onBefore: function () {
return;
},
onAfter: function () {
return;
},
show: function () {
this.style.display = "";
},
hide: function () {
this.style.display = "none";
},
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
testQuery: function (query, txt, _row) {
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
}, opt);
this.go = function () {
var i = 0,
noresults = true,
query = options.prepareQuery(val),
val_empty = (val.replace(' ', '').length === 0);
for (var i = 0, len = rowcache.length; i < len; i++) {
if (val_empty || options.testQuery(query, cache[i], rowcache[i])) {
options.show.apply(rowcache[i]);
noresults = false;
} else {
options.hide.apply(rowcache[i]);
}
}
if (noresults) {
this.results(false);
} else {
this.results(true);
this.stripe();
}
this.loader(false);
options.onAfter();
return this;
};
this.stripe = function () {
if (typeof options.stripeRows === "object" && options.stripeRows !== null)
{
var joined = options.stripeRows.join(' ');
var stripeRows_length = options.stripeRows.length;
jq_results.not(':hidden').each(function (i) {
$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
});
}
return this;
};
this.strip_html = function (input) {
var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
output = $.trim(output.toLowerCase());
return output;
};
this.results = function (bool) {
if (typeof options.noResults === "string" && options.noResults !== "") {
if (bool) {
$(options.noResults).hide();
} else {
$(options.noResults).show();
}
}
return this;
};
this.loader = function (bool) {
if (typeof options.loader === "string" && options.loader !== "") {
(bool) ? $(options.loader).show() : $(options.loader).hide();
}
return this;
};
this.cache = function () {
jq_results = $(target);
if (typeof options.noResults === "string" && options.noResults !== "") {
jq_results = jq_results.not(options.noResults);
}
var t = (typeof options.selector === "string") ? jq_results.find(options.selector) : $(target).not(options.noResults);
cache = t.map(function () {
return e.strip_html(this.innerHTML);
});
rowcache = jq_results.map(function () {
return this;
});
return this.go();
};
this.trigger = function () {
this.loader(true);
options.onBefore();
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
e.go();
}, options.delay);
return this;
};
this.cache();
this.results(true);
this.stripe();
this.loader(false);
return this.each(function () {
$(this).bind(options.bind, function () {
val = $(this).val();
e.trigger();
});
});
};
}(jQuery, this, document));
I try to figure out where and how I can make a split/add space between numbers and letters. Cause some people type for example "ip1500" and the script cant match the input with an element that is like "ip 1500". My problem ist that Im a js beginner.
I was trying and trying but i cant get it work. I also tried this
I found this spot and I think it can be done here where the everything get splitted by an " " (space):
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
Would be very nice if somebody can help me.
If you want "123abc345def" to "123 abc 345 def". The replace function may help. The code is like this.
var str = "123abc345def";
str = str.replace(/(\d+)/g, function (_, num){
console.log(num);
return ' ' + num + ' ';
});
str = str.trim();
The code you linked didn't work mainly because it's using a different programming language to javascript. In theory, it should work, but javascript does not support regular expression lookbehinds (at this present time)..
Instead, I have re-wrote that fragment of code:
prepareQuery: function (val) {
function isNotLetter(a){
return (/[0-9-_ ]/.test(a));
}
var val=val.toLowerCase().split("");
var tempArray=val.join("").split("");
var currentIndex=1;
for (var i=0;i<val.length-1;i++){
if (isNotLetter(val[i]) !== isNotLetter(val[i+1])){
tempArray.splice(i+currentIndex, 0, " ");
currentIndex++;
}
}
return tempArray.join("");
}
Since you're new to javascript, I'm going to explain what it does.
It declares a function in prepareQuery to check whether or not a string contains a letter [this can be moved somewhere else]
It then splits val into an array and copies the content of val into tempArray
An index is declared (explained later)
A loop is made, which goes through every single character in val
The if statement detects whether or not the current character (val[i] as set by the loop) is the same as the character next to it (val[i+1]).
IF either one are different to the other (ie the current character is a letter while the next isn't) then a space is added to the tempArray at that "index"
The index is incremented and used as an offset in #6
The loop finishes, joins the "array" into a string and outputs the result.
DEMO:
http://jsbin.com/ebitus/1/edit
(JSFiddle was down....)
EDIT:
Sorry, but I completely misinterpreted your question... You failed to mention that you were using "quicksearch" and jQuery. In that case I'm assuming that you have a list of elements that have names and you want to search through them with the plugin...
A much easier way to match the user's query (if there is no space) is to strip the space from the search table along with the query itself - though original reverse method will work (just not as efficiently) [aka: expanding the user's query]
In this case, stripping the space from both the search table and user input would be a better method
prepareQuery: function (val) {
return val.toLowerCase().replace(/ /ig,'').split(" ");
},
testQuery: function (query, txt, _row) {
txt=txt.toLowerCase().replace(/ /ig,'');
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
DEMO:
http://jsfiddle.net/q9k9Y/3/
Edit 2:
It seems like your real intent is to create a fully functioning search feature on your website, not to just add spaces between letters and numbers. With this, I suggest using Quicksilver. I would love to work out an algorithm to extend quickSearcher but at the current time I cannot (timezones). Instead, I suggest using Quicksilver
http://jsbin.com/oruhet/12/

Store the value, not the expression in JavaScript

First of all, I'm not so sure about how to ask this question. I have an array of elements:
var buttons = publishedWork.getElementsByTagName('button');
and what I what is that each button changes its content from ▶ to ◼ and viceversa by clicking. The thing is that I don't know how many buttons will be in total, I intend to do it with a for:
var currentButton;
for (var i = buttons.length; i;) {
buttons[--i].onclick = function() {
if (currentButton === buttons[i]) {
currentButton.textContent = '▶';
currentButton = null;
} else {
currentButton = buttons[i];
currentButton.textContent = '◼';
}
}
}
But what this code does is that, no matter what button I click, it always changes the content of the first button, from which I get that is the expression buttons[i] the one that is stored in currentButton, and not the reference to the button itself.
So my question is:
is this a case to resolve by closures (a topic that I'm just beginning to grasp) or is there another solution?
Unless I'm mistaken, it looks like the common "defining an index-dependent function in a loop" issue. When the onclick function is invoked, it accesses the i variable. But when that happens, i has been all the way through the loop, and is 0. So all the click-handlers just see i == 0
To solve it, you can create a function that, in turn, creates the click-handler:
var currentButton;
function createClickHandler(index) {
var button = buttons[index];
return function() {
if (currentButton === button) {
currentButton.textContent = '▶';
currentButton = null;
} else {
currentButton = button;
currentButton.textContent = '◼';
}
};
}
for (var i = buttons.length; i;) {
buttons[--i].onclick = createClickHandler(i);
}
Edit: Or use Diode's suggestion :)
I was focused on the "index in a closure" problem, but Diode's answer is much cleaner, and the better way to handle it
Use this or event.currentTarget inside click handler
....
buttons[--i].onclick = function(event) {
// both `this` and `event.currentTarget` gives the clicked button here
}
....
.
var currentButton;
for (var i = buttons.length; i;) {
buttons[--i].onclick = function() {
if (currentButton === this) {
...
currentButton = null;
} else {
currentButton = this;
...
}
}
}
and in your else condition you have to reset the current button first.
var currentButton;
for (var i = buttons.length; i;) {
buttons[--i].onclick = function(event) {
if (currentButton === this) {
currentButton.textContent = '▶';
currentButton = null;
} else {
if(currentButton){
currentButton.textContent = '▶';
}
currentButton = this;
currentButton.textContent = '◼';
}
}
}

Losing object reference on lookup in Javascript

I'm working on an extension for Google Chrome and I ran into the following situation:
I'm trying to get all the existing tabs from all the opened windows in the same instance of Google Chrome. I manage to get them and construct an array of objects that contain the relevant data for me.
When I look at the constructed array using console.log (which is saved for future use also) I can see the collection of objects, but I can't reference them (when I try I get undefined).
I tried to save the array outside my object in a container, but nothing changes.
Any idea why the reference to the objects go away when I try to look them up? Thanks.
Here is the code:
(function(window){
//defining a namespace
var example = {
bmarksmaster: (function() {
var bmarksmaster = function() {
return new bmarksmaster.fn.init();
}
bmarksmaster.fn = bmarksmaster.prototype = {
debug: false,
tabs: [],
constructor: bmarksmaster,
init: function() {
return this;
},
windowParser: function(ctx, filter) {
var local = ctx;
var filter = filter;
return function(wObj) {
if((wObj !== null) && (wObj !== undefined)) {
for(var idx in wObj) {
var cw = wObj[idx];
if((cw.tabs !== null) && (cw.tabs !== undefined)) {
var cwtabs = cw.tabs;
for(var tabIdx in cwtabs) {
local.tabs.push(filter(tabIdx, cwtabs[tabIdx]));
}
}
}
}
};
},
getTabs: function() {
var returnData = [];
chrome.windows.getAll(
{
"populate": true
}, this.windowParser(this, function(i, e) {
var data = {};
if(!e.incognito) {
data["title"] = e.title;
data['url'] = e.url;
data['favicon'] = e.favIconUrl || "";
}
return data;
}));
return this.tabs;
},
getTab: function(callback) {
this.getTabs();
for (var tabIdx in this.tabs) {
if(callback(tabIdx, this.tabs[tabIdx])) {
return this.tabs[tabIdx];
}
}
},
getTabsData: function(callback) {
var data = [];
var tabs = [];
tabs = this.getTabs();
console.log(this.tabs[0]);
for (var tabIdx in tabs) {
console.log(tabs[tabIdx]);
var tabData = callback(tabIdx, tabs[tabIdx]);
if(tabData) {
data.push(tabData);
}
}
return data;
},
setDebug: function() {
this.debug = true;
},
resetDebug: function() {
this.debug = false;
}
};
bmarksmaster.fn.init.prototype = bmarksmaster.fn;
return bmarksmaster;
})()
};
window.example = example;
})(window);
//end of bmarksmaster.js file
console.log(example.bmarksmaster().getTabs()); //this works, I can see the array
console.log(example.bmarksmaster().getTabs()[0]); //this doesn't work, I get undefined, never mind the shortcut
I think the logic in your code is wrong. It is a bit convoluted and hard to follow. I would recommend rewriting it a bit to be simpler. Something like this might help get you started. It collects all the windows, putts all the tabs into the tabs var.
var tabs = [];
chrome.windows.getAll({ populate: true}, function(windows) {
var localTabs = windows.reduce(function(a, b){
return a.tabs.concat(b.tabs);
});
tabs = localTabs.filter(function(element){
return !element.incognito;
});
})

Categories

Resources