The Joomla! Community Magazine™

Joomla! 3.0 Extension Development Series: Admin Configuration and Code Cleanup

Written by | Wednesday, 01 May 2013 00:00 | Published in 2013 May
Hi! And welcome to the final code portion of our development series. If you have followed this series from start to finish you have now walked through the code development for creating a native Joomla! 3.x series extension. It’s been a long process but hopefully one that you’ve found both rewarding and informative. I’ve enjoyed the opportunity to share tips and tricks of Joomla extension development with you as we’ve gone through these past articles. In this final development article we are going to explore a few final concepts, perform a bit of clean up and discuss additional features which could be added in the future. I invite you now to get ready as we dig into the final article regarding Joomla 3.x component development.

  Step 0: Make Coffee

Yes, you are correct. As with each development article previously we are going to begin with a fresh cup of coffee. We’ll start getting our minds ready for writing code and begin focusing on the tasks at hand. This article will hopefully not be as detailed as some of the previous articles and so I trust will not require an extra cup of coffee and with luck you may make it through the majority of this tutorial before you reach the end of your coffee.


  Step 1: Writing the Admin Panel

The Joomla administrator panel has a variety of uses currently and there are several different ways in which it can be used most effectively. It could be suggested that the administrator panel can be a unique opportunity for each component to use as needed. We have written Lendr to be a front-side application and most of the functionality and purpose of the component is utilized by front-end users. Because this is the case we will use the administrator panel in Lendr purely for additional functionality and some basic option settings. By doing so we will not have duplicate code stored in both the administrator component models, views, and controllers, and yet we will continue to be able to demonstrate proper code structure for the administrator side. Hopefully by following this approach you will be able to understand when and how the administrator panel can be used most effectively.

The following files have been created for the administrator side code: a helper file, a controller, a model, a view, an access file, a config file, and our language file.

We will work through each of these files in turn starting with the primary entry point: lendr.php.

joomla_root/administrator/components/com_lendr/lendr.php

<?php // No direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
//load classes
JLoader::registerPrefix('Lendr', JPATH_COMPONENT_ADMINISTRATOR);
//Load plugins
JPluginHelper::importPlugin('lendr');
 
//application
$app = JFactory::getApplication();
 
// Require specific controller if requested
$controller = $app->input->get('controller','display');
// Create the controller
$classname  = 'LendrControllers'.ucwords($controller);
$controller = new $classname();
 
// Perform the Request task
$controller->execute();
              
This code is quite similar to the front end entry point and mainly serves to direct traffic through the appropriate controller.

joomla_root/administrator/components/com_lendr/controllers/display.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrControllersDisplay extends JControllerBase
{
  public function execute()
  {
    // Get the application
    $app = $this->getApplication();
 
    // Get the document object.
    $document     = JFactory::getDocument();
 
    $viewName     = $app->input->getWord('view', 'statistics');
    $viewFormat   = $document->getType();
    $layoutName   = $app->input->getWord('layout', 'default');
    $app->input->set('view', $viewName);
 
    // Register the layout paths for the view
    $paths = new SplPriorityQueue;
    $paths->insert(JPATH_COMPONENT . '/views/' . $viewName . '/tmpl', 'normal');
 
    $viewClass  = 'LendrViews' . ucfirst($viewName) . ucfirst($viewFormat);
    $modelClass = 'LendrModels' . ucfirst($viewName);
    $view = new $viewClass(new $modelClass, $paths);
    $view->setLayout($layoutName);
    // Render our view.
    echo $view->render();
 
    return true;
  }
}

You should notice right away the similarities between this controller and the default frontend controller. We are performing similar tasks with both. In this case we are setting the default view to be different than we did in the front end.

Next, we’ll examine the html.php folder located within the default view (as noted previously the default view is labeled statistics).

/joomla_root/administrator/components/com_lendr/views/statistics/html.php

