Joomla! 3.0 Bootstrapped Component

Interested in what Lendr can do? Check the features below.

Native Joomla! 3.0

Lendr is an extension written for the Joomla! CMS system and is a native component using the latest platform code available.

Responsive

Lendr allows you to use it on any device size. A fully responsive layout allows easy use on a mobile device, tablet or desktop computer.

Made With Bootstrap

Integration with Twitter Bootstrap allows Lendr to have a both a beautiful interface as well as standardized buttons and layouts.

Libraries

Add books to your library catalogs and keep track of your entire collection. Track information about all your books in one easy location.

Lending

Lend books to others easily. Track who has borrowed which book and request to be added to waitlists when books are unavailable.

Reviews

Share your thoughts, opinions and ratings about books you hold in your collection or that you have borrowed from another library.


The Purpose

Lendr is made available as a walk-through tutorial for Joomla! 3 component development.


Step 0: Make Coffee

Again, it is important to start your day and project off right. Get into a pattern, a rhythm and soon you’ll find your creativity, focus, and coding skills to be at the ready when you sit down to begin work. Don’t overlook the importance of being in the right frame of mind when you begin work.


Step 1: Write basic component outline for files needed

The first thing to do is to create a rough overview of the files, folders, database tables, and associated fields. For our component we will be creating the following system.

Component Details

Name:
Lendr
Component:
com_lendr
Description:
Lendr is a bootstrapped Joomla! 3.0 component which will allow users to create a profile, add their books to their library collection, view other users libraries, request a book to borrow, add books to a wishlist, and sign up to be on a waitlist for a particular book.

Basic Functions

  • User accounts / basic profiles
  • Books / Libraries for Users
  • Wishlists for books desired
  • Lending / Borrowing of a book
  • Requesting to borrow a book
  • Waitlists for a book already lent

Now we should write down our basic structure of files needed. This will not be a comprehensive list and will most definitely be modified as we progress through the process. However, having a starting outline will help to keep things somewhat on track. Here is the initial outline of key files needed for Lendr.

Basic Files Needed

Controllers Models Views Tables Misc.
Save
List
Add
Edit
Lend
Delete
Wish
Review
Request
Default
Book
Default
Library
Profile
Review
Waitlist
Wishlist
Book
Wishlist
Library
Profile
Waitlist
Review
Book
Wishlist
Library
Waitlist
Review
Install
Router
XML

Now that we have this written out and a rough outline we begin creating these files in our folder structure.


Step 2: Write the Database table files

We begin by creating the database table files. We store these in the table folder located in the frontside of our component. Please refer back to the first article in this series to recall how your local environment should be configured. We create each of the files we described in our outline. Below is one of these files.

/joomla_root/components/com_lendr/site/tables/book.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 

  class TableBook extends JTable
  {                      
    /**
    * Constructor
    *
    * @param object Database connector object
    */
    function __construct( &$db ) {
      parent::__construct('#__lendr_books', 'book_id', $db);
    }
  }
            
You can find the rest of the database table files (similar in structure) in our Github repository.

In our case the table file holds a single construct function. This function provides the table name associated with this JTable file and also defines the primary key field, book_id, in this file.

While creating these database tables it is appropriate to begin the process of creating the install.mysql.sql script which will be used when the component is installed through the Joomla! administrator panel. The start of that file is below:

