Creating an Element Library in Krang

This document is a guide to creating an element library in Krang. Being able to build your own element library (or customize an existing element library) is a very important concept in Krang, as it unlocks most of the power and flexibility within Krang.

This guide is aimed developers with experience working in Perl (Krang is a pure-Perl application). Experience with Object-Oriented development, especially in Perl, will be extremely useful. At the very least, be familiar with OO design methodologies.

Before reading this document, you should be comfortable with how Krang works, and be able to set up a Krang installation for your own development use.

Reccommended reading ahead of this document would be:


What an Element Set Looks Like

This guide will use the Default element set that comes with Krang - it can be found at KRANG_ROOT/element_lib/Default/. It will be helpful if you configure a Krang instance (see Krang Configuration) using the Default element set as well, to follow along within the Krang web interface.

Borrowing from the Krang Element System Overview, an Element Set (or Library - the terms are interchangable) describes how all story and category data is structured and handled in a Krang instance. (Starting in Krang v3.04, it can also describe how customized media data is stored.)

Every story within Krang has a number of standard fields - title, slug, category, publish date. Beyond that, everything is defined by the Element set.

When a story is created, a Type has to be chosen - this chooses a root element for the story, and determines what fields will be made available to the user.

For example, if you create a story with a type of Article, you will immediately have the following fields presented in the web UI:

  - Metadata Title
  - Metadata Description
  - Metadata Keywords
  - Promo Title
  - Promo Teaser
  - Deck
  + Page