>?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrViewsStatisticsHtml extends JViewHtml
{
  function render()
  {
    $app = JFactory::getApplication();
   
    //retrieve task list from model
    $model = new LendrModelsStatistics();
    $this->stats = $model->getStats();
    $this->addToolbar();
    //display
    return parent::render();
  } 
    /**
     * Add the page title and toolbar.
     *
     * @since   1.6
     */
    protected function addToolbar()
    {
        $canDo  = LendrHelpersLendr::getActions();
        // Get the toolbar object instance
        $bar = JToolBar::getInstance('toolbar');
        JToolbarHelper::title(JText::_('COM_LENDR_STATISTICS'));
               
        if ($canDo->get('core.admin'))
        {
            JToolbarHelper::preferences('com_lendr');
        }
    }
}
            

This file has several new elements so we’ll walk through it a bit more indepth. First notice that we are inheriting the JViewHtml class. This provides us with some basic functionality which you can find if you look at JViewHtml class directly within the Joomla libraries folder. Then we have the typical call to our model to load the specific data we need for our view.

Next we have a new line. The addToolbar function is located within this same file and is typically used to add elements to the top toolbar within the component on the administrator side. Because we are only doing a basic admin panel with limited functionality we will only have one item on our toolbar.

Lastly we render the view, just as we did on the front side.

The addToolbar function as discussed previously adds buttons to the sub-navigation top toolbar within the component. Because we do not have a full set of views and functionality that we are incorporating into our admin panel example we will have only one button in our case. The first thing we will do is to create an instance of the standard Joomla toolbar class and then assign our title and buttons to this instance.

Notice our language file is being used for all language strings. If we wanted an image to appear next to our component title we would simply pass a second reference when defining the title line.

Note: Remember in previous articles when viewing the admin we had an error being displayed? That’s because we were not setting a page title and the Isis template was expecting one.

In this function we also call our helper class. Now is a good time to review what that helper class does and how it’s used. This helper class contains a few useful functions for use throughout the component administration.

Note: Note: It should be noted that we never included this helper directly but rather the Joomla auto-loader (as defined in the main lendr.php file) has found the file automatically based on our naming conventions and loaded it when we made the call.

joomla_root/administrator/components/com_lendr/helpers/lendr.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_lendr
 *
 * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('_JEXEC') or die;
/**
 * Lendr component helper.
 *
 * @package     Joomla.Administrator
 * @subpackage  com_lendr
 * @since       1.6
 */
class LendrHelpersLendr
{
  public static $extension = 'com_lendr';
  /**
   * @return  JObject
   */
  public static function getActions()
  {
    $user = JFactory::getUser();
    $result = new JObject;
    $assetName = 'com_lendr';
    $level = 'component';
    $actions = JAccess::getActions('com_lendr', $level);
    foreach ($actions as $action)
    {
      $result->set($action->name, $user->authorise($action->name, $assetName));
    }
    return $result;
  }
}
          

In this helper we have one function right now. The getActions function will use the access.xml file stored in the administrator root of the component. This is a nice bit of functionality that simply loads an object with the values found in the XML.

Let’s take a minute and look at the access.xml file and see how it relates to the functionality that will be used throughout the component.

joomla_root/administrator/components/com_lendr/access.xml

<?xml version="1.0" encoding="utf-8"?>
<access component="com_lendr">
  <section name="component">
    <action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
  </section>
</access>

For the purpose of this tutorial we have an extremely minimal access file. Basically we are merely looking for admin privileges or not. We are going to use the admin level access for determining whether or not the user has the ability to view the options in the admin component sub menu (the point we started at previously in this tutorial). Many more sections and actions could be added here and custom actions added as well. This remains the same as what resides in Joomla 2.5 and it is recommend reviewing the specifics in those tutorials if interested.

Note: You can view the Joomla 2.5 access information in the associated http://docs.joomla.org tutorial

This brings us to the second part of this step. The component options.

Component Options

