PHP-和-jQuery-高级教程-四-

59 阅读33分钟

PHP 和 jQuery 高级教程(四)

原文:Pro PHP and jQuery

协议:CC BY-NC-SA 4.0

七、使用 jQuery 增强用户界面

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1230-1_​7) contains supplementary material, which is available to authorized users.

目前的应用功能齐全。可以查看事件,具有管理权限的用户可以登录以创建、编辑或删除事件。

下一步是向应用添加一层润色,以创建最终的外观和感觉,这将通过使用一种称为渐进增强的技术向应用添加 AJAX 功能来完成。

使用 jQuery 添加渐进式增强

渐进式改进是一个术语,最初由 Steven Champeon 1 在 2003 年创造,用于描述一种 web 开发技术,其中应用被设计为可访问任何互联网连接和浏览器,使用语义 HTML 和其他分层应用的技术(如 CSS 文件和 JavaScript 代码)。

对于遵循渐进增强原则的应用,它必须遵循以下准则:

  • 所有浏览器都可以使用最简单、最具语义的 HTML 标记来访问基本内容。
  • 该应用的所有基本功能都适用于所有浏览器。
  • 尊重用户的偏好;这意味着 web 应用不会覆盖浏览器设置(如窗口大小)。
  • 外部链接的 CSS 处理文档的样式和表示。
  • 外部链接的 JavaScript 增强了用户体验,但它并不引人注目,或者对应用的操作并不重要。

您的应用已经满足了前四条准则(虽然不太好看,但是应用可以在禁用样式的情况下工作)。因此,只要您的 JavaScript 没有创建任何在禁用 JavaScript 的情况下无法访问的新功能,您就已经成功地创建了一个逐步增强的 web 应用。

设定渐进增强目标

使用渐进式增强的原则,您将添加无需在模式窗口中刷新页面即可查看事件信息的能力,模式窗口是位于现有标记之上的内容区域,用于显示附加信息。这种窗口通常是由 JavaScript 触发的,它们被用在许多当今最流行的网站上。

在您的日历应用中,您将使用一个模态窗口在用户单击事件标题后显示事件的详细信息。这不需要使用 AJAX 刷新页面就可以完成。

在日历应用中包含 jQuery

正如您在本书前面的 jQuery 介绍中所了解到的,使用 jQuery 语法要求您首先包含 jQuery 库。如果可能的话,最好将<script>标签放在 HTML 标记的底部附近,就在结束 body 标签(</body>)之前。这样,HTML 可以先加载,用户不必在看到任何页面之前等待(通常较慢的)脚本文件加载。此外,它还可以防止代码与未完全加载的页面元素交互时出现 JavaScript 错误。

为了便于始终如一地遵循这一实践,您将在footer.inc.php ( /public/assets/common/footer.inc.php)中包含 jQuery 库和所有后续文件。首先在你的应用中包含最新版本的 jQuery 您可以通过在footer.inc.php中添加以下粗体行来实现这一点:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js

</script>

</body>

</html>

保存这段代码,然后在浏览器中加载http://localhost/。打开 Firebug 控制台并执行以下命令,以确保 jQuery 加载到您的应用中:

$("h2").text();

运行此命令后,控制台将显示以下输出:

>>> $("h2").text();

"January 2016

Note

因为您使用的是 Google 托管的 jQuery 库,所以除了 Apache 服务器之外,您还需要有一个可用的 Internet 连接。如果您无法访问互联网连接或不喜欢使用互联网连接,请从 http://jquery.com/ 下载最新版本的 jQuery 并包含它。

创建 JavaScript 初始化文件

您的应用遵循渐进增强准则,因此所有脚本都将保存在一个名为init.js的外部文件中。它将驻留在公共的js文件夹(/public/assets/js/init.js)中,并将包含您的应用的所有定制 jQuery 代码。

包括应用中的初始化文件

在您的应用可以使用任何脚本之前,您需要在应用中包含初始化文件。您的应用将使用 jQuery 语法,因此初始化文件需要包含在footer.inc.php中加载 jQuery 库的脚本之后。

通过在footer.inc.php中插入以下粗体代码,您可以将该文件包含在您的应用中:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js

</script>

<script src="assets/js/init.js"></script>

</body>

</html>

确保文档在脚本执行前准备就绪

在创建了init.js之后,使用 jQuery 中的document.ready快捷方式来确保在文档真正准备好被操作之前没有脚本执行。将以下代码插入init.js:

"use strict";  // enforce variable declarations – safer coding

// Makes sure the document is ready before executing scripts

jQuery(function($){

// A quick check to make sure the script loaded properly

console.log("init.js was loaded successfully.");

});

保存这个文件,并在 Firebug 控制台打开的情况下在浏览器中加载http://localhost/。文件加载后,您应该会在控制台中看到以下结果:

init.js was loaded successfully.

为 jQuery 创建的元素创建新的样式表

为了确保用 jQuery 创建的元素在开始构建时看起来是正确的,我们将向前跳一步,创建一个新的 CSS 文件来存储将要用 jQuery 脚本创建的元素的样式信息。

这个文件将被命名为ajax.css,它将驻留在css文件夹(/public/assets/css/ajax.css)中。创建后,在其中放置以下样式规则:

.modal-overlay {

position: fixed;

top: 0;

left: 0;

bottom: 0;

width: 100%;

height: 100%;

background-color: rgba(0,0,0,.5);

z-index: 4;

}

.modal-window {

position: absolute;

top: 140px;

left: 50%;

width: 300px;

height: auto;

margin-left: -150px;

padding: 20px;

border: 2px solid #000;

background-color: #FFF;

-moz-border-radius: 6px;

-webkit-border-radius: 6px;

border-radius: 6px;

-moz-box-shadow: 0 0 14px #123;

-webkit-box-shadow: 0 0 14px #123;

box-shadow: 0 0 14px #123;

z-index: 5;

}

.modal-close-btn {

position: absolute;

top: 0;

right: 4px;

margin: 0;

padding: 0;

text-decoration: none;

color: black;

font-size: 16px;

}

.modal-close-btn:before {

position: relative;

top: -1px;

content: "Close";

text-transform: uppercase;

font-size: 10px;

}

在索引文件中包含样式表

接下来,打开index.php,通过添加粗体行将新样式表包含在$css_files数组中:

<?php

declare(strict_types=1);

/*

* Include necessary files

*/

include_once '../sys/core/init.inc.php';

/*

* Load the calendar

*/

$cal = new Calendar($dbo, "2016-01-01 12:00:00");

/*

* Set up the page title and CSS files

*/

$page_title = "Events Calendar";

$css_files = array('style.css', 'admin.css', 'ajax.css');

/*

* Include the header

*/

include_once 'assets/common/header.inc.php';

?>

<div id="content">

<?php

/*

* Display the calendar HTML

*/

echo $cal->buildCalendar();

?>

</div><!-- end #content -->

<?php

/*

* Include the footer

*/

include_once 'assets/common/footer.inc.php';

?>

为事件数据创建模式窗口

您将为此应用创建的模式窗口将非常简单;创建它的脚本将遵循以下步骤。

Prevent the default action (opening the detailed event view in view.php).   Add an active class to the event link in the calendar.   Extract the query string from the event link’s href attribute.   Create a button that will close the modal window when clicked.   Create the modal window itself and put the Close button inside it.   Use AJAX to retrieve the information from the database and display it in the modal window.  

当针对事件标题链接触发 click 事件时,将执行前面的所有步骤。

将函数绑定到标题链接的点击事件

首先向init.js添加一个新的选择器,它选择列表项(li>a)的直接后代的所有锚元素,并使用.on()方法将一个处理程序绑定到 click 事件(jQuery 版本来会使用.live()方法,但现在已经不推荐使用了)。

因为您需要绑定到许多动态创建的元素,所以您将始终使用三参数形式的.on() : $( selector ).on( event, childSelector, function ),其中 selector 是父容器(非动态),childSelector 表示(通常)动态创建的元素。为了获得最大的灵活性,您将使用“body”作为父容器。

将以下粗体代码插入init.js:

"use strict";  // enforce variable declarations – safer coding

// Makes sure the document is ready before executing scripts

jQuery(function($){

// Pulls up events in a modal window

$("body").on("click", " li>a", function(event){

// Event handler scripts go here

});

});

防止默认操作并添加活动类

接下来,您需要使用. preventDefault()来阻止默认动作,然后使用. addClass()向被点击的元素添加一个active类。

这是通过添加以下粗体代码来实现的:

"use strict";  // enforce variable declarations – safer coding

// Makes sure the document is ready before executing scripts

jQuery(function($){

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view.php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Proves the event handler worked by logging the link text

console.log( $(this).text() );

});

});

保存此代码后,在浏览器中重新加载http://localhost/,并点击任何事件标题。不是在view.php上查看事件详情,而是在控制台中输出事件标题。例如,如果您单击New Year’s Day事件,您将在控制台中看到以下输出:

New Year’s Day

用正则表达式提取查询字符串

创建模式窗口是为了显示事件信息,所以您需要某种方法来知道应该显示哪个事件。无需添加任何额外的标记,您实际上可以使用正则表达式从href属性中提取事件 ID。

