wikishia:User scripts/Guide

From wikishia

Template:Wikipedia:User scripts/Navbox

This is a small guide on writing user scripts for Wikimedia sites. Of course, some basic JavaScript knowledge is required. (See also JavaScript syntax.)

Getting from main list

Starting up

Go to our scripts and select one that you want. Either click on the superscript link (if there is one) and follow the instructions in the footnote, or follow the link with the script's name to read the documentation and find out how to install it. Normally you need to paste some code into your common.js page. After you do that, save your common.js and follow the instructions at the top of your new common.js (the part about bypassing your browser's cache). You should now be able to use the script.

Adding more

To add another module, follow the same instructions. (If it requires a function that you already have, you do not need to add it to your monobook.js again.) Paste the code either above or below the previous code so that the new code does not interfere with it.

Writing your own

Prerequisites

To write user scripts, you will need to have some knowledge of JavaScript. Try these links if you do not already know it:

Also, it would definitely help if you tried using one of our scripts and got it working. The rest of this tutorial assumes you know where the various things are (all explained in the "Starting up" section).

Your first script

We will be writing an independent module by modifying your js, so you may want to get our module template. For the purpose of this tutorial, we will write a simple version of the Quick wikify module, so change MODULE_NAME in the module template to "Qwikify". Your template should now look like this:

// Qwikify
$(document).ready( function () {
    MODULE_CODE;
} );

In MODULE_CODE, we want to add the "Wikify" tab, so we will use the addPortletLink function (which requires "mediawiki.util" module). Replace MODULE_CODE with a call to this function. Then we'll bind an event handler so that when this link is clicked, we will call another function named doQwikify() that will actually execute the code. The name is what is shown on the tab, so set that to "Wikify". Most tabs have an id of "ca-(their name)", so set the id to "ca-wikify". The title (also known as mouseover or rollover text) should be something like "Mark for wikification". Lastly, we use jQuery's .click() to listen for clicks on this link, and when that happens execute a function. After we call doQwikify(), it says event.preventDefault(). Since we clicked on a link, we need to tell the browser to prevent it's default behavior (which is going to a the url, '#'). We want the page to stay right where it's at, so to prevent the browser from following the link, we prevent that and do our own custom action.

Altogether, your new function should look like this:

// Make sure the utilities module is loaded (will only load if not already)
mw.loader.using( 'mediawiki.util', function () {

    // Wait for the page to be parsed
    $(document).ready( function () { 

   //see below "Portlets" subsection
        var link = mw.util.addPortletLink( 'p-cactions', '#', 'Wikify', 'ca-wikify', 'Mark for wikification'); 
        $( link ).click( function ( event ) {
            event.preventDefault();
            doQwikify();

        } );
    } );
} );

Now, we must write our actual doQwikify() function. It will edit the edit box, so we need to get the name of that and its form. Viewing the source of the page shows that the form is named editform and the textbox is named wpTextbox1. So, the text inside the textbox is document.editform.wpTextbox1.value. To add {{wikify}} (and two new lines), we simply do:

document.editform.wpTextbox1.value = "{" + "{wikify}}\n\n" + document.editform.wpTextbox1.value;