The options for our component can be found through the options button on the top toolbar located within the Lendr component. This button is the one we previously looked at in the preceding part of the html.php file (above). The line inside the canDo access check will add a preferences button to the toolbar. This preferences is also known as the global configuration options for the component. As you are probably aware from other components all preference buttons when clicked will direct the user to the com_config component and the corresponding component view.

The data used for this view (when you navigate to the com_config page for the Lendr component) is pulled from one specific file. The config.xml file also stored within the root of the lendr component on the administrator side. Let’s look at that file now.

joomla_root/administrator/components/com_lendr/config.xml
<?xml version="1.0" encoding="utf-8"?>
<config>
  <fieldset name="component"
    label="COM_LENDR_OPTIONS"
    description="COM_LENDR_OPTIONS_DESC"
  >
    <field name="required_account" type="radio"
      default="0"
      class="btn-group"
      label="COM_LENDR_REQUIRED_ACCOUNT"
      description="COM_LENDR_REQUIRED_ACCOUNT_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
    <field name="new_reviews" type="radio"
      default="1"
      class="btn-group"
      label="COM_LENDR_ALLOW_NEW_REVIEWS"
      description="COM_LENDR_ALLOW_NEW_REVIEWS_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
    <field name="new_wishlists" type="radio"
      default="1"
      class="btn-group"
      label="COM_LENDR_ALLOW_NEW_WISHLISTS"
      description="COM_LENDR_ALLOW_NEW_WISHLISTS_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
    <field name="new_waitlists" type="radio"
      default="1"
      class="btn-group"
      label="COM_LENDR_ALLOW_NEW_WAITLISTS"
      description="COM_LENDR_ALLOW_NEW_WAITLISTS_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
  </fieldset>
  <fieldset name="permissions"
    description="JCONFIG_PERMISSIONS_DESC"
    label="JCONFIG_PERMISSIONS_LABEL"
  >
    <field name="rules" type="rules"
      component="com_lendr"
      filter="rules"
      validate="rules"
      label="JCONFIG_PERMISSIONS_LABEL"
      section="component" />
  </fieldset>
</config>

In this file you will see we have defined to fieldsets. These fieldsets correspond to the tabs located in the admin side when viewing the com_config options. The first fieldset displays the specific parameters we wish to allow to be configured by the admin users, and the second fieldset deals with the ability to see the options button.

Note: You can define as many unique fieldsets (tabs) as you wish within your component.

The following are important things to keep in mind when viewing and creating this file. First, you should use your language file to define all the appropriate strings. Secondly, you can, and should, define classes for your fields so they use the appropriate styles. You can define multiple types of fields as you would use in other parts of Joomla, in this particular instance we are mainly utilizing radio buttons to toggle either a yes or no value.

Now we need to explore the code that’s been added to the front side which utilizes these new parameters.

We’ll look at two instances in particular. First, the option for a required_account. This option allows us to define whether or not a person should be logged in before being able to view Lendr. We implement this option in the following file.

joomla_root/components/com_lendr/controllers/default.php
// Line 11 - 19
 $params = JComponentHelper::getParams('com_lendr');
    if ($params->get('required_account') == 1) 
    {
        $user = JFactory::getUser();
        if ($user->get('guest'))
        {
            $app->redirect('index.php',JText::_('COM_LENDR_ACCOUNT_REQUIRED_MSG'));
        }
    }

We first get our parameters object using the Joomla Component Helper. Once we have those params we can then check to see if we are requiring the users to have an account. If we are then we check to see if they are logged in and if they are not we send them to the index.php with a message to login before viewing Lendr.

The other instance where we’ll look at the usage of a parameter value is in the following view layout.

