Manual:SpecialPage AJAX API

From mediawiki.org

This article illustrates how to use AJAX on a Special Page in the MediaWiki framework. Creation of such a page involves several different API’s including API Extension, HTMLForm, ResourceLoader, in addition to the normal steps involved in a Special Page extension.

Configure Basic Extension pieces[edit]

The developing extensions manual gives a step-by-step guide to this setup. In this example, the extension is named “HelloWorldAJAX,” stored in a directory named “HelloWorldAJAX,” uses core extension setup file “HelloWorldAJAX.php,” and uses the newer style JSON I18N configuration in directory i18n (not using the deprecated single message file setup). Then register the extension in LocalSettings.php with:

require_once( "$IP/extensions/HelloWorldAJAX/HelloWorldAJAX.php" );

At this stage, HelloWorldAJAX.php contains:

<?php
if( !defined( 'MEDIAWIKI' ) ){
	die( "This is not a valid entry point.\n" );
}

$dir = dirname( __FILE__ ) . "/";

$wgExtensionCredits['specialpage'][] = array(
		'path' => __FILE__,
		'name' => "helloworldAJAX",
		'description' => "Sample for using AJAX on a SpecialPage.",
		'descriptionmsg' => "helloworldAJAX-desc",
		'version' => 1.0,
		'author' => "contributor",
		'url' => "none yet",
		'license-name' => "",
);

$wgMessagesDirs['WikimediaMessages'] = __DIR__ . '/i18n';

API Extension[edit]

The next step is to configure our message generator API extension. In the HelloWorldAJAX directory, create a new file HelloWorldAPI.php containing a class named HelloWorldAPI which extends APIBase.

For this example, we will provide the minimum functionality necessary by implementing the execute method.

	public function execute() {
		$this->getResult()->addValue(null, $this->getModuleName(), "Hello World");
	}

We next need to register the extension in the extension setup page, HelloWorldAJAX.php:

$wgAutoloadClasses['HelloWorldAPI']	= __DIR__  . '/HelloWorldAPI.php';
$wgAPIModules['hworld'] = 'HelloWorldAPI';

At this point, we can test our API Extension using the API page: http://server/w/api.php?action=hworld (server could be localhost or whatever development server you are using and the api path can be customized). The result in XML format should look like:

<?xml version="1.0"?>
<api hworld="Hello World" />

Special Page[edit]

The next step is to setup the framework for the special page which will be calling this API.

In extension directory HelloWorldAJAX, create a new file “SpecialHelloWorldAJAX.php” which contains a class SpecialHelloWorldAJAX extending SpecialPage. The constructor must be implemented and the execute method will hold the logic creating the HTMLForm.

	function execute( $par ) {
		$request = $this->getRequest();
		$output = $this->getOutput();
		$this->setHeaders();
	
		$form  = array (
			'hwldTextBox' => array(
				'label-message' => 'hworldAJAXform-textlabel',
				'type' => 'text',
				'id' => 'hwldTextBox',
			),
			
		);
				
		$htmlForm = new HTMLForm( $form, $this->getContext(), 'hworldAJAXform' );
		$htmlForm->setId("hworldAJAXform");
		$htmlForm->setSubmitID("hwldButton");
		$htmlForm->setSubmitTextMsg( 'hworldAJAXform-submit' );
		
		
		$htmlForm->show();
	}

Of special note, is the ID attribute on the button (hwldButton) and text field(hwldTextBox). These will be essential in the JavaScript code to find the elements we wish to customize.

The basic elements of registering a special page needs to added to our extension setup file, HelloWorldAJAX.php.

$wgAutoloadClasses['SpecialHelloWorldAJAX']	= __DIR__  . '/SpecialHelloWorldAJAX.php';
$wgSpecialPages[ 'HelloWorldAJAX' ] = 'SpecialHelloWorldAJAX';

There is also some internationalization settings in the JSON bundle. In the en.json file, strings should be added for form elements:

    "hworldAJAXform-submit":"Say Hello",
    "hworldAJAXform-textlabel":"The Message"

At this point, the page should be available in the special pages list as well as directly at the URLhttp://server/w/index.php/Special:HelloWorldAJAX.

JavaScript[edit]

In extension directory HelloWorldAJAX, create a new directory “js”. In that directory, create file ext.HelloWorldAJAX.core.js. Before we get involved with writing JavaScript, register the file with the ResourceLoader in our extension setup page, HelloWorldAJAX.php.

$wgResourceModules['ext.HelloWorldAJAX'] = array(
	'scripts' => array( 'js/ext.HelloWorldAJAX.core.js'),
	'localBasePath' => __DIR__ ,
	'remoteExtPath' => 'HelloWorldAJAX',
);

In this javascript file, the first order of business is to convert the submit button to not actually submit, but instead to invoke another method. In the real world, you would probably want a submit button in addition to another button that invokes an AJAX method. Many pages may not use a button to trigger the AJAX call, but a different event, like a drop down change event or similar. In this example, we first disable form submission and then set an event handler. This uses the JQuery syntax supported by recent versions of Mediawiki. Switching to raw javascript would involve the addEventHandler API.

