Ajax, ZF and Smarty feed reader: part 3

This time we will take a look at the feed list window and the manage window. This will also be the concluding part of the series.

Revisiting index.tpl shows us this (Three vertical dots mark areas that I’ve left out.):

.
.
.
<script type="text/javascript">
	var params = new Array();
	params['parent_id'] = "parent-template";
	params['origin_id'] = "list-template";
	params['new_class'] = "default_window";
	params['target_id'] = "dbuser-drop";
	params['focus_id'] 	= "dbuser-drop-dragger";
	params['table']		= "dbuser";
	params['topstyle']	= "";
	params['template']	= "rss_drop.tpl";
</script>
<input type="button" value="manage feeds" onclick="createWindowParam(params, ajax_rss, 'manageFeeds')"/>
.
.
.

This is a different way of doing basically the same thing as is being done above with function calls using massive amounts of parameters. It is however superior because it’s more dynamic, the number of parameters can change without breaking anything, least of all your programmer back. :)

Let’s revisit common.js:

function createWindowParam(params, ajax_obj, func){
	createWindow(params['parent_id'], params['origin_id'], params['new_class'], params['target_id'], params['focus_id']);
	params = serialize(params);
	ajax_obj.displayWin(params, func);
}

And AjaxForm.class.php:

function displayWin($params, $func){
	$params = unserialize($params);
	$this->$func($params);
	return $this->response;
}

$func would in this case be manageFeeds():

function manageFeeds($params){
	$table = $params['table'];
	$target_id = $params['target_id'];
	$obj = AjaxCommon::loadModel($table);
	$obj->loadSession();
	$sess_key = $table."_dropchildren";
	$_SESSION[$sess_key]['target_id'] = $table."-dropchildren";
	$_SESSION[$sess_key]['id_value'] = $obj->session->id;
	$_SESSION[$sess_key]['template'] = "rss_drop_children.tpl";
	$this->smarty->assign('parent_table', $table);
	$this->smarty->assign('parent_table_id', $obj->session->id);
	$this->smarty->assign('drop_children', $obj->getChildren($obj->session->id));
	$this->response->assignAttr($target_id, 'onclick', "ajax_rss.setFocus('$target_id', '{$params['focus_id']}')");
	$this->displayDropForm($target_id, $params['template'], $table);
	$this->response->combineActions($this->setFocus($target_id, $params['focus_id'], $params['topstyle']));
	return $this->response;
}
function displayDropForm($target_id, $tpl, $table){
	$this->smarty->assign('parent_id', $target_id);
	$this->smarty->assign('table', $table);
	$inner_html = utf8_encode($this->smarty->fetch($tpl));
	$this->response->assignAttr($target_id,'innerHTML',$inner_html);
}

Notice how we use the $_SESSION in a straight way. The ZF session handling with namespaces would have been better, an example of just that can be found in the Writing a CMS/Community with Smarty and the Zend Framework series.

I also opened the manage window when I had already opened the list window and then setFocus() worked just fine. Apparently the issue, with not setting focus that we touched upon in part 2, only applies when we have a single window. An important detail if we ever wanted to resolve that bug. Something else worth remarking on is the use of combineActions() to execute actions of different HTML_AJAX_Action objects. Apparently we use a template called rss_drop_children. This markup is drawn inside a div in the window:

.
.
.
<script type="text/javascript">
	var {$param_name} = new Array();
	{$param_name}['parent_id']  = "parent-template"; 
	{$param_name}['origin_id']  = "list-template"; 
	{$param_name}['new_class']  = "default_window"; 
	{$param_name}['target_id']  = "{$param_name}"; 
	{$param_name}['focus_id'] 	= "{$param_name}-dragger"; 
	{$param_name}['table']		= "feeds"; 
	{$param_name}['id_value']	= "{$row.i_table.feeds}"; 
	{$param_name}['topstyle']	= ""; 
	{$param_name}['template']	= "rss_headlines.tpl"; 
</script>
<a href="#" class="default_link" onclick="createWindowParam({$param_name}, ajax_rss, 'displayHeadlines')">
	{$row[$child_table.row_headline]}
</a>
.
.
.
<script type="text/javascript">
	var drop_id = '{$target_id}';
	{literal}
	Droppables.add(drop_id, {hoverclass:'hoverRow', onDrop:function(element, dropon){dropInsert(element, dropon)}});
	{/literal}
</script>
.
.
.

The first block above will open the feed reader itself where we display all articles for reading. The second block handles subscriptions, a feed can bee drawn from the list feeds window and dropped here which will add it as a subscription. Basically what happens in the second block is that we assign two callback functions and the id of the drop area. Back to common.js:

function dropHandler(dragged_el, target_el){
	var dragger_info = dragged_el.id.split("-");
	var target_info = target_el.id.split("-");
	if(dragger_info[0] != target_info[0]){
		ajax_rss.dropHandler(dragged_el.id, target_el.id);
	}
}
	
function dropInsert(dragged_el, target_el){
	var dragger_info = dragged_el.id.split("-");
	var target_info = target_el.id.split("-");
	if(dragger_info[0] != target_info[0]){
		ajax_rss.dropInsertReference(dragged_el.id, target_el.id);
	}
}

And in php:

function dropHandler($dragger_id, $target_id){
	list($d_table, $d_id, $d_type) = explode("-", $dragger_id);
	list($t_table, $t_id, $t_type) = explode("-", $target_id);
	$t_obj = AjaxCommon::loadModel($t_table);
	$new_values = $t_obj->updateRowWithDeps($d_table, $d_id, $t_id);
	if(is_array($new_values)){
		foreach($new_values as $field => $value){
			$this->response->assignAttr("{$t_table}-{$t_id}-{$field}", 'innerHTML', $value);
		}
	}else{
		$this->response->insertScript("alert('no connection')");
	}
	return $this->response;
}
	
function dropInsertReference($dragger_id, $target_id){
	list($d_table, $d_id, $d_type) = explode("-", $dragger_id);
	list($t_table, $t_id, $t_type) = explode("-", $target_id);
	$obj = AjaxCommon::loadModel($t_table);
	$obj->insertReference($d_id, $d_table, $t_id, $t_table);
	$sess_key = $t_table."_dropchildren";
	$sess_val =& $_SESSION[$sess_key];
	$this->displayDropChildren($sess_val['id_value'], $sess_val['target_id'], $sess_val['template'], $t_table);
	return $this->response;
}

Apparently the two methods responsible are updateRowWithDeps() and insertReference() in AjaxModel. Feel free to explore the inner workings of those two methods at your leisure. In the above code we se $dragger_id, let’s find out where this div comes from. In index.tpl we see the function createListWindow which in turn calls listData():

function listData($table, $target_id, $focus_id = '', $template = '', $topstyle = '', $text_size = 10){
	$_SESSION[$target_id]['desc'] = true;
	$tpl = $template == '' ? 'list.tpl' : $template;
	$_SESSION[$target_id]['tpl'] = $tpl;
	$obj = AjaxCommon::loadModelWithLabels($table);
	$_SESSION[$target_id]['headlines'] = $obj->fields;
	$rows = $obj->fetchToArrDeps();
	$_SESSION[$target_id]['rows'] = $rows;
	$widths = AjaxCommon::getWidths($rows, $text_size, $obj);
	$_SESSION[$target_id]['widths'] = $widths;
	$this->response->assignAttr($target_id, 'onclick', "ajax_rss.setFocus('$target_id', '$focus_id')");
	$total_width = array_sum($widths) + 65;
	$this->response->assignAttr($target_id, 'style', "width:{$total_width}px;");
	$this->displayList($table, $target_id, $rows, $widths, $tpl, $obj->fields);
	$this->response->combineActions($this->setFocus($target_id, $focus_id, $topstyle));
	return $this->response;
}
function displayList($table, $target_id, &$rows, &$widths, $tpl, $headlines){
	$this->smarty->assign('headlines', $headlines);
	$this->smarty->assign('parent_id', $target_id);
	$this->smarty->assign('rows', $rows);
	$this->smarty->assign('widths', $widths);
	$this->smarty->assign('table', $table);
	$inner_html = utf8_encode($this->smarty->fetch($tpl));
	$this->response->assignAttr($target_id,'innerHTML',$inner_html);
}

