Plugins for Skosmos 3 - NatLibFi/Skosmos GitHub Wiki

It is possible to extend and customize Skosmos 3 through plugins written in JavaScript.

Creating Plugins

A plugin consists of a folder, with a unique name. The folder name is also used as the plugin name. Within the folder, there should be a plugin.json file, which provides information about each plugin's resources. For example:

{
    "js": ["widget.js"],
    "css": ["stylesheet.css", "anotherfile.css"],
    "callback": ["myCallback"]
}

The js resources are used to define JavaScript files used in the plugin. They are loaded for every page and are located near the bottom of HTML <body> section, specified in scripts.inc.twig.

The css resources are used to define CSS stylesheets used in the plugin. They are also loaded for each page and are located within the HTML <head> section, specified in base-template.twig.

The callback resources are used to define JavaScript functions that are called for each loaded page.

Template resources are no longer supported in Skosmos 3. Any templating is to be done within the JavaScript resources.

Installing Plugins

When you install Skosmos, there should be a folder in the root of the application, called "plugins". Inside this folder you can put your plugin folders. As explained in the previous section, the folder names are used as plugin names as well.

For each request, Skosmos loads the content of each enabled plugin's plugin.json, and utilizes its resources, as explained in the previous section.

Plugins are enabled in the configuration file, config.ttl. There are four ways to enable plugins. This is explained in the next section.

Enabling Plugins

Global Plugins

The first method for enabling plugins in Skosmos is through global configuration. This is done via the skosmos:globalPlugins property in the config.ttl configuration file.

:config a skosmos:Configuration ;
  # ...
  skosmos:globalPlugins ( "plugin1" "plugin2" "plugin3" ) .

This will initialize the plugins "plugin1", "plugin2", and "plugin3" for all Skosmos.

Vocabulary-specific Plugins

The second method to enable plugins is through vocabulary-specific configuration. This is done via the skosmos:vocabularyPlugins property. This property is added to each skosmos:Vocabulary, zero or multiple times.

Changes from Skosmos 2

Vocabulary-level plugin configuration has been simplified in Skosmos 3 compared to Skosmos 2, which had many overlapping mechanisms. The skosmos:hasParamPlugin and skosmos:hasPlugin configuration settings for vocabularies have been removed in Skosmos 3. Instead, skosmos:vocabularyPlugins is the only vocabulary-level configuration property.

:ysa a skosmos:Vocabulary, void:Dataset ;
    dc:title "YSA - Yleinen suomalainen asiasanasto"@fi,
        "YSA - Allmän tesaurus på finska"@sv,
        "YSA - General Finnish thesaurus"@en ;
    # ...
    void:sparqlEndpoint <http://api.dev.finto.fi/sparql> ;
    skosmos:vocabularyPlugins ( "plugin1" "plugin2" ) ;
    skosmos:sparqlGraph <http://www.yso.fi/onto/ysa/>
.

This will initialize plugins "plugin1" and "plugin2" for the vocabulary ysa. These plugins will appear only on the pages of the vocabularies for which they were enabled via the skosmos:vocabularyPlugins property.

The skosmos:vocabularyPlugins property determines both the enabling and ordering of vocabulary-specific and global plugins. The list can contain either plugin names as plain literals or references to parameterized plugin resources (see below). The order in the list determines the execution and rendering order of the plugins.

Parameterized Plugins

It is possible to include parameterized plugin resources in the skosmos:vocabularyPlugins list. These resources must be of type skosmos:ParameterizedPlugin and contain a skosmos:usePlugin property to identify the plugin, along with skosmos:parameters containing the plugin-specific configuration.

:ysa a skosmos:Vocabulary, void:Dataset ;
    dc:title "YSA - Yleinen suomalainen asiasanasto"@fi,
        "YSA - Allmän tesaurus på finska"@sv,
        "YSA - General Finnish thesaurus"@en ;
    # ...
    void:sparqlEndpoint <http://api.dev.finto.fi/sparql> ;
    skosmos:vocabularyPlugins ( "plugin2" :messageWidget "plugin1" ) ;
    skosmos:sparqlGraph <http://www.yso.fi/onto/ysa/>
