symfony 的 Ajax 助手令程序界面美化成为小菜一碟。 本教程将向你一步步展示如何在片刻间创建一个 Ajax 支持的 symfony 应用。
如果懒得不能忍受阅读苦楚的家伙们,建议你们观看完全展示如下内容的 在线视频。
电子商务应用程序中向购物车添加物品并不完全符合“添加到购物车”的提法,因为这个过程需要点击 “添加到购物车” 按钮,浏览一个新的页面(购物车),然后返回商店或者结账。
Ajax 启用拖放(drag-and-drop)交互、给与即时的视觉反馈而不需要离开商店,令程序通过更接近购物车的提法。
该教程的目标程序是开发 symfony 版本的Rails上由script.aculo.us发布的购物车演示。prototype javascript 架构(framework)(已捆绑在 symfony 内)和一些 script.aculo.us javascript 是 javascript 助手的核心。
首先建立一个 sfdemo 项目,一个 app 应用和一个 cart 模块:
$ cd /home/steve
$ mkdir sfdemo
$ cd sfdemo
$ symfony init-project sfdemo
$ symfony init-app app
$ symfony init-module app cart
配置网站服务另这个新应用得以被访问(如网站服务器安装所述,使用虚拟主机或者别名)这里假设模块可以通过 localhost 来访问:
http://localhost/cart/
在向你道恭喜(Congratulations)。
你的 app 应用必须可以访问 symfony javascript 类库。 如果你的 app 不能工作,从浏览器检查是否可以访问类库(比如测试 http://localhost/sf/js/prototype.js)。如果不能访问,你可以用 3 种不同的方法解决这个问题:
以 别名(Alias) 配置 Apache:
Alias /sf /$data_dir/symfony/web/sf
在 web 目录建立 sf 链接(symbolic link):
$ cd /home/steve/sfdemo/web
$ ln -sf /$data_dir/symfony/web/sf sf
将 javascript 文件拷贝到 web/js 目录:
$ cd /home/steve/sfdemo/web
$ mkdir -p sf/js
$ cp /$data_dir/symfony/web/sf/js/*.js sf/js/
首先,你需要建立一个代购物品列表。 为了简化这个之,该列表可以通过 cart 动作类的 getProducts() 方法访问。 购物车是 sfUser 对象的一个具有属性 参数容器(parameter holder) 的参数。 将 sfdemo/app/modules/cart/actions/actions.class.php 改为:
[php]
class cartActions extends sfActions
{
public function executeIndex()
{
$this->getUser()->setAttribute('cart', array());
$this->products = $this->getProducts();
}
private function getProducts()
{
return array('iPod black', 'iMac', 'iMac RC', 'iPod');
}
}
cart 模块的主业面将包含一个物品列表,和一个接受拖放的区域。 这个区域便是购物车。 打开 sfdemo/app/modules/cart/templates/indexSuccess.php 模板并写入:
[php]
<h1>symfony Apple store demo</h1>
<div id="shopping_cart">
<h2>Products:</h2>
<div id="product_list">
<?php foreach($products as $id => $title): ?>
<?php echo image_tag('product'.$id, array(
'id' => 'product_'.$id,
'class' => 'products'
)) ?>
<?php endforeach ?>
</div>
<h2>Cart:</h2>
<div id="cart" class="cart">
</div>
</div>
你可以看到产品以图片形式显示。 使用 这个压缩文件中的图片,将它们放在 sfdemo/web/images/ 目录中, 另外,部分样式已经帮你设置好, 所以建议你将 这个样式表 放在 sfdemo/web/css/ 目录中并在 sfdemo/app/modules/cart/config/ 目录中添加 view.yml 文件,写入如下内容:
all:
stylesheets: [cart]
现在访问下面的地址查看购物车准备设置:
http://localhost/cart/
购物车中的内容将随你的物品拖放动作而改变。 这意味着模板中 cart 内容必须在独立文件中。使用 include_partial() 助手来实现之。 购物车中的物品将被储存在样式为 float:left 的一个个div中,因此在该容器之后需要一个空白的 div。这就需要我们对 indexSuccess.php 模板作如下修改:
[php]
<h2>Cart:</h2>
<div id="cart" class="cart">
<div id="items">
<?php include_partial('cart') ?>
</div>
<div style="clear:both"></div>
</div>
</div>
include_partial() 助手将在 sfdemo/app/modules/cart/templates/ 目录中查找并引用 _cart.php 文件。 以如下内容创建这个文件:
[php]
<?php foreach($sf_user->getAttribute('cart') as $product_id => $quantity): ?>
<div>
<?php for($i = 1; $i <= $quantity; $i++): ?>
<?php echo image_tag('product'.$product_id, array(
'class' => 'cart-items',
'id' => 'item_'.$product_id.'_'.$i,
'style' => 'position:relative'
)) ?>
<?php endfor ?>
(<?php echo $quantity ?> <?php echo $products[$product_id] ?>)
</div>
<?php endforeach ?>
<?php if (!$sf_user->getAttribute('cart')): ?>
nothing yet in your shopping cart.
<?php endif ?>
如果购物车中有物品,则将以图片形式出现,有多少个出现多少次,其数量显示在每种产品之后。
现在重新查看购物车:
http://localhost/cart/
好了,没有太多的变化,仍旧空空荡荡...... 是添加 Ajax 的时候了。
编辑 indexSuccess.php 模板,引入 javascript 助手:
[php]
<?php use_helper('Javascript') ?>
添加对 draggable_element javascript 助手的调用,另图片可以被拖拽:
[php]
<?php foreach($products as $id => $title): ?>
<?php echo image_tag('product'.$id, array(
'id' => 'product_'.$id,
'class' => 'products'
)) ?>
<?php echo draggable_element('product_'.$id, array('revert' => true)) ?>
<?php endforeach ?>
这为产品列表中的每个图片添加了 '可拖拽(draggable)' 行为。其中 revert(返回) 选项使图片在被拖拽释放时回到原来的位置(除非被其它元素接收)。
现在,将 cart 定义为接收元素。 你只需要确定模板中那个部分在事件发生执行需要更新,需要调用那个动作来提供更新内容,以及那个类型的可拖拽元素可以被接收。使用drop_receiving_elements javascript 助手实现之:
[php]
<?php echo drop_receiving_element('cart', array(
'update' => 'items',
'url' => 'cart/add',
'accept' => 'products',
)) ?>
现在再试一下,将物品移动到购物车:可以了。 当一个可拖拽物品被拖动至接收元素,一个 XMLHTTPRequest 被发送至 add 动作,其结果被显示在 items div 中。问题是 cart 模块的 add动作还没有被定义。
在 sfdemo/app/modules/cart/actions/actions.class.php 中添加 add 动作:
[php]
public function executeAdd()
{
$tmp = split('_', $this->getRequestParameter('id', ''));
$product_id = $tmp[1];
$cart = $this->getUser()->getAttribute('cart');
if (!isset($cart[$product_id]))
{
$cart[$product_id] = 1;
}
else
{
++$cart[$product_id];
}
$this->getUser()->setAttribute('cart', $cart);
$this->products = $this->getProducts();
}
该动作接收 javascript 传递来的参数(被拖放物品的 id)并将其添加至购物车。
add 的结果将显示在仅仅是引用 _cart.php 的 addSuccess.php 模板,但此次需要传递 products 参数:
[php]
<?php include_partial('cart', array('products' => $products)) ?>
该模板不能使用全局布局(global layout),因此编辑 sfdemo/app/modules/cart/config/ 目录中的 view.yml 文件,写入:
addSuccess:
has_layout: off
all:
has_layout: on
stylesheets: [cart]
试一下:你现在可以将物品添加到购物车了。
你现在可以止步了,但是这个购物车还有个大问题:在购物车被更新过程中,界面上没有变化,用户可能会感到茫然。 这是请求不同步的一个普遍问题:需要添加一个指示(indicator)区域,用以标示请求正在处理。 另外,也没有告知用户什么时候物品被购物车接收到,因此还需要设置 cart div 的 hover 样式。
做到这一点需要编辑 indexSuccess.php 模板并写入:
[php]
<div style="height:20px">
<p id="indicator" style="display:none">
<?php echo image_tag('indicator.gif') ?> updating cart...
</p>
</div>
将 'indicator.gif' 图片文件 存放在 sfdemo/web/images/ 目录。
现在修改 drop_receiving_element() javascript 助手的调用代码,在请求过程中显示指示器并声明 hover 样式:
[php]
<?php echo drop_receiving_element('cart', array(
'update' => 'items',
'url' => 'cart/add',
'accept' => 'products',
'script' => 'true',
'hoverclass' => 'cart-active',
'loading' => "Element.show('indicator')",
'complete' => "Element.hide('indicator')"
)) ?>
该演示的全部代码可以从这里下载 并且可以在线访问。 你将注意到些许的不同(增加了一个回收箱) 但和谐内容没有变化。
javascript 助手的文档完善之前,你可以从 script.aculo.us documentation获得更多信息。