Notice how I’ve made some half crappy attempt at calculating widths with AjaxCommon::getWidths(), reason being that the headlines of the list are separate from the list itself which is in it’s own div. And the reason for that is that a long list should be scrollable while at the same time the user should be able to see the headlines. So some kind of logic to control the widths are in order to sync with the list, however as you easily can see if you use the application this does not work 100%. Anyway, apparently we use list.tpl here:

.
.
.
{foreach from=$headlines key=field item=info}
<td valign="top" width="{$widths[$field]}" height="25px">
	<a href="#" class="default_link" onclick="ajax_rss.sortData('{$table}', '{$parent_id}', '{$field}')">
		{$info.label}
	</a>
</td>
{/foreach}
.
.
.
{foreach from=$row key=field item=value}
	{if $field neq "id"}
		{capture assign="el_id"}{$table}-{$id}-{$field}{/capture}
		<td width="{$widths[$field]}" align="left" height="25px">
		<div id="{$el_id}" onclick="ajax_rss.edit('{$el_id}', '{$value}')">
			{$value}
		</div>
		</td>
	{/if}
{/foreach}
.
.
.			
<td width="25">
	{capture assign="dragger_id"}{$table}-{$id}-dragger{/capture}
	<img id="{$dragger_id}" src="images/drag.png">&nbsp;</div>
	<script type="text/javascript">
		var dragger_id = '{$dragger_id}';
		{literal}
		new Draggable(dragger_id, {revert:true})
		{/literal}
	</script>
</td>
.
.
.
<td width="25">
	{capture assign="target_id"}{$table}-{$id}-target{/capture}
	<img id="{$target_id}" src="images/drop.png">&nbsp;</div>
	<script type="text/javascript">
		var drop_id = '{$target_id}';
		{literal}
		Droppables.add(drop_id, {hoverclass:'hoverRow', onDrop:function(element, dropon){dropHandler(element, dropon)}});
		{/literal}
	</script>
</td>
.
.
.

Interesting parts here are calls to sortData() to sort the feeds, edit() to do the basic text to dropdown and back to text routine. And finally at the bottom we see the dragger we use to drop in order to add new feeds to our subscription. The last block is unused, it was used to be the target for feed categories but this logic was abandoned in preference of the text->dropdown->text routine. It should have been removed but I forgot to remove it from the source before I uploaded it.

I realize that I’ve only brushed the surface of the code in this application. However if you are genuinely interested and have questions regarding something in the code, which by the way has room for a lot of improvements, then just comment on this post and I will do my best to explain. I hope this series was helpful for someone starting out in the world of desktop apps on the web.

Related Posts

Tags: , , ,

Posts linking to this article:

[web] 連結分享
又delay 了...最近太忙,一下子積了太多連結。 不過現在真的發現我的分類能力很差...orz. 所以今年重新調整分類了,網路上的服務或資訊歸類到internet 。還是大家能不能給我一些分類的建議? 有空的話(有嗎?) 要寫一個自己用的書籤整理工具. ...

[web] 連結分享
php. the new way to write ajax applications with php. 繼承phpajax 這個類別後,你就可以很方便地產生以ajax 操作的html 元素。 the completely unofficial xdebug.ini. 最完整的xdebug 可在php.ini 設定的項目及說明。 ...

ajax feedreader with zend framework and smarty
prodevtips have a good series on creating an ajax (asynchronous javascript and xml ) feedreader in three parts. the tutorial uses zend framework, smarty, html_ajax , prototype and scriptaculous. scriptaculous and prototype are used for ...

create an ajax feedreader with zend framework and smarty
prodevtips have a good series on creating an ajax feedreader in three parts. the tutorial uses zend framework, smarty, html_ajax , prototype and scriptaculous. scriptaculous and prototype are used for windowing and drag&drop, ...

ProDevTips.com: Ajax, ZF and Smarty feed reader: part 3
ProDevTips.com has posted the thier part of their series looking at building a feed reader with Ajax, the Zend Framework and Smarty (see parts one and two here). This time we will take a look at the feed list window and the manage ...

Subscribe with Google Reader