How to write a JavaScript to display data in a Semantic MediaWiki

From semantic-mediawiki.org

MediaWiki is a powerful tool, and with Semantic MediaWiki, you can do some quite fancy bits, but what if you have that perfect idea for how to display the data in a way that makes sense to users, but you just need to massage the information a little bit (or a lot) to make it look appealing, more interactive, or to support connection between sites. That is what this article aims to do.


[This page is currently a work in progress, please feel free to contribute]

Making your scripts accessible to a site[edit]

It is easy to embed code in MediaWiki:Common.js, although this facility is being deprecated. Because of limitations with this solution, we will skip this option completely, for our example we will use the Widgets extension. There is also the option of using the ResourceLoader.

More information on this can be found under Developing with ResourceLoader

Using Widgets to create Javascript libraries[edit]

High level flow[edit]

[Not tested yet, a starter for ten, based on the writers limited] As you may have realized by now, MediaWiki does not allow you to run JavaScript straight on a page, while all the reasons and thinking for MediaWiki's design decision is outside the scope of this article, there are good reasons, mostly about security.

SMWjava.jpg

To the right you can see (a over simplified) version showing the differences between a standard webpage and mediawiki or Semantic Mediawiki server serving up a webpage. What we will work on is the three green boxes. I.E how to store, create hook and call our javascript. We will then run a SMWAPI call which, when working, should display a red box just over this text explaining about the security issue. Please note, for security, only administrators are allowed to add custom javascript code to wikipedia. As such, to use this article to write a small javascript you will need administrator rights to the media wiki.[will need to be discussed with SMW admins weather or not they would allow us to add a custom javascript function for this article]

Storage[edit]

The first thing we need to do is create somewhere to store the javascript. One option would be to store it in MediaWiki:Common.js which would make it available to all pages within the mediawiki. However, to keep things clean, we only want our code to be available on the pages which will use it, so we need some kind of container.

The way we will do this is by creating a widget using the Mediawiki:Widgets extension. The Widgets extension allows the creation of raw HTML pages that can be embedded (similarly to templates) in normal wiki pages. It does mean that we will need to work with the code as a html page with javascript and css embedded rather than a pure javascript and css files, but it still meets our requirements.

Now its time to see if we can get it to write something. Go to the mediawiki, and the page .../index.php/Widget:Test (You should be told that the page does not exists, so click edit).

In the editor window write the following code:

<script type="text/javascript">
   document.write('I wrote something!!!');
</script>


Once you save and exit, you should now see the actual code on the Widgets:Test

Creating our hook[edit]