joomla_root/components/com_lendr/views/book/tmpl/_entry.php
// Line 43 - 48
<?php if($this->params->get('new_wishlists') == 1 ): ?>
                      <li><a href="javascript:void(0);" onclick="addToWishlist('<?php echo $this->book->book_id; ?>');"><?php echo JText::_('COM_LENDR_ADD_WISHLIST'); ?></a></li>
                    <?php endif; ?>
                    <?php if($this->params->get('new_reviews') == 1 ): ?>
                      <li><a href="#newReviewModal" data-toggle="modal"><?php echo JText::_('COM_LENDR_WRITE_REVIEW'); ?></a></li>
                    <?php endif; ?>

In this check we follow the same basic principle that we followed in the controller. The only thing worth noting is the call to the Joomla Component Helper to set the params object is done in the html.php renderer rather than inline with where it’s utilized, this can be seen by the fact that we are calling $this->params in the view.


  Step 2: Code Cleanup

There are two aspects of code clean up we are going to review quickly here. First we need to add the ability to delete objects and secondly we need to have a list view to display all books in the system.

Deletions

Deleting objects can be done in a variety of ways. In one case we are not going to truly delete the data from the database (although this can be done easily enough) and then in a second case we will actually delete the data completely. First, often it is helpful to hide the information from displaying without actually deleting the data from the site. This allows us to “restore” an item should we have accidentally deleted something. The quickest and most effective way in Lendr to soft delete an item is to simply set the published variable to 0 (zero). In some other components a published of 0 may not imply a soft delete, and in those cases it is more common to see a -1 set as the published value. In our specific use case we do not use the published = 0 for any specific functionality and thus by setting to 0 we are effectively soft deleting the item from displaying.

joomla_root/components/com_lendr/controllers/delete.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrControllersDelete extends JControllerBase
{
  public function execute()
  {
    $app = JFactory::getApplication();
    $return = array("success"=>false);
    
    $type = $app->input->get('type','waitlist');
   
    $modelName = 'LendrModels'.ucfirst($type);    
    $model = new $modelName();
    if ( $row = $model->delete() )
    {
      $return['success'] = true;
      $return['msg'] = JText::_('COM_LENDR_BOOK_DELETE_SUCCESS');
    }else{
      $return['msg'] = JText::_('COM_LENDR_BOOK_DELETE_FAILURE');
    }
    echo json_encode($return);
  }
}

In our delete controller (because every controller is a single action) we have the function that will pass the data on to the appropriate model for processing. Here we configure the model name based on a type value based through the JInput variable. The model is where we will actually delete the item.

joomla_root/components/com_lendr/models/book.php

 /**
  * Delete a book
  * @param int      ID of the book to delete
  * @return boolean True if successfully deleted
  */
  public function delete($id = null)
  {
    $app  = JFactory::getApplication();
    $id   = $id ? $id : $app->input->get('book_id');
    $book = JTable::getInstance('Book','Table');
    $book->load($id);
    $book->published = 0;
    if($book->store()) 
    {
      return true;
    } else {
      return false;
    }
  }

The deleting of a book is one example where we are soft deleting instead of permanently removing an object. The code is quite simple as shown above. We will locate the id of the book we wish to delete, we will then get an instance of the Book table and load the appropriate row. Once we have the row loaded we can easily set the published status to 0 and then store the result. If the row is stored successfully we will return a true value to the javascript, or if the store fails we will return a false.

The second place we will look at for deleting an item is located in the waitlist model.

joomla_root/components/com_lendr/models/waitlist.php

/**
  * Delete a book from a waitlist
  * @param int      ID of the book to delete
  * @return boolean True if successfully deleted
  */
  public function delete($id = null)
  {
    $app  = JFactory::getApplication();
    $id   = $id ? $id : $app->input->get('waitlist_id');
    if (!$id)
    {
      if ($book_id = $app->input->get('book_id')) 
      {
        $db = JFactory::getDbo();
        $user = JFactory::getUser();
        $query = $db->getQuery(true);
        $query->delete()
            ->from('#__lendr_waitlists')
            ->where('user_id = ' . $user->id)
            ->where('book_id = ' . $book_id);
        $db->setQuery($query);
        if($db->query()) {
          return true;
        }
      } 
    } else {
      $waitlist = JTable::getInstance('Waitlist','Table');
      $waitlist->load($id);
      if ($waitlist->delete()) 
      {
        return true;
      }      
    }
    return false;
  }

