Standard Function Library

in Script development
Subscribe to Standard Function Library 26 posts, 10 voices



Peter Bunyan Scriptwright

I think we need one of these. It would be very useful.
What would you put in it? Write your own functions, or post other people's here (with credit to them, of course).
Here goes:

$x - by Johan Sundström

function $x(xpath, root) { // From Johan Sundström
  var doc = root ? root.evaluate ? root : root.ownerDocument : document, next;
  var got = doc.evaluate(xpath, root||doc, null, null, null), result = [];
  while(next = got.iterateNext())
    result.push(next);
  return result;
}

remove - by Peter Bunyan

function remove(element) {
  if (element) 
    element.parentNode.removeChild(element);
}

 
Jasper de Vries Scriptwright

xpathExec

function xpathExec(xpath, func) {
	var result = document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (var i = 0; i < result.snapshotLength; i++) {
		if (result.snapshotLength == 1) return func(result.snapshotItem(i), arguments[2], arguments[3]);
		else func(result.snapshotItem(i), arguments[2], arguments[3]);
	}
}
// example
xpathExec("//td[@class='ebcPpl']", replaceInItem, "PayPal", "PP");

 
Arphen Lin Scriptwright

Is it possible to include a js-library file in my script? For example, prototype.js or others.js written by myself.

 
Arvid Scriptwright

Quick answer Aprhen: No, not yet. It is being discussed and code is being written. You can read more about it on the greasemonkey mailinglist.

 
Brad Stewart User

If your intent is to write functions to be used as standard libraries, you guys might want to comment your functions a little better. A description of what the function does and what variables it expects would be really helpful. For example, Peter, your $x function seems pretty incomprehensible (at leas with my limited JS expertise). A more descriptive name would probably be in order too. I don't mean to be critical, I just think that would make it a lot easier for others to make use of your code.

 
Henrik N Admin

Much of the appeal of the $x function is its short name ;) Also, it's pretty self-documenting if you've done JS XPath scripting before, though obviously less so if you haven't. It returns an array of elements matching an XPath expression and takes an optional root element for that expression.

 
Arphen Lin Scriptwright

It's not a good idea to include a js file from local disk for security reasons. How about include js from remote site?

 
Arvid Scriptwright

How is a remote source more trusted than a local?

 
Henrik N Admin

Including stuff from remote sites is a privacy issue – the logs for that server will track your browsing history (well, the pages where those scripts are applied).

I'd just cut-and-paste for now.

 
Arvid Scriptwright

Instead of using Jasper's xpathExec, i use Johan's $x and forEach, I find it simple and clean.

$x("//a[@target='_blank']").forEach(function(v, i, a) {
     v.removeAttribute('target');
});

forEach, and some other useful array methods seems to be relatively unknown, so I'm including a tutorial that explains them all.

 
Lior Zur Scriptwright

Arvid, thanks for the tutorial, it's great. Always love learning something new.

 
Arvid Scriptwright

Here is something I use for creating elements in place of the a bit too verbose for my taste document.createElement and el.setAttribute.

createEl({n: nodename, a: {attr1: val, attr2: val}, c: [child1, child2], evl: {type: eventlistener_type, f: eventlistener_function, bubble: bool}}, appendTo)

function createEl(elObj, parent) {
	var el;
	if (typeof elObj == 'string') {
		el = document.createTextNode(elObj);
	}
	else {
		el = document.createElement(elObj.n);
		if (elObj.a) {
			attributes = elObj.a;
			for (var key in attributes) {
				if (key.charAt(0) == '@')
					el.setAttribute(key.substring(1), attributes[key]);
				else 
					el[key] = attributes[key];
			}
		}
		if (elObj.evl) {
			el.addEventListener(elObj.evl.type, elObj.evl.f, elObj.evl.bubble);
		}
		if (elObj.c) {
			elObj.c.forEach(function (v, i, a) { createEl(v, el); });
		}
	}
	if (parent)
		parent.appendChild(el);
	return el;
}

//example usage, not tested :)
createEl({n: 'ol', a: {'@class': 'some_list', '@id': 'my_list'}, c: [
    {n: 'li', a: {textContent: 'first point'}, evl: {type: 'click', f: function() {alert('first point');}, bubble: true}},
    {n: 'li', a: {textContent: 'second point'}},
    {n: 'li', a: {textContent: 'third point'}}
]}, document.body);

//instead of writing:
var ol, li;
ol = document.createElement('ol');
ol.setAttribute('class', 'some_list');
ol.setAttribute('id', 'my_list');
li = document.createElement('li');
li.textContent = "first point";
li.addEventListener('click', function() { alert('click'); }, true);
ol.appendChild(li);
li = document.createElement('li');
li.textContent = "second point";
ol.appendChild(li);
li = document.createElement('li');
li.textContent = "third point";
ol.appendChild(li);

The function might be too heavyweigth for some scripts, and the syntax might be a bit too unreadable. But I've taken a fondness of it.

Here's another piece of code I use for de/serializing objects and saving them with GM_get/setValue.

serialize/deserialize

function deserialize(name, def) {
	return eval(GM_getValue(name, (def ? def : '({})')) );
}

function serialize(name, val) {
	GM_setValue(name, uneval(val));
}

//example
var settings = {a: 1, b: 2, c: 3};
serialize('test', settings);
var _settings = deserialize('test');
// now "settings == _settings" should be true

For selecting one element with XPath:

$xs