So now we have the code in our widget, the next step is simple. Create a new wikipage, and enter the following code. {{#widget:Test}} Once you save and edit, you should see a page with the text "I Wrote Something!!!". If this is the case, success, we have stored some code in a safe manner.

However, this isn't enough is it. For this to be useful we need to send it some information while we are at it as well, and read some standard variables. We will be sending two variable to our widget, warning and red. Due to how Widgets manages variables, we want to set them as part of the call rather than just sending the data.

What we mean by that is, often when you call a object or function, the function will be responsible to interpret and manage the data sent with the call, where as with Widgets, any data will be interpreted by default as a request to set a variable with that name to true. Nice for web coding but not how we want to use it.

For the hook we will add a text and a color variable. {{#widget:Test|text=warning|colour=red}}

We will also need to add some new commands to our script

 <noinclude>__NOTOC__
This is just a little widget to prove how we code, this text will be visible when you "look" at the widget
</noinclude>
<includeonly>
<script type="text/javascript">
 var text = '<!--{$text}-->'; 
var colour = '<!--{$colour}-->'; 
document.write('So I need to send the message' + text); 
document.write('And I will use the colour' + colour); 
document.write('I wrote something!!!'); 
</script>
</includeonly>

If your screenshot of your test wiki page looks like the picture to the right, you are doing it right

Hook-test-screenshot.png

Talking to the Smarty(pants) Template Engine[edit]

Widgets uses the smarty Template Engine (the pants addition is just the writers sense of humour). To understand its role we will quickly cover what happens when we put code in a widget. [This is based on the authors understanding, please confirm correctness]

In short,the code in the widget will then be lifted and rendered into a php file (have a look in compiled_templates) prior to being stored as a precompiled template in the database.

The Smarty manage things like translation of variables. The four codes we use above are:

<noinclude< Basically, this block of text will be used if anyone navigates to the widget, good idea is to pur some kind of instructions for the user.

__NOTOC__[Author thinks this is to avoid having a table of content rendered]

<includeonly>This is where we put the code that will be inserted when the wikipage calls {{#Widget:[name]~~

<!--{$text}-->This is all Smarty, and to resolve the problem of server memory space not available for client side code. What will happen is that during compilation Smarty will replace this section by whatever is in $text at the time. While there is a rather extensive manual at Smarty.net, I have not been able to find a good manual for how to use it with MediaWiki and Widgets.

At this point, we have completed the three first three green boxes.

We have created a piece of javascript in a safe storage.
Created a hook (OK, Widgets did that all to its self, but we can take the credit)
We created a data structure so we can send instructions to our code
We have printed updated the page that made the call

Widget:Showwarning, a useful example[edit]

We will be doing one last thing before we move on to the API calls. We will update our code to do the following

take one more variable, which we will call divid, this will tell us where to put our box.
The code will attempt to create a box and write the text stored as Warning in JSON.
It will use the pre defined variable wgSiteName to display the name of the wiki. For further reading on preset variables Mediawiki:Javascript
It will then use javascript to set background colour and apply our class.
It will use css to create a class called prettybox

To create the Widget Showwarning (just go to Widget:Showwarning) and enter the following code:

<noinclude>__NOTOC__
Showwarning is a simple javascript to display a warning message.

The syntax is &#123;&#123; Widgets:Showwarning | messageid | divid | [ colour <i>optional</i> ]&#125;&#125;
</noinclude>
<includeonly>
<style type="text/css" media="all">
.prettybox {
	-moz-border-radius: 8px;
	-webkit-border-radius: 8px;
	border-radius: 8px;
	/*IE 7 AND 8 DO NOT SUPPORT BORDER RADIUS*/
	-moz-box-shadow: 0px 0px 20px #000000;
	-webkit-box-shadow: 0px 0px 20px #000000;
	box-shadow: 0px 0px 20px #000000;
	/*IE 7 AND o8 DO NOT SUPPORT BLUR PROPERTY OF SHADOWS*/
	text-align: center;
	margin: 10px;
	padding: 15px;
}
</style>
<script type="text/javascript">
var colour ='<!--{$colour|escape:'quotes'|default:red}-->'; //If there is no colour well set it to red
var message = '<!--{$message|escape:'quotes'}-->'; //We should have a message
var divid = '<!--{$divid|escape:'quotes'}-->';  //Remove quotes
//Thats all the user input

var texts = '{ "messages" : [{ "Warning":"This is a warning"}]}';
var obj = JSON.parse(texts); //Yes, I know, this is a ridiculously problematic way to so something really simple, but we want to change the above to call SMWAPI next iteration. 
document.getElementById(divid).innerHTML = '<H3>' + message + ' from ' + wgSiteName + '<br>' + obj.messages[0].Warning ; 
/*
wgSiteName is the preset variable we spoke of, and yes, it is a ridicolous way of addressing a JSON object, but this is about how to do MediaWiki javasctipt, not how to use JSON. Now lets do some CSS to our div
*/
document.getElementById(divid).style.backgroundColor = colour;
//And because we are (kind of) web developing, we'll apply the pretty box css to our box
document.getElementById(divid).className = 'prettybox' ;


</script>
</includeonly>
Warning for Shwowarning.png

To call our new code, we need to change the wiki somewhat, as we will need a div id which matches the id we send to our Widget. The code we will use is: <div id="boxme"></div>

{{#widget:Showwarning|message=Warning|divid=boxme}}

If done right, and I did not forget any steps, you should see something along the lines of the screenshots to my right, and isn't that box all pretty.


Create some data to retrieve[edit]

Usermessages-secialbrows.png

So, we can now display a textbox using our wanting, but we want to move one step further, we want to use SMW data to decide what we put in our box. For this we will create a Usermessages page as a standard MediaWiki (with Semantic extension) page, so go to /index.php/Usermessages edit and type in.

[[Message::Warning::"This is a warning, and it is real like real warning about something you need to be warned about"]]
[[Message::Hello::"This is just a friendly hello, because we are a very friendly bunch"]]


If we now go to SMW special page "Special:Browse/Usermessages" you should be able to see our two messages as per the screenshot.



We can now also ask the API for this information. wiki/api.php?action=browsebysubject&subject=Usermessages&format=jsonfm. Now we get the following back.

{
    "query": {
        "subject": "Usermessages#0#",
        "data": [
            {
                "property": "Hello",
                "dataitem": [
                    {
                        "type": 9,
                        "item": "\"This_is_just_a_hello,_because_we_are_a_very_friendly_bunch\"#0#"
                    }
                ]
            },
            {
                "property": "Message",
                "dataitem": [
                    {
                        "type": 9,
                        "item": "\"This_is_a_warning,_and_it_is_real_like_real_warning_about_something_you_need_to_be_warned_about\"#0#"
                    },
                    {
                        "type": 9,
                        "item": "\"This_is_just_a_hello,_because_we_are_a_very_friendly_bunch\"#0#"
                    }
                ]
            },
            {
                "property": "Warning",
                "dataitem": [
                    {
                        "type": 9,
                        "item": "\"This_is_a_warning,_and_it_is_real_like_real_warning_about_something_you_need_to_be_warned_about\"#0#"
                    }
                ]
            },
            {
                "property": "_MDAT",
                "dataitem": [
                    {
                        "type": 6,
                        "item": "1/2015/3/30/8/25/0"
                    }
                ]
            },
            {
                "property": "_SKEY",
                "dataitem": [
                    {
                        "type": 2,
                        "item": "Usermessages"
                    }
                ]
            }
        ],
        "serializer": "SMW\\Serializers\\SemanticDataSerializer",
        "version": 0.1
    }
}

In the business, this is what we would call our "Bobs your uncle" moment. We got data in JSON format, and it is almost like javascript was written to use data in JSON format, so we just need to make a call to that address, then get a function to get the message we want. There are nicer ways, and there are further examples of other API's below, but for us, this is all we need for our little app.


Showmessage to use JSON return[edit]

So as a last step. We now want to get our javascript to use the JSON data to identify the warning message we have seen.
To do this we need to complete the following steps.

    Get Jquery to do the JSON call for us (already built into mediawiki)
    step through the JSON data until we find a message matching our criteria (we have both a Hello and a Warning now)
    Move our HTMLinner commands into the return function for the JSON call (otherwise we'll end up writing the message before we have retrieved it)
    Massage the data a bit to drop the underscores in favour of space


<noinclude>__NOTOC__
Showwarning is a simple javascript to display a warning message.<br>
The syntax is &#123;&#123; Widgets:Showwarning | messageid | divid | [ colour <i>optional</i> ]&#125;&#125;
</noinclude>
<includeonly>
<style type="text/css" media="all">
.prettybox {
	-moz-border-radius: 8px;
	-webkit-border-radius: 8px;
	border-radius: 8px;
	/*IE 7 AND 8 DO NOT SUPPORT BORDER RADIUS*/
	-moz-box-shadow: 0px 0px 20px #000000;
	-webkit-box-shadow: 0px 0px 20px #000000;
	box-shadow: 0px 0px 20px #000000;
	/*IE 7 AND o8 DO NOT SUPPORT BLUR PROPERTY OF SHADOWS*/
	text-align: center;
	margin: 10px;
	padding: 15px;
}
</style>
<script type="text/javascript">


var colour ='<!--{$colour|escape:'quotes'|default:red}-->'; //If there is no colour well set it to red
var message = '<!--{$message|escape:'quotes'}-->'; //We should have a message
var divid = '<!--{$divid|escape:'quotes'}-->';  //Remove quotes
//Thats all the user input

$.getJSON("http://v-ghost.port0.org:8081/dbfswiki/api.php?action=browsebysubject&subject=Usermessages&format=json", function(data) {
      $(data.query.data).each(function(key, data){
            if(( data.property == message )){
               var text=data.dataitem[0].item.split('_').join(' '); // get rid of _
                document.getElementById(divid).innerHTML = '<H3>' + message + ' from ' + wgSiteName + '<br>' + text ; 
              document.getElementById(divid).style.backgroundColor = colour;
                //And because we are (kind of) web developing, we'll apply the pretty box css to our box
                document.getElementById(divid).className = 'prettybox' ;
            }
      });
});

 //Yes, I know, this is a ridiculously problematic way to so something really simple, but we want to change the above to call SMWAPI next iteration. 

/*
wgSiteName is the preset variable we spoke of, and yes, it is a ridicolous way of addressing a JSON object, but this is about how to do MediaWiki javasctipt, not how to use JSON. Now lets do some CSS to our div
*/
               //document.getElementById(divid).innerHTML = '<H3>' + message + ' from ' + wgSiteName + '<br>' + text ; 
  


</script>
</includeonly>



And now we have succeeded, we can run our javascript, for any client who opens our page, and provide our script with information from the SMWapi as promised.


Tips, Tricks and Painkillers[edit]

How to clear precompiled templates from database/cache[edit]

[Author have no clue, but needs to be answered. For now only way is to create a template widget with a new name (say test2) to force the widget to update]

There is a potential work around for this issue, update the call, ie, change the colour to blue in the Widget:Test call. Due to how the caching and Smarty works with hardcoded variables, any change to the options to send will force Widget to be recreated.

< is not the same as <

Basically, mediawiki by default have methods for avoiding people to put scripts and unknown/unsafe hooks on pages, as such, the way it "disarms" code is changing control characters to ASCII codes, looks the same but with a different meaning. The way to write a HTML ASCII code is &#ASCIICODE or &#CODE, so basically, when you put in <script> it is translated to ASCII codes as &lt;script&gt;, making certain your browser will not identify the HTML tag.

Turn of that fancy editor

If you have a rich text editor enabled, it might be worth turning it off in user preferences-> Editing for the user you use for development, as some of those editors tries to "clean" up the text, including "disarming" code.

Write access to compiled_templates with SELinux

Widgets gets really upset if they can't be written to the compiled_templates directory. If you use CentOS or other SELinux enabled Linux distro, the answer is to run chcon -R -t httpd_sys_rw_content_t compiled_templates. In needs root access to probably one for your sys admin if its not clear what this means.


Options to access via API[edit]

There where a lot of people who contributed to this article, and especially providing links for additional readings on API. We picked the easiest option for what we where doing, but if you need further functionality from the API, or just want to look at other options, these where some of the ones that where suggested.

MediaWiki's ResourceLoader[edit]

Resource registration via MW's ResourceLoader can happen within the PHP source using OutputPage::addResources or internally via JavaScript itself mw.loader.using( 'ext.mymodule', function() { ... });.

JavaScript modules[edit]

Testability and readability should be considered when writing a JavaScript module and to encourage such virtues using namespaced modules (opposed to littering the global $ namespace) is one way to improve maintainability. For an example have a look at ext.srf.boilerplate.namespace.js.

Using SMWAPI[edit]

Register the smw.api dependency with the ResourceLoader.

'ext.my.module' => $moduleTemplate + array(
   'scripts' => ( ... ), 
   'styles' => array( ... ),
   'dependencies' => 'ext.smw.api'
)
var smwApi = new smw.api();

// Parse JSON data into a smw-datamodel object (such as smw.dataItem.wikiPage etc.)
var container = context.find( 'myContainerId' )
var data = smwApi.parse( container );

// Access data via 
data.query.ask.parameters,
data.query.result.results

// Transform raw data into a query string
// @param {array} printouts - example: ['?Capital of', '?Population']
// @param {object} parameters - example: {'limit': 10'}
// @param {array|string} conditions - example: ['[[Category:Cities]]']
var query = new smw.query( printouts, parameters, conditions ).toString();

// Create a link to the Special:Ask query
var link = new smw.query( printouts, parameters, conditions ).getLink();

// Fetch data via Ajax/SMWAPI
smwApi.fetch( query )
.done( function ( result ) {
// Do something
.fail( function ( error ) {
// Do something
} );

Examples for the use of the smw.api/smw.query can be found in ext.srf.formats.eventcalendar.js and ext.srf.formats.datatables.js

Using MWAPI (with JQuery)[edit]

  var query = 'http://mywiki/mw/api.php?action=ask&query=' + myEncodedQuery + '&format=json';
  $.getJSON(query, function(data) {
    // do amazing stuff with your data.
  });

Later versions of Javascript (possibly using polyfills for compatibility) obviate the need for JQuery, but may as well use it since it's built in.


Contributing to this article[edit]

Edit the page.