miscoded

the web is a hack

Advanced User JavaScript: event handling

Why User JavaScript events?

Any script can ask Opera to be notified when a specific event occurs, for instance a mouse click on a button. By using the special User JavaScript events your user script can ask Opera to be notified in advance of the page script. The user script can then either cancel the event, change the event or allow the page script to receive it unchanged.

Opera also can notify the user script about special events that normal scripts can not detect, such as when an external JavaScript library will be loaded.


How to use User JavaScript event handling

A user JavaScript can add event listeners to the window.opera object with the command window.opera.addEventListener(). The following new events are available to allow the user script to enhance and control the environment:


BeforeExternalScript
This event takes place before Opera loads an external JavaScript file.

BeforeScript / AfterScript
These events happen before and after any SCRIPT tag is ran.

BeforeEvent / AfterEvent
BeforeEventListener / AfterEventListener

These are 'meta-events' you can use to either be notified in advance or after about every event taking place in the document, or every event that the scripts in the page will use.

To the 'meta-events' you can add .TYPE to detect specific events only, for instance BeforeEventListener.load . This is the preferred way of using these events because a generic handler will be called a lot - for instance every time you move the mouse.

Cancelling events with event.preventDefault() in the event handler stops the default action. If you cancel 'Before' script notification events, scripts will not be loaded or ran. If you cancel 'meta-events' the page script will not receive the real events. (If this explanation is too confusing see the examples! smile )

Writing User JavaScript event handlers

The event handler function receives an object with information about the event. The function can check various properties of this object to get information about the event. The element property tells you what element the event is happening to, and the event property is the event object that will be passed to the page's script.

Examples
1
To prevent the script on a page from running its onload handler you could add the following to a User JavaScript:
opera.addEventListener('BeforeEventListener.load', function(ev){ ev.preventDefault(); }, false);

2
Say you have a form with an onsubmit handler that prevents Opera from sending the form:
<form action=''http://example.com'' onsubmit=''return false;''>

The event.preventDefault() in the following example will cancel the return false from the page's script because the page's onsubmit event handler will not be called:
opera.addEventListener('BeforeEvent.submit', function(ev){ ev.preventDefault(); }, false);

3
The following user script will fix a buggy DHTML script on <http://www.thegreenbutton.com/downloads.aspx> by rewriting the script source code with a regular expression:

if(window.location.href.indexOf('thegreenbutton.com')>-1){

window.opera.addEventListener( 'BeforeScript', function (ev){ev.element.text = ev.element.text.replace(/!=
null/g,''); },false);

}

Opera 8 beta 3 introduces User JavaScriptif today isn't yesterday, reload now!

Comments

Marek Pawłowskilockoom Thursday, March 17, 2005 10:12:10 AM

Awesome!
But why there is no complementary AfterExternalScript event? Especially that Opera does not support onload event for <script>...

scipio Thursday, March 17, 2005 10:12:10 AM

Will have to test it to fully understand how it works, but it looks very useful. smile

jimjjewett Thursday, March 17, 2005 10:12:10 AM

Please put some of this (particularly the "fix a broken script" example) in the official documentation. Outwitting a sniffer script would also be a good example.

In that same location, it would be good to link to a very basic javascript intro that does show how to register for an event.

Hallvord R. M. Steenhallvors Thursday, March 17, 2005 10:12:10 AM

You've got AfterScript.. I'm not entirely sure what you would want to do specifically after an external file has loaded.

Marek Pawłowskilockoom Thursday, March 17, 2005 10:12:10 AM

Well. For example I wrote userJS which adds Google Suggest features to normal Google pages:
http://my.opera.com/forums/showthread.php?s=&amp;postid=870518#post867265

I create <script> element to download Google Suggest engine. After that I have to run engine via InstallAC function. As you may see I've done a nasty hack by adding <script> element right after document.body is available in DOM and registering 'load' eventlistener that triggers 'InstallAC'.

Obviously 'setTimeout' is not a good solution.

Mark 'Tarquin' Wilton-Jonestarquinwj Thursday, March 17, 2005 10:12:10 AM

Docs are being written smile

scipio Thursday, March 17, 2005 10:12:10 AM

You're using function(ev) in your examples. Where does the 'ev' come from or what does it represent exactly? Is 'ev' a name you chose yourself and would 'pi' work just as well?

Hallvord R. M. Steenhallvors Thursday, March 17, 2005 10:12:10 AM

In these examples, "ev" is the object with information about the event (mainly the "element" and "event" properties). You can name it anything you want smile

I could also have omitted the argument part and just used the global "event" property as in function(){event.preventDefault()} but this is an IEism AFAIK. I recommend you write event handlers that take one argument.

Was that making things any clearer?

Hallvord R. M. Steenhallvors Thursday, March 17, 2005 10:12:10 AM

I think Opera at the moment actually pauses the script until the newly added script has been loaded and ran, but this behaviour may change.

You might set up a while loop doing nothing, but I'm not sure if I like that idea - if for some reason the external file failed to load Opera would be stuck in that loop forever.

x.type = 'text/javascript';
x.src = '/ac.js';
document.body.insertBefore(x, document.body.firstChild);

// Empty while loop example below

while (typeof(InstallAC) == 'undefined');

var googleFrm = (document.gs ? document.gs : document.f);
InstallAC(googleFrm, googleFrm.q, googleFrm.btnG, 'search', 'en');


Perhaps experiment with adding an AfterScript event handler that calls InstallAC and removes itself?

scipio Thursday, March 17, 2005 10:12:10 AM

Yes, that makes things clearer. Thanks. I'm now looking for a way to insert all these other events into a user.js that has the structure of Rijk's suggestion... smurf

scipio Thursday, March 17, 2005 10:12:10 AM

...and I'll be back with the results if they're worth it. smile

Arve Bersvendsenvirtuelvis Thursday, March 17, 2005 10:12:10 AM

I have a working "Google Suggest" in user JS, but the Google Labs terms of service are _very_ unlclear regarding what is permitted and not.

I have written Google Labs, asking permission to release this script, but they have not responded so far.

Side-question: Does the event.element act like any other DOM node:
* Is reading properties/attributes of the script element possible, such as source, type, language, defer and anything else added to it?

Marek Pawłowskilockoom Thursday, March 17, 2005 10:12:10 AM

My 'setTimeout' loop is not the optimal solution but 'while' loop is even worse smile
What do you mean "adding an AfterScript event handler that calls InstallAC and removes itself"? I thought that AfterScript runs right after the closing tag is parsed. I don't think this will do the job for external scripts (I mean it wont wait until script is loaded) but I'll check it.

Marek Pawłowskilockoom Thursday, March 17, 2005 10:12:10 AM

I don't think it breaks licence. You use _their_ technology on _their_ site smile They can't blame you for that... wink

As for the event.element, for my experience it's a fully functional node.

Arve Bersvendsenvirtuelvis Thursday, March 17, 2005 10:12:10 AM

"Better safe than sorry". If I receive a positive answer from Google, I will release the code. Patience.

(The general advice, though, if you want to try yourself: The Greasemonkey extension for Google Suggest works, with some conceptual alterations, since Opera doesn't support XPath).

Hallvord R. M. Steenhallvors Thursday, March 17, 2005 10:12:10 AM

I meant something like

function InstallGoogleAutoComplete(){
if(typeof(InstallAC)!='undefined'){
var googleFrm = (document.gs ? document.gs : document.f);
InstallAC(googleFrm, googleFrm.q, googleFrm.btnG, 'search', 'en');
opera.removeEventListener('AfterScript', InstallGoogleAutoComplete, false); /* Event listener no longer required */
}
}

opera.addEventListener('AfterScript', InstallGoogleAutoComplete, false);

x.type = 'text/javascript';
x.src = '/ac.js';
document.body.insertBefore(x, document.body.firstChild);

Hallvord R. M. Steenhallvors Thursday, March 17, 2005 10:12:10 AM

> Does the event.element act like any other DOM node

Yes. On one point User JS has wider powers than a normal script: it can read event.element.text even if the element in question has loaded its content from another domain. Apart from that everything is the same as normal DOM scripting.

Write a comment

You must be logged in to write a comment. If you're not a registered member, please sign up.