您的位置:首页 > Web前端 > JavaScript

Smarty 模板为 Ajax 请求生成 JSON、XML 和 HTML 响应

2011-06-20 21:33 309 查看
在本系列文章的第一篇中,您将了解如何使用 Smarty 模板为 Ajax 请求生成 JSON、XML 和 HTML 响应。这些技术允许您在开发 PHP 代码时关注于应用逻辑,而这些应用逻辑是与 Ajax 客户端和服务器之间通信所使用的数据格式分离的。

您还将了解如何创建两个版本的表单,其中一个提供输入域让用户输入数据,另一个使用隐藏域并以不可编辑形式显示数据。通过单击一个按钮,用户能够切换两个版本的表单,使用 Ajax 向服务器提交数据并获取用于更新页面的 HTML 内容。此外,这个表单在 Web 浏览器禁用 JavaScript 时仍然能够使用。

本文的最后一部分包含配置 Smarty 和示例应用的说明。如果您的服务器或工作站启用了 SELinux,这个过程会有些复杂。这些信息对于需要修改公共文件的 Web 应用是很有用的,如内容管理系统和允许用户上传内容的网站。不管您使用的是 Smarty、流行 CMS 或定制系统,在您的代码尝试修改 Web 文件时,您都会遇到相同的与 SELinux 有关的配置问题。本文阐述了如何使用 Linux 的
restorecon
chcon
setsebool
命令解决这些问题。

使用 Smarty 生成 Ajax 响应

在本节中,我们将了解如何创建用来为 Ajax 请求生成响应的 Smarty 模板。您可以使用任何一种通用格式,如 JSON、XML 或 HTML。Smarty 语法主要是面向 HTML 设计的,这使它也非常适合于 XML。然而,使用 Smarty 创建 JSON 响应会有一些困难,因为模板构建的语法使用了
{
}
,这意味着如果 JSON 使用了这两个字符,您需要对它们进行转义。然而,您会发现可以修改 Smarty 的分隔符以避免这个语法冲突。您还将了解如何创建自定义修饰符和函数,并将它们注册到 Smarty 框架,这样您就能够在您的模板中使用它们了。

使用 Smarty 生成 XML 文档

让我们从一个简单的例子(如清单 1 所示)开始,它将生成一个可以被 Ajax 客户端使用的 XML 响应。首先,PHP 代码应该设置内容类型以及
no-cache
响应头,它可以保证 Web 浏览器不会缓存 Ajax 响应。考虑到您以前可能没有使用过 Smarty,我们在这里简要介绍一下 demo_xml.php 文件的作用:它通过
require
包含了 Smarty 类,创建了这个类的一个实例,设置了它的编译和调试标记,使用
assign()
创建两个名为
root_attr
elem_data
的变量,然后使用
display()
调用一个 Smarty 模板,这样就将生成了 XML 响应。

清单 1. demo_xml.php 示例

<?php
header("Content-Type: text/xml");
header("Cache-Control: no-cache");
header("Pragma: no-cache");

require 'libs/Smarty.class.php';

$smarty = new Smarty;

$smarty->compile_check = true;
$smarty->debugging = false;
$smarty->force_compile = 1;

$smarty->assign("root_attr", "< abc & def >");
$smarty->assign('elem_data', array("111", "222", "333"));

$smarty->display('demo_xml.tpl');

?>

demo_xml.tpl 模板(见清单 2)生成了一个
<root>
元素,它有一个属性,它的值是从 demo_xml.php 文件的变量
root_attr
查询的。字符
<
>
"
'
&
分别使用 Smarty 的
escape
修饰词替换成
<
>
"
'
&
。在
root
元素中,模板使用 Smarty 的
{section}
遍历
elem_data
数组的元素,这个数组是 demo_xml.php 文件中定义的第二个变量。demo_xml.tpl 会为数组中的每一个元素生成包含从数组查询到的值的 XML 元素。

清单 2. demo_xml.tpl 模板

<root attr="{$root_attr|escape}">
{section name="d" loop=$elem_data}
<elem>{$elem_data[d]|escape}</elem>
{/section}
</root>

清单 3 包含了由 demo_xml.php 文件和 demo_xml.tpl 模板生成的 XML 输出。