为此,您需要从链接中提取查询字符串。(如果href属性值为http://localhost/view.php?event_id=1,查询字符串为event_id=1。)

您将使用两项来提取查询字符串:.replace(),一个接受字符串或正则表达式模式进行匹配的原生 JavaScript 函数,匹配的字符串或模式应该替换为。

使用惰性方法:基于字符串的替换

乍一看,显而易见的解决方案似乎如下:

var data = string.replace("``http://localhost/view.php

是的,这确实有效,产生了输出"event_id=1"(如果你假设$string的初始值是http://localhost/view.php?event_id=1)。不幸的是,这种方法不够灵活;例如,如果应用被移动到另一个域名,该怎么办?或者,文件名改成event.php怎么办?这两种更改都会破坏前面的逻辑,需要更新脚本。

采用更好的解决方案:正则表达式

然而,有一个更好的解决方案:正则表达式。在大多数现代编程语言中,正则表达式是一种强大的模式匹配工具。

为了提取查询字符串,您将使用一种模式来查找字符串中的第一个问号(?),然后返回其后的所有内容。该模式将如下所示:

/.*?\?(.*)$/

JavaScript 中的正则表达式由表达式两端的正斜杠(/)分隔。在这个表达式中,模式寻找零个或更多的任意字符(从左到右),直到第一次到达一个问号;然后,它将问号之后直到字符串末尾的所有字符存储为一个命名组,以便在替换中使用。

Note

在第九章中,你会学到更多关于正则表达式及其工作原理的知识。

将正则表达式合并到脚本中

您想要提取被点击链接的href值,所以您将使用this关键字。为了使用 jQuery 方法,您必须首先将this作为选择器传递给 jQuery 函数。现在用.attr()方法访问href值,调用.replace(),并提取查询字符串。

. replace()中使用正则表达式时,不使用引号将模式括起来。使用刚才描述的正则表达式,修改init.js,将来自单击链接的查询字符串存储在一个名为data的变量中;通过添加粗体显示的代码来实现这一点:

"use strict";  // enforce variable declarations – safer coding

// Makes sure the document is ready before executing scripts

jQuery(function($){

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view.php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Gets the query string from the link href

var data = $(this)

.attr("href")

.replace(/.+?\?(.*)$/, "$1");

// Logs the query string

console.log( data );

});

});

保存这段代码,重新加载http://localhost/,然后点击一个链接。您应该会在控制台中看到类似以下内容的内容:

event_id=1

创建模式窗口

下一步是生成实际创建模式窗口和覆盖图的 HTML 标记。这个标记非常简单:它基本上由一个包装在其他内容周围的div元素组成。例如,New Year’s Day事件模式窗口标记将如下所示:

<div class="modal-window">

<h2>New Year’s Day</h2>

<p class="dates">January 01, 2016, 12:00am—11:59pm</p>

<p>Happy New Year!</p>

</div>

您还将为其他特性使用这个相同的模式窗口(比如为事件显示编辑表单),因此模式窗口的实际创建将被抽象在一个单独的函数中,以便于重用。因为您将重用多个函数,所以您将通过将所有实用函数放在一个对象文字中来组织您的脚本,该对象文字是一个逗号分隔的名称-值对列表(有关更多信息,请参见侧栏“使用实用函数的对象文字”)。

创建实用函数来检查模式窗口

init.js的顶部,声明一个名为fx的新对象文字来存储您的实用函数:

"use strict";  // enforce variable declarations – safer coding

// Makes sure the document is ready before executing scripts

jQuery(function($){

// Functions to manipulate the modal window

var fx = {};

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view.php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Gets the query string from the link href

var data = $(this)

.attr("href")

.replace(/.+?\?(.*)$/, "$1");

// Logs the query string

console.log( data );

});

});

存储在fx中的第一个函数将被调用initModal,它将检查一个模态窗口是否已经存在。如果是,该函数将选择它;否则,它将创建一个新的并将其附加到 body 标签中。

要查看某个元素是否已经存在,请在使用该元素的选择器执行 jQuery 函数后使用length属性。如果length属性返回0,则该元素当前不存在于文档对象模型(DOM)中。

通过在init.js内的fx中插入以下粗体代码,执行检查并返回一个模态窗口:

// Functions to manipulate the modal window

var fx = {

// Checks for a modal window and returns it, or

// else creates a new one and returns that

"initModal" : function() {

// If no elements are matched, the length

// property will return 0

if ( $(".modal-window").length==0 )

{

// Creates a div, adds a class, and

// appends it to the body tag

return $("<div>")

.addClass("modal-window")

.appendTo("body");

}

else

{

// Returns the modal window if one

// already exists in the DOM

return $(".modal-window");

}

}

};

从事件处理程序调用实用函数

接下来,通过在init.js中添加以下粗体代码,修改 click 事件处理程序,将fx.initModal的结果加载到一个变量中,以便在脚本中使用:

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view.php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Gets the query string from the link href

var data = $(this)

.attr("href")

.replace(/.+?\?(.*)$/, "$1"),

// Checks if the modal window exists and

// selects it, or creates a new one

modal = fx.initModal();

});

Note

在本例中,data变量后的分号已被逗号替换。

保存并重新加载http://localhost/,然后点击其中一个事件标题,使一个模态窗口出现在屏幕上(见图 7-1 )。

A978-1-4842-1230-1_7_Fig1_HTML.jpg

图 7-1。

Clicking an event title causes a modal window to appear Using an Object Literal for Utility Functions

在编写应用时,效用函数经常发挥作用。应用越复杂,就越有可能存在大量的实用功能,也越难将这些功能组织起来。

保持效用函数有组织的一个选择是使用对象文字。这使得开发人员可以将功能放在一个地方,甚至可以根据它们的用途对功能进行分组。

理解对象文字

最简单地说,对象文字是 JavaScript 中的一个变量,它是一组空的花括号,表示一个空的对象文字:

var obj = {};

您可以使用逗号分隔的名称-值对向对象文字添加任意数量的值:

var obj = {

"name" : "Jason Lengstorf",

"age" : "25"

};

要访问一个值,只需添加一个点(.)和您希望访问的属性的名称:

alert(obj.name); // alerts "Jason Lengstorf"

对象文字如此有用是因为您还可以在其中存储函数:

var obj = {

"func" : function() { alert("Object literals rule!"); }

};

若要调用存储在对象文字中的函数,请使用与访问值相同的语法;但是,您还必须在末尾包含括号。否则,JavaScript 假设您试图将该函数存储在另一个变量中,并简单地返回它:

obj.func(); // alerts "Object literals rule!"

对象文字中的函数也可以接受参数:

var obj = {

"func" : function(text){ alert(text); }

};

obj.func("I’m a parameter!"); // alerts "I’m a parameter!"

对象文字与过程编程

将函数组织在一个对象文字中可以使代码更易读,如果开发人员努力使函数足够抽象,可以减少将来维护代码所花费的时间,因为所有的东西都是分门别类的,很容易找到。

也就是说,对象文字并不总是最好的解决方案。在处理多个对象的情况下,使用完全面向对象的方法会更好。如果几乎不需要任何脚本,一个对象文字可能是多余的。

最终,由作为开发人员的您来决定哪种方法最适合您的项目。最终,这是一个品味和舒适的问题;你需要决定什么使你的开发过程最容易。

用 AJAX 检索和显示事件信息

既然模式窗口已经加载,现在是时候加载事件信息并显示它了。为此,您将使用$.ajax()方法。

使用$.ajax()方法,您将使用POST方法将数据发送到一个处理文件(您将在下一节构建该文件),然后将响应插入到模态窗口中。

创建一个文件来处理 AJAX 请求

在调用$.ajax()之前,了解数据应该发送到哪里以及如何发送会有所帮助。在inc文件夹中,创建一个名为ajax.inc.php ( /public/assets/inc/ajax.inc.php)的新文件。这个文件将非常类似于process.inc.php,除了它将专门处理 AJAX 调用。因为从 PHP 函数返回的值不能被 JavaScript 读取,除非该值被实际输出(使用echo或其类似物),process.inc.php在应用的这个方面将不能正常工作。

本质上,ajax.inc.php将使用一个查找数组来确定需要使用哪些对象和方法,然后使用echo输出返回值,以便与 AJAX 一起使用。

首先启用会话,加载必要的配置信息,定义一个常数,并组装一个自动加载功能。现在将以下内容添加到ajax.inc.php:

<?php

declare(strict_types=1);

/*

* Enable sessions if needed.

* Avoid pesky warning if session already active.

*/

$status = session_status();

if ($status == PHP_SESSION_NONE){

//There is no active session

session_start();

}

/*

* Include necessary files

*/

include_once '../../../sys/config/db-cred.inc.php';

/*

* Define constants for config info

*/

foreach ( $C as $name => $val )

{

define($name, $val);

}

function __autoload($class_name)

{

$filename = '../../../sys/class/class.'

. strtolower($class_name) . '.inc.php';

if ( file_exists($filename) )

{

include_once $filename;

}

}

?>

接下来,使用用于加载事件数据的信息定义查找数组,然后编写将实例化对象的代码,调用方法,并使用下面的粗体代码输出返回值:

<?php

declare(strict_types=1);

/*

* Enable sessions if needed.

* Avoid pesky warning if session already active.

*/

$status = session_status();

if ($status == PHP_SESSION_NONE){

//There is no active session

session_start();

}

/*

* Include necessary files

*/

include_once '../../../sys/config/db-cred.inc.php';

/*

* Define constants for config info

*/

foreach ( $C as $name => $val )

{

define($name, $val);

}

/*

* Create a lookup array for form actions

*/

define(ACTIONS, array(

'event_view' => array(

'object' => 'Calendar',

'method' => 'displayEvent'

)

)

);

/*

* Make sure the anti-CSRF token was passed and that the

* requested action exists in the lookup array

*/

if ( isset(ACTIONS[$_POST['action']]) )

{

$use_array = ACTIONS[$_POST['action']];

$obj = new $use_array'object';

$method = $use_array['method'];

/*

* Check for an ID and sanitize it if found

*/

if ( isset($_POST['event_id']) )

{

$id = (int) $_POST['event_id'];

}

else { $id = NULL; }

echo $obj->$method($id);

}

function __autoload($class_name)

{

$filename = '../../../sys/class/class.'

. strtolower($class_name) . '.inc.php';

if ( file_exists($filename) )

{

include_once $filename;

}

}

?>

与前面代码中的process.inc.php唯一真正的区别是在查找数组中缺少一个头键,并且使用了echo来输出被调用方法的返回值。

使用 AJAX 加载事件数据

回到init.js,您现在可以将呼叫添加到$.ajax()。在您的应用中,最终会有几次对$.ajax()的调用,所以将处理文件的位置存储在一个变量中,以便在文件位置或名称可能改变时易于维护。通过插入粗体显示的代码,将该变量添加到init.js的顶部:

"use strict";  // enforce variable declarations – safer coding

// Makes sure the document is ready before executing scripts

jQuery(function($){

// File to which AJAX requests should be sent

var processFile = "assets/inc/ajax.inc.php",

// Functions to manipulate the modal window

fx = {

// Checks for a modal window and returns it, or

// else creates a new one and returns that

"initModal" : function() {

// If no elements are matched, the length

// property will be 0

if ( $(".modal-window").length==0 )

{

// Creates a div, adds a class, and

// appends it to the body tag

return $("<div>")

.addClass("modal-window")

.appendTo("body");

}

else

{

// Returns the modal window if one

// already exists in the DOM

return $(".modal-window");

}

}

};

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view. php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Gets the query string from the link href

var data = $(this)

.attr("href")

.replace(/.+?\?(.*)$/, "$1"),

// Checks if the modal window exists and

// selects it, or creates a new one

modal = fx.initModal();

});

});

