Using userscript GM_functions inside the page context - greasemonkey

I want to "re-link" everything in a specific page through a XMLHTTPRequest to a local network domain. That would lead me to GM_xmlhttpRequest in GreaseMonkey/NinjaKit except that I want to run it when the link is clicked, not when the userscript actually runs...
So I have something like:
links = document.getElementsByTagName('a');
for (i = 0; i < links.length; i++) {
oldhref = links[i].getAttribute('href');
links[i].setAttribute('href', 'javascript:loadLink(' + oldhref + ')');
}
I understand I can either use unsafeWindow or add a script element to document to inject loadLink function.
But how can I use GM_xmlhttpRequest in loadLink?
I've looked at 0.7.20080121.0 Compatibility page but I'm not sure if that is for what I need...
I've also considered adding an iframe to the page and the modified links would load inside the iframe (triggering the userscript again), but I'd prefer a cleaner solution...

You almost never need to use GM functions inside the page context, and from the code posted so far, you don't need unsafeWindow in this case either.
Also, it is not necessary to rewrite the href for what is posted so far.
Something like this will accomplish what you want:
var links = document.getElementsByTagName ('a');
for (var J = 0, len = links.length; J < len; ++J) {
links[J].addEventListener ("click", myLoadLink, false);
}
function myLoadLink (zEvent) {
zEvent.preventDefault();
zEvent.stopPropagation();
var targetHref = zEvent.currentTarget.getAttribute ('href');
GM_xmlhttpRequest ( {
//wtv
} );
return false;
}
Or with jQuery:
$("a").click (myLoadLink);
function myLoadLink () {
var targetHref = $(this).attr ('href');
GM_xmlhttpRequest ( {
//wtv
} );
return false;
}

Ok, so I managed to get that GreaseMonkey official workaround working (dunno what I did wrong the first time) with:
unsafeWindow.loadLink = function(href) {
setTimeout(function(){
GM_xmlhttpRequest({
//wtv
});
},0);
}
But I'd still prefer a solution without using unsafeWindow if there is one... (especially since this one feels so wrong...)

Related

Youtube onYouTubePlayerReady() function not firing

This question has been asked many times before, however I couldn't solve it even after referring the solutions.
I have an embedded Youtube Player. I am following
https://developers.google.com/youtube/js_api_reference#Examples.
Below is my JS code:
var params = {
scale:'noScale',
salign:'lt',
menu:'false',
allowfullscreen :'true',
wmode :'transparent',
allowScriptAccess: 'always'
};
var atts = { id: "myytplayer" };
swfobject.embedSWF("http://www.youtube.com/v/vPxGBYJ9Wt8?enablejsapi=1&playerapiid=ytplayer&version=3", "ytapiplayer", "560", "315", "8.0.0", null, null, params, atts);
function onYouTubePlayerReady(playerId) {
alert("youtube player ready");
ytplayer = document.getElementById("myytplayer");
ytplayer.addEventListener("onStateChange", "onytplayerStateChange");
}
var count = 0;
function onytplayerStateChange(newState) {
alert("inside");
if (newState == 1){
count = count + 1;
//Trying to count the number of times video is played
}
}
I can watch the video, but I am not able to get any of the alert messages.
It is being served from a Web Server. Also, the JS code is written within a JSP page. What are the things I should check?
Update
I tried the same code in a separate fie and it works just fine.
I solved the problem by putting the window.onYouTubePlayerReady inside $(document).ready(function())

Calling Google Apps Script from a Chrome Extension