In this example we are actually hard deleting the row from the database table. The reason we do so in this case is because there does not exist a published column on this data and thus the option to delete is considered appropriate.

As with the book deletion example earlier it is good to see that if we have a specific waitlist ID we can load that particular object and delete it directly. If however we do not have the specific waitlist ID we can look up the necessary row using the book ID and the user ID of the person and then deleting the associated row.

In Lendr we handle deleting books through AJAX calls and as a result we want to automatically remove the associated row from the page we are viewing when the delete button is clicked. Here is the javascript used to handle the AJAX call and subsequent removal of the row.

joomla_root/components/com_lendr/assets/js/lendr.js

function deleteBook(book_id,type) 
{
  jQuery.ajax({
    url:'index.php?option=com_lendr&controller=delete&format=raw&tmpl=component',
    type:'POST',
    data: 'book_id='+book_id+'&type='+type,
    dataType: 'JSON',
    success:function(data)
    {
      alert(data.msg);
      if(data.success)
      {
        jQuery("tr#bookRow"+book_id).hide();
      }
    }
  });
}

Here we pass the book_id as well as the type - the type is important for use in the delete controller as we saw previously (remember: this is how we routed the task to the appropriate model). We then will display the resulting alert message generated by the delete controller and if the deletion is successful we will remove the associated row from the list view. We remove the row by merely hiding it via jQuery. The ID of the table row has been defined by bookRow and the ID of that particular book (which is a unique identifier).

Book List View

The list view for all books is the other item we need to clean up. This is pretty straightforward and not much code required to do so. First we need to modify the html.php view class for the books. This can be done by updating as follows.

