Scalar 2 User's Guide

Extending the core: changing Scalar using ‘hooks’

Sometimes Scalar's various customization options just won't cut it, and the need to change Scalar itself is needed.  We highly recommend against changing any of Scalar's system files, including CSS and JS files, as these will be overwritten the next time Scalar is updated (this would be the same recommendation for any software). Fortunately, since Scalar is built on top of CodeIgniter (CI), you can take advantage of CI hooks to make various adjustments to the core. Possible changes include adding a CSS or JS file to the HTML header, blocking built-in files from loading, and adding custom Layouts. We suggest using the following steps, as opposed to the CI hooks documentation, as they have been fine-tuned to Scalar specifically.

Step 1: Enable hooks

Change false to true in /system/application/config/config.php.

$config['enable_hooks'] = TRUE;

Step 2: Edit hooks.php

Add the following to /system/application/config/hooks.php.

$hook['post_controller_constructor'][] = array(
  'class' => '',
  'function' => 'my_hook',
  'filename' => 'custom.php',
  'filepath' => 'hooks/my_hook',
  'params' => array()
);

According to the CI hooks documentation, there are a few different strings that can be used in place of "post_controller_constructor".  However, due to how Scalar's Controllers are architected, with _remap(), "post_controller_constructor" is the best solution.

Step 3: Add hook files

For the purposes of this demo, let's assume you wish to add both a CSS file and a JS file. Either way you'll need to create a new directory named after the "filepath" you define above ("hooks/my_hook"). Then, make sure to add a custom.php file inside, as that was also defined above.

/system/application/hooks/my_hook   // The new directory as defined in Step 2
/system/application/hooks/my_hook/custom.php   // Required as defined in Step 2
/system/application/hooks/my_hook/custom.js   // Optional JS file, can be named anything
/system/application/hooks/my_hook/custom.css   // Optional CSS file, can be named anything


Keep in mind that Scalar already includes jQuery, so you can use that right out of the box in custom.js without needing to add anything else.

Step 4: Edit custom.php

Open custom.php and add a function that matches what was set in the "function" field in Step 2 (in this case, "my_hook"). Then, put the CI instance into a variable ("$ci"). From here you can do anything you can typically do in PHP, though for this step let's stick to adding the CSS and JS files. A more thorough example is included further down.

<?php 
function my_hook() {
 
  $ci =& get_instance();
 
  $ci->template->add_js('system/application/hooks/my_hook/custom.js');
  $ci->template->add_css('system/application/hooks/my_hook/custom.css');
 
}
?>

Note that adding the CSS file in this way will place the CSS file at the top of the stack of CSS files in Scalar's front-end. This means that Scalar's default stylesheets will take precedence! If this poses a problem, go ahead and remove the add_css(...) line above, adding the stylesheet, instead, in custom.js.

Step 5: Edit custom.js

