A Concrete5 Development Tutorial; Creating a Quote Block

7th July 2011 | Tags:

I needed the means to add a quote to a web­site I was work­ing on — a sim­ple task, which could be acheieved by sim­ply adding a text or HTML block. How­ever this can be taken a step fur­ther by imple­ment­ing a sim­ple block type specif­i­cally for quotes, with the markup gen­er­ated for you.

To mark up a quote seman­ti­cally, we’ll want a blockquote for the con­tent and a cite for the source (or author), so the HTML needs to look some­thing like this:

<blockquote>
<p>It is the mark of an educated mind to be able to entertain a thought without accepting it.</p>
<p><cite>Aristotle</cite></p>
</blockquote>

…which will give us some­thing a lit­tle like this:

Concrete5 Module Development Tutorial: the final result
Our quote will end up look­ing a lit­tle like this (sub­ject to CSS styling!)

Of course we could just paste this struc­ture into an HTML block, but a sim­ple block con­tain­ing two fields — con­tent and source — is actu­ally pretty easy to build. So, let’s begin.

First Steps

Let’s call the block — imag­i­na­tively enough — quote. Incor­po­rat­ing this block into a a pack­age is pretty sim­ple, but out­side the scope of this tuto­r­ial (and I’ve cov­ered pack­ages before). So, sim­ply cre­ate a direc­tory named quotes in the blocks direc­tory (not the concrete direc­tory — this might mir­ror the top-​level direc­tory struc­ture, but it’s reserved for the core, and would get over­writ­ten with any upgrade).

The Data­base Schema

The first thing we’ll need is to define a new data­base table to hold our quotes. This sim­ply needs an iden­ti­fier (which becomes the table’s pri­mary key), the text of the quote (i.e. the con­tent) and the source. Let’s keep the con­tent flex­i­ble and allow plenty of text if required, but the source is unlikely to require any more char­ac­ters than a sim­ple text-​field will give us. The data­base schema in Concrete5 gets defined accord­ing to the ADOdb XML schema, so a table is defined by cre­at­ing a sim­ple XML file in your block’s direc­tory which must be called db.xml — and it will look like this: /blocks/quote/db.xml

<?xml version="1.0"?>
<schema version="0.3">
 <table name="btQuoteBlockContent">
    <field name="bID" type="I">
   <key />
   <unsigned />
    </field>
    <field name="content" type="C2">
    </field>
    <field name="source" type="C" size="255">
    </field>
 </table>
</schema>

So, three fields — bID for the block iden­ti­fier, which is an unsigned inte­ger (rep­re­sented by I) — which is the pri­mary key — a content field, which is a Multi­byte var­char (see the doc­u­men­ta­tion) and the source is a 255-​character field (a MySQL varchar, ref­er­enced using a C).

The Add /​Edit Forms

Next up, we need the means to add a new quote. When adding a new block, the file add.php gets ren­dered, and all we need is a sim­ple form. How­ever since the form for adding a new quote is, to all intents and pur­poses, the same form as for edit­ing an exist­ing quote, what we can do is sim­ply include a sep­a­rate file which defines the form — which by con­ven­tion is called form_setup_html.php — and use the same code for the forms for both adding and edit­ing. So, both add.php and edit.php are iden­ti­cal, and look like this: /blocks/quote/add.php and /blocks/quote/edit.php

<?php   
defined('C5_EXECUTE') or die(_("Access Denied."));
$this->inc('form_setup_html.php'); 

The first line pre­vents the file from being accessed directly, the sec­ond includes the file which defines the form, which is shown below:

/blocks/quote/form_setup_html.php

<?php defined('C5_EXECUTE') or die(_("Access Denied.")); ?>  
<div>
<h2><?php echo t('Quote Content') ?></h2>
<textarea id="ccm-QuoteContent" name="content" style="width:98%; height:200px;">
<?php echo $content ?>
</textarea>
<h2><?php echo t('Quote Source') ?></h2>
<textarea id="ccm-QuoteSource" name="source" style="width:98%; height:100px;">
<?php echo $source ?>
</textarea>
</div>

Noth­ing com­pli­cated here, just a form where any cur­rent value will have been made avail­able as the vari­ables $content and $source respec­tively — we’ll see these get pop­u­lated by exam­ing the next file we need to cre­ate, the block controller.

The Block Controller

/blocks/quote/controller.php

<?php   
defined('C5_EXECUTE') or die(_("Access Denied."));

Loader::block('library_file');

class QuoteBlockController extends BlockController {

 protected $btTable = 'btQuoteBlockContent';
 protected $btInterfaceWidth = "600";
 protected $btInterfaceHeight = "400";

 public $content = "";
 public $source = "";   

 public function getBlockTypeDescription() {
    return t("Add simple quotes to your website.");
 }

 public function getBlockTypeName() {
    return t("Quote");
 }  

 public function __construct($obj = null) {  
    parent::__construct($obj); 
 }

 public function view(){ 
    $this->set('content', $this->content);
    $this->set('source', $this->source); 
 } 

 public function save($data) { 
    $args['content'] = isset($data['content']) ? $data['content'] : '';
    $args['source'] = isset($data['source']) ? $data['source'] : '';
    parent::save($args);
 }
}
?>

