HTML JS Only half of select list is being deleted? - javascript

This is weird, im trying to clear my select list and exactly half is being cleared, not everything. Its also every other item which is left behind. I havent included the data here, but just the setup of the select list. The data is 10 items and only 5 are deleted. UniList calls DeleteAll. The first alert prints out '10' and the second prints out '5'
DID is just document.getelementbyid()
function DeleteAll(string) {
var i;
var list = DID(string);
alert(DID(string).options.length);
for (i = 0; i<list.options.length; i++) {
list[i] = null;
}
alert(list.options.length);
alert("finished deleting");
}
<select size='12' name='UniList' id='UniList' style='width:180px;' onclick=changeuni('UniList','Uni')>
<option value=''></option>
<option value=''></option>
<option value=''></option>
</select>

list.options is a HTMLOptionsCollection that is assumed to be live:
Note: Collections in the HTML DOM are assumed to be live meaning that they are automatically updated when the underlying document is changed.
So with every iteration one item is removed and the document is updated so that list.options and thus list.options.length is updated too. So instead of removing the first, second, third, etc. option of the list you actually remove the first, third, fifth, etc. option.
Use list.options[0] instead to always remove the first option:
while (list.options.length > 0) {
list.options[0] = null;
}
Or remove the options from behind:
for (i = list.options.length-1; i>=0; i--) {
list.options[i] = null;
}
By the way: The HTMLSelectElement has a remove method to remove an option by its index:
while (list.length > 0) {
list.remove(0);
}

You could just replace your existing DeleteAll function with
function DeleteAll(listId) {
var list = document.getElementById(listId);
list.options.length=0;
}
or even
function DeleteAll(listId) {
document.getElementById(listId).options.length=0;
}
If you still prefer to use DID() you can just swap it in

This might be an option as well
while ( list.options.length > 0 )
{
list[0] = null;
}

Try looping from the end of the select list to the beginning. UI suspect as you are deleting them it is reordering the array such that once you have deleted half your next index then is over the top. eg if you have 10 items then after deleting 5 it will try to delete item with index 6 but you only have a list length of 5 so that won't work...
So basically try:
for (i = list.options.length-1 ; i>=0; i--) {
list[i] = null;
}
I think I got my indexes right there but adjust the loop accordingly in case I got it wrong.
Edit:
I realised that there is also a better way to delete. Just use list.options.length=0 will clear your list. I've put some examples into a jsFiddle so you can see them in action (and so I coudl test they worked).
http://jsfiddle.net/HUvA2/3/ - The first button runs your method (You'll see it deletes every other entry) and the second runs my modified loop which you can see gets rid of them all. The "best" button just sets the list length to 0. :)

Related

Using parent() in a for loop

