9 min read

SilverStripe 2.4 Module Extension, Themes, and Widgets: Beginner’s Guide: RAW

SilverStripe 2.4 Module Extension, Themes, and Widgets: Beginner's Guide: RAW

Create smashing SilverStripe applications by extending modules, creating themes, and adding widgets

Why can’t we simply use templates and $Content to accomplish the task?

  • Widgets and short codes generally don’t display their information directly like a placeholder does
  • They can be used to fetch external information for you—we’ll use Google and Facebook services in our examples
  • Additionally they can aggregate internal information—for example displaying a tag cloud based on the key words you’ve added to pages or articles

Widget or short code?

Both can add more dynamic and/or complex content to the page than the regular fields. What’s the difference?

Widgets are specialized content areas that can be dynamically dragged and dropped in a predefined area on a page in the CMS. You can’t insert a widget into a rich-text editor field, it needs to be inserted elsewhere to a template. Additionally widgets can be customised from within the CMS.

Short codes are self-defined tags in squared brackets that are entered anywhere in a content or rich-text area. Configuration is done through parameters, which are much like attributes in HTML.

So the main difference is where you want to use the advanced content.

Creating our own widget

Let’s create our first widget to see how it works. The result of this section should look like this:

Time for action – embracing Facebook

Facebook is probably the most important communication and publicity medium in the world at the moment. Our website is no exception and we want to publish the latest news on both our site and Facebook, but we definitely don’t want to do that manually.

You can either transmit information from your website to Facebook or you can grab information off Facebook and put it into your website. We’ll use the latter approach, so let’s hack away:

    1. In the Page class add a relation to the WidgetArea class and make it available in the CMS:

