concrete5's rich user interface is one of the things that sets it apart from some of its open source competitors. With its emphasis on in-context editing, overlays, slide-down panes, AJAX form submission and the ability to add large amounts of content (including files) without ever leaving the page, concrete5 frequently behaves more like a desktop application than a typical web application. In this how-to, I'm going to show you how you can integrate a wide variety of rich JavaScript functionality into the front-end of your applications and blocks.
Note: this isn't meant to be the one tutorial to rule them all: this is merely an overview to show you some of the ways concrete5 integrates and works well with JavaScript, how you can start exploring further.
jQuery: concrete5's JavaScript Framework
Much of concrete5's interaction model would simply not be possible without jQuery. One of the most popular extensions to JavaScript available, jQuery relieves much of the incompatibility and verbosity that has typically plagued the scripting of complex web applications.
We didn't always use an intermediary JavaScript framework (and – gulp – for a little while we flirted with Prototype), but jQuery makes so much about complex scripting easier than it used to be. And even better: jQuery's plugin model has been used to create great additions to the core library, including effects libraries and new widgets. Several of these plugins are also included in concrete5 core.
An Overview of All jQuery Components Included in concrete 5.4.2
The following jQuery libraries included with concrete 5.4.2:
- jQuery Core (1.6.2)
- jQuery UI (1.8.14)
- jQuery Scroll-To (1.3.3)
- jQuery Rating (3.0)
- jQuery Metadata
- jQuery LiveUpdate
- jQuery Form (2.12)
- jQuery Cookie
- jQuery Color Picker
jQuery UI
The following jQuery UI components are included in jquery.ui.js.
- jQuery UI Draggable
- jQuery UI Droppable
- jQuery UI Selectable
- jQuery UI Sortable
- jQuery UI Accordion
- jQuery UI Slider
- jQuery UI Progressbar
- jQuery UI Effects
- jQuery UI Datepicker
How to include jQuery and its Components in your Block or Single Page
Single Pages
Including jQuery in your single pages or pages of a certain type is as easy as calling addHeaderItem in your page's controller method. Let's say you have a page accessed at
http://www.yoursite.com/index.php/your_special_page/
That means you have a controller here at
controllers/your_special_page.php
or
controllers/your_special_page/controller.php
And within that controller file, in your view() method, the following will include the jQuery UI library, its required stylesheet, the concrete5 dialog class (more on this in a moment) and its required stylesheet.
public function view() {
$html = Loader::helper('html');
$this->addHeaderItem($html->css('jquery.ui.css'));
$this->addHeaderItem($html->css('ccm.dialog.css'));
$this->addHeaderItem($html->javascript('jquery.ui.js'));
$this->addHeaderItem($html->javascript('ccm.dialog.js'));
}
The HtmlHelper class takes care of locating the correct file and printing out the proper tag. The Controller::addHeaderItem() function makes sure only one instance of a given header item is added, and is properly output in the HEAD tag of the page.
Blocks
Calling CSS and JavaScript in your blocks is even easier. If you'd like a particular block's view layer to require certain JavaScript and CSS, simply add it to the block controller's on_page_view() function:
public function on_page_view() {
$html = Loader::helper('html');
$this->addHeaderItem($html->css('jquery.ui.css'));
$this->addHeaderItem($html->css('ccm.dialog.css'));
$this->addHeaderItem($html->javascript('jquery.ui.js'));
$this->addHeaderItem($html->javascript('ccm.dialog.js'));
}
Note: this is not the block's view() function. on_page_view() runs before the page is rendered, ensuring that the CSS and JavaScript files are added in the HEAD tag.
Once jQuery and jQuery UI are included in your page (and jQuery is typically included in themes by default) you'll be able to use all the great functionality described at jQuery.com.
How to use the built-in concrete5 Dialog Class
The built-in concrete5 dialog class is responsible for rendering all modal and non-modal dialog windows (not the slide-downs) that are positioned in the middle of the screen. This library is not the same as the jQuery UI dialog class. It was created before jQuery UI Dialog, and as such, currently conflicts with it.
Example 1: Loading an external url into a dialog using on-link attributes
The easiest way of calling a dialog window is by specifying certain attributes in the link you wish to use to call the window, and then calling the .dialog() function on that link.
<a
dialog-title="This is the dialog title."
dialog-modal="false"
dialog-width="550"
dialog-height="380"
id="myDialog"
href="yourpage.php">This is the link</a>
Now, run the dialog function:
<script type="text/javascript">
$(function() {
$("#myDialog").dialog();
});
</script>
This will load yourpage.php via AJAX, create a new 550x380 dialog window with the proper title, make it non-modal, and load the content into it.
Example 2: Loading an external url into a dialog from within a JavaScript function
The same operation above can be run from within a JavaScript function
<script type="text/javascript">
loadMyDialog = function() {
jQuery.fn.dialog.open({
title: 'This is my title',
href: 'yourpage.php',
width: 550,
modal: false,
height: 380,
onClose: function() {
alert('This will fire when the dialog is closed.');
}
});
}
</script>
Simply run loadMyDialog() and the proper dialog will open. Note the "onClose" function that will run when the dialog is closed.
Example 3: Loading the contents of an on-page element into a dialog from within a JavaScript function
If you'd rather load a dialog and populate it with in-page content, you can do so using the syntax below:
<div id="myDialogContent" style="display: none">
This is the content of my dialog window.
</div>
<script type="text/javascript">
loadMyDialog = function() {
jQuery.fn.dialog.open({
title: 'This is my title',
element: '#myDialogContent',
width: 550,
modal: false,
height: 380,
onClose: function() {
alert('This will fire when the dialog is closed.');
}
});
}
</script>
AJAX
If you want to use jQuery to integrate some server-side processing into concrete5 (AJAX), it can be a little difficult to know where to start. There are basically two approaches available: 1) use a controller action and then exit from the action before rendering the page, or 2) use a tool script.
What's a tool script? A tool script is a simple PHP script either in your local tools/ directory or your block's tools/ directory. This script has no UI wrapped around it. It's lightweight, and simply loads a number of concrete5 objects and connects to the database. Otherwise, you're free to do what you wish inside it.
Using an Action for Server-Side Processing in a Block
From within the block's view layer, make the HREF of the link you wish to tie to the backend equal the value of an action method in your controller:
$action = $this->action('like');
This will create a link that will pass through to the block when the form is submitted, and then "action_like" action will be run.
Now, in HTML, we create the link:
<a href="<?=$action?>" id="myActionLink">Like!</a>
Now, in JavaScript, we make it so that the link in question submits to the backend:
$('#myActionLink').click(function() {
var url = $(this).attr('href');
$.get(url, function(r) {
alert('Success!');
});
return false;
});
Finally, the action_like() method in the block's controller processes, and then exits at the end.
Tools
Tools are another way to enable server-side processing. Using tools outside the block level is easy. For example, the JavaScript function ccm_dashboardRequestRemoteInformation runs every time the front page of the dashboard is hit:
ccm_dashboardRequestRemoteInformation = function() {
$.get(CCM_TOOLS_PATH + '/dashboard/get_remote_information');
}
This simply loads the "dashboard/get_remote_information.php" tool through AJAX. The tools path takes care of making sure the script in the correct location is loaded.
Loading a block-specific tool is nearly as easy. From within a block's view layer, the following will automatically load information from a tool found in the block's tools/ directory:
<?php
$th = Loader::helper('concrete/urls');
$bt = $b->getBlockTypeObject();
?>
<script type="text/javascript">
$(function() {
$.get('<?=$th->getBlockTypeToolsURL($bt)?>/load_information?>');
});
</script>
This will automatically find the "load_information.php" script within the block's tools directory, and load it silently. You can pass query string arguments to the script to obtain more functionality, and process them silently from within the script.
JSON
If you'd like to return JSON to your AJAX function, it's as easy as defining a simple PHP object within your tool or action function, instantiating the properties you want to pass back, and using the concrete5 JSON helper.
$obj = new stdClass;
$obj->favoriteColor = 'red';
$obj->name = 'Andrew';
$obj->isAwesome = true;
$js = Loader::helper('json');
print $js->encode($obj);
exit;
This will print out
{"favoriteColor":"red","name":"Andrew","isAwesome":true}
and exit, leaving you to use jQuery's $.getJSON function to parse the string into JavaScript objects that you can do with what you will.
Putting it All Together: Building a "Like This!" Block
Getting a feel for this functionality is hard without a concrete example. That's why I'm releasing a free "Likes This!" block, similar to one we use on concrete5.org. This is a very simple block: it allows logged-in users to say that they "like" a certain page. They are tied to the page, and a page attribute is also incremented to allow pages to be sorted easily by the number of likes that they have received.
This is the default text for those not logged in:
Logged in users receive the ability to like the page.
If they click the link, the result happens inline, dynamically populating the view area and highlighting the div.
Finally, you can view the full list of users who've favorited a page.
This block is available for free from the marketplace here:
http://www.concrete5.org/marketplace/addons/likes-this/
This block is a good demonstration of concrete5's JavaScript abilities, because it features:
- jQuery UI and concrete5's dialog functionality are included intelligently on page load
- The block view template intelligently references tools and controller actions.
- The block automatically includes JavaScript by including view.js in its directory. This JavaScript is used by the block view template.
- The dialog class is used to pull data in from a tools script, through AJAX.
- The response from the block's controller action is loaded into the page seamlessly.
- jQuery UI effects are used.
- Everything will degrade gracefully even when JavaScript is disabled.
Including the Proper Header Items
The following code in the block's controller file is responsible for including the dialog and jQuery UI code in the header.
public function on_page_view() {
$u = new User();
$html = Loader::helper('html');
$this->addHeaderItem($html->javascript('jquery.js'));
$this->addHeaderItem($html->javascript('ccm.dialog.js'));
$this->addHeaderItem($html->css('ccm.dialog.css'));
if ($u->isRegistered()) {
$this->addHeaderItem($html->javascript('jquery.ui.js'));
$this->addHeaderItem($html->css('jquery.ui.css'));
}
}
Notice, we only include the jQuery UI code if the user who is viewing the page is logged in. Why? Because user's who aren't logged in won't have the ability to "like" a page – so why should we slow their page loading down?
The Block View Template
The block view template is here:
<?php $userLikesThis = $controller->userLikesThis($u); ?>
<?php $c = Page::getCurrentPage(); ?>
<div class="ccm-block-likes-this-wrapper">
<p>
<? $action = $this->action('like'); ?>
<? if (!$userLikesThis) { ?><a href="<?=$action?>"
class="ccm-block-likes-this-link"><? } ?>
<img src="<?php echo $this->getBlockURL()."/images/heart.png"; ?>"
style="float: left; margin-right: 4px" />
<? if (!$userLikesThis) { ?></a><? } ?>
<? if ($count > 0) { ?>
<? $th = Loader::helper('concrete/urls');
$bt = $b->getBlockTypeObject();
?>
<a class="ccm-block-likes-this-link-list" target="_blank"
title="<?=t('View User List')?>"
href="<?=$th->getBlockTypeToolsURL($bt)?>/list?cID=<?=$c->getCollectionID()?>">
<?php if($count == 1) {
echo number_format($count);?> person likes this page.
<?php } elseif($count > 1) {
echo number_format($count); ?> people like this page.
<?php } ?>
</a>
<? } ?>
</p>
<p>
<?
if ($controller->userLikesThis($u)) { ?>
<strong><?=t('You like this!')?></strong>
<? } else if ($u->isRegistered()) { ?>
<a href="<?php echo $action;?>"
class="ccm-block-likes-this-link">I like it<?php echo ($count > 0?" too":"");?>!</a>
<? } else { ?>
<?=t('You must <a href="%s">sign in</a> to
mark this page as one you like.', $this->url('/login', 'forward', $c->getCollectionID()));?>
<? } ?>
</p>
</div>
The function of this template should be fairly self-explanatory. Basically, we don't include links to like the page if the user is not logged in, or if the user has already said they like the page.
Including JavaScript when the Block is Included
The "Likes This" includes view.js in its folder. By doing so, it ensures that the functions within the view.js file will be automatically included in the page when the block itself is. Here are its contents:
ccm_blockLikesThisSetupList = function() {
$('a.ccm-block-likes-this-link-list').each(function() {
$(this).unbind();
$(this).click(function() {
var url = $(this).attr('href');
jQuery.fn.dialog.open({
href: url,
modal: false,
title: $(this).attr('title')
});
return false;
});
});
}
$(function() {
$('a.ccm-block-likes-this-link').each(function() {
$(this).click(function() {
var url = $(this).attr('href') + '&ajax=1';
var elem = $(this).parent();
$.get(url, function(r) {
$('div.ccm-block-likes-this-wrapper').before(r).remove();
$('div.ccm-block-likes-this-wrapper').effect("highlight", {}, 800);
ccm_blockLikesThisSetupList();
});
return false;
});
});
ccm_blockLikesThisSetupList();
});
There is one function defined, and one block of code that automatically runs whenever this block is included in a page. The ccm_blockLikesThisSetupList function locates all links on the page with the ccm-block-likes-this-link class, and finds their URL, and loads them into a concrete5 dialog box. This uses the tools script referenced in the view template.
The automatically loaded event looks for all links with the class ccm-block-likes-this-link, and finds its URL. It appends &ajax=1 to the end of the link, which is then used in the action_like() function to separate those requests called through or AJAX or simply through a POST, and then uses jQuery's $.get() function to submit to the action_like() method defined in controller.php. Then the HTML rendered by the action_like() function is inserted into the page, allowing for an instant in-page update of the block itself. Finally, jQuery's highlight effect is used to inform the user that something has changed successfully when they say that they "like" a page.
Learn More
This is a quick introduction to using jQuery and JavaScript with concrete5 on the front-end, but hopefully it should pique your interest and help you start understanding what's possible with such a combination. Here are some links to further sites of interest