Четвертая статья из цикла "Создание MVC компонента для Joomla 1.6-2.5"

Сегодня мы рассмотрим создание формы редактирования для админки!

Для организации вывода списка записей в нашем компоненте нужно создать контроллер, модель и вид:

имя_компонентаControllerИмя_вида - контроллер должен лежать в папке controllers, Файл должен называться как имя_вида.php, и контроллер для списка должен унаследоваться от класса JControllerAdmin и содержать в себе метод определяющий класс модели для работы с записью в таблице!
Класс будет наследовать в себе методы для удаления записей, для публикации и для снятия с публикации, и для смены порядка вывода!
Для вызова метода в контроллере нужно вызвать имя_контроллера.имя_задачи

Код контроллера для списка:
<?php
// No direct access
defined( '_JEXEC' ) or die;
//Подключаем класс JControllerAdmin
jimport( 'joomla.application.component.controlleradmin' );

/**
 * Контроллер TestControllerTestlist наследуется от класса JControllerAdmin
 * и получает его методы на сохранение порядка, удаления, публикации
 */
class TestControllerTestlist extends JControllerAdmin
{
	/**
	 * Конструктор класса
	 */
	function __construct( $config = array() )
	{
		parent::__construct( $config );
	}
	/**
	 * Устанавливаем модель которая отвечает за функции удаления и снятия публикации для записей таблицы #__test
	 * тоесть мы вызываем модель TestModelTestedit
	 */
	public function getModel( $name = 'testedit', $prefix = 'testModel', $config = array( 'ignore_request' => true ) )
	{
		return parent::getModel( $name, $prefix, $config );
	}

}

Далее нам нужно создать модель:

имя_компонентаModelИмя_вида - модель должна лежать в папке models, Файл должен называться как имя_вида.php, и модель для списка должна унаследоваться от класса JModelList. В модели в принципе достаточно только создать метод getListQuery() и в ней вернуть запрос который выдаст нам все записи для отображения списка! Разбиение на страницы будет выполнено автоматически все остальные методы в примере есть необязательными и служат для создания фильтров и сортировки по определенным полям!

Код модели для отображения списка записей:
<?php
// No direct access
defined( '_JEXEC' ) or die;
//Подключаем класс JModelList
jimport( 'joomla.application.component.modellist' );
/**
 * Класс TestModelTestlist
 */
class TestModelTestlist extends JModelList
{
	/**
	 * Конструктор класса
	 */
	public function __construct( $config = array() )
	{
		//Устанавливаем поля по которым будет сортировка
		if ( empty( $config['filter_fields'] ) ) {
			$config['filter_fields'] = array( 'title', 'state', 'ordering', 'created_by', 'created', 'id' );
		}
		parent::__construct( $config );
	}
	/**
	 * Создаем поля для фильтрации
	 */
	protected function populateState( $ordering = null, $direction = null )
	{
		if ( $layout = JFactory::getApplication()->input->get( 'layout' ) ) {
			$this->context .= '.' . $layout;
		}
		//получаем значение поля search и устанавливаем в переменную filter.search
		$search = $this->getUserStateFromRequest( $this->context . '.filter.search', 'filter_search' );
		$this->setState( 'filter.search', $search );
		$published = $this->getUserStateFromRequest( $this->context . '.filter.published', 'filter_published', '' );
		$this->setState( 'filter.published', $published );
		//Устанавливаем по умолчанию по какому полю сортировать и направление сортировки
		parent::populateState( 'title', 'asc' );
	}
	protected function getStoreId( $id = '' )
	{
		$id .= ':' . $this->getState( 'filter.search' );
		$id .= ':' . $this->getState( 'filter.published' );
		$id .= ':' . $this->getState( 'filter.author_id' );
		return parent::getStoreId( $id );
	}
	/**
	 * Метод возвращает запрос для формиорвания списка!
	 * Для модели больше ничего не нужно, пользователь может не определять количество записей в таблице по запросу,
	 * все сформируется автоматически, в данно методе описан только запрос и возвращает метод именно запрос!
	 */
	protected function getListQuery()
	{
		$query = $this->getDbo()->getQuery( true );
		$query->select( '`t1`.`id`, `t1`.`title`, `t1`.`alias`, `t1`.`published` as `state`, `t1`.`created`, `t1`.`ordering`, `t1`.`hits`' );
		$query->select( '`u`.`username` as `created_by`, `u`.`id` as `created_by_id`' );
		$query->join( 'LEFT', '`#__users` AS `u` ON `u`.`id` = `t1`.`created_by`' );
		$query->from( '#__test as `t1`' );
		$published = $this->getState( 'filter.published' );
		if ( is_numeric( $published ) ) {
			$query->where( '`t1`.`published`=' . intval( $published ) );
		}
		$search = $this->getState( 'filter.search' );
		if ( !empty( $search ) ) {
			$search = $this->getDbo()->Quote( '%' . $this->getDbo()->escape( $search, true ) . '%' );
			$query->where( '(`t1`.`title` LIKE ' . $search . ' OR `t1`.`alias` LIKE ' . $search . ')' );
		}
		$orderCol = $this->state->get( 'list.ordering' );
		$orderDirn = $this->state->get( 'list.direction' );
		$query->order( $this->getDbo()->escape( $orderCol . ' ' . $orderDirn ) );
		return $query;
	}
}