接下来,在事件处理程序中设置对$.ajax()的调用。它将使用POST方法,指向processFile,并发送适当的数据。因为从链接中提取的查询字符串不包含 action 字段,所以在这里手动插入一个。最后,如果调用成功,使用.append()将返回的标记插入模式窗口,如果调用失败,则显示一条错误消息。

通过在init.js中插入以下粗体行来完成:

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view.php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Gets the query string from the link href

var data = $(this)

.attr("href")

.replace(/.+?\?(.*)$/, "$1"),

// Checks if the modal window exists and

// selects it, or creates a new one

modal = fx.initModal();

// Loads the event data from the DB

$.ajax({

type: "POST",

url: processFile,

data: "action=event_view``&

success: function(data){

// Alert event data for now

modal.append(data);

},

error: function(msg) {

modal.append(msg);

}

});

});

保存您的更改,重新加载http://localhost/,点击事件标题,查看加载到模态窗口中的事件信息(见图 7-2 )。

A978-1-4842-1230-1_7_Fig2_HTML.jpg

图 7-2。

The event information loaded into the modal window

添加关闭按钮

就目前的情况而言,在单击事件标题后摆脱模式窗口的唯一方法是重新加载页面。当然,这还不够好,所以您需要添加一个关闭按钮。

为此,您需要创建一个新的链接,并绑定一个 click 事件处理程序,从 DOM 中移除模式窗口。为了给它一个传统的关闭按钮的感觉,使用乘法符号作为它的内容(并且ajax.css中的 CSS 在它前面添加了“关闭”这个词)。另外,添加一个href属性来确保鼠标悬停在链接上时,鼠标的行为就像按钮是可点击的一样。

接下来,通过在init.js中插入以下粗体代码来添加一个关闭按钮:

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view.php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Gets the query string from the link href

var data = $(this)

.attr("href")

.replace(/.+?\?(.*)$/, "$1"),

// Checks if the modal window exists and

// selects it, or creates a new one

modal = fx.initModal();

// Creates a button to close the window

$("<a>")

.attr("href", "#")

.addClass("modal-close-btn")

.html("``&

.click(function(event){

// Prevent the default action

event.preventDefault();

// Removes modal window

$(".modal-window")

.remove();

})

.appendTo(modal);

// Loads the event data from the DB

$.ajax({

type: "POST",

url: processFile,

data: "action=event_view&" + data,

success: function(data){

// Alert event data for now

modal.append(data);

},

error: function(msg) {

modal.append(msg);

}

});

});

保存前面的代码后,加载http://localhost/并点击事件标题以查看新的关闭按钮(参见图 7-3 )。单击关闭按钮移除模式窗口。

A978-1-4842-1230-1_7_Fig3_HTML.jpg

图 7-3。

The Close button is now visible in the modal window

向模式窗口的创建和销毁添加效果

为了给模式窗口增加更多的风格和修饰,您将添加一些效果,使盒子在创建时淡入,在移除时淡出。此外,为了在模式窗口处于活动状态时帮助将焦点吸引到它,您将向站点添加一个覆盖图,该覆盖图将使除模式窗口之外的所有内容变暗。

淡出模式窗口

首先,您需要添加淡出模式窗口的效果。这个函数会以几种方式触发,其中一些也会触发事件;要处理这个问题,您需要创建一个条件语句来检查事件是否被触发,如果是这种情况,就阻止默认操作。

接下来,从所有链接中移除类active,因为当模式窗口不可见时,它们都不被使用。

最后,使用. fadeOut()选择并淡出模态窗口。在.fadeOut()的回调函数中,模态窗口将被完全从 DOM 中移除。

您可以通过在fx对象文本中插入以下粗体代码来添加该函数:

// Functions to manipulate the modal window

fx = {

// Checks for a modal window and returns it, or

// else creates a new one and returns that

"initModal" : function() {

// If no elements are matched, the length

// property will be 0

if ( $(".modal-window").length==0 )

{

// Creates a div, adds a class, and

// appends it to the body tag

return $("<div>")

.addClass("modal-window")

.appendTo("body");

}

else

{

// Returns the modal window if one

// already exists in the DOM

return $(".modal-window");

}

},

// Fades out the window and removes it from the DOM

"boxout" : function(event) {

// If an event was triggered by the element

// that called this function, prevents the

// default action from firing

if ( event!=undefined )

{

event.preventDefault();

}

// Removes the active class from all links

$("a").removeClass("active");

// Fades out the modal window, then removes

// it from the DOM entirely

$(".modal-window")

.fadeOut("slow", function() {

$(this).remove();

}

);

}

};

要将这个新函数合并到脚本中,请使用以下粗体代码修改 Close 按钮的 click 事件处理程序:

// Creates a button to close the window

$("<a>")

.attr("href", "#")

.addClass("modal-close-btn")

.html("×")

.click(function(event){

// Removes modal window

fx.boxout(event);

})

.appendTo(modal);

保存init.js并在浏览器中重新加载http://localhost/。单击事件标题创建一个新的模态窗口,然后单击关闭按钮观看模态窗口淡出(参见图 7-4 )。

A978-1-4842-1230-1_7_Fig4_HTML.jpg

图 7-4。

The modal window mid-fade after the user clicks the Close button

在模式窗口中添加覆盖和淡入淡出

要在模式窗口中添加覆盖和淡入,需要向fx对象文字添加另一个函数。它将被调用boxin,在事件标题点击处理程序中$.ajax()的成功回调中被调用。该函数将接受两个参数:由ajax.inc.php ( data)返回的数据和模态窗口对象(modal)。

首先,该函数将创建一个新的div,其类为modal-overlay;接下来,它将隐藏div并将其追加到 body 元素中。为了提高可用性,这个覆盖图还会附带一个 click 处理程序,当通过调用fx.boxout()被点击时,它会移除模态窗口。

接下来,该函数将隐藏模态窗口,并将存储在data中的信息追加到其中。最后,它将使用.fadeIn()淡入两个元素。

通过插入粗体显示的代码,将该函数添加到fx对象文字中:

// Functions to manipulate the modal window

fx = {

// Checks for a modal window and returns it, or

// else creates a new one and returns that

"initModal" : function() {

// If no elements are matched, the length

// property will be 0

if ( $(".modal-window").length==0 )

{

// Creates a div, adds a class, and

// appends it to the body tag

return $("<div>")

.addClass("modal-window")

.appendTo("body");

}

else

{

// Returns the modal window if one

// already exists in the DOM

return $(".modal-window");

}

},

// Adds the window to the markup and fades it in

"boxin" : function(data, modal) {

// Creates an overlay for the site, adds

// a class and a click event handler, then

// appends it to the body element

$("<div>")

.hide()

.addClass("modal-overlay")

.click(function(event){

// Removes event

fx.boxout(event);

})

.appendTo("body");

// Loads data into the modal window and

// appends it to the body element

modal

.hide()

.append(data)

.appendTo("body");

// Fades in the modal window and overlay

$(".modal-window,.modal-overlay")

.fadeIn("slow");

},

// Fades out the window and removes it from the DOM

"boxout" : function(event) {

// If an event was triggered by the element

// that called this function, prevents the

// default action from firing

if ( event!=undefined )

{

event.preventDefault();

}

// Removes the active class from all links

$("a").removeClass("active");

// Fades out the modal window, then removes

// it from the DOM entirely

$(".modal-window")

.fadeOut("slow", function() {

$(this).remove();

}

);

}

};

接下来,您需要修改在单击事件标题调用fx.boxin时成功执行$.ajax()时触发的回调函数;您可以通过添加以下粗体代码行来实现这一点:

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){

// Stops the link from loading view.php

event.preventDefault();

// Adds an "active" class to the link

$(this).addClass("active");

// Gets the query string from the link href

var data = $(this)

.attr("href")

.replace(/.+?\?(.*)$/, "$1"),

// Checks if the modal window exists and

// selects it, or creates a new one

modal = fx.initModal();

// Creates a button to close the window

$("<a>")

.attr("href", "#")

.addClass("modal-close-btn")

.html("×")

.click(function(event){

// Removes modal window

fx.boxout(event);

})

.appendTo(modal);

// Loads the event data from the DB

$.ajax({

type: "POST",

url: processFile,

data: "action=event_view&" + data,

success: function(data){

fx.boxin(data, modal);

},

error: function(msg) {

modal.append(msg);

}

});

});

保存这段代码,重新加载http://localhost/,点击一个事件标题,查看模态叠加和模态窗口淡入(见图 7-5 )。

A978-1-4842-1230-1_7_Fig5_HTML.jpg

图 7-5。

The modal window with an overlay to help draw the focus

你可能已经注意到模式窗口在打开时会闪烁。发生这种情况是因为fx.initModal()将模态窗口附加到 body 元素,而没有隐藏它。要纠正这一点,使用下面的粗体代码添加对fx.initModal()中的.hide()的调用:

// Functions to manipulate the modal window

fx = {

// Checks for a modal window and returns it, or

// else creates a new one and returns that

"initModal" : function() {

// If no elements are matched, the length

// property will be 0

if ( $(".modal-window").length==0 )

{

// Creates a div, adds a class, and

// appends it to the body tag

return $("<div>")

.hide()

.addClass("modal-window")

.appendTo("body");

}

else

{

// Returns the modal window if one

// already exists in the DOM

return $(".modal-window");

}

},

// Adds the window to the markup and fades it in

"boxin" : function(data, modal) {

// Code omitted for brevity

},

// Fades out the window and removes it from the DOM

"boxout" : function(event) {

// Code omitted for brevity

}

};

最后,单击关闭按钮不会删除覆盖。要淡出并移除覆盖图,只需修改fx.boxout()中的选择器以包含类别modal-overlay:

// Functions to manipulate the modal window

