Все статьи цикла:

  1. Разработка расширения для Joomla! 3.0 – подготовка
  2. Разработка расширения для Joomla! 3.0 – начинаем разработку
  3. Текущая статья
  4. Разработка расширения для Joomla! 3.0 – больше функционала
  5. Разработка расширения для Joomla! 3.0 – интерфейс администратора и доработка кода

 

В предыдущих статьях мы рассмотрели подготовку и начало разработки. В этой части мы сфокусируемся на деталях, создавая те функции и файлы, которые будут являться ядром нашего расширения.

Шаг 1: Детали и функции моделей

Итак, у нас есть базовые файлы, и мы начали создавать наши модели. Теперь нам необходимо определить модели, которые мы будем наполнять функциями. Вот краткий обзор этих моделей и функций:

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

Теперь мы можем приступить к наполнению моделей. В процессе очень важно следовать следующему принципу – наш список динамичен. Мы всегда можем вернуться к списку и добавить или удалить функции по необходимости. Не бойтесь по мере прогресса пересматривать этот список. Возможно мы его упростим, добавляя функцию в определенную модель, а может быть понадобится создать более абстрактную функцию, которую мы добавим в модель по умолчанию (default model). В одном нужно быть уверенным, мы не хотим постоянно переписывать один и тот же код. Если мы замечаем за собой такое поведение, то настало время подумать о том, как сделать код более абстрактным и перенести его в общую модель. Давайте начнем создавать наши модели.

Шаг 2: Создаем модели

В одной статье мы не можем рассмотреть все модели. Поэтому мы сфокусируемся на нескольких ключевых моделях, которые помогут продемонстрировать большую часть компонента и оставим в стороне второстепенные модели, которые вы сами сможете посмотреть в соответствующем GitHub репозитории.

Мы начнем с модели "книга" (book). Когда я только начинал размышлять о том, с чего начать написание кода, я решил начать с наиболее конкретного и далее перейти к модели "библиотека" (library) и модели "профиль" (profile). Этому есть простое объяснение. Книга является самым маленьким элементом, библиотека состоит из книг, а профиль содержит библиотеку. Но сначала мы создадим модель по умолчанию (default model). Мы будем использовать её для функций, которые понадобятся всем нашим моделям, а так как мы пишем объектно-ориентированный код, мы не хотим писать одни и те же функции в каждой модели.

models/default.php

<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrModelsDefault extends JModelBase
{
	protected $__state_set = null;
	protected $_total = null;
	protected $_pagination = null;
	protected $_db = null;
	protected $id = null;
	protected $limitstart = 0;
	protected $limit = 10;

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

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

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

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

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

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

		return $row;
	}


Так как большинство наших моделей будут сохранять данные, мы создали стандартную функцию store(). Мы будем расширять эту функцию по мере надобности.

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

		return $previous;
	}

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

 


Нам часто понадобится устанавливать и получать переменные класса моделей из разных мест компонента. Вместо того, чтобы позволять напрямую устанавливать или ссылаться на переменные, мы будем использовать функции get и set.

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

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

		$item = $db->loadObject();

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

		return $list;
	}

 


Эти две функции являются базовыми для получения элемента и получения списка элемента.

Мы используем нижнее подчеркивание ( _ ) для определения защищенных (protected) функций.

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

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

		return $db->getNumRows();
	}

 


Эти две функции используются для получения списка из базы данных и получения количества элементов в списке.

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

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

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

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

		return $this->_pagination;
	}
}



Последние три функции используются для постраничной навигации. Мы рассмотрим их более подробно в следующих статьях.

models/book.php

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

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


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

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

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

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

		return $query;
	}

	/**
	* Builds the filter for the query
	* @param object Query object
	* @return object Query object
	*
	*/
	protected function _buildWhere(&$query)
	{
		if(is_numeric($this->_book_id))
		{
			$query->where('b.book_id = ' . (int) $this->_book_id);
		}

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

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

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

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

		return $query;
	}
}


models/library.php

