Confusing error with an if statement - javascript

I have a Greasemonkey script that keeps track of different things on unicreatures.com.
One of the things I wanted to count was clicks on some links on the page, but not all links.
These need counted,
http://unicreatures.com/explore.php?area=sea&id=89&key=bf12
These should not be counted,
http://unicreatures.com/explore.php?area=sea&gather=5&enc=394844&r=
Someone helped me figure out a regexp that did what I wanted, but I had to code each different explore location (area=**) into it, so I decided that wouldn't work.
The regexp version
var links = document.getElementsByTagName( 'a' );
for ( var i = 0; i < links.length; i++ ) {
var link = links[i];
if ( /area=sea(?!\&gather)/.test( link.href )) {
link.addEventListener( 'click', function () {
localStorage.steps=Number(localStorage.steps)+1
// alert(localStorage.steps + ' in Sargasso' );
}, true );
}
}
Obviously I don't want a billion if statements for the different values of area=, and I couldn't find a way to add a variable to a regexp.
So I finally found some string manipulation commands and put together this:
var url = window.location.href;
var startOf=url.indexOf("=")+1;
var endOf=url.indexOf("&");
var loc =url.substring(startOf,endOf);
var links = document.getElementsByTagName( 'a' );
for ( var i = 0; i < links.length; i++ ) {
var link = links[i];
if (url.indexOf("area=")>=0 && url.indexOf("gather=")<0) {
link.addEventListener( 'click', function () {
localStorage.steps=Number(localStorage.steps)+1
localStorage[loc+"Steps"]=Number(localStorage[loc+"Steps"])+1
alert(localStorage[loc+"Steps"] +" in local"+loc);
}, true );
}
}
For some reason it counts even when the second condition is false. Is this a simple case of me getting the syntax wrong somewhere, or is this a Greasemonkey bug? I don't get any errors in the console.