Open up the file KRANG_ROOT/element_lib/Default/article.pm. What you will see in the subroutine new() is a parameter children that defines all of those elements, plus some additional ones. The code begins with:

  sub new {
     my $pkg = shift;
     my %args = ( name => 'article',
                  children => [
                      Krang::ElementClass::Text->new( name => 'metadata_title',
                                                      display_name => 'Metadata Title',
                                                      min => 1,
                                                      max => 1,
                                                      reorderable => 0,
                                                      allow_delete => 0,
                                                    ),

This is where element relationships are set. This defines the article element as having a series of children, each one with its own settings as to its' appearance, its' name, how many of them exist, etc.

These definitions are used by Krang as a guide during the process of content manipulation - both in the web UI and in the Krang API, users are limited to those actions that are deemed legal by the Element Set. For example, looking at the code sample above, a user will not be allowed to delete, re-order, or create additional metadata_title elements.

Other elements, however, will afford much greater flexibility to users.

A Note on Element Manipulation

One of the most important things to remember when working with element libraries is this:

Maintain Compatibility with Existing Content.

It is quite easy to make changes to your element library that will break existing stories. For example, if you were to remove the entry for metadata_title in the above code sample, all existing stories of type Article would immediately break, as they would all contain an element that is no longer in the definition of an article.

If you are considering extensive changes to an element set currently in use, you may need to write a script to migrate existing content. This will be covered later.


Elements in Krang

Within Krang, an element is split into two parts, with separate APIs:

  1. The element itself, covered by Krang::Element. The Krang::Element API covers general element behavior - creation/deletion, data storage/retrieval, children, tree location, etc.

  2. The second part is the element's class, covered by Krang::ElementClass and the modules derived from it (e.g. Krang::ElementClass::TopLevel, Krang::ElementClass::Media, Krang::ElementClass::Textarea, Krang::ElementClass::StoryLink).

It is the definitions in Krang::ElementClass and its subclasses that determine how the element being created will behave - how it is be presented to the user in the UI, what data it will store, how it publishes itself, and so on.

Every element in an Element Set is a subclass of either Krang::ElementClass or one of its subclasses.


Setting up an Element Set

This section of the guide will walk through the creation of a very basic element set.

Step 1 - The Element Set Spec

The most important thing when creating an element set is deciding how all the elements will relate to eachother. Once in use, making changes to an element set becomes much more difficult (see Revisions to a Live Element Set). Like all other development, spending the extra time in the design stage will save you a lot of time down the line.

The example element set used here has the following requirements:

These requirements define a very basic element set - a real site would have a far more intricate element set, but this will suffice for educational purposes.

These requirements can now be broken out into element trees, which would look something like this:

  Multi-Page Story:
  + basic_article
     - headline       (textbox)
     - deck           (textarea)
     + page
        - page_header (textbox)
        - paragraph   (textarea)
        - story_link  (storylink)
        - image_link  (medialink)
  Cover Story:
  + cover_story
     - headline    (textbox)
     - paragraph   (textarea)
     - story_link  (storylink)
     - image_link  (medialink)
  Category:
  + category
     - display_name  (textbox)
  Media:
  + media
     - <no children>

By putting together this tree, we now have a much clearer idea of what's needed, how it's organized, and what modules need to be developed.

We can see now take the tree above and turn it into a list of Krang elements.

Anything that subclasses a Krang element will need to be written (basic_article, page, cover_story, category).

Everything else has already been developed. We simply need to list the children properly in the elements we are developing.

Step 2 - Creating category.pm

The first step is to create a directory for the new element library.

We're going to call the new element set Tutorial, so we create the directory KRANG_ROOT/element_lib/Tutorial/.

Now, we create KRANG_ROOT/element_lib/Tutorial/category.pm - it's the simplest of the three.

Every element set in Krang needs to have a category element. Category elements are used to build category-specific output (e.g. nav bars, containers, etc), and as a result, are required.

This category element is quite simple - it has a display_name field, something that a user can then populate, and have show up on output. category.pm looks like this:

  package Tutorial::category;
  use strict;
  use warnings;
  =head1 NAME
  Tutorial::category
  =head1 DESCRIPTION
  category element class for Tutorial.  It has a display_name for a sub-element.
  =cut
  use base 'Krang::ElementClass::TopLevel';
  sub new {
      my $pkg  = shift;
      my %args = ( name     => 'category',
                   children => [
                                Krang::ElementClass::Text->new(name => 'display_name',
                                                               allow_delete => 0,
                                                               min => 1,
                                                               max => 1,
                                                               reorderable => 0,
                                                               required => 1),
                               ],
                   @_);
      return $pkg->SUPER::new(%args);
  }

What this does is the following:

There are many additional options that can be used at this point - read the API documentation on Krang::ElementClass to get a further idea of what can be done.

NOTE: The @_ at the end of the %args definition - what this does is allow you to add to or override any of the arguments that are listed here at element instantiation. For example,

  my $element = Tutorial::category->new(max => 5);

Would create a Tutorial::category element object, with all the parameters above, but direct Krang to not allow a user to create more than 5 of them in the location where this one is created.

On the other hand,

  my $element = Tutorial::category->new(name => 'new_category');

Would change the name from 'category' (as defined originally) to 'new_category'.

Step 3 - Creating media.pm

Starting in Krang v3.04, Media objects also have the ability to store element information. This is a new feature still being fine-tuned, and can mostly be ignored unless you'd like to associate customized data with images and other media objects. The only requirement is that your element set contains at least one module (preferably media.pm) that inherits from Krang::ElementClass::Media; its list of children can be empty. (This is required because the user interface needs to know where to look for infomation about media elements.)

  package Tutorial::media;
  =head1 NAME
  Tutorial::media;
  =head1 DESCRIPTION
  Media element class for Krang.
  =cut
  use base 'Krang::ElementClass::Media';
  sub new {
    my $pkg = shift;
    my %args = (
      name => 'media',
      children => [],
      ,
    );
    return $pkg->SUPER::new(%args);
  }

Step 4 - Creating cover_story

Repeating the process we started with category and media, we now create KRANG_ROOT/element_lib/Tutorial/cover_story.pm:

  package Tutorial::cover_story;
  use strict;
  use warnings;
  =head1 NAME
  Tutorial::cover_story
  =head1 DESCRIPTION
  cover_story element class for Tutorial.  It has the following sub-elements:
  headline, paragraph, story_link, image_link
  =cut
  use base 'Krang::ElementClass::Cover';
  sub new {
      my $pkg  = shift;
      my %args = ( name     => 'cover_story',
                   children => [
                                Krang::ElementClass::Text->new(name => 'headline',
                                                               allow_delete => 0,
                                                               size => 40,
                                                               min => 1,
                                                               max => 1,
                                                               reorderable => 0,
                                                               required => 1),
                                Krang::ElementClass::Textarea->new(name => 'paragraph'),
                                Krang::ElementClass::StoryLink->new(name => 'story_link'),
                                Krang::ElementClass::MediaLink->new(name => 'media_link'),
                               ],
                   @_);
      return $pkg->SUPER::new(%args);
  }

We have now created:

Step 5 - Configuration Changes

With a category and cover page, we now have enough to look at things in Krang. It's time to make some configuration changes so Krang knows to look for the new element library.

Now, run:

  $ bin/krang_createdb --all

krang_createdb will now iterate over all configured Instances in conf/krang.conf, and create databases as needed (e.g. new Instances only).

At this point, you can re-start Krang.

  $ sudo bin/krang_ctl stop
  $ sudo bin/krang_ctl start


Checking the Results

When you log into the Tutorial instance, the first thing you will need to do is create a site for everthing to be published under.

The Cover Story Element

With the newly-created site, create a new story. You will see only one option available under the Type pulldown - the Cover Story type we just created!

As you go through the process of story creation, you'll see that all the sub-elements we defined are there:

The Category Element

To look at (and edit) the category element, go to the Categories menu (in the left nav, in the Admin section).

When you create a site in Krang, a category is automatically created, representing the root of the site.

Edit that category, and you will see the single Display Name element that we added to the category definition.

The category UI works in the same fashion as the story UI. If we had made the category definition more complex (optional elements, etc), you would have the same options to add/delete/reorder that you have available to you in the story UI.

The Media Element

If elements are added to the media element, they are displayed in a UI that works identically to the story and category element UI.

Congratulations, you now have completed several element types! The one thing missing for both of these is output templates, which we will address later.


Step 6 - Creating the basic_article

The basic_article story type is a little more involved. Remember, the structure of basic_article was defined earlier as a multi-page story:

  + basic_article
     - headline       (textbox)
     - deck           (textarea)
     + page
        - page_header (textbox)
        - paragraph   (textarea)
        - story_link  (storylink)
        - image_link  (medialink)

basic_article has three children: headline, deck, and page.

But now page has its own set of children: page_header, paragraph, story_link and image_link.

So we will need to create two new files. We will start with page.

KRANG_ROOT/element_lib/Tutorial/page.pm:

  package Tutorial::page;
  use strict;
  use warnings;
  =head1 NAME
  Tutorial::page
  =head1 DESCRIPTION
  the page element class for Tutorial.
  It will be used by basic_story - the multi-page story type.
  page has the following children:
  page_header, paragraph, story_link, image_link
  =cut
  use base 'Krang::ElementClass';
  sub new {
      my $pkg  = shift;
      my %args = ( name => 'page',
                   min  => 1,
                   pageable => 1,
                   children => [
                                Krang::ElementClass::Text->new(name => 'page_header',
                                                               min  => 1,
                                                               max  => 1,
                                                               reorderable  => 0,
                                                               allow_delete => 0
                                                              ),
                                Krang::ElementClass::Textarea->new(name => 'paragraph',
                                                                   min  => 1
                                                                  ),
                                Krang::ElementClass::StoryLink->new(name => 'story_link'),
                                Krang::ElementClass::MediaLink->new(name => 'image_link')
                               ],
                   @_
                 );
      return $pkg->SUPER::new(%args);
  }

The page element cannot stand up by itself as a story type. It is designed to be a child of another element - basic_article.

KRANG_ROOT/element_lib/Tutorial/basic_article.pm:

  package Tutorial::basic_article;
  use strict;
  use warnings;
  =head1 NAME
  Tutorial::basic_article - simple article type for the Tutorial element
  library
  =head1 DESCRIPTION
  basic_article is a simple multi-page story type in the Tutorial
  element library.
  It has the following children: headline, deck, page (Tutorial::page).
  =cut
  use base 'Krang::ElementClass::TopLevel';
  sub new {
      my $pkg  = shift;
      my %args = (
                  name => 'basic_article',
                  children => [
                               Krang::ElementClass::Text->new(name => 'headline',
                                                              allow_delete => 0,
                                                              size => 40,
                                                              min => 1,
                                                              max => 1,
                                                              reorderable => 0,
                                                              required => 1
                                                             ),
                               Krang::ElementClass::Textarea->new(name         => 'deck',
                                                                  allow_delete => 0,
                                                                  reorderable  => 0,
                                                                  required     => 1,
                                                                  min => 1,
                                                                  max => 1
                                                                 ),
                               Tutorial::page->new(name => 'article_page',
                                                   min  => 1
                                                  )
                              ],
                  @_
                 );
      return $pkg->SUPER::new(%args);
  }

A few notes on basic_article:

Finally, add basic_article to set.conf,

KRANG_ROOT/element_lib/Tutorial/set.conf:

  Version 1.0
  TopLevels category media cover_story basic_article

and restart Krang.

  $ sudo bin/krang_ctl stop
  $ sudo bin/krang_ctl start

Congratulations! You now have the four most fundamental story components of Krang.


Templates

Now that you have story types created, you need output templates. Without output templates, you have no way of publishing your results. In fact, try to preview one of your stories - Krang will complain.

Template design and construction is beyond the scope of this document - that's all covered in Writing HTML::Template Templates in Krang.

One important thing to note, however:

Templates are stored in the Krang database, not on the filesystem. It's a VERY good idea to create a .kds (Krang Data Set) file of your templates to bundle with your element library.

Once you are finished with your template development, export the templates as a .kds file:

  bin/krang_export --templates --output templates.kds
  cp templates.kds element_lib/Tutorial/templates.kds

An additional benefit:

If you create additional Krang instances using your element library, bin/krang_createdb will see the element_lib/Tutorial/templates.kds file, and automatically install the templates for you.

Once finished with development of your element library (e.g. everything works as planned, templates are finished, etc), you may want to creating a Krang add-on out of your element library. There are a number of benefits to this:

Read Building a Krang Add-on for more information on creating an Add-on, and take a look at available Krang AddOns to see some existing addons.


Further Development

This section covers more advanced topics in element library development.

Lists

Lists in Krang are a very powerful concept - it allows you to provide your editorial staff with pulldown menus in the editorial user interface, pulldows whose content they can control themselves through the Lists interface.

The flexibility that lists provide is attractive to editorial staffs, as they give nearly the flexibility of a textbox (over a pulldown element whose options are fixed by the element definition), with none of the potential for error through misspelling.

Additionally, Krang lists can be multidimensional and hierarchtical - think of 2 lists, where the content of the second list is dependent on the selection made in the first one.

The classic example is the Year/Make/Model search seen on a lot of car websites - a manufacturer might have produced a 6000 SUX in 1999, but not in 2000.

NOTE: multidimensional lists are not supported properly in the Krang UI in versions prior to 1.102.

Setting up Lists - lists.conf

The structure of the lists associated with an element set can be saved in a config file in the same directory as the element set itself.

The lists.conf file is an XML file containing a description of each list group. For example:

  <list_groups>
    <list_group list_group_name="Segments" description="This list group just contains one list">
      <list list_name="Segments" />
    </list_group>
    <list_group list_group_name="Cars" description="Year/Make/Model Car list">
      <list list_name="Year" />
      <list list_name="Make" />
      <list list_name="Model" />
    </list_group>
  </list_groups>

This config file describes two different lists:

NOTE:

The lists.conf file is only read automatically when a database is set up using bin/krang_createdb.

If your database is already set up, you can use bin/krang_create_lists to create the lists in Krang specified by your lists.conf file.

Using Lists in the Element System

To demonstrate how lists are used, we're going to build a new story type: pulldown_article

  + pulldown_article
     - headline       (textbox)
     - deck           (textarea)
     - segments_list  (listgroup)
     - cars_list      (listgroup)
     + page
        - page_header (textbox)
        - paragraph   (textarea)
        - story_link  (storylink)
        - image_link  (medialink)

For the sake of simplicity, pulldown_article is derived from basic_article - in fact, it's just basic_article with the two lists we created in lists.conf added.

pulldown_article looks as follows:

  package Tutorial::pulldown_article;
  use strict;
  use warnings;
  =head1 NAME
  Tutorial::pulldown_article - article type that makes use of Krang lists.
  =head1 DESCRIPTION
  Tutorial::pulldown_article is used to demonstrate how lists work in Krang.
  =cut
  use base 'Krang::ElementClass::TopLevel';
  sub new {
      my $pkg  = shift;
      my %args = (
                  name => 'pulldown_article',
                  children => [
                               Krang::ElementClass::Text->new(name => 'headline',
                                                              allow_delete => 0,
                                                              size => 40,
                                                              min => 1,
                                                              max => 1,
                                                              reorderable => 0,
                                                              required => 1
                                                             ),
                               Krang::ElementClass::Textarea->new(name         => 'deck',
                                                                  allow_delete => 0,
                                                                  reorderable  => 0,
                                                                  required     => 1,
                                                                  min => 1,
                                                                  max => 1
                                                                 ),
                               Krang::ElementClass::ListGroup->new(name       => 'segments',
                                                                   list_group => 'Segments',
                                                                   multiple   => 1,
                                                                   min => 1,
                                                                   max => 1,
                                                                  ),
                               Krang::ElementClass::ListGroup->new(name       => 'car_selector',
                                                                   list_group => 'Cars',
                                                                   multiple   => 0,
                                                                   min => 1,
                                                                   max => 1,
                                                                  ),
                               Tutorial::page->new(name => 'article_page',
                                                   min  => 1
                                                  )
                              ],
                  @_
                 );
      return $pkg->SUPER::new(%args);
  }

This should look familiar by now - the only addition over the basic_article story type are two Krang::ElementClass::ListGroup elements. The first one uses the Segments list we defined in lists.conf, the second uses the Cars list.

The only parameter of note is the multiple parameter - in single-dimensional lists, you can allow the user to select multiple options. This will not work in multi-dimensional lists.

Using Lists in the User Interface

Once everything has been set up, the content of these lists needs to be populated. This can be done within the Lists interface under the Admin menu.

If you already have a large set of list information that needs to be imported, this can be done programatically by using the API provided by Krang::ListGroup, Krang::List, and Krang::ListItem.

Once your lists are populated, simply create a new pulldown_article story, and you're good to go.


Additional Reading

This document only covers the basics in Krang Element Library development. Further reading: