You are on page 1of 36

Beginning Development Wrap-up.......................................................................................................

18
Functionality...............................................................................................................................................19

Lendr - A social library system with lending,

Step 1: Model File Details and Functions...........................................................................................19


Basic Files and Functions Needed....................................................................................................19

tracking, wishlists and reviews

Step 2: Write the Models......................................................................................................................20


Step 3: Incorporate Additional Resources..........................................................................................30
Gravatar.............................................................................................................................................30

Contents

Open Library.....................................................................................................................................30

Lendr - A social library system with lending, tracking, wishlists and reviews............1

Step 4: Start View Layouts and Styles................................................................................................31

Overview.......................................................................................................................................................3

Writing the Files................................................................................................................................31

Joomla!3.0BootstrappedComponent.....................................................................................................3

Remaining layouts.............................................................................................................................36

Native Joomla! 3.0...............................................................................................................................3

Step 5: Javascript and CSS..................................................................................................................37

Responsive...........................................................................................................................................3
Made With Bootstrap.........................................................................................................................3

Conclusion.............................................................................................................................................40
Morefunctionality......................................................................................................................................40

Libraries...............................................................................................................................................3

Step 0: Make Coffee..............................................................................................................................40

Lending................................................................................................................................................3

Step 1: Create the modal windows......................................................................................................41

Reviews.................................................................................................................................................3

Step 2: Writing lending & returning functionality............................................................................45

ThePurpose..................................................................................................................................................4

Controller...........................................................................................................................................45

BeginningDevelopment................................................................................................................................4

Model..................................................................................................................................................46

Step 0: Make Coffee................................................................................................................................4

Javascript...........................................................................................................................................47

Step 1: Write basic component outline for files needed.......................................................................4

Step 3: Adding wishlists, waitlists, and reviews.................................................................................47

Component Details..............................................................................................................................4

Step 4: Searching books........................................................................................................................50

Basic Functions....................................................................................................................................4
Basic Files Needed...............................................................................................................................5

Step 5: Wrapping up.............................................................................................................................57


AdminConfiguration$Cleanup..................................................................................................................57

Step 2: Write the Database table files...................................................................................................5

Step 0: Make Coffee..............................................................................................................................57

Step 3: Begin component folder and file creation................................................................................6

Step 1: Writing the Admin Panel.........................................................................................................58

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

Component Options..........................................................................................................................62

Install Files...........................................................................................................................................7

Step 2: Code Cleanup...........................................................................................................................65

Root file (lendr.php)..........................................................................................................................10

Deletions.............................................................................................................................................65

Controllers.........................................................................................................................................11

Book List View..................................................................................................................................68

View Controllers...............................................................................................................................13

Step 3: Menus and File Cleanup..........................................................................................................70

Step 5: Create Models...........................................................................................................................15

Create Menu Links...........................................................................................................................70

Remove unnecessary files and functions.........................................................................................71

The Purpose

Step 4: Additional Suggestions.............................................................................................................71

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

Overview

Beginning Development

Joomla! 3.0 Bootstrapped Component

Step 0: Make Coffee

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

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

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.

Step 1: Write basic component outline for files needed

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.

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

Made With Bootstrap

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

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

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.

Lending

Basic Functions

Libraries

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.

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
3

starting outline will help to keep things somewhat on track. Here is the initial outline of key files
needed for Lendr.

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.

Basic Files Needed

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:

Controllers Models Views Tables Misc.


Save
List
Book
Add
Book
Default
Book
Edit
Wishlist
Library
Wishlist Install
Lend
Library
Profile
Library Router
Delete
Profile
Review
Waitlist XML
Wish
Waitlist
Waitlist
Review
Review
Review
Wishlist
Request
Default

/joomla_root/administrator/components/com_lendr/admin/install.mysql.sql
1. CREATE TABLE IF NOT EXISTS `#__lendr_books` (
2.
`book_id` int(11) NOT NULL AUTO_INCREMENT,
3.
`user_id` int(11) DEFAULT NULL,
4.
`isbn` varchar(255) DEFAULT NULL,
5.
`title` varchar(255) DEFAULT NULL,
6.
`summary` text DEFAULT NULL,
7.
`pages` varchar(55) DEFAULT NULL,
8.
`image` varchar(255) DEFAULT NULL,
9.
`publish_date` varchar(255) DEFAULT NULL,
10.
`created` datetime NOT NULL,
11.
`modified` datetime NOT NULL,
12.
`lent` tinyint(2) DEFAULT NULL,
13.
`due_date` datetime NOT NULL,
14.
`lent_uid` varchar(255) DEFAULT NULL,
15.
`published` tinyint(2) DEFAULT 0,
16.
PRIMARY KEY (`book_id`)
17. );
18.

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.

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.

/joomla_root/components/com_lendr/site/tables/book.php
1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );
2.
3.
class TableBook extends JTable
4.
{
5.
/**
6.
* Constructor
7.
*
8.
* @param object Database connector object
9.
*/
10.
function __construct( &$db ) {
11.
parent::__construct('#__lendr_books', 'book_id', $db);
12.
}
13.
}
14.

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.
1. com_lendr/
2.
admin/
3.
controllers/
4.
models/
5.
views/
6.
index.html
7.
install.mysql.sql
8.
lendr.php
9.
site/
10.
assets/

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

11.
controllers/
12.
helpers/
13.
language/
14.
models/
15.
tables/
16.
views/
17.
index.html
18.
lendr.php
19.
router.php
20. install.php
21. lendr.xml

10.

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.
1. <install>
2.
<sql>
3.
<file charset="utf8" driver="mysql">mysql.install.sql</file>
4.
</sql>
5. </install>

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.

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
1. <files folder="site">
2.
<folder>assets</folder>
3.
<folder>controllers</folder>
4.
<folder>helpers</folder>
5.
<folder>languages</folder>
6.
<folder>models</folder>
7.
<folder>tables</folder>
8.
<folder>views</folder>
9.
<filename>index.html</filename>
10.
<filename>lendr.php</filename>
11.
<filename>router.php</filename>
12. </files>

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

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.
1.

<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.
1. <languages folder="site">
2.
<language tag="en-GB">languages/en-GB/en-GB.com_lendr.ini</language>
3. </languages>

lendr.xml
1. <extension type="component" version="2.5.0" method="upgrade">
2.
<name>COM_LENDR</name>
3.
<creationDate>2013-01-31</creationDate>
4.
<author>Spark</author>
5.
<authorEmail>info@websparkinc.com</authorEmail>
6.
<authorUrl>http://lendr.websparkinc.com</authorUrl>
7.
<copyright>Copyright Info</copyright>
8.
<license>License Info</license>
9.
<version>1.0.0</version>

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).

<description>COM_LENDR_DESCRIPTION</description>

1. <administration>
2.
<menu
link="option=com_lendr"
img="components/com_lendr/assets/images/lendr_icon.png">COM_LENDR</menu
>
3.
<submenu>
4.
<menu
view="settings"
img="components/com_lendr/assets/images/settings_icon.png"
5.
alt="LENDR/Settings">COM_LENDR_SETTINGS</menu>
6.
</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.

1. /**
2. * Method to install the component
3. *
4. * @param mixed
$parent
The class calling this method
5. * @return void
6. */
7. function install($parent)
8. {
9.
echo JText::_('COM_LENDR_INSTALL_SUCCESSFULL');
10. }

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

Notice that a submenu can be defined but is not necessary.


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

1. /**
2. * Method to update the component
3. *
4. * @param mixed $parent
The class calling this method
5. * @return void
6. */
7. function update($parent)
8. {
9.
echo JText::_('COM_LENDR_UPDATE_SUCCESSFULL');
10. }

<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
GB.com_lendr.ini</language>
13.
<language
GB.com_lendr.sys.ini</language>
14.
</languages>
15.
16.
</administration>
17. </extension>

tag="en-GB">languages/en-GB/entag="en-GB">languages/en-GB/en-

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.
1. /**
2. * method to run before an install/update/uninstall method
3. *
4. * @param mixed $parent
The class calling this method
5. * @return void
6. */
7. function preflight($type, $parent)
8. {
9.
...
10. }
11.
12. function postflight($type, $parent)
13. {
14.
...
15. }

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.
install.php
1.
2.
3.
4.

<?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.

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)

10

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.
1. <?php // No direct access
2. defined( '_JEXEC' ) or die( 'Restricted access' );
3.
4. //sessions
5. jimport( 'joomla.session.session' );
6.
7. //load tables
8. JTable::addIncludePath(JPATH_COMPONENT.'/tables');
9.
10. //load classes
11. JLoader::registerPrefix('Lendr', JPATH_COMPONENT);
12.
13. //Load plugins
14. JPluginHelper::importPlugin('lendr');
15.
16. //application
17. $app = JFactory::getApplication();
18.
19. // Require specific controller if requested
20. if($controller = $app->input->get('controller','default')) {
21.
require_once (JPATH_COMPONENT.'/controllers/'.$controller.'.php');
22. }
23.
24. // Create the controller
25. $classname = 'LendrController'.$controller;
26. $controller = new $classname();
27.
28. // Perform the Request task
29. $controller->execute();