Try just to tweak your regexp version, instead of this:
if ( /area=sea(?!\&gather)/.test( link.href )) {
use this regex:
if ( /area=(\w*)&id=/.test( link.href )) {
that will match all links that have an 'area' parameter followed by an 'id' parameter, which seems enough to match the links you want.

Prefilter the links and test the link.href, not url -- which was set to the page's address by previous code.
var linksWithArea = document.querySelectorAll ("a[href*='area=']");
for (var J = linksWithArea.length - 1; J >= 0; --J) {
var link = linksWithArea[J];
if ( ! /gather=/i.test (link.href) ) {
link.addEventListener ('click', function () {
localStorage.steps = Number(localStorage.steps) + 1;
localStorage[loc+"Steps"] = Number(localStorage[loc+"Steps"]) + 1;
alert(localStorage[loc+"Steps"] +" in local"+loc);
}, true );
}
}

Related

Wrong sorting on final step. function getColor

I can not understand what I'm doing wrong when I send a variable to function as a wrap of the previous function.
The function GetColor must recive two values (variable sort and variable a) at the input and then compare them. If the some of the values of a[i].getAttribute('href') matches the values of sort[i] - print these tags a on the screen and paint these tags a inside the DOM in yellow color.
Now I'm get an odd sorting on the output of GetColor on the previously discarded value "http://internal.com/" in the GetSort func.
I think the error in my bad knowleges of transfering function arguments.
I will be thankful for your help.
<script>
let a = document.body.getElementsByTagName('a');
function getList(list) { // creating an array from all a tag elements.
let arr = [];
for (let i = 0; i < a.length; i++) {
if (a[i].getAttribute('href')) {
arr.push(a[i].getAttribute('href'));
}
}
return arr;
};
function getSort(f) { // sort array given from getList() by symbols 'http'...
let sorting;
let arr = [];
for (let i = 0; i < f.length; i++) {
if (f[i].includes('://') && !f[i].includes('http://internal.com/')) {
console.log(f[i]);
arr.push(f[i]);
}
}
return arr; // [ "http://google.com" , "ftp://ftp.com/my.zip" ,
// "http://nodejs.org" ]
};
let sort = getSort(getList());
console.log(sort);
function getColor(sort) { // paint a tags based on sort elements from getSort()
for (let i = 0; i < a.length; i++) {
if (a[i].getAttribute('href') == sort[i]) {
a[i].setAttribute('class', 'external'); // paint sorted a tags in DOM
// by [external] attribute
console.log(a[i]);
}
}
return a;
}
getColor(a);
</script>
HTML:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
.external {
background-color: yellow
}
</style>
</head>
<body>
<a name="list">list</a>
<ul>
<li>http://google.com</li>
<li>/tutorial.html</li>
<li>local/path</li>
<li>ftp://ftp.com/my.zip</li>
<li>http://nodejs.org</li>
<li>http://internal.com/</li>
</ul>
</body>
</html>
Alright.
If you add some log statements, you can see exactly what's happening:
let a = document.body.getElementsByTagName('a');
function getList(list) { // creating an array from all a tag elements.
let arr = [];
for (let i = 0; i < a.length; i++) {
if (a[i].getAttribute('href')) {
arr.push(a[i].getAttribute('href'));
}
}
return arr;
};
function getSort(f) { // sort array given from getList() by symbols 'http'...
let sorting;
let arr = [];
for (let i = 0; i < f.length; i++) {
if (f[i].includes('://') && !f[i].includes('http://internal.com/')) {
console.log(f[i]);
arr.push(f[i]);
}
}
return arr; // [ "http://google.com" , "ftp://ftp.com/my.zip" ,
// "http://nodejs.org" ]
};
let sort = getSort(getList());
function getColor(sort) { // paint a tags based on sort elements from getSort()
for (let i = 0; i < a.length; i++) {
console.log( 'a' );
console.log( a[ i ] );
console.log( a[ i ].getAttribute( 'href' ) );
console.log( 'sort' );
console.log( sort[ i ] );
console.log( sort[ i ].toString() );
console.log( a[ i ].getAttribute( 'href' ) == sort[ i ] );
console.log( '-----' );
if (a[i].getAttribute('href') == sort[i]) {
a[i].setAttribute('class', 'yellow'); // paint sorted a tags in DOM
// by [external] attribute
}
}
return a;
}
getColor(a);
.yellow {
background-color: yellow
}
<a name="list">list</a>
<ul>
<li>http://google.com</li>
<li>/tutorial.html</li>
<li>local/path</li>
<li>ftp://ftp.com/my.zip</li>
<li>http://nodejs.org</li>
<li>http://internal.com/</li>
</ul>
a is a nodelist defined outside your functions.
Then you create the sort array with getSort()
And then finally you call getColor() using a instead of sort.
So basically you're comparing a to itsself.
Since a contains html nodes, sort also contains html nodes.
So when you use a[i].getAttribute( 'href' ) you get a string.
And then you compare that string with its own node.
Since you're using == ( compare value ) and not === ( comapre value And type ), the nodes in sort ( which is the same node as in a ) will call its own toString() function to cast it into a string.
As you can see in the console.log statements I added, the href attributes come back with a / at the end, if the link doesn't contain one yet ( as in http://internal.com/test )
And since http://google.com/ is not the same as http://google.com, you get false. Same with http://nodejs.org/ and http://nodejs.org.
So only ftp://ftp.com/my.zip and http://internal.com/test satisfy the condition and get printed in yellow.
If I had to write this, I would go for something like this.
It doesn't incldue the functions, but shows the workflow i'd follow.
// 0) Get the array of tag elements.
const hyperlinks = document.querySelectorAll( 'a' );
// 1) Creating an array from all a tag elements.
const ary_hyperlinks = Array.from( hyperlinks );
// If your browser doesn't support Array.from(), you can use the slice method.
// const ary_hyperlinks = Array.prototype.slice.call( hyperlinks );
// 2) Sort array given from getList() by symbols 'http'.
// Since your code doesn't actually do any SORTING as we understand sorting, i'll just write what I think the question is.
// Since the assignment doesn't actually say that you have to reorder the elements in sorted order, this operation basically does nothing.
// If you have to reoder the elements while you color them yellow, we'd need to adjust the code.
const sorted_filtered_hyperlinks = ary_hyperlinks
// Filter out all links that don't have a href attribute including http, ftp and that are not internal.
.filter( function( node ) {
const uri = node.getAttribute( 'href' );
if ( uri ) return !uri.includes( 'internal' ) && uri.includes( 'http' ) || uri.includes( 'ftp' );
else return false;
} )
// Sort the links by type. Since the type is the start of the href attribute of the link, this comes down to sorting the urls alphabetically
.sort( function( first, second ) {
// Alphabetically earlier eltters are smaller than later letters.
if ( first.getAttribute( 'href' ) < second.getAttribute( 'href' ) ) return 1;
else return -1;
} );
// 3) paint a tags based on sort elements from getSort()
sorted_filtered_hyperlinks.forEach( function( node ) {
node.setAttribute( 'class', 'yellow' );
} );
.yellow {
background-color: yellow
}
<a name="list">list</a>
<ul>
<li>http://google.com</li>
<li>/tutorial.html</li>
<li>local/path</li>
<li>ftp://ftp.com/my.zip</li>
<li>http://nodejs.org</li>
<li>http://internal.com/</li>
</ul>
I can't comment your question and It is what i would like indeed to do. As shilly, your issue doesn't seem clear to me as well.
But few issue in your code.
function getList : you specify a list parameter in the definition that you never use, better not to put i think. Personally, i will keep the parameter and avoid using outer scope variable inside my function a[i]... and make sure the function does its job only when passed an appropriate variable.
function getSort : if i follow well, it is just a funtion that construct a new array of link excluding links beginning with http://internal.com/ ? Rigth ? What the local variable sorting is then doing ? you never return it nor use it for anything inside you function.
function getColor : You call getColor(a), so bear in mind that a is passed by reference to getColor by mean of sort parmater. Hence in you getColor sort and a are the same. you are comparing same thing all time in you getColor function
Someone can say if i am wrong on all this.
Write yours function to do their job, with their own arguments/parameters and avoid using any outer parameter directly them would be my advice

Javascript loop doesn't work in meteor

This should work but its not. What am I doing wrong? I want to output "selected" to tags I have on a meteor page
Template.editor.onRendered( function() {
var cats = ["Comedy","Action","Self Help"];
var arrayLength = cats.length;
for (var i = 0; i < arrayLength; i++) {
if(cats[i].indexOf(getDocument.category) != -1){
//found
var id = cats[i].trim().toLowerCase();
$("body").find("#"+id).attr("selected=selected");
console.log(id);
} else {
console.log(getDocument.category)
}
}
}
also
getDocument.category = ["Action", "Comedy"]
Maybe change
$("body").find("#"+id).attr("selected=selected");
with
$("body").find("#"+id).attr("selected","selected");
Edit:
if(cats[i].indexOf(getDocument.category) != -1){
I think you have here a wrong direction
try this instead:
if(getDocument.category.indexOf(cats[i]) != -1){
If I do not mistakenly understand what you asking for, you are trying to find the elements of 'cats' array if exist in the getDocument.category. If so, the above approach is wrong. Take a look at this line:
if(cats[i].indexOf(getDocument.category) != -1){...}
the result of this if checking will always returning -1, the explanation is below:
cats[i] will return the element (with index i) of cats, so if i=0 the result will be "Comedy". Then, indexOf will be executed on it, "Comedy".indexOf() ,
to find the position of getDocument.category (which is an array).
That's means you are looking for an array inside a string? that's will not work.
Actually, we can check if an element exists in array with includes methods. So maybe the complete code will be looked like this:
Template.editor.onRendered(function() {
var cats = ["Comedy", "Action", "Self Help"];
var arrayLength = cats.length;
for (var i = 0; i < arrayLength; i++) {
if (getDocument.category.includes(cats[0])) {
//found
var id = cats[i].trim().toLowerCase();
$("body").find("#" + id).attr("selected=selected");
console.log(id);
} else {
console.log(getDocument.category)
}
}
})
Hope this will help, thanks
You need to change a line to set selected attribute
$("body").find("#"+id).attr("selected","selected");
Or try the following:
$(document).find("#"+id).attr("selected","selected");

Is there a way to pass 'missing' object to .append()

jQuery's .append() function can take multiple arguments, either flat or in an array. I have some code where I need to append 3 items, one of which might not exist, like:
whatever.append(always).append(maybe).append(alwaysToo);
/* or */
whatever.append(always, maybe, alwaysToo);
/* or */
var arrayOfThoseThree = [ always, maybe, alwaysToo ];
whatever.append(arrayOfThoseThree);
I can not make out from the jQuery docs what, if anything, the value of maybe should be to say "just ignore this one":
maybe = '';
maybe = null;
maybe = undefined;
maybe = ???
as in:
maybe = needMaybe ? $('<blah...>') : ignoreThisValue;
I could, of course, do something like:
whatever.append(always);
if (maybe) whatever.append(maybe);
whatever.append(alwaysToo);
but that's ugly (especially as this is part of a larger chain).
And I could experiment with different values until I find one that "works", but I was hoping there was an "official" documented way that won't fail to work some future day because I was using an "undocumented feature".
Point me in the right direction?
[EDIT]
I was wondering in general, but the concrete example in front of me is:
var titl = this.dataset.title; /* optional */
var ifr = $('<iframe>');
var bas = $('<base href="' + document.baseURI + '">');
var ttl = titl ? $('<title>' + titl + '</title>') : null; /* HERE */
var lnk = $('<link rel="stylesheet" href="/css/print.css">');
/* ... */
ifr.contents().find('head').append(bas, ttl, lnk);
How about
whatever.append([always, maybe, alwaysToo].filter(item => !!item));
Here's what happens in the jQuery code (the version I'm using anyway).
Note that this defines what "works" today, not what is documented to work and continue working in the future.
The .append() function is written similarly to many others in that domManip() does much of the work:
append: function() {
return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 ||
this.nodeType === 11 ||
this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.appendChild( elem );
}
});
},
and the first thing domManip() does is:
domManip: function( args, callback ) {
// Flatten any nested arrays
args = concat.apply( [], args );
then it calls buildFragment():
fragment = jQuery.buildFragment( args, ... );
which does:
buildFragment: function( elems, context, scripts, selection ) {
var /* ..., */ i = 0;
for ( ; i < l; i++ ) {
elem = elems[ i ];
if ( elem || elem === 0 ) {
/* ... process this argument ... */
}
}
So empty arrays get squashed by Array.prototype.concat() and then anything that fails the test ( elem || elem === 0 ) gets ignored.
So, in fact, when ttl could be null, all of these (currently) do "the right thing":
whatever.append( bas, ttl, lnk);
whatever.append([bas, ttl, lnk]);
whatever.append([bas],[ttl], [lnk]);
whatever.append( bas, [ttl], lnk);
whatever.append(bas).append( ttl ).append(lnk);
whatever.append(bas).append([ttl]).append(lnk);
But, as near as I can find, the documentation makes no statements about a value or values which you can use which will safely be ignored (now and forever).
Thus the safest course of action (at least where => is supported) is the Answer from Assan:
whatever.append( [bas, ttl, lnk].filter( e => !!e ) );

Get the matches from specific groups

I want to validate a String and, after that, get all the matches from some groups.
RegEx:
/^<[A-Za-z0-9]>::=(<[A-Za-z0-9]>)+(\|(<[A-Za-z0-9]>)+)+$/
So, if I get something like <A>::=<B><A>|<Y><A>|<Z> is valid, but if I get something like <A>::=<B>| is false.
There's no problem with the validation, the problem is that I want to take the text inside < and > because I need it later.
So, if I get <exparit>::=<number>|<number><exparit>, then I want to get ["exparit", "number", "number", "exparit"]
My code looks like
Rules = {
"BNF" : /^<[A-Za-z0-9]>::=(<[A-Za-z0-9]>)+(\|(<[A-Za-z0-9]>)+)+$/
};
var checkBNF = function ( bnf ) {
if ( Rules.BNF.test( bnf ) ) {
console.log('ok');
//How to get the text inside < and > ??
}
else {
console.log('no');
}
};
I really appreciate any kind of help, such a book, link, example or the resolution of this problem.
Thanks!
If I can help you a bit, here's something to stat with :
http://jsfiddle.net/URLhb/1/
var test = "<A>::=<B><A>|<Y><A>|<Z>";
var patt0 = /<([a-zA-Z]*)>/g; //Old and bad version
var patt1 = /[a-zA-Z]+/g;
var testRE = test.match(patt1);
alert(testRE[0]);
alert(testRE[1]);
alert(testRE[2]);
alert(testRE[3]);
alert(testRE[4]);
alert(testRE[5]);
This code will capture the text inside the < >, but also the < and > symbols with it. I'm trying to fix this, I'll update if I get a better result.
EDIT : Found the issue : I was using a * instead of a + ! It works perfectly now !
What I did was slightly different as I wasn't sure that you would only ever have [a-zA-Z0-9] within the <>
Rules = {
"BNF" : /^<[A-Za-z0-9]>::=(<[A-Za-z0-9]>)+(\|(<[A-Za-z0-9]>)+)+$/
};
var checkBNF = function ( bnf ) {
if ( Rules.BNF.test( bnf ) ) {
console.log('ok');
//How to get the text inside < and > ??
var patt = /\<(.*?)\>/g;
var strArray = bnf.match(patt);
for (i=0;i<strArray.length;i++) {
strArray[i] = strArray[i].replace('<','').replace('>','');
}
return strArray;
}
else {
console.log('no');
}
};
var test = "<A>::=<B><A>|<Y><A>|<Z>";
var result = checkBNF(test);
console.log(result)
http://jsfiddle.net/UmT3P/

JavaScript 'input multiple' file duplication

This is my code:
var rowData = [];
var showFiles = function () {
var input = document.getElementById('doc-upload');
if (input.files.length > 0) {
for (var i = 0; i < input.files.length; i += 1) {
rowData.push([input.files[i].name]);
};
console.log(rowData);
};
};
document.getElementById('doc-upload').addEventListener('change', showFiles, this);
'rowData' recives a normal value for the first time i upload some thing.
At the second try it starts to duplicate incoming values.
If i uploaded 1 file first try it's ok.
Second time i wold get 2 same files.
Third 4 files, exetera.
Why this is happening?
UPD:
More explanations:
What happaning is: i upload x.doc my array is [x.doc], now i want to upload y.doc. I choose y.doc and my array turns in to [x.doc, y.doc, y.doc], then i upload z.doc and aaray looks like [x.doc, y.doc, y.doc, z.doc, z.doc, z.doc, z.doc]. And so on. It have to do some thing with length property, i messed it up some how.
UPD2:
Now the full version of the code:
//file upload
var rowData = [];
docAddButton.addListener('click', function () {
var showFiles = function () {
var input = document.getElementById('doc-upload');
if (input.files.length > 0) {
for (var i = 0; i < input.files.length; i += 1) {
if (rowData.indexOf(true, input.files[i].name, Math.round(input.files[i].size * 0.001) + ' кб') === -1) {
rowData.push([
true,
input.files[i].name,
Math.round(input.files[i].size * 0.001) + ' кб'
]);
}
}
console.log(rowData);
tableModel.setData(rowData);
};
};
document.getElementById('doc-upload').addEventListener('change', showFiles, this);
});
Thanx every one who helped!
Solution:
You can see from UPD2 that I have my function inside eventListener 'click'. What was happening is every time I pressed my input button it was reciving extra 'change' listener.
I changed .addListener for input to .addListenerOnce.
Presumably you're not reloading the page, and you're not clearing out rowData. You haven't shown how/where you define rowData, but it would appear that the same instance of it is reused between calls to showFiles. (If you haven't declared it anywhere, you're falling prey to The Horror of Implicit Globals.)
From your comments below, it sounds like you want to keep the entries in rowData, but you want to only add new entries. If so, you'll need an explicit check:
var showFiles = function () {
var input, i, file;
input = document.getElementById('doc-upload');
for (i = 0; i < input.files.length; i += 1) {
file = input.files[i].name;
if (indexOfFile(rowData, file) === -1) {
rowData.push([file]);
}
};
console.log(rowData);
};
function indexOfFile(data, file) {
var index;
for (index = 0; index < data.length; ++index) {
// Note that the 0 may need to be changed if the
// `rowData.push([file])` line above doesn't put
// the filename at index 0
if (data[index][0] === file) {
return index;
}
}
return -1;
}
Side note: You've said that the reason you're pushing an array rather than just the filename is that there's other information you're also including in that array but you left it out of your question. That's fine, but it may be delicate, see my note about index 0 above. You might consider using an array of objects instead:
rowData.push({
file: file,
other: "info",
goes: "here"
});
...and then in indexOfFile:
if (data[index].file === file) {
That way, the code is less susceptible to breaking if you change the structure of what you're pushing.
Basically,
rowData.push()
will just keep appending the elements to the end of your variable. You need to have a check if those inputs are already present.
(Edit-)
Try this:-
rowData=[];
var showFiles = function () {
var input = document.getElementById('doc-upload');
if (input.files.length > 0) {
for (var i = 0; i < input.files.length; i += 1) {
var f=[];
for(var j=0;j<rowData.length;j++)
f.push(rowData[j][0]);
if(f.indexOf(input.files[i].name) == -1)
rowData.push([input.files[i].name]);
}
console.log(rowData);
};
};
document.getElementById('doc-upload').addEventListener('change', showFiles, this);

Categories