Вид:

имя_компонентаViewИмя_вида - вид должен лежать в папке views/имя_вида/ и файл должен называться view.html.php, вид должен унаследоваться от класса JView и содержать метод display() для отображения шаблона!

Пример кода вида:
<?php
// No direct access
defined( '_JEXEC' ) or die;
//Подключаем класс JView
jimport( 'joomla.application.component.view' );
/**
 * Класс TestViewTestlist
 */
class TestViewTestlist extends JView
{
	//Список записей
	protected $items;
	//Постраничная навигация
	protected $pagination;
	//Состояния
	protected $state;
	//Пользлватель
	protected $user;
	/**
	 * Отображение вида
	 */
	public function display( $tpl = null )
	{
		//Получаем списко записей из метода getItems который вызываетя в унаследованном методе getItems
		//класса TestModelTestlist
		$this->items = $this->get( 'Items' );
		//Получаем постраниччную навигацию она также вызывается из метода getPagination класса TestModelTestlist
		$this->pagination = $this->get( 'Pagination' );
		//тоже самой и с getState! Модель TestModelTestlist наследуетсься от класса JModelList
		$this->state = $this->get( 'State' );
		//Получчаем объект пользователя
		$this->user = JFactory::getUser();
		//Подключаем помощник вида
		$this->loadHelper( 'testHelper' );
		//выводим панель инструментов
		$this->_setToolBar();
		parent::display( $tpl );
	}
	/**
	 * Method to display the toolbar
	 */
	protected function _setToolBar()
	{
		//Устанавливаем заголовок компонента
		JToolBarHelper::title( JText::_( 'COM_TEST' ) );
		//получаем список возможных разрешений для пользователя
		$canDo = testHelper::getActions( 'testedit' );

		//Если пользователь может добавлять новые звписи
		if ( $canDo->get( 'core.create' ) || ( count( $this->user->getAuthorisedCategories( 'com_test', 'core.create' ) ) ) > 0 ) {
			//Добавляем кнопку "Создавть" которая будет вызывать в контроллере testedit метод add
			JToolBarHelper::addNew( 'testedit.add' );
		}

		//Если пользователь может редактировать
		if ( ( $canDo->get( 'core.edit' ) ) || ( $canDo->get( 'core.edit.own' ) ) ) {
			//Добавляем кнопку "Изменить" которая будет вызывать в контроллере testedit метод edit
			JToolBarHelper::editList( 'testedit.edit' );
		}
		//Если пользователь может менять состояни
		if ( $canDo->get( 'core.edit.state' ) ) {
			//выводим разделитель
			JToolBarHelper::divider();
			//Добавляем кнопку "Опубликовать" которая будет вызывать в контроллере testlist метод publish
			JToolBarHelper::publish( 'testlist.publish', 'JTOOLBAR_PUBLISH', true );
			//Добавляем кнопку "Снять с публикации" которая будет вызывать в контроллере testlist метод publish
			JToolBarHelper::unpublish( 'testlist.unpublish', 'JTOOLBAR_UNPUBLISH', true );
			//выводим разделитель
			JToolBarHelper::divider();
			//Если пользователь может удалять запись
			if ( $canDo->get( 'core.delete' ) ) {
				//Добавляем кнопку "Удалить" которая будет вызывать в контроллере testlist метод delete
				JToolBarHelper::deleteList( 'DELETE_QUERY_STRING', 'testlist.delete', 'JTOOLBAR_DELETE' );
				//выводим разделитель
				JToolBarHelper::divider();
			}
			//Если пользователь может настраивать компонент
			if ( $canDo->get( 'core.admin' ) ) {
				//Добавляем кнопку "Настройки" при нажатии на которую появится всплывающее окно с отображением настроек  компонента
				JToolBarHelper::preferences( 'com_test' );
				//выводим разделитель
				JToolBarHelper::divider();
			}
		}

	}
}