I am creating a chrome extension that blocks all porn results on all torrent search engine sites.
So I am trying to retrieve the name of the torrents and check them against the array of strings containing blocked (adult/porn) words that I created. If it matches the array word then it should set the display of the parent element to none. But parent() from jQuery doesn't seem to work around this in a for loop. This is the code that I am using.
// 'blockedWords' is the array.
// '$("dl dt")' contains the words that I am checking against strings from
// the array 'blockedWords'.
for (var i = 0; i < $("dl dt").length; i++) {
for (var j = 0; j < blockedWords.length; j++) {
if($("dl dt")[i].innerText.indexOf(blockedWords[j]) > -1){
$(this).parent().style.display= "none"; // 1st Method or
$("dl dt")[i].parent().style.display= "none"; // 2nd Method
}
}
}
// 1st Method shows the error 'Cannot set property 'display' of undefined'
// 2nd Method shows the error '$(...)[i].parent is not a function'
// '$("dl dt")[i].parent().style.display' doesn't work but
// '$("dl dt").parent().style.display' doesn't work either
// '$("dl dt")[i].style.display' works perfectly without parent().
I have also tried 'parents()'.
Any help will be appreciated :).
As a newbie, I am also open to any other suggestions or recommendations.
And I would be really grateful if you could explain your code as well :)
And by the way, can you believe there are more than 500 porn companies out there :o :P :D
Since you have jQuery, you can avoid using nested for-loops using jQuery's filter() and JavaScript reduce(s,v):
// Filter function removes elements that return a false/falsey value like 0
$("dl dt").filter(function() {
// Save current element's innerText so we can use it within the reduce function
var str = $(this).text();
// Return sum of reduce function
return blockedWords.reduce(function(s, v) {
// For each item in blockedWords array, check whether it exists in the string. Add to total number of matches.
return s + !!~str.indexOf(v);
}, 0); // 0 = intial value of reduce function (number of matches)
}).parent().hide(); // Hide elements which pass through the filter function
Demo:
var blockedWords = [
'shit', 'fuck', 'sex'
];
$("dl dt").filter(function() {
var str = $(this).text();
return blockedWords.reduce(function(s, v) {
return s + !!~str.indexOf(v);
}, 0);
}).parent().hide();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<dl><dt>this is shit</dt></dl>
<dl><dt>this is okay</dt></dl>
<dl><dt>fuck this</dt></dl>
<dl><dt>no problem</dt></dl>
<dl><dt>sex videos</dt></dl>
EDIT: I apologize for the earlier answer if you saw it, as it was incomplete. I have also added a snippet for demonstration purposes. For further explanation of the reduce algorithm, check this answer out (basically it converts the value of indexOf to either a 0 or 1, because indexOf returns -1 if not found, or another 0-indexed integer of the position if found).
JQuery's parent function returns a JQuery object with the parent element inside of it. If you want to access the element from this object you need to retrieve the element from the object using the bracket notation.
If you were to provide some HTML I would be able to test this and make sure it works, but here is some code that could get you pointed in the right direction to use mostly JQuery instead of relying on for loops with JavaScript.
JQuery Rewrite
$("dl dt").each(function(index, element){
if($.inArray(blockedWords,$(element).text()) > -1) {
$(this).parent().css("display", "block");
$(element).parent().css("display", "block");
}
})
The Answer To Your Specific Question
Change this:
$(this).parent().style.display= "none"; // 1st Method or
$("dl dt")[i].parent().style.display= "none"; // 2nd Method
to this:
$(this).parent()[0].style.display= "none"; // 1st Method or
$($("dl dt")[i]).parent()[0].style.display= "none"; // 2nd Method
optionally, you can instead use JQuery's css function like this:
$(this).parent().css("display", "none"); // 1st Method or
$($("dl dt")[i]).parent().css("display","none"); // 2nd Method

Preselected ng-select value issue

I'm trying to get a preselected value within , i've tried multiple tutorials and looked for answers here but none worked.
Here's the deal ->
I load my shifts with $http.get("api/shifts"), then:
<select multiple class="form-control" ng-options="shift as shift.nom for shift in shifts" ng-model="selectedShift"></select>
And it goes to a modal window. But before this modal window opens, I can preselect a shift (to see who would be able to do it). In this case, I have
if(preselectedShift){
$http.get("api/shifts/"+preselectedShift._id).success(function(shift){
$scope.selectedShift = shift; //so it's replacing the ng-model in <select>
})
}
And all shifts appear as they should, but gives me no preselected shift. I also tried with a loop.
for (shifts.length) -> if(shift[i]._id == preselectedShift._id) ->$scope.selectedShift = shift[i]
Which gave me an error "value.forEach is not a function"
Nor ng-select="shift._id == preselectedShift._id" has worked (gives the same error).
Thank you in advance!
The problem was with "multiple" in the select tag. Multiple means that I can select multiple options and therefore the passed object is an array.
So I had to init $scope.selectedShift = [] and then, as Matthew suggested $scope.selectedShift.push($scope.shifts[x])
I think you're pretty close with your solution, it's just that forEach is not supported natively, if you use a for loop like so it should be good:
$scope.selectedShift = [];
if(preselectedShift){
$scope.selectedShift.length = 0;
$http.get("api/shifts/"+preselectedShift._id).success(function(shift){
for(var x = 0; x < $scope.shifts.length; x++){
if($scope.shifts[x]['_id'] === shift['_id']){
$scope.selectedShift.push($scope.shifts[x]);
}
}
})
}
The reason you have to do this is because in your ng-options you use shift as shift.nom ... which means that in order for it to be selected it actually has to be the same reference from the array not just something equaling it. Here's a quick example explaining (hopefully) how angular checks to see if to select something:
var myTest = { test: 4};
var myArray = [{ test: 4 }];
var myTest2 = myArray[0];
console.log(myArray[0] === myTest); //this outputs false
console.log(myArray[0] === myTest2); //this outputs true