fx = {

// Checks for a modal window and returns it, or

// else creates a new one and returns that

"initModal" : function() {

// Code omitted for brevity

},

// Adds the window to the markup and fades it in

"boxin" : function(data, modal) {

// Code omitted for brevity

},

// Fades out the window and removes it from the DOM

"boxout" : function(event) {

// If an event was triggered by the element

// that called this function, prevents the

// default action from firing

if ( event!=undefined )

{

event.preventDefault();

}

// Removes the active class from all links

$("a").removeClass("active");

// Fades out the modal window and overlay,

// then removes both from the DOM entirely

$(".modal-window,.modal-overlay")

.fadeOut("slow", function() {

$(this).remove();

}

);

}

};

完成更改后,重新加载http://localhost/并点击事件标题。模式窗口和覆盖将淡入,单击关闭按钮或覆盖将导致模式窗口和覆盖淡出。

摘要

在本章中,您学习了如何使用渐进式增强技术在 jQuery 中动态加载事件数据。您还了解了事件处理、基本效果,甚至还了解了一点正则表达式。

在下一章中,您将继续添加 AJAX 功能,通过 AJAX 使编辑控件工作。

Footnotes 1

www.hesketh.com/about-us/leadership-team/

八、使用 AJAX 和 jQuery 编辑日历

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1230-1_​8) contains supplementary material, which is available to authorized users.

现在您的应用可以显示事件数据而无需刷新页面,您可以看到 AJAX 在 web 应用中提供的额外便利。从历史上看,使用 web 应用的最大缺陷之一是,每个动作,无论多小,通常都需要等待页面刷新,同时保存设置。当用户需要在共享计算机上访问他的信息时,Web 应用很方便,但缓慢的工作流程通常足以让用户尽可能倾向于桌面应用。

然而,随着 AJAX 的主流接受和使用,用户现在可以快速地进行更改,而不必一直等待页面重新加载。这让网络应用感觉更像桌面应用,也让它们对用户更有吸引力。

在本章中,您将学习添加脚本,使管理员的编辑控件运行顺畅,而不需要为每个操作刷新页面。唯一需要页面刷新的操作是登录,因为这需要对会话进行更改。

Note

开始本章中的练习之前,请登录日历应用。默认登录依赖于用户名testuser和密码admin

打开事件创建表单

首先,您将修改脚本,让管理员在不刷新页面的情况下添加新事件。打开init.js,选择按钮,按其类别添加新事件(admin)。添加一个 click 事件处理程序来阻止默认操作,并(目前)记录一条消息来确认它被正确触发:

"use strict";

jQuery(function($){

var processFile = "assets/inc/ajax.inc.php",

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...}

}

// Pulls up events in a modal window

$("body").on("click", “li>a”, function(event){...});

// Displays the edit form as a modal window

$("body").on("click", ".admin", function(event){

// Prevents the form from submitting

event.preventDefault();

// Logs a message to prove the handler was triggered

console.log( "Add a New Event button clicked!" );

});

});

Note

为了简洁起见,所有未更改的函数都被缩写,并且本章的代码示例中省略了注释。你可以在这本书的首页找到代码, http://www.apress.com/9781484212318

保存此代码并刷新http://localhost/。单击“添加新事件”按钮,您将看到以下结果记录到控制台中:

Add a New Event button clicked!

添加 AJAX 调用来加载表单

接下来,创建一个变量来存储将被发送到处理文件的动作。您正在加载编辑和创建表单,因此将动作设置为event_edit

现在可以调用$.ajax()函数了。该函数类似于将事件数据加载到模式窗口的脚本;事实上,唯一的区别在于提交的数据和处理返回值的方式。

在一次成功的加载中,您隐藏表单并在变量form中存储对它的引用。接下来,使用fx.initModal()检查模态窗口,并使用第一个参数为空的fx.boxin()淡入。最后,将表单添加到模式窗口,淡入,并为其分配类edit-form,以便以后选择。

将以下粗体代码添加到init.js以执行这些步骤:

"use strict";

jQuery(function($){

var processFile = "assets/inc/ajax.inc.php",

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...}

}

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){...});

// Displays the edit form as a modal window

$("body").on("click", ".admin", function(event){

// Prevents the form from submitting

event.preventDefault();

// Loads the action for the processing file

var action = "edit_event";

// Loads the editing form and displays it

$.ajax({

type: "POST",

url: processFile,

data: "action="+action,

success: function(data){

// Hides the form

var form = $(data).hide(),

// Make sure the modal window exists

modal = fx.initModal();

// Call the boxin function to create

// the modal overlay and fade it in

fx.boxin(null, modal);

// Load the form into the window,

// Fades in the content, and adds

// a class to the form

form

.appendTo(modal)

.addClass("edit-form")

.fadeIn("slow");

},

error: function(msg){

alert(msg);

}

});

});

});

但是您还没有准备好运行这段代码。

修改 AJAX 处理文件以加载表单

在前面的 AJAX 调用工作之前,您需要修改ajax.inc.php查找数组。添加一个新的数组元素,告诉脚本创建一个新的Calendar对象,然后用粗体显示的代码调用displayForm()方法:

<?php

declare(strict_types=1);

/*

* Enable sessions if needed.

* Avoid pesky warning if session already active.

*/

$status = session_status();

if ($status == PHP_SESSION_NONE){

//There is no active session

session_start();

}

/*

* Include necessary files

*/

include_once '../../../sys/config/db-cred.inc.php';

/*

* Define constants for config info

*/

foreach ( $C as $name => $val )

{

define($name, $val);

}

/*

* Create a lookup array for form actions

*/

define(ACTIONS, array(

'event_view' => array(

'object' => 'Calendar',

'method' => 'displayEvent'

),

'edit_event' => array(

'object' => 'Calendar',

'method' => 'displayForm'

)

)

);

/*

* Make sure the anti-CSRF token was passed and that the

* requested action exists in the lookup array

*/

if ( isset(ACTIONS[$_POST['action']]) )

{

$use_array = ACTIONS[$_POST['action']];

$obj = new $use_array'object';

$method = $use_array['method'];

/*

* Check for an ID and sanitize it if found

*/

if ( isset($_POST['event_id']) )

{

$id = (int) $_POST['event_id'];

}

else { $id = NULL; }

echo $obj->$method($id);

}

function __autoload($class_name)

{

$filename = '../../../sys/class/class.'

. strtolower($class_name) . '.inc.php';

if ( file_exists($filename) )

{

include_once $filename;

}

}

?>

现在保存文件,加载http://localhost/,点击“添加新事件”按钮。一个新的模态窗口将淡入,编辑表单在里面(见图 8-1 )。

A978-1-4842-1230-1_8_Fig1_HTML.jpg

图 8-1。

The event creation form loaded in a modal window

使取消按钮的行为像关闭按钮一样

您可能已经注意到,当显示窗体时,模式窗口不包含关闭按钮。但是,模式窗口包含一个取消按钮,单击该按钮将刷新页面。不需要在窗口中添加更多的按钮,只需要让 Cancel 按钮调用fx.boxout()方法来关闭窗口。

为了实现这一点,使用.on()将一个 click 事件处理程序绑定到带有edit-form类的表单中包含单词 cancel 的任何链接。

"use strict";

jQuery(function($){

var processFile = "assets/inc/ajax.inc.php",

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...}

}

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){...});

// Displays the edit form as a modal window

$("body").on("click", ".admin", function(event){

// Make the cancel button on editing forms behave like the

// close button and fade out modal windows and overlays

$("body").on("click", ".edit-form a:contains(cancel)", function(event){

fx.boxout(event);

});

});

保存文件,重新加载http://localhost/,点击“添加新事件”按钮。模式窗口加载后,点击表单中的Cancel链接。模式窗口和覆盖将淡出,就像单击关闭按钮时一样。

在数据库中保存新事件

要使表单正常工作,现在必须向表单上的提交按钮添加一个 click 事件处理程序。这个处理程序将阻止默认的表单提交,然后它将使用.serialize()从表单输入中创建一个查询字符串。然后,它将使用序列化数据通过POSTajax.inc.php提交表单。

首先,向存在于带有edit-form类的表单中的submit类型的任何输入添加一个新的点击处理程序。使用.on()确保动态创建的输入仍然是处理程序的目标。您可以使用event.preventDefault()阻止默认动作。

通过将粗体代码插入init.js来完成:

"use strict";

jQuery(function($){

var processFile = "assets/inc/ajax.inc.php",

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...}

}

// Pulls up events in a modal window

$("body").on("click", "li>a", function(event){...});

// Displays the edit form as a modal window

$("body").on("click", ".admin", function(event){

// Make the cancel button on editing forms behave like the

// close button and fade out modal windows and overlays

$("body").on("click", ".edit-form a:contains(cancel)", function(event){

fx.boxout(event);

});

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function(event){

// Prevents the default form action from executing

event.preventDefault();

// Logs a message to indicate the script is working

console.log( "Form submission triggered!" );

});

});

接下来,在浏览器中保存并重新加载日历。单击“添加新事件”按钮打开模式窗口,然后单击“创建新事件”按钮提交表单。这会将以下结果输出到控制台:

Form submission triggered!

序列化表单数据

要将事件数据发送到处理文件,您需要将数据转换为查询字符串。幸运的是,jQuery 有一个名为.serialize()的内置方法来做这件事。它将把表单输入转换成一串名称-值对,用一个&符号(&)隔开。

修改init.js通过选择点击输入的父表单来序列化表单数据,然后序列化其数据。接下来,暂时将输出记录到控制台:

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function(event){

// Prevents the default form action from executing

event.preventDefault();

// Serializes the form data for use with $.ajax()

var formData = $(this).parents("form").serialize();

// Logs a message to indicate the script is working

console.log( formData );

});

保存前面的代码,并在浏览器中打开事件创建表单。现在输入以下测试数据:

  • 事件标题:测试事件
  • 活动开始时间:2016-01-04 08:00:00
  • 活动结束时间:2016-01-04 10:00:00
  • 事件描述:这是一个测试描述。

单击“创建新事件”按钮提交表单,并将以下内容输出到您的控制台(令牌值会有所不同):

event_title=Test+Event&event_start=2016-01-04+08%3A00%3A00&event_end=2016 A978-1-4842-1230-1_8_Figa_HTML.jpg

-01-04+10%3A00%3A00&event_description=This+is+a+test+description.&event_id=&token= A978-1-4842-1230-1_8_Figa_HTML.jpg

861e58daa0cfcf2c215f71d6f2bda1661e81c4c0&action=event_edit

将序列化的表单数据提交到处理文件

既然表单数据已经序列化,您就可以使用$.ajax()将数据发送到处理文件。

使用POST方法将序列化数据提交给ajax.inc.php,然后淡出模态窗口,并在成功提交时使用fx.boxout()进行覆盖。此外,在 Firebug 控制台中记录一条确认消息,并将以下粗体代码附加到init.js:

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function(event){

// Prevents the default form action from executing

event.preventDefault();

// Serializes the form data for use with $.ajax()

var formData = $(this).parents("form").serialize();

// Sends the data to the processing file

$.ajax({

type: "POST",

url: processFile,

data: formData,

success: function(data) {

// Fades out the modal window

fx.boxout();

// Logs a message to the console

console.log( "Event saved!" );

},

error: function(msg) {

alert(msg);

}

});

});

此时,脚本已经准备好保存新事件。但是您还没有准备好运行这段代码。首先,需要修改ajax.inc.php来接受这个数据。

修改 AJAX 处理文件以处理新的提交

ajax.inc.php准备好接受来自事件编辑表单的提交就像向查找数组添加一个新元素一样简单:

<?php

declare(strict_types=1);

/*

* Enable sessions if needed.

* Avoid pesky warning if session already active.

*/

$status = session_status();

if ($status == PHP_SESSION_NONE){

//There is no active session

session_start();

}

/*

* Include necessary files

*/

include_once '../../../sys/config/db-cred.inc.php';

/*

* Define constants for config info

*/

foreach ( $C as $name => $val )

{

define($name, $val);

}

/*

* Create a lookup array for form actions

*/

define(ACTIONS, array(

'event_view' => array(

'object' => 'Calendar',

'method' => 'displayEvent'

),

'edit_event' => array(

'object' => 'Calendar',

'method' => 'displayForm'

),

'event_edit' => array(

'object' => 'Calendar',

'method' => 'processForm'

)

)

);

/*

* Make sure the anti-CSRF token was passed and that the

* requested action exists in the lookup array

*/

if ( isset(ACTIONS[$_POST['action']]) )

{

$use_array = ACTIONS[$_POST['action']];

$obj = new $use_array'object';

$method = $use_array['method'];

/*

* Check for an ID and sanitize it if found

*/

if ( isset($_POST['event_id']) )

{

$id = (int) $_POST['event_id'];

}

else { $id = NULL; }

echo $obj->$method($id);

}

function __autoload($class_name)

{

$filename = '../../../sys/class/class.'

. strtolower($class_name) . '.inc.php';

if ( file_exists($filename) )

{

include_once $filename;

}

}

?>

保存该文件并重新加载http://localhost/。接下来,单击“添加新事件”按钮,在模式窗口中打开表单,然后输入一个包含以下信息的新事件:

  • 事件标题:测试事件
  • 活动开始时间:2016-01-04 08:00:00
  • 活动结束时间:2016-01-04 10:00:00
  • 事件描述:这是一个测试描述。

现在点击“创建新事件”按钮;模式窗口将淡出,并且控制台中将记录以下消息:

Event saved!

请注意,除非页面刷新,否则新事件不会出现在日历中。这可能会让用户感到困惑,因此在下一节中,您将修改应用,以便在成功保存后将新创建的事件添加到日历中。

添加事件而不刷新

将新事件添加到日历中相当复杂;保存事件后,您需要采取以下步骤。

Deserialize the form data.   Create date objects for both the currently displayed month and the new event.   Make sure the month and year match up for the new event.   Get the new event’s ID.   Determine on what day of the month the event falls.   Generate a new link with the proper event data and insert it into the corresponding calendar day.  

这个功能将被包含在一个新添加的名为addeventfx对象中,它将接受从ajax.inc.php ( data)返回的数据,以及序列化的表单数据(formData)。

首先,通过插入以下粗体代码修改init.js中的fx对象文字:

"use strict";

jQuery(function($){

var processFile = "assets/inc/ajax.inc.php",

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {... },

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Code to add the event goes here

}

};

$("body").on("click", "li>a", function(event){...});

$("body").on("click", ".admin", function(event){...});

$("body").on("click", ".edit-form a:contains(cancel)", function(event){...});

$("body").on("click", ".edit-form input[type=submit]", function(event){...});

反序列化表单数据

添加新事件的第一步是反序列化表单数据。因为这个动作是独立的,所以您将通过在接受字符串(str)的名为deserializefx对象文本中创建一个附加函数来处理这个步骤:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Code to add the event goes here

},

// Deserializes the query string and returns

// an event object

"deserialize" : function(str){

// Deserialize data here

}

};

正如您在本书前面所学的,序列化的字符串是一系列由等号(=)连接并由&符号(&)分隔的名称-值对。两个序列化名称-值对的示例可能如下所示:

name1=value1&name2=value2

要反序列化这些值,首先使用本地 JavaScript 函数.split()在每个&符号处拆分字符串。此函数将字符串分解为一个名称-值对数组:

Array

(

0 => "name1=value1",

1 => "name2=value2"

)

接下来,您需要遍历名称-值对数组。在这个循环中,在等号处分割数组,并将数组存储在一个名为pairs的变量中。这意味着每个名称-值对被分成一个数组,第一个索引包含名称,第二个索引包含值。该数组遵循以下格式:

Array

(

0 => "name1",

1 => "value1"

)

将这些值分别存储在名为keyval的变量中,然后作为属性存储在名为entry的新对象中。

当循环完成时,返回反序列化的data对象。

接下来,在fx.deserialize中添加以下粗体代码:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Code to add the event goes here

},

// Deserializes the query string and returns

// an event object

"deserialize" : function(str){

// Breaks apart each name-value pair

var data = str.split("``&

// Declares variables for use in the loop

pairs=[], entry={}, key, val;

// Loops through each name-value pair

for (var x in data )

{

// Splits each pair into an array

pairs = data[x].split("=");

// The first element is the name

key = pairs[0];

// Second element is the value

val = pairs[1];

// Stores each value as an object property

entry[key] = val;

}

return entry;

}

};

解码表单值中任何 URL 编码的字符

fx.deserialize正式投入使用之前,您必须首先修改它来解码任何 URL 编码的实体。当数据被序列化时,字符串值被编码,以便它们可以在查询字符串中传递。这意味着字符串“我正在测试&日志记录!”序列化时将转换为以下内容:

I’m+testing+%26+logging!

要扭转这种情况,使用正则表达式/\+/g将所有加号(+)替换为空格;该表达式只匹配加号。表达式结束分隔符后面的g使正则表达式进行全局搜索,因此不止一个匹配将被替换。

接下来,您需要使用本机的独立 JavaScript 函数decodeURIComponent()。在fx中创建一个名为urldecode的新函数,并插入以下代码:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Code to add the event goes here

},

// Deserializes the query string and returns

// an event object

"deserialize" : function(str){

// Breaks apart each name-value pair

var data = str.split("&"),

// Declares variables for use in the loop

pairs=[], entry={}, key, val;

// Loops through each name-value pair

for ( x in data )

{

// Splits each pair into an array

pairs = data[x].split("=");

// The first element is the name

key = pairs[0];

// Second element is the value

val = pairs[1];

// Stores each value as an object property

entry[key] = val;

}

return entry;

},

// Decodes a query string value

"urldecode" : function(str) {

// Converts plus signs to spaces

var converted = str.replace(/\+/g, ' ');

// Converts any encoded entities back

return decodeURIComponent(converted);

}

};

接下来,通过添加以下粗体代码来实现fx.deserialize中的fx.urldecode:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Code to add the event goes here

},

// Deserializes the query string and returns

// an event object

"deserialize" : function(str){

// Breaks apart each name-value pair

var data = str.split("&"),

// Declares variables for use in the loop

pairs=[], entry={}, key, val;

// Loops through each name-value pair

for ( x in data )

{

// Splits each pair into an array

pairs = data[x].split("=");

// The first element is the name

key = pairs[0];

// Second element is the value

val = pairs[1];

// Reverses the URL encoding and stores

// each value as an object property

entry[key] = fx.urldecode(val);

}

return entry;

},

"urldecode" : function(str) {...}

};

将这一切结合在一起

有了fx.deserializefx.urldecode之后,您现在可以通过添加一个变量(entry)来存储反序列化的事件数据,从而修改fx.addevent:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Converts the query string to an object

var entry = fx.deserialize(formData);

},

"deserialize" : function(str){...},

"urldecode" : function(str) {...}

};

创建日期对象

因为只有为正在显示的月份创建的事件才应该添加到日历中,所以您需要确定正在显示的月份和年份,以及事件发生的月份和年份。

Note

对于这一步,您将利用 JavaScript 的内置Date对象,它提供了简化许多日期相关操作的方法。关于与Date对象相关的所有可用方法的完整解释,请访问 http://w3schools.com/jsref/jsref_obj_date.asp

使用 ID 修改日历类

要为当前显示的月份生成一个Date对象,您需要向在日历上方显示月份的h2元素添加一个 ID。为了确保跨浏览器的兼容性,用下面的粗体代码修改Calendar类中的buildCalendar()方法:

public function buildCalendar()

{

/*

* Determine the calendar month and create an array of

* weekday abbreviations to label the calendar columns

*/

$cal_month = date('F Y', strtotime($this->_useDate));

$cal_id = date('Y-m', strtotime($this->_useDate));

define('WEEKDAYS', array('Sun', 'Mon', 'Tue',

'Wed', 'Thu', 'Fri', 'Sat'));

/*

* Add a header to the calendar markup

*/

$html = "\n\t<h2 id=\"month-$cal_id\">$cal_month</h2>";

for ( $d=0, $labels=NULL; $d<7; ++$d )

{

$labels .= "\n\t\t<li>" . WEEKDAYS[$d] . "</li>";

}

$html .= "\n\t<ul class=\"weekdays\">"

. $labels . "\n\t</ul>";

// For brevity, the remainder of this method has been omitted

}

Note

对 ID 使用“month-”前缀意味着您符合 W3 标准,该标准规定元素 ID 必须以字母开头。

用 JavaScript 构建日期对象

为了确保新事件在当前月份内,创建两个空的Date对象:一个用于当前月份,一个用于新事件。

要设置当前月份的Date对象的值,使用.attr()方法从H2元素中检索 ID 属性,在连字符处将其拆分,并将其存储在cdata变量中。

对于新事件,在空格处拆分entry.event_start的值,并获取第一个数组元素(以YYYY-MM-DD的格式表示的日期)并将其存储在一个名为date的变量中。接下来,在连字符处分割信息,并将数组存储在一个名为edata的变量中。

要设置Date对象,使用来自cdataedata的数据分别设置calevent中的日期。

最后,用下面的粗体代码修改fx.addevent:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Converts the query string to an object

var entry = fx.deserialize(formData ),

// Makes a date object for current month

cal = new Date(NaN),

// Makes a date object for the new event

event = new Date(NaN),

// Extracts the calendar month from the H2 ID

cdata = $("h2").attr("id").split('-'),

// Extracts the event day, month, and year

date = entry.event_start.split(' ')[0],

// Splits the event data into pieces

edata = date.split('-');

// Sets the date for the calendar date object

cal.setFullYear(cdata[1], cdata[2], 1);

// Sets the date for the event date object

event.setFullYear(edata[0], edata[1], edata[2]);

},

"deserialize" : function(str){...},

"urldecode" : function(str) {...}

};

修复时区不一致

您没有向Date对象传递时间或时区,因此该对象将默认为格林威治标准时间午夜(00:00:00 GMT)。这可能会导致您的日期对于不同时区的用户表现出意外。为了解决这个问题,您需要使用两个内置的Date对象方法:.setMinutes().getTimezoneOffset()来根据时区偏移量调整日期。

. getTimezoneOffset()的返回值是 GMT 和用户所在时区的分钟数之差。例如,.getTimezoneOffset()在山地标准时间(-0700)的返回值是 420。

使用.setMinutes(),您可以将时区偏移量的值添加到Date对象中,这将把日期返回到给定日期的午夜,而不管用户处于哪个时区。

您可以使用以下粗体代码进行调整:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Converts the query string to an object

var entry = fx.deserialize(formData),

// Makes a date object for current month

cal = new Date(NaN),

// Makes a date object for the new event

event = new Date(NaN),

// Extracts the event day, month, and year

date = entry.event_start.split(' ')[0],

// Splits the event data into pieces

edata = date.split('-'),

// Extracts the calendar month from the H2 ID

cdata = $("h2").attr("id").split('-');

// Sets the date for the calendar date object

cal.setFullYear(cdata[1], cdata[2], 1);

// Sets the date for the event date object

event.setFullYear(edata[0], edata[1], edata[2]);

// Since the date object is created using

// GMT, then adjusted for the local time zone,

// adjust the offset to ensure a proper date

event.setMinutes(event.getTimezoneOffset());

},

"deserialize" : function(str){...},

"urldecode" : function(str) {...}

};

确保事件发生在当月

下一步是设置一个条件语句,确保只追加属于日历的事件。如果当前日历月和事件日期之间的年份和月份都匹配,您可以使用Date对象的. getDay()方法提取该月的日期。为了正确处理下一步,即向一位数日期添加前导零,您还需要将该值转换为字符串,这是通过将该值传递给String()来完成的。

一个月中的某一天需要有一个前导零来正确匹配日历。例如,如果返回的日期只有一位数字,您可以在日期前添加一个前导零。为此,请插入以下粗体代码:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Converts the query string to an object

var entry = fx.deserialize(formData),

// Makes a date object for current month

cal = new Date(NaN),

// Makes a date object for the new event

event = new Date(NaN),

// Extracts the event day, month, and year

date = entry.event_start.split(' ')[0],

// Splits the event data into pieces

edata = date.split('-'),

// Extracts the calendar month from the H2 ID

cdata = $("h2").attr("id").split('-');

// Sets the date for the calendar date object

cal.setFullYear(cdata[1], cdata[2], 1);

// Sets the date for the event date object

event.setFullYear(edata[0], edata[1], edata[2]);

// Since the date object is created using

// GMT, then adjusted for the local timezone,

// adjust the offset to ensure a proper date

event.setMinutes(event.getTimezoneOffset());

// If the year and month match, start the process

// of adding the new event to the calendar

if ( cal.getFullYear()==event.getFullYear()

&& cal.getMonth()==event.getMonth() )

{

// Gets the day of the month for event

var day = String(event.getDate());

// Adds a leading zero to 1-digit days

day = day.length==1 ? "0"+day : day;

}

},

"deserialize" : function(str){...},

"urldecode" : function(str) {...}

};

将事件附加到日历

您终于可以将新事件添加到日历中了。为此,创建一个新的锚元素,隐藏它,设置它的href属性,并使用事件的标题作为链接文本。

接下来,使用.delay(1000)设置一秒钟的延迟,并淡入新事件。

您可以通过添加以下粗体显示的代码来实现这一点:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

// Adds a new event to the markup after saving

"addevent" : function(data, formData){

// Converts the query string to an object

var entry = fx.deserialize(formData),

// Makes a date object for current month

cal = new Date(NaN),

// Makes a date object for the new event

event = new Date(NaN),

// Extracts the event day, month, and year

date = entry.event_start.split(' ')[0],

// Splits the event data into pieces

edata = date.split('-'),

// Extracts the calendar month from the H2 ID

cdata = $("h2").attr("id").split('-');

// Sets the date for the calendar date object

cal.setFullYear(cdata[1], cdata[2], 1);

// Sets the date for the event date object

event.setFullYear(edata[0], edata[1], edata[2]);

// Since the date object is created using

// GMT, then adjusted for the local timezone,

// adjust the offset to ensure a proper date

event.setMinutes(event.getTimezoneOffset());

// If the year and month match, start the process

// of adding the new event to the calendar

if ( cal.getFullYear()==event.getFullYear()

&& cal.getMonth()==event.getMonth() )

{

// Gets the day of the month for event

var day = String(event.getDate());

// Adds a leading zero to 1-digit days

day = day.length==1 ? "0"+day : day;

// Adds the new date link

$("<a>")

.hide()

.attr("href", "view.php?event_id="+data)

.text(entry.event_title)

.insertAfter($("strong:contains("+day+")"))

.delay(1000)

.fadeIn("slow");

}

},

"deserialize" : function(str){...},

"urldecode" : function(str) {...}

}

Note

到目前为止,data变量还没有定义。您将在下一节中解决这个问题。

现在,回到 Submit 按钮的 click 事件处理程序,使用下面的粗体代码修改$.ajax()函数的成功回调以执行fx.addevent():

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function (event){

// Prevents the default form action from executing

event.preventDefault();

// Serializes the form data for use with $.ajax()

var formData = $(this).parents("form").serialize();

// Sends the data to the processing file

$.ajax({

type: "POST",

url: processFile,

data: formData,

success: function(data) {

// Fades out the modal window

fx.boxout();

// Adds the event to the calendar

fx.addevent(data, formData);

},

error: function(msg) {

alert(msg);

}

});

});

保存该文件并重新加载http://localhost/。调出事件创建表单,用以下信息创建一个新事件:

  • 事件标题:加法测试
  • 活动开始时间:2016-01-09 12:00:00
  • 活动结束时间:2016-01-09 14:00:00
  • 事件描述:这是一个向日历动态添加新事件的测试。

提交表单会导致模式窗口淡出;一秒钟后,新的事件标题将淡入日历的适当位置(见图 8-2 )。

A978-1-4842-1230-1_8_Fig2_HTML.jpg

图 8-2。

The calendar after the new event is created

获取新事件的正确 ID

目前,新事件在创建后不刷新页面是不可见的。要看到这一点,请立即尝试单击您刚刚添加的新创建的“添加测试”链接。如果你从第四章开始就一直关注事态的发展,你会得到如图 8-3 所示的相当令人惊讶的结果。(如果您没有遵循,您可能会得到不同的结果,例如一个空的对话框,甚至是一个致命的错误!)

A978-1-4842-1230-1_8_Fig3_HTML.jpg

图 8-3。

Something has gone terribly wrong!

你很可能会问,这里究竟出了什么问题。没必要惊慌。如果您打开文件/sys/class/class.calendar.inc.php并检查processForm()方法,您会注意到在成功完成后,它返回TRUE。然后TRUE被解释为事件 ID,被强制转换为整数 1,于是元旦被取出。(如果您一直关注事态的发展,这个事件的 ID 将等于 1。)

修改事件创建方法以返回新的事件 id

为了解决这个新的事件 ID 问题,您只需要在processForm()方法中做一个小的调整。在这个方法中,修改return命令,使用 PDO 的lastInsertId()方法输出最后插入的行的 ID:

public function processForm()

{

/*

* Exit if the action isn’t set properly

*/

if ( $_POST['action']!='event_edit' )

{

return "The method processForm was accessed incorrectly";

}

/*

* Escape data from the form

*/

$title = htmlentities($_POST['event_title'], ENT_QUOTES);

$desc = htmlentities($_POST['event_description'], ENT_QUOTES);

$start = htmlentities($_POST['event_start'], ENT_QUOTES);

$end = htmlentities($_POST['event_end'], ENT_QUOTES);

/*

* If no event ID passed, create a new event

*/

if ( empty($_POST['event_id']) )

{

$sql = "INSERT INTO events``

(event_title, event_desc, event_start,

``event_end)

VALUES

(:title, :description, :start, :end)";

}

/*

* Update the event if it’s being edited

*/

else

{

/*

* Cast the event ID as an integer for security

*/

$id = (int) $_POST['event_id'];

$sql = "UPDATE events``

SET

``event_title=:title,

``event_desc=:description,

``event_start=:start,

``event_end=:end

WHERE event_id=$id";

}

/*

* Execute the create or edit query after binding the data

*/

try

{

$stmt = $this->db->prepare($sql);

$stmt->bindParam(":title", $title, PDO::PARAM_STR);

$stmt->bindParam(":description", $desc, PDO::PARAM_STR);

$stmt->bindParam(":start", $start, PDO::PARAM_STR);

$stmt->bindParam(":end", $end, PDO::PARAM_STR);

$stmt->execute();

$stmt->closeCursor();

/*

* Returns the ID of the event

*/

return $this->db->lastInsertId();

}

catch ( Exception $e )

{

return $e->getMessage();

}

}

完成上述更改后,保存该文件并在浏览器中重新加载http://localhost/。接下来,使用以下信息创建一个新事件:

  • 事件标题:ID 测试
  • 活动开始时间:2016-01-06 12:00:00
  • 活动结束时间:2016-01-06 16:00:00
  • 事件描述:此事件在创建后应该可以立即查看。

现在保存活动,标题将出现在日历上。点击标题,事件将加载到一个模态窗口中(见图 8-4 )。

A978-1-4842-1230-1_8_Fig4_HTML.jpg

图 8-4。

An event loaded immediately after creation

在模式窗口中编辑事件

在目前的状态下,你的应用离允许用户从模态窗口编辑事件只有一步之遥。现有的用于加载事件创建表单的 click 事件处理程序也可以用于事件编辑,只需稍加修改。

首先,展开选择器以包含任何具有 admin 类的元素;您可以通过包含以下粗体代码来实现这一点:

// Displays the edit form as a modal window

$("body").on("click", ".admin-options form,.admin", function(event){

// Prevents the form from submitting

event.preventDefault();

// Loads the action for the processing file

var action = "edit_event";

// Loads the editing form and displays it

$.ajax({

type: "POST",

url: processFile,

data: "action="+action,

success: function(data){

// Hides the form

var form = $(data).hide(),

// Make sure the modal window exists

modal = fx.initModal();

// Call the boxin function to create

// the modal overlay and fade it in

fx.boxin(null, modal);

// Load the form into the window,

// fades in the content, and adds

// a class to the form

form

.appendTo(modal)

.addClass("edit-form")

.fadeIn("slow");

},

error: function(msg){

alert(msg);

}

});

});

确定表单操作

在为单个事件显示的编辑控件中,按钮名称描述了该按钮采取的操作(例如,edit_event表示“编辑该事件”按钮,delete_event表示“删除该事件”按钮)。这些按钮将被ajax.inc.php用作提交的动作。

因为事件创建按钮没有按钮名,所以需要保留一个默认值(edit_event)。

要访问被点击按钮的名称,可以使用名为targetevent对象的属性。此属性包含对触发事件的元素的引用。使用 jQuery 选择事件目标,并使用.attr()检索其名称。

现在,使用以下粗体代码修改事件处理程序:

// Displays the edit form as a modal window

$("body").on("click", ".admin-options form,.admin", function(event){

// Prevents the form from submitting

event.preventDefault();

// Sets the action for the form submission

var action = $(event.target).attr("name") || "edit_event";

// Loads the editing form and displays it

$.ajax({

type: "POST",

url: processFile,

data: "action="+action,

success: function(data){

// Hides the form

var form = $(data).hide(),

// Make sure the modal window exists

modal = fx.initModal();

// Call the boxin function to create

// the modal overlay and fade it in

fx.boxin(null, modal);

// Load the form into the window,

// fades in the content, and adds

// a class to the form

form

.appendTo(modal)

.addClass("edit-form")

.fadeIn("slow");

},

error: function(msg){

alert(msg);

}

});

});

存储事件 ID(如果存在)

接下来,需要提取事件 ID,假设它是可用的。要查找这个值,再次使用event.target属性,但是这次查找名为event_id的兄弟元素,然后将这个值存储在名为id的变量中。使用下面的粗体代码将它添加到事件处理程序中:

// Displays the edit form as a modal window

$("body").on("click", ".admin-options form,.admin", function(event){

// Prevents the form from submitting

event.preventDefault();

// Sets the action for the form submission

var action = $(event.target).attr("name") || "edit_event" ,

// Saves the value of the event_id input

id = $(event.target)

.siblings("input[name=event_id]")

.val();

// Loads the editing form and displays it

$.ajax({

type: "POST",

url: processFile,

data: "action="+action,

success: function(data){

// Hides the form

var form = $(data).hide(),

// Make sure the modal window exists

modal = fx.initModal();

// Call the boxin function to create

// the modal overlay and fade it in

fx.boxin(null, modal);

// Load the form into the window,

// fades in the content, and adds

// a class to the form

form

.appendTo(modal)

.addClass("edit-form")

.fadeIn("slow");

},

error: function(msg){

alert(msg);

}

});

});

将事件 ID 添加到查询字符串

有了存储在id变量中的 ID,您现在可以将该值附加到查询字符串中,以便提交给ajax.inc.php

先检查id是否未定义,然后创建一个event_id名值对。接下来,使用以下粗体代码将数据附加到查询字符串:

// Displays the edit form as a modal window

$("body").on("click", ".admin-options form,.admin", function(event){

// Prevents the form from submitting

event.preventDefault();

// Sets the action for the form submission

var action = $(event.target).attr("name") || "edit_event",

// Saves the value of the event_id input

id = $(event.target)

.siblings("input[name=event_id]")

.val();

// Creates an additional param for the ID if set

id = ( id!=undefined ) ? "``&

// Loads the editing form and displays it

$.ajax({

type: "POST",

url: processFile,

data: "action="+action+id,

success: function(data){

// Hides the form

var form = $(data).hide(),

// Make sure the modal window exists

modal = fx.initModal();

// Call the boxin function to create

// the modal overlay and fade it in

fx.boxin(null, modal);

// Load the form into the window,

// fades in the content, and adds

// a class to the form

form

.appendTo(modal)

.addClass("edit-form")

.fadeIn("slow");

},

error: function(msg){

alert(msg);

}

});

});

从模式窗口中移除事件数据

若要用编辑窗体替换模式窗口的内容,必须首先移除事件显示信息。

在成功处理程序中调用了fx.initModal()的地方,选择所有不属于关闭按钮的子按钮并删除它们。移除它们之后,调用.end()恢复到模态窗口的初始选择。(调用子元素后,jQuery 对象只引用您刚刚删除的子元素。)

您可以通过添加以下粗体代码来实现这一点:

// Displays the edit form as a modal window

$("body").on("click", ".admin-options form,.admin", function(event){

// Prevents the form from submitting

event.preventDefault();

// Sets the action for the form submission

var action = $(event.target).attr("name") || "edit_event",

// Saves the value of the event_id input

id = $(event.target)

.siblings("input[name=event_id]")

.val();

// Creates an additional param for the ID if set

id = ( id!=undefined ) ? "&event_id="+id : "";

// Loads the editing form and displays it

$.ajax({

type: "POST",

url: processFile,

data: "action="+action+id,

success: function(data){

// Hides the form

var form = $(data).hide(),

// Make sure the modal window exists

modal = fx.initModal()

.children(":not(.modal-close-btn)")

.remove()

.end();

// Call the boxin function to create

// the modal overlay and fade it in

fx.boxin(null, modal);

// Load the form into the window,

// fades in the content, and adds

// a class to the form

form

.appendTo(modal)

.addClass("edit-form")

.fadeIn("slow");

},

error: function(msg){

alert(msg);

}

});

});

保存该文件并在浏览器中重新加载http://localhost/后,点击New Year’s Day事件标题,调出事件描述。在模式窗口中,单击“编辑此事件”按钮;这导致事件描述消失,编辑表单将淡入,条目的数据加载到表单中进行编辑(参见图 8-5 )。

A978-1-4842-1230-1_8_Fig5_HTML.jpg

图 8-5。

Editing an event in a modal window

确保日历中只添加新事件

如果您对元旦活动进行编辑并保存,一个额外的活动标题将被添加到日历中(参见图 8-6 )。

A978-1-4842-1230-1_8_Fig6_HTML.jpg

图 8-6。

After you edit an event, its title is duplicated

如果刷新页面,将会消除重复。

为了防止这种重复问题,您需要向表单提交点击处理程序添加一个额外的调整。因为正在编辑的事件将把它们的 ID 加载到名为event_id的编辑表单的隐藏输入中,所以您可以检查输入值的长度。如果长度不为零,就不要调用fx.addevent()

插入以下粗体代码进行检查:

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function (event){

// Prevents the default form action from executing

event.preventDefault();

// Serializes the form data for use with $.ajax()

var formData = $(this).parents("form").serialize();

// Sends the data to the processing file

$.ajax({

type: "POST",

url: processFile,

data: formData,

success: function(data) {

// Fades out the modal window

fx.boxout();

// If this is a new event, adds it to

// the calendar

if ( $("[name=event_id]").val().length==0 )

{

fx.addevent(data, formData);

}

},

error: function(msg) {

alert(msg);

}

});

});

事件编辑功能就要完成了。但是,您可能已经注意到了一个遗留问题。如果事件标题发生了变化,那么在页面没有刷新的情况下,新标题不会出现。(如果您还没有尝试过,现在就尝试一下。)幸运的是,您可以很容易地解决这个问题。您只需要将主日历页面上显示的标题与提交时表单中包含的(可能)不同的标题进行比较。

插入以下粗体代码来实现此逻辑:

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function (event){

// Prevents the default form action from executing

event.preventDefault();

// If editing an existing event, need to pay attention to title.

if ( $(this).attr("name")=="event_submit"``&&

{

// Need to check if the event title has been changed.

// Here’s the title that’s on the main calendar page.

var oldTitle = $(".active")[0].innerHTML;

// Here we fish out the (possibly) different title from the form data.

var formArray = $(this).parents("form").serializeArray();

var titleArray = $.grep(formArray, function(elem) {

return elem.name === 'event_title';

});

var newTitle = titleArray.length > 0 ? titleArray[0].value : "";

if (newTitle !== oldTitle)

{

// The event title has been changed, so update the page.

$(".active")[0].innerHTML = newTitle;

}

}

// Serializes the form data for use with $.ajax()

var formData = $(this).parents("form").serialize();

// Sends the data to the processing file

$.ajax({

type: "POST",

url: processFile,

data: formData,

success: function(data) {

// Fades out the modal window

fx.boxout();

// If this is a new event, adds it to

// the calendar

if ( $("[name=event_id]").val().length==0 )

{

fx.addevent(data, formData);

}

},

error: function(msg) {

alert(msg);

}

});

});

有了这些更改,您的用户现在可以编辑事件,而不会在页面上看到潜在的令人困惑的重复标题或过时标题。

在模式窗口中确认删除

为了完善您的应用,您还将允许用户在不刷新页面的情况下删除条目。完成这项工作所需的大部分脚本已经准备好了,所以添加这项功能主要需要对现有代码进行调整。

