Developing an MVC Component/Adding a front-end form: Difference between revisions
From Joomla! Documentation
Marked this version for translation |
first draft - checkpoint save |
||
| Line 3: | Line 3: | ||
en</translate>}} | en</translate>}} | ||
== Web Page Under Construction !!!== | |||
== | |||
<translate><!--T:3--> This tutorial is part of the [[S:MyLanguage/J3.x:Developing_an_MVC_Component/Introduction|Developing a MVC Component for Joomla! 3.x]] tutorial. You are encouraged to read the previous parts of the tutorial before reading this.</translate> | <translate><!--T:3--> This tutorial is part of the [[S:MyLanguage/J3.x:Developing_an_MVC_Component/Introduction|Developing a MVC Component for Joomla! 3.x]] tutorial. You are encouraged to read the previous parts of the tutorial before reading this.</translate> | ||
''' | == Introduction == | ||
In this step we add a form to the site front end to allow users to add a new Helloworld record. You could adapt the code in this step to provide a range of front-end data entry functionality, from, eg | |||
* a registration form to allow visitors to the site to express interest, to, | |||
* a facility to allow suppliers to maintain the data in certain tables, but without giving them access to the back end administration functionality. | |||
== Functionality == | |||
In this step we will build the following functionality | |||
* a front-end form that allows someone to enter a new helloworld record, together with Save and Cancel buttons. | |||
* additional fields in the helloworld database table to capture the user who entered the record, and the creation date/time | |||
* validation of the data entered into the fields, including removing dangerous items such as html tags | |||
* access control on the form, controlling who can add a new record | |||
* an email to an administrator, to indicate that someone has entered a new record (and we'll include a field on the form where the user can include some text which just goes into the email) | |||
* a captcha on the form | |||
* a front-end menu item which points to the form (ie how users get access to the form) | |||
* update to the admin helloworlds view to include the author and creation date in the display. | |||
== Approach == | |||
Providing a form on the site front end is pretty much the same as on the admin back end, so much of what is developed in this step reflects what was done in the step [[S:MyLanguage/J3.x:Developing an MVC Component/Adding backend actions|Adding backend actions]], and in particular the Edit form for helloworld messages. | |||
For this form we'll use (all in the site part) | |||
* the same main helloworld controller (controller.php) | |||
* a new view – which we'll call "form" | |||
* a new layout, associated with the form view, which we'll call "edit" (like the admin layout file) | |||
* a new model – which we'll call "form" also, to align with the view | |||
* an xml file for the form, which we'll put in add-form.xml in the model | |||
In addition we'll need to handle the HTTP POST requests arising from the user pressing the Save and Cancel buttons, and we'll do this in | |||
* a new helloworld controller in the controllers directory (handling Save and Cancel) | |||
* the same form.php model as above (handling Save) | |||
Upon completion of Save/Cancel we'll redirect back to the same form. | |||
As for the admin case, we'll consider our classes extending some of the controller and model classes which are richer in functionality that the simple legacy ones | |||
* the model will extend JModelAdmin (which supports forms, as well as saving records to the database) - even though it's called JModelAdmin it's still available within the site part | |||
* the controller handling the POST will extend JControllerForm (which includes the cancel() and save() methods). | |||
Some of the other aspects in this step will involve using the techniques covered in earlier steps, namely [[S:MyLanguage/J3.x:Developing an MVC Component/Adding a menu type to the site part|Adding a menu type to the site part]], [[S:MyLanguage/J3.x:Developing an MVC Component/Adding verifications|Adding verifications]], [[S:MyLanguage/J3.x:Developing an MVC Component/Adding configuration|Adding configuration]] and [[S:MyLanguage/J3.x:Developing an MVC Component/Adding ACL|Adding ACL]]. | |||
==Adding a menu item== | |||
To enable a menu item to point to the form, we need to put an xml file into the associated layout directory. Extend your directory structure (remembering to include the index.php file in each new directory) and put the following into | |||
<span id="site/views/form/tmpl/edit.xml"> | |||
<tt>site/views/form/tmpl/edit.xml</tt> | |||
<source lang="xml"> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<metadata> | |||
<layout title="COM_HELLOWORLD_ADD_VIEW_TITLE"> | |||
<message>COM_HELLOWORLD_ADD_VIEW_DESC</message> | |||
</layout> | |||
</metadata> | |||
</source> | |||
</span> | |||
==Displaying the new form view== | |||
Create a new forms subdirectory under the site models directory, and add the definition of the form into | |||
<span id="site/models/forms/add-form.xml"> | |||
<tt>site/models/forms/add-form.xml</tt> | |||
<source lang="xml"> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<form | |||
addrulepath="/administrator/components/com_helloworld/models/rules" | |||
> | |||
<fieldset | |||
name="details" | |||
label="COM_HELLOWORLD_HELLOWORLD_DETAILS" | |||
> | |||
<field | |||
name="id" | |||
type="hidden" | |||
/> | |||
<field | |||
name="greeting" | |||
type="text" | |||
label="COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_GREETING_DESC" | |||
size="40" | |||
class="inputbox" | |||
validate="greeting" | |||
required="true" | |||
hint="COM_HELLOWORLD_HELLOWORLD_GREETING_HINT" | |||
default="" | |||
/> | |||
<field | |||
name="catid" | |||
type="category" | |||
extension="com_helloworld" | |||
class="inputbox" | |||
default="" | |||
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC" | |||
required="true" | |||
> | |||
<option value="0">JOPTION_SELECT_CATEGORY</option> | |||
</field> | |||
<field | |||
name="message" | |||
type="textarea" | |||
rows="5" | |||
cols="80" | |||
label="COM_HELLOWORLD_HELLOWORLD_MESSAGE_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_MESSAGE_DESC" | |||
hint="COM_HELLOWORLD_HELLOWORLD_MESSAGE_HINT" | |||
required="true" | |||
> | |||
</field> | |||
<field | |||
name="captcha" | |||
type="captcha" | |||
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC" | |||
validate="captcha" | |||
> | |||
</field> | |||
<fields name="params"> | |||
<field | |||
name="show_category" | |||
type="list" | |||
label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC" | |||
default="" | |||
useglobal="true" | |||
> | |||
<option value="0">JHIDE</option> | |||
<option value="1">JSHOW</option> | |||
</field> | |||
</fields> | |||
</fieldset> | |||
</form> | |||
</source> | |||
</span> | |||
The above form includes a captcha. To get this working you need to | |||
The existing controller (in controller.php) will not need to be changed, but we'll need to define a new view, layout and model for the new form. | |||
<span id="site/views/form/view.html.php"> | |||
<tt>site/views/form/view.html.php</tt> | |||
<source lang="php"> | |||
<?php | |||
/** | |||
* @package Joomla.Administrator | |||
* @subpackage com_helloworld | |||
* | |||
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved. | |||
* @license GNU General Public License version 2 or later; see LICENSE.txt | |||
*/ | |||
// No direct access to this file | |||
defined('_JEXEC') or die('Restricted access'); | |||
/** | |||
* HelloWorld View | |||
* This is the site view presenting the user with the ability to add a new Helloworld record | |||
* | |||
*/ | |||
class HelloWorldViewForm extends JViewLegacy | |||
{ | |||
protected $form = null; | |||
protected $canDo; | |||
/** | |||
* Display the Hello World view | |||
* | |||
* @param string $tpl The name of the layout file to parse. | |||
* | |||
* @return void | |||
*/ | |||
public function display($tpl = null) | |||
{ | |||
// Get the form to display | |||
$this->form = $this->get('Form'); | |||
// Get the javascript script file for client-side validation | |||
$this->script = $this->get('Script'); | |||
// Check that the user has permissions to create a new helloworld record | |||
$this->canDo = JHelperContent::getActions('com_helloworld'); | |||
if (!($this->canDo->get('core.create'))) | |||
{ | |||
$app = JFactory::getApplication(); | |||
$app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error'); | |||
$app->setHeader('status', 403, true); | |||
return; | |||
} | |||
// Check for errors. | |||
if (count($errors = $this->get('Errors'))) | |||
{ | |||
throw new Exception(implode("\n", $errors), 500); | |||
} | |||
// Call the parent display to display the layout file | |||
parent::display($tpl); | |||
// Set properties of the html document | |||
$this->setDocument(); | |||
} | |||
/** | |||
* Method to set up the html document properties | |||
* | |||
* @return void | |||
*/ | |||
protected function setDocument() | |||
{ | |||
$document = JFactory::getDocument(); | |||
$document->setTitle(JText::_('COM_HELLOWORLD_HELLOWORLD_CREATING')); | |||
$document->addScript(JURI::root() . $this->script); | |||
$document->addScript(JURI::root() . "/administrator/components/com_helloworld" | |||
. "/views/helloworld/submitbutton.js"); | |||
JText::script('COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE'); | |||
} | |||
} | |||
</source> | |||
</span> | |||
And into the layout file: | |||
<span id="site/views/form/templ/edit.php"> | |||
<tt>site/views/form/templ/edit.php</tt> | |||
<source lang="php"> | |||
<?php | |||
/** | |||
* @package Joomla.Administrator | |||
* @subpackage com_helloworld | |||
* | |||
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved. | |||
* @license GNU General Public License version 2 or later; see LICENSE.txt | |||
* | |||
* This layout file is for displaying the front end form for capturing a new helloworld message | |||
* | |||
*/ | |||
// No direct access | |||
defined('_JEXEC') or die('Restricted access'); | |||
JHtml::_('behavior.formvalidator'); | |||
?> | |||
<form action="<?php echo JRoute::_('index.php?option=com_helloworld&layout=edit'); ?>" | |||
method="post" name="adminForm" id="adminForm" class="form-validate"> | |||
<div class="form-horizontal"> | |||
<fieldset class="adminform"> | |||
<legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_DETAILS') ?></legend> | |||
<div class="row-fluid"> | |||
<div class="span6"> | |||
<?php echo $this->form->renderFieldset('details'); ?> | |||
</div> | |||
</div> | |||
</fieldset> | |||
</div> | |||
<div class="btn-toolbar"> | |||
<div class="btn-group"> | |||
<button type="button" class="btn btn-primary" onclick="Joomla.submitbutton('helloworld.save')"> | |||
<span class="icon-ok"></span><?php echo JText::_('JSAVE') ?> | |||
</button> | |||
</div> | |||
<div class="btn-group"> | |||
<button type="button" class="btn" onclick="Joomla.submitbutton('helloworld.cancel')"> | |||
<span class="icon-cancel"></span><?php echo JText::_('JCANCEL') ?> | |||
</button> | |||
</div> | |||
</div> | |||
<input type="hidden" name="task" /> | |||
<?php echo JHtml::_('form.token'); ?> | |||
</form> | |||
</source> | |||
</span> | |||
All the above is similar to the admin functionality, except there are certain fields we don't want to allow the front-end user to see (eg the permissions) and instead of the standard Joomla admin buttons (and toolbar) we use a Save button and a Cancel button styled using the Bootstrap CSS, and which result in the <tt>task</tt> parameter being set to <tt>helloworld.save</tt> or <tt>helloworld.cancel</tt>. | |||
Our new model is almost exactly the same as the admin equivalent; we just have to point to our new add-form instead. | |||
<span id="site/models/form.php"> | |||
<tt>site/models/form.php</tt> | |||
<source lang="php"> | |||
<?php | |||
/** | |||
* @package Joomla.Administrator | |||
* @subpackage com_helloworld | |||
* | |||
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved. | |||
* @license GNU General Public License version 2 or later; see LICENSE.txt | |||
*/ | |||
// No direct access to this file | |||
defined('_JEXEC') or die('Restricted access'); | |||
/** | |||
* HelloWorld Model | |||
* | |||
* @since 0.0.1 | |||
*/ | |||
class HelloWorldModelForm extends JModelAdmin | |||
{ | |||
/** | |||
* Method to get a table object, load it if necessary. | |||
* | |||
* @param string $type The table name. Optional. | |||
* @param string $prefix The class prefix. Optional. | |||
* @param array $config Configuration array for model. Optional. | |||
* | |||
* @return JTable A JTable object | |||
* | |||
* @since 1.6 | |||
*/ | |||
public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array()) | |||
{ | |||
return JTable::getInstance($type, $prefix, $config); | |||
} | |||
/** | |||
* Method to get the record form. | |||
* | |||
* @param array $data Data for the form. | |||
* @param boolean $loadData True if the form is to load its own data (default case), false if not. | |||
* | |||
* @return mixed A JForm object on success, false on failure | |||
* | |||
* @since 1.6 | |||
*/ | |||
public function getForm($data = array(), $loadData = true) | |||
{ | |||
// Get the form. | |||
$form = $this->loadForm( | |||
'com_helloworld.form', | |||
'add-form', | |||
array( | |||
'control' => 'jform', | |||
'load_data' => $loadData | |||
) | |||
); | |||
if (empty($form)) | |||
{ | |||
return false; | |||
} | |||
return $form; | |||
} | |||
/** | |||
* Method to get the data that should be injected in the form. | |||
* As this form is for add, we're not prefilling the form with an existing record | |||
* But if the user has previously hit submit and the validation has found an error, | |||
* then we inject what was previously entered. | |||
* | |||
* @return mixed The data for the form. | |||
* | |||
* @since 1.6 | |||
*/ | |||
protected function loadFormData() | |||
{ | |||
// Check the session for previously entered form data. | |||
$data = JFactory::getApplication()->getUserState( | |||
'com_helloworld.edit.helloworld.data', | |||
array() | |||
); | |||
return $data; | |||
} | |||
/** | |||
* Method to get the script that have to be included on the form | |||
* This returns the script associated with helloworld field greeting validation | |||
* | |||
* @return string Script files | |||
*/ | |||
public function getScript() | |||
{ | |||
return 'administrator/components/com_helloworld/models/forms/helloworld.js'; | |||
} | |||
} | |||
</source> | |||
</span> | |||
==Handling the form HTTP POST== | |||
As in the admin case, we'll need to create a new controller in the /controllers subdirectory with save() and cancel() methods, and we'll need a model with a save() method. In fact, because the above model class (in site/models/form.php) extends JModelAdmin, we'll be able to reuse it without adding any more code. (For just displaying the form our model class could have extended JModelForm, but JModelAdmin extends JModelForm and provides additional functions such as the save() one which we need). | |||
Note that we don't need any new view file or layout file, because we're just going to redirect the user back to the same form. | |||
So the only new file we need is a new controller in a new controllers subdirectory: | |||
<span id="site/controllers/helloworld.php"> | |||
<tt>site/controllers/helloworld.php</tt> | |||
<source lang="php"> | |||
<?php | |||
/** | |||
* @package Joomla.Site | |||
* @subpackage com_helloworld | |||
* | |||
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved. | |||
* @license GNU General Public License version 2 or later; see LICENSE.txt | |||
*/ | |||
// No direct access to this file | |||
defined('_JEXEC') or die('Restricted access'); | |||
/** | |||
* HelloWorld Controller | |||
* | |||
* @package Joomla.Site | |||
* @subpackage com_helloworld | |||
* | |||
* Used to handle the http POST from the front-end form which allows | |||
* users to enter a new helloworld message | |||
* | |||
*/ | |||
class HelloWorldControllerHelloWorld extends JControllerForm | |||
{ | |||
public function cancel($key = null) | |||
{ | |||
parent::cancel($key); | |||
// set up the redirect back to the same form | |||
$this->setRedirect( | |||
JUri::getInstance()->current(), | |||
JText::_(COM_HELLOWORLD_ADD_CANCELLED) | |||
); | |||
} | |||
/* | |||
* Function handing the save for adding a new helloworld record | |||
* Based on the save() function in the JControllerForm class | |||
*/ | |||
public function save($key = null, $urlVar = null) | |||
{ | |||
// Check for request forgeries. | |||
JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN')); | |||
$app = JFactory::getApplication(); | |||
$input = $app->input; | |||
$model = $this->getModel('form'); | |||
// Check that this user is allowed to add a new record | |||
if (!JFactory::getUser()->authorise( "core.create", "com_helloworld")) | |||
{ | |||
$app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error'); | |||
$app->setHeader('status', 403, true); | |||
return; | |||
} | |||
// get the data from the HTTP POST request | |||
$data = $input->get('jform', array(), 'array'); | |||
// set up context for saving form data | |||
$context = "$this->option.edit.$this->context"; | |||
// Validate the posted data. | |||
// First we need to set up an instance of the form ... | |||
$form = $model->getForm($data, false); | |||
if (!$form) | |||
{ | |||
$app->enqueueMessage($model->getError(), 'error'); | |||
return false; | |||
} | |||
// ... and then we validate the data against it | |||
$validData = $model->validate($form, $data); | |||
// Handle the case where there are validation errors | |||
if ($validData === false) | |||
{ | |||
// Get the validation messages. | |||
$errors = $model->getErrors(); | |||
// Display up to three validation messages to the user. | |||
for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) | |||
{ | |||
if ($errors[$i] instanceof Exception) | |||
{ | |||
$app->enqueueMessage($errors[$i]->getMessage(), 'warning'); | |||
} | |||
else | |||
{ | |||
$app->enqueueMessage($errors[$i], 'warning'); | |||
} | |||
} | |||
// Save the form data in the session. | |||
$app->setUserState($context . '.data', $data); | |||
// Redirect back to the same screen. | |||
$this->setRedirect(JUri::getInstance()->current()); | |||
return false; | |||
} | |||
// add the 'created by' and 'created' date fields | |||
$validData['created_by'] = JFactory::getUser()->get('id', 0); | |||
$validData['created'] = date('Y-m-d h:i:s'); | |||
// Attempt to save the data. | |||
if (!$model->save($validData)) | |||
{ | |||
// Handle the case where the save failed | |||
// Save the data in the session. | |||
$app->setUserState($context . '.data', $validData); | |||
// Redirect back to the edit screen. | |||
$this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError())); | |||
$this->setMessage($this->getError(), 'error'); | |||
$this->setRedirect(JUri::getInstance()->current()); | |||
return false; | |||
} | |||
// clear the data in the form | |||
$app->setUserState($context . '.data', null); | |||
// notify the administrator that a new helloworld message has been added on the front end | |||
// get the id of the person to notify from global config | |||
$params = $app->getParams(); | |||
$userid_to_email = (int) $params->get('user_to_email'); | |||
$user_to_email = JUser::getInstance($userid_to_email); | |||
$to_address = $user_to_email->get("email"); | |||
// get the current user (if any) | |||
$current_user = JFactory::getUser(); | |||
if ($current_user) | |||
{ | |||
$current_username = $current_user->get("username"); | |||
} | |||
else | |||
{ | |||
$current_username = "a visitor to the site"; | |||
} | |||
// get the Mailer object, set up the email to be sent, and send it | |||
$mailer = JFactory::getMailer(); | |||
$mailer->addRecipient($to_address); | |||
$mailer->setSubject("New helloworld message added by " . $current_username); | |||
$mailer->setBody("New greeting is " . $validData['greeting']); | |||
try | |||
{ | |||
$mailer->send(); | |||
} | |||
catch (Exception $e) | |||
{ | |||
JLog::add('Caught exception: ' . $e->getMessage(), JLog::Error, 'jerror'); | |||
} | |||
$this->setRedirect( | |||
JUri::getInstance()->current(), | |||
JText::_('COM_HELLOWORLD_ADD_SUCCESSFUL') | |||
); | |||
return true; | |||
} | |||
} | |||
</source> | |||
</span> | |||
In the above file we've been able to reuse the cancel() method from the parent JControllerForm class, with the exception that we want to redirect back to the same form. (The parent cancel() method redirects back to the view showing the list of records - as in the admin case). | |||
However, our save() method functionality is significantly different from that of the parent class, so it's easier just to write our own. | |||
The mailing functionality relies on you having set up mail in your Joomla instance. If you're using localhost then one possibility is to configure this using a gmail account, and you can easily find the smtp settings for this on the Internet. Note that you'll need to allow "less secure apps" on the gmail account. | |||
==Configuration Parameters== | |||
We've introduced two new configuration parameters - the captcha to display on the form, and the user to email with notification of a new helloworld message being added. So update the configuration settings as follows: | |||
<span id="admin/config.xml"> | |||
<tt>admin/config.xml</tt> | |||
<source lang="xml" highlight="18-37"> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<config> | |||
<fieldset | |||
name="greetings" | |||
label="COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_LABEL" | |||
description="COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_DESC" | |||
> | |||
<field | |||
name="show_category" | |||
type="radio" | |||
label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC" | |||
default="0" | |||
> | |||
<option value="0">JHIDE</option> | |||
<option value="1">JSHOW</option> | |||
</field> | |||
<field | |||
name="captcha" | |||
type="plugins" | |||
folder="captcha" | |||
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC" | |||
default="0" | |||
filter="cmd" | |||
> | |||
<option value="">JOPTION_USE_DEFAULT</option> | |||
<option value="0">JOPTION_DO_NOT_USE</option> | |||
</field> | |||
<field | |||
name="user_to_email" | |||
type="user" | |||
label="COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_LABEL" | |||
description="COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_DESC" | |||
default="0" | |||
> | |||
</field> | |||
</fieldset> | |||
<fieldset | |||
name="permissions" | |||
label="JCONFIG_PERMISSIONS_LABEL" | |||
description="JCONFIG_PERMISSIONS_DESC" | |||
> | |||
<field | |||
name="rules" | |||
type="rules" | |||
label="JCONFIG_PERMISSIONS_LABEL" | |||
class="inputbox" | |||
validate="rules" | |||
filter="rules" | |||
component="com_helloworld" | |||
section="component" | |||
/> | |||
</fieldset> | |||
</config> | |||
</source> | |||
</span> | |||
{{-}} | {{-}} | ||
Revision as of 11:15, 4 November 2017
Articles in This Series
- Introduction
- Developing a Basic Component
- Adding a View to the Site Part
- Adding a Menu Type to the Site Part
- Adding a Model to the Site Part
- Adding a Variable Request in the Menu Type
- Using the Database
- Basic Backend
- Adding Language Management
- Adding Backend Actions
- Adding Decorations to the Backend
- Adding Verifications
- Adding Categories
- Adding Configuration
- Adding ACL
- Adding an Install/Uninstall/Update Script File
- Adding a Frontend Form
- Adding an Image
- Adding a Map
- Adding AJAX
- Adding an Alias
- Using the Language Filter Facility
- Adding a Modal
- Adding Associations
- Adding Checkout
- Adding Ordering
- Adding Levels
- Adding Versioning
- Adding Tags
- Adding Access
- Adding a Batch Process
- Adding Cache
- Adding a Feed
- Adding an Update Server
- Adding Custom Fields
- Upgrading to Joomla4
This is a multiple-article series of tutorials on how to develop a Model-View-Controller Component for Joomla! Version
.
Begin with the Introduction, and navigate the articles in this series by using the navigation button at the bottom or the box to the right (the Articles in this series).
Web Page Under Construction !!!
This tutorial is part of the Developing a MVC Component for Joomla! 3.x tutorial. You are encouraged to read the previous parts of the tutorial before reading this.
Introduction
In this step we add a form to the site front end to allow users to add a new Helloworld record. You could adapt the code in this step to provide a range of front-end data entry functionality, from, eg
- a registration form to allow visitors to the site to express interest, to,
- a facility to allow suppliers to maintain the data in certain tables, but without giving them access to the back end administration functionality.
Functionality
In this step we will build the following functionality
- a front-end form that allows someone to enter a new helloworld record, together with Save and Cancel buttons.
- additional fields in the helloworld database table to capture the user who entered the record, and the creation date/time
- validation of the data entered into the fields, including removing dangerous items such as html tags
- access control on the form, controlling who can add a new record
- an email to an administrator, to indicate that someone has entered a new record (and we'll include a field on the form where the user can include some text which just goes into the email)
- a captcha on the form
- a front-end menu item which points to the form (ie how users get access to the form)
- update to the admin helloworlds view to include the author and creation date in the display.
Approach
Providing a form on the site front end is pretty much the same as on the admin back end, so much of what is developed in this step reflects what was done in the step Adding backend actions, and in particular the Edit form for helloworld messages.
For this form we'll use (all in the site part)
- the same main helloworld controller (controller.php)
- a new view – which we'll call "form"
- a new layout, associated with the form view, which we'll call "edit" (like the admin layout file)
- a new model – which we'll call "form" also, to align with the view
- an xml file for the form, which we'll put in add-form.xml in the model
In addition we'll need to handle the HTTP POST requests arising from the user pressing the Save and Cancel buttons, and we'll do this in
- a new helloworld controller in the controllers directory (handling Save and Cancel)
- the same form.php model as above (handling Save)
Upon completion of Save/Cancel we'll redirect back to the same form.
As for the admin case, we'll consider our classes extending some of the controller and model classes which are richer in functionality that the simple legacy ones
- the model will extend JModelAdmin (which supports forms, as well as saving records to the database) - even though it's called JModelAdmin it's still available within the site part
- the controller handling the POST will extend JControllerForm (which includes the cancel() and save() methods).
Some of the other aspects in this step will involve using the techniques covered in earlier steps, namely Adding a menu type to the site part, Adding verifications, Adding configuration and Adding ACL.
To enable a menu item to point to the form, we need to put an xml file into the associated layout directory. Extend your directory structure (remembering to include the index.php file in each new directory) and put the following into
site/views/form/tmpl/edit.xml
<?xml version="1.0" encoding="utf-8"?>
<metadata>
<layout title="COM_HELLOWORLD_ADD_VIEW_TITLE">
<message>COM_HELLOWORLD_ADD_VIEW_DESC</message>
</layout>
</metadata>
Displaying the new form view
Create a new forms subdirectory under the site models directory, and add the definition of the form into
site/models/forms/add-form.xml
<?xml version="1.0" encoding="utf-8"?>
<form
addrulepath="/administrator/components/com_helloworld/models/rules"
>
<fieldset
name="details"
label="COM_HELLOWORLD_HELLOWORLD_DETAILS"
>
<field
name="id"
type="hidden"
/>
<field
name="greeting"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_GREETING_DESC"
size="40"
class="inputbox"
validate="greeting"
required="true"
hint="COM_HELLOWORLD_HELLOWORLD_GREETING_HINT"
default=""
/>
<field
name="catid"
type="category"
extension="com_helloworld"
class="inputbox"
default=""
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC"
required="true"
>
<option value="0">JOPTION_SELECT_CATEGORY</option>
</field>
<field
name="message"
type="textarea"
rows="5"
cols="80"
label="COM_HELLOWORLD_HELLOWORLD_MESSAGE_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_MESSAGE_DESC"
hint="COM_HELLOWORLD_HELLOWORLD_MESSAGE_HINT"
required="true"
>
</field>
<field
name="captcha"
type="captcha"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC"
validate="captcha"
>
</field>
<fields name="params">
<field
name="show_category"
type="list"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
default=""
useglobal="true"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
</fields>
</fieldset>
</form>
The above form includes a captcha. To get this working you need to
The existing controller (in controller.php) will not need to be changed, but we'll need to define a new view, layout and model for the new form.
site/views/form/view.html.php
<?php
/**
* @package Joomla.Administrator
* @subpackage com_helloworld
*
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
/**
* HelloWorld View
* This is the site view presenting the user with the ability to add a new Helloworld record
*
*/
class HelloWorldViewForm extends JViewLegacy
{
protected $form = null;
protected $canDo;
/**
* Display the Hello World view
*
* @param string $tpl The name of the layout file to parse.
*
* @return void
*/
public function display($tpl = null)
{
// Get the form to display
$this->form = $this->get('Form');
// Get the javascript script file for client-side validation
$this->script = $this->get('Script');
// Check that the user has permissions to create a new helloworld record
$this->canDo = JHelperContent::getActions('com_helloworld');
if (!($this->canDo->get('core.create')))
{
$app = JFactory::getApplication();
$app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error');
$app->setHeader('status', 403, true);
return;
}
// Check for errors.
if (count($errors = $this->get('Errors')))
{
throw new Exception(implode("\n", $errors), 500);
}
// Call the parent display to display the layout file
parent::display($tpl);
// Set properties of the html document
$this->setDocument();
}
/**
* Method to set up the html document properties
*
* @return void
*/
protected function setDocument()
{
$document = JFactory::getDocument();
$document->setTitle(JText::_('COM_HELLOWORLD_HELLOWORLD_CREATING'));
$document->addScript(JURI::root() . $this->script);
$document->addScript(JURI::root() . "/administrator/components/com_helloworld"
. "/views/helloworld/submitbutton.js");
JText::script('COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE');
}
}
And into the layout file:
site/views/form/templ/edit.php
<?php
/**
* @package Joomla.Administrator
* @subpackage com_helloworld
*
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* This layout file is for displaying the front end form for capturing a new helloworld message
*
*/
// No direct access
defined('_JEXEC') or die('Restricted access');
JHtml::_('behavior.formvalidator');
?>
<form action="<?php echo JRoute::_('index.php?option=com_helloworld&layout=edit'); ?>"
method="post" name="adminForm" id="adminForm" class="form-validate">
<div class="form-horizontal">
<fieldset class="adminform">
<legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_DETAILS') ?></legend>
<div class="row-fluid">
<div class="span6">
<?php echo $this->form->renderFieldset('details'); ?>
</div>
</div>
</fieldset>
</div>
<div class="btn-toolbar">
<div class="btn-group">
<button type="button" class="btn btn-primary" onclick="Joomla.submitbutton('helloworld.save')">
<span class="icon-ok"></span><?php echo JText::_('JSAVE') ?>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn" onclick="Joomla.submitbutton('helloworld.cancel')">
<span class="icon-cancel"></span><?php echo JText::_('JCANCEL') ?>
</button>
</div>
</div>
<input type="hidden" name="task" />
<?php echo JHtml::_('form.token'); ?>
</form>
All the above is similar to the admin functionality, except there are certain fields we don't want to allow the front-end user to see (eg the permissions) and instead of the standard Joomla admin buttons (and toolbar) we use a Save button and a Cancel button styled using the Bootstrap CSS, and which result in the task parameter being set to helloworld.save or helloworld.cancel.
Our new model is almost exactly the same as the admin equivalent; we just have to point to our new add-form instead.
site/models/form.php
<?php
/**
* @package Joomla.Administrator
* @subpackage com_helloworld
*
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
/**
* HelloWorld Model
*
* @since 0.0.1
*/
class HelloWorldModelForm extends JModelAdmin
{
/**
* Method to get a table object, load it if necessary.
*
* @param string $type The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return JTable A JTable object
*
* @since 1.6
*/
public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array())
{
return JTable::getInstance($type, $prefix, $config);
}
/**
* Method to get the record form.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return mixed A JForm object on success, false on failure
*
* @since 1.6
*/
public function getForm($data = array(), $loadData = true)
{
// Get the form.
$form = $this->loadForm(
'com_helloworld.form',
'add-form',
array(
'control' => 'jform',
'load_data' => $loadData
)
);
if (empty($form))
{
return false;
}
return $form;
}
/**
* Method to get the data that should be injected in the form.
* As this form is for add, we're not prefilling the form with an existing record
* But if the user has previously hit submit and the validation has found an error,
* then we inject what was previously entered.
*
* @return mixed The data for the form.
*
* @since 1.6
*/
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = JFactory::getApplication()->getUserState(
'com_helloworld.edit.helloworld.data',
array()
);
return $data;
}
/**
* Method to get the script that have to be included on the form
* This returns the script associated with helloworld field greeting validation
*
* @return string Script files
*/
public function getScript()
{
return 'administrator/components/com_helloworld/models/forms/helloworld.js';
}
}
Handling the form HTTP POST
As in the admin case, we'll need to create a new controller in the /controllers subdirectory with save() and cancel() methods, and we'll need a model with a save() method. In fact, because the above model class (in site/models/form.php) extends JModelAdmin, we'll be able to reuse it without adding any more code. (For just displaying the form our model class could have extended JModelForm, but JModelAdmin extends JModelForm and provides additional functions such as the save() one which we need).
Note that we don't need any new view file or layout file, because we're just going to redirect the user back to the same form.
So the only new file we need is a new controller in a new controllers subdirectory:
site/controllers/helloworld.php
<?php
/**
* @package Joomla.Site
* @subpackage com_helloworld
*
* @copyright Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
/**
* HelloWorld Controller
*
* @package Joomla.Site
* @subpackage com_helloworld
*
* Used to handle the http POST from the front-end form which allows
* users to enter a new helloworld message
*
*/
class HelloWorldControllerHelloWorld extends JControllerForm
{
public function cancel($key = null)
{
parent::cancel($key);
// set up the redirect back to the same form
$this->setRedirect(
JUri::getInstance()->current(),
JText::_(COM_HELLOWORLD_ADD_CANCELLED)
);
}
/*
* Function handing the save for adding a new helloworld record
* Based on the save() function in the JControllerForm class
*/
public function save($key = null, $urlVar = null)
{
// Check for request forgeries.
JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
$app = JFactory::getApplication();
$input = $app->input;
$model = $this->getModel('form');
// Check that this user is allowed to add a new record
if (!JFactory::getUser()->authorise( "core.create", "com_helloworld"))
{
$app->enqueueMessage(JText::_('JERROR_ALERTNOAUTHOR'), 'error');
$app->setHeader('status', 403, true);
return;
}
// get the data from the HTTP POST request
$data = $input->get('jform', array(), 'array');
// set up context for saving form data
$context = "$this->option.edit.$this->context";
// Validate the posted data.
// First we need to set up an instance of the form ...
$form = $model->getForm($data, false);
if (!$form)
{
$app->enqueueMessage($model->getError(), 'error');
return false;
}
// ... and then we validate the data against it
$validData = $model->validate($form, $data);
// Handle the case where there are validation errors
if ($validData === false)
{
// Get the validation messages.
$errors = $model->getErrors();
// Display up to three validation messages to the user.
for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
{
if ($errors[$i] instanceof Exception)
{
$app->enqueueMessage($errors[$i]->getMessage(), 'warning');
}
else
{
$app->enqueueMessage($errors[$i], 'warning');
}
}
// Save the form data in the session.
$app->setUserState($context . '.data', $data);
// Redirect back to the same screen.
$this->setRedirect(JUri::getInstance()->current());
return false;
}
// add the 'created by' and 'created' date fields
$validData['created_by'] = JFactory::getUser()->get('id', 0);
$validData['created'] = date('Y-m-d h:i:s');
// Attempt to save the data.
if (!$model->save($validData))
{
// Handle the case where the save failed
// Save the data in the session.
$app->setUserState($context . '.data', $validData);
// Redirect back to the edit screen.
$this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
$this->setMessage($this->getError(), 'error');
$this->setRedirect(JUri::getInstance()->current());
return false;
}
// clear the data in the form
$app->setUserState($context . '.data', null);
// notify the administrator that a new helloworld message has been added on the front end
// get the id of the person to notify from global config
$params = $app->getParams();
$userid_to_email = (int) $params->get('user_to_email');
$user_to_email = JUser::getInstance($userid_to_email);
$to_address = $user_to_email->get("email");
// get the current user (if any)
$current_user = JFactory::getUser();
if ($current_user)
{
$current_username = $current_user->get("username");
}
else
{
$current_username = "a visitor to the site";
}
// get the Mailer object, set up the email to be sent, and send it
$mailer = JFactory::getMailer();
$mailer->addRecipient($to_address);
$mailer->setSubject("New helloworld message added by " . $current_username);
$mailer->setBody("New greeting is " . $validData['greeting']);
try
{
$mailer->send();
}
catch (Exception $e)
{
JLog::add('Caught exception: ' . $e->getMessage(), JLog::Error, 'jerror');
}
$this->setRedirect(
JUri::getInstance()->current(),
JText::_('COM_HELLOWORLD_ADD_SUCCESSFUL')
);
return true;
}
}
In the above file we've been able to reuse the cancel() method from the parent JControllerForm class, with the exception that we want to redirect back to the same form. (The parent cancel() method redirects back to the view showing the list of records - as in the admin case).
However, our save() method functionality is significantly different from that of the parent class, so it's easier just to write our own.
The mailing functionality relies on you having set up mail in your Joomla instance. If you're using localhost then one possibility is to configure this using a gmail account, and you can easily find the smtp settings for this on the Internet. Note that you'll need to allow "less secure apps" on the gmail account.
Configuration Parameters
We've introduced two new configuration parameters - the captcha to display on the form, and the user to email with notification of a new helloworld message being added. So update the configuration settings as follows:
admin/config.xml
<?xml version="1.0" encoding="utf-8"?>
<config>
<fieldset
name="greetings"
label="COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_LABEL"
description="COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_DESC"
>
<field
name="show_category"
type="radio"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
default="0"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field
name="captcha"
type="plugins"
folder="captcha"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC"
default="0"
filter="cmd"
>
<option value="">JOPTION_USE_DEFAULT</option>
<option value="0">JOPTION_DO_NOT_USE</option>
</field>
<field
name="user_to_email"
type="user"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_DESC"
default="0"
>
</field>
</fieldset>
<fieldset
name="permissions"
label="JCONFIG_PERMISSIONS_LABEL"
description="JCONFIG_PERMISSIONS_DESC"
>
<field
name="rules"
type="rules"
label="JCONFIG_PERMISSIONS_LABEL"
class="inputbox"
validate="rules"
filter="rules"
component="com_helloworld"
section="component"
/>
</fieldset>
</config>