/joomla_root/administrator/components/com_lendr/admin/install.mysql.sql
CREATE TABLE IF NOT EXISTS `#__lendr_books` (
  `book_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `isbn` varchar(255) DEFAULT NULL,
  `title` varchar(255) DEFAULT NULL,
  `summary` text DEFAULT NULL,
  `pages` varchar(55) DEFAULT NULL,
  `image` varchar(255) DEFAULT NULL,
  `publish_date` varchar(255) DEFAULT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  `lent` tinyint(2) DEFAULT NULL,
  `due_date` datetime NOT NULL,
  `lent_uid` varchar(255) DEFAULT NULL,
  `published` tinyint(2) DEFAULT 0,
  PRIMARY KEY (`book_id`)
);
            
You can find the rest of the install.mysql.sql file in our Github repository.

We will continue to add to this file throughout the process of creating our tables. By adding these tables as we create them it will make it easier at the end when compiling the component to install.


Step 3: Begin component folder and file creation

After creating the database tables we set up the file structure for the entire component. Below is the basic directory structure.

com_lendr/
  admin/
    controllers/
    models/
    views/
    index.html
    install.mysql.sql
    lendr.php
  site/
    assets/
    controllers/
    helpers/
    language/
    models/
    tables/
    views/
    index.html
    lendr.php
    router.php
install.php
lendr.xml
You can find the entire interactive layout in our Github repository.

Each of these files and folders is important, though not all are required. We shall begin working through each of these files and the functions they contain. Along the way we will explain the purpose of each.


Step 4: Write the install files, root file, controllers, and view controllers

In this step we are going to add content to several files. First we will look at the install files, then we'll work with some controllers and finally we'll add our view controllers.

Install Files

The root level files are those files used by Joomla! during the install process. They are located outside the site and admin folders in your component folder. There is an XML file which is used to define the component details, and all associated files, menus, and languages; and there is also an install.php file. This install.php file holds a number of functions that run upon installation. The name is not specific but must be referenced properly in the XML file. The functions do not have to be utilized or even present but can be used to perform additional actions during the component install.

Sample
<extension type="component" version="2.5.0" method="upgrade">
  <name>COM_LENDR</name>
  <creationDate>2013-01-31</creationDate>
  <author>Spark</author>
  <authorEmail>info@websparkinc.com</authorEmail>
  <authorUrl>http://lendr.websparkinc.com</authorUrl>
  <copyright>Copyright Info</copyright>
  <license>License Info</license>
  <version>1.0.0</version>
  <description>COM_LENDR_DESCRIPTION</description>

This first block of details defines the component information. This information is displayed in the Joomla! Extension Manager and is also stored in the extensions database table.

<install>
  <sql>
    <file charset="utf8" driver="mysql">mysql.install.sql</file>
  </sql>
</install>

This block tells Joomla! where the SQL files are for the component. This is run by Joomla! during the installation process to create the necessary database tables. notice you can set your character set as well as the driver type to be used.

You can also have an uninstall block with similar structure to define a SQL file to be run upon uninstall
<files folder="site">
  <folder>assets</folder>
  <folder>controllers</folder>
  <folder>helpers</folder>
  <folder>languages</folder>
  <folder>models</folder>
  <folder>tables</folder>
  <folder>views</folder>
  <filename>index.html</filename>
  <filename>lendr.php</filename>
  <filename>router.php</filename>
</files> 

This block defines the folders that will be installed on the frontend of the Joomla! site in the components folder. It is not necessary to name every file but merely the folders and any root level files. All folders will be searched recursively and files added.

  <scriptfile>install.php</scriptfile>

The script file can define the set of functions that are run upon installation. In our case we have named this file install.php.

<languages folder="site">
  <language tag="en-GB">languages/en-GB/en-GB.com_lendr.ini</language>
</languages>

The languages section defines the necessary language files. These will be installed in the languages folder under the appropriate language tag in the root of the Joomla! site. Here there are two language files, with one being a system language file for this component. This language file is used during the installation process and whenever information about your component is displayed while not within the component itself (e.g. the Extension Manager).

<administration>
  <menu link="option=com_lendr" img="components/com_lendr/assets/images/lendr_icon.png">COM_LENDR</menu>
  <submenu>
    <menu view="settings" img="components/com_lendr/assets/images/settings_icon.png"
      alt="LENDR/Settings">COM_LENDR_SETTINGS</menu>
  </submenu>

The next block defines the administrator side details both the admin menu as well as the admin side component files. Images can be referenced for the menu items. Image paths are related to the administrator component folder.

Notice that a submenu can be defined but is not necessary.
    <files folder="admin">
      <folder>controllers</folder>
      <folder>languages</folder>
      <folder>models</folder>
      <folder>views</folder>
      <filename>lendr.php</filename>
      <filename>index.html</filename>
      <filename>install.sql</filename>
    </files>  

    <languages folder="admin">
      <language tag="en-GB">languages/en-GB/en-GB.com_lendr.ini</language>
      <language tag="en-GB">languages/en-GB/en-GB.com_lendr.sys.ini</language>
    </languages>
      
  </administration>
</extension>

Also inside the administration tag are both a definition of the files and folders to be installed on the administrator side as well as the language file to be installed on the admin side. In our example we have added a folder tag, admin, to these elements to tell Joomla! which folder in the install package should be referenced for these. This can be named anything you desire. The system language file (second one) is explained in the next file description and walkthrough.

See the entire lendr.xml file on Github.
<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 
jimport('joomla.installer.installer');
jimport('joomla.installer.helper');

In this first block we define the installer and helper class we wish to import to aid in the installation functions present in this file.

/**
* Method to install the component
* 
* @param  mixed    $parent     The class calling this method
* @return void
*/
function install($parent) 
{
  echo JText::_('COM_LENDR_INSTALL_SUCCESSFULL');
}

The install function is run after the installation is completed and typically can include a message of successful installation. Text should use language file strings which are defined in the administrator languages folder section in the XX-XX.com_lendr.sys.ini

/**
* Method to update the component
* 
* @param  mixed  $parent   The class calling this method
* @return void
*/
function update($parent) 
{   
  echo JText::_('COM_LENDR_UPDATE_SUCCESSFULL');
}

The update function is run when the installation method is defined as an update. This can be a great location to run some functions related to additional SQL columns being added to existing tables.

/**
* method to run before an install/update/uninstall method
*
* @param  mixed  $parent   The class calling this method
* @return void
*/
function preflight($type, $parent) 
{
  ...
}

function postflight($type, $parent)
{
  ...
}

The pre-flight and post-flight functions are a bit self-explanatory. Here you can define specific functions you wish to run either before the installation begins or after the installation has concluded. If you wished to offer a different set of files for a different version you could specify in the pre-flight the updated paths to those files. The post flight could be used to run SQL code unique to data within your component (rather than the table structure of the component itself).

Root file (lendr.php)

The lendr.php file in the root of the site folder is the first file recognized and read by Joomla! after installation. This file handles the redirection of tasks to other controllers, the loading of helper files, stylesheets, javascript, plugin libraries and other core pieces necessary throughout the entire component. The file below is the beginning of this main file. It will be expanded upon in future tutorials in this series.

<?php // No direct access
defined( '_JEXEC' ) or die( 'Restricted access' );

//sessions
jimport( 'joomla.session.session' );

//load tables
JTable::addIncludePath(JPATH_COMPONENT.'/tables');

//load classes
JLoader::registerPrefix('Lendr', JPATH_COMPONENT);

//Load plugins
JPluginHelper::importPlugin('lendr');

//application
$app = JFactory::getApplication();

// Require specific controller if requested
if($controller = $app->input->get('controller','default')) {
  require_once (JPATH_COMPONENT.'/controllers/'.$controller.'.php');
}

// Create the controller
$classname  = 'LendrController'.$controller;
$controller = new $classname();

// Perform the Request task
$controller->execute();

This file will load the tables associated with this component; import any plugins that exist in the plugin group "lendr"; determine the controller requested by the user and then execute the appropriate controller based on that request.

Controllers

The controllers in a Joomla! 3 component are created as a class with a single function. Typically the controller name defines the task for the controller. This is a departure from previous Joomla! versions where a controller was dedicated to a variety of tasks related to a particular area of a component. By creating controllers with a single executable function there is a greater opportunity for chaining controllers together and forming an easy-to-follow path for tracing an action and troubleshooting. Below is one of the controllers we will define for Lendr, followed by our default controller for some basic functionality.

Sample
joomla_root/components/com_lender/controllers/edit.php
<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 

class LendrControllersEdit extends LendrControllersDefault
{
  function execute()
  {
    $app = JFactory::getApplication();
    $viewName = $app->input->get('view');
    $app->input->set('layout','edit');
    $app->input->set('view', $viewName);
   
    //display view
    return parent::execute();
  }
}
This is merely a sample of the implementation for a controller. More detail will be taken in future articles.

There is one key aspect of this controller that is worth looking at in greater detail. Notice that our controller extends a LendrControllersDefault. This default controller is important and we will look at it in a second. We have extended our own controller class for a bit of default actioning necessary to display the correct layout. Below is the default controller.

joomla_root/components/com_lendr/controllers/default.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 

class LendrControllersDefault extends JControllerBase
{
  public function execute()
  {
    // Get the application
    $app = $this->getApplication();

    // Get the document object.
    $document     = $app->getDocument();

    $viewName     = $app->input->getWord('view', 'dashboard');
    $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);

    if (false === class_exists($modelClass))
    {
      $modelClass = 'LendrModelsDefault';
    }

    $view = new $viewClass(new $modelClass, $paths);
    $view->setLayout($layoutName);

    // Render our view.
    echo $view->render();

    return true;
  }
}

In our default controller we are accomplishing two things. First, we are providing the controller for the "default" action, typically this is the action triggered when no other task is defined. This still follows the Joomla! method for a single task per controller. This default controller will take the view, locate the appropriate view file, load the corresponding model and render the view. Joomla! 3 requires a model to be loaded with each view file loaded and so by following a standard naming convention between views and models we are able to link them and assign them without any extra code. If the model does not exist for some reason we load the default model.

It's worth reviewing what SplPriorityQueue represents. In PHP this is an array which is an implementation of a special heap and sorts the data based on priority.
The rest of the Lendr controllers will be defined as they are implemented.

View Controllers

Joomla! is a bit unique in how the views are handled. Joomla! makes use of a secondary controller to help in the rendering aspect of data and the assigning of variables to be used in the view layouts. This secondary controller is found in the views folder of the component and is often named similarly to the type of render desired. (e.g. html.php for rendering html, phtml.php for rendering a partial template, raw.php for rendering raw data, etc...). Below is one of the view controllers used by Lendr.

Sample
joomla_root/components/com_lendr/views/book/html.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 

class LendrViewsBookHtml extends JViewHtml
{
  function render()
  {
    $app = JFactory::getApplication();
    $type = $app->input->get('type');
    $id = $app->input->get('id');
    $view = $app->input->get('view');

    //retrieve task list from model
    $model = new LendrModelBook();

    $this->book = $model->getBook($id,$view,FALSE);
    
    //display
    return parent::render();
  } 
}
    

This view controller renders the details for a particular book based on the id. The model function getBook() will be defined in the next step. Notice the variables to be used in the layout are assigned directly into the current object. View controllers can hold very little logic or a great deal of logic depending on the circumstances. This particular view has a minimal amount.

joomla_root/components/com_lendr/views/book/raw.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 

class LendrViewsBookRaw extends JViewHtml
{
  function render()
  {
    $app = JFactory::getApplication();
    $type = $app->input->get('type');
    $id = $app->input->get('id');
    $view = $app->input->get('view');

    //retrieve task list from model
    $model = new LendrModelBook();

    $this->book = $model->getBook($id,$view,FALSE);
    
    //display
    echo $this->book;
  } 
}
    

This view controller renders the raw details for a particular book based on the id.

The remaining view controllers for Lendr will be added as they are implemented later.

Step 5: Create Models

Joomla! models work like most MVC systems and handle the bulk of the data manipulation and data retrieval. Lendr models will be focused on heavily in the next tutorial so here we will look only at the general structure.

Sample
joomla_root/components/com_lendr/models/book.php
<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 

class LendrModelsBook extends LendrModelsDefault
{
  function __construct()
  {
    parent::__construct();       
  }

  function store()
  {
    …
  }

  function getBook()
  {
    …
  }

  function getBooks()
  {
    …
  }

  function populateState()
  {
    …
  }
}

Again, for simplicity sake, in this tutorial we have left out the details of each function. They will be addressed in the next article. It is important to notice that once again in this case we are implementing our own Default class. By doing so we can add common generic functions to a single model and use it in each model.

joomla_root/components/com_lendr/models/default.php
<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 

class LendrModelsDefault extends JModelBase
{
  var $__state_set  = null;
  var $_total       = null;
  var $_pagination  = null;
  var $_db          = null;
  var $id           = null;

  function __construct()
  {
    parent::__construct();
    $this->_db = JFactory::getDBO();

    $app = JFactory::getApplication();
    $ids = $app->input->get("cids",null,'array');

    $id = $app->input->get("id");
    if ( $id && $id > 0 ){
      $this->id = $id;
    }else if ( count($ids) == 1 ){
      $this->id = $ids[0];
    }else{
      $this->id = $ids;
    }
  }

  /**
  * Modifies a property of the object, creating it if it does not already exist.
  *
  * @param   string  $property  The name of the property.
  * @param   mixed   $value     The value of the property to set.
  *
  * @return  mixed  Previous value of the property.
  *
  * @since   11.1
  */
  public function set($property, $value = null)
  {
    $previous = isset($this->$property) ? $this->$property : null;
    $this->$property = $value;

    return $previous;
  }

  /**
  * Gets an array of objects from the results of database query.
  *
  * @param   string   $query       The query.
  * @param   integer  $limitstart  Offset.
  * @param   integer  $limit       The number of records.
  *
  * @return  array  An array of results.
  *
  * @since   11.1
  */
  protected function _getList($query, $limitstart = 0, $limit = 0)
  {
    $db = JFactory::getDBO();
    $db->setQuery($query, $limitstart, $limit);
    $result = $db->loadObjectList();

    return $result;
  }

  /**
  * Returns a record count for the query
  *
  * @param   string  $query  The query.
  *
  * @return  integer  Number of rows for query
  *
  * @since   11.1
  */
  protected function _getListCount($query)
  {
    $db = JFactory::getDBO();
    $db->setQuery($query);
    $db->query();

    return $db->getNumRows();
  }

  /* Method to get model state variables
  *
  * @param   string  $property  Optional parameter name
  * @param   mixed   $default   Optional default value
  *
  * @return  object  The property where specified, the state object where omitted
  *
  * @since   11.1
  */
  public function getState($property = null, $default = null)
  {
    if (!$this->__state_set)
    {   
      // Protected method to auto-populate the model state.
      $this->populateState();

      // Set the model state set flag to true.
      $this->__state_set = true;
    }

    return $property === null ? $this->state : $this->state->get($property, $default);
  }
  
  /**
  * Get total number of rows for pagination
  */
  function getTotal() 
  {
    if ( empty ( $this->_total ) )
    {
      $query = $this->_buildQuery();
      $this->_total = $this->_getListCount($query);
    }
    
    return $this->_total;
  }

  /**
  * Generate pagination
  */
  function getPagination() 
  {
    // Lets load the content if it doesn't already exist
    if (empty($this->_pagination)) 
    {
      $this->_pagination = new JPagination( $this->getTotal(), $this->getState($this->_view.'_limitstart'), $this->getState($this->_view.'_limit'),null,JRoute::_('index.php?view='.$this->_view.'&layout='.$this->_layout));
    }
    
    return $this->_pagination;
  }
}

This default model incorporates some important functions that will be reused throughout the component. We will return to these functions in more detail in the following two tutorials.

These Joomla! models are just two of the files that will be created with the Lendr component system. The other models are similar in nature and basic structure but will be written in more detail in the following tutorial.

The rest of the Lendr models can be found in our Github repository.

Beginning Development Wrap-up

Now that we have created the basic folder structure of the component, written the database tables, install files, controllers, view controllers, and models our component should be installable. Of course while there is no functionality yet and the component merely creates an empty shell it does provide some sense of satisfaction to have an installable component. Be sure to examine our Github repository to view the other database tables, controllers, view controllers, and models which have not been written out here.

Download

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

Download

In the next tutorial we will dive into the actual functionality to be written to the various models.

Step 0: Fix an Espresso

You’re going to need a strong cup of coffee today. In this tutorial we will be covering the details of the models and views needed for the Lendr component series we’ve begun in previous tutorials. If you’re just joining this series, I recommend reading the first article, and then follow up with the initial setup article before continuing with this tutorial. Done with those articles? Ready for the next? Make sure you are ready to go by having your system setup and your code editor fired up ready to begin writing code. And don’t forget your espresso.


Step 1: Model File Details and Functions

Now that we’ve gotten our base files created and started writing our models we need to get into the good stuff. The first thing to do is to sort out the models we will be writing to and the functions that need to be added. Here is a brief overview of those models and functions:

Basic Files and Functions Needed

Model Functions
Default save
delete
set
get
getItem
listItems
getState
getTotal
getPagination
Book _buildQuery
_buildWhere
Wishlist _buildQuery
_buildWhere
Profile _buildQuery
_buildWhere
Library _buildQuery
_buildWhere
Waitlist _buildQuery
_buildWhere
Review _buildQuery
_buildWhere

Once we’ve defined a brief overview of the models and functions we’ll need we can begin writing the models. It’s very important to keep the following principle in mind during this process. Our list is fluid and dynamic. We are able to return to our list and add or remove functions as necessary. Don’t be afraid to revisit this list repeatedly as we progress and evaluate things. It’s possible we can simplify by adding a function to a particular model, or maybe we need to rewrite a more abstract function which we can add to the default model. One thing is sure, we don’t want to rewrite the same code over and over, the minute we find we’re starting down that path it’s time to consider how we abstract the code to a common model. Let’s start writing our models.


Step 2: Write the Models

There are a handful of models which make up this Lendr component but we don’t have the time to write all of them out here. We’ll focus on several key models that will help to demonstrate the bulk of the component and leave a few of the secondary models in the associated GitHub repository for you to review on your own. We’ll start by writing the book model. As I began thinking where to start coding I decided to start with the most specific and working out to the library model and then the profile model. The reason for this is simple. The book is the smallest unit, a library is made up of books and a profile then contains a library. I trust that helps you understand why we begin with the book model and work out from there. The very first model we’ll look at will be the default model. We use the default model to store some basic functions that we want to have available in all our models and since we write Object Oriented code we don’t want to re-write the same functions in each model.

Model Files
<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrModelsDefault extends JModelBase
{

  protected $__state_set    = null;
  protected $_total         = null;
  protected $_pagination    = null;
  protected $_db            = null;
  protected $id               = null;
  protected $limitstart     = 0;
  protected $limit            = 10;

First we set some class level variables.  This will allow us to reference them easily throughout the file.
 
  function __construct()
  {

    parent::__construct(); 
  }

  public function store($data=null)
  {
    $data = $data ? $data : JRequest::get('post');
    $row = JTable::getInstance($data['table'],'Table');

    $date = date("Y-m-d H:i:s");

     // Bind the form fields to the table
    if (!$row->bind($data))
    {
        return false;
    }

    $row->modified = $date;
    if ( !$row->created )
    {
      $row->created = $date;
    }

    // Make sure the record is valid
    if (!$row->check())
    {
        return false;
    }
 
    // Store the web link table to the database
    if (!$row->store())
    {
        return false;
    }

    return $row;
  }

Because most of our models will be storing data in some way we are going to write a standard store function into our default model. We will extend this function as needed in various specific cases but here we do the basic store functionality. This function will include more error checking and reporting on those errors when we get into the clean-up portion of the tutorial series.

 /**
  * Modifies a property of the object, creating it if it does not already exist.
  *
  * @param   string  $property  The name of the property.
  * @param   mixed   $value     The value of the property to set.
  *
  * @return  mixed  Previous value of the property.
  *
  * @since   11.1
  */
  public function set($property, $value = null)
  {
    $previous = isset($this->$property) ? $this->$property : null;
    $this->$property = $value;
 
    return $previous;
  }

  public function get($property, $default = null) 
  {
    return isset($this->$property) ? $this->$property : $default;
  }

Often we will have class level variables in each of our models that we will need to set from various other locations. Rather than allowing those variables to be set directly by referencing them, we will use get and set functions to better control and clean variables as necessary. Currently these two functions do not incorporate any additional features.

/**
* Build a query, where clause and return an object
*
*/
public function getItem()
{
  $db = JFactory::getDBO();

  $query = $this->_buildQuery();
  $this->_buildWhere($query);
  $db->setQuery($query);

  $item = $db->loadObject();

  return $item;
}

/**
* Build query and where for protected _getList function and return a list
*
* @return array An array of results.
*/
public function listItems()
{
  $query = $this->_buildQuery();    
  $query = $this->_buildWhere($query);
  
  $list = $this->_getList($query, $this->limitstart, $this->limit);

  return $list;
}

These two functions are the basic functions for getting a single item and getting a list of items. Again, most models will need to retrieve a single row and multiple rows from the database. We will extend these functions as necessary in specific models.

The _buildQuery and _buildWhere functions are not present in the default model. These functions are specific to each model and are available in each specific model.
Note: We are using an underscore ( _ ) to identify protected functions.
 /**
* Gets an array of objects from the results of database query.
*
* @param   string   $query       The query.
* @param   integer  $limitstart  Offset.
* @param   integer  $limit       The number of records.
*
* @return  array  An array of results.
*
* @since   11.1
*/
protected function _getList($query, $limitstart = 0, $limit = 0)
{
  $db = JFactory::getDBO();
  $db->setQuery($query, $limitstart, $limit);
  $result = $db->loadObjectList();

  return $result;
}

/**
* Returns a record count for the query
*
* @param   string  $query  The query.
*
* @return  integer  Number of rows for query
*
* @since   11.1
*/
protected function _getListCount($query)
{
  $db = JFactory::getDBO();
  $db->setQuery($query);
  $db->query();

  return $db->getNumRows();
}

These two functions are used for help in retrieving the list from the database and retrieving the count of the list returned by the query.

  /* Method to get model state variables
  *
  * @param   string  $property  Optional parameter name
  * @param   mixed   $default   Optional default value
  *
  * @return  object  The property where specified, the state object where omitted
  *
  * @since   11.1
  */
  public function getState($property = null, $default = null)
  {
    if (!$this->__state_set)
    {   
      // Protected method to auto-populate the model state.
      $this->populateState();

      // Set the model state set flag to true.
      $this->__state_set = true;
    }

    return $property === null ? $this->state : $this->state->get($property, $default);
  }

  /**
  * Get total number of rows for pagination
  */
  function getTotal() 
  {
    if ( empty ( $this->_total ) )
    {
      $query = $this->_buildQuery();
      $this->_total = $this->_getListCount($query);
    }
    
    return $this->_total;
  }

  /**
  * Generate pagination
  */
  function getPagination() 
  {
    // Lets load the content if it doesn't already exist
    if (empty($this->_pagination)) 
    {
      $this->_pagination = new JPagination( $this->getTotal(), $this->getState($this->_view.'_limitstart'), $this->getState($this->_view.'_limit'),null,JRoute::_('index.php?view='.$this->_view.'&layout='.$this->_layout));
    }
    
    return $this->_pagination;
  }
}

The final three functions at the end of the default model are used when dealing with pagination. These functions will be discussed in more detail in the subsequent tutorials as we begin implementation of pagination throughout the component.

joomla_root/components/com_lendr/models/book.php
<?php // no direct access

defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrModelsBook extends LendrModelsDefault
{

  /**
  * Protected fields
  **/
  var $_book_id     = null;
  var $_user_id     = null;
  var $_library_id  = null;
  var $_pagination  = null;
  var $_total       = null;
  var $_published   = 1;
  var $_waitlist    = FALSE;


  function __construct()
  {
    parent::__construct();       
  }
 
  /**
  * Builds the query to be used by the book model
  * @return   object  Query object
  *
  *
  */
  protected function _buildQuery()
  {
    $db = JFactory::getDBO();
    $query = $db->getQuery(TRUE);

    $query->select('b.book_id, b.user_id, b.isbn, b.title, b.author, b.summary, b.pages, 
                    b.publish_date, b.lent, b.lent_date, b.due_date');
    $query->from('#__lendr_books as b');

    $query->select('w.waitlist_id');
    $query->leftjoin('#__lendr_waitlists as w on w.book_id = b.book_id');

    return $query;
  }

  /**
  * Builds the filter for the query
  * @param    object  Query object
  * @return   object  Query object
  *
  */
  protected function _buildWhere(&$query)
  {

    if(is_numeric($this->_book_id)) 
    {
      $query->where('b.book_id = ' . (int) $this->_book_id);
    }

    if(is_numeric($this->_user_id)) 
    {
      $query->where('b.user_id = ' . (int) $this->_user_id);
    }

    if(is_numeric($this->_library_id)) 
    {
      $query->where('b.library_id = ' . (int) $this->_library_id);
    }

    if($this->_waitlist)
    {
      $query->where('w.waitlist_id > 0');
    }

    $query->where('b.published = ' . (int) $this->_published);

    return $query;
  }
}

As mentioned previously, because we are extending the default model we are able to streamline the functions present in each of the specific models. In this case we have the code for the query and the where clause of the query. The code is straightforward and will return results based on the criteria.

joomla_root/components/com_lendr/models/library.php
<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrModelsLibrary extends LendrModelsDefault
{

  //Define class level variables
  var $_library_id  = null;
  var $_user_id     = null;
  var $_published   = 1;

  function __construct()
  {
    parent::__construct();      

    $app = JFactory::getApplication();
    $this->_library_id = $app->input->get('library_id',null);
    $this->_user_id = $app->input->get('user_id',JFactory::getUser()->id);
  }

We set some default values for class level variables in our construct function. If we do not have a user_id set for the library class we are setting it to the current logged in user’s ID.

  function getItem() 
  {
    $library = parent::getItem(); 

    $bookModel = new LendrModelsBook();
    $bookModel->set('_user_id',$this->_user_id);
    $library->books = $bookModel->listItems();

    return $library;
  }

This is a great example of extending a base level class. Notice that we have a function named getItem. This is the same function name as is present in the default model. When the getItem function is called for the library model it will run this function. In this instance we have grabbed the basic details of the “item” (in this case a library object) by using the default getItem function, then we add to that object below. Notice we are using the set method to set the user_id on the book model.

  function listItems()
  {
    $bookModel = new LendrModelsBook();
    $libraries = parent::listItems();

    $n = count($libraries);

    for($i=0;$i<$n;$i++)
    {
      $library = $libraries[$i];
      
      $bookModel->_library_id = $library->id;
      $library->books = $bookModel->listItems();
    }

    return $libraries;
  }

Again, similar to the single item function we are first calling the listItems from the parent default model and then adding the books to the individual objects as well.

Note: Do you see repeat code between the single and list functions? This means we could extract that code into a separate function and then simply call that function to reduce overall lines. We did not do that here simply for legibility and ease of understanding.
protected function _buildQuery()
  {
    $db = JFactory::getDBO();
    $query = $db->getQuery(TRUE);

    $query->select("l.library_id, l.name, l.description");
    $query->from("#__lendr_libraries as l");

    $query->select("u.username, u.name");
    $query->leftjoin("#__users as u ON u.id = l.user_id");

    $query->select("p.*");
    $query->leftjoin("#__user_profiles as p on p.user_id = u.id");

    return $query;
  }


  protected function _buildWhere(&$query)
  {

    if(is_numeric($this->_user_id)) 
    {
      $query->where('l.user_id = ' . (int) $this->_user_id);
    }

    if(is_numeric($this->_library_id)) 
    {
      $query->where('l.library_id = ' . (int) $this->_library_id);
    }

    $query->where('l.published = '. (int) $this->_published);

    return $query;
  }
   
}

As referenced in the default model and explained in the preceding book model. These two functions are the query and where pieces used by the default model for the single and list item functions.

joomla_root/components/com_lendr/models/profile.php
<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrModelsProfile extends LendrModelsDefault
{

  //Define class level variables
  var $_user_id     = null;

  function __construct()
  {

    $app = JFactory::getApplication();

    //If no User ID is set to current logged in user
    $this->_user_id = $app->input->get('profile_id', JFactory::getUser()->id);

    parent::__construct();       
  }

  function getItem()
  {

    $profile = JFactory::getUser($this->_user_id);
    $userDetails = JUserHelper::getProfile($this->_user_id);
    $profile->details =  isset($userDetails->profile) ? $userDetails->profile : array();

    $libraryModel = new LendrModelsLibrary();
    $libraryModel->set('_user_id',$this->_user_id);
    $profile->library = $libraryModel->getItem();

    $waitlistModel = new LendrModelsWaitlist();
    $waitlistModel->set('_waitlist', TRUE);
    $profile->waitlist = $waitlistModel->getItem();

    $profile->isMine = JFactory::getUser()->id == $profile->id ? TRUE : FALSE;

    return $profile;
  }

In this function we have no reference to the parent getItem. The profile is unique in it’s structure and because we are building on the Joomla! 3 CMS we have the option of not including an extension specific set of tables and functions related to users and profiles.

Lendr has very limited profile fields and only needs certain fields that are readily available in the Joomla! profile plugin (included by default in Joomla 3.)

After retrieving the profile we then instantiate a couple additional models to grab associated data, specifically the library and waitlist model. In the future we will return to this function to add the wishlists and reviews.

Important: Be sure to enable the Joomla! 3 profile plugin listed in the extension manager.
  protected function _buildQuery()
  {
    $db = JFactory::getDBO();
    $query = $db->getQuery(TRUE);

    $query->select("u.id, u.username, u.name, u.email, u.registerDate");
    $query->from("#__users as u");

    $query->select("COUNT(b.book_id) as totalBooks");
    $query->leftjoin("#__lendr_books as b on b.user_id = u.id");

    $query->select("COUNT(r.review_id) as totalReviews");
    $query->leftjoin("#__lendr_reviews as r on r.user_id = u.id");

    return $query;
  }

In this function we left join a few additional tables to include total number of books added by a user and total number of reviews written.

  protected function _buildWhere($query)
  {
    $query->group("u.id");

    return $query;
  }

}

Because we are counting rows from a joined table as a field in our results we need to group things by an ID field to make sure we get all possible results. Here we group by the user id field.

Note: The additional models will be available in our GitHub repository and filled out in a later article.

Step 3: Incorporate Additional Resources

As we write this component there are certain aspects where it would be nice to incorporate third party features rather than reinventing the wheel. We have been able to simplify our styling and layouts by utilizing the Bootstrap classes available with Joomla! 3. Other handy resources that can be used include, Gravatar and Open Library. If you are unfamiliar with these tools you can read more about them on their respective websites. Below is a brief explanation of how they are used in Lendr.

Gravatar

Gravatar provides a simple way to retrieve an image or avatar associated with an email address. Lendr makes use of this to display profile pictures in an incredibly straightforward way. You will see the code necessary to make it happen when we write the view layouts in the step below.

Open Library

Open Library provides a great way to retrieve a book cover specific to an ISBN. This allows us to easily include a cover with each book without the hassle of maintaining storage space, image uploads etc. Open Library has several fields which can be used to reference the book cover and return the image, Lendr makes use of the ISBN which is part of the Add Book form found below. You will find the code in the layouts below.


Step 4: Start View Layouts and Styles

The first thing we do will be to return to the entry point for the component. This is the default controller as we reference it within the root lendr.php file. After writing out our models it’s clear we need to update where the user starts. We have changed the following line of code in the default.php controller:

joomla_root/components/com_lendr/controllers/default.php
$layoutName   = $app->input->getWord('layout', 'list');

This will now send the user to the list view of the profiles. That’s the first view we’ll look at. In our views folder we have a profile folder with the following structure in it:

Folder Structure
profile
  tmpl
    _entry.php
    index.html
    list.php
    profile.php
  html.php
  index.html
  phtml.php

Writing the Files

We will look first at the html.php file. This file is the default type we referenced by Joomla! and the current naming conventions. We saw the basic structure of these files in the previous article.

Individual Files
joomla_root/components/com_lendr/views/profile/html.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrViewsProfileHtml extends JViewHtml
{
  function render()
  {
    $app = JFactory::getApplication();
    $layout = $app->input->get('layout');

    //retrieve task list from model
    $profileModel = new LendrModelsProfile();

    switch($layout) {

      case "profile":
        $this->profile = $profileModel->getItem();
        
        $this->_addBookView = LendrHelpersView::load('Book','_add','phtml');

        $this->_libraryView = LendrHelpersView::load('Library','_library','phtml');
        $this->_libraryView->library = $this->profile->library;

        $this->_waitlistView = LendrHelpersView::load('Waitlist','_waitlist','phtml');
        $this->_waitlistView->waitlist = $this->profile->waitlist;
      break;

      case "list":
      default:
        $this->profiles = $profileModel->listItems();
        $this->_profileListView = LendrHelpersView::load('Profile','_entry','phtml');
      break;

    }

    //display
    return parent::render();
  } 
}

This file contains a single render function which is the function referenced by controllers and other areas. Within this file we define certain variables that will be used by the specific layouts (which we will explore next). Here is the first time we use the LendrHelpersView. Naming conventions of variables used throughout Lendr are designed to help with readability of code as you continue through the tutorial. We have named several variables here with an underscore ( _ ) and View in the variable name. This helps with a couple of things.

First, it is clear at a glance that a specific variable is a view object if it contains the View reference in its name. We can now render this object wherever we wish the view be displayed.

Secondly, the underscore is used to refer to the fact that we have a partial template. This helps to remind us that the view will more than likely be included in another view and not used as a standalone view.

Again, these are used purely for assistance when reviewing the code and providing a standardized method to variable naming.

Note: This file is named specific to the folder structure that defines it.
Note: Because this component uses the new MVC structure notice that we are extending JViewHTML with this class.
Aside: Because we have called the view helper it is worth reviewing that file now.
joomla_root/components/com_lendr/helpers/view.php
<?php
// no direct access
defined('_JEXEC') or die('Restricted access');

class LendrHelpersView
{
  function load($viewName, $layoutName='default', $viewFormat='html', $vars=null)
  {
    // Get the application
    $app = JFactory::getApplication();

    $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);

    if (false === class_exists($modelClass))
    {
      $modelClass = 'LendrModelsDefault';
    }

    $view = new $viewClass(new $modelClass, $paths);

    $view->setLayout($layoutName);
      
    if(isset($vars)) 
    {
      foreach($vars as $varName => $var) 
      {
        $view->$varName = $var;
      }
    }

    return $view;
  }
}

This file provides a function very similar in many aspects to the default controller. We do have a longer list of parameters as well as a foreach loop to assign variables, but otherwise it is quite similar. This helper view will be useful for calling a view class and assigning variables for our partial templates and other parts of our component.

Returning now to the original html view file, we can see the parameters we pass to the view helper load function. First, we tell the helper which view folder we wish to use, next the layout to be used, and lastly in these cases, we return the page format type. For a variety of reasons we have created a new file and called it phtml.php to represent partial html.php. We have used this file instead of the standard html view because of the various additional functions currently being called within the html.php file. Because in most cases we simply want a basic render function for partial templates we don’t want the overhead associated with the standard html file. While there are other methods for handling problems like this, having a different file for the view will help organize things and also provide an easy location should additional functions relevant only to partial templates be necessary.

joomla_root/components/com_lendr/views/profile/phtml.php
<?php

// no direct access
defined( '_JEXEC' ) or die( 'Restricted access' ); 

//Display partial views
class LendrViewsProfilePhtml extends JViewHTML
{
  function render()
  {
    return parent::render();
  }
}
joomla_root/components/com_lendr/views/profile/tmpl/_entry.php
<div class="media well well-small span6">
  <a class="pull-left" href="<?php echo JRoute::_('index.php?option=com_lendr&view=profile&layout=profile&id='.$this->profile->id); ?>">
     <img src="http://www.gravatar.com/avatar/<?php echo md5(strtolower(trim($this->profile->email))); ?>?s=60" />
  </a>
  <div class="media-body">
    <h4 class="media-heading"><a href="<?php echo JRoute::_('index.php?option=com_lendr&view=profile&layout=profile&profile_id='.$this->profile->id); ?>"><?php echo $this->profile->name; ?></a></h4>
    <p><strong><?php echo JText::_('COM_LENDR_TOTAL_BOOKS'); ?></strong>: <?php echo $this->profile->totalBooks; ?><br />
       <strong><?php echo JText::_('COM_LENDR_TOTAL_REVIEWS'); ?></strong>: <?php echo $this->profile->totalReviews; ?>
    </p>
  </div>
</div>

As mentioned we are making use of the Gravatar API to display the profile image associated with their email address. Notice also the extensive use of Bootstrap styles and layout. This provides an enormous time saving opportunity.

Note: Remember that we are viewing a partial template based on the file name.
joomla_root/components/com_lendr/views/profile/tmpl/list.php
<h2 class="page-header"><?php echo JText::_('COM_LENDR_PROFILES'); ?></h2>
<div class="row-fluid">
  <?php for($i=0, $n = count($this->profiles);$i<$n;$i++) { 
          $this->_profileListView->profile = $this->profiles[$i];
          echo $this->_profileListView->render();
  } ?>
</div>

This file is the main file that the component will send users to by default. This was set previously in our default controller and referenced above. This list view has made use of the standard html.php file located in the profiles view folder and you will notice the use of the _profileListView partial template. To word this differently, we have called this view which loads up the container for our list of profiles. Inside this view we are then making a call to a partial template view (_entry.php) which will display each of the appropriate individual profile layouts.

Note: Partial templates allow for blocks of html to be reused in multiple locations and all locations be updated by editing a single file.
<a href="<?php echo JRoute::_('index.php?option=com_lendr&view=profile&layout=list'); ?>" class="btn pull-right"><i class="icon icon-chevron-left"></i> <?php echo JText::_('COM_LENDR_BACK'); ?></a>
<h2 class="page-header"><?php echo $this->profile->name; ?></h2>
<div class="row-fluid">
  <div class="span3">
    <img src="http://www.gravatar.com/avatar/<?php echo md5(strtolower(trim($this->profile->email))); ?>?s=180" />
  </div>
  <div class="span9 well well-small">
    <dl class="dl-horizontal">
      <dt><?php echo JText::_('COM_LENDR_PROFILE_NAME'); ?></dt>
      <dd><?php echo $this->profile->name; ?></dd>
      <dt><?php echo JText::_('COM_LENDR_PROFILE_JOIN'); ?></dt>
      <dd><?php echo JHtml::_('date', $this->profile->registerDate, JText::_('DATE_FORMAT_LC3')); ?></dd>
      <dt><?php echo JText::_('COM_LENDR_PROFILE_BIO'); ?></dt>
      <dd><?php if(isset($this->profile->details['aboutme'])) echo $this->profile->details['aboutme']; ?></dd>
    </dl>
  </div>
</div>
<br />
<div class="row-fluid">
<div class="tabbable">
  <ul class="nav nav-tabs">
    <li class="active"><a href="#libraryTab" data-toggle="tab"><?php echo JText::_('COM_LENDR_LIBRARY'); ?></a></li>
    <li><a href="#wishlistTab" data-toggle="tab"><?php echo JText::_('COM_LENDR_WISHLIST'); ?></a></li>
    <li><a href="#waitlistTab" data-toggle="tab"><?php echo JText::_('COM_LENDR_WAITLIST'); ?></a></li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane active" id="libraryTab">
      <?php if($this->profile->isMine) { ?>
        <a href="#newBookModal" role="button" data-toggle="modal" class="btn pull-right"><i class="icon icon-pencil-2"></i> <?php echo JText::_('COM_LENDR_ADD_BOOK'); ?></a>
      <?php } ?>
      <h2><?php echo JText::_('COM_LENDR_LIBRARY'); ?></h2>
      <?php echo $this->_libraryView->render(); ?>
    </div>
    <div class="tab-pane" id="wishlistTab">
      <h2><?php echo JText::_('COM_LENDR_WISHLIST'); ?></h2>
    </div>
    <div class="tab-pane" id="waitlistTab">
      <h2><?php echo JText::_('COM_LENDR_WAITLIST'); ?></h2>
      <?php echo $this->_waitlistView->render(); ?>
    </div>
  </div>
</div>
</div>

<?php echo $this->_addBookView->render(); ?>

This profile layout is the view the user will see when navigating to an individual record. Similar to the list view we make extensive use of partial templates to re-use our code and provide a standardized single location to make changes. Reading through this file we see again the use of Gravatar for a profile image (this time in a larger size) as well as several various bootstrap functions (tabs, buttons, wells, description blocks). Notice that each of our tabs uses a partial template to render the appropriate content.

Make a note

An interesting point to view at the bottom of the file is the use of an _addBookView partial template. Because we have a standardized naming convention it is safe to assume we are including a form view for adding a new book. This is exactly what occurs. If the user is logged in and viewing their own profile a button is displayed within the Library tab (see above) which will call a modal window to be displayed with the _addBookView partial template.

Remaining layouts

We’ve reviewed all the layouts in the profile tab but through the process we’ve found additional views called to be rendered within the profile layout. For the sake of the length of this article we will not list each view and the code found within. Instead, you can view the code associated with each of these view directly in the GitHub repository.


Step 5: Javascript and CSS

The last piece we’ll look at in this article is the beginning of the Javascript and CSS associated with Lendr. Because Lendr uses Bootstrap and jQuery the modal window referenced in the previous step for adding a new book is included automatically and we don’t have to write any specific Javascript functions to accomplish that. There are quite a few parts of the system though where we will need to write specific javascript code and there will also be times when specific CSS styles will be necessary. We will add the following helper file to address styles and javascript.

joomla_root/components/com_lendr/helpers/style.php
<?php
// no direct access
defined('_JEXEC') or die('Restricted access');

class LendrHelpersStyle
{
  function load()
  {
    $document = JFactory::getDocument();

    //stylesheets
    $document->addStylesheet(JURI::base().'components/com_lendr/assets/css/style.css');

    //javascripts
    $document->addScript(JURI::base().'components/com_lendr/assets/js/lendr.js');
  }
}

Here we associate any css and javascript related to our component. This helper file resides in the helper folder and again follows the standard class naming convention for the component. This class is included automatically based on the namespace (loader) as defined in the root lendr.php file. We call this class from that same root file with the line of code:

joomla_root/components/com_lendr/lendr.php
//Load styles and javascripts
LendrHelpersStyle::load();

Now that we have javascript and CSS files included we can begin to add functions as we need them. The first function we’ll add is in relation to the new modal we’ve just created for adding a book.

After the add book form has been filled out the user submits the form through the “Add” button. When this button is clicked it fires the javascript action addBook();

joomla_root/components/com_lendr/assets/js/lendr.js
//add a book
function addBook()
{
  var bookInfo = {};
  jQuery("#bookForm :input").each(function(idx,ele){
    bookInfo[jQuery(ele).attr('name')] = jQuery(ele).val();
  });

  jQuery.ajax({
    url:'index.php?option=com_lendr&controller=add&format=raw&tmpl=component',
    type:'POST',
    data:bookInfo,
    dataType:'JSON',
    success:function(data)
    {
      if ( data.success ){
        jQuery("#book-list").append(data.html);
        jQuery("#newBookModal").modal('hide');
      }else{

      }
    }
  });
}

In this function we first use jQuery to create a bookInfo object which contains all of our form variables. Once we have those variables in a single form we begin creating an ajax submission, again using jQuery. Notice that we specify our URL details to include the controller, the format, and the tmpl (or template type). A few things to note here. Because Lendr is a Joomla! 3.x extension our controllers are single function controllers, meaning they all contain only one function (execute). The format is used to return only the data from the controller and the tmpl tells the Joomla! template which file (component.php or index.php) to use. We then describe the type of submission in this case POST. For the data we assign the bookInfo object we created earlier and for the type we specify JSON.

The controller (in this case add.php) will then handle taking the form submission, posting it to the correct model to be stored and then return the result. The result will be a JSON encoded array with a success variable being set. This controller is below:

joomla_root/components/com_lendr/controllers/add.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrControllersAdd extends JControllerBase
{
  public function execute()
  {

    $return = array("success"=>false);

    $model = new LendrModelsBook();
    if ( $row = $model->store() )
    {
      $return['success'] = true;
      $return['msg'] = JText::_('COM_LENDR_BOOK_SAVE_SUCCESS');

      $bookView = LendrHelpersView::load('Book','_entry','phtml');
      $bookView->book = $row;

      ob_start();
      echo $bookView->render();
      $html = ob_get_contents();
      ob_clean();

      $return['html'] = $html;
    }else{
      $return['msg'] = JText::_('COM_LENDR_BOOK_SAVE_FAILURE');
    }
    echo json_encode($return);
  }
}
Note: ob_start(), ob_get_contents(), and ob_clean() are used so that we can render the partial template and return the resulting html to the javascript. to be added dynamically to the page.
Note: The result of this execute function is an echo of the JSON encoded return array. This is because we process this controller through AJAX.

Returning to the javascript function above we now can examine the results of the AJAX post and parse the response.

  if ( data.success )
  {
    jQuery("#book-list").append(data.html);
    jQuery("#newBookModal").modal('hide');
  }else{
    ...
  }

Here we take the html (which we rendered in the controller and assigned to the html variable) and append it to the book list table body. We will also hide the modal upon success. Currently we have an empty “else” statement which we will fill in later with an appropriate message. This will be part of the finishing touches article yet to come later in this series.


Conclusion

Did you make it through? This article is a much more detailed and in-depth article than previous ones have been but should provide a fairly comprehensive approach to developing commercial level extensions in Joomla! 3 using established coding standards and also implementing new MVC functionality as well as other features unique to Joomla! 3.x. The next tutorial in this series will continue to fill out models and controllers, views and javascript as necessary to continue developing the extension. I hope the above has been helpful as you create your own components for Joomla! and I hope you will contact us if you have questions or comments regarding any of the above. Parts of the above article were re-written multiple times as I sought the best method and clearest format for both code and layouts. I am aware there are aspects of this step that have not been walked through step-by-step (e.g. the book _entry layout with buttons). I am certainly willing to provide smaller breakout tutorials on those items should they be of interest and I receive comments requesting them. I look forward to the next article in this series where we will continue to add functionality to the component and add even more exciting features!

Download

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

Download

In the next tutorial we will write more of the model and controller functionality.

Step 0: Make Coffee

At this point we have spent several articles together as we have built this component. I am confident at this point you are aware of the first step. Before beginning on writing code or structuring the next bit of the process it’s important to continue the habits we’ve started from the beginning. Find your favorite cup and fill it with the liquid calmness of your choice. If this is new to you, I’d recommend reviewing the previous articles, start at the beginning and work your way through the series so far. I’ll wait here for you.

Ok! welcome back. In this article we’ll take a look at some of the other features involved in Lendr. You will notice I’ve added more code to many of the files that were empty before. Rather than spending time reviewing each of them I will focus only on those areas where new functionality or concepts are introduced. You should be able to easily understand the code based on the previous articles. Now we will continue in our series by writing the various modal windows necessary for the Lendr component.


Step 1: Create the modal windows

In this article we take a more of a relaxed approach and work on some additional features and detail work. First you will notice that we’ve added more modal windows. We’ve done all our modal windows the same way (utilizing the Bootstrap technique). In order to give a bit more detail I’ll explain the two options and then detail the option we’ve chosen. First you can load a view and layout via AJAX and using the typical Joomla method which although using a modal window loads an entire view file when a button or link is clicked. This has been historically the standard method for Joomla to load modal windows (think about them a bit like iframes). The second method is to load the modal window details onto the page when the page is initially loaded however the modal window (in essence, a div containing the view) is hidden by default on the page load and is not visible until activated by a link or button click.

I’ve chosen this second method for our modals within Lendr for a couple of reasons. By adding the modal div into the page on load but keeping it hidden this puts all the page load speed into the initial page load. While this may make you think the page will load slower initially this is a quite miniscule addition. On the other hand, by already loading the HTML into the page when the button or link is clicked the modal appears instantly (since it has already been loaded). This tends to make the page feel as though it has loaded incredibly fast. While it may be a personal preference, the quickness is one that I notice and therefore prefer. A second reason for choosing this modal loading method resides in the fact that there are only a few data fields being added to the modal. Because the requested data is minimal it is not a very difficult task to assign those variables to the modal on the fly instead of requesting an entire page via AJAX. Again, this is a bit of a personal preference in regards to speed.

Let’s look at the lines of code necessary to include a modal into our page. First, we’ll look at the html.php file for the “container” view. This might also be considered the parent view for the modal window we plan to load.

/joomla_root/components/com_lendr/views/book/html.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrViewsBookHtml extends JViewHtml
{
  function render()
  {
    $app = JFactory::getApplication();
   
    //retrieve task list from model
    $model = new LendrModelsBook();
    $this->book = $model->getItem();
    
    $this->_addReviewView = LendrHelpersView::load('Review','_add','phtml');
    $this->_addReviewView->book = $this->book;
    $this->_addReviewView->user = JFactory::getUser();

    $this->_lendBookView = LendrHelpersView::load('Book', '_lend', 'phtml');
    $this->_lendBookView->borrower = $this->book->waitlist_user;
    $this->_lendBookView->book = $this->book;

    $this->_returnBookView = LendrHelpersView::load('Book', '_return', 'phtml');
    $this->_returnBookView->borrower = $this->book->waitlist_user;
    $this->_returnBookView->book = $this->book;

    $this->_reviewsView = LendrHelpersView::load('Review','list','phtml');
    $this->_reviewsView->reviews = $this->book->reviews;

    $this->_modalMessage = LendrHelpersView::load('Profile','_message','phtml');

    //display
    return parent::render();
  } 
}

These lines use the Lendr Helper view we have created and used previously, notice again that we use an underscore (_) to signify the partial templates, and we also denote a phtml, for our format type.

The following file is the file we are loading as our partial template.

joomla_root/components/com_lendr/views/book/tmpl/_lend.php
<div id="lendBookModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="lendBookModalLabel" aria-hidden="true">
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
    <h3 id="myModalLabel"><?php echo JText::_('COM_LENDR_LEND_BOOK'); ?></h3>
  </div>
  <div class="modal-body">
  <div class="row-fluid">
    <form id="lendForm">
      <div class="alert alert-info">
        <?php echo JText::_('COM_LENDR_LEND_BOOK_TO'); ?> <span id="borrower_name"></span>
      </div>
      <div id="book-modal-info" class="media"></div>
      <input type="hidden" name="book_id" id="bookid" value="<?php echo $this->book->book_id; ?>" />
      <input type="hidden" name="user_id" value="<?php echo JFactory::getUser()->id; ?>" />
      <input type="hidden" name="table" value="Book" />
      <input type="hidden" name="waitlist_id" value="<?php echo $this->book->waitlist_id; ?>" />
      <input type="hidden" name="borrower_id" id="borrower_id" value="<?php echo $this->book->borrower_id; ?>" />
      <input type="hidden" name="lend" value="1" />
    </form>
  </div>
  </div>
  <div class="modal-footer">
    <button class="btn" data-dismiss="modal" aria-hidden="true"><?php echo JText::_('COM_LENDR_CLOSE'); ?></button>
    <button class="btn btn-primary" onclick="lendBook();"><?php echo JText::_('COM_LENDR_LEND'); ?></button>
  </div>
</div>

The second piece involved with loading this code in our parent view is to add the following line of code to the file at the bottom.

joomla_root/components/com_lendr/views/book/tmpl/book.php
<h2 class="page-header"><?php echo $this->book->title; ?></h2>
<div class="row-fluid">
  <div class="span3">
    <img class="media-object" src="http://covers.openlibrary.org/b/isbn/<?php echo $this->book->isbn; ?>-L.jpg">
  </div>
  <div class="span9 well well-small">
      <h3><?php echo $this->book->title; ?></h3>
      <p class="lead"><?php echo $this->book->summary; ?></p>
      <p>
        <?php if($this->book->user_id == JFactory::getUser()->id) { ?>
                <?php if ($this->book->lent) { ?>
                    <a href="#returnBookModal" class="btn btn-info btn-large"><?php echo JText::_('COM_LENDR_RETURN'); ?></a>                   
                <?php } elseif($this->book->waitlist_id > 0) { ?>
                 <a href="#lendBookModal" class="btn btn-large btn-success" data-toggle="modal" role="button" id="lendButton"><?php echo JText::_('COM_LENDR_LEND_BOOK'); ?></a>
                <?php } ?>
          <?php } else {       
              if(($this->book->waitlist_id > 0) && $this->book->user_id == JFactory::getUser()->id) { ?>
              <a href="javascript:void(0);" onclick="cancelRequest(<?php echo $this->book->book_id; ?>);" class="btn btn-danger"><?php echo JText::_('COM_LENDR_CANCEL_REQUEST'); ?></a>
              <?php } else { ?>
              <div class="btn-group">
                <a href="javascript:void(0);" onclick="borrowBookModal(<?php echo $this->book->book_id; ?>);" class="btn"><?php echo JText::_('COM_LENDR_BORROW'); ?></a>
                <button class="btn dropdown-toggle" data-toggle="dropdown">
                  <span class="caret"></span>
                </button>
                <ul class="dropdown-menu">
                  <li><a href="javascript:void(0);" onclick="addToWishlist('<?php echo $this->book->book_id; ?>');"><?php echo JText::_('COM_LENDR_ADD_WISHLIST'); ?></a></li>
                  <li><a href="#newReviewModal" data-toggle="modal"><?php echo JText::_('COM_LENDR_WRITE_REVIEW'); ?></a></li>
                </ul>
              </div>
              
              <?php } 
          } ?>
        </p>
  </div>
</div>
<br />
<div class="row-fluid">
<div class="tabbable">
  <ul class="nav nav-tabs">
    <li class="active"><a href="#detailsTab" data-toggle="tab"><?php echo JText::_('COM_LENDR_BOOK_DETAILS'); ?></a></li>
    <li><a href="#reviewsTab" data-toggle="tab"><?php echo JText::_('COM_LENDR_REVIEWS'); ?></a></li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane active" id="detailsTab">
      <h2><?php echo JText::_('COM_LENDR_BOOK_DETAILS'); ?></h2>
      <p>
        <strong><?php echo JText::_('COM_LENDR_AUTHOR'); ?></strong><br />
        <?php echo $this->book->author; ?>
      </p>
      <p>
        <strong><?php echo JText::_('COM_LENDR_PAGES'); ?></strong><br />
        <?php echo $this->book->pages; ?>
      </p>
      <p>
        <strong><?php echo JText::_('COM_LENDR_PUBLISH_DATE'); ?></strong><br />
        <?php echo $this->book->publish_date; ?>
      </p>
      <p>
        <strong><?php echo JText::_('COM_LENDR_ISBN'); ?></strong><br />
        <?php echo $this->book->isbn; ?>
      </p>
    </div>
    <div class="tab-pane" id="reviewsTab">
      <a href="#newReviewModal" role="button" data-toggle="modal" class="btn pull-right"><i class="icon icon-star"></i> <?php echo JText::_('COM_LENDR_ADD_REVIEW'); ?></a>
      <h2><?php echo JText::_('COM_LENDR_REVIEWS'); ?></h2>
      <?php echo $this->_reviewsView->render(); ?>
    </div>
  </div>
</div>
</div>

<?php echo $this->_addReviewView->render(); ?>
<?php echo $this->_lendBookView->render(); ?>
<?php echo $this->_returnBookView->render(); ?>
<?php echo $this->_modalMessage->render(); ?>

This will then render the entire secondary view on the page, but because we have written our code using Bootstrap CSS the div will be hidden until actvated.

I will continue to use this code repeatedly to load modal windows throughout Lendr. You’ll notice there are two different calls I will make to load modals. One is a direct loading of a modal window (this is when no additional data is needed in the modal) and the second uses a javascript call to load the modal window (I’ll use this second method when I want to add a variable to the modal before displaying it).

You can find the rest of the modal files (similar in structure) in our Github repository.

Step 2: Writing lending & returning functionality

Now that we have our modal windows loading and displaying additional information we want to begin adding functionality to Lendr through the use of these modals. First we’ll look at the actual core process of lending and returning. In case you’re wondering, we’re not building any advanced tracking or monitoring system into Lendr (this may be something I’ll look at in greater detail later if it’s requested). In Lendr we’ll simply let you lend a book and mark it as returned when you receive it back. There are a couple of files related to the lending and returning process. We’ll look at each in more detail below:

Controller

First we shall review the lend.php controller file which serves as the main controller for both lending and returning.

joomla_root/components/com_lendr/controllers/lend.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrControllersLend extends JControllerBase
{
  public function execute()
  {

    $return = array("success"=>false);

    $model = new LendrModelsBook();
    if ( $row = $model->lend() )
    {
      $return['success'] = true;
      $return['msg'] = JText::_('COM_LENDR_BOOK_LEND_SUCCESS');

    }else{
      $return['msg'] = JText::_('COM_LENDR_BOOK_LEND_FAILURE');
    }

    echo json_encode($return);

  }
}

Model

In the controller we’re again performing only a single task (execute) and in this task we pass the data and the request directly to the Book model which will handle the lend function call. Below is the lend function located within the book model.

joomla_root/components/com_lendr/models/book.php
public function lend($data = null)
  {
    $data = isset($data) ? $data : JRequest::get('post');

    if (isset($data['lend']) && $data['lend']==1)
    {
      $date = date("Y-m-d H:i:s");

      $data['lent'] = 1;
      $data['lent_date'] = $date;
      $data['lent_uid'] = $data['borrower_id'];

      $waitlistData = array('waitlist_id'=>$data['waitlist_id'], 'fulfilled' => 1, 'fulfilled_time' => $date, 'table' => 'Waitlist');
      $waitlistModel = new LendrModelsWaitlist();
      $waitlistModel->store($waitlistData);
    } else {
      $data['lent'] = 0;
      $data['lent_date'] = NULL;
      $data['lent_uid'] = NULL;

    }
    
    $row = parent::store($data);    
    
    return $row;

  }

In this function you’ll notice we handle both the lending and returning. Notice that when a book is lent successfully we also mark the accompanying waitlist item as fulfilled.

Javascript

This is the associated javascript functions used with lending and returning. As I mentioned earlier I am creating a function to first load the modal (so I can inject variables into the modal window). The second function is used to actually lend the book. I am using a jQuery AJAX call to pass the form data to the lend controller (listed above). If the controller/model executes successfully then the modal window is closed.

joomla_root/components/com_lendr/assets/js/lendr.js
function loadLendModal(book_id, borrower_id, borrower, waitlist_id)
{
  jQuery("#lendBookModal").modal('show');
  jQuery('#borrower_name').html(borrower);
  jQuery("#book_id").val(book_id);
  jQuery("#borrower_id").val(borrower_id);
  jQuery("#waitlist_id").val(waitlist_id);
}

function lendBook()
{
  var lendForm = {};
  jQuery("#lendForm :input").each(function(idx,ele){
    lendForm[jQuery(ele).attr('name')] = jQuery(ele).val();
  });
  
  jQuery.ajax({
    url:'index.php?option=com_lendr&controller=lend&format=raw&tmpl=component',
    type:'POST',
    data:lendForm,
    dataType:'JSON',
    success:function(data)
    {
      if ( data.success )
      {
        jQuery("#lendBookModal").modal('hide');
      }
    }
  });
}

There are several different places where you may notice the lack of error messages. We’ll be writing all of these at once in our clean up article still to come.


Step 3: Adding wishlists, waitlists, and reviews

There are three areas related to books that we are going to work on. The wishlist and waitlist functionality is pretty simple. Both of these will simply load a modal and add the specific book to either the wishlist or waitlist of the user. Again, this code is similar in most aspects to the other modal code and lender codes listed above. Because the review code has a bit more custom work than the other two we’ll focus on the code involved with the review process.

Note: You can review both the wishlist and waitlist code in the GitHub repository.

Originally the intention was to utilize the review controller for the new reviews, however upon further thought I decided that technically a new review should follow the same “add” controller as other parts of the system. This involved a bit of a rewrite on the add controller to properly route the data to the correct model.

A review is created from a modal window. The modal loads a form which holds the title and summary of the review. Hidden fields track the book and user submitting the review. The form for a new review is below.

joomla_root/components/com_lendr/views/review/tmpl/_add.php
<div id="newReviewModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="newReviewModal" aria-hidden="true">
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
    <h3 id="myModalLabel"><?php echo JText::_('COM_LENDR_ADD_REVIEW'); ?></h3>
  </div>
  <div class="modal-body">
  <div class="row-fluid">
    <form id="reviewForm">
      <input class="span12" type="text" name="title" placeholder="<?php echo JText::_('COM_LENDR_TITLE'); ?>" />
      <textarea class="span12" placeholder="<?php echo JText::_('COM_LENDR_SUMMARY'); ?>" name="review" rows="10"></textarea>
      <input type="hidden" name="user_id" value="<?php echo $this->user->id; ?>" />
      <input type="hidden" name="view" value="review" />
      <input type="hidden" name="book_id" value="<?php echo $this->book->book_id; ?>" />
      <input type="hidden" name="model" value="review" />
      <input type="hidden" name="item" value="review" />
      <input type="hidden" name="table" value="review" />
    </form>
  </div>
  </div>
  <div class="modal-footer">
    <button class="btn" data-dismiss="modal" aria-hidden="true"><?php echo JText::_('COM_LENDR_CLOSE'); ?></button>
    <button class="btn btn-primary" onclick="addReview()"><?php echo JText::_('COM_LENDR_ADD'); ?></button>
  </div>
</div>
Note: We are passing the table and other fields necessary to route to the appropriate model and function.

Below is the javascript associated with the review process.

joomla_root/components/com_lendr/assets/js/lendr.js
//add a review
function addReview()
{
  var reviewInfo = {};
  jQuery("#reviewForm :input").each(function(idx,ele){
    reviewInfo[jQuery(ele).attr('name')] = jQuery(ele).val();
  });

  jQuery.ajax({
    url:'index.php?option=com_lendr&controller=add&format=raw&tmpl=component',
    type:'POST',
    data:reviewInfo,
    dataType:'JSON',
    success:function(data)
    {
      if ( data.success ){
        console.log(data.html);
        jQuery("#review-list").append(data.html);
        jQuery("#newReviewModal").modal('hide');
      }else{

      }
    }
  });

}

In this function we perform a couple of actions. First, we use a nifty jQuery loop to get all the form data to pass through to the controller. After that submit the form and wait for the response. If the response is successful then we append the response to the review list and hide the modal window. This means I am passing the full html row back from the ajax call. Below is how that is handled in the updated controller.

joomla_root/components/com_lendr/controllers/add.php
<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrControllersAdd extends JControllerBase
{
  public function execute()
  {
    $app      = JFactory::getApplication();
    $return   = array("success"=>false);

    $modelName  = $app->input->get('model', 'Book');
    $view       = $app->input->get('view', 'Book');
    $layout     = $app->input->get('layout', '_entry');
    $item       = $app->input->get('item', 'book');

    $modelName  = 'LendrModels'.ucwords($modelName);

    $model = new $modelName();
    if ( $row = $model->store() )
    {
      $return['success'] = true;
      $return['msg'] = JText::_('COM_LENDR_SAVE_SUCCESS');
$return['html'] = LendrHelpersView::getHtml($view, $layout, $item, $row);

    }else{
      $return['msg'] = JText::_('COM_LENDR_SAVE_FAILURE');
    }

    echo json_encode($return);

  }

}

The html that is returned to the javascript is loaded from the Helper file. You’ll notice we pass the view, layout, item, and row to the getHtml function. It is necessary at this point to review the helper file function for getHtml.

joomla_root/components/com_lendr/helpers/view.php
function getHtml($view, $layout, $item, $data)
{
  $objectView = LendrHelpersView::load($view, $layout, 'phtml');
    $objectView->$item = $data;
  
ob_start();
    echo $objectView->render();
    $html = ob_get_contents();
    ob_clean();
    
return $html;
}

This view serves a dual purpose. First it loads a partial view located earlier in the View Helper file, and then once loaded it renders that view out to a variable. This variable is then returned to be passed back to the javascript which in turn will append it to the page.

This concludes the review process. View all the related files directly in the GitHub repository.

Step 4: Searching books

The search process is an interesting one to evaluate. There are again a couple of options that can be pursued. Either you can incorporate a full functioning search system into your component or you can take advantage of the system already built into Joomla in the Finder component, module, and plugins. While it may not be appropriate in all instances to use the Finder system, in the Lendr component we will take this opportunity to integrate with Finder. By integrating with Finder we will first simplify the amount of code and structure that must be added to Lendr, and secondly we will also be able to demonstrate the code necessary to create a plugin for a new content type with Finder.

The plugin I write for Finder is a simple one and certainly does not demonstrate all the typical capabilities of the plugin system. Below is the XML file associated with the Finder plugin we are writing (I call it Smart Search - Books).

joomla_root/plugins/finder/books/books.xml
<?xml version="1.0" encoding="utf-8"?>
<extension version="3.1" type="plugin" group="finder" method="upgrade">
  <name>Smart Search - Books</name>
  <author>Joomla! Project</author>
  <creationDate>March 2013</creationDate>
  <copyright>(C) 2005 - 2013 Open Source Matters. All rights reserved.</copyright>
  <license>GNU General Public License version 2 or later; see LICENSE.txt</license>
  <authorEmail>admin@joomla.org</authorEmail>
  <authorUrl>www.joomla.org</authorUrl>
  <version>3.0.0</version>
  <description></description>
  <files>
    <file plugin="books">books.php</file>
      <filename>index.html</filename>
  </files>
</extension>

This XML file will just create the standard fields to be used when installing the Lendr Books plugin. The second file necessary for the search functionality is created below.

joomla_root/plugins/finder/books/books.php
<?php
/**
 * @package     Joomla.Plugin
 * @subpackage  Finder.Books
 *
 * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_BASE') or die;

require_once JPATH_ADMINISTRATOR . '/components/com_finder/helpers/indexer/adapter.php';

/**
 * Finder adapter for Lendr Books.
 *
 * @package     Joomla.Plugin
 * @subpackage  Finder.Books
 * @since       3.0
 */
class PlgFinderBooks extends FinderIndexerAdapter
{
  /**
   * The plugin identifier.
   *
   * @var    string
   * @since  2.5
   */
  protected $context = 'Books';

  /**
   * The extension name.
   *
   * @var    string
   * @since  2.5
   */
  protected $extension = 'com_lendr';

  /**
   * The sublayout to use when rendering the results.
   *
   * @var    string
   * @since  2.5
   */
  protected $layout = 'book';

  /**
   * The type of content that the adapter indexes.
   *
   * @var    string
   * @since  2.5
   */
  protected $type_title = 'Book';

  /**
   * The table name.
   *
   * @var    string
   * @since  2.5
   */
  protected $table = '#__lendr_books';

  /**
   * The field the published state is stored in.
   *
   * @var    string
   * @since  2.5
   */
  protected $state_field = 'published';

  /**
   * Load the language file on instantiation.
   *
   * @var    boolean
   * @since  3.1
   */
  protected $autoloadLanguage = true;

  /**
   * Method to remove the link information for items that have been deleted.
   *
   * @param   string  $context  The context of the action being performed.
   * @param   JTable  $table    A JTable object containing the record to be deleted
   *
   * @return  boolean  True on success.
   *
   * @since   2.5
   * @throws  Exception on database error.
   */
  public function onFinderDelete($context, $table)
  {
    if ($context == 'com_lendr.book')
    {
      $id = $table->id;
    }
    elseif ($context == 'com_finder.index')
    {
      $id = $table->link_id;
    }
    else
    {
      return true;
    }
    // Remove the items.
    return $this->remove($id);
  }

  /**
   * Method to determine if the access level of an item changed.
   *
   * @param   string   $context  The context of the content passed to the plugin.
   * @param   JTable   $row      A JTable object
   * @param   boolean  $isNew    If the content has just been created
   *
   * @return  boolean  True on success.
   *
   * @since   2.5
   * @throws  Exception on database error.
   */
  public function onFinderAfterSave($context, $row, $isNew)
  {
    // We only want to handle books here
    if ($context == 'com_lendr.book')
    {
      // Check if the access levels are different
      if (!$isNew && $this->old_access != $row->access)
      {
        // Process the change.
        $this->itemAccessChange($row);
      }

      // Reindex the item
      $this->reindex($row->id);
    }
    return true;
  }

  /**
   * Method to reindex the link information for an item that has been saved.
   * This event is fired before the data is actually saved so we are going
   * to queue the item to be indexed later.
   *
   * @param   string   $context  The context of the content passed to the plugin.
   * @param   JTable   $row     A JTable object
   * @param   boolean  $isNew    If the content is just about to be created
   *
   * @return  boolean  True on success.
   *
   * @since   2.5
   * @throws  Exception on database error.
   */
  public function onFinderBeforeSave($context, $row, $isNew)
  {
    // We only want to handle books here
    if ($context == 'com_lendr.book')
    {
      // Query the database for the old access level if the item isn't new
      if (!$isNew)
      {
        $this->checkItemAccess($row);
      }
    }

    return true;
  }

  /**
   * Method to update the link information for items that have been changed
   * from outside the edit screen. This is fired when the item is published,
   * unpublished, archived, or unarchived from the list view.
   *
   * @param   string   $context  The context for the content passed to the plugin.
   * @param   array    $pks      A list of primary key ids of the content that has changed state.
   * @param   integer  $value    The value of the state that the content has been changed to.
   *
   * @return  void
   *
   * @since   2.5
   */
  public function onFinderChangeState($context, $pks, $value)
  {
    // We only want to handle articles here
    if ($context == 'com_lendr.book')
    {
      $this->itemStateChange($pks, $value);
    }
    // Handle when the plugin is disabled
    if ($context == 'com_plugins.plugin' && $value === 0)
    {
      $this->pluginDisable($pks);
    }
  }

  /**
   * Method to index an item. The item must be a FinderIndexerResult object.
   *
   * @param   FinderIndexerResult  $item    The item to index as an FinderIndexerResult object.
   * @param   string               $format  The item format
   *
   * @return  void
   *
   * @since   2.5
   * @throws  Exception on database error.
   */
  protected function index(FinderIndexerResult $item, $format = 'html')
  {
    // Check if the extension is enabled
    if (JComponentHelper::isEnabled($this->extension) == false)
    {
      return;
    }

    $item->setLanguage();

    $extension = ucfirst(substr($item->extension, 4));

    $item->url = $this->getURL($item->id, $item->extension, $this->layout);
    $item->route = 'index.php?option='.$this->extension.'&view=book&layout='.$this->layout.'&id='.$item->book_id;

    // Add the type taxonomy data.
    $item->addTaxonomy('Type', 'Book');

    // Add the language taxonomy data.
    $item->addTaxonomy('Language', $item->language);

    // Index the item.
    $this->indexer->index($item);
  }

  /**
   * Method to get the SQL query used to retrieve the list of books.
   *
   * @param   mixed  $sql  A JDatabaseQuery object or null.
   *
   * @return  JDatabaseQuery  A database object.
   *
   * @since   2.5
   */
  protected function getListQuery($sql = null)
  {
    $db = JFactory::getDbo();
    // Check if we can use the supplied SQL query.
    $sql = $sql instanceof JDatabaseQuery ? $sql : $db->getQuery(true);
    $sql->select('b.book_id as id, b.title, b.author, b.summary, b.pages, b.publish_date');
    $sql->from('#__lendr_books AS b');
    $sql->where($db->quoteName('b.book_id') . ' > 1');

    return $sql;
  }

  /**
   * Method to get a SQL query to load the published state
   *
   * @return  JDatabaseQuery  A database object.
   *
   * @since   2.5
   */
  protected function getStateQuery()
  {
    $sql = $this->db->getQuery(true);
    $sql->select($this->db->quoteName('b.book_id'));
    $sql->select($this->db->quoteName('b.published') . ' AS book_state');
    $sql->from($this->db->quoteName('#__lendr_books') . ' AS b');

    return $sql;
  }
}

These functions are used by Finder to index the correct table, load the data and route it correctly in the search results. Because of the level of this tutorial I am not going to go into too much detail regarding each of these functions nor will I be loading up the secondary route helpers which are often used to make the component language specific and other additional features. If you are interested in learning more about the search plugin system leave a message in the comments or contact me for more information.


Step 5: Wrapping up

In this article we’ve covered quite a few functions, structural design, and additional ideas which can be applied to any other component. We’ve looked at how to best utilize Bootstrap for modals, we’ve taken a deeper look at javascript functionality and use and then we’ve expanded our use beyond components to integration with a secondary plugin. I have purposely not included every file written or every function modified simply for the purpose of keeping the focus of this article on the thought process and concepts surrounding component development and not individual functions which may be more readily discerned from previous tutorials. If for any reason you have questions about any of the code written don’t hesitate to ask.

In the next article we will begin wrapping things up and finalizing the code. We will cover the administrator interface, proper use of live updates on the page when an AJAX call is completed, and we will begin cleaning up the code, removing unnecessary files, and looking at ways we can simplify or reduce our code.


Download

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

Download

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.

For any queries or issues. Contact us info@websparkinc.com