Can't empty JS array - javascript

Yes, I am having issues with this very basic (or so it seems) thing. I am pretty new to JS and still trying to get my head around it, but I am pretty familiar with PHP and have never experienced anything like this. I just can not empty this damn array, and stuff keeps getting added to the end every time i run this.
I have no idea why, and i am starting to think that it is somehow related to the way chekbox id's are named, but i may be mistaking....
id="alias[1321-213]",
id="alias[1128-397]",
id="alias[77-5467]" and so on.
I have tried sticking
checkboxes = []; and
checkboxes.length = 0;
In every place possible. Right after the beginning of the function, at the end of the function, even outside, right before the function, but it does not help, and the only way to empty this array is to reload the page. Please tell me what I am doing wrong, or at least point me to a place where i can RTFM. I am completely out of ideas here.
function() {
var checkboxes = new Array();
checkboxes = $(':input[name="checkbox"]');
$.each(checkboxes,
function(key, value) {
console.log(value.id);
alert(value.id);
}
);
checkboxes.length = 0;
}
I have also read Mastering Javascript Arrays 3 times to make sure I am not doing something wrong, but still can't figure it out....

I think there's a lot of confusion coming out of this because you are clearing the array -- just maybe not for the purpose you want, or at the wrong time, etc.
function () {
var checkboxes = new Array(); // local-scope variable
checkboxes = $(':input[name="checkbox"]'); // new instance
$.each(checkboxes, /* ... */); // (truncated for brevity)
checkboxes.length = 0; // this clears the array
// but, for what...?
// nothing happens now
}
As per your snippet, every call to the function recreates and clears the array. However, all work with the array is done while the array is full (nothing happens after it's cleared).
Also, checkboxes is a private variable to the function. The variable only exists during execution of the function and is forgotten once the function is done.
So, what is the big picture? Why are you trying to clear the array?
To take a guess, it sounds like you're intending on clearing it for the next call of the function.
i.e. (filling in doSomething for function name):
doSomething(); // log all array elements and clears the array
doSomething(); // does nothing, since the array is already empty
To accomplish this, you need to define checkboxes in a single location outside of the function, either as a global variable or using closures (the heavily more recommended, albeit more complex, option):
NOTE: If you haven't dealt with closures before, they may not make much sense after only a single example. But, there are thousands of resources available, including Stack Overflow, to help explain them better than I can here.
// closure (or instantly-called function)
// allows for defining private variables, since they are only known by
// code blocks within this function
(function () {
// private (closure-scoped) variable(s)
var checkboxes = $(':input[name="checkbox"]');
function doSomething() {
$.each(checkboxes, /* ... */);
checkboxes.length = 0;
}
})();
The closure will run once, defining checkboxes as the array of inputs while doSomething will iterate the array before clearing it.
Now, the last step is to expose doSomething -- cause, as with checkboxes, it is also private. You can accomplish exposing it by passing the function reference from the closure to a variable outside the closure:
var doSomething = (function () {
/* ... */
return doSomething; // note that you DO NOT want parenthesis here
})();

Is setting length even possible? ;)
Any way checkboxes is a jquery object not an array.
When You do checkboxes = $(':input[name="checkbox"]'); it's not an array any more and whatever there was before has no influence. It doesn't matter what was in a variable if You assign something new to it in any language I know.
You are making some jquery related error. Please elaborate more so that I can help
Are You sure You put name="checkbox" in all of them? It doesn't seem to have a lot of sense. Maybe You waned $(':input[type="checkbox"]'); ?
Edit: that's funny. the above selector isn't too good as well. It should be:
$('input:checkbox');
as for removing stuff:
delete varname
delete arrname[elem]
is the right way to do it.
assigning null does not change the length but just makes trouble.

What about doing this:
checkboxes = new Array();

You can also delete it.
delete checkboxes;

When you rerun the function it will search for all checkboxes again.
Consider this strategy:
function() {
var checkboxes = $(':input[name=checkbox]:not(.examined)');
checkboxes.addClass('examined');
$.each(checkboxes, function(key, value) {
// ... stuff
});
}
You could also use .data() if you don't want to "pollute" the DOM.

Related

How do I keep an array reference through closure?

