Howto create a frontend widget

  • Written by Tijs Verkoyen on Monday 19 September 2011
  • 8 comments

Because of the lack of documentation, and multiple people were asking how to execute custom code on the frontend, I wrote a brief howto on creating widgets on the frontend.

Widgets are items you can link to a block on a page. An example of a widget is a content-block. In this article we'll explain how to create your own widget that shows the last 5 tweets of your Twitter-account. We assume you have the basic fork installation covered.

Creating the widget

A widget can only live inside a module, so if you don't have a module yet, you should create one. Creating a module isn't that hard:

First we will insert a new module in the modules-table, which can be done by executing the query below

INSERT INTO modules(name, description, active) VALUES ('twitter', NULL, 'Y');

Use the translations-module to add the translation for your module name. In the example we will need to add an label with the reference code "Twitter". Once that is done, we should set the permissions

INSERT INTO groups_rights_modules(id, group_id, module) VALUES (NULL, 1, 'twitter');

All database stuff is done, so fire up your favourite PHP-editor.

Open up the default_www/frontend/modules-folder and create a new folder with the same name as the module you inserted into the database, in our example we create a folder called twitter.

Each module has the same structure in Fork, so we need to create some basic folder and files.

The configuration file

Each module must have a configuration file, which defines some options. The file looks the same across the modules, except for the class name.

The class name is always build like this: FrontendCamelCasedModuleNameConfig, in our example the class name will be FrontendTwitterConfig. You can copy/paste the code below.

<?php

/**
* This is the configuration-object
*
* @package
frontend
* @subpackage
twitter
*
* @author
Tijs Verkoyen
* @since
2.6
*/
final class FrontendTwitterConfig extends FrontendBaseConfig
{
}

?>

You can download the file at github.

Because our widget doesn't have any configuration stuff, it's just an empty class. The functionality is extended from the base config class.

The code for the widget

Of course, our widget will need some code to grab the tweets, or to authenticate if needed.

In a module we have different types of code: actions, ajax, model, widgets. For this how-to, we just need widgets, so we will create a folder called widgets in the module.

The name I choose for the widget is stream. I choose this name because in the future we can expand the module with new widgets, actions, ... So, the code for a widget is placed in a file with the same name as the widget. So create a file stream.php

Copy/paste the code below. The code is documented inline.

<?php

/**
* This is a widget with the twitter-stream
* It will show do the oAuth-dance if no settings are stored. If the settings are stored it will grab the last
*
* @package
frontend
* @subpackage
twitter
*
* @author
Tijs Verkoyen
* @since
2.6
*/
class FrontendTwitterWidgetStream extends FrontendBaseWidget
{
/**
* The number of tweets to show
*
* @var int
*/
const NUMBER_OF_TWEETS = 5;


/**
* An array that will hold all tweets we grabbed from Twitter.
*
* @var array
*/
private $tweets = array();


/**
* Execute the extra
*
* @return void
*/
public function execute()
{
// call parent
parent::execute();

// load template
$this->loadTemplate();

// parse
$this->parse();
}


/**
* Get the data from Twitter
* This method is only executed if the template isn't cached
*
* @return void
*/
private function getData()
{
// for now we only support one account, in a later stadium we could implement multiple account.
$id = 1;

// init the application-variabled, alter these keys if you want to user your own application
$consumerKey = 'K99h85EM3twFkqoRwvWRKw';
$consumerSecret = 'tVQa9QYJiJifhWkhRZaGKSIaf1Keb4WvmmFloa6ClY';

// grab the oAuth-tokens from the settings
$oAuthToken = FrontendModel::getModuleSetting('twitter', 'oauth_token_' . $id);
$oAuthTokenSecret = FrontendModel::getModuleSetting('twitter', 'oauth_token_secret_' . $id);

// grab the user if from the settings
$userId = FrontendModel::getModuleSetting('twitter', 'user_id_' . $id);

// require the Twitter-class
require_once PATH_LIBRARY . '/external/twitter.php';

// create instance
$twitter = new Twitter($consumerKey, $consumerSecret);

// if the tokens aren't available we will start the oAuth-dance
if($oAuthToken == '' || $oAuthTokenSecret == '')
{
// build url to the current page
$url = SITE_URL . '/' . $this->URL->getQueryString();
$chunks = explode('?', $url, 2);
$url = $chunks[0];

// check if we are in th second part of the authorization
if(isset($_GET['oauth_token']) && isset($_GET['oauth_verifier']))
{
// get tokens
$response = $twitter->oAuthAccessToken($_GET['oauth_token'], $_GET['oauth_verifier']);

// store the tokens in the settings
FrontendModel::setModuleSetting('twitter', 'oauth_token_' . $id, $response['oauth_token']);
FrontendModel::setModuleSetting('twitter', 'oauth_token_secret_' . $id, $response['oauth_token_secret']);

// store the user-id in the settings
FrontendModel::setModuleSetting('twitter', 'user_id_' . $id, $response['user_id']);

// redirect to the current page, at this point the oAuth-dance is finished
SpoonHTTP::redirect($url);
}

// request a token
$response = $twitter->oAuthRequestToken($url);

// let the user authorize our widget
$twitter->oAuthAuthorize($response['oauth_token']);
}

else
{
// set tokens
$twitter->setOAuthToken($oAuthToken);
$twitter->setOAuthTokenSecret($oAuthTokenSecret);

// grab tweets
$this->tweets = $twitter->statusesUserTimeline($userId, null);
}
}


/**
* Parse
*
* @return void
*/
private function parse()
{
// we will cache this widget for 10 minutes, so we don't have to call Twitter each time a visitor enters a page with this widget.
$this->tpl->cache(FRONTEND_LANGUAGE . '_twitterWidgetStreamCache', (10 * 60 * 60));

// if the widget isn't cached, we should grab the data from Twitter
if(!$this->tpl->isCached(FRONTEND_LANGUAGE . '_twitterWidgetStreamCache'))
{
// get data from Twitter
$this->getData();

// init var
$tweets = array();

// build nice array which can be used in the template-engine
foreach($this->tweets as $tweet)
{
// we don't want to show @replies, so skip tweets that have in_reply_to_user_id set.
if($tweet['in_reply_to_user_id'] != '') continue;

// init var
$item = array();

// add values we need
$item['user_name'] = $tweet['user']['name'];
$item['url'] = 'http://twitter.com/' . $tweet['user']['screen_name'] . '/status/' . $tweet['id'];
$item['text'] = $tweet['text'];
$item['created_at'] = strtotime($tweet['created_at']);

// add the tweet
$tweets[] = $item;
}

// get the numbers
$this->tpl->assign('widgetTwitterStream', array_slice($tweets, 0, self::NUMBER_OF_TWEETS));
}
}
}