Now that the JS file is loaded, you can hand off most of the work to Javascript or jQuery. For example, you can load in custom.css (if you decided to skip it in Step 4), set a custom JS file for CKEditor (Scalar's WYSIWYG editor), and load in more JS files. Keep in mind Scalar's pageLoadComplete event, under which you should include any DOM manipulation.

$(document).ready(function() {
 
  var approot = $('link#approot').attr('href');
 
  // Add stylesheet to bottom of stack (if skipped adding in Step 2)
  $('head').append('<link type="text/css" rel="stylesheet" href="'+approot+'hooks/my_hook/custom.css" />');
 
  // Custom CKEditor settings
  if (window.CKEDITOR) CKEDITOR.config.customConfig = approot+'hooks/my_hook/ckeditor_custom.js';
 
  // Wait until Scalar has finished its DOM changes to do ours
  $('body').on('pageLoadComplete', function() {
    $('#mainMenuInside .home_link > a').eq(0).html('Start page');
  }
 
});

More with custom.php

As mentioned, there is more that you can do inside custom.php. You can, as you would expect, add your own functions, access models and views via the CI instance, etc. Below is an example of some common tasks. Keep in mind that there might be better ways to do some tasks than use a hook. For example, both CSS and JS files can be added on a per-book bases in the Dashboard. So we recommend first becoming familiar with Scalar's Dashboard, our Advanced Topics, and the various config files kept in /system/application/config.

<?php 
function my_hook() {
 
  $ci =& get_instance();
 
  // Add a custom Layout
  $my_layout = array('name'=>'My Layout','description'=>'<b>A custom layout only available in this Scalar install.</b> Javascript included somewhere will be invoked if this layout is mentioned in the HTML header.','image'=>'hooks/my_hook/custom_layout.png');
  $ci->config->config['views']['my_layout'] = $my_layout;
  $ci->data['views']['my_layout'] = $my_layout;
 
  // Don't continue if on the edit page or annotation editor page
  $ignore_pages = array('.edit', '.annotation_editor');
  foreach ($ignore_pages as $ignore) {
    if ($ignore == substr($ci->uri->uri_string, strlen($ignore)*-1)) return;
  }
 
  // Don't continue if in the Dashboard
  if ('system' == $ci->router->fetch_class()) return;
 
  // Only continue if in a specific book
  if ('my-book' != $ci->data['book']->slug) return;
 
  // Remove Scalar's main stylesheet and add our own
  $ci->template->block('system/application/views/melons/cantaloupe/css/common.css', 'css');
  $ci->template->add_css('system/application/hooks/my_hook/custom.css');
 
}
?>

More with custom.js

Once in Javascript there are a few built-in variables that can be leveraged for minor adjustments to the front-end look-and-feel. Of course, you can adjust just about anything. You'll want to make sure to wrap any DOM manipulation in a ready event handler, and, because Scalar does a fair amount of manipulation itself, also include Scalar's pageLoadComplete event in most cases.

$(document).ready(function() {
 
  var approot = $('link#approot').attr('href');
  var parent = $('link#parent').attr('href');
  var base = $('base').attr('href');
  var slug = base.replace(parent,'');
 
  // Custom CKEditor settings
  if (window.CKEDITOR) {
    CKEDITOR.config.customConfig = approot+'hooks/my-hook/ckeditor_custom.js';
  };
 
  $('body').on('pageLoadComplete', function() {
    // Custom scripts for individual pages
    if ('my-custom-page' == slug) $.getScript(approot+'hooks/my-book/custom_script.js');
  });
 
  // Built in variables for minor adjustments to front-end
  scalarMediaHideSourceFileTab = true;
  scalarMediaDetailsHideSourceFileLink = true;
  scalarMediaDetailsAddDescription = false;
  scalarMediaDetailsHideCitationsSectionIfNoCitations = true;
 
  // Overwrite the "Details" tab underneath each media element
  customAddMetadataTableForNodeToElement = function(node, element, linkify) {
    var propOrder = [
      ['dcterms:title','Title'],
      ['dcterms:description','Description'],
      ['dcterms:creator','Creator'],
      ['dcterms:date','Creation date'],
      ['dcterms:temporal','Timeline date'],
      ['dcterms:source','Source'],
      ['dcterms:rights','Rights'],
      ['dcterms:provenance','Provenance'],
      ['vra:culturalContext','Culture/community'],
      ['dcterms:language','Language'],
      ['dcterms:type','Type'],
      ['dcterms:format','Material'],
      ['dcterms:coverage','Location'],
      ['dcterms:spatial','Coordinates'],
      ['dcterms:identifier','Identifier']
    ]
    var obj = $.extend({}, node.current.auxProperties, {
      'dcterms:title':[node.getDisplayTitle()],
      'dcterms:description':[node.current.description],
      'dcterms:source':[node.current.source],
      'art:sourceLocation':[node.current.sourceLocation]
    });
    var $table = $('<table></table>').appendTo(element);
    for (var j = 0; j < propOrder.length; j++) {
      if ('undefined' == typeof(obj[propOrder[j][0]])) continue;
      var values = obj[propOrder[j][0]];
      for (var k = 0; k < values.length; k++) {
        var value = (null == values[k]) ? '' : values[k];
        if (!value.length) continue;
        $table.append( '<tr><td><span title="'+propOrder[j][0]+'">' + propOrder[j][1] + '</span></td><td>' + linkify(value) + '</td></tr>');
      };
    };
    $table.append('<tr><td>Scalar URL</td><td><a href="'+node.url+'">'+node.url+'</a> (version '+node.current.number+')</td></tr>');
    $table.append('<tr><td>Source URL</td><td><a href="'+node.current.sourceFile+'" target="_blank">'+node.current.sourceFile+'</a> ('+node.current.mediaSource.contentType+'/'+node.current.mediaSource.name+')</td></tr>');
  };
 
});

This page has paths: