Improving CD Collection module: backend wildcard & templates system | qzminski Blog

Improving CD Collection module: backend wildcard & templates system

Today I will present you how to improve our CD Collection module, by adding the templates system similar to one in the News module, and displaying a wildcard in the backend. Furthermore, we will add a selectable list of CD categories in modules’ options, so you can choose which categories should be displayed in the FE.

Before we start enhancing our module, please do a backup. Note that the backup folder should not stay in the /system/modules/ as it may override new files.

Wildcard in the backend

The wildcard in Contao is a thing to be displayed when nesting a module in the article. In our CD Collection tutorial, we wrote a compile() function that holds the actual code of the module. Try to put the module directly in an article and see what happens in the backend. You will notice a dropdown list with categories and a submit button. Too bad, our module rendered in the admin area! Imagine what would happen if you got a javascript inside or some HTML elements positioned absolutely.

Therefore Contao provides a neat system of wildcards. When a module is selected, it displays a short info about a module – title, type and id.

Module's wildcard visible in the backend

The below code is actually universal and is used in all of Contao modules. I do always copy&paste it, as the only thing to modify is a wildcard parameter:

/**
 * Display a wildcard in the back end
 * @return string
 */
public function generate()
{
  if (TL_MODE == 'BE')
  {
    $objTemplate = new BackendTemplate('be_wildcard');

    $objTemplate->wildcard = '### CD COLLECTION LIST ###';
    $objTemplate->title = $this->headline;
    $objTemplate->id = $this->id;
    $objTemplate->link = $this->name;
    $objTemplate->href = 'contao/main.php?do=themes&table=tl_module&act=edit&id=' . $this->id;

    return $objTemplate->parse();
  }

  return parent::generate();
}

Almost forgot, put this code in the /cd_collection/ModuleCdCollection.php file, just before the compile() function.

Improving templates system

Our CD Collection new features will be set directly in edit view of the module. We will add a checkbox-list of categories that will be displayed in the FE, just like the News module has its list of archives. The second improvement will be a select menu with a list of available templates.

Extending the DCA of tl_module

First of all, to save our new data somewhere in the database, we need to extend the tl_module table (because the new options will be available in DCA of tl_module). Below syntax might seem a little strange, as it clearly states that we want to create a new table.  Normally you would write a query “ALTER TABLE …”, but this time you need to follow Contao’s unconventional way. Modify the /cd_collection/config/database.sql file by adding this code at the end:

CREATE TABLE `tl_module` (
  `cds_categories` blob NULL,
  `cds_template` varchar(64) NOT NULL default ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Now go to the Extension Manager and click the top right button “Update database”. This is exactly the same if you would go to Install Tool and update database there.

Update the Database by Extension Manager

Okay, that was pretty easy.

As you should already know, each DCA file needs to be named the same as DCA it extends. For example, if we have to extend the DCA of tl_module, we should create a new file /cd_collection/dca/tl_module.php:

/**
 * Add palettes to tl_module
 */
$GLOBALS['TL_DCA']['tl_module']['palettes']['cd_collection'] = '{title_legend},name,headline,type;{config_legend},cds_categories;{template_legend:hide},cds_template;{expert_legend:hide},cssID,space';

/**
 * Add fields to tl_module
 */
$GLOBALS['TL_DCA']['tl_module']['fields']['cds_categories'] = array
(
  'label'                => &$GLOBALS['TL_LANG']['tl_module']['cds_categories'],
  'exclude'              => true,
  'inputType'            => 'checkbox',
  'foreignKey'           => 'tl_cds_category.title',
  'eval'                 => array('multiple'=>true, 'mandatory'=>true)
);

$GLOBALS['TL_DCA']['tl_module']['fields']['cds_template'] = array
(
  'label'                => &$GLOBALS['TL_LANG']['tl_module']['cds_template'],
  'exclude'              => true,
  'inputType'            => 'select',
  'options_callback'     => array('tl_module_cds', 'getCdsTemplates')
);

Firstly, we add a new palette. A basic palette for tl_module should contain at least these elements:

$GLOBALS['TL_DCA']['tl_module']['palettes']['new_palette'] = '{title_legend},name,headline,type;{expert_legend:hide},cssID,space';

Although name and type is added automatically, even if you don’t have it set, the elements: headline, cssID and space; should be present. Simply because they are present in every Contao module ;)

The extra elements are two fieldsets {config_legend} and {template_legend}. Contao provides some ready-to-use fieldsets (commonly known as “legends” here) to blur the differences between custom extensions. For example, each module’s configuration fields should be put into the {config_legend}. Therefore, the user always knows where to find the module options.

Now to the fields. In “cds_categories” we set the inputType as checkbox and multiple set to true, because we want to have a checkbox-list. You might wonder why there is no options_callback (options key doesn’t find a use here – the data is dynamically fetched from the database) parameter. Since this is a very simple list, where we are going to store there only an array of categories IDs. So instead of writing a custom function, Contao provides a foreignKey option. In short words, its value determines what data should be used. As you can see the syntax is identical as SQL’s one. First we define a table, then, after a dot, a field – tableName.field. The fetched field will be used as a checkbox label, while the tableName.id as a checkbox value. This is equivalent to a custom function:

public function getCategories()
{
  $arrCategories = array();
  $objCategory = $this->Database->execute("SELECT * FROM tl_cds_category");

  while ($objCategory->next())
  {
    $arrCategories[$objCategory->id] = $objCategory->title;
  }

  return $arrCategories;
}

The “cds_template” is a simple select menu. It also has a dynamic data, but unlike the “cds_categories”, it is not fetched f rom the database. Therefore we use a custom function defined in the options_callback key:

class tl_module_cds extends Backend
{

  /**
   * Return all CDs templates as array
   * @param object
   * @return array
   */
  public function getCdsTemplates(DataContainer $dc)
  {
    return $this->getTemplateGroup('cds_', $dc->activeRecord->pid);
  }
}

Put above code at the end of the file.

The function is very short and simple, it returns the array of all templates that names start with “cds_”. This works exactly the same as templates in News module.

You might wonder why you need a second parameter for this function. Well, this is actually optional. As you know we are modifying the DCA of tl_module. From Contao version 2.9 there are available Themes. Now each module has its parent, which is actually a theme. Thus, the module’s pid is an id of a current theme. Going back to function, the second parameter is used to fetch the current theme’s templates folder.

Languages

Okay, we got the backend’s part ready. The last adjustment is adding some labels and descriptions to our fields. Put this code to the /cd_collection/languages/en/tl_module.php:

/**
 * Fields
 */
$GLOBALS['TL_LANG']['tl_module']['cds_categories'] = array('CDs categories', 'Please select one or more CDs categories.');
$GLOBALS['TL_LANG']['tl_module']['cds_template']   = array('CDs template', 'Here you can select the CDs template.');

Hmm, why there are no labels for legends here? As I said before, there are some legends provided with Contao by default. That means, also the languages are included. You don’t need to worry about it.

Now that looks like a professional module!

Altering the module code

It is time to do some php coding at last :) Examine the below code and you will see one minor, and one major change:

  • We modified a query that fetches CD categories, by adding a condition “WHERE id IN(…)”. The value between brackets will be a list of IDs separated by commas.
  • We removed, or actually moved, the code which took CDs data and put them into array. Now a new function parseCds() do that for us.
/**
 * Generate module
 */
protected function compile()
{
  // Get category ID
  $intCategory = ($this->Input->get('cat')) ? $this->Input->get('cat') : 1;

  // Fetch data from the database
  $objCds = $this->Database->prepare("SELECT * FROM tl_cds WHERE pid=? ORDER BY title")->execute($intCategory);
  $objCategory = $this->Database->prepare("SELECT * FROM tl_cds_category WHERE id=?")->execute($intCategory);
  $objCategories = $this->Database->execute("SELECT id, title FROM tl_cds_category WHERE id IN (" . implode(',', array_map('intval', deserialize($this->cds_categories))) . ") ORDER BY title");

  // Put all categories into array
  while ($objCategories->next())
  {
    $blnSelected = ($objCategories->id == $intCategory) ? true : false;

    $arrCategories[] = array
    (
      'id' => $objCategories->id,
      'title' => $objCategories->title,
      'selected' => $blnSelected
    );
  }

  // Assign data to the template
  $this->Template->cds = $this->parseCds($objCds);
  $this->Template->catTitle = $objCategory->title;
  $this->Template->catDescription = $objCategory->description;
  $this->Template->categories = $arrCategories;
}