Quick question that i seem unable to google...
I'm wondering how to keep an array reference through closure. I think.
Im using AngularJS but raw JS has the same behaviour in my trials.
Context: think of a memory game where you match two images. When two selected tiles mismatch, they flip back but only after showing the selected images. So I want to delay the flip.
I have an array that i .filter to a new array.
$scope.selectedTiles = $scope.tiles.filter($scope.filterSelected);
An aside: This new array; does it only contain references or copies of data? MDN says
constructs a new array of all the values for which callback returns a true value.
Not very clear imo. My conclusion is they are references.
Anyways...
This selectedTiles is sent to a closure in order to keep its references. Only it doesnt.
flipSelectedTiles = function (selection){
return function(){
selection.forEach($scope.flipTile);
}
};
function reset(selection){
return function(){
$scope.flipSelectedTiles(selection);
}
}
var resetTiles = reset(filteredTiles);
$timeout($scope.flipSelectedTiles($scope.selectedTiles), $scope.delay);
I need this because I intend to wait a while before executing the flipSelectedTiles.
I also need to clear the selectedTiles immediately in case someone clicks another image.
selectedTiles.splice(0, $scope.selectedTiles.length);
As the timeout fires the array is empty and nothing happens.
How do I keep an array reference through closure?
Edit: here is a fiddle in plain JS http://jsfiddle.net/Tobis/TkjEF/
Edit: added flipSelectedTiles function
You are keeping a reference. The problem is you're emptying the array too early, as you noted yourself. I believe you need a copy instead:
var resetTiles = reset(filteredTiles.slice(0));
$timeout(resetTiles, $scope.delay);
Also note that you weren't passing a function to $timeout, you were invoking flipSelectedTiles immediately (unless flipSelectedTiles returns a function).

jQuery plugin on multiple elements executes asynchronously

EDIT - Js fiddle link to script file - http://jsfiddle.net/r4uH3/
EDIT 2 RE ACCEPTED ANSWER Although question already closed, thought I would add some detail on why I accepted the answer below.
Also see this re why original code didn't work - How do I return the response from an asynchronous call? - it's points re AJAX are not literally related, but the explanation of asynchronicity is important to understand.
Fabrício Matté's answer works perfectly for me, although I adapted it slightly:
(function($){
// some pre-iteration stuff here
// iteration vars
var elementIndex = 0;
var collectionLength = this.size();
var ts = this;
// THIS IS THE KEY BIT AS PER ACCEPTED ANSWER
// RATHER THAN USING THE NORMAL "this.each"
(function initLoop(){
// check if got to last element
if (elementIndex < collectionLength){
// DO STUFF, WHATEVER, AS LONG AS YOU DON'T EXPECT
// ASYNCHRONOUS FUNCTIONS LIKE AJAX / TIMERS NOT TO, WELL, EXECUTE ASYNCHRONOUSLY UNLESS YOU HANDLE THEM PROPERLY!!
// AND FINALLY - GO TO NEXT ELEMENT IN COLLECTION
initLoop();
};
})());
})(jQuery);
The other thing that helped, although not exactly related, was using global and element-specific variables stored using jQuery(el).data(); rather than using window.VARIABLENAME or MyNamespace.VARIABLE_NAME. Eg:
// outside of iteration
jQuery(window).data("GLOBAL_STUFF", { /* add properties here and set them later*/ });
var globalData = jQuery(window).data("GLOBAL_STUFF");
// inside iteration
jQuery(currentElement).data("ELEMENT_DATA", { /* add properties here and set them later*/ });
var elementData = jQuery(window).data("ELEMENT_DATA");
// then set props like so (obviously get, similarly..)
globalData.someArrayOfSomething.push(something);
elementData.someBooleanValue = true;
Again, thanks to Fabrício.
I have written a jQuery plugin that, like most, can be executed on multiple (i.e. a collection of) elements.
in the this.each(function(i,el){ }); part of the function, I create a new instance of another object type (nothing to do with jQuery) and call its "Init" method.
I expect, with the .each loop, that it will loop to the next instance after the init method has been fully executed.
I am not using any async (AJAX / timers) anywhere.
I am using callbacks always on anything like "jQuery.fadeIn" or similar.
THE PROBLEM
The Init methods are called virtually in parallel. They do not complete their execution before the next one is called.
Can anybody advise of any known issues? Is there something I'm missing from the above "theory"?
Using jQuery 2.0.
Try replacing your for loop with:
var i = 0,
l = window.JRTE_INSTANCES.length;
(function initloop() {
if (i < l) window.JRTE_INSTANCES[i].Init(initloop);
i++;
}());
This will start the init loop by calling window.JRTE_INSTANCES[0].Init. The initloop passed as callback will execute again when the Init concludes, starting another Init with the next i and so forth until it has iterated over all instances.
Here's a more practical async demo using a very similar structure as the above: Fiddle

JavaScript Strange Array Scope Issue