?>

You can download the file at github.

As you read through the code you'll see that we use a Twitter-class. Download this class here and save it under /library/external/twitter.php. We use this class to communicate with Twitter.

Just as in the config file, the name of the class has some logic: FrontendCamelCasedModuleNameWidgetCamelCasedWidgetName, for our widget this will be: FrontendTwitterWidgetStream.

The layout

Ok, the code is handled, now we need some layout. The layout folder contains the files that will be used to display what our code generates. Create a folder layout/widgets/ in the module.

Just like for the code, create a file with the same name as the widget, in our example: stream.tpl

Copy/paste the code below. The code is documented inline.

{* We use the templates caching-feature as a way to limit the calls to the Twitter API *}
{cache:{$LANGUAGE}_twitterWidgetStreamCache}

{* Only show the stream if there are tweets *}
{option:widgetTwitterStream}
<ul>

{* Loop the tweets *}
{iteration:widgetTwitterStream}

{* By using the cleanupplaintext-modifier the links in the tweet will be parsed. *}
{$widgetTwitterStream.text|cleanupplaintext}
<p class="date">
<a href="{$widgetTwitterStream.url}">
<time datetime="{$widgetTwitterStream.created_at|date:'Y-m-dTH:i:s'}" pubdate>
{$widgetTwitterStream.created_at|timeago}
</time>
</a>
</p>,

{/iteration:widgetTwitterStream}

{/option:widgetTwitterStream}

{/cache:{$LANGUAGE}_twitterWidgetStreamCache}

You can download the file at github.

As you can see, we have the cache-tags in place with the same name ({$LANGUAGE}_twitterWidgetStreamCache) we used in the code. This means the output will be cached. If there are tweets, we will loop them and parse them in an unordered list. Also, some meta-data is parsed.

Inserting the extra

An extra is the item that can be linked to a block on a page. For the user to be able to choose our widget in the block dropdown menu, we need to add it into the pages_extras.

INSERT INTO pages_extras(id, module, type, label, action, data, hidden, sequence) VALUES (NULL, 'twitter', 'widget', 'Stream', 'stream', NULL, 'N', '10000');

The action field needs to be the name of the widget, stream in our example.

Linking your widget

Go to the pages-modules and edit the page on which you would like the widget to appear, and link the widget to the correct place.

In the dialog, select widget as type, choose the module (Twitter) you created in the second dropdownmenu. In the third dropdownmenu you can select your newly created widget (Stream).

Once you save the page, the widget is linked. If you open up the page you will be redirect to Twitter to authorize the application (the oAuth-dance), if you authorized the application, you will see your tweets on the page.

That's all folks, enjoy your widgets!

Comments

Koen Vinken wrote 12 years ago

Tijs,

Thanks for this tutorial. To make it little easier, I simply wrote a little script that reads out an array. The twitter stuff is also great though.

I get how these widgets work now, which is great. But is there also a howto incoming on writing a custom backend to a module/widget? Because the standard modules (blog, faq, ...) won't always be enough.

Tijs wrote 12 years ago

Koen, there isn't a lot of documentation yet, we are aware that this is a big issue. But we have all day jobs that require a lot of dedication to.

For the moment I can only advice you to look at the existing modules.

Koen wrote 12 years ago

No problem. I'll see what I can figure out by looking at the modules. Thx for the reply.

Koen Vinken wrote 12 years ago

Hmm, can't seem to figure this out.

In the pages-module, when I link the Twitter widget to a block, the name of the module reads "($lblPagesTwitter)" and the widget is set to "($lblPagesStream)". Not very clean. Any idea where I could've made a mistake?

mlitn wrote 12 years ago

In http://<yoursite>/private/<language>/locale/add you should create these 2 translations.

--

Reference code: Twitter (this is thing you're seeing now)

Translation: Twitter (this is the translation you want)

Language: <whatever language you want the translation for>

Application: Backend (this translation is shown in the backend)

Module: Core (you're building a Twitter-module, but these 2 translations will be shown in the pages-module, so make it a site-wide "Core" translation)

Type: label (it's just a simple label - see the $lbl reference)

--

Reference code: Stream

Translation: Stream

Language: <whatever language you want the translation for>

Application: Backend

Module: Core

Type: label

Koen Vinken wrote 12 years ago

Awesome, looks much better now. Thanks a lot!

remi brouard

remi brouard wrote 11 years ago

This documentation may need an update.

In fork 3.x the database structure is quite different.

Is there an up to date tutorial ?

Gaurav

Gaurav wrote 10 years ago

nice code ...............