И файл шаблона для отображения вида должен лежать в папке: views/имя_вида/tmpl/default.php - в этом файле содержится основная разметка для вывода списка!

Пример кода шаблона:
<?php
// запрет доступ из вне
defined( '_JEXEC' ) or die;
//по какому полю текущая сортировка
$listOrder = $this->state->get( 'list.ordering' );
//Какое направление сортировки
$listDirn = $this->state->get( 'list.direction' );
$saveOrder = $listOrder == 'ordering';
?>
<form action="<?php echo JRoute::_( 'index.php?option=com_test&view=testlist' ); ?>" method="post" name="adminForm" id="adminForm">
	<fieldset id="filter-bar">
		<div class="filter-search fltlft">
			<label class="filter-search-lbl" for="filter_search"><?php echo JText::_( 'JSEARCH_FILTER_LABEL' ); ?></label>
			<input type="text" name="filter_search" id="filter_search" value="<?php echo $this->escape( $this->state->get( 'filter.search' ) ); ?>" title="<?php echo JText::_( 'FILTER_SEARCH_DESC' ); ?>" />
			<button type="submit" class="btn"><?php echo JText::_( 'JSEARCH_FILTER_SUBMIT' ); ?></button>
			<button type="button" onclick="document.id('filter_search').value='';this.form.submit();"><?php echo JText::_( 'JSEARCH_FILTER_CLEAR' ); ?></button>
		</div>
		<div class="filter-select fltrt">
			<select name="filter_published" class="inputbox" onchange="this.form.submit()">
				<option value=""><?php echo JText::_( 'JOPTION_SELECT_PUBLISHED' ); ?></option>
				<?php echo JHtml::_( 'select.options', JHtml::_( 'jgrid.publishedOptions', array( 'archived' => false, 'trash' => false ) ), 'value', 'text', $this->state->get( 'filter.published' ), true ); ?>
			</select>
		</div>
	</fieldset>
	<div class="clr"></div>
	<table class="adminlist">
		<thead>
		<tr>
			<th width="1%"><input type="checkbox" name="checkall-toggle" value="" onclick="checkAll(this)" /></th>
			<th><?php echo JHtml::_( 'grid.sort', 'JGLOBAL_TITLE', 'title', $listDirn, $listOrder ); ?></th>
			<th width="5%"><?php echo JHtml::_( 'grid.sort', 'JPUBLISHED', 'state', $listDirn, $listOrder ); ?></th>
			<th width="10%">
				<?php
				echo JHtml::_( 'grid.sort', 'JGRID_HEADING_ORDERING', 'ordering', $listDirn, $listOrder );
				if ( $saveOrder ) {
					echo JHtml::_( 'grid.order', $this->items, 'filesave.png', 'testlist.saveorder' );
				}
				?>
			</th>
			<th width="10%"><?php echo JHtml::_( 'grid.sort', 'JGRID_HEADING_CREATED_BY', 'created_by', $listDirn, $listOrder ); ?></th>
			<th width="5%"><?php echo JHtml::_( 'grid.sort', 'JDATE', 'created', $listDirn, $listOrder ); ?></th>
			<th width="1%" class="nowrap"><?php echo JHtml::_( 'grid.sort', 'JGRID_HEADING_ID', 'id', $listDirn, $listOrder ); ?></th>
		</tr>
		</thead>
		<tfoot>
		<tr>
			<td colspan="15">
				<?php echo $this->pagination->getListFooter(); ?>
			</td>
		</tr>
		</tfoot>
		<tbody>
		<?php
		$n = sizeof( $this->items );
		foreach ( $this->items as $i => $item ) {
			$item->max_ordering = 0;
			$ordering = ( $listOrder == 'ordering' );
			$canEdit = $this->user->authorise( 'core.edit', 'com_test.testedit.' . $item->id );
			$canEditOwn = $this->user->authorise( 'core.edit.own', 'com_test.testedit.' . $item->id ) && $item->created_by_id == $this->user->id;
			$canChange = $this->user->authorise( 'core.edit.state', 'com_test.testedit.' . $item->id );
			?>
		<tr class="row<?php echo $i % 2; ?>">
			<td class="center"><?php echo JHtml::_( 'grid.id', $i, $item->id ); ?></td>
			<td>
				<?php if ( $canEdit || $canEditOwn ) : ?>
				<a href="/<?php echo JRoute::_( 'index.php?option=com_test&task=testedit.edit&id=' . $item->id ); ?>"><?php echo $this->escape( $item->title ); ?></a>
				<?php else: ?>
				<?php echo $this->escape( $item->title ); ?>
				<?php endif; ?>
				<p class="smallsub"><?php echo JText::sprintf( 'JGLOBAL_LIST_ALIAS', $this->escape( $item->alias ) ); ?></p>
			</td>
			<td class="center">
				<?php echo JHtml::_( 'jgrid.published', $item->state, $i, 'testlist.', $canChange, 'cb' ); ?>
			</td>
			<td class="order">
				<?php if ( $canChange ): ?>
				<?php if ( $saveOrder ): ?>
					<?php if ( $listDirn == 'asc' ): ?>
						<span><?php echo $this->pagination->orderUpIcon( $i, $n, 'testlist.orderup', 'JLIB_HTML_MOVE_UP', $ordering ); ?></span>
						<span><?php echo $this->pagination->orderDownIcon( $i, $n, true, 'testlist.orderdown', 'JLIB_HTML_MOVE_DOWN', $ordering ); ?></span>
						<?php elseif ( $listDirn == 'desc' ) : ?>
						<span><?php echo $this->pagination->orderUpIcon( $i, $n, 'testlist.orderup', 'JLIB_HTML_MOVE_UP', $ordering ); ?></span>
						<span><?php echo $this->pagination->orderDownIcon( $i, $n, true, 'testlist.orderdown', 'JLIB_HTML_MOVE_DOWN', $ordering ); ?></span>
						<?php endif; ?>
					<?php endif; ?>
				<?php $disabled = $saveOrder ? '' : 'disabled="disabled"'; ?>
				<input type="text" name="order[]" size="5" value="<?php echo $item->ordering; ?>" <?php echo $disabled ?> class="text-area-order" />
				<?php else : ?>
				<?php echo $item->ordering; ?>
				<?php endif; ?>
			</td>
			<td class="center"><?php echo $item->created_by; ?></td>
			<td><?php echo JHTML::_( 'date', $item->created, JText::_( 'DATE_FORMAT_LC4' ) ); ?></td>
			<td><?php echo $item->id; ?></td>
		</tr>
			<?php
		}
		?>
		</tbody>
	</table>
	<input type="hidden" name="task" value="" />
	<input type="hidden" name="boxchecked" value="0" />
	<input type="hidden" name="filter_order" value="<?php echo $listOrder; ?>" />
	<input type="hidden" name="filter_order_Dir" value="<?php echo $listDirn; ?>" />
	<?php echo JHtml::_( 'form.token' ); ?>
</form<

Логика работы:
Когда мы переходим по ссылке: index.php?view=com_test&view=testlist то нам отобразится наш вид в следующем виде: Пример вида списка

При нажатии на любую из кнопок будет обращение к контроллеру testlist который выполнит переданный метод!

Допустим при переходе по ссылке index.php?option=com_test&view=testlist&task=testlist.delete&cid=1 будет означать что выполниться метод удаления который удалит запист с идентификатором id равным 1.
Во время удаления будет также проверяться на уровне контроллера или у пользователя есть права на удаление записи!

Скачать пример компонента com_test можно тут!