A Concrete5 Development Tutorial; Creating a Quote Block

7thJuly 2011 | Tags:

I needed the means to add a quote to a website I was working on - a simple task, which could be acheieved by simply adding a text or HTML block. However this can be taken a step further by implementing a simple block type specifically for quotes, with the markup generated for you.

To mark up a quote semantically, we'll want a blockquote for the content and a cite for the source (or author), so the HTML needs to look something 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 something a little like this:

Concrete5 Module Development Tutorial: the final result
Our quote will end up looking a little like this (subject to CSS styling!)

Of course we could just paste this structure into an HTML block, but a simple block containing two fields - content and source - is actually pretty easy to build. So, let's begin.

First Steps

Let's call the block - imaginatively enough - quote. Incorporating this block into a a package is pretty simple, but outside the scope of this tutorial (and I've covered packages before). So, simply create a directory named quotes in the blocks directory (not the concrete directory - this might mirror the top-level directory structure, but it's reserved for the core, and would get overwritten with any upgrade).

The Database Schema

The first thing we'll need is to define a new database table to hold our quotes. This simply needs an identifier (which becomes the table's primary key), the text of the quote (i.e. the content) and the source. Let's keep the content flexible and allow plenty of text if required, but the source is unlikely to require any more characters than a simple text-field will give us. The database schema in Concrete5 gets defined according to the ADOdb XML schema, so a table is defined by creating a simple XML file in your block's directory 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 identifier, which is an unsigned integer (represented by I) - which is the primary key - a content field, which is a Multibyte varchar (see the documentation) and the source is a 255-character field (a MySQL varchar, referenced 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 rendered, and all we need is a simple form. However since the form for adding a new quote is, to all intents and purposes, the same form as for editing an existing quote, what we can do is simply include a separate file which defines the form - which by convention is called form_setup_html.php - and use the same code for the forms for both adding and editing. So, both add.php and edit.php are identical, 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 prevents the file from being accessed directly, the second 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>

Nothing complicated here, just a form where any current value will have been made available as the variables $content and $source respectively - we'll see these get populated by examing the next file we need to create, 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 nothing too complicated. This file sub-classes the BlockController class, which we'll call QuoteBlockController because our module is called, simply, quote. First up, the methods getBlockTypeName() and getBlockTypeDescription() expose information for the administrative interface. The name is used when choosing a block to add, and the description is used in addition to the name when adding new functionality in the dashboard. The line:

protected $btTable = 'btQuoteBlockContent';

...defines a property of our block controller which indicates what database table to use. Once an association is made with a table, operations can be performed on that table using methods such as save within the controller. Also, some functionality - such as querying the table when showing a block - is performed automatically, once the controller knows where to look. The properties $btInterfaceWidth and $btInterfaceHeight control the dimensions of the popup window in the adimistrative interface when adding or editing an instance of our new block. The save method is called when the add or edit forms are submitted, and gets passed an associative array of data from the form; you'll recognise that the indexes content and source match the names of the form elements previously defined. Calling the parent's save method with this data will store the information in the table we've previously created, because by referring to the table name in the $btTable property we've already told the controller where that data goes. Finally, the view method is automatically called when the block is rendered and simply needs to take the properties of the block,content and source - which, by harnessing the power of the blocks system have already been loaded for us are already available as properties of the controller - and assign them to the view. This is achieved using the set method, and as a consequence they will be made available as variables in the view template, 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 simple stuff - and by doing it this way, we ensure that our markup for the quote is exactly as we intended it.

Installing & Configuring the New Block

So, if you browse to the Dashboard and select Add Functionality, you should see that the new block is available to install under the heading "Downloaded and Ready to Install".

Concrete5 Module Development Tutorial: installing the new block
Your new block will appear under the heading "Downloaded and Ready to Install" - click install

(Note that the icon displayed alongside the new block type can be personalised; all I've done is create a simple 16x16px icon and call it icon.png.) You can now try adding a block; if you scroll to the bottom of the list of available blocks you should see the new block listed:

Concrete5 Module Development Tutorial: Adding a block
At the bottom of the list of available blocks, you'll notice our new block - *quote* is now available

Selecting the quote block gives you the form we've just created:

Concrete5 Module Development Tutorial: the add / edit form
This illustrates the form you'll get when adding a block of type *quote* - enter the content 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 something like the screenshot below.

Concrete5 Module Development Tutorial: the final result
The final result - a semantically correct quote

(I've applied some very basic styling, I'm sure you can do better!) This is an extremely simple example, and I&rsquo've left out certain things - such as validation - for brevity, but hopefully it's enough to give you an idea of how easy it is to create functionality like this and to get started developing 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