<?php // no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrModelsLibrary extends LendrModelsDefault
{ 
	//Define class level variables
	var $_library_id = null;
	var $_user_id = null;
	var $_published = 1;

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

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

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

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

		return $library;
	} 

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

		$n = count($libraries);

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

		return $libraries;
	} 

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

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

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

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

		return $query;
	}

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

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

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

		return $query;
	}
}


Это прекрасный пример расширения базового класса. Обратите внимание, что у нас есть функция под названием getItem. Функция с таким же названием есть и в модели по умолчанию. Здесь мы получаем основные детали «элемента» (в этом случае объект библиотеки) используя родительскую функцию getItem, далее мы добавляем список книг. Обратите внимание, что мы устанавливаем user_id для модели книги.

models/profile.php

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

class LendrModelsProfile extends LendrModelsDefault
{	 
	//Define class level variables
	var $_user_id = null;
	 
	function __construct()
	{
		$app = JFactory::getApplication();

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

		parent::__construct();
	}
	 
	function getItem()
	{
		$profile = JFactory::getUser($this->_user_id);
		$userDetails = JUserHelper::getProfile($this->_user_id);
		$profile->details = isset($userDetails->profile) ? $userDetails->profile : array();

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

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

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

		return $profile;
	}


В этой функции мы не ссылаемся на родительский метод getItem. Профиль уникален по своей структуре, а так как мы пишем, основываясь на Joomla! 3 CMS, мы можем не включать набор таблиц и методов, относящихся к пользователям и профилю.

У Lendr будет ограниченное число полей профиля и ему необходимы только определенные поля, доступные в плагине профиля Joomla!.

После получения профиля мы инстанцируем несколько дополнительных моделей для получения связанных данных, в нашем случае модель библиотеки и списка ожиданий. В дальнейшем мы вернемся к этой функции и добавим модели обзоров и избранного.

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

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

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

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

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

		return $query;
	}
 
}


Убедитесь, что вы включили плагин профиля в менеджере расширений.

Шаг 3: подключаем дополнительные ресурсы

В нашем компоненте есть некоторые аспекты, в которых было бы неплохо подключить сторонние возможности, чем заново изобретать велосипед. Упростить и стилизовать наши шаблоны нам поможет Bootstrap. Другими полезными ресурсами, которые мы могли бы включить в наш компонент, являются Gravatar и Open Library. Если вы не знакомы с этими инструментами, вы можете прочитать о них на соответствующих сайтах. Ниже представлено краткое пояснение того, как они будут использоваться в Lendr.

Gravatar

Gravatar предоставляет простой путь к получению картинки или аватара, связанных с адресом электронной почты. Lendr использует это для отображения картинок профиля. Мы рассмотрим код подключения, когда начнем описывать шаблоны.

Open Library

Open Library предоставляет прекрасную возможность для получения обложки книги из ISBN. Это позволяет нам легко включить обложку каждой книги без необходимости поддержки места хранения этих обложек, загрузки картинок и т.д. У Open Library есть несколько полей, которые могут быть использованы для создания ссылки на обложку книги и возврата картинки. Lendr использует ISBN в форме Add Book, которую мы рассмотрим ниже при создании шаблонов.

Шаг 4: Создаем шаблоны и стили

Первым делом мы возвращаемся к нашей точке входа компонента, а именно к дефолтному контроллеру, на который мы ссылаемся в корневом файле  lendr.php. После создания наших моделей стало ясно, что необходимо обновить место старта пользователя. Мы изменили следующую строку в контроллере  controllers/default.php:

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

 


Этот код будет перенаправлять пользователя к списку профилей. Это первое представление, которое мы рассмотрим. В нашей папке  views есть папка  profile со следующей структурой:

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

 



Сначала рассмотрим  html.php файл. К файлу этого типа обращается Joomla! по умолчанию согласно соглашению по именованию файлов. Мы видели базовую структуру этих файлов в предыдущей статье.

views/porfile/html.php

 

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