.

:messageWidget  a skosmos:ParameterizedPlugin ;
                skosmos:usePlugin "awesome-message-widget";
                skosmos:parameters [
                        a schema:PropertyValue ;
                        schema:propertyID "msg";
                        Schema:value "Message in Finnish"@fi, "Message in Swedish"@sv, "Default message without language code";
                    ] ,
                    [
                        a schema:PropertyValue ;
                        schema:propertyID "color" ;
                        schema:value "#800000" ;
                    ] ,
                    [
                        a schema:PropertyValue ;
                        schema:propertyID "imageUrl" ;
                        schema:value "http://example.com/media/unicorn.png" ;
                    ] .

This way it is possible to pass parameters to a plugin. The plugin is identified by skosmos:usePlugin determined for the :messageWidget resource. Each parameter is identified by schema:propertyID. The parameters are accessible through the global window.SKOSMOS.pluginParameters JavaScript object. It is up to the plugin to interpret these parameters. An example of this can be seen here.

Backup and Log monitoring

It is recommended to prepare a backup of your installation before installing plugins (as well as updating, patching, etc), or to use a testbed server. You may also want to enable exceptions logging, as well as log to a file, and monitor both the Skosmos log file, as well as the web application and system logs for any unexpected errors.

Finally, as some resources load JavaScript and CSS files, comparing the browser console before and after installing a plugin can also be very helpful.

Example Plugins

This section presents two simple examples of adding plugins to Skosmos.

Vanilla JavaScript plugin

This example plugin is implemented using vanilla JavaScript with no external dependencies. It displays a message below the content in the header of vocabulary home pages. Plugins can be inserted anywhere on the page but Skosmos 3 offers several designated slots for plugins:

  • #headerbar-top-slot above the header bar
  • #headerbar-bottom-slot below the header bar
  • #main-content-top-slot above main content on vocabulary home page and concept page
  • #main-content-bottom-slot below main content on vocabulary home page and concept page

First, create a folder with the name "vanilla-js-plugin" under the Skosmos/plugins folder. Also add the plugin.json file, with the following content:

{
  "js": ["widget.js"],
  "css": ["stylesheet.css"],
  "callback": ["vanillaJSCallback"]
}

Now we will go through each resource file used in this plugin. First, the JavaScript file widget.js:

const VANILLA_JS_PLUGIN = {
  message: 'Vanilla JS plugin',
  template: function () {
    return `<p id="vanilla-js-plugin-message">${ this.message }</p>`
  },
  render: function() {
    // Check if the plugin already exists
    const existingPlugin = document.getElementById('vanilla-js-plugin')
    if (existingPlugin) {
      // Remove the existing plugin
      existingPlugin.remove()
    }

    // Create a new div for the plugin and add the template to it
    const newPlugin = document.createElement('div')
    newPlugin.id = 'vanilla-js-plugin'
    newPlugin.innerHTML = this.template()

    // Add the new plugin to the DOM
    document.getElementById('headerbar-bottom-slot').appendChild(newPlugin)
  },
  remove: function() {
    // Remove the plugin if it exists
    const existingPlugin = document.getElementById('vanilla-js-plugin')
    if (existingPlugin) {
      existingPlugin.remove()
    }
  }
}

document.addEventListener('DOMContentLoaded', function() {
  // Avoid polluting global namespace, or use meaningful and less likely to collide names
  window.vanillaJSCallback = function(params) {
    console.log('This is executed for each page, once loaded')

    if (params.pageType == 'vocab-home') {
      // Render plugin on the vocabulary home page
      VANILLA_JS_PLUGIN.render()
    } else {
      // Remove plugin on any other page
      VANILLA_JS_PLUGIN.remove()
    }
  }
})

The code above contains the callback definition near the end, where we define the global vanillaJSCallback function. Note that it matches exactly the name of the callback declared in plugin.json. It will be executed for each page that is loaded. Skosmos offers the current uri, prefLabels, pageType and embedded JsonLd data as parameters for callback functions.