Array.splice inside a for loop causing errors

im using Angular ng-repeat to display $scope.currentMessageList array
i also have a remove button bound via ng-click to the remove function, which looks like this:
remove: function () {
for (var i = 0; i < 25; i++) {
var index = i;
$scope.currentMessageList.splice(index, 1);
console.log($scope.currentMessageList.length + 'left');
}
}
There are 25 items in this collection, when I call the remove function,
I get this output:
24left
23left
22left
21left
20left
19left
18left
17left
16left
15left
14left
13left
13times X 12left
If I replace the for loop with angular.forEach
I get "12 left" only once, still it doesn`t remove more than 13 items
Ive also tried to use angular.apply, than I get digest already in progress error
Performing a splice while iterating through an array is a bad idea.
You should replace
for( var i = 0; i < 25; i++ ){
var index = i;
$scope.currentMessageList.splice( index, 1 );
console.log($scope.currentMessageList.length + 'left');
}
by a simple
$scope.currentMessageList.splice( 0, 25 );
You're removing items while walking the array.
When you reach half of the array you've already removed half the items, so you won't remove anything else.
You can fix this either by always removing the first item or by iterating backwards from 24 towards 0.
When you remove array items in loop, indexes get shifted too. As the result you can iterate over only the half of them. This is the issue here.
If you want to clear 25 first items you can remove them with Array.prototype.shift method instead. In this case it will remove the first element of the array 25 times, giving you expected result:
remove: function () {
for (var i = 0; i < 25; i++) {
currentMessageList.shift();
}
}
When you splice the array.. the length of the array changes.
When you are trying to remove the element at index 13, the length is 12 only.
Hence it is not removed.
Instead of splice, try shift();
You don't need to iterate over your array to remove all the items. Just do this:
remove : function(){
$scope.currentMessageList = [];
}
Check out this answer also. There are others way to achieve this that are also valid.

GetElementsByClassName Not Working As Expected [duplicate]

This question already has answers here:
What do querySelectorAll and getElementsBy* methods return?
(12 answers)
Closed 8 years ago.
I have a table that typically looks like this on the screen:
The multiple rooms are displayed by using a foreach loop.
Now I need to disable all the second dropdown boxes when a value has been selected in one of the first ones, or vice versa.
Typical code for one of the dropdown boxes is
<select onchange="std()" class="numrooms" name="numrooms[4]">
<option value="" selected>Select</option>
<option value="1"> 1</option>
<option value="2"> 2</option>
</select>
I am using the following javascript:
function std() {
d = document.getElementsByClassNames("numrooms").value;
if (d>0) {
document.getElementsByClassNames('numrooms_nr').disabled = true;
}else{
document.getElementsByClassNames('numrooms_nr').disabled = false;
}
}
function nr() {
e = document.getElementsByClassNames("numrooms_nr").value;
if (e>0) {
document.getElementsByClassNames('numrooms').disabled = true;
}else{
document.getElementsByClassNames('numrooms').disabled = false;
}
}
but it doesn't work.
I have tried changing the classes to IDs and then using GetElementById() in the script and that does work, but of course it only works on one pair of dropdowns. I thought going to classes and using Get ElementsBy ClassName() would do the trick, but apparently not.
Am I missing something obvious? Or doing it completely wrong?
EDIT
As everyone pointed out, I wrote "getElementsByClass" in the question when it should have been "getElementsByClassName". However that was a mistake when I wrote the question and not in my actual code. I've corrected it here now.
EDIT2
I'm getting there, but not quite fully sorted yet. I've adopted #Notulysses suggestion so for testing purpose my script is
function std() {
d = document.getElementsByClassName('numrooms')[1].value;
if (d>0) {
var n = document.getElementsByClassName('numrooms_nr')
for(var i=0;i<n.length;i++){
n[i].disabled = true;
}
}else{
var n = document.getElementsByClassName('numrooms_nr')
for(var i=0;i<n.length;i++){
n[i].disabled = false;
}
}
}
function nr() {
e = document.getElementsByClassName('numrooms_nr')[0].value;
if (e>0) {
var n = document.getElementsByClassName('numrooms')
for(var i=0;i<n.length;i++){
n[i].disabled = true;
}
}else{
var n = document.getElementsByClassName('numrooms')
for(var i=0;i<n.length;i++){
n[i].disabled = false;
}
}
}
function(std) now disables all of the second dropdown boxes when the first dropdown in the second room is selected (because I have set it to 1). Similarly function(nr) disables all of the first dropdown boxes (because I have set it to [0]).
But how do I disable all the second dropdowns when any of the first dropdowns is selected?
You are using getElementsByClass (it doesn't exist) and changing property for the whole collection (not valid, you should iterate through Node list to change attribute's value). You should do something like this :
var n = document.getElementsByClassName('numrooms')
for(var i=0;i<n.length;i++){
n[i].disabled = true;
}
Its GetElementsByClassName not GetElementsByClass and it returns you NodeList of nodes so if you want to change any property you need to use indexing, i.e, looping:
document.getElementsByClassName('numrooms_nr')[0].disabled = true;
and here is your complete code:
var d = document.getElementsByClassNames("numrooms");
for(var i=d.length-1;i>=0; i--){
if(n[i].value > 0){
n[i].disabled = true;
}
else{
n[i].disabled = false;
}
}
You are using it wrong. It's not getElementsByClass , it is getElementsByClassName. And it returns a HTMLCollection of found elements. To have an access to any element you should use indexing.
document.getElementsByClassName('someclass')[0] <- index
See the link for more details -> Link
getElementsByClassName and getElementsByTagName do not return a single element, like get ElementById. Rather, they return an array containing all the elements with that class. This has tripped up many Javascripters over time. Also note that getElementsByClassName won't work in early IE versions (surprise surprise!)
As such, you are missing the bit with the [0] or [1] or the [2] etc. after getElementsByClassName is written, for example:
document.getElementsByClassName("numrooms")[0]
will refer to the first of the bunch with that class name.

Unsetting javascript array in a loop?

I have the following code:
$.each(current, function(index, value) {
thisArray = JSON.parse(value);
if (thisArray.ttl + thisArray.now > now()) {
banned.push(thisArray.foodID);
}
else{
current.splice(index,1);
}
});
There's a problem with the following line:
current.splice(index,1);
What happens is that it (probably) unsets the first case which fits the else condition and then when it has to happen again, the keys don't match anymore and it cannot unset anything else. It works once, but not in the following iterations.
Is there a fix for this?
You can use a regular for loop, and loop backwards:
for(var i=current.length-1; i>= 0; i--) {
var thisArray = JSON.parse(current[i]);
if (thisArray.ttl + thisArray.now > now()) {
banned.push(thisArray.foodID);
} else {
current.splice(i, 1);
}
});
Also it seems thisArray is actually an object, not an array...
you can use regular for loop, but after splice, decrease index by 1
for(var i=0; i<current.length; i++){
current.splice(i, 1); i--;
}
You should pay attention if you mutate an array (by moving objects) while it's being traversed because some element may get skipped like in your case.
When you call current.splice(index, 1) element index will be removed and element index+1 will take its place. But then index will be incremented, thus skipping one element.
A better solution is IMO the read-write approach. You keep one index as the "read pointer" and you always increment it, and another index (the "write pointer") that is incremented only when you decide an element should be kept in the array:
var wp = 0; // The "write pointer"
for (var rp=0; rp<a.length; rp++) {
if (... i want to keep element a[rp] ...) {
a[wp++] = a[rp];
}
}
a.splice(wp); // Remove all elements after wp
This is a o(N) operation and will move each element at most once. Other approaches like starting from the end and using the same splice(i, 1) approach instead will keep moving all elements after i each time an element needs to be removed.

Categories