plainblack.com
Username Password
search
Bookmark and Share

Assets

An asset is the basic unit of content inside WebGUI. Assets can be files, images, articles, CSS, JavaScript, forums, calendars, galleries, applications, and more. Some assets are used to display other assets. Some assets are used to display and manage collateral data. Assets are the base of WebGUI, the main event, the big show, the way to get WebGUI to do what you need.

 

With the full power of the Session object and the rest of the WebGUI API, you can make assets that can do anything. With the SQL API, you can make an asset to manage your customer information. With the User and ProfileField API's, you can make new ways to display and manage user information.

 

The Bare Necessities: a Cliched Example

Making the simplest of assets is a simple process. To make it even easier, you could start with the asset skeleton, located in WebGUI/lib/WebGUI/Asset/_NewAsset.skeleton. The example here starts from scratch with the barest minimum. These lines set up your new asset as a subclass of WebGUI::Asset.

 

package WebGUI::Asset::HelloWorld;

use strict;

use base 'WebGUI::Asset';

 

Definition

The definition method gives information to your asset. The asset's properties, what table in the database, and the internationalized name of the asset are just part of the information you can get from the definition.

 

sub definition {

my $class = shift;

my $session = shift;

my $definition = shift;

 

These lines start your definition. sub definition is a class method, so the first argument will be the class. $session is a WebGUI::Session object, which you'll need later to get a WebGUI::International object (or other things). $definition is a definition from a subclass. Since definitions need to be inherited, each class's definition sub calls its parent's definition sub, and so on up the chain to WebGUI::Asset->definition(). The end result is an array reference of hash references, with your new class's information as the first element.

 

tie my %properties, 'Tie::IxHash', (

 

Next, create a hash to store your asset's properties. In order to maintain the order of the hash, the example uses Tie::IxHash. This way, every time you call values %properties, or keys %properties, the information returned will be in the same order as they were added (read Perldoc Tie::IxHash for more information). To begin, add a property that will store what greeting you want to use.

 

tie my %properties, 'Tie::IxHash', (

greeting => {

tab => 'properties',

fieldType => 'text',

defaultValue => 'Hello',

label => 'Greeting',

hoverHelp => 'What greeting to give the user',

},

);

 

These properties will show up inside of a WebGUI::TabForm, so asset properties are defined in terms of a WebGUI::Form::Control object with some additional properties added in. From top to bottom, you have:

 

tab

This is which tab of the WebGUI::TabForm this property will show up in. This will be discussed again later.

 

fieldType

The fieldType is the type of WebGUI::Form::Control this property is. This will prefix "WebGUI::Form::" to this value, and upper-case the first character, so this greeting is a WebGUI::Form::Text control.

 

defaultValue

This is the default value for this property. This is not only given to the WebGUI::Form::Control, but it is handled specially by the update() sub.

 

label

This is the label that will be used for this property. This will usually be something from a WebGUI::International object, which is covered later.

 

hoverHelp

This will be shown when a user hovers over the label for this property. It is also something that should be part of a WebGUI::International object.

 

Additional keys in the property hash reference will be given directly to the form control object, so see the WebGUI::Form::Control subclass for more information. Properties are only one part of the definition, though they are the most important part.

 

push @$definition, {

properties => \%properties,

assetName => 'Hello World',

icon => 'assets.gif',

tableName => 'HelloWorld',

className => __PACKAGE__,

autoGenerateForms => 1,

};

return $class->SUPER::definition( $session, $definition );

}

 

Here's where you add your entry to this asset's definition and send it up the chain. From top to bottom:

 

properties

Here's where you give a reference to your hash of properties.

 

assetName

This is the name of the asset, which will show up on the edit form, the new content pane, and other places. This may be internationalized.

 

icon

This is a filename for an image to be used as this asset's icon. The asset icons are located in the WebGUI/www/extras/assets directory, with the smaller version located in WebGUI/www/extras/assets/small. One filename will be used in both folders.

 

tableName

This is the name of the table in the WebGUI database.

 

className

This is the name of the class you're making. Using the __PACKAGE__ special value makes this easy.

 

autoGenerateForms

If this is set to a true value, the asset edit form will be automatically generated from the properties. In most cases, you'll want to leave this set to 1. Advanced techniques with the asset edit form will be covered later.

 

The final line sends your definition to your superclass, which will in turn send it up until it reaches WebGUI::asset.

 

Though all of these keys are required, the most important is the tableName. This is the name of the table that this asset's properties will be stored in. Only this class's properties will be stored in this table, since each level of the definition has its own tableName. This is an important part of the asset system, and what makes subclassing easy.

 

Create the Database Table

Since your tableName is "HelloWorld", you must make a table in your database called "HelloWorld".

 

CREATE TABLE `HelloWorld` (

`assetId` VARCHAR(22) BINARY NOT NULL,

`revisionDate` BIGINT NOT NULL,

`greeting` VARCHAR(255),

PRIMARY KEY ( `assetId`, `revisionDate` )

);

 

Your table needs at least the assetId and revisionDate columns, with those exact properties. The assetId is a base64 string, so it needs the BINARY property in order to ensure that it's treated as case-sensitive. The revisionDate is an epoch time, so it needs a BIGINT so that it doesn't overflow a normal INT field.

 

Following the two required columns is your greeting column. This column has the same name as the key from the properties hash. Since it's a text field, assign a varchar type to it. Notice that you cannot tell this column to be NOT NULL, since new assets are initially inserted with only the assetId and revisionDate columns when they are added to the database. Only these columns can be NOT NULL.

 

The final line sets your primary key to be a combination of the assetId and revisionDate columns. Since assets are most often looked up by both assetId and revisionDate, this will significantly speed up the lookups.

 

View Method

The other required method is the view method. This method is the default view for the asset. It differs from www_view (and other www_ methods) in that this method will be called when the asset is part of a Page Layout. www_view is what is called when the asset is accessed directly, which in turn calls the view method. So for the default view, override the view method.

 

sub view {

my $self = shift;

my ($session, $user) = $self->session->quick( qw( session user ) );

my $output = join ', ', $self->get('greeting'), $user->username;

return $output;

}

 

The first two lines are self-explanatory. They set up the subroutine as an object method. The next line gives you a couple objects from the session, namely the session and user objects. The fourth line makes your output, using the get(property) method to get your greeting, and gets the username from the user object. Line five returns your output, which will then be printed to the user.

 

Add the Asset to the Configuration Table

In order to actually use a new asset in your site, you need to tell WebGUI about it. This is done through the configuration file.

 

There are three sections in the WebGUI configuration file that control which asset types are shown in the New Content menu of the admin accordion and the asset manager. The first one, “assets”, is used for general-purpose assets, and the Hello, World! asset would definitely fit in that category. The other two are used for more specialized asset types. “utilityAssets” is best used for those assets that are used less frequently by normal users. “assetContainers” is reserved for those assets that don't have any content of their own, but instead display other assets, like a Page Layout or a Folder.

 

To add your asset, change the “assets” configuration to look something like this:

 

“assets” : [

“WebGUI::Asset::HelloWorld”,

“WebGUI::Asset::Snippet”,

“WebGUI::Asset::Redirect”,

...

],



 

Now, when you restart your server, your Hello World asset will be available in the New Content menu.

 

Hello World

The complete HelloWorld asset looks like this:

 

package WebGUI::Asset::HelloWorld;

use strict;

use base 'WebGUI::Asset';

sub definition {

my $class = shift;

my $session = shift;

my $definition = shift;

tie my %properties, 'Tie::IxHash', (

greeting => {

tab => 'properties',

fieldType => 'text',

defaultValue => 'Hello',

label => 'Greeting',

hoverHelp => 'What greeting to give the user',

},

);

push @$definition, {

properties => \%properties,

assetName => 'Hello World',

icon => 'assets.gif',

tableName => 'HelloWorld',

className => __PACKAGE__,

autoGenerateForms => 1,

};

return $class->SUPER::definition( $session, $definition );

}

sub view {

my $self = shift;

my ($session, $user)

= $self->session->quick( qw( session user ) );

my $output

= join ', ', $self->get('greeting'), $user->username;

return $output;

}

1; # The truth is right here

 

Adding a Template

Time for a more fun example: make an InfoCard to store some personal information about a person. While you're at it, add a template so that users of your asset can decide how to display that information. Start out with making your new properties. First, make a template.

 

sub definition {

my $class = shift;

my $session = shift;

my $definition = shift;

 

tie my %properties, 'Tie::IxHash', (

templateIdView => {

tab => 'display',

fieldType => 'template',

label => 'Template',

hoverHelp => 'Template for this InfoCard',

namespace => 'InfoCard',

},

 

The templateIdView property will use WebGUI::Form::Template (line 9) to make a select box populated with templates in the InfoCard namespace (line 12). This property will show up on the display tab (line 8), which is the standard place for template select boxes.

 

For your additional properties, make some fields for the person's name, address, city, state, zip, country, phone, and e-mail.

 

name => {

tab => 'properties',

fieldType => 'text',

label => 'Name',

hoverHelp => 'The persons name',

},

address => {

tab => 'properties',

fieldType => 'textArea',

label => 'Address',

},

city => {

tab => 'properties',

fieldType => 'text',

label => 'City',

},

state => {

tab => 'properties',

fieldType => 'text',

label => 'State',

size => 2,

},

zip => {

tab => 'properties',

fieldType => 'zipcode',

label => 'Zip Code',

},

country => {

tab => 'properties',

fieldType => 'country',

label => 'Country',

},

phone => {

tab => 'properties',

fieldType => 'phone',

label => 'Phone',

},

email => {

tab => 'properties',

fieldType => 'email',

label => 'E-Mail',

},

);

 

Finally, to round out your definition method, you'll need your other properties.

 

push @$definition, {

properties => \%properties,

assetName => 'InfoCard',

icon => 'assets.gif',

tableName => 'InfoCard',

className => __PACKAGE__,

autoGenerateForms => 1,

};

}

 

The creation of the InfoCard table is left as an exercise for the reader.

 

getTemplateVars

Once you have your definition, you can create your view. This time, however, instead of just returning a bunch of HTML, you're going to build a template. The first thing you need to do is build a method to get the variables you're going to give the template processor. Call your method getTemplateVars.

 

sub getTemplateVars {

my $self = shift;

my $var = $self->get;

 

$var->{ url } = $self->getUrl;

 

return $var;

}

 

Since most of the interesting data is part of your asset's properties, start out with that. The get method, when given no arguments, returns a hash reference of the asset's properties. Since the asset's URL property may not be the same URL that the user needs to access your asset, due to gateway or other considerations, override the URL property with the value from getUrl, which gets an absolute URL to your asset (including gateway, but not including the domain). Finish up by returning this hash reference.

 

processTemplate

Once you have your template vars, you need to put them inside a template. Since this is such a common operation, there's an easy way to do it: processTemplate.

 

sub view {

my $self = shift;

my $var = $self->getTemplateVars;

return $self->processTemplate( $var, $self->get('templateIdView') );

}

 

processTemplate will instantiate the WebGUI::Asset::Template object with the given ID (the second argument), and give the processor the hash reference of variables (the first argument). It then returns the value from the template's process method. The end result is the template gets processed and you get your output.

 

prepareView

One of WebGUI's many features is Content Chunking. In short, this means that the user is given something, anything, before the real process intensive operation (the view method probably) is run. In order to make this happen, you need to be able to send the HTTP header and the site header (which includes the <head> block) before you run the view method, but your asset (and its template) may have tags it wants to add to the <head> block.

 

For this reason, there's the prepareView method. prepareView prepares the template that is to be used in the view method.

 

sub prepareView {

my $self = shift;

$self->SUPER::prepareView();

my $templateId = $self->get('templateIdView');

my $template

= WebGUI::Asset::Template->new( $self->session, $templateId );

$template->prepare;

$self->{_viewTemplate} = $template;

return;

}

 

Starting with line 3, call the superclass prepareView. The top-level, WebGUI::Asset::prepareView prepares the head tags for the asset and adds them to the queue to be printed. Lines 4-7 instantiates your template and prepares it, which adds its own head tags to the queue to be printed. Line 8 adds your template to the object, which you'll need later, and line 9 is the best practices method of ending a sub with nothing returned.

 

Now, you need to alter your view method to use the prepared template.

 

return $self->processTemplate( $var, undef, $self->{_viewTemplate} );

 

The third argument to processTemplate allows you to pass in the already-prepared template.

 

The completed WebGUI::Asset::InfoCard:

 

package WebGUI::Asset::InfoCard;

use strict;

use base 'WebGUI::Asset';

use Tie::IxHash;

sub definition {

my $class = shift;

my $session = shift;

my $definition = shift;

tie my %properties, 'Tie::IxHash', (

templateIdView => {

tab => 'display',

fieldType => 'template',

label => 'Template',

hoverHelp => 'Template for this InfoCard',

namespace => 'InfoCard',

},

name => {

tab => 'properties',

fieldType => 'text',

label => 'Name',

hoverHelp => 'If you need help here, try a professional.',

},

address => {

tab => 'properties',

fieldType => 'textArea',

label => 'Address',

},

city => {

tab => 'properties',

fieldType => 'text',

label => 'City',

},

state => {

tab => 'properties',

fieldType => 'text',

label => 'State',

size => 2,

},

zip => {

tab => 'properties',

fieldType => 'zipcode',

label => 'Zip Code',

},

country => {

tab => 'properties',

fieldType => 'country',

label => 'Country',

},

phone => {

tab => 'properties',

fieldType => 'phone',

label => 'Phone',

},

email => {

tab => 'properties',

fieldType => 'email',

label => 'E-Mail',

},

);

push @$definition, {

properties => \%properties,

assetName => 'InfoCard',

icon => 'assets.gif',

tableName => 'InfoCard',

className => __PACKAGE__,

autoGenerateForms => 1,

};

}

sub getTemplateVars {

my $self = shift;

my $var = $self->get;

$var->{ url } = $self->getUrl;

return $var;

}

sub prepareView {

my $self = shift;

$self->SUPER::prepareView();

my $templateId = $self->get('templateIdView');

my $template

= WebGUI::Asset::Template->new( $self->session, $templateId );

$template->prepare;

$self->{_viewTemplate} = $template;

return;

}

sub view {

my $self = shift;

my $var = $self->getTemplateVars;

return $self->processTemplate( $var, undef, $self->{_viewTemplate} );

}

1;

 

The Edit Form

You now have an asset that stores a person's information and displays that information using a template. Now the powers that be have decided that you need to keep track of updates to a person's information, what was updated, and, most importantly, why. Luckily for you, with the asset versioning system, WebGUI will already keep track of what was updated, but you'll need to keep track of the why.

 

getEditTabs

Instead of putting this on the properties tab with the rest of the information, this example makes a new tab called "History". To add more tabs to the asset edit form, override the getEditTabs method.

 

sub getEditTabs {

my $self = shift;

return (

[ 'history', "History" ],

);

}

 

getEditTabs returns a list of array references. The first element in the arrayref is the ID of the tab, which you'll use in the definition sub. The second element is the label, which is the friendly label for the user. Now, if you go to your asset edit page, you'll see an additional tab called History, but you've yet to put any properties in it. Add a text area to the history tab by adding it to the properties in the definition sub.

 

history => {

tab => 'history',

fieldType => 'textArea',

label => 'History',

},

 

Once you add history to your data table, you'll be able to edit the history from your asset edit page, but now you'll also be able to edit the old history, which shouldn't be allowable. You're going to have to do something different. Add the old history as text on the history tab, and have the form element be for adding to the history (instead of changing the history).

 

getEditForm

The getEditForm method returns the WebGUI::TabForm object that will render the asset edit form. If you want to change how the edit form looks, you can override it and add to it as you wish. If you want to replace the elements you defined in the definition properties, first you need to tell WebGUI not to add the element automatically with the autoGenerate key:

 

history => {

tab => 'history',

fieldType => 'textArea',

label => 'History',

autoGenerate => 0,

},

 

By setting autoGenerate to some false value (0, undef, "", or "0"), the default getEditForm method will not add the property field to the TabForm, so you can add it yourself by overriding the getEditForm method.

 

sub getEditForm {

my $self = shift;

my $tabform = $self->SUPER::getEditForm;

$tabform->getTab('history')->raw('<pre>' . $self->get('history') . '</pre>');

$tabform->getTab('history')->textArea({

name => 'history',

value => ( $self->session->form->get('history') ),

});

return $tabform;

}

 

Start by getting the initial tabform by calling the superclass method. Next, add the old history as plain text to the history tab. The getTab method takes a single argument, the ID of the tab, and returns a WebGUI::HTMLForm object. Then, call raw on that object to put in some raw HTML.

 

After you add the old history, add the form element for the new history as shown in lines 5-8. Give it the same name as the property from the definition, and a value from the current form submit. Get the value from the session in case the form gets shown again after it's submitted, which can happen if there are any errors in the form.

 

Finally, return the tabform object so that it can be rendered. Now, your form looks like you want it to, but you're still resetting the history every time you hit the Save button. How can you take the value of the history element and add it to the old history?

 

processPropertiesFromFormPost

Pressing the Save button on the asset edit form runs the www_editSave method. This method has the appropriate magic for adding and editing assets, but the method that actually saves the information from the edit form is processPropertiesFromFormPost. By default, it overwrites all the properties it gets, which will destroy your old history, so you're going to have to tell it not to by using the noFormPost key in the definition.

 

history => {

tab => 'history',

fieldType => 'textArea',

label => 'History',

autoGenerate => 0,

noFormPost => 1,

},

 

Now the old value won't get clobbered by the new value, but you still haven't done anything to add the new value. Do so by overriding the processPropertiesFromFormPost method.

 

sub processPropertiesFromFormPost {

my $self = shift;

my $form = $self->session->form;

my $errors = $self->SUPER::processPropertiesFromFormPost || [];

 

First, call the superclass processPropertiesFromFormPost. Since it may return an array reference of errors, try to grab it. If it doesn't, initialize your errors with an empty array reference. You need $errors to be an array reference for the remainder of the sub.

 

unless ( $form->get('history') ) {

push @$errors, "You must update the History!";

}

return $errors if @$errors;

 

Next, verify that the user entered some history text. If the user didn't, add the error to your list of errors. After compiling your list of errors, you can return the array reference if necessary.

 

# Data passes all checks, save

$self->update({

history => join "\n", $form->get('history'), $self->get('history'),

});

return;

}

 

After you've verified your data, you can update your asset. Add your new history on top of your old history. When you save your asset, you're required to add a reason why you're editing it, and that reason is saved along with all the old reasons.

 

www_edit

The final problem from the powers that be is that the asset edit form is part of the Admin Console. Fortunately, this is easy enough to fix, as the most important part of the www_edit page is handled by getEditForm. The only important things that www_edit does is check that the user has privileges, check that the asset is unlocked, and return the edit form.

 

sub www_edit {

my $self = shift;

return $self->session->privilege->noAccess unless $self->canEdit;

return $self->session->privilege->locked unless $self->canEditIfLocked;

return $self->getAdminConsole->render( $self->getEditForm->print );

}

 

This is the default www_edit sub. To remove the asset from the Admin Console, you just need to add this subroutine to your asset and edit line 5 like so:

 

return $self->getEditForm->print;

 

Your edit form will now appear outside of the Admin Console. Your new code looks like this:

 

sub getEditForm {

my $self = shift;

my $tabform = $self->SUPER::getEditForm;

$tabform->getTab('history')->raw('<pre>' . $self->get('history') . '</pre>');

$tabform->getTab('history')->textArea({

name => 'history',

value => ( $self->session->form->get('history') ),

});

return $tabform;

}

sub getEditTabs {

my $self = shift;

return (

[ 'history', "History" ],

);

}

sub processPropertiesFromFormPost {

my $self = shift;

my $form = $self->session->form;

my $errors = $self->SUPER::processPropertiesFromFormPost || [];

unless ( $form->get('history') ) {

push @$errors, "You must update the History!";

}

return $errors if @$errors;

# Data passes all checks, save

$self->update({

history => join "\n", $form->get('history'), $self->get('history'),

});

return;

}

sub www_edit {

my $self = shift;

return $self->session->privilege->noAccess unless $self->canEdit;

return $self->session->privilege->locked unless $self->canEditIfLocked;

return $self->getEditForm->print;

}

 

More Definition Magic: Filters

By using another key in the definition, you can automatically apply filters to all data in your asset properties. Say you're thinking ahead and want to format the address property to be HTML-ized, based on the plain text input. You don't just want to format it from the asset edit form. You also want anyone adding your asset programmatically to have to go through this filter too.

 

Instead of using processPropertiesFromFormPost(), which would only cover users adding your asset from the asset edit form, you can use the “filter” key in the properties part of your definition. This defines a subroutine in your asset class to use to filter the data for that property.

 

address => {

tab => 'properties',

fieldType => 'textArea',

label => 'Address',

filter => 'filterAddress',

},

 

Here, define a filter called “filterAddress”. You need a method to handle the filtering. This method takes the string to be filtered as the first parameter, and returns the value to be set in the database.

 

sub filterAddress {

my $self = shift;

my $string = shift;

my $output = $string;

 

# First change newlines into HTML

$output =~ s{\n}{<br />}g;

 

# Wrap the whole thing in address tags if necessary

if ( $output !~ s{^<address>} ) {

$output = '<address>' . $output . '</address>';

}

 

return $output;

}

 

Now, whenever the address is changed, your filterAddress method will be run to make sure that it adheres to the appropriate format.

 

Tips, Tricks, and Things Left Unsaid

  • When something is wrong, step one is always to check the error log.

    If you visit your asset and you see a blank screen, check the WebGUI error log. If you don't see what you expect to see, check the WebGUI error log. If things are bad enough that you see Apache's Internal Server Error page, then check Apache's error log.

    If the WebGUI error log doesn't have anything, make sure you bump up the log level from ERROR to INFO or DEBUG. See etc/log.conf for more details.

  • When using WebGUI::Session::Privilege to return an error message, use noAccess() over insufficient(). noAccess gives a login form for those who are not logged in, and after they log in, they are taken right to where they wanted to be instead of to the default view of the asset. See WebGUI::Session::Privilege for more information.

  • The default processTemplate() method adds a bunch of things to the variables passed into it, including the asset's metadata, all the asset's properties, and the controls for use in admin mode. The controls should be added to each template you create. It's located in <tmpl_var controls>. Use <tmpl_if session.var.adminOn> to show only the controls when admin mode is on.

  • By default, the canAdd() method checks if the user is allowed to add any asset of the given class. It does so by checking the config file, and falls back to the Turn Admin On group. As a rule, canAdd() should check the lowest possible level of privileges: if anyone could be allowed to add your asset, canAdd should just return true. If you receive an error when trying to add your new asset, check canAdd first.

  • When saving a new asset, the user also has to pass a canEdit() check on the parent asset. If you're writing an asset that will contain other assets, you will probably need to make a special case in canEdit() to handle adding new assets. If you checked canAdd and are still receiving errors trying to add your new asset, check here next. Note that the canAdd check is done before you see the form, and the canEdit check is done after you fill in the form and click Save.

  • You can prevent certain classes from being added underneath your asset by overriding addChild() and returning undef if the asset being added doesn't pass your checks. This should only be done to make sure the wrong assets don't get added as children of your asset, since there will be no useful error message shown to the user.

Keywords: asset content module wobject

Search | Most Popular | Recent Changes | Wiki Home
© 2018 Plain Black Corporation | All Rights Reserved