(We separate the two "{" brackets in the front of the wikify template so it doesn't get expanded when we write this code on the wiki.)

Finally, we want to submit the form for the user. Luckily, JavaScript has a built-in function just for this named submit(). To submit our editing form, use the code document.editform.submit(). Your code should now look something like this:

function doQwikify() {
    document.editform.wpTextbox1.value = "{" + "{wikify}}\n\n" + document.editform.wpTextbox1.value;
    document.editform.submit();
}

And that's it! Save the common.js and then do a hard refresh. Go and edit a page (I use the Sandbox) and see what happens![1]

On load/document ready function

The personal "user" module (build from /common.js, /common.css and optionally the skin-specific files for the current skin) and gadgets are loaded on all pages. Most scripts will want to manipulate elements on the page; to do so the page needs to be ready (which may not be the case at the time the modules are loaded). We can defer execution of code by using a special function. Most commonly .ready() from jQuery.

// Define our main function
function myScript() {
  // ... code ...
};

// Schedule it to run after the HTML page is parsed
$( document ).ready( myScript );

// This shorthand is also valid
jQuery( myScript );

Since the function is called only once, many users prefer to shorten this code with an anonymous function:

$( document ).ready( function () {
  //... code ...
} );

// Or
jQuery( function () {
  //... code ...
} );

Note: $ and jQuery are the same object; choosing between them is purely a matter of opinion.

Many scripts use this function simply to add some script interface, such as a link in a portlet. Then the main part of the code is executed after the user clicks on that link.

Built-in scripts

All Wikipedia pages include some built-in MediaWiki JavaScript code, with variables and functions that can be used in user scripts. The specific code depends on the viewed page and current users, for more details see Wikipedia:Catalogue of CSS classes#Stylesheets and JavaScript.

Of most interest are:

Writing and testing

The following methods are can be used to test your script.

Basic

  • Using the preview button: You can edit your script directly on your /common.js page, then click [Show preview] and the new code is executed right away on the preview page. Note that if you are trying to add a user script to your own installation of MediaWiki software, you will need to open LocalSettings.php and set $wgAllowUserJs: in order to configure your installation to allow user scripts.
$wgAllowUserJs = true;
  • Saving it: If required elements are missing on the preview page (for example, your script does something on history pages), you will have to save the script in order to test it. However, it's not convenient and creates unnecessary entries in the page history.
  • Execute it in your browser's JavaScript console: All modern browsers come with a JavaScript console and other development tools. You can type or paste and execute your code there. You need to find out how to open the console in your browser. In Google Chrome and Internet Explorer, you can do that by pressing F12, in FireFox - Ctrl-Shift-K, in Safari it's Ctrl-Alt-I, and in Opera - Ctrl-Shift-I. You may need to click the Console tab if a different pane is currently open. To execute a multi-line code in Opera, you need to press Ctrl+Enter. For Firefox, you may also want to install the Firebug add-on.

Using a separate local HTML file

  • Save the Wikipedia page to your local hard drive, including all the corresponding .css and .js files. The specific details depend on your browser.
  • Open saved page in your editor, insert your script code either between <script></script> tags or as a separate local file with <script src="file://C://you_local_path/name.js"></script>.
  • Open the saved page in your browser and preview the result.

This is a very traffic-wise way to quickly develop a user script. However, it has the following disadvantages:

  • the browser will not let you test Ajax queries from a local file
  • you have to save different pages depending on exactly which page (history, etc.) is needed for testing
  • you have to periodically re-save all .js files to synchronize with MediaWiki changes
  • if you want to "casually" test the script while you're browsing Wikipedia, you still have to use other testing methods

Loading it from a localhost web server

The best and most recommended way to load a JavaScript file during development is from your local web server (see below for an easy way to install a web server). Put this string in your /common.js:

mw.loader.load( 'https://localhost/wikipediatest.js' );

Then run any web server on your computer and create the wikipediatest.js file in the appropriate folder. The code inside this file will be executed as if it was inside your personal script.

You can edit your wikipediatest.js file with any text editor, perhaps with syntax highlighting and other convenient features, save the file and simply reload any Wikipedia page to see the results. (You don't need to wait, and if your web server is nice or you set it right, you don't even need to bypass your browser cache.)

On Windows, you could use for example TinyWeb which is less than 100 kbyte on disk and doesn't require installation. Save and unzip tinyweb.zip for example into c:\Program Files\Tinyweb, create a shortcut to tiny.exe, and add an argument in shortcut properties — path to your folder with wikipediatest.js and any file index.html (required). Start TinyWeb with this shortcut; unload it with Task Manager.

Note that this method doesn't work in Opera 9.50 (and later) due to added security restrictions, see Opera 9.50 for Windows changelog: "Local servers can use remote resources, but not vice versa". In Chrome also need enable SSL certificate, else a script will not work with the error in console log.

Browser-specific

Some browsers allow you to automatically execute your JavaScript code on specific web pages. This way you don't even have to be logged in.

The most famous solution is Greasemonkey for Firefox browsers. Some alternatives for other browsers are listed at Greasemonkey#Greasemonkey compatibility and equivalents for other browsers.

However, making user scripts work with one of these extensions might require some modifications to the script code.

Some notes for Opera:

  • Placing your script in a corresponding folder as <name>.js file should work for many user scripts.
  • Older versions of Opera (probably below 9.50) did not support UTF-8 local scripts, meaning you could only use Latin characters.

Running pieces of code

You can run pieces of code on already loaded pages, for example directly in the browser address field: javascript: var s = document.title; alert(s); void 0

Or you can use the bookmarklet «JavaScript Shell». It opens a new browser window where you can paste or type your code and run it in the context of the current page.

However a full-blown JavaScript debugger is much more convenient.

Publishing

Once you have finished the user script code, you either need to paste it into your personal script if it is only for personal use. Or, if it is for use by others, you should upload it to for instance User:Yourname/yourscript.js. Then other users can import it by putting the following line in their /common.js.

importScript( 'User:Yourname/yourscript.js' ); //linkback [[User:Yourname/yourscript.js]]

Note: The "linkback" comment is enables you to see how many pages it's been added to via the "what links here" option.

Text editors and debugging

Any text editor will do. If you plan to use non-ASCII characters in string, your text editor should support UTF-8.

Notepad++ is recommended for Windows users, since it can:

  • Highlight JavaScript code
  • Quickly insert standard JavaScript keywords and methods with Ctrl+↵ Enter
  • Show the list of all functions and quickly jump to any function
  • Code folding

Mac OS X users should look into one of these free editors:

Or one of these paid editors:

Most Linux distributions come with a text editor capable of being used for development such as gedit or Kate.

For debugging in Firefox you can use Tools ? JavaScript Console which shows all JavaScript and CSS errors. Firebug is strongly recommended for convenient debugging.

For debugging in Chrome and Chromium, you can use Tools ? Developer Tools.

For debugging in Safari, open up Safari ? Preferences ? Advanced and enable the "Show Develop menu in menu bar" option. Afterwards, you can use Develop ? Show Web Inspector to open up the development tools.

For debugging in Opera, you can use Tools ? Advanced ? Error Console which shows all JavaScript and CSS errors. Dragonfly is strongly recommended for convenient debugging.

For debugging in Internet Explorer, see JScript Debugger in Internet Explorer 8

For debugging in Internet Explorer before version 8, see IEBlog: Scripting Debugging in Internet Explorer


Basic methods

Finding elements

Every HTML element is a node in a DOM model which allows scripts to access the element, for example, on the following HTML page.

<form name="frmname" id="frmid">
	<textarea name="txtname" id="txtid"></textarea>
	<input id="neighbor" />
</form>

We can find element textarea:

  • Using its id: $( '#txtid' )
  • In the array of all elements with the same tag: $( 'textarea' )
  • Using an element next to it: $( '#neighbor').prev()
  • As a child of its parent: $( '#frmid' ).children( 'form' )
  • As a form element, using name: $( '#frmid [name="txtname"]')

This example on jsFiddle

The jQuery API reference is an excellent source for documentation.

Checking the page

Many scripts are supposed to work only on some pages. You can check:

  • The page address
if ( mw.config.get( 'wgAction' ) === 'history' ) {
  //Continue only on history pages.
  • wg variables; many of them have the same meaning as Magic words
if ( mw.config.get( 'wgCanonicalNamespace' ) === 'User_talk') {
  //Continue only on User_talk pages.
  • Presence of elements (only in second and third parts of the script)
function func_start() {
   if ( $( '#editForm' ).length == 0  ) return; //No edit form  ? exit
   // …

Portlets

Usual places to add your own links — portlet blocks with these id's:

p-logo p-personal name My talk My preferences

p-search

 
p-cactions Article Discussion Read Edit History

p-navigation

 Main page …

p-interaction
  …


p-tb

Upload file


p-lang
(interwikis)

Portlet structure:

<div id="p-myname" class="portlet">
 <h5>Header</h5>
 <div class="body">
  <ul>
  <li id="…"> <a …>  //Links
  <li id="…"> <a …>
  … …

There is a special function in mediawiki.util.js that simplifies the process of adding your own links into portlets:
mw.util.addPortletLink (portlet, href, text, id, tooltip, accesskey, nextnode)

// Several examples of portlet links

// Adds a link to your js file to the toolbox
mw.util.addPortletLink ( 'p-tb', mw.util.getUrl( 'Special:MyPage/common.js' ),
	'My JS', 'pt-myvector', 'Visit your js file');

// Add a link to the edit page for your Notes in your personal links
// Note: We assume that short/pretty URLs are in use with ?action, ideally you
// would check for that.
mw.util.addPortletLink ( 'p-personal', mw.util.getUrl( 'Special:MyPage/Notes' ) + '?action=edit',
	'My notes', 'pt-mynotes', 'Edit your personal notes' );

// Adds a link to prefix index for the current page to the toolbox
mw.util.addPortletLink ( 'p-tb',
	mw.util.getUrl( 'Special:Prefixindex/' + mw.config.get( 'wgPageName' ) ),
	'Prefixindex', 'tb-prefixindex');
	
// Adds a link to 
mw.util.addPortletLink ( 'p-personal',
	mw.util.getUrl( 'Special:Log/' + mw.config.get( 'wgUserName' ) ),
	'My logs', 'pt-mylogs');

Removing elements

To move an element, simply attach it in another place with .append() or .prepend().

To hide an element, you can simply use .hide().

// Example: remove special characters toolbar from edit page
$( '#editpage-specialchars' ).hide();

// Or modify the CSS directly
$( '#editpage-specialchars' ).css( 'display', 'none' );

This is easier with your CSS though:

#editpage-copywarn {
	display:none;
}

Text manipulation

The most important element on the edit page is a <textarea> with the article text inside. You can reference it with

var $txt = $( '#wpTextbox1' );

You can add new text to the beginning: $txt.prepend( 'new phrase' ) or to the end: $txt.append( 'new phrase' ).

There is a function in mediawiki.action.edit.js that can add text to the cursor position:

// This surrounds the cursor with '<pre>' and '</pre>' and pre-fills the selection with 'Test!' highlighted.
mw.toolbar.insertTags( '<pre>', '</pre>', 'Test!' );

Toolbar

WikiEditor is now the default toolbar when editing the source code of articles, but some users are still using the original toolbar. You can turn on and off WikiEditor by checking and unchecking the "Enable enhanced editing toolbar" check box in your preferences.

See mw:Extension:WikiEditor/Toolbar customization for information on how to customize WikiEditor.

See also User:V111P/js/addToolbarButtons for a script which allows you to easily add buttons to whichever of the two toolbars the user is using.

The old toolbar

The buttons above the text area when using the classic toolbar are located inside <div id='toolbar'>.

Buttons are defined with mwEditButtons[] and mwCustomEditButtons[] arrays. Then the second part of your script is called by addOnloadHook. Only after that the buttons are created by the initialization of the mediawiki.action.edit module.

So the easiest way to modify buttons is to work with these arrays:

//Example: modify signature button.
if (mwEditButtons.length >= 10 && mwEditButtons[9].tagOpen == '--~~~~')
  mwEditButtons[9].tagOpen = ' — ~~~~';

Also see User:MarkS/Extra edit buttons.

Edittools

There is another edit panel under textarea. Usually it's generated from MediaWiki:Edittools by Extension:CharInsert and consists of a lot of JavaScript links to the insertTags(). In the English Wikipedia, this approach was replaced by MediaWiki:Edittools.js.

//Example: adding your own quick insert to Edittools
var specialchars = document.getElementById ('editpage-specialchars');
specialchars.innerHTML +=
"<a onclick=\"insertTags('<div>','</div>','');return false\"
href='#'>&lt;div&gt;</a>";

There is no crucial difference between toolbar and edittools, you can insert your own custom links into both.

Working with CSS

Some user scripts also use some CSS code, or even are built with CSS only. Then you need to code and test CSS code. That can be done in your /common.css, but it is slow and messy.

Instead, you can load a CSS file from your local web server (see the previous section for an easy-to-install web server). Put this line at the very top of your /common.css:

@import "http://localhost/wikipediatest.css";

Note! Such @import statements must come before any other declarations in your CSS. But there can be /* comments */ above them.

An alternative way is to put this line anywhere in your css instead:

mw.loader.load( 'http://localhost/wikipediatest.css', 'text/css' );

Publishing a CSS file

Once you have finished the CSS code, you either need to paste it into your /vector.css if it is only for personal use. Or if it is for use by others then you should upload it to for instance User:Yourname/yourscript.css. Then other users can import it by putting the following line in their /common.js file. Note, that is in their ".js", not their ".css".

importStylesheet( 'User:Yourname/yourscript.css' );

If the CSS should be used together with a user script written in JavaScript then you can make it easy for the users. Simply put the line above in the JavaScript code for your user script, then the users only need to "install" your JavaScript.

For completeness, in case someone wonders, users can import your User:Yourname/yourscript.css from their /common.css too. This of course has the advantage that it works even if the user has JavaScript disabled. Although it takes this slightly complex line of code:

@import "/w/index.php?title=User:Yourname/yourscript.css&action=raw&ctype=text/css";

Ajax

AJAX (asynchronous JavaScript and XML) is a popular name for a web programming technique that queries the server or fetches content without reloading the entire page.

While programming AJAX can be complex, libraries of functions can make it much easier. Since the 1.16 release, MediaWiki comes with the jQuery library, which provides a convenient framework for easily making Ajax requests.

Common problems

  • AJAX programmers commonly run into problems if they don't account for AJAX's asynchronicity. If you try to pop up a box with another page's content, you will almost certainly pop up a box containing null. This occurs because the script continued even though the query wasn't finished.

    To correct the problem, you need to use callback functions. Place the next portion of code after a query into a function, and call the function when the query completes. jQuery makes this very easy to do.

  • AJAX scripts cannot reach a page on a different server (for example, google.ca or en.wikisource.org from en.wikipedia.org). Trying to do so will cause the script to halt with or without error. This can be circumvented using a proxy on the current server, but none is available for Wikimedia user scripts.

Basic examples

MediaWiki provides some modules with helper functions which facilitate the use of its API. The main modules available are

If your script makes use any method or code provided by these modules, remember to indicate the dependencies with mw.loader.using or, in case of gadgets, on its definition at MediaWiki:Gadgets-definition.

Fetch page content

Fetching a page content can be done using GET.

$.ajax({
	url: mw.util.getUrl( 'Wikipedia:Sandbox' )
})
.done(function( data ) {
	alert( 'The remote page contains:\n' + data );
})
.fail(function() {
	alert( 'The ajax request failed.' );
});

Get the wikitext of a page

Using module mediawiki.api

Note: make sure to add "mediawiki.api" to your dependencies!

function doSomethingWithText( wikitext ) {
	/* .. */
	alert( 'The wikitext of the page is:\n\n' + wikitext );
}
function doSomethingInCaseOfError () {
	/* .. */
	console.log( 'err' );
}
(new mw.Api()).get( {
	prop: 'revisions',
	rvprop: 'content',
	rvlimit: 1,
	indexpageids: true,
	titles: 'Wikipedia:Sandbox'
} )
.done( function ( data ) {
	var q = data.query,
		id = q && q.pageids && q.pageids[0],
		pg = id && q.pages && q.pages[ id ],
		rv = pg && pg.revisions;
	if ( rv && rv[0] && rv[0]['*'] ) {
		doSomethingWithText( rv[0]['*'] );
	}
} )
.fail( doSomethingInCaseOfError );
Using plain jQuery
$.getJSON(
	mw.util.wikiScript('api'),
	{
		format: 'json',
		action: 'query',
		prop: 'revisions',
		rvprop: 'content',
		rvlimit: 1,
		titles: 'Wikipedia:Sandbox'
	}
)
	.done(function ( data ) {
		var page, wikitext;
		try {
			for ( page in data.query.pages ) {
				wikitext = data.query.pages[page].revisions[0]['*'];
				doSomethingWithText( wikitext );
			}
		} catch ( e ) {
			doSomethingInCaseOfError();
		}
	})
	.fail( doSomethingInCaseOfError );

Edit a page and other common actions

Scripts can perform common actions (like editing, protection, blocking, deletion, etc.) through the API. These actions require an edit token, which is valid for any action during the same session. (However, you should get a new token for different tasks in case this changes in the future.)

The code below shows how to edit a page, but it can easily be adapted to other actions by reading the API documentation.

// Edit page (must be done through POST)
// the line "text: info.text," will cause the call 
// to replace entire page content with supplied data.
// alternatively, one can append or prepend the data to the page, by using
// "appendtext: info.text," or "prependtext: info.text," instead.
// when using "appendtext", it is possible to append the text to a specific section,
// by setting the optional field "section".
function editPage( info ) {
	$.ajax({
		url: mw.util.wikiScript( 'api' ),
		type: 'POST',
		dataType: 'json',
		data: {
			format: 'json',
			action: 'edit',
			title: info.title,
			text: info.text, // will replace entire page content
			summary: info.summary,
			token: mw.user.tokens.get( 'editToken' )
		}
	})
	.done (function( data ) {
		if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
			alert( 'Page edited!' );
		} else {
			alert( 'The edit query returned an error. =(' );
		}
	})
	.fail ( function() {
		alert( 'The ajax request failed.' );
	});
}
editPage({
	title: 'User:' + mw.config.get( 'wgUserName' ) + '/Sandbox',
	text: 'Cool! It works! :-) ~~' + '~~',
	summary: 'Trying to edit my sandbox [[Project:User scripts/Guide/Ajax|using AJAX]]...'
});

See also

References

  1. This section originally written by raylu my monobook.js Thanks a ton to all the users who helped improve this tutorial!
  2. https://lists.wikimedia.org/pipermail/mediawiki-l/2010-June/034396.html