All source code is available here: Simply view the page source. Half the code is on that page, the other half is in the 'deputyDepot.js' file which is linked in the source and should also be viewable.
Just a few things: this is obviously still a work in progress. I'm pretty disorganized when coding, and I tend to jump around and do a little of this and a little of that. So it's a bit messy. Also the background. We'll just pretend that doesn't exist, okay?
So now, on to the problem! It seems that I'm unable to access arrays in any functions.
Currently, the way I've been testing is by modifying my main function (called weTalkNow(input)). This is located in deputyDepot.js. I set it to return whatever values I'm testing to see what they're set to. These values get printed in my chat box thingy. So I use it like console.log(). Currently it's set to return the length of the user input, but that's completely irrelevant.
_inputArray is populated by taking the user input (a string) and splitting it on spaces. It is declared at the top of the page. This is all fine and dandy.
Where the problem arises is if an array is populated manually.
var x = [ [1,2], [3,4] ];
/* Main Function */
function weTalkNow(){
return x[0][0];
}
That code, as far as I can tell, SHOULD output 1. But it does not. When the code is modified to this, it works fine:
/* Main Function */
function weTalkNow(){
var x = [ [1,2], [3,4] ];
return x[0][0];
}
This isn't very helpful, however. I need a global array, not a local one.
What makes things really weird is that if I decide to declare
var x = [ [1,2], [3,4] ];
on my main page (the one with HTML and stuffs), and then do this
/* Main Function */
function weTalkNow(){
return x[0][0];
}
in the deputyDepot.js file, it works fine. So suddenly if I declare a global array on a different page it works okay.
Please let me know if I can clarify any points in any way. I tried to give all the info I could. Random side tips are welcome too, but I'm mostly just focusing on this.
Again, just so I'm clear: for whatever reason, I CANNOT USE ARRAYS if I populate them manually (I assume this is somehow related to the problem). _inputArray is doing it's job fine, so that array works okay. It's the only global array that does so. But it isn't populated manually, but by the split function. I can't seem to be able to make a global array that is accessible by functions.
EDIT:
Okay, I found the problem! All the code I was writing works fine, like it's supposed to. The problem is that at the very top of my .js file was a broken function. This very first line prevented all the code below it from running, and so my arrays were never being initialized in the first place. For this reason I was unable to access them.
Once I checked the web console I was able to fix everything. I didn't know there was a web console before posting this question.
If your function refers to an array that is declared below the line where the function is called, the array will not be in scope.
myFunc();
function myFunc () {
console.log(ra[0]); // won't work
};
var ra = ["a"];
This will work:
var ra = ["a"];
myFunc();
function myFunc () {
console.log(ra[0]); // will work
};
The other thing to keep in mind is that javascript includes are processed in order also. One more thing: while generally javascript is processed from top to bottom, in other words; a function cannot call or reference variables that are defined on a lower line in your file, there is an exception. The exception is named functions, which you are using.
This won't work.
var funcA = function () {
funcB(); // wont work
};
funcA();
var funcB = function () {
console.log("from funcB");
};
This will work:
funcC(); // works fine
function funcC () {
funcD(); // works fine
}
function funcD () {
console.log("from funcD");
};
These subtle differences may be perceived to represent less than perfect design, but they can work very well.

Prototype injection, google maps api

I need to catch the event of getting back suggestions for google maps autocomplete. I know it is undocumented, but doing some research I found that it could be down via some prototype hacking.
<input type='text' id='myInput'>
<script src="http://maps.google.com/maps/api/js?libraries=places&sensor=false&language=en-EN"></script>
<script>
function catcher(key) { console.log(key); }
function MyProto() {}
MyProto.prototype = new google.maps.MVCObject();
MyProto.prototype.changed = catcher;
var gAuto = new google.maps.places.Autocomplete(
document.getElementById('myInput'), ['geocode']);
// one of two should be commented
//gAuto.__proto__.__proto__.changed = catcher; // every key, including 'predictions'
gAuto.__proto__.__proto__ = MyProto.prototype; // only 'types', '0', and 'place' when selected
</script>
JSFiddle link: http://jsfiddle.net/agentcooper/hRyTF/ (check the console)
Check the last two lines. When setting 'changed' function directly on MVCObject prototype (first one, commented), everything works great and I can catch key 'predictions' in the 'catcher' function. The problem is that catcher needs to be different if, for example, I need to have two instances of autocomplete on my page. So when I'm trying to inject custom object in autocomplete's prototype chain (last line) everything fails. Is there any way to solve this?
EDIT: working version, thanks to Sajid :-)
UPDATE: Completed the code, maybe it will be helpful to anyone
In the second line, you are replacing the entire prototype of the MVC object with an instance of the MVC object, and depending on how the thing is initialized, this will likely not work at all. The first like replaces one function, though in the process it completely breaks that function since you don't call its superclass version, so you are not extending, you are really clobbering. To not clobber, you need to do:
(function() {
var oldChanged = gAuto.__proto__.__proto__.changed;
function catcher(key) {
// call old version, and make sure to maintain this reference correctly
oldChanged.call(this, key);
// do your stuff here
}
gAuto.__proto__.__proto__.changed = catcher;
})();
One simple solution is that each object has an idea of this mentioned above. So changed has a reference to this, which will refer to the object being used as the caller's target (except in specific situations that are a bit out of scope here). But basically:
var x = new MVCObject();
x.changed('hi') // this === x
So if your two versions need to do different things, you can check which this the changed method was called from and react appropriately.

