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:
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.
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.
Within Krang, an element is split into two parts, with separate APIs:
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.
This section of the guide will walk through the creation of a very basic element set.
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:
simple_article).
The story will have a single headline and deck common to all pages. Each page in the story will have a page header, along with zero or more of the following: paragraphs, links to images, and links to other stories.
A cover story type (cover_story).
A cover story is an index page for a given category. It is a single-page story, with a headline, and zero or more of the following: paragraphs, links to images, and links to other stories.
A category element (category).
The category will have a display_name, and nothing else at the moment.
A media element (media).
The media element will be empty for the moment (but is still necessary, as explained later).
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.
basic_article - a subclass of the Krang::ElementClass::TopLevel
(more on that later) element, with the following children:
headline - a Krang::ElementClass::Text element.
deck - a Krang::ElementClass::Textarea element.
page - a subclass of Krang::ElementClass. page stores no
data of its own, but it has the following children:
page_header - a Krang::ElementClass::Text element.
paragraph - a Krang::ElementClass::Textarea element.
story_link - a Krang::ElementClass::StoryLink element.
image_link - a Krang::ElementClass::MediaLink element.
cover_story - a subclass of the Krang::ElementClass::Cover
element, with the following children:
headline - a Krang::ElementClass::Text element.
paragraph - a Krang::ElementClass::Textarea element.
story_link - a Krang::ElementClass::StoryLink element.
image_link - a Krang::ElementClass::MediaLink element.
category - a subclass of the Krang::ElementClass::TopLevel
element, with the following children:
display_name - a Krang::ElementClass::Text element.
media - a subclass of the Krang::ElementClass::Media
element, empty by default, but allowing media objects to
store additional element information.
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.
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:
Tutorial::category object as being a subclass of
Krang::ElementClass::TopLevel.
The default name for a Tutorial::category element is 'category'.
A Tutorial::category element can have a single child,
display_name. display_name has the following properties:
min => 1).
no additional display_name elements can be added (max => 1).
it cannot be deleted (allow_delete => 0).
If there were additional elements here, you could not change its
position in the order of elements (reorderable => 0).
The user is required to enter some data in it - it cannot be left
empty (required => 1).
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'.
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);
}
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:
Tutorial::cover_story, which is a subclass of Krang::ElementClass::Cover.
cover_story has the following children:
headline is a 40-character text box (see Krang::ElementClass::Text) that will
always be present. It cannot be deleted or reordered.
paragraph is a Krang::ElementClass::Textarea object, and will
create a 30x4 text area. It will not show up by default, but the user
is free to add as many of them as they want, and delete or reorder
them as they see fit.
story_link is a Krang::ElementClass::StoryLink object. It is
used to handle links to other stories within Krang. Like
paragraph, it will not show up by default, but users are free to
add as many as they want, and add/delete/reorder them with impunity.
media_link is a Krang::ElementClass::MediaLink object. Like
story_link, media_link is responsible for handling links to all
media objects in Krang.
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.
set.confKRANG_ROOT/element_lib/Tutorial/set.conf:
Version 1.0 TopLevels category media cover_story
What this tells Krang is that there are currently three Top-Level
elements in the Tutorial element set, category, media and
cover_story (we will get to basic_article later). Krang will
look for them at KRANG_ROOT/element_lib/Tutorial/category.pm and
KRANG_ROOT/element_lib/Tutorial/cover_story.pm.
krang.confKRANG_ROOT/conf/krang.conf to create a new instance in
Krang using the Tutorial element set. At the end of your
krang.conf, add the following:
<Instance tutorial>
# the UI display name for this instance
InstanceDisplayName "Tutorial"
# the virtual host users will use to access this instance
InstanceHostName cms.mytutorial.com
# MySQL database name for this instance
InstanceDBName tutorial
# the element set to be used in this instance. Instances may share
# element sets.
InstanceElementSet Tutorial
</Instance>
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
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.
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:
Headline that you cannot delete, and cannot
reorder.
Under the Add Element section, the Paragraph, Story Link and
Media Link elements, which have no limits as to additions,
deletetions, and reorderings.
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.
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.
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:
basic_article subclasses Krang::ElementClass::TopLevel rather
than Krang::ElementClass::Cover.
The reason is that the story slug (entered when creating a new story)
is used along with the story's category in building the URL for the
story. Cover stories (those that subclass
Krang::ElementClass::Cover) by default use only the
category path for the URL. (Prior to Krang v3.00, Cover stories were
prohibited from including slugs in their URLs; in newer versions this
behavior is optional for all types, and governed by a method called
slug_use() which is explained further in ElementClass/TopLevel.pm)
min => 1 argument in the lines:
Tutorial::Page->new(name => 'article_page',
min => 1)
is actually redundant - it was already made in the page.pm file.
However, you could make the page element entirely optional by setting
it to 0 in this call. Conversely, you could force every article to
contain multiple pages by setting min to a value greater than 1.
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.
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.
This section covers more advanced topics in element library development.
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.
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:
Segments list is a one-dimensional list, that will show up as a single pulldown.
The Cars list is a three-dimensional list, where the contents of
each pulldown will be dependent on the item chosen in the list above
it.
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.
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.
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.
This document only covers the basics in Krang Element Library development. Further reading: