424 123
发新话题
打印

[symfony]高度交互性的应用 - 集成Ajax

[symfony]高度交互性的应用 - 集成Ajax

[转载请注明:PHP开发资源网(htt://bbs.phpres.com/)]   

     在Web2.0的应用中, 经常遇到与客户端交互作用, 复杂的视觉效果及异步通讯之类的问题. 要实现这些功能, 都要用到javascript, 但是用javascript编程是很繁琐且难以测试的. 幸运的是, 通过在模板中包括一组完整的辅助函数, symfony可以自动生成许多javascript的常用功能. 许多客户端功能甚至不需要一行javascript语句就可以开发出来, 开发人员只需要考虑他们想要达到的效果, 复杂的语法和兼容性问题都由symfony去处理.

本贴将通过介绍以下内容告诉你如何用symfony提供的工具来完成客户端代码:

>在symfony模板中,基本的javascript helper输出’标记 以更新DOM(文档对象模型)元素或触发一个链接的代码.
>symfony中集成了一个javascript库Prototype, 它为javascript核心增加了新的函数和方法, 以便加速客户端代码的开发.
>ajax helper让用户可以通过点击一个链接, 提交一个表单或修改一个表单元素来更新一个页面的某些部分.
>通过运用这些辅助函数的选项,可以提供回调函数一类的更灵活的能力.
>symfony中还集成了另一个javascript库Script.aculo.us, 使用它可以增强动态视觉效果以增强界面和用户体验.
>JSON(Javascript对象标识)是一种用语在客户端和服务器端通信的标准.
>与前面提到的元素结合在一起的复杂的客户端交互都可以在symfony应用中实现.只要写一行PHP语句去调用一个symfony 辅助函数, 就可以实现自动完成, 拖拽, 可排序列表及可编辑文本等功能.

TOP

基本的javascript 辅助函数

以前由于javascript 存在浏览器兼容性问题, 所以在专业的web应用中很少用到它. 但是现在, 兼容性问题已经基本解决, 利用许多质量稳定的javascript库, 可以无需写大量代码和费时测试 就可以写出复杂的javascript交互代码. ajax就是最普遍的应用, 本章后面的”ajax 辅助函数”就会介绍它.

你可能会奇怪本章中只有极少的javascript代码. 这是因为symfony有自己的编写客户端代码的方法: 通过将javascript行为封装和抽象进helper中, 在你的模板中可以完全没有javascript代码. 开发人员只需要用一行PHP语句就可以为页面中的一个元素增加一种行为. 但是被调用的helper会输出javascript代码, 研究所产生的响应可以揭示封装的复杂性, 包括浏览器一致性, 复杂的限制条件和扩展性等等, 因此javascript代码的总量是非常重要的. 本章将告诉你怎样不使用javascript语句达到用javascript才能产生的效果.

这里介绍的所有辅助函数都在模板中, 只要你事先声明使用”Javascript辅助函数"组即可.
[php]
<?php use_helper('Javascript') ?>

你马上还会看到, 有些辅助函数输出HTML代码, 另一些输出javascript代码.

TOP

模板中的javascript

在XHTML中, javascript 代码块必须包含在CDATA声明中. 但是如果在页面中需要写许多javascript代码块时, 这就变得非常繁琐. 所以symfony提供了’javascript tag()’辅助函数, 用于将一个字符串转化为一个复合XHTML规范的’

但是javascript的主要应用不时在代码块中, 而是在会触发某个具体脚本的超链接中.表11-2显示了’link_to_function()’helper的使用.

表11-2通过一个包含’link_to_function()’辅助函数的链接触发javascript.
[php]
<?php echo link_to_function('Click me!', "alert('foobar')") ?>
相当于 <a href="#" onClick="alert('foobar'); return none;">Click me!</a>

在‘link_to()’辅助函数中加入第三个参数, 可以为标记加入内容.

NOTE Just as the link_to() helper has a button_to() brother, you can trigger JavaScript from a button (<input type="button">) by calling the button_to_function() helper. And if you prefer a clickable image, just call link_to_function(image_tag('myimage'), "alert('foobar')").注意, 与’link_to()’相似的还有 ‘button_to()’, 你可以调用'button_to_function()'辅助函数 来通过一个按钮(<input type="button">) 触发javascript. 如果你还想要一个可点击的图片, 只要调用’link_to_function(image_tag(‘myimage’), “alert(‘foobar’)”)’即可.

TOP

更新一个DOM元素

更新页面中的一个元素是动态界面经常要解决的问题. 表13-3是为此而经常编写的代码.

表13-3 用javascript更新一个元素
[php]
<div id="indicator">Data processing beginning</div>
<?php echo javascript_tag("
  document.getElementById("indicator").innerHTML =
    "<strong>Data processing complete</strong>";
") ?>

symfony为此提供了一个名为’update_element_function()’的辅助函数, 用于生成javascript代码而不是html代码. 表11-4是一个示例.

表11-4 在javascript中用’update_element_function()’辅助函数 更新一个元素
[php]
<div id="indicator">Data processing beginning</div>
<?php echo javascript_tag(
  update_element_function('indicator', array(
    'content'  => "<strong>Data processing complete</strong>",
  ))
) ?>

你也许在想: 这条辅助函数语句和真正的javascript代码一样长, 那使用它有什么特别的好处呢? 好处在于它的可读性. 例如, 如果你想根据某种条件在一个元素之前或之后插入内容,或者不是更新元素内容而是删除一个元素, 甚至什么也不做时, javascript代码将会变得非常混乱, 但是’update_element_function()’却可以象表11-5那样清晰易读.

Listing 11-5 - Options of the update_element_function() Helper

表11-5 update_element_function辅助函数的选项
[php]
// 在’indicator’元素之后插入内容
update_element_function('indicator', array(
  'position' => 'after',
  'content'  => "<strong>Data processing complete</strong>",
));

// 如果$condition成立, 则删除’indicator’之前的元素
update_element_function('indicator', array(
  'action'   => $condition ? 'remove' : 'empty',
  'position' => 'before',
))

可以看出, 这个辅助函数让你的模板比任何javascript代码都要容易理解, 而且你只要使用一种语法就可以处理相似的行为. 这也是为什么这个辅助函数名字这样长的原因: 无需额外的注释, 就可以充分地自己解释自己的用途.

TOP

优雅地降级

代码中用<noscript> 指明的某些html代码, 仅在不支持javascript的浏览器中显示.symfony用一个辅助函数来提供这种帮助, 利用它可以让某些代码仅在支持javascript的浏览器中执行. 表11-6中, 显示了用’if_javascript()’和’end_if_javascript()’实现这种降级的用法:

表11-6 用’if_javascript()’辅助函数优雅地降级.
[php]
<?php if_javascript(); ?>
  <p>You have JavaScript enabled.</p>
<?php end_if_javascript(); ?>

<noscript>
  <p>You don't have JavaScript enabled.</p>
</noscript>

NOTE You don't need to include echo when calling the if_javascript() and end_if_javascript() helpers. 注意, 调用’if_javascript()’和’end_if_javascript()’时, 不需要用’echo’.

TOP

Prototype

Prototype是一个优秀的javascript库, 它扩展了客户端脚本的能力, 增加了开发者希望拥有的功能,并且提供了操作DOM的新机制. 该项目的网址是 http://prototypejs.org/.

symfony框架中绑定了Prototype文件, 在每个项目的web/sf/prototype目录中可以访问到它的文件. 这样你可以在你的action中增加下列代码就可以使用Prototype:
[php]
$prototypeDir = sfConfig::get('sf_prototype_web_dir');
$this->getResponse()->addJavascript($prototypeDir.'/js/prototype');

或者在view.yml文件中加入::
all:
  javascripts: [%SF_PROTOTYPE_WEB_DIR%/js/prototype]

NOTE Since the symfony Ajax helpers, described in the next section, rely on Prototype, the Prototype library is already included automatically as soon as you use one of them. It means that you won't need to manually add the Prototype JavaScript to your response if your template calls a _remote helper.注意,下一节将要介绍的symfony Ajax辅助函数需要用到Prototype, 只要你用到Prototype库, 它就自动被包括进去. 也就是说, 如果你的模板调用一个_remote 辅助函数, 你不需要手工向你的响应里增加Prototype Javascript.

一旦你载入了Prototype库, 你就可以利用它加入到javascript核心中的新功能. 本书的主要目的不是描述这些功能, 你可以很容易在互联网上找到所需的文档, 以下是几个有关的网站:
Particletree: http://particletree.com/features/quick-guide-to-prototype/
Sergio Pereira: http://www.sergiopereira.com/articles/prototype.js.html
Script.aculo.us: http://wiki.script.aculo.us/scriptaculous/show/Prototype

Prototype加入到javascript 中的函数之一是’$()’函数. 简单地说, 这个函数可以看成是’document.getElementById()’函数的缩写, 但它有更强的功能.表11-7给出了一个应用的例子.

表11-7 用’$()’函数根据DOM的元素ID值取得元素值.
[php]
node = $('elementID');

// 相当于
node = document.getElementById('elementID');

// 也可以一次检索多个元素
// 在本例中返回值是一个由DOM元素组成的数组.
nodes = $('firstDiv', 'secondDiv');

Prototype还提供了javascript真正缺乏的一个功能, 就是取得以类class为参数的DOM元素的数组.
[php]
nodes = document.getElementByClassName('myclass');

当然你将不太会用到这个函数, 因为Prototype提供了一个更强大的’$$()’函数.这个函数根据CSS selector返回一个由DOM元素组成的数组.所以前面的调用可以写成如下形式:
[php]
nodes = $$('.myclass');

凭借CSS selector的力量, 你可以根据class, ID, 父子关系和前后关系去解析DOM, 这比通过XPath表达式去解析更为简单. 你甚至可以用一个混合了所有这些selector的复杂selector去访问对应的元素.
[php]
nodes = $$('body div#main ul li.last img > span.legend');

Prototype 提供的增强语法功能的最后一个例子是数组迭代. 它为javascript定义匿名函数和closure(译者注: 如果在一个javascript函数体中又出现function定义时, 称为定义了一个closure)功能提供了和PHP同样的简化形式. 如果你要编写 javascript代码, 会大量用到这个功能.
[php]
var vegetables = ['Carrots', 'Lettuce', 'Garlic'];
vegetables.each(function(food) { alert('I love ' + food); });

因为用Prototype编写javascript更为有趣, 并且因为Prototype也是symfony的一个组成部分, 所以你需要花些时间去研究它的文档.

TOP

Ajax 辅助函数

如果你想在服务器端用php脚本去更新页面中的元素内容, 而不想用javascript去更新(如表11-5所示), 那该怎么做呢? 这样你可以根据一个服务器的响应来改变页面的某个部分. ‘remote_function()’辅助函数就可以完成这个任务, 如表11-8所示:

表11-8 应用’remote_function()’ 辅助函数
[php]
<div id="myzone"></div>
<?php echo javascript_tag(
  remote_function(array(
    'update'  => 'myzone',
    'url'     => 'mymodule/myaction',
  ))
) ?>

NOTE The url parameter can contain either an internal URI (module/action?key1=value1&...) or a routing rule name, just as in a regular url_for().注意: ‘url’参数既可以包含一个内部URI(‘module/action?key1=value1&…’),也可以包含一个路由规则名, 就象在规则’url_for()’中那样.

当你调用这个函数时, 这段脚本就会根据响应请求或’mymodule/myaction’动作的请求去更新id为myzone的元素.这种交互就是Ajax, 也正是高度可交互的web应用的核心. (http://en.wikipedia.org/wiki/AJAX) 描述了Ajax的特点::

Ajax通过让页面只和服务器交换很少量的数据, 因而每当用户改变页面时, 不需要重新导入整个网页,而使得页面的响应更好.也就是说增强了网页的交互性,速度和可用性.

Ajax依赖于javascript的一个对象’XMLHttpRequest’, 利用这个对象, 你可以从一个服务器请求更新它并且重用它去操纵页面的剩余部分. 这个对象是相当底层的一个对象, 并且不同的浏览器用不同的方法去处理它. 所幸的是, Prototype封装了所有Ajax需要的代码并提供了一个简单的Ajax对象, symfony就借助于这个对象. 这也是为什么当你在一个模板中使用Ajax 辅助函数时, Prototype就会自动装入的原因.

CAUTION The Ajax helpers won't work if the URL of the remote action doesn't belong to the same domain as the current page. This restriction exists for security reasons, and relies on browsers limitations that cannot be bypassed.注意 如果远程动作的URL不属于当前页所在的域, Ajax 辅助函数将不工作. 这是出于安全考虑, 并且有些浏览器也限制远程动作通过.

一个Ajax交互由三个部分组成:一个调用者(链接,按钮,表单,时钟或用户可以操纵以启动动作的任何控件), 一个服务器动作和页面中的一个显示动作响应的区域. 如果远程动作返回的数据还要被客户端的javascript函数处理, 你可以创建更复杂的交互.symfony提供多个名字中包含remote的辅助函数以便于你在模板中插入Ajax交互. 他们还使用共同的语法, 可以将所以的Ajax参数放进一个数组中. 注意, Ajax 辅助函数输出的是HTML, 而不是Javascript.

SIDEBAR How about Ajax actions?Ajax动作如何工作?

Actions called as remote functions are regular actions. They follow routing, can determine the view to render the response with their return, pass variables to the templates, and alter the model just like other actions.作为远程函数被调用的动作就是一个通常的动作. 就象其它动作一样, 它们可以路由, 可以确定视图以提交带有’return’参数的响应, 向模板传递参数以及改变模型等.

However, when called through Ajax, actions return true to the following call但是, 当通过Ajax调用动作时, 动作将返回true给下面的调用:
[php]
$isAjax = $this->isXmlHttpRequest();

Symfony knows that an action is in an Ajax context and can adapt the response processing accordingly. Therefore, by default, Ajax actions don't include the web debug toolbar in the development environment. Also, they skip the decoration process (their template is not included in a layout by default). If you want an Ajax view to be decorated, you need to specify explicitly has_layout: true for this view in the module view.yml file.Symfony 知道动作所在的上下文, 因而能够对响应做相应的处理. 因此,在默认情况下,在开发环境中的 Ajax 动作不包含web调试工具栏, 而且也会忽略装饰过程(默认情况下, 展示框架中没有他们的模板). 如果你想装饰Ajax的界面, 你需要在模块的view.yml文件中对这个界面明确指明has_layout的值为true.

One more thing: Because responsiveness is crucial in Ajax interactions, if the response is not too complex, it might be a good idea to avoid creating a view and instead return the response directly from the action. So you can use the renderText() method in the action to skip the template and boost Ajax requests.还有一点: 因为在Ajax的交互中响应性是关键因素, 所以如果响应不是非常复杂, 最好不要创建界面, 而是直接从动作返回响应. 这样你可以在动作中用renderText()方法, 直接跳过模板而加速Ajax请求.

TOP

Ajax 链接
在Web 2.0应用中, Ajax 链接是Ajax交互中可能共享的主要部分. Link_to_remote()输出一个链接, 该链接调用一个远程函数. 除了第二个参数是一个由Ajax选项组成的数组以外, 其他语法类似于link_to(). 表11-9是一个示例.
表11-9 Ajax 链接函数link_to_remote() 辅助函数
[php]
<div id="feedback"></div>
<?php echo link_to_remote('Delete this post', array(
    'update' => 'feedback',
    'url'    => 'post/delete?id='.$post->getId(),
)) ?>
在这个例子中, 点击”Delete this post” 链接就会在后台发出一个对post/delete的调用. 从服务器返回的响应将出现在id为feedback的元素中. 图11-1显示了运行的过程.
图11-1 用链接触发一个远程更新.



对于链接, 你可以用图片代替字符串, 用规则名代替内部的模块/动作URL, 还可以将选项加进标记的第三个参数中. 如表11-10所示.
表11-10 link_to_remote() 辅助函数的选项
[php]
<div id="emails"></div>
<?php echo link_to_remote(image_tag('refresh'), array(
    'update' => 'emails',
    'url'    => [email=]'@list_emails'[/email],
), array(
    'class'  => 'ajax_link',
)) ?>

TOP

Ajax驱动的表单
Web表单一般要调用另一个动作, 但这会导致整个页面被刷新. 对表单来说,类似于 link_to_function() 的功能可以让表单的提交后, 仅用服务器的响应去更新页面中的一个元素, form_remote_tag()辅助函数就是完成这个任务, 表11-11展示了它的语法:
表11-11 使用form_remote_tag() 辅助函数的Ajax 表单
[php]
<div id="item_list"></div>
<?php echo form_remote_tag(array(
    'update'   => 'item_list',
    'url'      => 'item/add',
)) ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <?php echo submit_tag('Add') ?>
</form>
例子中, 类似form_tag() 辅助函数, form_remote_tag()打开一个
. 提交这个表单会在后台向item/add动作发出一个POST请求, 请求的参数就是item域的内容. 响应会替换item_list元素的内容(如图11-2所示). 最后用正规的
标记关闭Ajax 表单.
图11-2 触发一个带有表单的远程更新.




CAUTION Ajax forms can't be multipart. This is a limitation of the XMLHttpRequest object. This means you can't handle file uploads via an Ajax form. There are workarounds though--for instance, using a hidden iframe instead of an XMLHttpRequest (see an implementation at http://www.air4web.com/files/upload/).注意 由于XMLHttpRequest对象的限制, Ajax表单不可以分为多个部分.这意味着你不能通过Ajax表单上传文件. 不过可以用其他方法解决这个问题—比如, 用隐式的iframe代替XMLHttpRequest(参看http://www.air4web.com/files/upload/给出的一个实现)
如果你想让一个表但同时工作在页模式和Ajax模式, 最好的方法是定义一个正规的表单, 然后除了提供通用的提交按钮, 再增加一个按钮(<input type=”button”/>)用于以Ajax方式提交表单. symfony将这个按钮称为submit_to_remote().这有助于你建立一个与以前的方法兼容的Ajax交互表单.
表11-12 具有正常提交和Ajax提交的表单
[php]
<div id="item_list"></div>
<?php echo form_tag([email=]'@item_add_regular'[/email]) ?>
  <label for="item">Item:</label>
  <?php echo input_tag('item') ?>
  <?php if_javascript(); ?>
    <?php echo submit_to_remote('ajax_submit', 'Add in Ajax', array(
        'update'   => 'item_list',
        'url'      => [email=]'@item_add'[/email],
    )) ?>
  <?php end_if_javascript(); ?>
  <noscript>
    <?php echo submit_tag('Add') ?>
  </noscript>
</form>
混合了正常提交和Ajax提交标记的例子还有用于编辑文章的表单. 它可以提供一个用Ajax提交实现的预览按钮和一个用普通提交实现的发布按钮.
NOTE When the user presses the Enter key, the form is submitted using the action defined in the main <form> tag--in this example, a regular action.注意 当用户按下回车键时, 表单会用定义在主要的
标记中的动作去提交,在 本例中, 就是正常提交动作. 现代表单不仅仅在提交时才作出反应, 在用户改变某个域的值时也会有反应.在symfony中, 你可以用observe_field()辅助函数来实现. 表11-13给出了一个使用这个辅助函数的例子, 在item域中每输入一个字符, 都会触发一个Ajax调用去刷新页面中的item_suggestion. 表11-13 当值变化时, 由observe_field()调用一个远程函数. [php] Item:
'item_suggestion', 'url' => [email=]'@item_being_typed'[/email], )) ?>
每当用户改变了域item(成为”发现域”)的值时, 即使没有提交表单, 也会调用@item_being_typed规则中的模块/动作. 这个动作将从value请求参数中获得当前的item值. 如果你想传递非发现域的内容, 你可以用javascript表达式的with参数来指明. 例如, 如果你想让动作得到param参数, 可以将observe_field()写成如下形式(表11-14):
表11-14 用with 选项将你自己的参数传递给远程动作
[php]
<?php echo observe_field('item', array(
    'update'   => 'item_suggestion',
    'url'      => [email=]'@item_being_typed'[/email],
    'with'     => "'param=' + value",
)) ?>
注意, 这个辅助函数并不输出一个HTML元素, 而是输出作为一个参数的元素的行为.在本章中你会看到更多指定行为的javascript 辅助函数的例子.
如果你想发现一个表单中的所有域, 你可以使用observe_form() 辅助函数, 每当表单中的任意一个域发生变化时, 它都会调用一个远程动作.

TOP

Periodically Calling Remote Functions

Last but not least, the periodically_call_remote() helper is an Ajax interaction triggered every few seconds. It is not attached to an HTML control, but runs transparently in the background, as a behavior of the whole page. This can be of great use to track the position of the mouse, autosave the content of a large text area, and so on. Listing 11-15 shows an example of using this helper.

表11-15 用periodically_call_remote()周期性调用一个远程函数.
[php]
<div id="notification"></div>
<?php echo periodically_call_remote(array(
    'frequency' => 60,
    'update'    => 'notification',
    'url'       => '@watch',
    'with'      => "'param=' + $('mycontent').value",
)) ?>

如果你不想指明两次调用远程函数之间的间隔秒数(frequency), 默认值是10秒. 注意, with参数是javascript计算出来的, 所以你可以用Prototype函数, 比如$().
远程调用参数

除了update 和url参数以外, 前面描述的所有Ajax 辅助函数还可以带另外一些参数.由Ajax参数组成的数组可以调整和改变远程调用的行为和对它们的响应的处理.

TOP

 424 123
发新话题