I know similar questions have been asked here before, but I didn't find an answer that helped me. Sorry for that! I'm probably just too inexperienced to understand those answers, so please bear with me.
I have written a Google Apps Script that scans a specific Spreadsheet (not authored by me, but I can view) and counts certain fields. The doGet(e) function returns the count, so when I run the published-as-web-app I see a number.
Now from my background.js in my chrome extension I did something like this:
var my_app = "https://script.google.com/a/macros/google.com/s/.../exec?param=value";
var xhr = new XMLHttpRequest();
try {
xhr.open("GET", my_app);
xhr.send(null);
var result = xhr.getAllResponseHeaders();
localStorage.count = result;
chrome.browserAction.setBadgeText({text: localStorage.count});
} catch(e)
...
}
This is very rough, since I'm very new to JavaScript and Chrome Extensions and such.
I'm guessing getAllResponseHeaders() is not how I get to the resulting number that is displayed when calling my_app, so what should I use instead? In the API reference for XMLHttpRequest I didn't find anything obvious.
I'm pretty sure there's a lot more wrong with my code, but let's go one step at a time.
Thanks already in advance! Detailed answers would be great, so I can follow them and extend my currently very limited knowledge.
If you are making an HTTP Request from client side JavaScript to Apps Script, then you can use something like this:
function requestToAppsScript() {
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
} else { // code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function() {
//console.log('xmlhttp.readyState: ' + xmlhttp.readyState);
if (xmlhttp.readyState===4 && xmlhttp.status===200) {
//console.log('xmlhttp.responseText: ' + xmlhttp.responseText);
var theReturnData = xmlhttp.responseText;
//console.log('return value: ' + theReturnData);
};
};
xmlhttp.open("GET","URL here",true);
xmlhttp.send();
};
basically I just didn't parse the output right. The output I received contained a lot of things and the number I was looking for was hidden in that mess of a string, always behind some string called "cajaHtml".
Therefore the call is now:
xhr.open("GET", my_app);
xhr.onreadystatechange = handleResponse;
xhr.responseType = "text";
xhr.send(null);
with
function handleResponse() {
if (xhr.readyState == 4) {
var result = xhr.responseText.split("cajaHtml")[1];
}
}
Thanks!

how to import external library to nodejs

I would like to know how can I import an external library to nodejs.
For example I would like to have phanotmjs library (I know that arelady exist an npm to get phantomjs, but is only an example).
I think that a way was to get the source file of library and include it into a module like that:
module.exports = function (name, cb) {
//source code of library
});
But I think it is a wrong way of doing it.
How can I include an external library to nodejs project to use it inside the project with its functionality?
Thanks
Without exporting, a no elegant way is to copy past the entire library, in the bottom of your node file. nasty you may already thought about it. there is also a bad thing about that. you will not be able to reuse it in all different files.
The other way is to export the files along your workflow every time you need a function. And i think this is ok.
Otherwise to answer that, you can write the export this way:
module.exports = {
removeElementFromArray_Mutate,
hasClass,
hasClass_ONEtest,
removeClassFromAll,
addClass,
removeClass
};
you can do that with node. all of those are normal function declared this way:
function removeClassFromAll(DOMobject, classes){
for(let i = 0; i < DOMobject.length; i++){
removeClass(DOMobject[i], classes);
}
}
function hasClass_ONEtest(DOMElement, classe) {
let allClasses = DOMElement.className.split(/\s+/);
for(let i = 0; i < allClasses.length; i++){
if(allClasses[i].trim() === classe){
return true;
}
}
return false;
}
function hasClass(DOMElement, classes) {
if (typeof classes === 'string') {
return hasClass_ONEtest(DOMElement, classes);
} else { // multiple classes as array
for (let i = 0; i < classes.length; i++) {
if (!hasClass_ONEtest(DOMElement, classes[i])) {
return false;
}
}
return true;
}
}
this way you can write a quick script that parse all the file, and take out the definitions of the functions, if you can't do it manually. You can use regex, to speed up that. you need two patterns. the first for function name( and the second for name = function(. Hope that was helpful!
the question was more about if there is a way included with nodejs. There is none at the moment. it may be in the future. You may also see this How do I include a JavaScript file in another JavaScript file?. It may not help though.
When one requires a module on nodejs, the content of module.exports is returned. So, one can return a function (as you do on your example) or an object, as in
in module.js:
module.exports={
func:function(){ return true; },
val:10,
...
}
So that, in the requiring file, you can:
var m=require('module');
assert(m.func()===true);
assert(10===m.val);
This is explained in the nodejs documentation under Modules
So if you have an external JS library that exposes three functions: a, b, and c, you might wrap them as:
module.exports={
exportedA:lib.a,
exportedB:lib.b,
exportedC:lib.c
};
lib.a=function(){ ... };
lib.b=function(){ ... };
lib.c=function(){ ... };