There’s quite a lot to go through here, but noth­ing too com­pli­cated. This file sub-​classes the BlockController class, which we’ll call QuoteBlockController because our mod­ule is called, sim­ply, quote. First up, the meth­ods getBlockTypeName() and getBlockTypeDescription() expose infor­ma­tion for the admin­is­tra­tive inter­face. The name is used when choos­ing a block to add, and the descrip­tion is used in addi­tion to the name when adding new func­tion­al­ity in the dash­board. The line:

protected $btTable = 'btQuoteBlockContent';

…defines a prop­erty of our block con­troller which indi­cates what data­base table to use. Once an asso­ci­a­tion is made with a table, oper­a­tions can be per­formed on that table using meth­ods such as save within the con­troller. Also, some func­tion­al­ity — such as query­ing the table when show­ing a block — is per­formed auto­mat­i­cally, once the con­troller knows where to look. The prop­er­ties $btInterfaceWidth and $btInterfaceHeight con­trol the dimen­sions of the popup win­dow in the adimis­tra­tive inter­face when adding or edit­ing an instance of our new block. The save method is called when the add or edit forms are sub­mit­ted, and gets passed an asso­cia­tive array of data from the form; you’ll recog­nise that the indexes content and source match the names of the form ele­ments pre­vi­ously defined. Call­ing the parent’s save method with this data will store the infor­ma­tion in the table we’ve pre­vi­ously cre­ated, because by refer­ring to the table name in the $btTable prop­erty we’ve already told the con­troller where that data goes. Finally, the view method is auto­mat­i­cally called when the block is ren­dered and sim­ply needs to take the prop­er­ties of the block,content and source — which, by har­ness­ing the power of the blocks sys­tem have already been loaded for us are already avail­able as prop­er­ties of the con­troller — and assign them to the view. This is achieved using the set method, and as a con­se­quence they will be made avail­able as vari­ables in the view tem­plate, view.php, which is shown below.

The View Template

/blocks/quote/view.php

<?php defined('C5_EXECUTE') or die(_("Access Denied.")); ?>

<div id="QuoteBlock<?php   echo intval($bID)?>" class="QuoteBlock">
    <blockquote>
    <p><?php print $content; ?></p>
 <p><cite><?php print $source; ?></cite></p>
    </blockquote>
</div>

Pretty sim­ple stuff — and by doing it this way, we ensure that our markup for the quote is exactly as we intended it.

Installing & Con­fig­ur­ing the New Block

So, if you browse to the Dash­board and select Add Func­tion­al­ity, you should see that the new block is avail­able to install under the head­ing “Down­loaded and Ready to Install”.

Concrete5 Module Development Tutorial: installing the new block
Your new block will appear under the head­ing “Down­loaded and Ready to Install” — click install

(Note that the icon dis­played along­side the new block type can be per­son­alised; all I’ve done is cre­ate a sim­ple 16x16px icon and call it icon.png.) You can now try adding a block; if you scroll to the bot­tom of the list of avail­able blocks you should see the new block listed:

Concrete5 Module Development Tutorial: Adding a block
At the bot­tom of the list of avail­able blocks, you’ll notice our new block — *quote* is now available

Select­ing the quote block gives you the form we’ve just created:

Concrete5 Module Development Tutorial: the add / edit form
This illus­trates the form you’ll get when adding a block of type *quote* — enter the con­tent of the quote and its source (author) in the fields provided

If you enter a quote and its source and then click add then, all being well, you should end up with some­thing like the screen­shot below.

Concrete5 Module Development Tutorial: the final result
The final result — a seman­ti­cally cor­rect quote

(I’ve applied some very basic styling, I’m sure you can do bet­ter!) This is an extremely sim­ple exam­ple, and I&rsquo’ve left out cer­tain things — such as val­i­da­tion — for brevity, but hope­fully it’s enough to give you an idea of how easy it is to cre­ate func­tion­al­ity like this and to get started devel­op­ing for Concrete5.

Comments

    Nice tutorial for beginning blocks! A couple things I noticed about it:
    form setup. You might want to load the form helper $form=loader::helper('form'); to make these areas like $form->textareas('content'); as it is faster. For the source, I would use a text field as opposed to a text area.
    Controller: You shouldn’t have var $pobj anymore, that was legacy, and you shouldn’t need public $content = "";
    public $source = "";, they should be defined by default. You might want to explain what the t() function is for (translation), as if a block is going to be used in multiple languages this is a necessity.
    public function __construct($obj = null) {
    parent::__construct($obj);
    }

    public function view(){
    $this->set('content', $this->content);
    $this->set('source', $this->source);
    }
    are not required functions unless you’re doing something funky. Save isn’t required either (except by checkboxes), but can be used to define a variable if it was left blank in the form.

    9th July 2011
    Jack
    Jack

    Installing the block failed, and an SQL error message appeared. In the db.xml file, there is no size defined for the field "content". After changing the line to the following, it worked:

    11th January 2013
    Torsten
    Torsten

    field name="content" type="C2" size="255"

    11th January 2013
    Torsten
    Torsten

Links and images are allowed, but please note that rel="nofollow" will be automactically appended to any links.