function $xs(xpath, root) {
	var doc = root ? root.evaluate ? root : root.ownerDocument : document, next;
	return doc.evaluate(xpath, root||doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

(yay, no double linebreaks in pre-tags anymore!)

 
Lior Zur Scriptwright

Elegant code! A true pleasure to behold! My mastery of Javascript is much lesser. Arvid, do you know any other good tutorials? I mean about using objects the way your functions do?

 
Henrik N Admin

I copied some of these into http://wiki.greasespot.net/Code_Snippets and tidied that page up some. Forums are great for discussing this stuff, but the wiki is probably a better reference.

 
Lior Zur Scriptwright

Can someone make this post a sticky, please?

 
Henrik N Admin

Lior: I'm not sure about making this thread sticky. I think the wiki is better as a reference. This thread is useful for discussions (e.g. pros and cons of different implementations of $x()), but I'm not sure that warrants stickiness.

 
Lior Zur Scriptwright

Henrik: I see what you mean. I simply noticed that I was coming back to this thread a lot (even bookmarked it yesterday), and that it contained info that was not in the Wiki (as far as I know). It's your call.

 
Henrik N Admin

Lior: Update the wiki ;)

 
alien_scum Scriptwright

In the $x function I like to add the line
xpath=xpath.replace(/([^.])\.(\w+)/g,'$1[@class="$2"]').replace(/#(\w+)/g,'[@id="$1"]').replace(/\/[/g,'/*[');
This way you can use css syntax for the class and id selectors instead of the cumbersome [@class="myclass"]
eg
$x('//.ad').forEach(remove)

 
Lior Zur Scriptwright

Henrik: Got it. :-) BTW, my programming improved tremendously from careful reading of these snippets.
alien_scum: That's a cool idea! Also congrats on the continually evolving MonkeyBarrel, just got me the recent one.

 
alien_scum Scriptwright

Here is my full (and almost insane) $x, it remove the necessity of using forEach on the methods of the returned nodes

function $x(xpath) {
xpath=xpath.replace(/((^|\|)\s*)([^/|]+)/g,'$2//$3').replace(/([^.])\.(\w*)/g,'$1[@class="$2"]').replace(/#(\w*)/g,'[@id="$1"]').replace(/\/\[/g,'/*[');
  var got=document.evaluate(xpath,document,null,null,null), result=[];
  if (next=got.iterateNext()) { 
      for(var i in next.wrappedJSObject)
        if(typeof result[i] =='undefined') result[i]=Function('a=arguments;this.forEach(function(n) {try {if(n.'+i+') n.'+i+'(a[0],a[1],a[2],a[3],a[4])}catch(e){}})');
    result.push(next);
    while(next=got.iterateNext()) result.push(next);
  }
  return result;
}

it allows for things like
$x('.ad|.advert').forEach(remove) //remove every node with a class ad or advert
or (and this is the clever bit)
$x('//a[contains(@target,"blank")]').removeAttribute('target'); //stop links from opening in a new window

although it doesn't work for
$x('img').addEventListener('mouseover',showlarge,false);

as it says that addEventListener is undefined some proding shows that it shows up in:
for (i in $x('img')[0].wrappedJSObject)
but not in the $x function in
for (i in next.wrappedJSObject)

does anyone have anyidea why this would be? It is driving me insane

 
Junk Blocker Scriptwright

How about the autoupdate code? Here's a pretty minimal version assembled from various sources on here:

// Script Header
//
// @version   1.2
...
...
  // code
function autoUpdateFromUserscriptsDotOrg(SCRIPT) {
  try {
    if (!GM_getValue) return; // Older version of Greasemonkey. Can't run.

    // avoid a flood of dialogs e.g. when opening a browser with multiple tabs set to homepage
    // and a script with * includes or opening a tabgrop
    var DoS_PREVENTION_TIME = 2 * 60 * 1000;
    var isSomeoneChecking = GM_getValue('CHECKING', null);
    var now = new Date().getTime();
    GM_setValue('CHECKING', now.toString());
    if (isSomeoneChecking && (now - isSomeoneChecking) < DoS_PREVENTION_TIME) return;

    // check daily
    var ONE_DAY = 24 * 60 * 60 * 1000;
    var lastChecked = GM_getValue('LAST_CHECKED', null);
    if (lastChecked && (now - lastChecked) < ONE_DAY) return;

    GM_xmlhttpRequest({
      method: 'GET',
      url: SCRIPT.url + '?source', // don't increase the 'installed' count just for update checks
      onload: function(result) {
        if (!result.responseText.match(/@version\s+([\d.]+)/)) return;     // did not find a suitable version header

        var theOtherVersion = parseFloat(RegExp.$1);
        if (theOtherVersion <= parseFloat(SCRIPT.version)) return;      // no updates or older version on userscripts.orge site

        if (window.confirm('A new version ' + theOtherVersion + ' of greasemonkey script "' + SCRIPT.name + '" is available.\nYour installed version is ' + SCRIPT.version + ' .\n\nUpdate now?\n')) {
          GM_openInTab(SCRIPT.url);   // better than location.replace as doing so might lose unsaved data
        }
      }
    });
    GM_setValue('LAST_CHECKED', now.toString());
  } catch (ex) {
  }
}

// usage example
autoUpdateFromUserscriptsDotOrg({
  name: 'RSS+Atom Feed Subscribe Button Generator',
  url: 'http://userscripts.org/scripts/source/688.user.js',
  version: "1.2",
});

 
alien_scum Scriptwright

EDIT: Junk Blocker your totally right, thanks for pointing that out to me.

Using your example will artificially increase the install count every time it checks for updates. A less antisocial way would be to check http://userscripts.org/scripts/source/688 for updates instead

 
Junk Blocker Scriptwright

No, it won't. See this post by Henrik Nyh

      url: SCRIPT.url + '?source', // don't increase the 'installed' count just for update checks

i.e. appending '?source' to the source url is an alternate way of avoiding increasing the count. :)

 
Henrik N Admin

I just made a makeMenuToggle function and posted it to the wiki. Quite useful!

It generalizes the case where you add a menu item to toggle some persisted variable.