Insight Joomla! (DAY 4) - Examing Joomsport (DAY 2) - they are the same DAY!

本文深入探讨了Joomla!的内容管理系统中组件的设计与实现原理,包括菜单项类型与视图布局配置参数的关系,以及如何通过元数据文件来确定可用视图。

What a Component Would be In Real World?


In the last blog, I've just explored the pure principles and theories about strict MVC design of a Joomla! component. However, in real world, things always change. You will find that principles are just rough guidances, you don't have to confine yourself to them. 


Copy a Shadow for Examination


I am not sure to what extent the change I will make to the current web application, and just for backup purpose, I decide to make a shadow copy and work on that copy.


First of all, we need to copy the content of Joomla! folder into a new created folder, say, soccer. And dump the data of the database in phpMyAdmin:


dump-phpmyadmin 

just keep the setting as default, and get a .sql file downloaded.


Secondly, create new database, with new dedicated user, grant the priveleges. And suppose the profile:


dbname: dbname

username: username

pwd: password


Then log into the admin panel of the new web application by accessing new url: localhost/soccer. Go to Global Config->Server, fill in the new data:


j-server-setting 

And the final step is to open the configuration.php file to change the $password to your password. 


That's done! All the following operations will be performed on the new copy.


How does Joomla! determine which views/layouts to show when specifying a menu item's type


In addition to where does Joomla! store the component menu items data, this is the one problem that I am most eager to solve. Joomsport offers six views with one default layout for menu item:


menu-item-type-url


It seems to be a complex question, since no one answered me so far in Joomla! forum. So I have to figure out the logic on my own. It's not really hard, the url is a very useful clue:


http://localhost/soccer/administrator/index.php?option=com_menus&task=type&menutype=mainmenu&cid[]=27&expand=joomsport

The component to handle that job is com_menus, so open:


/administrator/components/com_menus/admin.menus.php:  

$controller = new MenusController( array('default_task' => 'viewMenus') );
$controller->registerTask('apply', 'save');
$controller->execute( JRequest::getCmd( 'task' ) );
$controller->redirect();

what it did is just pass control to main controller, then go to


/administrator/components/com_menus/controller.php: 

and the task parameter in url is type, so find the type method:


	function type()
	{
		JRequest::setVar( 'edit', true );
		$model	=& $this->getModel( 'Item' );
		$view =& $this->getView( 'Item' );
		$view->setModel( $model, true );

		// Set the layout and display
		$view->setLayout('type');
		$view->type();
	}

which tells that, the view and model called is Item, and the layout is type.


open: /administrator/components/com_menus/views/item/view.php


theoretically, the class MenusViewItem in this file should call the counterpart model method to get data:


	function type($tpl = null)
	{
		......

		// Initialize variables
		$item			= &$this->get('Item');
		$expansion		= &$this->get('Expansion');
		$component		= &$this->get('Component');
		$name			= $this->get( 'StateName' );
		$description	= $this->get( 'StateDescription' );
		$menuTypes 		= MenusHelper::getMenuTypeList();

		dump($item, 'item');
		dump($expansion, 'expansion');
		dump($component, 'component');

		// Set document title
		if ($item->id) {
			$document->setTitle(JText::_( 'Menu Item' ) .': ['. JText::_( 'Edit' ) .']');
		} else {
			$document->setTitle(JText::_( 'Menu Item' ) .': ['. JText::_( 'New' ) .']');
		}

		$this->assignRef('item',		$item);
		$this->assignRef('components',	$components);
		$this->assignRef('expansion',	$expansion);

		parent::display($tpl);
	}

It assign three variables: item, components and expansion, which means the views data must be stored in one of them, so I add dump() function to dump these three variables and the result:


jdump-expansion


from the data nature, &expansion contains the tree HTML code as its first element. So let's find out how expansion its value is gained:


$expansion = &$this->get('Expansion');

this statement actually call MenusModelItem method &getExpansion. So open: /administrator/components/com_menus/models/item.php


	function &getExpansion()
	{
		$item				= &$this->getItem();
		$return['option']	= JRequest::getCmd('expand');
		$menutype			= JRequest::getVar('menutype', '', '', 'menutype');

		if ($return['option'])
		{
			require_once(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_menus'.DS.'classes'.DS.'ilink.php');
			$handler		= new iLink($return['option'], $item->id, $menutype);
			$return['html'] = $handler->getTree();
			return $return;
		} else {
			$return['html'] = null;
		}
		return $return;
	}

It calls iLink method getTree to get the HTML code, then open /administrator/components/com_menus/classes/ilink.php, and find the method getTree:


	function getTree()
	{
		$depth = 0;
		$this->reset();
		$class = null;

		// Recurse through children if they exist
		while ($this->_current->hasChildren())
		{
			$this->_output .= '<ul>';
			$children = $this->_current->getChildren();
			for ($i=0,$n=count($children);$i<$n;$i++)
			{
				$this->_current = & $children[$i];
				$this->renderLevel($depth,($i==$n-1)?1:0);
			}
			$this->_output .= '</ul>';
		}
		return $this->_output;
	}

This piece of code doesn't give us anything, but take a look at its constructor:


	function __construct($component, $id=null, $menutype=null)
	{
		parent::__construct();

		if ($id) {
			$this->_cid = "&cid[]=".$id;
		} else {
			$this->_cid = null;
		}

		if ($menutype) {
			$this->_menutype = "&menutype=" . JFilterInput::clean($menutype, 'menutype');
		} else {
			$this->_menutype = null;
		}

		$this->_com = preg_replace( '#\W#', '', $component );

		// Build the tree
		if (!$this->_getOptions($this->_getXML(JPATH_SITE.'/components/com_'.$this->_com.'/metadata.xml', 'menu'), $this->_root))
		{
			if (!$this->_getViews())
			{
				// Default behavior
			}
		}
	}

The core code is the call to $this->_getOption, and it invokes $this->_getXML():


	function _getXML($path, $xpath='control')
	{
		dump($path, 'path');
		// Initialize variables
		$result = null;
		// load the xml metadata
		if (file_exists( $path )) {
			$xml =& JFactory::getXMLParser('Simple');
			if ($xml->loadFile($path)) {
				if (isset( $xml->document )) {
					$result = $xml->document->getElementByPath($xpath);
				}
			}
			return $result;
		}
		return $result;
	}

Above the first line, I put a dump function, to trace out the xml file's path:


mata-xml-path


So, at this point, although I've not clearify the recursive calls and how the XML data be parsed and extracted, we can see that all the XML are under the views' folders which appear in the 'Change Menu Item Type' panel, so we can sum up at the point: Joomla! detect the views by finding its metadata XML file, if it has, then that would be shown in the 'Change Menu Item Type' panel.


The Relation between Metadata Description & Admin Panel:


view:


view-meta


layout:


layout-meta


How does the metadata file result in the basic parameter setting panel on the left(as shown in the above shot) is my biggest discovery today. But I did the exam on team view instead. 


Go to file: /components/com_joomsport/views/blteam/tmpl/default.xml


	<state>
		<name>Team Layout</name>
		<description>Team Layout</description>
		<url addpath="/administrator/components/com_joomsport/elements">
			<param name="sid" type="season" default="0" label="Select Season" description="Season" />
			<param name="tid" type="team" default="0" label="Select Team" description="Team" />
		</url>
		<params>
		</params>
	</state>

There's one line specifies the path: /administrator/components/com_joomsport/elements, which should tell Joomla! where to look for some file, and the directory is:


element-directory


And the attribute 'type' specifies season for one parameter and team for the other, those names may match the php files under the element directory. Let's check out that, open  team.php:


<?php
defined('_JEXEC') or die( 'Restricted access' );
class JElementTeam extends JElement
{

	var $_name = 'team';
	function fetchElement($name, $value, &$node, $control_name)
	{
		global $mainframe;
		$db		=& JFactory::getDBO();
		$doc 		=& JFactory::getDocument();
		$template 	= $mainframe->getTemplate();
		$fieldName	= $control_name.'['.$name.']';
		$article->title = '';
		if ($value)
		{
			$query = "SELECT * FROM #__bl_teams WHERE id=".$value;
			$db->setQuery($query);

			$rows = $db->loadObjectList();
			if(isset($rows[0]))
			{
				$row = $rows[0];
				$article->title = $row->t_name;
			}
		}
		else
		{
			$article->title = JText::_('Select Team');
		}
		$js = "
		function jSelectArticle(id, title, object) {
			document.getElementById(object + '_id').value = id;
			document.getElementById(object + '_name').value = title;
			document.getElementById('sbox-window').close();
		}";
		$doc->addScriptDeclaration($js);
		$link = 'index.php?option=com_joomsport&task=team_menu&tmpl=component&object='.$name;
		JHTML::_('behavior.modal', 'a.modal');
		$html = "\n".'<div style="float: left;"><input style="background: #ffffff;" type="text" id="'.$name.'_name" value="'.htmlspecialchars($article->title, ENT_QUOTES, 'UTF-8').'" disabled="disabled" /></div>';
//		$html .= "\n   <input class=\"inputbox modal-button\" type=\"button\" value=\"".JText::_('Select')."\" />";
		$html .= '<div class="button2-left"><div class="blank"><a class="modal" title="'.JText::_('Select Team').'"  href="'.$link.'" rel="{handler: \'iframe\', size: {x: 650, y: 375}}">'.JText::_('Select').'</a></div></div>'."\n";
		$html .= "\n".'<input type="hidden" id="'.$name.'_id" name="'.$fieldName.'" value="'.(int)$value.'" />';
		return $html;
	}
}

It is a little bit complcated with first glance, basically, it defines a class, with one method, but the last line of the method tells that it returns a piece of HTML code to its caller, and that HTML code begins with '<div style=...' and end in a hidden input tag. If this piece of code appears in the page of editing menu item, then we can confirm the relation between the parameter tag in xml file and the panel. For exactly getting the returned code, I added a dump() function call before it returns.


return-html-code


And in the admin page, use Chrome tools to interrogate the HTML piece:


parameter-panel-html


The code just matchs exactly!!!


And how was the popup window generated?


box-win


It is actually realized by using iframe tag. And the src attribute is a link, which is the same ashref of the parameter button 'Select Team'. And open the link in a new tab:  


link 


The javascript function is just added by  fetchElement method of  JElementclass:


		$js = "
		function jSelectArticle(id, title, object) {
			document.getElementById(object + '_id').value = id;
			document.getElementById(object + '_name').value = title;
			document.getElementById('sbox-window').close();
		}";
		$doc->addScriptDeclaration($js);


And the link is generated:


$link = 'index.php?option=com_joomsport&task=team_menu&tmpl=component&object='.$name;

Finally, it assemblys all the HTML pieces and these variables:


$html = "\n".'<div style="float: left;"><input style="background: #ffffff;" type="text" id="'.$name.'_name" value="'.htmlspecialchars($article->title, ENT_QUOTES, 'UTF-8').'" disabled="disabled" /></div>';
		$html .= '<div class="button2-left"><div class="blank"><a class="modal" title="'.JText::_('Select Team').'"  href="'.$link.'" rel="{handler: \'iframe\', size: {x: 650, y: 375}}">'.JText::_('Select').'</a></div></div>'."\n";
		$html .= "\n".'<input type="hidden" id="'.$name.'_id" name="'.$fieldName.'" value="'.(int)$value.'" />';

Then just return it.


Examing the Frontend Output of Joomsport


Remember the suppppper simple Joomla! template I created a couple of weeks ago?! It comes to be handy now, let's use it to inspect each view/layout its output of joomsport, and then map them to the PHP code behind.


First thing I want to point out is the mechanism of how Joomla! component handle tasks, the theory says that, the methods of main controller class correspond to tasks, the name of method is exactly the same as the task it is supposed to handle, so if you have set task=match in your URL, then Joomla! will seek match() method in controller class difination and run it. There are some preset tasks, such as display, edit, save, delete and so on.. And display is default.


But the observation of Joomsport is:


menu-table


In fact, the URL of the menu item will never contains parameter 'task', whereas in the main controller class of joomsport, there are five methods(we ignore the last one for a while):


main-controller-methods


So, the question is will the method be invoked and in what case if yes?


A trial:

add test code to view_match method:


	function view_match()
	{
		echo 'view match task';
		JRequest::setVar( 'view', 'match' );
		parent::display();
	}


And access: http://localhost/soccer/index.php?option=com_joomsport&task=view_match&id=1 (which is not generated by menu item, its typed manually)

in my Chrome:


view-match-task


This result tells us the view_match method did be invoked. But what this method did is just setting the view parameter, and supposed that task parameter is not set and the view is set to 'match', then the method display will take charge because of default task is display, and let's take a look at its body:


	function display()
	{
		//echo 'display';	//test go here by default
		$view = JRequest::getCmd( 'view' );
		//var_dump($view);	//test 'ltable'
		if(!$view)
		{
			$view = 'ltable';
		}
		JRequest::setVar( 'view', $view );
		$cal = JRequest::getCmd( 'layout' );
		//var_dump($cal);	//test empty
		if($cal == 'calendar')
		{
			JRequest::setVar( 'view', 'calendar' );
			JRequest::setVar( 'layout', 'default' );
		}
		parent::display();
	}

In this function, if the view parameter is set, it will load it, otherwise load ltable view. So, we do not have to bother with setting task when setting view is enough. Finally, the other methods in main controller are redundant utterly.




Refs:

http://docs.joomla.org/Adding_view_layout_configuration_parameters

Joomla! API Docs




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值