清单 3. XML 输出

<root attr="< abc & def >">
<elem>111</elem>
<elem>222</elem>
<elem>333</elem>
</root>

使用 Smarty 创建一个 JSON 响应

demo_json.php 文件(如清单 4 所示)设计了 no-cache 头,并且如清单 3 所示例子一样创建和配置了 Smarty 对象。此外,它还定义了两个名为
json_modifier()
json_function()
的函数,它们会调用
json_encode()
PHP 函数。这两个函数被注册到 Smarty 上,这样它们就可以在模板中使用了,您将在本节的后面看到如何使用。在这之后,demo_json.php 文件创建了一些以下类型的 Smarty 变量:字符串型、数字型、布尔型和数组。然后,PHP 例子执行 demo_json.tpl 模板生成 JSON 响应。

清单 4. demo_json.php 示例

<?php
header("Content-Type: application/json");
header("Cache-Control: no-cache");
header("Pragma: no-cache");

require 'libs/Smarty.class.php';

$smarty = new Smarty;

$smarty->compile_check = true;
$smarty->debugging = false;
$smarty->force_compile = 1;

function json_modifier($value) {
return json_encode($value);
}

function json_function($params, &$smarty) {
return json_encode($params);
}

$smarty->register_modifier('json', 'json_modifier');
$smarty->register_function('json', 'json_function');

$smarty->assign('str', "a/"b/"c");
$smarty->assign('num', 123);
$smarty->assign('bool', false);
$smarty->assign('arr', array(1,2,3));

$smarty->display('demo_json.tpl');

?>

除了注册插件(如修饰符或函数)到 Smarty,您也可以使用 Smarty 文档中描述的一些特别的命名规范 (见 参考资料)。然后您可以将您的代码放到 plug-ins 目录,这样您的 Smarty 修饰符和函数就可以在应用的任何 Web 页面中使用。

因为 JSON 和 Smarty 的语法中都使用了
{
}
,所以您必须在 Smarty 模板中使用
{ldelim}
{rdelim}
生成 JSON 响应中使用的
{
}
字符。您也可以将
{
}
放到
{literal}
{/literal}
之间。您将在另一个例子中看到,我们可以修改 Smarty 分隔符以避免这样的麻烦。

demo_json.tpl
模板(见清单 5)使用
json
修饰符对 demo_json.php 文件中的四个变量值进行编码。例如,对引号和其它特殊字符进行转义是很有用的,如字符串中的制表符和换行符。Smarty 将会在 demo_json.php 文件中模板每次使用
|json
时调用
json_modifier()
json
修饰符前面必须加上
@
字符,这样数组变量就可以传递到
json_modifier()
。如果没有使用
@
字符,修饰符函数会对数组的每一个元素进行调用。

从 demo_json.tpl 模板构建的
{json ... }
会被转换成 demo_json.php 文件的
json_function()
的一个调用。这个函数会从模板中获取一个名为
params
的数组属性,同时被传递到
json_encode()
,这个函数会返回 JSON 表示的 PHP 数组。

清单 5. demo_json.tpl 模板

{ldelim}
s: {$str|json},
n: {$num|json},
b: {$bool|json},
a: {$arr|@json},
o: {json os=$str on=$num ob=$bool oa=$arr},
z: {literal}{ x: 1, y: 2 }{/literal}
{rdelim}

清单 6 包含了由 demo_json.php 文件和 demo_json.tpl 模板生成的 JSON 输出。

清单 6. JSON 输出

{
s: "a/"b/"c",
n: 123,
b: false,
a: [1,2,3],
o: {"os":"a/"b/"c","on":123,"ob":false,"oa":[1,2,3]},
z: { x: 1, y: 2 }
}

要避免在 Smarty 模板中使用
{ldelim}
{rdelim}
,您可以修改如清单 7 所示的 Smarty 分隔符。

清单 7. 修改 Smarty 分隔符

$smarty->left_delimiter = '<%';
$smarty->right_delimiter = '%>';

清单 8 所示的模板使用 Smarty 构件中的
<%
%>
分隔符生成 JSON 响应。

清单 8. demo_json2.tpl 模板

{
s: <% $str|json %>,
n: <% $num|json %>,
b: <% $bool|json %>,
a: <% $arr|@json %>,
o: <% json os=$str on=$num ob=$bool oa=$arr %>,
z: {  x: 1, y: 2 }
}

使用 Smarty 创建一个 Ajax

本节的例子说明了如何使用 Smarty 生成使用 Ajax 获得的 HTML 内容。此外,这个 Web 页面也包含了一个 HTML 表单,这个表单的数据是使用 jQuery 框架以 Ajax 方式提交到服务器上的。如果 Web 浏览器禁用了 JavaScript,这个表单仍然会正确工作,Smarty 也仍然可用在服务器端生成内容。

使用 Smarty 处理 HTML 表单

demo_form.tpl 模板(见清单 9)包含了一个 HTML 表单,这个表单的域可能是可编辑的,也可能是不可编辑的,这取决于名为
edit_mode
的变量值。这个变量是在调用模板的 PHP 代码中设置的,您将会在本节的后面看到。
edit_mode
的值也会被存储在表单的一个隐藏域中。

清单 9. demo_form.tpl 模板的 HTML 表单

<form method="POST" name="demo_form">

<input type="hidden" name="edit_mode"
value="{if $edit_mode}true{else}false{/if}">

<table border="0" cellpadding="5" cellspacing="0">
...
</table>

</form>

清单 10 显示的是表单的第一个域,如果
edit_mode
true
,它就是一个输入框;如果
edit_mode
false
,它就是一个隐藏域。在后一种情况中,这个域的不可编辑值会被
{$smarty.post.demo_text|escape}
包含在输出中。当用户提交这个可编辑表单时,参数
demo_text
包含了用户的输入。当表单是不可编辑的,由于有一个隐藏域,这个参数仍然会出现。因此,不管表单是否可以编辑,我们都可以使用
$smarty.post.demo_text
获得这个 post 参数的值。

清单 10. demo_form.tpl 模板的文本框

<tr>
<td>Demo Text:</td>
<td>
{if $edit_mode}
<input type="text" name="demo_text" size="20"
value="{$smarty.post.demo_text|escape}">
{else}
<input type="hidden" name="demo_text"
value="{$smarty.post.demo_text|escape}">
{$smarty.post.demo_text|escape}
{/if}
</td>
</tr>

这个表单的下一个输入域是一个复选框(见清单 11)。在可编辑的表单中,元素
input
只有在出现参数
demo_checkbox
时才会有一个属性
checked
。类似地,不可编辑的表单只有在所提交表单数据中包含了名为
demo_checkbox
的 post 参数时才会包含这个隐藏表单元素。

清单 11. demo_form.tpl 模板的复选框

<tr>
<td>Demo Checkbox:</td>
<td>
{if $edit_mode}
<input type="checkbox" name="demo_checkbox"
{if $smarty.post.demo_checkbox}checked{/if}>
{else}
{if $smarty.post.demo_checkbox}
<input type="hidden" name="demo_checkbox" value="On">
{/if}
{if $smarty.post.demo_checkbox}On{else}Off{/if}
{/if}
</td>
</tr>

表单的表格的下面一行包含了三个单选按钮(见清单 12)。模板代码通过比较参数
demo_radio
与每个按钮的值决定应该选择哪个单选按钮。不可编辑的表单使用一个隐藏输入域存储参数值并使用
$smarty.post.demo_radio
向用户显示这个值。

清单 12. demo_form.tpl 模板的单选按钮

<tr>
<td>Demo Radio:</td>
<td>
{if $edit_mode}
<input type="radio" name="demo_radio" value="1"
{if $smarty.post.demo_radio == '1'}checked{/if}>1
<input type="radio" name="demo_radio" value="2"
{if $smarty.post.demo_radio == '2'}checked{/if}>2
<input type="radio" name="demo_radio" value="3"
{if $smarty.post.demo_radio == '3'}checked{/if}>3
{else}
<input type="hidden" name="demo_radio"
value="{$smarty.post.demo_radio|escape}">
{$smarty.post.demo_radio|escape}
{/if}
</td>
</tr>

一个表单列表的选项是在一个循环的
{section}
中生成的,如清单 13 所示。当前循环的次数会被赋值给一个名为
demo_counter
的模板变量,它会与选项元素的值进行比较以便确定这个选项是否被选中。