class LendrViewsProfileHtml extends JViewHtml
{
	function render()
	{
		$app = JFactory::getApplication();
		$layout = $app->input->get('layout');

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

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

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

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

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

		}

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

 


Этот файл содержит единственную функцию render(), к которой обращаются контроллеры. Внутри файла мы определяем переменные, которые будут использоваться определенными шаблонами. Здесь мы в первый раз используем LendrHelpersView. Соглашение именования переменных используемых в Lendr помогают восприятию кода. Имена некоторых переменных мы начали с нижнего подчеркивания ( _ ) и имени представления. Это помогает в нескольких ситуациях.

Во-первых, сразу видно, что определенные переменные являются объектами представлений, если содержат имя представления. Мы можем отобразить объект там, где это нам необходимо.

Во-вторых, нижнее подчеркивание указывает на факт того, что у нас частичный шаблон. Это помогает нам запомнить, что представление скорее всего будет включено в другое представление и не используется как отдельное представление.

Так как компонент использует новую MVC структуру, мы расширяем классJViewHtml.

Так как мы вызвали хелпер представления (view helper) LendrHelpersView есть смысл рассмотреть и его.

helpers/view.php

<?php
// no direct access
defined('_JEXEC') or die('Restricted access');
 
class LendrHelpersView
{
	function load($viewName, $layoutName='default', $viewFormat='html', $vars=null)
	{
		// Get the application
		$app = JFactory::getApplication();

		$app->input->set('view', $viewName);

		// Register the layout paths for the view
		$paths = new SplPriorityQueue;
		$paths->insert(JPATH_COMPONENT . '/views/' . $viewName . '/tmpl', 'normal');
		$viewClass = 'LendrViews' . ucfirst($viewName) . ucfirst($viewFormat);
		$modelClass = 'LendrModels' . ucfirst($viewName);

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

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

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

		return $view;
	}
}


Этот файл предоставляет функционал по многим аспектам схожий с контроллером по умолчанию. У нас есть более длинный список параметров, а также foreach для назначения переменных, но в остальном он очень похож. Этот хелпер представления полезен для вызова классов представления и назначения переменных нашим частичным шаблонам и другим частям компонента.

Возвращаясь к оригинальному html-файлу представления, обратите внимание на параметры, которые мы передаем в функцию load хелпера. Первым параметром мы говорим хелперу какую папку мы хотим использовать. Второй параметр указывает на файл шаблона. В третьем параметре мы передаем тип формата страницы.

Мы также создали файл  phtml.php для отображения частичного  html.php. Мы используем этот файл вместо стандартного html представления, так как внутри файла  html.php будут вызываться различные функции. Так как в большинстве случаев для частичных шаблонов нам необходима только функция render(), мы не хотим нагружать их лишним кодом, связанным со стандартным файлом представления.

views/porfile/phtml.php

<?php
// no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
//Display partial views
class LendrViewsProfilePhtml extends JViewHTML
{
	function render()
	{
		return parent::render();
	}
}


views/porfile/tmpl/_entry.php

<div>
	<a href="/<?php echo JRoute::_('index.php?option=com_lendr&view=profile&layout=profile&id='.
		$this->profile->id); ?>">
	<img src="http://www.gravatar.com/avatar/<?php 
		echo md5(strtolower(trim($this->profile->email))); ?>?s=60" />
	</a>
	<div>
		<h4>
			<a href="/<?php echo JRoute::_('index.php?option=com_lendr&view=profile&layout=profile&profile_id='.
				$this->profile->id); ?>"><?php echo $this->profile->name; ?>
			</a>
		</h4>
		<p>
			<strong><?php echo JText::_('COM_LENDR_TOTAL_BOOKS'); ?></strong>: 
			<?php echo $this->profile->totalBooks; ?><br />
			<strong><?php echo JText::_('COM_LENDR_TOTAL_REVIEWS'); ?></strong>: 
			<?php echo $this->profile->totalReviews; ?>
		</p>
	</div>
</div>


Как уже упоминалось, мы используем Gravatar API для отображения аватаров пользователя. Обратите внимание на широкое использование стилей и разметки Bootstrap.