joomla_root/components/com_lendr/views/book/html.php
// Lines 8 - 19
    $layout = $this->getLayout();
    $this->params = JComponentHelper::getParams('com_lendr');
    //retrieve task list from model
    $model = new LendrModelsBook();
    if($layout == 'list')
    {
        $this->books = $model->listItems();
        $this->_bookListView = LendrHelpersView::load('Book','_entry','phtml');
    } else {
  …
          

Here we have added a call to get the layout variable and then we check to see which view we are loading. If we are in the list view then we want to display a list of all the available items otherwise we’ll load the view the same way as before.

Next we’ll add a new list layout to display the books.

joomla_root/components/com_lendr/views/book/tmpl/list.php
<table cellpadding="0" cellspacing="0" width="100%" class="table table-striped">
  <thead>
    <tr>
      <th><?php echo JText::_('COM_LENDR_DETAILS'); ?></th>
      <th><?php echo JText::_('COM_LENDR_STATUS'); ?></th>
      <th><?php echo JText::_('COM_LENDR_ACTIONS'); ?></th>
    </tr>
  </thead>
  <tbody id="book-list">
    <?php for($i=0, $n = count($this->books);$i<$n;$i++) { 
            $this->_bookListView->book = $this->books[$i];
            $this->_bookListView->type = 'book';
            echo $this->_bookListView->render();
    } ?>
  </tbody>
</table>
          

You should notice the similarity between this view and the library views we have written previously. The only difference is we are now loading all books in the system instead of those books associated with a particular library.


  Step 3: Menus and File Cleanup

There are two parts to this step we are going to review. The first part involves creating and defining possible menu links entry points to be used when creating menu links in the administrator panel. The second part will consist of some minor removal of unnecessary files and/or functions.

Create Menu Links

It’s often necessary to create a menu link which can be added to a menu link to be displayed on the front side. Menu links are added through the administrator menu manager. When adding a new menu item you select the type of menu link you wish to add. We need to define those views we want to display in the menu type modal selection. There are two layouts we wish to add to the menu type options. First, we want to allow users to be able to link to a list of all profiles and secondly we want to be able to link to a list of all books.

Menu links are based on associated metadata.xml files located within the various view folders within the component. Below is the example for the profile layout.

joomla_root/components/com_lendr/views/profile/tmpl/list.xml
<?xml version="1.0" encoding="utf-8"?>
<metadata>
  <layout title="COM_LENDR_PROFILE_LIST">
    <message><![CDATA[COM_LENDR_PROFILE_LIST_DESC]]></message>
  </layout>
</metadata>
          
Note: The name of this file should coordinate with the name of the layout file in the same directory.

Notice that because we are linking to a layout we define a <layout> object if we were to link to a view instead then we would create a <view> object. The language strings are stored in the admin system language file.

The system language file for Lendr is below.

joomla_root/administrator/languages/en-GB/en-GB.com_lendr.sys.ini
COM_LENDR = Lendr
COM_LENDR_SETTINGS = Lendr Settings
COM_LENDR_PROFILE_LIST = Profile List
COM_LENDR_PROFILE_LIST_DESC = Display list of all profiles
COM_LENDR_BOOK_LIST = Book List
COM_LENDR_BOOK_LIST_DESC = Display list of all books

These strings are used for those areas that might refer to Lendr from outside of the actual Lendr component. This means these strings are always loaded throughout Joomla and not only when inside index.php?option=com_lendr. The menu type modal is one example when these strings are used. The administrator component menu is a second example.

By adding this metadata.xml file to the appropriate folder we can now view this layout in the menu section list. The metadata file can also contain advanced options for adding additional parameters. (e.g. Adding the ability to select a particular book or profile for the menu link to be associated with).

Remove unnecessary files and functions

We’ve done a fairly good job throughout this tutorial not adding unnecessary functions or files so there are relatively few things to remove. There are several controllers on the front side to remove that were never implemented in the scope of this tutorial and there were a couple of views that we also cleaned up from a layout perspective. For the most part this step is listed here mainly to serve as a reminder for future development to always be sure to release clean code.

It is important for security, package distribution size, and general clean coding standards to be sure there are no extra files, folders, or functions existing that might pose a problem in the future. Remember it’s always a good idea to code for someone else. This means documenting your code correctly and removing anything that’s not necessary and might be confusing in the future.


  Step 4: Additional Suggestions

The following are a few additional suggestions which could be explored in future articles as add-ons to this series should there be interest in any or all of them. Some of these ideas are brand new cutting edge opportunities to provide new features to a Joomla 3.x component. If you are interested in seeing an article on one or more of these ideas, leave a message in the comments.

Tags

Tags is a new feature from Joomla 3.1. With tags there is the opportunity to assign tags to books and then be able to search and group books by those tags. Tagging will also allow for books to be assigned to multiple tags for each filtering and sorting.

Categories

Categories will provide the opportunity to add books to specific categories. This allows for large scale groupings and a demonstration of using the Joomla category structure and demonstrate the proper method for using Joomla categories.

Web Services / JSON

Using a basic web services model we can demonstrate methods for retrieving data directly from the Lendr component without utilizing the standard component views and layouts. Retrieve the data in a JSON feed for use in other systems.


Download

Download the component as it exists to this point from the Github repository.

Download
Next Article: Maintaining Releases

As I mentioned this article represents the final bit of serious coding in the series, however in the next release we’ll discuss some ideas for maintaining releases and supporting the component. We’ll explore GitHub and distribution methods as well as version numbering conventions, release schedules and more.

Read 30380 times
Tagged under Developers, English
David Hurley

David Hurley

David is the Community Manager for Joomla! and member of the Production Leadership Team. He is one of the CMS maintainers and also a framework maintainer. He is co-founder at WebSpark and has produced several well-known extensions for Joomla! CMS (CRMery, Banter, JForce), as well as an open source standalone CRM Cobalt.