清单 13. demo_form.tpl 模板的列表

<tr>
<td>Demo Select:</td>
<td>
{if $edit_mode}
<select name="demo_select" size="1">
{section name="demo_section" start=10 loop=100 step="10"}
{assign var="demo_counter"
value=$smarty.section.demo_section.index}
<option {if $smarty.post.demo_select == $demo_counter}
selected{/if} value="{$demo_counter}">
{$demo_counter}
</option>
{/section}
</select>
{else}
<input type="hidden" name="demo_select"
value="{$smarty.post.demo_select|escape}">
{$smarty.post.demo_select|escape}
{/if}
</td>
</tr>

提交按钮会根据
edit_mode
标记(见清单 14)的不同值显示不同的标签(Save 或 Edit)。
onclick
属性包含了一个名为
submitDemoForm()
的 JavaScript 函数调用。您将会在本文后面的内容中看到这一点,这个函数使用 Ajax 将表单数据提交到服务器,然后返回
false
,这样 Web 浏览器不会多次响应按钮的单击事件而提交相同的数据。然而,如果 JavaScript 被禁用了,
submitDemoForm()
将不会被调用,而 Web 浏览器就会将表单提交到服务器上。因此,这个表单不管 JavaScript 是否启用都会生效。

清单 14. demo_form.tpl 模板的提交按钮

<tr>
<td> </td>
<td>
<button type="submit" onclick="return submitDemoForm()">
{if $edit_mode}Save{else}Edit{/if}
</button>
</td>
</tr>

开发页面模板

demo_page.tpl 文件(见清单 15)包含了两个
<script>
元素,其中一个是引用 jQuery,另一个是引用示例应用中的 JavaScript 文件。这个页面模板使用 Smarty 的
{include}
包含了元素
<div>
中的一个表单模板。

清单 15. demo_page.tpl 模板

<html>
<head>
<title>Demo</title>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
</script>
<script type="text/javascript" src="demo_js.js">
</script>
</head>
<body>
<div id="demo_div">
{include file="demo_form.tpl"}
</div>
</body>
</html>

为 Smarty 和 Ajax 创建一个 PHP 控制器

demo_html.php 文件(如清单 16 所示)是 Ajax 和 Smarty 之间的桥梁,负责处理 Ajax 请求并在 demo_form.tpl 模板中使用 Smarty 生成 Ajax 响应,而这个模板只有当出现一个 Ajax 请求头时才会被调用。这个头是在 JavaScript 代码中设置的,您将在下面的小节中看到。如果没有 Ajax 头,这些代码就会使用 demo_page.tpl 模板,这意味着这是 Web 浏览器 的初始页面请求或 JavaScript 被禁用。在每一个请求中,
edit_mode
标记的值都会转换成另一个可编辑表单,并且它是不可编辑的。

清单 16. demo_html.php 示例

<?php
header("Cache-Control: no-cache");
header("Pragma: no-cache");

require 'libs/Smarty.class.php';

$smarty = new Smarty;

$smarty->compile_check = true;
$smarty->debugging = false;
$smarty->force_compile = 1;

$edit_mode = @($_REQUEST['edit_mode'] == "true");
$smarty->assign("edit_mode", !$edit_mode);

$ajax_request = @($_SERVER["HTTP_AJAX_REQUEST"] == "true");
$smarty->display($ajax_request ? 'demo_form.tpl' : 'demo_page.tpl');

?>

使用 Ajax 调用 Smarty 模板

单击表单按钮时会调用
submitDemoForm()
函数(见清单 17)。这个函数会通过 jQuery 使用
POST
和 Web 表单的相同 URL 将表单数据发送到服务器。然后表单数据会被 jQuery 的
serialize()
API 编码成一个字符串。在本例中,jQuery 在发送 Ajax 请求之前会调用
beforeSend()
函数设置
Ajax-Request
头,这个信息是服务器端用以确定 Ajax 请求的。在 Ajax 请求结束后,
success()
函数会被调用。这个回调函数会将响应内容插入到 Web 页面的
<div>
元素中。

清单 17. demo_js.js 示例