edit.php
joomla_root/components/com_lender/controllers/edit.php
1. <?php // no direct access
2. defined( '_JEXEC' ) or die( 'Restricted access' );
3.
4. class LendrControllersEdit extends LendrControllersDefault
5. {
6.
function execute()
7.
{
8.
$app = JFactory::getApplication();
9.
$viewName = $app->input->get('view');
10.
$app->input->set('layout','edit');
11.
$app->input->set('view', $viewName);
12.
13.
//display view
14.
return parent::execute();
15.
}
16. }

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.
default.php
joomla_root/components/com_lendr/controllers/default.php
1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );
2.
3. class LendrControllersDefault extends JControllerBase
4. {
5.
public function execute()
6.
{
7.
// Get the application
8.
$app = $this->getApplication();
9.
10.
// Get the document object.
11.
$document
= $app->getDocument();
12.
13.
$viewName
= $app->input->getWord('view', 'dashboard');
14.
$viewFormat
= $document->getType();
15.
$layoutName
= $app->input->getWord('layout', 'default');
16.
17.
$app->input->set('view', $viewName);
18.
19.
// Register the layout paths for the view
20.
$paths = new SplPriorityQueue;
21.
$paths->insert(JPATH_COMPONENT . '/views/' . $viewName . '/tmpl',
'normal');

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

11

12

22.
23.

$viewClass
=
'LendrViews'
.
ucfirst($viewName)
ucfirst($viewFormat);
24.
$modelClass = 'LendrModels' . ucfirst($viewName);
25.
26.
if (false === class_exists($modelClass))
27.
{
28.
$modelClass = 'LendrModelsDefault';
29.
}
30.
31.
$view = new $viewClass(new $modelClass, $paths);
32.
$view->setLayout($layoutName);
33.
34.
// Render our view.
35.
echo $view->render();
36.
37.
return true;
38.
}
39. }

6.
{
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20. }
21.

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.

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

raw.php
joomla_root/components/com_lendr/views/book/raw.php
1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );
2.
3. class LendrViewsBookRaw extends JViewHtml
4. {
5.
function render()
6.
{
7.
$app = JFactory::getApplication();
8.
$type = $app->input->get('type');
9.
$id = $app->input->get('id');
10.
$view = $app->input->get('view');
11.
12.
//retrieve task list from model
13.
$model = new LendrModelBook();
14.
15.
$this->book = $model->getBook($id,$view,FALSE);
16.
17.
//display
18.
echo $this->book;
19.
}
20. }
21.

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

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.

1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );


2.
3. class LendrViewsBookHtml extends JViewHtml
4. {
5.
function render()

13

//retrieve task list from model


$model = new LendrModelBook();

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.

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.

html.php
joomla_root/components/com_lendr/views/book/html.php

$app = JFactory::getApplication();
$type = $app->input->get('type');
$id = $app->input->get('id');
$view = $app->input->get('view');

14

3.
4. class LendrModelsDefault extends JModelBase
5. {
6.
var $__state_set = null;
7.
var $_total
= null;
8.
var $_pagination = null;
9.
var $_db
= null;
10.
var $id
= null;
11.
12.
function __construct()
13.
{
14.
parent::__construct();
15.
$this->_db = JFactory::getDBO();
16.
17.
$app = JFactory::getApplication();
18.
$ids = $app->input->get("cids",null,'array');
19.
20.
$id = $app->input->get("id");
21.
if ( $id && $id > 0 ){
22.
$this->id = $id;
23.
}else if ( count($ids) == 1 ){
24.
$this->id = $ids[0];
25.
}else{
26.
$this->id = $ids;
27.
}
28.
}
29.
30.
/**
31.
* Modifies a property of the object, creating it if it does not
already exist.
32.
*
33.
* @param
string $property The name of the property.
34.
* @param
mixed
$value
The value of the property to set.
35.
*
36.
* @return mixed Previous value of the property.
37.
*
38.
* @since
11.1
39.
*/
40.
public function set($property, $value = null)
41.
{
42.
$previous = isset($this->$property) ? $this->$property : null;
43.
$this->$property = $value;
44.
45.
return $previous;
46.
}
47.
48.
/**
49.
* Gets an array of objects from the results of database query.
50.
*
51.
* @param
string
$query
The query.
52.
* @param
integer $limitstart Offset.
53.
* @param
integer $limit
The number of records.
54.
*
55.
* @return array An array of results.
56.
*
57.
* @since
11.1
58.
*/

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

book.php
joomla_root/components/com_lendr/models/book.php
1. <?php // no direct access
2. defined( '_JEXEC' ) or die( 'Restricted access' );
3.
4. class LendrModelsBook extends LendrModelsDefault
5. {
6.
function __construct()
7.
{
8.
parent::__construct();
9.
}
10.
11.
function store()
12.
{
13.

14.
}
15.
16.
function getBook()
17.
{
18.

19.
}
20.
21.
function getBooks()
22.
{
23.

24.
}
25.
26.
function populateState()
27.
{
28.

29.
}
30. }

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.
default.php
joomla_root/components/com_lendr/models/default.php
1. <?php // no direct access
2. defined( '_JEXEC' ) or die( 'Restricted access' );

15

16

59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.

protected function _getList($query, $limitstart = 0, $limit = 0)


{
$db = JFactory::getDBO();
$db->setQuery($query, $limitstart, $limit);
$result = $db->loadObjectList();

114.
if ( empty ( $this->_total ) )
115.
{
116.
$query = $this->_buildQuery();
117.
$this->_total = $this->_getListCount($query);
118.
}
119.
120.
return $this->_total;
121.
}
122.
123.
/**
124.
* Generate pagination
125.
*/
126.
function getPagination()
127.
{
128.
// Lets load the content if it doesn't already exist
129.
if (empty($this->_pagination))
130.
{
131.
$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));
132.
}
133.
134.
return $this->_pagination;
135.
}
136. }

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
92.
*
93.
* @since
11.1
94.
*/
95.
public function getState($property = null, $default = null)
96.
{
97.
if (!$this->__state_set)
98.
{
99.
// Protected method to auto-populate the model state.
100.
$this->populateState();
101.
102.
// Set the model state set flag to true.
103.
$this->__state_set = true;
104.
}
105.
106.
return $property === null ? $this->state : $this->state>get($property, $default);
107.
}
108.
109.
/**
110.
* Get total number of rows for pagination
111.
*/
112.
function getTotal()
113.
{

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.

17

18

Download

Model

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

Review

Functionality
Youre 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 weve begun in previous
tutorials. If youre 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 dont forget your espresso.

Once weve defined a brief overview of the models and functions well need we can begin
writing the models. Its 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. Dont be afraid to revisit this list repeatedly as we progress and evaluate
things. Its 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 dont want to rewrite the same code over and over, the minute we find were starting
down that path its time to consider how we abstract the code to a common model. Lets start
writing our models.

Step 1: Model File Details and Functions

Step 2: Write the Models

Now that weve 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:

There are a handful of models which make up this Lendr component but we dont have the time
to write all of them out here. Well 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. Well 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
well 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
dont want to re-write the same functions in each model.

Basic Files and Functions Needed


Model

Default

Book
Wishlist
Profile
Library
Waitlist

Functions
save
delete
set
get
getItem
listItems
getState
getTotal
getPagination
_buildQuery
_buildWhere
_buildQuery
_buildWhere
_buildQuery
_buildWhere
_buildQuery
_buildWhere
_buildQuery
_buildWhere

Model Files

default.php
1. <?php // no direct access
2. defined( '_JEXEC' ) or die( 'Restricted access' );
3.
4. class LendrModelsDefault extends JModelBase
5. {
6.
7.
protected $__state_set
= null;
8.
protected $_total
= null;
9.
protected $_pagination
= null;
10.
protected $_db
= null;
11.
protected $id
= null;
12.
protected $limitstart
= 0;
13.
protected $limit
= 10;
14.

19

Functions
_buildQuery
_buildWhere

20

15. First we set some class level variables.


This will allow us to
reference them easily throughout the file.
16.
17.
function __construct()
18.
{
19.
20.
parent::__construct();
21.
}
22.
23.
public function store($data=null)
24.
{
25.
$data = $data ? $data : JRequest::get('post');
26.
$row = JTable::getInstance($data['table'],'Table');
27.
28.
$date = date("Y-m-d H:i:s");
29.
30.
// Bind the form fields to the table
31.
if (!$row->bind($data))
32.
{
33.
return false;
34.
}
35.
36.
$row->modified = $date;
37.
if ( !$row->created )
38.
{
39.
$row->created = $date;
40.
}
41.
42.
// Make sure the record is valid
43.
if (!$row->check())
44.
{
45.
return false;
46.
}
47.
48.
// Store the web link table to the database
49.
if (!$row->store())
50.
{
51.
return false;
52.
}
53.
54.
return $row;
55.
}

7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.

3.
4.
5.
6.

public function get($property, $default = null)


{
return isset($this->$property) ? $this->$property : $default;
}

1. /**
2. * Build a query, where clause and return an object
3. *
4. */
5. public function getItem()
6. {
7.
$db = JFactory::getDBO();
8.
9.
$query = $this->_buildQuery();
10.
$this->_buildWhere($query);
11.
$db->setQuery($query);
12.
13.
$item = $db->loadObject();
14.
15.
return $item;
16. }
17.
18. /**
19. * Build query and where for protected _getList function and return a
list
20. *
21. * @return array An array of results.
22. */
23. public function listItems()
24. {
25.
$query = $this->_buildQuery();
26.
$query = $this->_buildWhere($query);
27.
28.
$list = $this->_getList($query, $this->limitstart, $this->limit);
29.
30.
return $list;
31. }

/**
* 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.
*

21

return $previous;
}

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.

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.
1.
2.

* @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;

22

6.

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.

7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.

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.
1. /**
2. * Gets an array of objects from the results of database query.
3. *
4. * @param
string
$query
The query.
5. * @param
integer $limitstart Offset.
6. * @param
integer $limit
The number of records.
7. *
8. * @return array An array of results.
9. *
10. * @since
11.1
11. */
12. protected function _getList($query, $limitstart = 0, $limit = 0)
13. {
14.
$db = JFactory::getDBO();
15.
$db->setQuery($query, $limitstart, $limit);
16.
$result = $db->loadObjectList();
17.
18.
return $result;
19. }
20.
21. /**
22. * Returns a record count for the query
23. *
24. * @param
string $query The query.
25. *
26. * @return integer Number of rows for query
27. *
28. * @since
11.1
29. */
30. protected function _getListCount($query)
31. {
32.
$db = JFactory::getDBO();
33.
$db->setQuery($query);
34.
$db->query();
35.
36.
return $db->getNumRows();
37. }

/* Method to get model state variables


*
* @param
string $property Optional parameter name
* @param
mixed
$default
Optional default value
*

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.
23

// Set the model state set flag to true.


$this->__state_set = true;
}

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


22.
}
23.
24.
/**
25.
* Get total number of rows for pagination
26.
*/
27.
function getTotal()
28.
{
29.
if ( empty ( $this->_total ) )
30.
{
31.
$query = $this->_buildQuery();
32.
$this->_total = $this->_getListCount($query);
33.
}
34.
35.
return $this->_total;
36.
}
37.
38.
/**
39.
* Generate pagination
40.
*/
41.
function getPagination()
42.
{
43.
// Lets load the content if it doesn't already exist
44.
if (empty($this->_pagination))
45.
{
46.
$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));
47.
}
48.
49.
return $this->_pagination;
50.
}
51. }

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.
1.
2.
3.
4.
5.

* @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();

24

52.
protected function _buildWhere(&$query)
53.
{
54.
55.
if(is_numeric($this->_book_id))
56.
{
57.
$query->where('b.book_id = ' . (int) $this->_book_id);
58.
}
59.
60.
if(is_numeric($this->_user_id))
61.
{
62.
$query->where('b.user_id = ' . (int) $this->_user_id);
63.
}
64.
65.
if(is_numeric($this->_library_id))
66.
{
67.
$query->where('b.library_id = ' . (int) $this->_library_id);
68.
}
69.
70.
if($this->_waitlist)
71.
{
72.
$query->where('w.waitlist_id > 0');
73.
}
74.
75.
$query->where('b.published = ' . (int) $this->_published);
76.
77.
return $query;
78.
}
79. }

book.php
joomla_root/components/com_lendr/models/book.php
1. <?php // no direct access
2.
3. defined( '_JEXEC' ) or die( 'Restricted access' );
4.
5. class LendrModelsBook extends LendrModelsDefault
6. {
7.
8.
/**
9.
* Protected fields
10.
**/
11.
var $_book_id
= null;
12.
var $_user_id
= null;
13.
var $_library_id = null;
14.
var $_pagination = null;
15.
var $_total
= null;
16.
var $_published
= 1;
17.
var $_waitlist
= FALSE;
18.
19.
20.
function __construct()
21.
{
22.
parent::__construct();
23.
}
24.
25.
/**
26.
* Builds the query to be used by the book model
27.
* @return
object Query object
28.
*
29.
*
30.
*/
31.
protected function _buildQuery()
32.
{
33.
$db = JFactory::getDBO();
34.
$query = $db->getQuery(TRUE);
35.
36.
$query->select('b.book_id, b.user_id, b.isbn, b.title, b.author,
b.summary, b.pages,
37.
b.publish_date, b.lent, b.lent_date, b.due_date');
38.
$query->from('#__lendr_books as b');
39.
40.
$query->select('w.waitlist_id');
41.
$query->leftjoin('#__lendr_waitlists
as
w
on
w.book_id
=
b.book_id');
42.
43.
return $query;
44.
}
45.
46.
/**
47.
* Builds the filter for the query
48.
* @param
object Query object
49.
* @return
object Query object
50.
*
51.
*/

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.
library.php
joomla_root/components/com_lendr/models/library.php
1. <?php // no direct access
2. defined( '_JEXEC' ) or die( 'Restricted access' );
3.
4. class LendrModelsLibrary extends LendrModelsDefault
5. {
6.
7.
//Define class level variables
8.
var $_library_id = null;
9.
var $_user_id
= null;
10.
var $_published
= 1;
11.
12.
function __construct()
13.
{
14.
parent::__construct();
15.
16.
$app = JFactory::getApplication();
17.
$this->_library_id = $app->input->get('library_id',null);

25

26

18.
19.

$this->_user_id =

$app->input->get('user_id',JFactory::getUser()-

3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37. }

>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 users ID.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

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.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.

function listItems()
{
$bookModel = new LendrModelsBook();
$libraries = parent::listItems();
$n = count($libraries);
for($i=0;$i<$n;$i++)
{
$library = $libraries[$i];

$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.

$bookModel->_library_id = $library->id;
$library->books = $bookModel->listItems();
}

profile.php
joomla_root/components/com_lendr/models/profile.php

return $libraries;
}

1. <?php // no direct access


2. defined( '_JEXEC' ) or die( 'Restricted access' );
3.
4. class LendrModelsProfile extends LendrModelsDefault
5. {
6.
7.
//Define class level variables
8.
var $_user_id
= null;
9.
10.
function __construct()
11.
{
12.

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.
1. protected function _buildQuery()
2.
{

27

$db = JFactory::getDBO();
$query = $db->getQuery(TRUE);

28

13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.

$app = JFactory::getApplication();

parent::__construct();

$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.

function getItem()
{

1.
protected function _buildWhere($query)
2.
{
3.
$query->group("u.id");
4.
5.
return $query;
6.
}
7.
8. }

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

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.

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


: FALSE;

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

return $profile;
}

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.

protected function _buildQuery()


{
$db = JFactory::getDBO();
$query = $db->getQuery(TRUE);

$query->select("u.id,
u.username,
u.name,
u.registerDate");
7.
$query->from("#__users as u");
8.
9.
$query->select("COUNT(b.book_id) as totalBooks");

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.

Important: Be sure to enable the Joomla! 3 profile plugin listed in the extension manager.

Open Library
u.email,

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

29

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

In this function we have no reference to the parent getItem. The profile is unique in its 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.

1.
2.
3.
4.
5.
6.

10.
11.
12.
13.
14.
15.
16.

//If no User ID is set to current logged in user


$this->_user_id
=
$app->input->get('profile_id',
JFactory::getUser()->id);

30

9.
10.
11.
12.
13.
14.
15.
16.
17.
18.

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 its clear
we need to update where the user starts. We have changed the following line of code in the
default.php controller:

1. $layoutName

This will now send the user to the list view of the profiles. Thats the first view well look at. In
our views folder we have a profile folder with the following structure in it:
Folder Structure
1. profile
2.
tmpl
3.
_entry.php
4.
index.html
5.
list.php
6.
profile.php
7.
html.php
8.
index.html
9.
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.

case "profile":
$this->profile = $profileModel->getItem();
=

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

$this->_waitlistView
LendrHelpersView::load('Waitlist','_waitlist','phtml');
24.
$this->_waitlistView->waitlist = $this->profile->waitlist;
25.
break;
26.
27.
case "list":
28.
default:
29.
$this->profiles = $profileModel->listItems();
30.
$this->_profileListView
LendrHelpersView::load('Profile','_entry','phtml');
31.
break;
32.
33.
}
34.
35.
//display
36.
return parent::render();
37.
}
38. }

21.
22.
23.

= $app->input->getWord('layout', 'list');

switch($layout) {

$this->_addBookView
LendrHelpersView::load('Book','_add','phtml');

19.
20.

joomla_root/components/com_lendr/controllers/default.php

//retrieve task list from model


$profileModel = new LendrModelsProfile();

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.

Individual Files

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.

html.php
joomla_root/components/com_lendr/views/profile/html.php

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.

1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );


2.
3. class LendrViewsProfileHtml extends JViewHtml
4. {
5.
function render()
6.
{
7.
$app = JFactory::getApplication();
8.
$layout = $app->input->get('layout');

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

31

32

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.
view.php
joomla_root/components/com_lendr/helpers/view.php
1.
2.
3.
4.
5.
6.
7.

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

class LendrHelpersView
{
function load($viewName, $layoutName='default', $viewFormat='html',
$vars=null)
8.
{
9.
// Get the application
10.
$app = JFactory::getApplication();
11.
12.
$app->input->set('view', $viewName);
13.
14.
// Register the layout paths for the view
15.
$paths = new SplPriorityQueue;
16.
$paths->insert(JPATH_COMPONENT . '/views/' . $viewName . '/tmpl',
'normal');
17.
18.
$viewClass
=
'LendrViews'
.
ucfirst($viewName)
.
ucfirst($viewFormat);
19.
$modelClass = 'LendrModels' . ucfirst($viewName);
20.
21.
if (false === class_exists($modelClass))
22.
{
23.
$modelClass = 'LendrModelsDefault';
24.
}
25.
26.
$view = new $viewClass(new $modelClass, $paths);
27.
28.
$view->setLayout($layoutName);
29.
30.
if(isset($vars))
31.
{
32.
foreach($vars as $varName => $var)
33.
{
34.
$view->$varName = $var;
35.
}
36.
}
37.
38.
return $view;
39.
}
40. }

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 dont 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.
phtml.php
joomla_root/components/com_lendr/views/profile/phtml.php
1. <?php
2.
3. // no direct access
4. defined( '_JEXEC' ) or die( 'Restricted access' );
5.
6. //Display partial views
7. class LendrViewsProfilePhtml extends JViewHTML
8. {
9.
function render()
10.
{
11.
return parent::render();
12.
}
13. }

_entry.php
joomla_root/components/com_lendr/views/profile/tmpl/_entry.php
1. <div class="media well well-small span6">
2.
<a
class="pull-left"
href="<?php
echo
JRoute::_('index.php?option=com_lendr&view=profile&layout=profile&id='.
$this->profile->id); ?>">
3.
<img
src="http://www.gravatar.com/avatar/<?php
echo
md5(strtolower(trim($this->profile->email))); ?>?s=60" />
4.
</a>
5.
<div class="media-body">
6.
<h4
class="media-heading"><a
href="<?php
echo
JRoute::_('index.php?option=com_lendr&view=profile&layout=profile&profi
le_id='.$this->profile->id);
?>"><?php
echo
$this->profile->name;
?></a></h4>
7.
<p><strong><?php
echo
JText::_('COM_LENDR_TOTAL_BOOKS');
?></strong>: <?php echo $this->profile->totalBooks; ?><br />
8.
<strong><?php
echo
JText::_('COM_LENDR_TOTAL_REVIEWS');
?></strong>: <?php echo $this->profile->totalReviews; ?>
9.
</p>
10.
</div>

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

33

34

17.
18.
19.
20.
21.
22.

</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>
23.
<li><a
href="#wishlistTab"
data-toggle="tab"><?php
echo
JText::_('COM_LENDR_WISHLIST'); ?></a></li>
24.
<li><a
href="#waitlistTab"
data-toggle="tab"><?php
echo
JText::_('COM_LENDR_WAITLIST'); ?></a></li>
25.
</ul>
26.
<div class="tab-content">
27.
<div class="tab-pane active" id="libraryTab">
28.
<?php if($this->profile->isMine) { ?>
29.
<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>
30.
<?php } ?>
31.
<h2><?php echo JText::_('COM_LENDR_LIBRARY'); ?></h2>
32.
<?php echo $this->_libraryView->render(); ?>
33.
</div>
34.
<div class="tab-pane" id="wishlistTab">
35.
<h2><?php echo JText::_('COM_LENDR_WISHLIST'); ?></h2>
36.
</div>
37.
<div class="tab-pane" id="waitlistTab">
38.
<h2><?php echo JText::_('COM_LENDR_WAITLIST'); ?></h2>
39.
<?php echo $this->_waitlistView->render(); ?>
40.
</div>
41.
</div>
42. </div>
43. </div>
44.
45. <?php echo $this->_addBookView->render(); ?>

11. </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.
list.php
joomla_root/components/com_lendr/views/profile/tmpl/list.php
1. <h2
class="page-header"><?php
echo
JText::_('COM_LENDR_PROFILES');
?></h2>
2. <div class="row-fluid">
3.
<?php for($i=0, $n = count($this->profiles);$i<$n;$i++) {
4.
$this->_profileListView->profile = $this->profiles[$i];
5.
echo $this->_profileListView->render();
6.
} ?>
7. </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.
profile.php
1. <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>
2. <h2 class="page-header"><?php echo $this->profile->name; ?></h2>
3. <div class="row-fluid">
4.
<div class="span3">
5.
<img
src="http://www.gravatar.com/avatar/<?php
echo
md5(strtolower(trim($this->profile->email))); ?>?s=180" />
6.
</div>
7.
<div class="span9 well well-small">
8.
<dl class="dl-horizontal">
9.
<dt><?php echo JText::_('COM_LENDR_PROFILE_NAME'); ?></dt>
10.
<dd><?php echo $this->profile->name; ?></dd>
11.
<dt><?php echo JText::_('COM_LENDR_PROFILE_JOIN'); ?></dt>
12.
<dd><?php echo JHtml::_('date', $this->profile->registerDate,
JText::_('DATE_FORMAT_LC3')); ?></dd>
13.
<dt><?php echo JText::_('COM_LENDR_PROFILE_BIO'); ?></dt>
14.
<dd><?php
if(isset($this->profile->details['aboutme']))
echo
$this->profile->details['aboutme']; ?></dd>
15.
</dl>
16.
</div>

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

35

36

Weve reviewed all the layouts in the profile tab but through the process weve 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.

Now that we have javascript and CSS files included we can begin to add functions as we need
them. The first function well add is in relation to the new modal weve 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

Step 5: Javascript and CSS

1. //add a book
2. function addBook()
3. {
4.
var bookInfo = {};
5.
jQuery("#bookForm :input").each(function(idx,ele){
6.
bookInfo[jQuery(ele).attr('name')] = jQuery(ele).val();
7.
});
8.
9.
jQuery.ajax({
10.
url:'index.php?option=com_lendr&controller=add&format=raw&tmpl=componen
t',
11.
type:'POST',
12.
data:bookInfo,
13.
dataType:'JSON',
14.
success:function(data)
15.
{
16.
if ( data.success ){
17.
jQuery("#book-list").append(data.html);
18.
jQuery("#newBookModal").modal('hide');
19.
}else{
20.
21.
}
22.
}
23.
});
24. }

The last piece well 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 dont 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
1. <?php
2. // no direct access
3. defined('_JEXEC') or die('Restricted access');
4.
5. class LendrHelpersStyle
6. {
7.
function load()
8.
{
9.
$document = JFactory::getDocument();
10.
11.
//stylesheets
12.
$document>addStylesheet(JURI::base().'components/com_lendr/assets/css/style.css'
);
13.
14.
//javascripts
15.
$document>addScript(JURI::base().'components/com_lendr/assets/js/lendr.js');
16.
}
17. }

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:

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/lendr.php
1. //Load styles and javascripts
2. LendrHelpersStyle::load();

joomla_root/components/com_lendr/controllers/add.php
37

38

1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );


2.
3. class LendrControllersAdd extends JControllerBase
4. {
5.
public function execute()
6.
{
7.
8.
$return = array("success"=>false);
9.
10.
$model = new LendrModelsBook();
11.
if ( $row = $model->store() )
12.
{
13.
$return['success'] = true;
14.
$return['msg'] = JText::_('COM_LENDR_BOOK_SAVE_SUCCESS');
15.
16.
$bookView = LendrHelpersView::load('Book','_entry','phtml');
17.
$bookView->book = $row;
18.
19.
ob_start();
20.
echo $bookView->render();
21.
$html = ob_get_contents();
22.
ob_clean();
23.
24.
$return['html'] = $html;
25.
}else{
26.
$return['msg'] = JText::_('COM_LENDR_BOOK_SAVE_FAILURE');
27.
}
28.
echo json_encode($return);
29.
}
30. }

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.

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.
1.
2.
3.
4.
5.
6.
7.

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.

More 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 its important to continue the habits weve started from the
beginning. Find your favorite cup and fill it with the liquid calmness of your choice. If this is
new to you, Id recommend reviewing the previous articles, start at the beginning and work your
way through the series so far. Ill wait here for you.
Ok! welcome back. In this article well take a look at some of the other features involved in
Lendr. You will notice Ive 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.

39

40

17.

Step 1: Create the modal windows

18.
19.
20.
21.

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 weve added more modal windows. Weve done all our
modal windows the same way (utilizing the Bootstrap technique). In order to give a bit more
detail Ill explain the two options and then detail the option weve 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.
Ive 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.
Lets look at the lines of code necessary to include a modal into our page. First, well 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.

22.
23.
24.
25.

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.
_lend.php
The following file is the file we are loading as our partial template.
joomla_root/components/com_lendr/views/book/tmpl/_lend.php
1. <div
id="lendBookModal"
class="modal
hide
fade"
tabindex="-1"
role="dialog" aria-labelledby="lendBookModalLabel" aria-hidden="true">
2.
<div class="modal-header">
3.
<button type="button" class="close" data-dismiss="modal" ariahidden="true"></button>
4.
<h3 id="myModalLabel"><?php echo JText::_('COM_LENDR_LEND_BOOK');
?></h3>
5.
</div>
6.
<div class="modal-body">
7.
<div class="row-fluid">
8.
<form id="lendForm">
9.
<div class="alert alert-info">
10.
<?php
echo
JText::_('COM_LENDR_LEND_BOOK_TO');
?>
<span
id="borrower_name"></span>
11.
</div>
12.
<div id="book-modal-info" class="media"></div>
13.
<input type="hidden" name="book_id" id="bookid" value="<?php
echo $this->book->book_id; ?>" />
14.
<input
type="hidden"
name="user_id"
value="<?php
echo
JFactory::getUser()->id; ?>" />
15.
<input type="hidden" name="table" value="Book" />

41

$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');
26.
$this->_reviewsView->reviews = $this->book->reviews;
27.
28.
$this->_modalMessage
LendrHelpersView::load('Profile','_message','phtml');
29.
30.
//display
31.
return parent::render();
32.
}
33. }

html.php
/joomla_root/components/com_lendr/views/book/html.php
1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );
2.
3. class LendrViewsBookHtml extends JViewHtml
4. {
5.
function render()
6.
{
7.
$app = JFactory::getApplication();
8.
9.
//retrieve task list from model
10.
$model = new LendrModelsBook();
11.
$this->book = $model->getItem();
12.
13.
$this->_addReviewView
LendrHelpersView::load('Review','_add','phtml');
14.
$this->_addReviewView->book = $this->book;
15.
$this->_addReviewView->user = JFactory::getUser();
16.

$this->_lendBookView = LendrHelpersView::load('Book', '_lend',


'phtml');
$this->_lendBookView->borrower = $this->book->waitlist_user;
$this->_lendBookView->book = $this->book;

42

16.

<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; ?>" />
18.
<input type="hidden" name="lend" value="1" />
19.
</form>
20.
</div>
21.
</div>
22.
<div class="modal-footer">
23.
<button class="btn" data-dismiss="modal" aria-hidden="true"><?php
echo JText::_('COM_LENDR_CLOSE'); ?></button>
24.
<button class="btn btn-primary" onclick="lendBook();"><?php echo
JText::_('COM_LENDR_LEND'); ?></button>
25.
</div>
26. </div>

22.

<button
class="btn
dropdown-toggle"
datatoggle="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>
27.
<li><a
href="#newReviewModal"
datatoggle="modal"><?php
echo
JText::_('COM_LENDR_WRITE_REVIEW');
?></a></li>
28.
</ul>
29.
</div>
30.
31.
<?php }
32.
} ?>
33.
</p>
34.
</div>
35. </div>
36. <br />
37. <div class="row-fluid">
38. <div class="tabbable">
39.
<ul class="nav nav-tabs">
40.
<li class="active"><a href="#detailsTab" data-toggle="tab"><?php
echo JText::_('COM_LENDR_BOOK_DETAILS'); ?></a></li>
41.
<li><a
href="#reviewsTab"
data-toggle="tab"><?php
echo
JText::_('COM_LENDR_REVIEWS'); ?></a></li>
42.
</ul>
43.
<div class="tab-content">
44.
<div class="tab-pane active" id="detailsTab">
45.
<h2><?php echo JText::_('COM_LENDR_BOOK_DETAILS'); ?></h2>
46.
<p>
47.
<strong><?php
echo
JText::_('COM_LENDR_AUTHOR');
?></strong><br />
48.
<?php echo $this->book->author; ?>
49.
</p>
50.
<p>
51.
<strong><?php echo JText::_('COM_LENDR_PAGES'); ?></strong><br
/>
52.
<?php echo $this->book->pages; ?>
53.
</p>
54.
<p>
55.
<strong><?php
echo
JText::_('COM_LENDR_PUBLISH_DATE');
?></strong><br />
56.
<?php echo $this->book->publish_date; ?>
57.
</p>
58.
<p>
59.
<strong><?php echo JText::_('COM_LENDR_ISBN'); ?></strong><br
/>
60.
<?php echo $this->book->isbn; ?>
61.
</p>
62.
</div>
63.
<div class="tab-pane" id="reviewsTab">
64.
<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>
65.
<h2><?php echo JText::_('COM_LENDR_REVIEWS'); ?></h2>

17.

23.
24.
25.
26.

book.php
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
1. <h2 class="page-header"><?php echo $this->book->title; ?></h2>
2. <div class="row-fluid">
3.
<div class="span3">
4.
<img
class="media-object"
src="http://covers.openlibrary.org/b/isbn/<?php echo $this->book->isbn;
?>-L.jpg">
5.
</div>
6.
<div class="span9 well well-small">
7.
<h3><?php echo $this->book->title; ?></h3>
8.
<p class="lead"><?php echo $this->book->summary; ?></p>
9.
<p>
10.
<?php if($this->book->user_id == JFactory::getUser()->id) { ?>
11.
<?php if ($this->book->lent) { ?>
12.
<a href="#returnBookModal" class="btn btn-info
btn-large"><?php echo JText::_('COM_LENDR_RETURN'); ?></a>
13.
<?php } elseif($this->book->waitlist_id > 0) { ?>
14.
<a href="#lendBookModal" class="btn btn-large btnsuccess" data-toggle="modal" role="button" id="lendButton"><?php echo
JText::_('COM_LENDR_LEND_BOOK'); ?></a>
15.
<?php } ?>
16.
<?php } else {
17.
if(($this->book->waitlist_id
>
0)
&&
$this->book>user_id == JFactory::getUser()->id) { ?>
18.
<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>
19.
<?php } else { ?>
20.
<div class="btn-group">
21.
<a
href="javascript:void(0);"
onclick="borrowBookModal(<?php
echo
$this->book->book_id;
?>);"
class="btn"><?php echo JText::_('COM_LENDR_BORROW'); ?></a>

43

44

66.
67.
68.
69.
70.
71.
72.
73.
74.
75.

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


</div>
</div>
</div>
</div>
<?php
<?php
<?php
<?php

echo
echo
echo
echo

10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
}
23. }

$this->_addReviewView->render(); ?>
$this->_lendBookView->render(); ?>
$this->_returnBookView->render(); ?>
$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. Youll
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 (Ill 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.

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

Model
In the controller were 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
1. public function lend($data = null)
2.
{
3.
$data = isset($data) ? $data : JRequest::get('post');
4.
5.
if (isset($data['lend']) && $data['lend']==1)
6.
{
7.
$date = date("Y-m-d H:i:s");
8.
9.
$data['lent'] = 1;
10.
$data['lent_date'] = $date;
11.
$data['lent_uid'] = $data['borrower_id'];
12.
13.
$waitlistData
=
array('waitlist_id'=>$data['waitlist_id'],
'fulfilled' => 1, 'fulfilled_time' => $date, 'table' => 'Waitlist');
14.
$waitlistModel = new LendrModelsWaitlist();
15.
$waitlistModel->store($waitlistData);
16.
} else {
17.
$data['lent'] = 0;
18.
$data['lent_date'] = NULL;
19.
$data['lent_uid'] = NULL;
20.
21.
}
22.
23.
$row = parent::store($data);
24.
25.
return $row;
26.
27.
}

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 well look at the actual
core process of lending and returning. In case youre wondering, were not building any
advanced tracking or monitoring system into Lendr (this may be something Ill look at in greater
detail later if its requested). In Lendr well 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.
Well 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
1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );
2.
3. class LendrControllersLend extends JControllerBase
4. {
5.
public function execute()
6.
{
7.
8.
$return = array("success"=>false);
9.

In this function youll 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.
45

$model = new LendrModelsBook();


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

46

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
1. function loadLendModal(book_id, borrower_id, borrower, waitlist_id)
2. {
3.
jQuery("#lendBookModal").modal('show');
4.
jQuery('#borrower_name').html(borrower);
5.
jQuery("#book_id").val(book_id);
6.
jQuery("#borrower_id").val(borrower_id);
7.
jQuery("#waitlist_id").val(waitlist_id);
8. }
9.
10. function lendBook()
11. {
12.
var lendForm = {};
13.
jQuery("#lendForm :input").each(function(idx,ele){
14.
lendForm[jQuery(ele).attr('name')] = jQuery(ele).val();
15.
});
16.
17.
jQuery.ajax({
18.
url:'index.php?option=com_lendr&controller=lend&format=raw&tmpl=compone
nt',
19.
type:'POST',
20.
data:lendForm,
21.
dataType:'JSON',
22.
success:function(data)
23.
{
24.
if ( data.success )
25.
{
26.
jQuery("#lendBookModal").modal('hide');
27.
}
28.
}
29.
});
30. }

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 well 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
1. <div
id="newReviewModal"
class="modal
hide
fade"
tabindex="-1"
role="dialog" aria-labelledby="newReviewModal" aria-hidden="true">
2.
<div class="modal-header">
3.
<button type="button" class="close" data-dismiss="modal" ariahidden="true"></button>
4.
<h3 id="myModalLabel"><?php echo JText::_('COM_LENDR_ADD_REVIEW');
?></h3>
5.
</div>
6.
<div class="modal-body">
7.
<div class="row-fluid">
8.
<form id="reviewForm">
9.
<input class="span12" type="text" name="title" placeholder="<?php
echo JText::_('COM_LENDR_TITLE'); ?>" />
10.
<textarea
class="span12"
placeholder="<?php
echo
JText::_('COM_LENDR_SUMMARY'); ?>" name="review" rows="10"></textarea>
11.
<input type="hidden" name="user_id" value="<?php echo $this>user->id; ?>" />
12.
<input type="hidden" name="view" value="review" />
13.
<input type="hidden" name="book_id" value="<?php echo $this>book->book_id; ?>" />
14.
<input type="hidden" name="model" value="review" />
15.
<input type="hidden" name="item" value="review" />
16.
<input type="hidden" name="table" value="review" />
17.
</form>
18.
</div>
19.
</div>
20.
<div class="modal-footer">
21.
<button class="btn" data-dismiss="modal" aria-hidden="true"><?php
echo JText::_('COM_LENDR_CLOSE'); ?></button>
22.
<button class="btn btn-primary" onclick="addReview()"><?php echo
JText::_('COM_LENDR_ADD'); ?></button>
23.
</div>
24. </div>

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

Step 3: Adding wishlists, waitlists, and reviews

47

48

10.
$modelName = $app->input->get('model', 'Book');
11.
$view
= $app->input->get('view', 'Book');
12.
$layout
= $app->input->get('layout', '_entry');
13.
$item
= $app->input->get('item', 'book');
14.
15.
$modelName = 'LendrModels'.ucwords($modelName);
16.
17.
$model = new $modelName();
18.
if ( $row = $model->store() )
19.
{
20.
$return['success'] = true;
21.
$return['msg'] = JText::_('COM_LENDR_SAVE_SUCCESS');
22. $return['html'] = LendrHelpersView::getHtml($view, $layout,
$row);
23.
24.
}else{
25.
$return['msg'] = JText::_('COM_LENDR_SAVE_FAILURE');
26.
}
27.
28.
echo json_encode($return);
29.
30.
}
31.
32. }

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
1. //add a review
2. function addReview()
3. {
4.
var reviewInfo = {};
5.
jQuery("#reviewForm :input").each(function(idx,ele){
6.
reviewInfo[jQuery(ele).attr('name')] = jQuery(ele).val();
7.
});
8.
9.
jQuery.ajax({
10.
url:'index.php?option=com_lendr&controller=add&format=raw&tmpl=componen
t',
11.
type:'POST',
12.
data:reviewInfo,
13.
dataType:'JSON',
14.
success:function(data)
15.
{
16.
if ( data.success ){
17.
console.log(data.html);
18.
jQuery("#review-list").append(data.html);
19.
jQuery("#newReviewModal").modal('hide');
20.
}else{
21.
22.
}
23.
}
24.
});
25.
26. }

The html that is returned to the javascript is loaded from the Helper file. Youll 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
1. function getHtml($view, $layout, $item, $data)
2. {
3.
$objectView = LendrHelpersView::load($view, $layout, 'phtml');
4.
$objectView->$item = $data;
5.
6. ob_start();
7.
echo $objectView->render();
8.
$html = ob_get_contents();
9.
ob_clean();
10.
11. return $html;
12. }

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

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.

1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );


2.
3. class LendrControllersAdd extends JControllerBase
4. {
5.
public function execute()
6.
{
7.
$app
= JFactory::getApplication();
8.
$return
= array("success"=>false);
9.

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

Step 4: Searching books


49

$item,

50

10. defined('JPATH_BASE') or die;


11.
12. require_once
JPATH_ADMINISTRATOR
'/components/com_finder/helpers/indexer/adapter.php';
13.
14. /**
15.
* Finder adapter for Lendr Books.
16.
*
17.
* @package
Joomla.Plugin
18.
* @subpackage Finder.Books
19.
* @since
3.0
20.
*/
21. class PlgFinderBooks extends FinderIndexerAdapter
22. {
23.
/**
24.
* The plugin identifier.
25.
*
26.
* @var
string
27.
* @since 2.5
28.
*/
29.
protected $context = 'Books';
30.
31.
/**
32.
* The extension name.
33.
*
34.
* @var
string
35.
* @since 2.5
36.
*/
37.
protected $extension = 'com_lendr';
38.
39.
/**
40.
* The sublayout to use when rendering the results.
41.
*
42.
* @var
string
43.
* @since 2.5
44.
*/
45.
protected $layout = 'book';
46.
47.
/**
48.
* The type of content that the adapter indexes.
49.
*
50.
* @var
string
51.
* @since 2.5
52.
*/
53.
protected $type_title = 'Book';
54.
55.
/**
56.
* The table name.
57.
*
58.
* @var
string
59.
* @since 2.5
60.
*/
61.
protected $table = '#__lendr_books';
62.
63.
/**
64.
* The field the published state is stored in.
65.
*

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).
books.xml
joomla_root/plugins/finder/books/books.xml
1. <?xml version="1.0" encoding="utf-8"?>
2. <extension version="3.1" type="plugin" group="finder" method="upgrade">
3.
<name>Smart Search - Books</name>
4.
<author>Joomla! Project</author>
5.
<creationDate>March 2013</creationDate>
6.
<copyright>(C) 2005 - 2013 Open Source Matters. All rights
reserved.</copyright>
7.
<license>GNU General Public License version 2 or later; see
LICENSE.txt</license>
8.
<authorEmail>admin@joomla.org</authorEmail>
9.
<authorUrl>www.joomla.org</authorUrl>
10.
<version>3.0.0</version>
11.
<description></description>
12.
<files>
13.
<file plugin="books">books.php</file>
14.
<filename>index.html</filename>
15.
</files>
16. </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.
books.php
joomla_root/plugins/finder/books/books.php
1. <?php
2. /**
3. * @package
Joomla.Plugin
4. * @subpackage Finder.Books
5. *
6. * @copyright
Copyright (C) 2005 - 2013 Open Source Matters, Inc. All
rights reserved.
7. * @license
GNU General Public License version 2 or later; see
LICENSE
8. */
9.

51

52

66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.

* @var
string
* @since 2.5
*/
protected $state_field = 'published';

119.
*/
120.
public function onFinderAfterSave($context, $row, $isNew)
121.
{
122.
// We only want to handle books here
123.
if ($context == 'com_lendr.book')
124.
{
125.
// Check if the access levels are different
126.
if (!$isNew && $this->old_access != $row->access)
127.
{
128.
// Process the change.
129.
$this->itemAccessChange($row);
130.
}
131.
132.
// Reindex the item
133.
$this->reindex($row->id);
134.
}
135.
return true;
136.
}
137.
138.
/**
139.
* Method to reindex the link information for an item that has been
saved.
140.
* This event is fired before the data is actually saved so we are
going
141.
* to queue the item to be indexed later.
142.
*
143.
* @param
string
$context The context of the content passed to
the plugin.
144.
* @param
JTable
$row
A JTable object
145.
* @param
boolean $isNew
If the content is just about to be
created
146.
*
147.
* @return boolean True on success.
148.
*
149.
* @since
2.5
150.
* @throws Exception on database error.
151.
*/
152.
public function onFinderBeforeSave($context, $row, $isNew)
153.
{
154.
// We only want to handle books here
155.
if ($context == 'com_lendr.book')
156.
{
157.
// Query the database for the old access level if the item isn't
new
158.
if (!$isNew)
159.
{
160.
$this->checkItemAccess($row);
161.
}
162.
}
163.
164.
return true;
165.
}
166.
167.
/**
168.
* Method to update the link information for items that have been
changed

/**
* 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.
81.
*
82.
* @param
string
$context
The context of the action being
performed.
83.
* @param
JTable $table
A JTable object containing the record
to be deleted
84.
*
85.
* @return boolean True on success.
86.
*
87.
* @since
2.5
88.
* @throws Exception on database error.
89.
*/
90.
public function onFinderDelete($context, $table)
91.
{
92.
if ($context == 'com_lendr.book')
93.
{
94.
$id = $table->id;
95.
}
96.
elseif ($context == 'com_finder.index')
97.
{
98.
$id = $table->link_id;
99.
}
100.
else
101.
{
102.
return true;
103.
}
104.
// Remove the items.
105.
return $this->remove($id);
106.
}
107.
108.
/**
109.
* Method to determine if the access level of an item changed.
110.
*
111.
* @param
string
$context The context of the content passed to
the plugin.
112.
* @param
JTable
$row
A JTable object
113.
* @param
boolean $isNew
If the content has just been created
114.
*
115.
* @return boolean True on success.
116.
*
117.
* @since
2.5
118.
* @throws Exception on database error.

53

54

169.
* from outside the edit screen. This is fired when the item is
published,
170.
* unpublished, archived, or unarchived from the list view.
171.
*
172.
* @param
string
$context The context for the content passed to
the plugin.
173.
* @param
array
$pks
A list of primary key ids of the
content that has changed state.
174.
* @param
integer
$value
The value of the state that the
content has been changed to.
175.
*
176.
* @return void
177.
*
178.
* @since
2.5
179.
*/
180.
public function onFinderChangeState($context, $pks, $value)
181.
{
182.
// We only want to handle articles here
183.
if ($context == 'com_lendr.book')
184.
{
185.
$this->itemStateChange($pks, $value);
186.
}
187.
// Handle when the plugin is disabled
188.
if ($context == 'com_plugins.plugin' && $value === 0)
189.
{
190.
$this->pluginDisable($pks);
191.
}
192.
}
193.
194.
/**
195.
* Method to index an item. The item must be a FinderIndexerResult
object.
196.
*
197.
* @param
FinderIndexerResult $item
The item to index as an
FinderIndexerResult object.
198.
* @param
string
$format The item format
199.
*
200.
* @return void
201.
*
202.
* @since
2.5
203.
* @throws Exception on database error.
204.
*/
205.
protected function index(FinderIndexerResult $item, $format =
'html')
206.
{
207.
// Check if the extension is enabled
208.
if (JComponentHelper::isEnabled($this->extension) == false)
209.
{
210.
return;
211.
}
212.
213.
$item->setLanguage();
214.
215.
$extension = ucfirst(substr($item->extension, 4));
216.
217.
$item->url = $this->getURL($item->id, $item->extension, $this>layout);

218.
$item->route
=
'index.php?option='.$this>extension.'&view=book&layout='.$this->layout.'&id='.$item->book_id;
219.
220.
// Add the type taxonomy data.
221.
$item->addTaxonomy('Type', 'Book');
222.
223.
// Add the language taxonomy data.
224.
$item->addTaxonomy('Language', $item->language);
225.
226.
// Index the item.
227.
$this->indexer->index($item);
228.
}
229.
230.
/**
231.
* Method to get the SQL query used to retrieve the list of books.
232.
*
233.
* @param
mixed $sql A JDatabaseQuery object or null.
234.
*
235.
* @return JDatabaseQuery A database object.
236.
*
237.
* @since
2.5
238.
*/
239.
protected function getListQuery($sql = null)
240.
{
241.
$db = JFactory::getDbo();
242.
// Check if we can use the supplied SQL query.
243.
$sql
=
$sql
instanceof
JDatabaseQuery
?
$sql
:
$db>getQuery(true);
244.
$sql->select('b.book_id as id, b.title, b.author, b.summary,
b.pages, b.publish_date');
245.
$sql->from('#__lendr_books AS b');
246.
$sql->where($db->quoteName('b.book_id') . ' > 1');
247.
248.
return $sql;
249.
}
250.
251.
/**
252.
* Method to get a SQL query to load the published state
253.
*
254.
* @return JDatabaseQuery A database object.
255.
*
256.
* @since
2.5
257.
*/
258.
protected function getStateQuery()
259.
{
260.
$sql = $this->db->getQuery(true);
261.
$sql->select($this->db->quoteName('b.book_id'));
262.
$sql->select($this->db->quoteName('b.published')
.
'
AS
book_state');
263.
$sql->from($this->db->quoteName('#__lendr_books') . ' AS b');
264.
265.
return $sql;
266.
}
267. }

55

56

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 weve covered quite a few functions, structural design, and additional ideas which
can be applied to any other component. Weve looked at how to best utilize Bootstrap for
modals, weve taken a deeper look at javascript functionality and use and then weve 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 dont 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.

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
1. <?php // No direct access
2.
3. defined( '_JEXEC' ) or die( 'Restricted access' );
4.
5. //load classes
6. JLoader::registerPrefix('Lendr', JPATH_COMPONENT_ADMINISTRATOR);
7.
8. //Load plugins
9. JPluginHelper::importPlugin('lendr');
10.
11. //application
12. $app = JFactory::getApplication();
13.
14. // Require specific controller if requested
15. $controller = $app->input->get('controller','display');
16.
17. // Create the controller
18. $classname = 'LendrControllers'.ucwords($controller);
19. $controller = new $classname();
20.
21. // Perform the Request task
22. $controller->execute();
23.

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

Admin Configuration $ Cleanup

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. Well 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.

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

57

58

1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );


2.
3. class LendrControllersDisplay extends JControllerBase
4. {
5.
public function execute()
6.
{
7.
8.
// Get the application
9.
$app = $this->getApplication();
10.
11.
// Get the document object.
12.
$document
= JFactory::getDocument();
13.
14.
$viewName
= $app->input->getWord('view', 'statistics');
15.
$viewFormat
= $document->getType();
16.
$layoutName
= $app->input->getWord('layout', 'default');
17.
18.
$app->input->set('view', $viewName);
19.
20.
// Register the layout paths for the view
21.
$paths = new SplPriorityQueue;
22.
$paths->insert(JPATH_COMPONENT . '/views/' . $viewName . '/tmpl',
'normal');
23.
24.
$viewClass
=
'LendrViews'
.
ucfirst($viewName)
.
ucfirst($viewFormat);
25.
$modelClass = 'LendrModels' . ucfirst($viewName);
26.
27.
$view = new $viewClass(new $modelClass, $paths);
28.
29.
$view->setLayout($layoutName);
30.
31.
// Render our view.
32.
echo $view->render();
33.
34.
return true;
35.
}
36.
37. }

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.

6.
function render()
7.
{
8.
$app = JFactory::getApplication();
9.
10.
//retrieve task list from model
11.
$model = new LendrModelsStatistics();
12.
$this->stats = $model->getStats();
13.
14.
$this->addToolbar();
15.
16.
//display
17.
return parent::render();
18.
}
19.
20.
/**
21.
* Add the page title and toolbar.
22.
*
23.
* @since
1.6
24.
*/
25.
protected function addToolbar()
26.
{
27.
$canDo = LendrHelpersLendr::getActions();
28.
29.
// Get the toolbar object instance
30.
$bar = JToolBar::getInstance('toolbar');
31.
32.
JToolbarHelper::title(JText::_('COM_LENDR_STATISTICS'));
33.
34.
if ($canDo->get('core.admin'))
35.
{
36.
JToolbarHelper::preferences('com_lendr');
37.
}
38.
}
39. }
40.

This file has several new elements so well 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, well examine the html.php folder located within the default view (as noted previously the
default view is labeled statistics).

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.

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

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.

1.
2. >?php defined( '_JEXEC' ) or die( 'Restricted access' );
3.
4. class LendrViewsStatisticsHtml extends JViewHtml
5. {

59

60

37.
38.

{
$result->set($action->name,
$assetName));
39.
}
40.
41.
return $result;
42.
}
43. }
44.

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?
Thats 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 its 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
1. <?php
2. /**
3. * @package
Joomla.Administrator
4. * @subpackage com_lendr
5. *
6. * @copyright
Copyright (C) 2005 - 2013 Open Source Matters, Inc. All
rights reserved.
7. * @license
GNU General Public License version 2 or later; see
LICENSE.txt
8. */
9.
10. defined('_JEXEC') or die;
11.
12. /**
13.
* Lendr component helper.
14.
*
15.
* @package
Joomla.Administrator
16.
* @subpackage com_lendr
17.
* @since
1.6
18.
*/
19. class LendrHelpersLendr
20. {
21.
public static $extension = 'com_lendr';
22.
23.
/**
24.
* @return JObject
25.
*/
26.
public static function getActions()
27.
{
28.
$user = JFactory::getUser();
29.
$result = new JObject;
30.
31.
$assetName = 'com_lendr';
32.
$level = 'component';
33.
34.
$actions = JAccess::getActions('com_lendr', $level);
35.
36.
foreach ($actions as $action)

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.
Lets 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
1. <?xml version="1.0" encoding="utf-8"?>
2. <access component="com_lendr">
3.
<section name="component">
4.
<action
name="core.admin"
description="JACTION_ADMIN_COMPONENT_DESC" />
5.
</section>
6. </access>

title="JACTION_ADMIN"

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.

61

$user->authorise($action->name,

62

50.
<fieldset name="permissions"
51.
description="JCONFIG_PERMISSIONS_DESC"
52.
label="JCONFIG_PERMISSIONS_LABEL"
53.
>
54.
55.
<field name="rules" type="rules"
56.
component="com_lendr"
57.
filter="rules"
58.
validate="rules"
59.
label="JCONFIG_PERMISSIONS_LABEL"
60.
section="component" />
61.
</fieldset>
62. </config>

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. Lets look at that file now.
joomla_root/administrator/components/com_lendr/config.xml
1. <?xml version="1.0" encoding="utf-8"?>
2. <config>
3.
<fieldset name="component"
4.
label="COM_LENDR_OPTIONS"
5.
description="COM_LENDR_OPTIONS_DESC"
6.
>
7.
8.
<field name="required_account" type="radio"
9.
default="0"
10.
class="btn-group"
11.
label="COM_LENDR_REQUIRED_ACCOUNT"
12.
description="COM_LENDR_REQUIRED_ACCOUNT_DESC">
13.
14.
<option value="0">JNO</option>
15.
<option value="1">JYES</option>
16.
</field>
17.
18.
<field name="new_reviews" type="radio"
19.
default="1"
20.
class="btn-group"
21.
label="COM_LENDR_ALLOW_NEW_REVIEWS"
22.
description="COM_LENDR_ALLOW_NEW_REVIEWS_DESC">
23.
24.
<option value="0">JNO</option>
25.
<option value="1">JYES</option>
26.
</field>
27.
28.
<field name="new_wishlists" type="radio"
29.
default="1"
30.
class="btn-group"
31.
label="COM_LENDR_ALLOW_NEW_WISHLISTS"
32.
description="COM_LENDR_ALLOW_NEW_WISHLISTS_DESC">
33.
34.
<option value="0">JNO</option>
35.
<option value="1">JYES</option>
36.
</field>
37.
38.
<field name="new_waitlists" type="radio"
39.
default="1"
40.
class="btn-group"
41.
label="COM_LENDR_ALLOW_NEW_WAITLISTS"
42.
description="COM_LENDR_ALLOW_NEW_WAITLISTS_DESC">
43.
44.
<option value="0">JNO</option>
45.
<option value="1">JYES</option>
46.
</field>
47.
48.
</fieldset>
49.

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 thats been added to the front side which utilizes these new
parameters.
Well 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
1. // Line 11 - 19
2. $params = JComponentHelper::getParams('com_lendr');
3.
if ($params->get('required_account') == 1)
4.
{
5.
$user = JFactory::getUser();
6.
if ($user->get('guest'))
7.
{
8.
$app>redirect('index.php',JText::_('COM_LENDR_ACCOUNT_REQUIRED_MSG'));
9.
}
10.
}

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
63

64

1. <?php defined( '_JEXEC' ) or die( 'Restricted access' );


2.
3. class LendrControllersDelete extends JControllerBase
4. {
5.
public function execute()
6.
{
7.
$app = JFactory::getApplication();
8.
9.
$return = array("success"=>false);
10.
11.
$type = $app->input->get('type','waitlist');
12.
13.
$modelName = 'LendrModels'.ucfirst($type);
14.
$model = new $modelName();
15.
16.
if ( $row = $model->delete() )
17.
{
18.
$return['success'] = true;
19.
$return['msg'] = JText::_('COM_LENDR_BOOK_DELETE_SUCCESS');
20.
}else{
21.
$return['msg'] = JText::_('COM_LENDR_BOOK_DELETE_FAILURE');
22.
}
23.
24.
echo json_encode($return);
25.
26.
}
27.
28. }

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 well look at the usage of a parameter value is in the following view
layout.
joomla_root/components/com_lendr/views/book/tmpl/_entry.php
1. // Line 43 - 48
2. <?php if($this->params->get('new_wishlists') == 1 ): ?>
3.
<li><a
href="javascript:void(0);"
onclick="addToWishlist('<?php echo $this->book->book_id; ?>');"><?php
echo JText::_('COM_LENDR_ADD_WISHLIST'); ?></a></li>
4.
<?php endif; ?>
5.
<?php if($this->params->get('new_reviews') == 1 ):
?>
6.
<li><a
href="#newReviewModal"
datatoggle="modal"><?php
echo
JText::_('COM_LENDR_WRITE_REVIEW');
?></a></li>
7.
<?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 its utilized, this can be seen by the fact that
we are calling $this->params in the view.

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.

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.

joomla_root/components/com_lendr/models/book.php
1. /**
2.
* Delete a book
3.
* @param int
ID of the book to delete
4.
* @return boolean True if successfully deleted
5.
*/
6.
public function delete($id = null)
7.
{
8.
$app = JFactory::getApplication();
9.
$id
= $id ? $id : $app->input->get('book_id');
10.
11.
$book = JTable::getInstance('Book','Table');
12.
$book->load($id);
13.
14.
$book->published = 0;
15.
16.
if($book->store())
17.
{
18.
return true;
19.
} else {

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

65

66

20.
21.
22.

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.

}
}

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

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

1. /**
2.
* Delete a book from a waitlist
3.
* @param int
ID of the book to delete
4.
* @return boolean True if successfully deleted
5.
*/
6.
public function delete($id = null)
7.
{
8.
$app = JFactory::getApplication();
9.
$id
= $id ? $id : $app->input->get('waitlist_id');
10.
11.
if (!$id)
12.
{
13.
if ($book_id = $app->input->get('book_id'))
14.
{
15.
$db = JFactory::getDbo();
16.
$user = JFactory::getUser();
17.
$query = $db->getQuery(true);
18.
$query->delete()
19.
->from('#__lendr_waitlists')
20.
->where('user_id = ' . $user->id)
21.
->where('book_id = ' . $book_id);
22.
$db->setQuery($query);
23.
if($db->query()) {
24.
return true;
25.
}
26.
}
27.
28.
} else {
29.
$waitlist = JTable::getInstance('Waitlist','Table');
30.
$waitlist->load($id);
31.
32.
if ($waitlist->delete())
33.
{
34.
return true;
35.
}
36.
}
37.
38.
return false;
39.
}

1. function deleteBook(book_id,type)
2. {
3.
jQuery.ajax({
4.
url:'index.php?option=com_lendr&controller=delete&format=raw&tmpl=compo
nent',
5.
type:'POST',
6.
data: 'book_id='+book_id+'&type='+type,
7.
dataType: 'JSON',
8.
success:function(data)
9.
{
10.
alert(data.msg);
11.
if(data.success)
12.
{
13.
jQuery("tr#bookRow"+book_id).hide();
14.
}
15.
}
16.
});
17. }

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
67

68

1. // Lines 8 - 19
2.
3.
$layout = $this->getLayout();
4.
5.
$this->params = JComponentHelper::getParams('com_lendr');
6.
7.
//retrieve task list from model
8.
$model = new LendrModelsBook();
9.
10.
if($layout == 'list')
11.
{
12.
$this->books = $model->listItems();
13.
$this->_bookListView
LendrHelpersView::load('Book','_entry','phtml');
14.
} else {
15.

16.

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


=

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 well load the view the same way as before.
Next well add a new list layout to display the books.

Its 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

joomla_root/components/com_lendr/views/book/tmpl/list.php
1. <table cellpadding="0" cellspacing="0" width="100%" class="table tablestriped">
2.
<thead>
3.
<tr>
4.
<th><?php echo JText::_('COM_LENDR_DETAILS'); ?></th>
5.
<th><?php echo JText::_('COM_LENDR_STATUS'); ?></th>
6.
<th><?php echo JText::_('COM_LENDR_ACTIONS'); ?></th>
7.
</tr>
8.
</thead>
9.
<tbody id="book-list">
10.
<?php for($i=0, $n = count($this->books);$i<$n;$i++) {
11.
$this->_bookListView->book = $this->books[$i];
12.
$this->_bookListView->type = 'book';
13.
echo $this->_bookListView->render();
14.
} ?>
15.
</tbody>
16. </table>
17.

1. <?xml version="1.0" encoding="utf-8"?>


2. <metadata>
3.
<layout title="COM_LENDR_PROFILE_LIST">
4.
<message><![CDATA[COM_LENDR_PROFILE_LIST_DESC]]></message>
5.
</layout>
6. </metadata>
7.

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

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.

1.
2.
3.
4.
5.
6.

69

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

70

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).

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

Remove unnecessary files and functions

Weve 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 its always a good idea to code for someone else. This means documenting your code
correctly and removing anything thats 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 addons 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

71

72

You might also like