pulling an array of objects

I currently have a validation script that has a selection of <input> elements stored in objects with properties such as "id", "type" "isRequired" and"isValid". I currently have this setup() function that does the following:
function setup(obj) {
obj.getElement().onkeyup = function() {validate(obj)}
}
In order to run this setup() function on all of my input objects I need to execute the following addEvents() function
function setEvents() {
setup(firstName)
setup(lastName)
setup(email)
setup(dateOfBirth)
}
I'm helping create a system that has multiple pages of nothing but forms so I'd prefer if I didn't have to type this for each object. Is there a way I can collect an array of all the objects that are based on a specific object template? This way I could loop through the array and apply a setup to each object in a single function. If not, are there alternatives?
(p.s. I've been asking so many object-oriented(oh, I crack myself up sometimes) questions lately because this is my first time messing with objects)
---Edit---
the object template I'm referring to looks something like this:
function input(id,isRequired,type) {
this.id = id
this.isRequired = isRequired
this.type = type
}
this is then followed by a
firstName = new input('firstName',true,'alpha')
As I said in my comment, you could add the element to an array when you create it:
var inputs = [];
var firstName = new input('firstName',true,'alpha');
inputs.push(firstName);
This is not ver convenient yet. But you could create another object which manages all this:
var InputManager = {
elements: [],
create: function(/* arguments here */) {
var n = new input(/* arguments here */);
this.elements.push(n);
return n;
},
setup: function() {
for(var i = this.elements.length; i--;) {
(function(obj) {
obj.getElement().onkeyup = function() {validate(obj)};
}(this.elements[i]));
}
}
};
with which you can do:
var firstName = InputManager.create('firstName',true,'alpha');
// etc.
InputManager.setup();
Something along these lines. I think this would be a quite object oriented way. If you have a collection of objects, you often have another object which handles the functions that should be performed on all those objects.
As with most javascript questions, the easiest way to do this is with a library such as jQuery. If you have a unique way to differentiate these objects with a css selector (e.g., they all have the class "validate" or they're the only input[type="text"] fields on the page or something), then you can do a simple selection like $('.validate') to get an array of all these objects. You can get this array using javascript of course but it's a tad more complicated. Once you have the array you can loop over the elements or you can do a simple bind like $('.validate').change(validate); which will call the validate() method whenever a dom element with the class 'validate' changes.
Edit: So obviously I don't know the entirety of what you're trying to accomplish, but if you're new to web programming, just note also that no matter what you're doing on the client side (ie in the browser), all validation should also be done on the server side. Javascript validation is generally used to just be user-friendly and not to actually validate your inputs, since I could easily just turn javascript off or redefine validate as function validate() {} and bypass javascript validation for whatever reason.
2nd Edit: So I'm not sure if this answer was 100% what you're looking for but it's good to know regardless.
Judging by your examples you are not using jQuery. And for that reason alone, I'm going to up vote you. On the same note, after you get really comfortable with JS and how you can do things, really consider using a framework or saving your scripts so you don't have to reinvent the wheel for each project.
You can actually use the DOM to your advantage!
All the forms in your page can be referenced with document.forms[index]. Alternatively you can also reference a named form with document.formName.
Look at this jsfiddle for an example using the latter.
UPDATE
Reading your update and the fact that you needed a way of creating the input objects and setup the validation. I updated my fiddle with a different approach.
Used the id to hold the validation info regarding the element then the addValidation function reverts the id to it's basic form so you can still use it normally throughout your application.
The only requirement is that you addValidation the first thing after page load. So the ID get revamped first.
The solution is also JS safe, meaning if the user doesn't have JS, apart from no validation, no other things will happen.
I think your problem is that the obj in the onkeyup scope is undefined.
function setup(obj) {
//obj is desired
obj.getElement().onkeyup = function() {validate(obj) //obj is undefined because onkeyup is the new scope of this function
}
instead you could do this:
function setup(obj) {
obj.getElement().onkeyup = function() {validate(this)
}

Categories

Resources