public static $has_one = array(
‘SideBar’ => ‘WidgetArea’,
);
public function getCMSFields(){
$fields = parent::getCMSFields();
$fields->addFieldToTab(
‘Root.Content.Widgets’,
new WidgetAreaEditor(‘SideBar’)
);
return $fields;
}


  1. Add $SideBar to templates/Layout/Page.ss in the theme directory, wrapping it inside another element for styling later on (the first and third line are already in the template, they are simply there for context):

    $Form
    <aside id=”sidebar”>$SideBar</aside>
    </section>

    
    

    <aside> is one of the new HTML5 tags. It’s intended for content that is only “tangentially” related to the page’s main content. For a detailed description see the official documentation at http://www.w3.org/TR/html-markup/aside.html.

  2. Create the widget folder in the base directory. We’ll simply call it widget_facebookfeed/.
  3. Inside that folder, create an empty _config.php file.
  4. Additionally, create the folders code/ and templates/.
  5. Add the following PHP class—you’ll know the filename and where to store it by now. The Controller’s comments haven’t been stripped this time, but are included to encourage best practice and provide a meaningful example:

    <?php
    class FacebookFeedWidget extends Widget {
    public static $db = array(
    ‘Identifier’ => ‘Varchar(64)’,
    ‘Limit’ => ‘Int’,
    );
    public static $defaults = array(
    ‘Limit’ => 1,
    );
    public static $cmsTitle = ‘Facebook Messages’;
    public static $description = ‘A list of the most recent
    Facebook messages’;
    public function getCMSFields(){
    return new FieldSet(
    new TextField(
    ‘Identifier’,
    ‘Identifier of the Facebook account to display’
    ),
    new NumericField(
    ‘Limit’,
    ‘Maximum number of messages to display’
    )
    );
    }
    public function Feeds(){
    /**
    * URL for fetching the information,
    * convert the returned JSON into an array.
    */
    $url = ‘http://graph.facebook.com/’ . $this->Identifier .
    ‘/feed?limit=’ . ($this->Limit + 5);
    $facebook = json_decode(file_get_contents($url), true);
    /**
    * Make sure we received some content,
    * create a warning in case of an error.
    */
    if(empty($facebook) || !isset($facebook[‘data’])){
    user_error(
    ‘Facebook message error or API changed’,
    E_USER_WARNING
    );
    return;
    }
    /**
    * Iterate over all messages and only fetch as many as needed.
    */
    $feeds = new DataObjectSet();
    $count = 0;
    foreach($facebook[‘data’] as $post){
    if($count >= $this->Limit){
    break;
    }
    /**
    * If no such messages exists, log a warning and exit.
    */
    if(!isset($post[‘from’][‘id’]) || !isset($post[‘id’] ||
    !isset($post[‘message’])){
    user_error(
    ‘Facebook detail error or API changed’,
    E_USER_WARNING
    );
    return;
    }
    /**
    * If the post is from the user itself and not someone
    * else, add the message and date to our feeds array.
    */
    if(strpos($post[‘id’], $post[‘from’][‘id’]) === 0){
    $posted = date_parse($post[‘created_time’]);
    $feeds->push(new ArrayData(array(
    ‘Message’ => DBField::create(
    ‘HTMLText’,
    nl2br($post[‘message’])
    ),
    ‘Posted’ => DBField::create(
    ‘SS_Datetime’,
    $posted[‘year’] . ‘-‘ .
    $posted[‘month’] . ‘-‘ .
    $posted[‘day’] . ‘ ‘ .
    $posted[‘hour’] . ‘:’ .
    $posted[‘minute’] . ‘:’ .
    $posted[‘second’]
    ),
    )));
    $count++;
    }
    }
    return $feeds;
    }

    
    
  6. Define the template, use the same filename as for the previous file, but make sure that you use the correct extension. So the file widget_facebookfeed/templates/FacebookFeedWidget.ss should look like this:

    <% if Limit == 0 %>
    <% else %>
    <div id=”facebookfeed” class=”rounded”>
    <h2>Latest Facebook Update<% if Limit == 1 %>
    <% else %>s<% end_if %></h2>
    <% control Feeds %>
    <p>
    $Message
    <small>$Posted.Nice</small>
    </p>
    <% if Last %><% else %><hr/><% end_if %>
    <% end_control %>
    </div>
    <% end_if %>

    
    
  7. Also create a file widget_facebookfeed/templates/WidgetHolder.ss with just this single line of content:

    $Content

    
    

    We won’t cover the CSS as it’s not relevant to our goal. You can either copy it from the final code provided or simply roll your own.

  8. Rebuild the database with /dev/build?flush=all.
  9. Log into /admin. On each page you should now have a Widgets tab that looks similar to the next screenshot. In this example, the widget has already been activated by clicking next to the title in the left-hand menu.

    If you have more than one widget installed, you can simply add and reorder all of them on each page by drag-and-drop. So even novice content editors can add useful and interesting features to the pages very easily.

  10. Enter the Facebook ID and change the number of messages to display, if you want to.
  11. Save and Publish the page.
  12. Reload the page in the frontend and you should see something similar to the screenshot at the beginning of this section.

    allow_url_fopen must be enabled for this to work, otherwise you’re not allowed to use remote objects such as local files. Due to security concerns it may be disabled, and you’ll get error messages if there’s a problem with this setting. For more details see http://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen.

What just happened?

Quite a lot happened, so let’s break it down into digestible pieces.

Widgets in general

Every widget is actually a module, although a small one, and limited in scope. The basic structure is the same: residing in the root folder, having a _config.php file (even if it’s empty) and containing folders for code, templates, and possibly also JavaScript or images. Nevertheless, a widget is limited to the sidebar, so it’s probably best described as an add-on. We’ll take a good look at its bigger brother, the module, a little later.

You’re not required to name the folder widget_*, but it’s a common practice and you should have a good reason for not sticking to it.

Common use cases for widgets include tag clouds, Twitter integration, showing a countdown, and so forth. If you want to see what others have been doing with widgets or you need some of that functionality, visit http://www.silverstripe.org/widgets/.

Keeping widgets simple
In general widgets should work with default settings and if there are additional settings they should be both simple and few in number. While we’ll be able to stick to the second part, we can’t provide meaningful default settings for a Facebook account. Still, keep this idea in mind and try to adhere to it where possible.

Facebook graph API

We won’t go into details of the Facebook Graph API, but it’s a powerful tool—we’ve just scratched the surface with our example. Looking at the URL http://graph.facebook.com/<username>/feed?limit=5 you only need to know that it fetches the last five items from the user’s feed, which consists of the wall posts (both by the user himself and others). <username> must obviously be replaced by the unique Facebook ID—either a number or an alias name the user selected. If you go to the user’s profile, you should be able to see it in the URL.

For example, SilverStripe Inc’s Facebook profile is located at https://www.facebook.com/pages/silverstripe/44641219945?ref=ts&v=wall—so the ID is 44641219945. That’s also what we’ve used for the example in the previous screenshot.

For more details on the Graph API see http://developers.facebook.com/docs/api.

Connecting pages and widgets

First we need to connect our pages and widgets in general. You’ll need to do this step whenever you want to use widgets.

You’ll need to do two things to make this connection:

  1. Reference the WidgetArea class in the base page’s Model and make it available in the CMS through getCMSFields().
  2. Secondly, we need to place the widget in our page.

$SideBar

You’re not required to call the widget placeholder $SideBar, but it’s a convention as widgets are normally displayed on a website’s sidebar. If you don’t have a good reason to do it otherwise, stick to it.

You’re not limited to a single sidebar
As we define the widget ourselves, we can also create more than one for some or all pages. Simply add and rename the $SideBar in both the View and Model with something else and you’re good to go. You can use multiple sidebars in the same region or totally different ones—for example creating header widgets and footer widgets. Also, take the name “sidebar” with a grain of salt, it can really have any shape you want.

What about the intro page?

Right. We’ve only added $SideBar to the standard templates/Layout/Page.ss. Shouldn’t we proceed and put the PHP code into ContentPage.php? We could, but if we wanted to add the widget to another page type, which we’ll create later, we’d have to copy the code. Not DRY, so let’s keep it in the general Page.php.

The intro page is a bit confusing right now. While you can add widgets in the backend, they can’t be displayed as the placeholder is missing in the template. To clean this up, let’s simply remove the Widget tab from the intro page. It’s not strictly required, but it prevents content authors from having a field in the CMS that does nothing visible on the website. To do this, simply extend the getCMSFields() in the IntroPage.php file, like this:

function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeFieldFromTab(‘Root.Content.Main’, ‘Content’);
$fields->removeFieldFromTab(‘Root.Content’, ‘Widgets’);
return $fields;
}


LEAVE A REPLY

Please enter your comment!
Please enter your name here