Template extensions

If the functionality that the Draw classes provide does not meet your needs, and you have basic skills in PHP, you can add your own functionality by writing an extension.

There are actually several ways you could add custom code to dmBridge to either add or override existing functionality.

  • The wrong way: You could take a look at the source code of the method you're using and re-implement your own version right there in the page template. This would be quick, but it would clutter up your template.
  • A variation on the wrong way: Break that custom code out into a separate file and include() it. This would work, but would be nonstandard.
  • The way you should never even think about: Instead of reimplementing the code in the template, you could rewrite the method itself directly into the dmBridge. This was necessary in CONTENTdm® template customization process, but is highly discouraged in dmBridge - you should never touch the dmBridge files. There is a better way.
  • The right way: You could override the method. That's what extensions do. Extensions are the only supported way to add or override functionality in dmBridge.

Some of the advantages of using extensions are:

  • They are isolated from the rest of the dmBridge code
  • They are isolated from the rest of your template code
  • They are consolidated and organized, making customizations easy to find
  • They make upgrading easier
  • They respect the standard dmBridge data interfaces
  • They prevent unexpected side effects from propagating to unexpected places
  • They are systematically testable
  • They can be easily shared with other dmBridge users

dmBridge is an object-oriented system composed of classes which are themselves composed (in part) of methods, each one of which performs a specific task. For example, Draw::loadTime() displays the amount of time the page took to load, in seconds, rounded to 4 digits.

What if you wanted it to be rounded to 8 because time is money? Without extensions, there would be no clean way to do it. With extensions, you could add a file called CustomDraw.class.php to your templates' extensions folder, copy the loadTime() method into it from the original place, and tweak it to round to 8 digits instead of 4. Alternately, if you put this custom file in the dmBridge core's extensions folder, it would be available to all template sets.

PHP is not able to load two classes with the same name simultaneously (without namespaces, anyway, which dmBridge does not use). Whenever dmBridge looks for a class, its class loader will look in the template sets' extensions folders first. If it finds a matching class, it will load that class and then stop. The loaded extension will override the built-in class with the same name. The class loader will look for classes in the following sequence:

  1. Inside template sets' extensions folder
  2. Inside dmBridge core's extensions folder
  3. In the dm/objects/extensions folder

So, when an extension with the same name as a built-in class exists in an extensions folder, the built-in class will not be loaded. This is bad; don't give an extension class a name that is already being used by dmBridge.

But what if the extension only contains one overridden method from the built-in class? What happens to the rest of the methods in the overridden class? Do they just get ignored? What if they're important?

The answer is the the extension class needs to extend (inherit from) its overridden class, so that all of the rest of the functionality remains in place.

If you don't have much exposure to object-oriented programming, don't worry, because the hard part is all behind you now and the examples are straight ahead.

Here's what an extension looks like:

<?php
class CustomObjectDraw extends ObjectDraw {

  
/**
    * Here, we are reimplementing the method we want to
    * override: in this case, the ObjectDraw class' metadata()
    * method. And then we return the result:
    */
  
public static function metadata() {
     
// Do some stuff...
     
$result = '<p>Test</p>';

     
// Finished, now return it
     
return $result;
   }

  
/**
    * Here, we write our own totally new and unique method
    * which does not already exist in ObjectDraw.
    */
  
public static function doSomethingCool() {
     
$text = '<p>I just did something cool...</p>';
      return
$text;
   }

}
?>

In the first method ("public static function"), we are overriding the metadata() method in the ObjectDraw class. In the second, we are writing a new method called doSomethingCool(). Either of these will be accessible from our page templates like so:

<?php
echo CustomObjectDraw::metadata()
?>

<?php
echo CustomObjectDraw::doSomethingCool()
?>

Note that CustomObjectDraw::metadata() does not affect ObjectDraw::metadata() in any way. No code in any collection templates that references the latter will be affected. So, you can use the CustomObjectDraw version in some places, and the built-in version in others.

Extension files must be saved in one of two places:

  1. Your templates folder's extensions subfolder
  2. Your dm/objects/extensionsfolder

They need to be named the same as their class name; the example above would be named CustomObjectDraw.class.php. The class name doesn't matter; you could name it PeanutButter if you wanted. CustomObjectDraw just helps to make it clear what it is.

Real-world extension example

The above code is not very useful in practice. Here is an example of an extension written at the University of Nevada, Las Vegas for the Southern Nevada: The Boomtown Years collection. We have a bunch of classroom activities stored in an XML file, with references to certain objects within each activity node. We needed to be able to provide dynamic links, from object view, to activities that referenced the current object. Here's how we did it in an extension:

<?php
abstract class CustomObjectDraw extends ObjectDraw {

   private static
$html;

  
/**
    * @return Boolean
    */
  
public static function arelinksToRelatedActivities() {
      if (!
self::$html) {
        
self::$html = self::linksToRelatedActivities();
      }
      return (bool)
self::$html;
   }

  
/**
    * @return String of HTML anchor tags
    */
  
public static function linksToRelatedActivities() {
      if (
self::$html) {
         return
self::$html;
      }

     
// DOMDocument is part of PHP's XML DOM extension.
     
$dxml = new DOMDocument('1.0', 'utf-8');
     
$path = dirname(__FILE__) . '/activities/activities.xml';
     
// Load our activites XML file...
     
$dxml->load($path);

     
// Find the XML nodes we need using an XPath query...
     
$dxp = new DOMXPath($dxml);
     
$xpath = sprintf(
        
'//activity[relatedArtifacts/artifact/@uri = "%s"]',
        
dmObject::getCurrent()->getReferenceURL()
      );

     
// Insert the node values into HTML hyperlinks...
     
$links = array();
      foreach (
$dxp-&gt;query($xpath) as $node) {
        
$links[] = sprintf(
           
'<li><a href="/boomtown/activities/view.php?id=%d">%s</a></li>',
           
$node->getAttribute('id'),
           
dmString::websafer(
              
$node->getElementsByTagName('title')->item(0)->nodeValue
           
)
         );
      }
     
// Return the hyperlinks
     
self::$html = implode("\n", $links);
      return
self::$html;
   }

}
?>

From the object view template, we want to find out if there are any links to related activities, and if so, draw them. We put this in object/view_simple.html.php and object/view_compound.html.php:

<h4>Related activities:</h4>
<?php if (CustomObjectDraw::areLinksToRelatedActivities()): ?>
   <ul>
      <?php echo CustomObjectDraw::linksToRelatedActivities() ?>
   </ul>;
<?php else: ?>
   <p>None</p>
<?php endif ?>

Now when we upgrade either CONTENTdm® or dmBridge, our customization is safe. It's also neat, organized, and easy to understand from the templates.

The code above would look quite daunting to a non-programmer. For better or worse, extensions are for programmers. Non-programmers will have to rely on the Draw methods.