显示确认对话框

要在点击“删除该事件”按钮时显示事件删除的确认对话框,您需要向ajax.inc.php中的查找数组添加一个额外的元素:

<?php

declare(strict_types=1);

/*

* Enable sessions if needed.

* Avoid pesky warning if session already active.

*/

$status = session_status();

if ($status == PHP_SESSION_NONE){

//There is no active session

session_start();

}

/*

* Include necessary files

*/

include_once '../../../sys/config/db-cred.inc.php';

/*

* Define constants for config info

*/

foreach ( $C as $name => $val )

{

define($name, $val);

}

/*

* Create a lookup array for form actions

*/

define(ACTIONS, array((

'event_view' => array(

'object' => 'Calendar',

'method' => 'displayEvent'

),

'edit_event' => array(

'object' => 'Calendar',

'method' => 'displayForm'

),

'event_edit' => array(

'object' => 'Calendar',

'method' => 'processForm'

),

'delete_event' => array(

'object' => 'Calendar',

'method' => 'confirmDelete'

)

)

);

/*

* Make sure the anti-CSRF token was passed and that the

* requested action exists in the lookup array

*/

if ( isset(ACTIONS[$_POST['action']]) )

{

$use_array = ACTIONS[$_POST['action']];

$obj = new $use_array'object';

$method = $use_array['method'];

/*

* Check for an ID and sanitize it if found

*/

if ( isset($_POST['event_id']) )

{

$id = (int) $_POST['event_id'];

}

else { $id = NULL; }

echo $obj->$method($id);

}

function __autoload($class_name)

{

$filename = '../../../sys/class/class.'

. strtolower($class_name) . '.inc.php';

if ( file_exists($filename) )

{

include_once $filename;

}

}

?>

此时点击模式窗口中的“删除该事件”按钮,将出现确认对话框(见图 8-7 )。

A978-1-4842-1230-1_8_Fig7_HTML.jpg

图 8-7。

The confirmation dialog to delete an event displayed in a modal window

为删除配置表单提交事件处理程序

确认事件删除需要对init.js稍加修改。为了正确执行,需要存储 Submit 按钮的值并将其传递给处理文件。这是因为表单可以用Yes, Delete ItNope! Just Kidding!作为值提交;该脚本检查哪个按钮被点击,以确定采取什么行动。

要存储按钮的值,使用this关键字作为 jQuery 选择器,然后将从.val()返回的字符串存储为一个名为submitVal的变量。接下来,检查按钮的name属性是否为confirm_delete。如果是这样,在提交之前将动作confirm_delete和按钮的值附加到查询字符串中。

插入以粗体显示的以下代码来完成此操作:

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function (event){

// Prevents the default form action from executing

event.preventDefault();

// If editing an existing event, need to pay attention to title.

if ( $(this).attr("name")=="event_submit" && $(".active").length > 0 )

{

// Need to check if the event title has been changed.

// Here’s the title that’s on the main calendar page.

var oldTitle = $(".active")[0].innerHTML;

// Here we fish out the (possibly) different title from the form data.

var formArray = $(this).parents("form").serializeArray();

var titleArray = $.grep(formArray, function(elem) {

return elem.name === 'event_title';

});

var newTitle = titleArray.length > 0 ? titleArray[0].value : "";

if (newTitle !== oldTitle)

{

// The event title has been changed, so update the page.

$(".active")[0].innerHTML = newTitle;

}

}

// Serializes the form data for use with $.ajax()

var formData = $(this).parents("form").serialize() ,

// Stores the value of the submit button

submitVal = $(this).val();

// If this is the deletion form, appends an action

if ( $(this).attr("name")=="confirm_delete" )

{

// Adds necessary info to the query string

formData += "``&

+ "``&

}

// Sends the data to the processing file

$.ajax({

type: "POST",

url: processFile,

data: formData,

success: function(data) {

// Fades out the modal window

fx.boxout();

// If this is a new event, adds it to

// the calendar

if ( $("[name=event_id]").val().length==0 )

{

fx.addevent(data, formData);

}

},

error: function(msg) {

alert(msg);

}

});

});

修改处理文件以确认删除

最后,您需要向ajax.inc.php中的查找数组添加一个额外的元素,以使删除按钮工作:

<?php

declare(strict_types=1);

/*

* Enable sessions if needed.

* Avoid pesky warning if session already active.

*/

$status = session_status();

if ($status == PHP_SESSION_NONE){

//There is no active session

session_start();

}

/*

* Include necessary files

*/

include_once '../../../sys/config/db-cred.inc.php';

/*

* Define constants for config info

*/

foreach ( $C as $name => $val )

{

define($name, $val);

}

/*

* Create a lookup array for form actions

*/

define(ACTIONS, array((

'event_view' => array(

'object' => 'Calendar',

'method' => 'displayEvent'

),

'edit_event' => array(

'object' => 'Calendar',

'method' => 'displayForm'

),

'event_edit' => array(

'object' => 'Calendar',

'method' => 'processForm'

),

'delete_event' => array(

'object' => 'Calendar',

'method' => 'confirmDelete'

),

'confirm_delete' => array(

'object' => 'Calendar',

'method' => 'confirmDelete'

)

)

);

/*

* Make sure the anti-CSRF token was passed and that the

* requested action exists in the lookup array

*/

if ( isset(ACTIONS[$_POST['action']]) )

{

$use_array = ACTIONS[$_POST['action']];

$obj = new $use_array'object';

$method = $use_array['method'];

/*

* Check for an ID and sanitize it if found

*/

if ( isset($_POST['event_id']) )

{

$id = (int) $_POST['event_id'];

}

else { $id = NULL; }

echo $obj->$method($id);

}

function __autoload($class_name)

{

$filename = '../../../sys/class/class.'

. strtolower($class_name) . '.inc.php';

if ( file_exists($filename) )

{

include_once $filename;

}

}

?>

您可以通过从日历中删除 ID 测试事件来测试前面的代码。在模态窗口淡出后,事件标题仍然存在并可点击;但是,如果您试图查看事件的详细信息,则它的信息不可用,并且没有意义(参见图 8-8 )。

A978-1-4842-1230-1_8_Fig8_HTML.jpg

图 8-8。

Because the event no longer exists, the event view makes no sense

删除后从日历中移除事件

您希望避免由于用户删除日历上不存在的事件而造成的混乱,因此您需要添加功能,以便在发生这种情况时从日历中删除事件。

为此,向名为removeeventfx对象文字添加一个新函数。当事件出现在模态窗口时,这个函数将使用应用于事件的active类来淡出事件并将其从 DOM 中移除。您可以使用以下粗体代码将该功能添加到fx:

fx = {

"initModal" : function() {...},

"boxin" : function(data, modal) {...},

"boxout" : function(event) {...},

"addevent" : function(data, formData){...},

// Removes an event from the markup after deletion

"removeevent" : function()

{

// Removes any event with the class "active"

$(".active")

.fadeOut("slow", function(){

$(this).remove();

});

},

"deserialize" : function(str){...},

"urldecode" : function(str) {...}

};

修改表单提交处理程序以删除已删除的事件

要在事件被删除后移除它们,需要向表单提交事件处理程序添加一个名为remove的新变量。这将存储一个布尔值,告诉脚本是否删除一个事件。默认情况下,该值将被设置为false,这意味着该事件不应被删除。

删除事件的唯一条件是在确认对话框中点击“是,删除”按钮。在 Submit 按钮中添加对该文本的检查,如果匹配,则将remove设置为true

在成功处理程序中,设置一个条件来检查remove是否为true,如果是,则触发fx.removeevent()

最后,为了防止空元素被添加到日历中,修改触发fx.addevent()的条件,以确保在执行之前removefalse

您可以通过添加以粗体显示的代码来进行这些更改:

// Edits events without reloading

$("body").on("click", ".edit-form input[type=submit]", function (event){

// Prevents the default form action from executing

event.preventDefault();

// If editing an existing event, need to pay attention to title.

if ( $(this).attr("name")=="event_submit" && $(".active").length > 0 )

{

// Need to check if the event title has been changed.

// Here’s the title that’s on the main calendar page.

var oldTitle = $(".active")[0].innerHTML;

// Here we fish out the (possibly) different title from the form data.

var formArray = $(this).parents("form").serializeArray();

var titleArray = $.grep(formArray, function(elem) {

return elem.name === 'event_title';

});

var newTitle = titleArray.length > 0 ? titleArray[0].value : "";

if (newTitle !== oldTitle)

{

// The event title has been changed, so update the page.

$(".active")[0].innerHTML = newTitle;

}

}

// Serializes the form data for use with $.ajax()

var formData = $(this).parents("form").serialize(),

// Stores the value of the submit button

submitVal = $(this).val(),

// Determines if the event should be removed

remove = false;

// If this is the deletion form, appends an action

if ( $(this).attr("name")=="confirm_delete" )

{

// Adds necessary info to the query string

formData += "&action=confirm_delete"

+ "&confirm_delete="+submitVal;

// If the event is really being deleted, sets

// a flag to remove it from the markup

if ( submitVal=="Yes, Delete It" )

{

remove = true;

}

}

// Sends the data to the processing file

$.ajax({

type: "POST",

url: processFile,

data: formData,

success: function(data) {

// If this is a deleted event, removes

// it from the markup

if ( remove===true )

{

fx.removeevent();

}

// Fades out the modal window

fx.boxout();

// If this is a new event, adds it to

// the calendar

if ( $("[name=event_id]").val().length== 0

&& remove===false )

{

fx.addevent(data, formData);

}

},

error: function(msg) {

alert(msg);

}

});

});

保存这些更改,重新加载http://localhost/,并调出测试事件描述。删除该事件;在你点击“是,删除”按钮后,模态框和事件标题将淡出,有效地从日历中消除事件,并为你的用户消除任何潜在的混淆(见图 8-9 )。

A978-1-4842-1230-1_8_Fig9_HTML.jpg

图 8-9。

After deleting Test Event, the event title is removed from the calendar

摘要

在本章中,您实现了允许用户快速创建、编辑和删除事件而无需刷新页面的控件。这使得应用感觉更加精简和用户友好。

在下一章中,您将学习如何使用正则表达式来确认编辑表单中输入的数据是有效的,确保您的应用不允许可能破坏它的数据输入到数据库中。