On a certain homepage I visit I want to hide all links that I click. My idea was to use a Greasemonkey script like this:
var blocklist = JSON.parse(GM_getValue("blocklist"));
var as = document.getElementsByTagName('a');
var alength = as.length;
for(var i=0; i<alength; i++) {
var a = as[i];
if(blocklist.indexOf(a.href) >= 0) {
a.style.display='none';
} else {
a.setAttribute('onclick', 'alert("HELP"); return true;');
}
}
Inside the script I can call this, no problem:
blocklist = blocklist.concat('http://someurl');
GM_setValue("blocklist", JSON.stringify(blocklist));
But in the website itself (read where it says alert("HELP");) I cannot call this function because neither the function nor the blocklist do exist.
Is there a way to access the function from the website? (probably not?) Where else could I store the values to get them back on the next load of the website? The firefox browser is set to sanitize on shutdown, so can't use a:visited or similar.
Don't try to call GM_ functions from a webpage. (1) It's not directly possible, (2) it's a security risk, (3) it's almost never really necessary.
Never use onclick in a Greasemonkey script (or at all, really). A simple alert("HELP"); return true; might work, but anything more will crash and it's bad form anyway.
Also, if you use querySelectorAll versus getElementsByTagName, you can fine-tune what links you process, EG: document.querySelectorAll ("div.main a.user") -- which would get only those links with the CSS class user that were inside the <div> with the class main.
In this case, use addEventListener (or use jQuery) to handle the links so your script code would become like:
var blocklist = JSON.parse (GM_getValue ("blocklist") );
var targlinks = document.querySelectorAll ('a');
for (var J = targlinks.length - 1; J >= 0; --J) {
var targlink = targlinks[J];
if (blocklist.indexOf (targlink.href) >= 0) {
targlink.style.display = 'none';
} else {
targlink.addEventListener ('click', virginLinkHandler, false);
}
}
function virginLinkHandler (zEvent) {
var newURL = zEvent.target.href;
blocklist = blocklist.concat (newURL);
GM_setValue ("blocklist", JSON.stringify (blocklist) );
}
You should use localStorage so that you can retain your list on subsequent page loads. It's really not too different from GM_setValue.
localStorage.setItem("blocklist", JSON.stringify(blocklist));
var blocklist = JSON.parse(localStorage.getItem("blocklist"));
Related
I'm getting an error which I'm not quite sure what to make of. Anyway, before I go on to that, I found out about Unobtrusive JavaScript, at first I was just going to add an "OnClick" to my HTML but then found out that isn't a very good thing to do.
Anyway, so I did that and turned up with this code which isn't quite finished yet, but I wanted to try it out anyway before I went in and made any other changes.
window.onload = function findSubmitButton(){
var button = document.getElementsByClass("send_info").addEventListener("click", retrieveInputText());
}
function retrieveInputText(){
var inputArray = document.querySelectorAll("#container_id input[type=text]");
var finalArray;
for (var i in inputArray){
if(inputArray[i].type == "text"){
finalArray.push(i);
}
alert("done");
}
}
The error chrome's console gives me is this: Uncaught TypeError: undefined is not a functionfindInputs.js:5 findSubmitButton
There was also something I wanted to know, I want to be able to use this script with any other sort of input form, so instead of directly identifying the button for this page, I used a class identifier, this way, it works with any page. The only way there would be any issues would be if I had two buttons of the sort, as it is right now, any page with that sort of information only has one button for such procedures. I would appreciate if someone helped me out with this, I'm new to JavaScript.
getElementsByClassName(), returns an array, not an element so it does not have the addEventListener method, also you need to pass a function reference
window.onload = function findSubmitButton() {
var button = document.getElementsByClassName("send_info")[0].addEventListener("click", retrieveInputText);
}
Also you need to initialize the array var finalArray = [];
window.onload = function findSubmitButton() {
var button = document.querySelector(".send_info").addEventListener("click", retrieveInputText);
}
function retrieveInputText() {
var inputArray = document.querySelectorAll("#container_id input[type=text]");
var finalArray = [];
for (var i in inputArray) {
if (inputArray[i].type == "text") {
finalArray.push(i);
}
}
alert("done:" + finalArray);
}
Demo: Fiddle
It's been a while since I wrote Javascript without jQuery, so please bear with me. I'm assuming I'm just doing something silly. I have this function that converts link urls to an internal representation that I use with a router I wrote.
Templater.prototype.replace_links = function() {
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
this.links[i].setAttribute(this.HREF, this.links[i].getAttribute("href"));
this.links[i].setAttribute("href", this.VOID);
this.links[i].onClick = function(self, link) {
return function() { self.router.go(link.getAttribute(self.HREF)); };
}(this, this.links[i]);
}
}
}
This function is called the first time when Templater is initialized. The first time it works correctly. However, I run it a second time after I append some html into the body of the document. I run it again just in case that appended html has links in it too:
<body>
<!-- arbitrary new html is loaded in here -->
Login <!-- becomes Login correctly -->
Home <!-- becomes Home correctly -->
</body>
When I console.log(this.links[0], this.links[0].onClick) after the function has been run but still within a Templater function, I get the correct html and then undefined for the onClick event:
Discover undefined
When I log the same to values within the replace_links scope, I get what I'm expecting. I.e. the function is shown:
Discover function () { self.router.go(link.getAttribute(self.HREF)); }
I was playing around with it some more and tried this way and got the same kind of thing.
Templater.prototype.replace_links = function() {
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
(function(self, link) {
link.setAttribute(self.HREF, link.getAttribute("href"));
link.setAttribute("href", self.VOID);
link.onClick = function() { self.router.go(link.getAttribute(self.HREF)); };
})(this, this.links[i]);
}
}
}
I console.log after the replace_link scope ends like before and this time I still get:
Discover undefined
I'd really appreciate any help and/or suggestions! Please let me know if I'm missing anything helpful.
The key points here have been treated as minor details.
I append some html into the body of the document
and
this.links[i].onClick = function(self, link) {
My point is, if you alter innerHTML, which I assume is the way you "append some html into the body of the document," the browser will serialize the DOM objects into HTML, do the string concatenation, and then parse it again. This results in new objects which no longer have the expandos, such as onClick. onClick is a custom property; you probably meant onclick anyway.
However, some of your changes will be serialized and parsed successfully, namely the setAttribute operations. Thus, when you run replace_links after the HTML appending, the
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF)))
check will treat the link as already replaced and not assign the onClick again.
Here's a fiddle that shows this in action. http://jsfiddle.net/k9d7b2ds/
UPDATE: Made some additional changes. The onclick event's default this object is always referencing the window object. You need to pass over the closure.
Check sample code here:
http://jsfiddle.net/y0443fz6/
Templater.prototype.replace_links = function() {
var that = this;
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
this.links[i].setAttribute(this.HREF, this.links[i].getAttribute("href"));
this.links[i].setAttribute("href", this.VOID);
this.links[i].onclick = function(self, link) {
return function() {
self.router.go(link.getAttribute(self.HREF));
};
}(that, this.links[i]);
}
console.log(this.links[i], this.links[i].onclick);
}
}
hope that helps. gl
In this code I am supposed to bind a rollover effect to each <area> tag in a <map> element.
function initLinks(webrt) {
var areas = document.querySelectorAll("map#streetmap > area");
var links = new Array(areas.length);
for (var i=0; i<links.length; i++) {
links[i] = new Image(786,272);
links[i].src = webrt+"templates/default/sketches/links"+(i+1)+".png";
areas[i].onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(i+1)+".png");};
areas[i].onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
}
}
Strangely, each <area> onmouseover event tries to load the non-existing image: /templates/default/sketches/links6.png. Why does it keep this variable i which has incremented to 6 as a global variable rather than take the string I am passing to the function?
How do I fix this?
Note: No jQuery!
i often find it cleaner to use array methods when using the index because you don't need extra wrappers and everything reads a little cleaner (imho):
function initLinks(webrt) {
[].forEach.call(document.querySelectorAll("map#streetmap > area"),
function(elm, index){
var img = new Image(786,272);
img.src = webrt+"templates/default/sketches/links"+(index+1)+".png";
elm.onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(index+1)+".png");};
elm.onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
});
}
the variable count is way down, and we avoid extra ram-hogging closures by not creating a extra new function in each iteration of the "loop".
to be sure, both ways work, but the newer array methods can also allow the procedure to be recycled by ripping it out of the forEach() call, and giving it a name.
Try using the following code:
function initLinks(webrt) {
var areas = document.querySelectorAll("map#streetmap > area");
var links = new Array(areas.length);
for (var i=0; i<links.length; i++) {
(function(index) {
links[index] = new Image(786,272);
links[index].src = webrt+"templates/default/sketches/links"+(index+1)+".png";
areas[index].onmouseover=function(){switchLinkImg(webrt+"templates/default/sketches/links"+(index+1)+".png");};
areas[index].onmouseout=function(){switchLinkImg(webrt+"templates/default/sketches/links.png");};
})(i);
}
}
You should wrap the i variable into a closure. Otherwise it gets incremented.
I've got a list of links that point to images, and a js function that takes a URL (of an image) and puts that image on the page when the function is called.
I was originally adding an inline onlick="showPic(this.getAttribute('href'))" to each a, but I want to separate out the inline js. Here's my func for adding an onclick to each a tag when the page loads:
function prepareLinks(){
var links = document.getElementsByTagName('a');
for(var i=0; i<links.length; i++){
var thisLink = links[i];
var source = thisLink.getAttribute('href');
if(thisLink.getAttribute('class') == 'imgLink'){
thisLink.onclick = function(){
showPic(source);
return false;
}
}
}
}
function showPic(source){
var placeholder = document.getElementById('placeholder');
placeholder.setAttribute('src',source);
}
window.onload = prepareLinks();
...but every time showPic is called, the source var is the href of the last image. How can I make each link have the correct onclick?
JavaScript doesn't have block scope, so the closed variable ends up being whatever was last assigned to it. You can fix this by wrapping it in another closure:
function prepareLinks() {
var links = document.getElementsByTagName('a');
for(var i = 0; i < links.length; i++) {
var thisLink = links[i];
var source = thisLink.getAttribute('href');
if(thisLink.getAttribute('class') == 'imgLink') {
thisLink.onclick = (function(source) {
return function() {
showPic(source);
return false;
};
})(source);
}
}
}
Of course, you can make this one simpler and use this:
function prepareLinks() {
var links = document.getElementsByTagName('a');
for(var i = 0; i < links.length; i++) {
var thisLink = links[i];
if(thisLink.getAttribute('class') == 'imgLink') {
thisLink.onclick = function() {
showPic(this.href);
return false;
};
}
}
}
I believe this either breaks compatibility with IE5 or IE6, but hopefully you don't care about either of those =)
Minitech's answer should fix your problem, which is that the source variable is shared by all your onclick handlers
The way you're doing it is very wasteful, there's no need to set a separate handler for each link. Also, it won't work if any links are added dynamically. Event delegation is the way to go.
function interceptLinks() {
// Bad way to set onclick (use a library)
document.onclick = function() {
if (this.tagName.toUpperCase() != 'A' ) {
return;
}
// Bad way to check if it contains a class (use a library)
if (this.getAttribute('class') == 'imgLink') {
showPic(this.getAttribute('href'));
return false;
}
}
}
This is the age-old problem of event handlers inside of a loop that access an outer variable.
Your source variable is pulled off the scope chain at the time of the click event, and by then, it's been set to the last href attribute due to the iteration being finished.
You need to break the closure by doing one of two things.
The easiest but not supported by many browsers is to use let which lets you use block scope.
let source = thisLink.getAttribute('href');
jsFiddle. It worked in Firefox, but not Chrome.
In 2038, when we're dealing with the year 2038 problem and all browsers have implemented ES6, this will be the standard way to fix this problem.
A more difficult to understand and implement method that is compatible with all browsers is to break the closure with a pattern such as...
thisLink.onclick = (function(src) {
return function(){
showPic(src);
return false;
}
})(source);
jsFiddle.
Thanks for all the replies. Turns out I had diagnosed the problem incorrectly, sorry. Actually using a new var and annon. function to add an onclick on each loop iteration works (the passed href is correct). It was not working because I was getting at the a-tags by the "imgLink" class which I had removed from the HTML when I removed the inline onclick handlers (I get them with an ID on a parent now). Also I needed to use "return !showPic(this.href);" to stop the link being followed normally when clicked.
Working code:
function showPic(source){
var placeholder = document.getElementById('placeholder');
placeholder.setAttribute('src',source);
return true;
}
function prepareLinks() {
if(!document.getElementById('imgLinks')){ return false; };
var galLinks = document.getElementById('imgLinks');
var links = galLinks.getElementsByTagName('a');
for(var i=0; i<links.length; i++) {
var thisLink = links[i];
thisLink.onclick = function() {
return !showPic(this.href);
};
}
}
window.onload = function(){
prepareLinks();
}
How can I do a spinlock in javascript?
I'm trying to load a bunch of images and I can only move forward after everything is loaded, so I have a spinlock like
for(...)
image[i].onload = function() { ++imagesloaded; }
while(imagesloaded != totalimages)
{
}
And it crashes my browser. Is there a better way to do it? Or a yield / sleep function I'm missing?
Short answer: don't spinlock.
Longer answer: here's how to do it:
var imagesLoaded = 0;
var totalImages = 42;
function handleImageLoad()
{
imagesLoaded++;
if (imagesLoaded === totalImages)
{
doSomething();
}
}
for (var i=0; i<totalImages; i++)
{
image[i].onload = handleImageLoad;
}
In general, when you want to sleep/wait/spin in JavaScript, instead think about solving the problem in terms of callbacks (and setTimeout/setInterval).
The answers above aren't useful as spinlocks may be required because of limitations/bugs in browsers. For instance safari (hopefully not future versions) requires the use of method window.open when you want to generate a file in javascript. The consequence of this is that you cannot generate the file using any callbacks (because of popup blockers), this in effect forces the use of a dialog window that first calls the file generation function (using callbacks) and then a button that downloads the file. Because spinlocks don't work the code becomes the following:
function process(callback) {
processCallbackData = null; // global var that must be a unique name
callback(function(data) {
processCallbackData = data;
});
}
function fowardButton() {
if(processCallbackData!=null) {
goForwardUsingCallbackIncompatibleCode();
} else {
displayStillLoadingWarning();
}
}
Don't use a loop to check. Check in the event handler function. (So you only do the check when an image has loaded, not continuously and as quickly as possible)