$( "#hworldAJAXform" ).on( "submit", false );
$("#hwldButton").on("click", sayMessage);

This implementation of the click event handler function starts with a JQuery style interface to event handling and then uses the ResourceLoader api call to the server to invoke the API. This is an asynchronous call. The done method executes when the AJAX call returns, so there could be some delay due to network latency or slow server response, but the browser will return control to the user. Indeed, several calls could be invoked in parallel and return out of order. The data.hworld value extracts the hworld argument which was set by the API class using $this->getModuleName(), in turn referring back to the main extension file, which registered the API using$wgAPIModules['hworld']. A real world example could use much more complicated pathing and, thus, require more sophisticated JSON extraction logic.

function sayMessage(evt) {
	
	mw.loader.using( 'mediawiki.api', function () {
		( new mw.Api() ).get( {
			action: 'hworld',
		} ).done( function ( data ) {
			var hwldTextBox = document.getElementById("hwldTextBox" );
			hwldTextBox.value = data.hworld;
		} );
	} );

	evt.stopPropagation();
}

Now the Special Page file needs to reference this javascript resource it relies on. Following the setHeaders line in the execute method, add a reference to the module defined for the ResourceLoader.

	$output->addModules('ext.HelloWorldAJAX');

At this point, the special page should be fully functional and display the "Hello World" string in the text box when the button is clicked.

Full HelloWorldAJAX.php
<?php
if( !defined( 'MEDIAWIKI' ) ){
	die( "This is not a valid entry point.\n" );
}

$dir = dirname( __FILE__ ) . "/";

$wgExtensionCredits['specialpage'][] = array(
		'path' => __FILE__,
		'name' => "HelloWorldAJAX",
		'description' => "Sample for using AJAX on a SpecialPage.",
		'descriptionmsg' => "helloworldAJAX-desc",
		'version' => 1.0,
		'author' => "contributor",
		'url' => "none yet",
		'license-name' => "",
);

$wgMessagesDirs['WikimediaMessages'] = __DIR__ . '/i18n';

$wgAutoloadClasses['HelloWorldAPI']	= __DIR__  . '/HelloWorldAPI.php';
$wgAPIModules['hworld'] = 'HelloWorldAPI';

$wgAutoloadClasses['SpecialHelloWorldAJAX']	= __DIR__  . '/SpecialHelloWorldAJAX.php';
$wgSpecialPages[ 'HelloWorldAJAX' ] = 'SpecialHelloWorldAJAX';

$wgResourceModules['ext.HelloWorldAJAX'] = array(
	'scripts' => 'js/ext.HelloWorldAJAX.core.js',
	'localBasePath' => __DIR__ ,
	'remoteExtPath' => 'HelloWorldAJAX',
);
Full i18n/en.json
{
    "@metadata": {
        "authors": [""]
    },
    
    
    "helloworldajax": "Hello World AJAX",
    "helloworldajax-desc": "A sample of using AJAX on a SpecialPage.",
    "helloworldajax-summary": "AJAX sample of SpecialPage",
    
    "hworldAJAXform":"form title",
    "hworldAJAXform-submit":"Say Hello",
    "hworldAJAXform-textlabel":"The Message"
    
}
Full HelloWorldAPI.php
<?php
class HelloWorldAPI extends ApiBase {
	public function execute() {
		$this->getResult()->addValue(null, $this->getModuleName(), "Hello World");
		#wfDebug(print_r($this->getResult(),true));
	}
}
Full SpecialHelloWorldAJAX.php
<?php
class SpecialHelloWorldAJAX extends SpecialPage {
	function __construct() {
		parent::__construct( 'HelloWorldAJAX' );
	}
	
	function execute( $par ) {
		$request = $this->getRequest();
		$output = $this->getOutput();
		$this->setHeaders();
	
		$output->addModules('ext.HelloWorldAJAX');
		$form  = array (
			'hwldTextBox' => array(
				'label-message' => 'hworldAJAXform-textlabel',
				'type' => 'text',
				'id' => 'hwldTextBox',
			),
			
		);
				
		$htmlForm = new HTMLForm( $form, $this->getContext(), 'hworldAJAXform' );
		$htmlForm->setId("hworldAJAXform");
		$htmlForm->setSubmitID("hwldButton");
		$htmlForm->setSubmitTextMsg( 'hworldAJAXform-submit' );
		
		
		$htmlForm->show();
	}
}
Full js/ext.HelloWorldAJAX.core.js
function sayMessage(evt) {
	
	mw.loader.using( 'mediawiki.api', function () {
		( new mw.Api() ).get( {
			action: 'hworld',
		} ).done( function ( data ) {
			var hwldTextBox = document.getElementById("hwldTextBox" );
			hwldTextBox.value = data.hworld;
		} );
	} );

	evt.stopPropagation();
}



$( "#hworldAJAXform" ).on( "submit", false );

$("#hwldButton").on("click", sayMessage);