The code is used to render a simple template defined in the template property of the VANILLA_JS_PLUGIN namespace object. You can also include an external templating language as a dependency for more complex applications.

Finally, the stylesheet file:

/*
 * Be aware values here may impact other parts of the template. Use
 * good names, that won't affect other plugins or the rest of Skosmos.
 * We recommend using a common prefix for all ids and class names of a plugin
 */
#vanilla-js-plugin-message {
  color: blue;
}

Putting it all together, the plugin is complete. We just need to enable it. Modify the config.ttl file, making sure the globalPlugins property loads the plugin.

:config a skosmos:Configuration ;
  # ...
  skosmos:globalPlugins ( "vanilla-js-plugin" ) .

Load Skosmos, and you should now have a custom message being displayed on all vocabulary home pages, as well as the callback message in the browser console.

Vue plugin

Skosmos 3 offers native support for plugins utilizing the Vue JavaScript framework. This example is implemented using Vue 3 and it displays a message in the #headerbar-bottom-slot dedicated plugin slot on concept pages.

First, create a folder with the name "vue-plugin" under the Skosmos/plugins folder. Also add the plugin.json file, with the following content:

{
  "js": ["widget.js"],
  "css": ["stylesheet.css"],
  "callback": ["vueCallback"]
}

Now we will go through each resource file used in this plugin. First, the JavaScript file widget.js:

const VUE_PLUGIN = {
  vueApp: null,
  createVueApp: function() {
    return Vue.createApp({
      data() {
        return {
          message: 'Vue plugin'
        }
      },
      template: `<p id="vue-plugin-message">{{ message }}</p>`
    })
  },
  render: function() {
    // Check if a mount point already exists
    const mountPoint = document.getElementById('vue-plugin')
    if (mountPoint) {
      // Unmount the Vue app if it exists
      if (this.vueApp) {
        this.vueApp.unmount()
      }
      // Remove the old mount point div
      mountPoint.remove()
    }

    // Create a new div for the Vue app
    const newMountPoint = document.createElement('div')
    newMountPoint.id = 'vue-plugin'
    document.getElementById('headerbar-bottom-slot').appendChild(newMountPoint)

    // Create a new Vue app instance and mount it to the new mount point
    this.vueApp = this.createVueApp()
    this.vueApp.mount('#vue-plugin')
  },
  remove: function() {
    // Unmount and remove the vue app if it exists
    if (this.vueApp) {
      this.vueApp.unmount()
      this.vueApp = null
    }
  }
}

document.addEventListener('DOMContentLoaded', function() {
  // Avoid polluting global namespace, or use meaningful and less likely to collide names
  window.vueCallback = function(params) {
    if (params.pageType == 'concept') {
      // Render plugin on the concept page
      VUE_PLUGIN.render()
    } else {
      // Remove plugin on any other page
      VUE_PLUGIN.remove()
    }
  }
})

The code above contains the callback definition near the end, where we define the global vueCallback function. Note that it matches exactly the name of the callback declared in plugin.json. It will be executed for each page that is loaded. Skosmos offers the current uri, prefLabels, pageType and embedded JsonLd data as parameters for callback functions.

Finally, the stylesheet file:

/*
 * Be aware values here may impact other parts of the template. Use
 * good names, that won't affect other plugins or the rest of Skosmos.
 * We recommend using a common prefix for all ids and class names of a plugin
 */
#vue-plugin-message {
  color: red;
}

Putting it all together, the plugin is complete. We just need to enable it. Modify the config.ttl file, making sure the globalPlugins property loads the plugin.

:config a skosmos:Configuration ;
  # ...
  skosmos:globalPlugins ( "vue-plugin" ) .

Load Skosmos, and you should now have a custom message being displayed on all concept pages, as well as the callback message in the browser console.

Other plugins

Search for Skosmos plugins created at NatLibFi (they are all named according to the pattern Skosmos-widget-*)

⚠️ **GitHub.com Fallback** ⚠️