Migrating from localStorage to chrome.storage

I'm in the process of migrating my Chrome extension's persistency repository, from localStorage to chrome.storage. An important difference between them is that chrome.storage is asynchronous, therefore a callback function needs to be passed.
How would you modify a loop that writes to localStorage, synchronously, to the async chrome.storage?
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
Thanks.
For this example, I'll use chrome.storage.local, but you can replace it with chrome.storage.sync if you prefer.
The goal is to use chrome.storage.local.set. The first step is to convert your obj into an object that contains the list of pair of key / value:
var keyValue = {};
for (var i = 0; i < obj.length; i++)
{
keyValue[obj[i].key] = obj[i].val;
}
Then we call the actual method:
chrome.storage.local.set(keyValue, function()
{
// Notify that we saved.
doThisWhenAllElementsAreSaved();
});
or, simply:
chrome.storage.local(keyValue, doThisWhenAllElementsAreSaved);
Note that the callback will be called on success, as well as on failure. If the storage failed, then chrome.runtime.lastError will be set.
You can save multiple items at once with the chrome.storage API, so I would use the following approach:
Before, using localStorage.setItem
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
After, using chrome.storage.local.set
var items = {};
for (var i = 0; i < obj.length; i++) {
items[obj[i].key] = obj[i].val;
}
chrome.storage.local.set(items, function() {
doThisWhenAllElementsAreSaved();
});
If you need to know whether the save operation succeeded, check the value of chrome.runtime.lastError in the callback.
The best practice would be just to change the code to use chrome.storage.local like others said.
But sometimes, it gets really messy and you may want some code to remain untouched.
I tried to use a library that highly incorporates window.localStorage in a packaged app environment(the library was angular-cached-resource) and didn't want to change the original source code. So I managed to make the following shim: https://github.com/stewartpark/chrome-localStorage-shim
Hope it helps! Happy hacking.

Chrome extension only opens last assigned url

I have a chrome extension browser action that I want to have list a series of links, and open any selected link in the current tab. So far what I have is this, using jquery:
var url = urlForThisLink;
var li = $('<li/>');
var ahref = $('' + title + '');
ahref.click(function(){
chrome.tabs.getSelected(null, function (tab) {
chrome.tabs.update(tab.id, {url: url});
});
});
li.append(ahref);
It partially works. It does navigate the current tab, but will only navigate to whichever link was last created in this manner. How can I do this for an iterated series of links?
#jmort253's answer is actually a good illustration of what is probably your error. Despite being declared inside the for loop, url has function scope since it is declared with var. So your click handler closure is binding to a variable scoped outside the for loop, and every instance of the closure uses the same value, ie. the last one.
Once Chrome supports the let keyword you will be able to use it instead of var and it will work fine since url will be scoped to the body of the for loop. In the meantime you'll have to create a new scope by creating your closure in a function:
function makeClickHandler(url) {
return function() { ... };
}
Inside the for loop say:
for (var i = 0; i < urls.length; i++) {
var url = urls[i];
...
ahref.click(makeClickHandler(url));
...
}
In your code example, it looks like you only have a single link. Instead, let's assume you have an actual collection of links. In that case, you can use a for loop to iterate through them:
// collection of urls
var urls = ["http://example.com", "http://domain.org"];
// loop through the collection, for each url, build a separate link.
for(var i = 0; i < urls.length; i++) {
// this is the link for iteration i
var url = urls[i];
var li = $('<li/>');
var ahref = $('' + title + '');
ahref.click( (function(pUrl) {
return function() {
chrome.tabs.getSelected(null, function (tab) {
chrome.tabs.update(tab.id, {url: pUrl});
});
}
})(url));
li.append(ahref);
}
I totally forgot about scope when writing the original answer, so I updated it to use a closure based on Matthew Gertner's answer. Basically, in the click event handler, I'm now passing in the url variable into an anonymous 1 argument function which returns another function. The returned function uses the argument passed into the anonymous function, so its state is unaffected by the fact that the next iterations of the for loop will change the value of url.

Resources