function submitDemoForm() {
var form = $("form[name=demo_form]");
$.ajax({
type: "POST",
url: form.action ? form.action : document.URL,
data: $(form).serialize(),
dataType: "text",
beforeSend: function(xhr) {
xhr.setRequestHeader("Ajax-Request", "true");
},
success: function(response) {
$("#demo_div").html(response);
}
});
return false;
}

在启用 SELinux 时建立 Smarty

在解压缩示例应用后,您应该会看一个名为 ajaxsmarty 的目录,它包含了多个 PHP 文件、一个 JavaScript 文件和四个子目录:cache、configs、templates 和 templates_c。模板目录包含了示例应用的 Smarty 模板。其它三个子目录是空的。

下载最新稳定版本的 Smarty (见 参考资料),然后解压缩它。(示例项目是在 Smarty 2.6.25 下测试的。)接下来,将 Smarty 的子目录 libs 复制到 ajaxsmarty 目录,这个目录是示例应用的主目录。

将 ajaxsmarty 目录(同时包含示例应用和 Smarty 的
libs
)上传或复制到 Apache 的 HTML 目录。如果您使用的是一个 Web 主机公司的服务器,SELinux 可能被禁用了,因为启用 SELinux 可能会有太多的支持呼叫。如果你在自己的 Linux 服务器上测试应用,您的服务器有可能启用了 SELinux,那么当浏览器请求一个 PHP 文件时您可能会得到下面的错误:“SELinux is preventing the httpd from using potentially mislabeled files.” 解决方法是以 root 身份运行清单 18 所示的命令。

清单 18. 设置 Web 文件的安全性上下文(标签)

restorecon -R -v /var/www/html/ajaxsmarty

至此,您应该能够在浏览器上打开地址:http://localhost/ajaxsmarty/,页面会显示三个链接。如果您单击其中一个链接,您会在 Web 浏览器上得到以下的 Smarty 错误:“Fatal error: Smarty error: unable to write to $compile_dir '/var/www/html/ajaxsmarty/templates_c'. Be sure $compile_dir is writable by the Web server user. in /var/www/html/ajaxsmarty/libs/Smarty.class.php on line 1113”。

出现上面的错误是因为 Smarty 安装还没有完成。您必须给 Web 服务器写 templates_c 和 cache 目录的用户权限。实现这一步的正确做法是修改它们的拥有者,如清单 19 所示。注意
apache
用户名和服务器的 HTML 目录在您的计算机上可能会有所不同。

清单 19. 修改两个目录的拥有者使 Smarty 能够创建文件

chown apache:apache /var/www/html/ajaxsmarty/templates_c
chown apache:apache /var/www/html/ajaxsmarty/cache

如果您不是使用 root 用户登录,您是不能使用
chown
修改 templates_c 和 cache 的写权限的。您可以通过使用 FTP 客户端修改,或者使用
chmod
命令将权限设置为 777。允许所有用户都能写这些文件夹不是一个非常好的做法,但是如果您不能使用
chown
命令,这是您唯一快速有效的方法。如果您的 Web 服务器是公共的,您应该联系服务器管理员帮您完成这个修改。

如果您的计算机启用了 SELinux,您可能还会在浏览器上遇到这样的一个错误:“SELinux prevented httpd reading and writing access to http files.” 或 “SELinux is preventing httpd (httpd_t) write to ./templates_c (public_content_rw_t).” 解决方法是以 root 身份运行清单 20 中的命令。

清单 20. 在启用 SELinux 时允许 Smarty 在它的目录中创建文件

chcon -t public_content_rw_t /var/www/html/ajaxsmarty/templates_c
chcon -t public_content_rw_t /var/www/html/ajaxsmarty/cache
setsebool -P allow_httpd_anon_write=1

带有
allow_httpd_anon_write
参数的
setsebool
命令必须只执行一次。它允许 httpd 守护进程将文件写到
public_content_rw_t
目录中。

结束语

在本文中,您了解了如何创建为 Ajax 请求产生 JSON、XML 和 HTML 响应的 Smarty 模板,如何使用 Smarty 创建一个即使在 Web 浏览器禁用 JavaScript 时仍然有效的 Ajax 表单,以及如何在启用 SELinux 的 Linux 主机上配置 Smarty。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