views/porfile/tmpl/list.php

<h2><?php echo JText::_('COM_LENDR_PROFILES'); ?></h2>
<div>
	<?php 
	for($i=0, $n = count($this->profiles);$i<$n;$i++) 
	{
		$this->_profileListView->profile = $this->profiles[$i];
		echo $this->_profileListView->render();
	} ?>
</div>


Этот файл является главным файлом, который по умолчанию будет отдавать пользователям компонент. Он был установлен в контроллере по умолчанию. Представление списка состоит их стандартного  html.php, расположенного в папке представления профиля и частичного шаблона  _profileListView. Другими словами мы вызываем это представление, которое загружает контейнер для списка профилей. Внутри этого представления мы вызываем представление частичного шаблона ( _entry.php), которое отобразит шаблон каждого отдельного профиля.

Частичные шаблоны создают своего рода html-блоки, которые можно повторно использовать в разных представлениях.

views/porfile/tmpl/profile.php

<a href="/<?php 
	echo JRoute::_('index.php?option=com_lendr&view=profile&layout=list'); 
	?>"><i></i> <?php echo JText::_('COM_LENDR_BACK'); ?>
</a>
<h2><?php echo $this->profile->name; ?></h2>
<div>
	<div>
		<img src="http://www.gravatar.com/avatar/<?php 
			echo md5(strtolower(trim($this->profile->email))); ?>?s=180" />
	</div>
	<div>
		<dl>
		<dt><?php echo JText::_('COM_LENDR_PROFILE_NAME'); ?></dt>
		<dd><?php echo $this->profile->name; ?></dd>
		<dt><?php echo JText::_('COM_LENDR_PROFILE_JOIN'); ?></dt>
		<dd><?php 
			echo JHtml::_('date', 
				$this->profile->registerDate, JText::_('DATE_FORMAT_LC3')); 
			?></dd>
		<dt><?php echo JText::_('COM_LENDR_PROFILE_BIO'); ?></dt>
		<dd><?php if(isset($this->profile->details['aboutme'])) 
				echo $this->profile->details['aboutme']; ?></dd>
		</dl>
	</div>
</div>
<br />
<div>
	<div>
		<ul>
			<li>
				<a href="#libraryTab" data-toggle="tab">
					<?php echo JText::_('COM_LENDR_LIBRARY'); ?>
				</a>
			</li>
			<li>
				<a href="#wishlistTab" data-toggle="tab">
					<?php echo JText::_('COM_LENDR_WISHLIST'); ?>
				</a>
			</li>
			<li>
				<a href="#waitlistTab" data-toggle="tab">
					<?php echo JText::_('COM_LENDR_WAITLIST'); ?>
				</a>
			</li>
		</ul>
		<div>
			<div id="libraryTab">
				<?php 
				if($this->profile->isMine) 
				{ ?>
					<a href="#newBookModal" role="button" data-toggle="modal">
						<i></i> <?php echo JText::_('COM_LENDR_ADD_BOOK'); ?>
					</a>
				<?php } ?>
				<h2><?php echo JText::_('COM_LENDR_LIBRARY'); ?></h2>
				<?php echo $this->_libraryView->render(); ?>
			</div>
			<div id="wishlistTab">
				<h2><?php echo JText::_('COM_LENDR_WISHLIST'); ?></h2>
			</div>
			<div id="waitlistTab">
				<h2><?php echo JText::_('COM_LENDR_WAITLIST'); ?></h2>
				<?php echo $this->_waitlistView->render(); ?>
			</div>
		</div>
	</div>
</div>
 
<?php echo $this->_addBookView->render(); ?>

 


Этот шаблон профиля является представлением, которое пользователь увидит переходя в конкретный профиль. Так же как и в представлении списка мы широко используем частичные шаблоны для повторного использования кода. Мы опять используем Gravatar для картинки профиля (на этот раз большего размера), а также различные bootstrap функции (tabs, buttons, wells, description blocks). Заметьте, что каждая из наших вкладок использует частичный шаблон для отображения соответствующего контента.