So let’s create this function, inside a ModuleCdCollection class and below the compile() function:

/**
 * Parse one or more items and return them as array
 * @param object
 * @param array
 */
protected function parseCds(Database_Result $objCds)
{
  // No CDs
  if ($objCds->numRows < 1)
  {
    return array();
  }

  // Put CDs into array
  while ($objCds->next())
  {
    $objTemplate = new FrontendTemplate($this->cds_template);
    $objTemplate->setData($objCds->row());

    // Add data
    $objTemplate->src = $objCds->image;
    $objTemplate->alt = specialchars($objCds->title);

    // Put each parsed template into array
    $arrCds[] = $objTemplate->parse();
  }

  return $arrCds;
}

This code is almost self-explanatory: just like before we run a while() loop to use all CDs, then for each of them we create a new template (let’s say it’s: “cds_full”) and finally parse it. The setData() function takes an array as its argument, and assigns all values to the template. That means if you pass a row from the database (like in the code above), you are able to display any field (id, pid, tstamp, title etc.) in the template.

It is a good habit to comment all your functions. This saves a lot of time when you need to come back to project you developed some time ago. Plus, it makes easier to reuse or extend your code by other developers. Try to look into Catalog 2 sources and guess what all these functions do… good god!

Template changes

At the end we need to adjust the module templates. Open up /cd_collection/templates/mod_cdcollection.tpl file and find a table that is responsible for displaying CDs. Replace the old code with this:

<table border="0" cellpadding="4">
  <?php foreach($this->cds as $cd) echo $cd; ?>
</table>

Echo’ing an array record looks pretty weird compared to what we had previously. Each FrontendTemplate can be parsed, which means that it is returned as HTML. Thus, our returning array from parseCds() function is nothing else but a pieces of plain HTML code.

Do not forget to create a template with a “cds_” prefix. In this example, as we display CDs within a table, let’s name our file a “cds_table.tpl”:

<tr>
  <td><img src="<?php echo $this->src ?>" height="100" width="100" alt="<?php echo $this->alt; ?>" /></td>
  <td>
    <p><strong><?php echo $this->title; ?></strong> (<?php echo $this->artist; ?>)</p>
    <?php echo $this->comment; ?>
  </td>
</tr>

Conclusion

Pretty awesome, eh? That’s all for today, thank you for your attention!

You can download all files here - cd_collection.zip (~ 14kB).


About Author

Kamil Kuzminski

Hi! I'm a webdeveloper from Olsztyn, Poland. I'm the manager of Contao (fka TYPOlight) polish support website and community. I work mainly as a freelancer for private clients or various agencies.





Comments

  1. Toffa November 14th

    Comment Arrow

    Hi Kamil,

    thanks for the good tutorial. Works this Modul with Contao 291?


  2. Kamil Kuzminski November 14th

    Comment Arrow

    Yes, it works with Contao 2.9.1


  3. Andreas January 25th

    Comment Arrow

    Hi Kamil,
    me too want to thank you for this tutorial (part one to three).

    While studying it, it came to my mind, that it would be great if I could choose the cd collections not only in the module but in the article where I add this module.


  4. Dursun April 11th

    Comment Arrow

    Hi Kmail,

    what must i do that this module works with contao 2.10.4
    can you please describe me where to make adjustments that it will work on new contao versios also!

    thanks

    aadursun


  5. Kamil Kuzminski April 17th

    Comment Arrow

    Hi Dursun, I think the module should work with 2.10.4 and newer. Do you get any errors?


Add Yours

  • Author Avatar

    YOU


Comment Arrow