« Documentation Home

Hands-on tutorial: building an Opera extension

This documentation relates to Opera's now deprecated .oex Extension API framework for Opera versions <= 12.15 and also provided by our OEX2NEX shim library.

For the latest Opera Extensions API documentation for Opera versions > 12.15 please consult the latest Opera Extensions API documentation online.

This tutorial assumes you have a basic knowledge of CSS and JavaScript.

Contents

Introduction

Ah, Slashdot. The grandaddy of wisdom-of-the-crowd news sites and still strong enough to bring web servers to their knees. As a result of its facelift a few years ago, it gained blocks in the sidebar that could be moved or closed, however this functionality is only available to registered members. In this tutorial, we'll see how we can make an Opera extension that will give users the ability to hide and show individual blocks, logged in or not.

Structure of an Opera extension

Firstly, let's look at the structure of Opera extensions. We'll begin with some ground rules:

The directory structure of an Opera extension

Figure 1: The directory structure of an Opera extension.

As you'd expect, Opera extensions can vary from basic to complex, both in structure and what they do. Figure 1 shows the directory structure of a relatively complex example extension, whereas the simplest type would be a simple JavaScript file applied to a web page. Let's try this simple approach with the Slashdot front page with the aim of hiding all content blocks in the sidebar.

Super simple config.xml

Step one, we need a config.xml file containing things like the name, description and author of the extension. This uses the configuration file for W3C widgets. Think of it as a passport—without this, you ain't goin' nowhere:

config.xml

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets">
  <name>Slashdotty</name>
  <description>Hide Slashdot side blocks you don't need.</description>
  <author href="http://people.opera.com/danield/">Daniel Davis (@ourmaninjapan)</author>
</widget>

Adding a little JavaScript

Create an includes folder, then create an empty JavaScript file. In it, we are first going to specify that this script applies only to pages in the slashdot.org domain:


// @include http://slashdot.org/*
// @include http://*.slashdot.org/*

Next, in the same JavaScript document, we add code to execute when the page has loaded:


window.addEventListener('DOMContentLoaded', function() {
    // Functionality will go here
}, false);

So far so good. Now to go through all the blocks on the page. Unfortunately some blocks don't have IDs but all the block titles do, so we'll use those. Thanks to the Selectors API, this is easy with the following line:


var block_titles = document.querySelectorAll('.block .title');

The result is an array of all the elements in the page with a class name of title within elements with a class name of block. Thankfully, Slashdot uses a consistent naming pattern, blockname-title and blockname-content, so if we remove the -title suffix from each title, we're left with the block name. To get the ID of the block's content, we just add the -content suffix. We need a loop to do that, like so:


var block_name, block_content; // Declare variables outside the loop for efficiency.
for (var i = 0, block_title; block_title = block_titles[i]; i++) {
    block_name = block_title.getAttribute('id').replace('-title', '');
    block_content = document.getElementById(block_name + '-content');
}

The final part of this stage is to set the display property to none for each block's content. Combining it with the previous steps, we get:

includes/slashdotty.js

// == Slashdotty ==
// Hide Slashdot side blocks you don't need.
// @include http://slashdot.org/*
// @include http://*.slashdot.org/*

window.addEventListener('DOMContentLoaded', function() {
    var block_titles = document.querySelectorAll('.block .title');
    var block_name, block_content; // Declare variables outside the loop for efficiency.
    for (var i = 0, block_title; block_title = block_titles[i]; i++) {
        // Remove the "-title" suffix to get the block name
        block_name = block_title.getAttribute('id').replace('-title', '');
        block_content = document.getElementById(block_name + '-content');
        // Check for the block's existence to avoid errors
        if (block_content) {
            block_content.style.display = 'none';
        }
    }
}, false);

Download the extension so far. (Rename it with a .zip extension to inspect its contents.)

Making our extension useful

Looking good! All that's remaining is to give the user control over which blocks are shown and hidden. To do this, we remove the line setting the block content's display to none and add two features. One is some clickable text in each block and the other is a function to change the value of the block content's display property to none or block. So, first things first, let's get the function ready:


// Function to show and hide a block's content
function addToggle(block_content, block_toggle) {
    var block_style = block_content.style;
    block_toggle.addEventListener('click', function() {
        block_style.display = (block_style.display !== 'none') ? 'none' : 'block';
    }, false);
}

It's worth mentioning that we have two arguments — a block's content element and toggle element—but in our example these are not essential. This is because the addToggle function is enclosed in a higher-level, anonymous function that executes when the page loads, however using arguments makes the function more portable for use in other projects.

As you can see, we set the display value with a nifty one line if statement. We could equally have used the following code which does the same thing but is more lengthy:


if (block_style.display !== 'none') {
    block_style.display = 'none';
} else {
    block_style.display = 'block';
}

The final piece of our extension jigsaw is something for the user to click, i.e. the block_toggle element that we pass to the addToggle function above. For simplicity, we'll just create a span element with static text and, in our loop, add it to each block's title, like so:


if (block_content) {
    // Add a show/hide text link
    block_toggle = document.createElement('span');
    block_toggle.textContent = 'Hide/Show';
    block_title.appendChild(block_toggle);
    addToggle(block_content, block_toggle);
}

Putting it all together, our complete slashdotty.js should look like this:

includes/slashdotty.js

// == Slashdotty ==
// Hide Slashdot side blocks you don't need.
// @include http://slashdot.org/*
// @include http://*.slashdot.org/*

window.addEventListener('DOMContentLoaded', function() {
    // Function to show and hide a block's content
    function addToggle(block_content, block_toggle) {
        var block_style = block_content.style;
        block_toggle.addEventListener('click', function() {
            block_style.display = (block_style.display !== 'none') ? 'none' : 'block';
        }, false);
    }

    var block_titles = document.querySelectorAll('.block .title');
    var block_name, block_content, block_toggle; // Declare variables outside the loop for efficiency.
    for (var i = 0, block_title; block_title = block_titles[i]; i++) {
        // Remove the "-title" suffix to get the block name
        block_name = block_title.getAttribute('id').replace('-title', '');
        block_content = document.getElementById(block_name + '-content');
        if (block_content) {
            // Add a show/hide text link
            block_toggle = document.createElement('span');
            block_toggle.textContent = 'Hide/Show';
            block_title.appendChild(block_toggle);
            addToggle(block_content, block_toggle);
        }
    }
}, false);

Download the completed extension, which also includes an extension icon. (Rename it with a .zip extension to inspect its contents.)

Conclusion

Hopefully this has helped you on your way to making Opera extensions. We hope you'll enjoy to taking this code and creating your own, or improving on what's here, for example by locally storing each block's state or by dynamically changing the toggle text based on each block's state. Extra points if you can animate the block with CSS3! Don't forget to share your completed work so users can benefit from it and other developers can learn from it.