Оставшиеся шаблоны

Мы рассмотрели все шаблоны вкладки profile, но в процессе внутри профиля будут отображаться дополнительные шаблоны. Для сокращения статьи мы не будем рассматривать код каждого представления. Вместо этого вы можете посмотреть его наGitHub репозитории.

Шаг 5: Javascript и CSS

Последнее что мы расмотрим в этой статье – это начало разработки Javascript и CSS нашего компонента. Так как Lendr использует Bootstrap и jQuery, модальное окно по добавлению книги, рассмотренное в предыдущем шаге, будет подключено автоматически. Но во многих местах компонента мы будем использовать функции, требующие написания JavaScript кода, а также написания CSS стилей. Мы добавим следующий хелпер для подключения стилей и скриптов:

helpers/style.php

<?php
// no direct access
defined('_JEXEC') or die('Restricted access');
 
class LendrHelpersStyle
{
	function load()
	{
		$document = JFactory::getDocument();

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

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


Здесь мы указываем весь css и javascript, связанный с нашим компонентом. Это хелпер файл расположен в папке  helpers и следует соглашению именования классов. Этот класс подключается автоматически благодаря регистрации префикса Lendr в корневом файле компонента. Мы вызываем этот класс из того же корневого файла с помощью следующей строки:

lendr.php

//Load styles and javascripts
LendrHelpersStyle::load();


Теперь мы подключили наши скрипты и CSS и можем начинать добавлять функции. Первым делом мы добавим функцию, которая связана с модальным окном добавления книги. После заполнения формы пользователь отправляет форму с помощью кнопки “Add”. Нажатие на эту кнопку вызывает javascript функцию addBook():

assets/js/lendr.js

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

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

		}
	}
});
}

 


В этой функции мы сначала ипользуем jQuery для создания объекта bookInfo, который содержит все переменные нашей формы. Как только мы поместили их в единую форму, мы начинаем создавать ajax запрос, снова используя jQuery. Обратите внимание, что мы указали детали в URL, включающие контроллер, формат и tmpl-тип. Так как Lendr является расширением Joomla! 3.x, то наши контроллеры являются контроллерами с единственной функцией execute(), которая и будет выполнена при отправке запроса. Формат используется для возвращения только чистых данных из контроллера, а tmpl говорит шаблону Joomla! какой файл использовать ( component.php или  index.php). Далее мы описываем тип отправки, в нашем случае POST. Для данных мы назначаем объектbookInfo, а в качестве типа мы указываем JSON.

Далее контроллер (в этом случае  add.php) обработает отправку формы, передаст её в корректную модель для сохранения и возвратит результат. Результатом будет массив в формате JSON с установленной переменной success. Ниже приведен код этого контроллера:

controllers/add.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' );
 
class LendrControllersAdd extends JControllerBase
{
	public function execute()
	{
		$return = array("success"=>false);

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

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

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

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


Примечание: ob_start()ob_get_contents() и ob_clean()используются для рендеринга частичных шаблонов и возврата html в javascript для динамического добавления на страницу. Результатом этой функции execute()является echo JSON-массива. Так получается потому, что мы используем этот контроллер в AJAX.

Возвращаясь к javascript функции, мы определяем результат AJAX запроса и парсим ответ:

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

 


Здесь мы берем html (который мы вывели в контроллере и назначили переменной html) и добавляем его к таблице. Также мы прячем модальное окно. На данный момент у нас остался пустой else, который мы заполним позже подходящим сообщением.

Итог

Вы осилили эту часть? Она, несомненно, содержит больше деталей и кода, чем предыдущие статьи, но должна обеспечить достаточный подход к разработке расширений коммерческого уровня, основываясь на стандартах, используя новую MVC функциональность, а также другие уникальные возможности Joomla! 3.x.

В следующей статье мы продолжим заполнять модели и контроллеры, представления и javascript, необходимые для разработки расширения.

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

        return $previous;
    }

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