dojo toolkit_使用Dojo Toolkit构建Ajax应用程序

dojo toolkit

在你开始前

发展有关此主题的技能

此内容是用于提高技能的渐进知识路径的一部分。 请参阅Dojo开发入门

本教程适用于有兴趣使用Dojo Toolkit相对轻松地创建视觉上令人印象深刻的RIA的Web应用程序开发人员。 本教程将指导您完成使用Dojo开发完整的Web应用程序的过程,尽管您将通过它学习很多关于Dojo的知识,但是建议您在继续操作之前熟悉Dojo的基础知识。 您还应该熟悉HTML和CSS,并且应该熟悉JavaScript开发。 您不需要成为专家,但是您对这些主题的了解越多,本教程就越容易被遵循。 有关详细介绍道场,请参阅相关信息的链接,一些介绍性文章。

关于本教程

Dojo工具包是一个功能强大JavaScript框架,提供了构建令人印象深刻的桌面样式RIA所需的所有构建块。 该框架的基础包括各种有用的功能,这些功能使编写JavaScript应用程序的过程变得更加容易,包括DOM查询和操作,效果和动画,Ajax事件处理等等。 但是,Dojo在其独一无二的窗口小部件系统中与众不同的地方是Dijit,该系统包括强大的解析器,可让您像使用常规HTML元素一样使用Dijit组件。 最后,Dojo还包括DojoX中的扩展套件,这些扩展提供了其他功能,包括对各种数据存储,数据网格,其他Dijit组件等的支持。

在本教程中,您将在类似于桌面联系人管理器应用程序的示例应用程序中实现工具包的这三个部分。 此应用程序将允许您通过添加新联系人以及编辑或删除现有联系人来管理您的联系人。 您可以将联系人分为几组,并根据需要在各组之间移动它们。 还可以添加,修改和删除组本身。 想法是,本教程将向您展示如何创建具有相对简单性和比您期望的少得多的代码的功能齐全的,动态的,数据库驱动的应用程序。 您可以轻松扩展或修改在本教程中创建的应用程序,以创建自己的项目。

先决条件

要遵循本教程,您将需要以下内容:

  • 支持PHP的Web服务器,例如Apache
  • PHP,版本5或更高版本
  • MySQL,版本4或更高版本(较早的版本可能适用)
  • Dojo Toolkit 1.5或更高版本(可以使用Google Content Delivery Network加载。或者,您可以在本地下载该工具包,然后从您的开发计算机上运行它。)

此应用程序中使用PHP脚本和MySQL表非常简单,因此它们应该相对容易地转换为其他服务器端语言,例如Java™代码,C#,Python等,以及数据库(例如DB2®,Oracle)和SQL Server。 该应用程序中的大部分工作由Dojo完成,PHP和MySQL仅用于持久性。

请参阅相关主题的链接,这些工具。

配置

本教程将指导您完成功能丰富的Dojo Web应用程序的开发。 本节介绍了本教程中介绍的Dojo Toolkit的各种概念,并提供了本教程将教您开发的示例应用程序的导览。

本教程中使用的Dojo概念

在学习本教程时,您将实践Dojo工具箱的各种功能。 以下各节概述了工具包不同部分(Base,Dijit和Dojox)中的功能。

基础

Dojo Toolkit的核心是许多JavaScript库共有的基本功能集。 这些功能通常包括DOM查询和操作功能,使开发人员能够进行跨浏览器友好的Ajax请求,事件处理,数据API等的功能。 在本教程中,您将使用Dojo Base的许多功能,包括:

  • DOM查询
  • DOM操作
  • XmlHttpRequests (XHR / Ajax)
  • 事件处理( dojo.connect
  • 读/写数据存储
迪吉特

Dojo Toolkit最具特色的方面之一是Dijit组件库,它使您能够以最小的工作量和大惊小怪的方式创建功能强大且外观精美的用户界面。 此外,借助Dojo强大的小部件解析功能,Dijit允许您以声明性方式使用JavaScript组件。 本教程实现了多种Dijit组件,包括:

  • 布局组件( BorderContainersContentPanes
  • 菜单(应用程序和上下文)
  • 对话方块
  • 表单, ValidationTextBoxesTextBoxesFilteringSelects和按钮
DojoX

尽管大多数Web应用程序所需的许多功能都包含在Dojo Toolkit的其他部分中,但是有时您需要开箱即用的其他功能。 对于其他库,您通常需要寻找第三方插件才能使用这些功能。 Dojo的社区驱动的DojoX扩展库意味着您可以访问许多稳定且实验性的其他组件,而无需查看其他地方。 在本教程中,我使用以下DojoX组件:DojoX。

示例应用程序-基本的联系人管理应用程序

本教程的重点是创建功能全面的示例应用程序,其中包括各种CRUD(创建,更新,删除)操作,以类似于潜在的实际应用程序。 最终结果是具有类似桌面性能的基于Web的联系人管理系统。 图1显示了最终产品在Web浏览器中的外观。

图1.最终应用程序主应用程序
三列包含名字,姓氏和电子邮件地址的列表。一个框会在其上覆盖编辑,移动或删除联系人。

在应用程序的顶部,有一个应用程序菜单,带有“文件”和“编辑”选项。 其中包括有关您可以执行的各种操作的子菜单,包括添加新的组和联系人,以及编辑和删除现有项目。

该应用程序的左侧是一棵树,其中显示了可用的不同组。 创建一个新组会导致将该组添加到此树中。 树上有一个上下文菜单,因此,右键单击树中的项目时,您会获得重命名或删除该组的选项。 选择(单击鼠标左键)组将在右侧从该组加载联系人。 如果选择根节点(无法删除的组),则每个联系人(无论其组)都显示在右侧。

应用程序的右侧分为两个视图,顶部显示当前所选组中联系人的网格视图,而下半部分显示当前所选联系人的详细信息。 网格再次具有上下文菜单,您可以通过右键单击联系人并选择适当的选项来编辑,移动或删除联系人。

该应用程序充分利用了弹出窗口来提供其他功能,特别是在创建,编辑和删除项目方面。 这样的一个例子如图2所示。

图2.对话框弹出示例:编辑联系人
一个带有各种字段的窗口,可让您编辑联系人的信息。

应用程序中的弹出窗口会在后台淡出主应用程序,并在应用程序窗口上方集中显示弹出窗口。 这些窗口通常采用带有组件的表单的形式,包括文本框(具有内置的“必需”验证)和自动完成的下拉列表。 删除项目时,将显示一个确认窗口,要求用户确认该项目的删除。 按确定将导致该项目被删除; 否则,该过程被取消。

我相信您会同意该应用程序看起来很像典型的桌面应用程序。 还包括其他功能,例如,能够调整应用程序左右两侧之间的分隔的大小以及右侧的网格和预览窗格的功能。 调整浏览器窗口大小时,应用程序将调整大小以占据整个窗口。 您应该能够使用此示例应用程序,并在其结构上进行构建以创建令人印象深刻的结果。

服务器端设置:数据库和PHP脚本

在本节中,您将建立一个新的MySQL数据库来存储联系人管理器应用程序的数据。 您还将设置一个PHP脚本,该脚本将管理与此数据库的连接,以及一系列PHP API,这些API将从MySQL提取数据并将其转换为JavaScript Object Notation(JSON)格式,以便于Dojo的XHR(Ajax)函数回调。

入门:MySQL数据库和服务器端配置

在开始使用Dojo之前,您需要设置应用程序的服务器端部分。 您需要做的第一件事是创建将构成应用程序数据层的MySQL用户,数据库,表和数据。 如果您具有MySQL的root访问权限(或至少具有创建用户和数据库的能力),则可以简单地运行本教程的源代码附带的install.sql脚本。 假设MySQL bin目录位于系统路径上,并且位于源代码文件夹中,则可以按以下方式运行此脚本:

$ mysql -u root -pMyPassword < install.sql

显然,您应该使用实际的MySQL根密码替换“ MyPassword”。 这将创建一个新的数据库用户,数据库,表和一些示例数据。 清单1显示了install.sql的内容。

清单1.用于设置MySQL数据库的install.sql脚本
create user 'dojo'@'localhost' identified by 'somepass';
create database dojocontacts;
grant all privileges on dojocontacts.* to 'dojo'@'localhost' with grant option;
use dojocontacts;

create table groups(
    id             int(11) auto_increment primary key,
    name             varchar(100) not null
) engine=INNODB;

create table contacts(
    id             int(11) auto_increment primary key,
    group_id        int(11) not null,
    first_name        varchar(100) not null,
    last_name        varchar(100) not null,
    email_address    varchar(255) not null,
    home_phone        varchar(100),
    work_phone        varchar(100),
    mobile_phone    varchar(100),
    twitter        varchar(255),
    facebook        varchar(255),
    linkedin        varchar(255),
    foreign key(group_id) references groups(id) on delete cascade
) engine=INNODB;

insert into groups(name) values('Family');
insert into groups(name) values('Friends');
insert into groups(name) values('Colleagues');
insert into groups(name) values('Others');

insert into contacts(group_id, first_name, last_name, email_address, home_phone, 
work_phone, twitter, facebook, linkedin)
values(3, 'Joe', 'Lennon', 'joe@joelennon.ie', '(555) 123-4567', '(555) 456-1237', 
'joelennon', 'joelennon', 'joelennon');

insert into contacts(group_id, first_name, last_name, email_address, home_phone, 
work_phone, twitter, facebook, linkedin)
values(1, 'Mary', 'Murphy', 'mary@example.com', '(555) 234-5678', '(555) 567-2348', 
'mmurphy', 'marym', 'mary.murphy');

insert into contacts(group_id, first_name, last_name, email_address, home_phone, 
work_phone, twitter, facebook, linkedin)
values(2, 'Tom', 'Smith', 'tsmith@example.com', '(555) 345-6789', '(555) 678-3459', 
'tom.smith', 'tsmith', 'smithtom');

insert into contacts(group_id, first_name, last_name, email_address, home_phone, 
work_phone, twitter, facebook, linkedin)
values(4, 'John', 'Cameron', 'jc@example.com', '(555) 456-7890', '(555) 789-4560', 
'jcameron', 'john.cameron', 'johnc');

insert into contacts(group_id, first_name, last_name, email_address, home_phone, 
work_phone, twitter, facebook, linkedin)
values(4, 'Ann', 'Dunne', 'anndunne@example.com', '(555) 567-8901', '(555) 890-5671',
'ann.dunne', 'adunne', 'dunneann');

insert into contacts(group_id, first_name, last_name, email_address, home_phone, 
work_phone, twitter, facebook, linkedin)
values(1, 'James', 'Murphy', 'james@example.com', '(555) 678-9012', '(555) 901-6782', 
'jmurphy', 'jamesmurphy', 'james.murphy');

使用PHP连接到数据库

创建MySQL数据库后,您现在可以创建PHP脚本,这些脚本将在这些数据库表中插入,更新和删除数据。

在此示例应用程序中,将仅使用Ajax或XHR请求来调用PHP脚本,并且将始终以JSON格式返回对请求对象的响应,但contact.php除外,后者实际上返回了插入到其中HTML代码段。 DOM。 所有PHP脚本都位于名为data的子目录中。 以下是此项目所需的脚本:

  • contact.php(检索给定联系人的详细联系信息)
  • contacts.php(检索数据库中联系人的JSON表示形式)
  • database.php(负责连接到MySQL)
  • delete_contact.php(从数据库中删除联系人)
  • delete_group.php(从数据库中删除组)
  • edit_contact.php(在数据库中添加/编辑联系人)
  • edit_group.php(重命名数据库中的组)
  • groups.php(检索数据库中组的JSON表示形式)
  • move_contact.php(将联系人移至其他组)
  • new_group.php(将新组添加到数据库)

由于篇幅所限,我无法在本教程中列出所有这些文件的内容,因此,有关完整的源代码清单,请参阅本教程随附的源代码 。 为了让您大致了解这些PHP脚本的工作方式,我将讨论其中的一些。

本教程中的所有其他PHP脚本都使用database.php文件打开与MySQL数据库的连接。 该文件非常简单; 其内容如清单2所示。

清单2. data / database.php文件内容
<?php
$db_host = "localhost";
$db_user = "dojo";
$db_pass = "somepass";
$db_name = "dojocontacts";

//Connect to MySQL
$conn = mysql_connect($db_host, $db_user, $db_pass);
//Select database
mysql_select_db($db_name, $conn);
?>

在PHP中创建JSON格式的API函数

在此应用程序中执行的操作主要有两种:检索数据和操纵数据。 让我们来看一个例子。 首先,groups.php负责从数据库中检索组列表,并使用Dijit Identity API结构表示该组,以便Dijit Tree小部件可以读取它。 清单3显示了该文件的内容。

清单3. data / groups.php文件内容
<?php
include_once("database.php");
//SQL statement to get all groups
$sql = "SELECT id, name, 'node' as type FROM groups ORDER BY id";
$result = mysql_query($sql, $conn) or die("Could not load groups");

//Always show "Groups" as root element
$data = array(
    'identifier' => 'id',
    'label' => 'name',
    'items' => array(
        array(
            'id' => 0,
            'name' => 'Groups',
            'type' => 'root'
        )
    )
);

//Retrieve groups and add to array
if(mysql_num_rows($result) > 0) {
    while($row = mysql_fetch_assoc($result)) {
        $data['items'][0]['groups'][] = array('_reference' => $row['id']);
        $data['items'][] = $row;
    }
}

//Output $data array as JSON
header('Content-Type: application/json; charset=utf8');
echo json_encode($data);
?>

Dijit Identity API要求JSON数据结构采用特定格式,并且属性identifierlabelitems位于顶层。 在清单3中,我默认添加了一个根Groups项。 这并不代表特定的组,但是将始终显示在树的顶部(即使没有组也是如此)。 然后,我遍历了SQL SELECT语句的结果,并将所有这些项添加为子元素。

首先,将每个项目添加到items数组,并且在根项目中添加了一个groups数组,其中包含具有_reference属性的对象集合,该_reference属性引用子项的ID。 最后,脚本采用它构建的$data数组并将其输出为JSON。 在本教程的稍后部分,您将创建一个数据存储以连接到该PHP脚本,该脚本将获取JSON输出并在Dijit Tree中相应地使用它。

我在这里讨论的第二个示例是move_contact.php,它将更新联系人记录,并根据需要设置group_id值。 清单4显示了此文件的代码。

清单4. data / move_contact.php文件内容
<?php
include_once("database.php");
//Get form values
$contact_id = $_POST['move_contact_id'];
$group_id = $_POST['move_contact_new'];
//Perform update
$sql = "UPDATE contacts SET group_id = ".mysql_real_escape_string($group_id, $conn)." WHERE id = ".mysql_real_escape_string($contact_id, $conn);
$result = mysql_query($sql) or die("Could not move contact in database");
//Check if performed via Ajax
if(mysql_affected_rows() > 0 && $_POST['move_contact_ajax'] == "0") {
    header("Location: ../index.html");
} else {
    //If Ajax, return JSON response
    header('Content-Type: application/json; charset=utf8');
    $data = array();
    //If rows affected, change was successful
    if(mysql_affected_rows() > 0) {
        $data['success'] = true;
        $data['id'] = $contact_id;
    } else {
        $data['success'] = false;
        $data['error'] = 'Error: could not move contact in database';
    }
    //Output array in JSON format
    echo json_encode($data);
}
?>

在清单4中,需要两个POST参数,即联系人ID和应将联系人移动到的新组ID。 然后将这些插入到SQL UPDATE语句中。 通过Ajax发出请求时,将使用一个参数指示该请求是使用Ajax发送的,而不是使用常规HTML POST命令发送的。 如果是后者,则刷新页面(以允许您根据需要实施标准HTML后备)。 如果确实使用Ajax执行,则会构造一个响应结构并以JSON格式返回。

大多数PHP服务器端API的工作方式与上述代码清单相似。 如前所述,有关所有脚本的完整代码清单,请参见本教程随附的源代码

配置了应用程序的服务器端部分之后,您现在就可以使用Dojo为该应用程序创建一个时髦的前端。 下一节将说明向您HTML文档中声明性添加Dijit组件有多么简单。

用Dijit构建用户界面

Dojo丰富的组件库Dijit使您能够以最少的编码和最少的麻烦构建完整的用户界面。 Dijit组件可以以两种方式使用:以编程方式使用JavaScript或以声明方式使用具有dojoType属性HTML样式的元素。 在本教程中,我将创建声明式使用的所有Dijit小部件。 但是,这些小部件后面的所有JavaScript逻辑都将在单独JavaScript源文件中完成。

主要的应用程序代码包含在三个文件中。 布局和小部件声明在主index.html文件中完成。 名为css的子目录包含一个文件style.css,该文件包含此特定应用程序所需的所有样式表。 最后,所有JavaScript逻辑都存储在js子目录下的script.js文件中。

有关style.css文件的代码,请参见本教程随附的源代码。 该代码都是基本CSS语法,应该相对不言自明。

在本节中,我将说明如何使用HTML和Dojo小部件为应用程序构建用户界面。

基本结构

通过创建基本HTML结构来启动应用程序。 这包括所有相对样式表,设置要使用的Dijit主题以及加载所需JavaScript源文件。 在此应用程序中,为了方便起见,我正在从Google的CDN加载Dojo。 当然,这意味着只有在可以连接Internet的情况下才能使用该应用程序。 如果要脱机使用该应用程序,则只需下载Dojo库并将其本地加载。

清单5显示了应用程序主index.html文件的起点。

清单5.基本的index.html结构
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Dojo Contacts</title>
        <link rel="stylesheet"
href="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/claro/claro.css">
        <link rel="stylesheet"
href="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/grid/resources/Grid.css">
        <link rel="stylesheet"
href="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/grid/resources
/claroGrid.css">
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body class="claro">
        <!-- Content goes here -->
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs
/dojo/1.5/dojo/dojo.xd.js" djConfig="parseOnLoad: true"></script>
        <script type="text/javascript" src="js/script.js"></script>
    </body>
</html>

如您所见,该应用程序加载了四个样式表。 其中三个来自Dojo本身:第一个是Claro Dijit主题的主主题CSS文件,另外两个用于Data Grid DojoX扩展。 需要注意的重要一点是,我在本文档的主要<body>元素中添加了类claro 。 这告诉Dojo解析器它应该使用Claro主题。 没有这个,应用程序将看起来不正确,因此不要遗漏它。 我已在文件底部(紧随</body>元素之前)为应用程序加载了<script>块。 这是通常推荐的包含JavaScript源文件的方式,因为这不会阻止脚本其余部分加载之前页面其余部分的加载。 我已经从Google的CDN和应用程序的脚本文件中加载了主要的Dojo脚本,您将在本教程的稍后部分中创建它们。

定义应用程序布局

接下来,创建主应用程序布局。 现在的目标是实现图3所示的外观。基本上,到本节结尾,您希望能够在Web浏览器中加载文件并看到类似此屏幕快照的内容。

图3.基本应用程序布局
一个窗口,左边有一个垂直列,右边有2个水平列。

要使用应用程序中使用的Dijit组件,您需要告诉Dojo使用dojo.require JavaScript函数来加载这些小部件。 创建一个名为script.js的文件,并将其保存在应用程序文件夹中名为js的子目录中。 将清单6的内容添加到此文件中。

清单6.加载Dijit布局组件
dojo.require("dijit.dijit");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.MenuBar");
dojo.require("dijit.PopupMenuBarItem");
dojo.require("dijit.Menu");
dojo.require("dijit.MenuItem");

这告诉Dojo,它应该加载BorderContainerContentPane布局小部件以及将在应用程序菜单栏中使用的各种Menu小部件。 加载这些项目后,您现在可以将它们添加到应用程序的index.html文件中。 用清单7的内容替换文件中的注释<!-- Content goes here -->

清单7.将布局组件添加到应用程序
<div dojoType="dijit.layout.BorderContainer" design="header" gutters="false" 
liveSplitters="true" id="borderContainer">
    <div dojoType="dijit.layout.ContentPane" region="top" id="topBar">
        <h1>Dojo Contacts</h1>
        <div dojoType="dijit.MenuBar" id="navMenu">
            <div dojoType="dijit.PopupMenuBarItem">
                <span>File</span>
                <div dojoType="dijit.Menu" id="fileMenu">
                    <div dojoType="dijit.MenuItem" 
jsId="mnuNewContact">New Contact</div>
                    <div dojoType="dijit.MenuItem" 
jsId="mnuNewGroup">New Group</div>
                </div>
            </div>
            <div dojoType="dijit.PopupMenuBarItem">
                <span>Edit</span>
                <div dojoType="dijit.Menu" id="editMenu">
                    <div dojoType="dijit.MenuItem" jsId="mnuEditContact" 
disabled="true">Edit Contact</div>
                    <div dojoType="dijit.MenuItem" jsId="mnuMoveContact" 
disabled="true">Move Contact</div>
                    <div dojoType="dijit.MenuItem" jsId="mnuRenameGroup"
disabled="true">Rename Group</div>

                    <div dojoType="dijit.MenuSeparator"></div>
                    <div dojoType="dijit.MenuItem" jsId="mnuDeleteContact" 
disabled="true">Delete Contact</div>
                    <div dojoType="dijit.MenuItem" jsId="mnuDeleteGroup" 
disabled="true">Delete Group</div>
                </div>
            </div>
        </div>
    </div>
    <div dojoType="dijit.layout.BorderContainer" region="center" gutters="true" 
liveSplitters="true" id="mainSection">
        <div dojoType="dijit.layout.ContentPane" splitter="true" region="left" 
id="treeSection">
            Tree to go here
        </div>
        <div dojoType="dijit.layout.BorderContainer" design="header" gutters="true" 
liveSplitters="true" id="mainContainer" region="center">
            <div dojoType="dijit.layout.ContentPane" region="top" splitter="true" 
id="gridSection">
                Grid to go here
            </div>
            <div dojoType="dijit.layout.ContentPane" id="contactView" 
jsId="contactView" region="center">
                <em>Select a contact to view above.</em>
            </div>
        </div>
    </div>
</div>

如清单7所示,您可以使用具有特殊dojoType属性的标准HTML元素将Dijit组件添加到页面中。 这些小部件声明还使用一些特定于该小部件类型的独特属性,例如, region属性用于定义BorderContainer的子元素应在容器上下文中显示的位置。 清单7中使用了很多组件,因此让我们看一下这些组件的基本结构:

  • dijit.layout.BorderContainer
    • dijit.layout.ContentPane (顶部)
  • 11
  • dijit.MenuBar
  • dijit.PopupMenuBarItem
    • dijit.Menu
  • dijit.MenuItem
  • dijit.MenuItem
  • dijit.PopupMenuBarItem
    • dijit.Menu
  • dijit.MenuItem
  • dijit.MenuItem
  • dijit.MenuItem
    • dijit.layout.BorderContainer (中心)
  • dijit.layout.ContentPane (左)
  • dijit.layout.BorderContainer (中心)
  • dijit.layout.ContentPane (顶部)
  • dijit.layout.ContentPane (中心)

在顶层,有一个BorderContainer ,它使您可以将子组件放置在五个区域之一中:顶部,中心,左侧,右侧和底部。 在这下面是两个组件,顶部是ContentPane ,中央是BorderContainer 。 因为只使用了两个组件,所以中心组件将占据顶部组件未使用的所有空间。 在顶部,有一个标准HTML h1标题和Dijit Menu结构。 在中间部分,有一个嵌套的BorderContainer组件,左边是ContentPane ,中间是另一个BorderContainer (占用了左边组件未使用的任何空间)。 左侧的ContentPane是放置Tree控件的位置。 中心的BorderContainer在顶部和中心区域具有两个子组件ContentPanes

在这一阶段,您应该能够保存文件并将其加载到Web浏览器中,以获得与之前在图3中看到的结果类似的结果。

添加Tree组件

接下来,在应用程序的左侧添加一个Tree组件。 Dijit Trees是复杂的组件,需要几个支持组件以确保它们正常工作。 首先,它们需要一个符合Dojo的Identity API的数据存储,并连接到某些JSON数据源。 在示例应用程序中,此数据由PHP脚本data / groups.php生成,因此您可以将树的数据存储连接到此API。 接下来, Tree需要一个模型组件来定义数据存储的结构以及应读取的数据。 然后,添加您的Tree组件并将其连接到该模型。

首先,您首先需要告诉Dojo它应该加载的组件,以便应用程序可以使用它们。 将清单8中所示的两行代码添加到script.js文件中。

清单8.加载与树相关的组件
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.Tree");

您可能想知道为什么要使用可写存储。 Dojo的Tree组件使用起来非常复杂,实际上不能直接异步刷新(除非您将其放入ContentPane并刷新整个ContentPane ),因此要动态地从树中添加/更新/删除项目,它需要连接到可写文件商店。

接下来,找到index.html文件的“ Tree to go here”部分,并将其替换为清单9中的代码。这定义了数据存储,树模型和树本身,并将其放置在目录树的左侧。应用。 它还会向树中添加一个上下文菜单,尽管现在将禁用这些项目。 您稍后将实现上下文菜单。

清单9.将树添加到应用程序
<div dojoType="dojo.data.ItemFileWriteStore" jsId="groupsStore" 
url="data/groups.php"></div>
<div dojoType="dijit.tree.TreeStoreModel" jsId="groupsModel" 
childrenAttrs="groups" store="groupsStore" query="{id:0}"></div>
<div dojoType="dijit.Tree" id="groupsTree" jsId="groupsTree" model="groupsModel">
    <div dojoType="dijit.Menu" targetNodeIds="groupsTree">
        <div dojoType="dijit.MenuItem" jsId="ctxMnuRenameGroup" 
disabled="true">Rename Group</div>
        <div dojoType="dijit.MenuItem" jsId="ctxMnuDeleteGroup" 
disabled="true">Delete Group</div>
    </div>
</div>

在清单9中,您会注意到在每个组件中都使用了jsId属性。 此属性告诉Dojo解析器为这些组件中的每个组件创建JavaScript变量,并将其命名为此处给出的值。 这使得以后使用这些组件变得更加容易,因为它无需使用dijit.byId来查询DOM以查找这些小部件。 它也应该提供性能上的回报,因为DOM查询在应用程序速度和响应能力方面可能是昂贵的,尤其是在较慢的Web浏览器上。

如果重新加载浏览器,则该应用程序现在应类似于图4中的窗口(假定您正确设置了PHP脚本,并且树的数据已实际加载)。

图4. Dijit Tree在起作用
“ Dojo联系人”窗口的左窗格中有一个组列表,而右侧则水平拆分为2个窗格。

添加Grid组件

将网格添加到应用程序应该类似于树组件。 但是,与树不同,可以通过刷新数据存储轻松地异步重新加载网格,因此不需要模型,并且可以安全地使用只读存储。 首先,将以下行添加到script.js文件,以便网格组件可在应用程序中使用:

dojo.require("dojox.grid.DataGrid");

请注意,Data Grid小部件包含在DojoX中,而不是Dijit库中。 DojoX组件通常需要加载其他样式表(在这种情况下,这些样式已经包含在我之前创建的基本模板中)。

现在,您可以将网格添加到主应用程序文件中。 Dojo中的数据网格可以应用于标准的<table>元素,从而使您可以轻松定义要使用的标题。 网格可以定义一系列属性来设置网格的工作方式。 例如,是否应启用客户端列排序。 在index.html文件中找到“ Grid to go here”引用,并将其替换为清单10中的代码。

清单10.将网格添加到应用程序
<span dojoType="dojo.data.ItemFileReadStore" jsId="contactsStore" 
url="data/contacts.php"></span>
<table dojoType="dojox.grid.DataGrid" id="contactsGrid" jsId="contactsGrid" 
columnReordering="true" sortFields="['last_name','first_name']" store="contactsStore" 
query="{first_name: '*'}"
clientSort="true" selectionMode="single" rowHeight="25" noDataMessage="<span 
class='dojoxGridNoData'>No contacts found in this group</span>">
    <thead>
        <tr>
            <th field="last_name" width="200px">Last Name</th>
            <th field="first_name" width="200px">First Name</th>
            <th field="email_address" width="100%">E-mail Address</th>
        </tr>
    </thead>

    <script type="dojo/method" event="onRowContextMenu" args="e">
    </script>
</table>
<div dojoType="dijit.Menu" id="gridMenu" targetNodeIds="contactsGrid">
    <div dojoType="dijit.MenuItem" jsId="ctxMnuEditContact" 
disabled="true">Edit Contact</div>
    <div dojoType="dijit.MenuItem" jsId="ctxMnuMoveContact" 
disabled="true">Move Contact</div>
    <div dojoType="dijit.MenuItem" jsId="ctxMnuDeleteContact"
disabled="true">Delete Contact</div>
</div>

再次,就像之前的树一样,网格还具有可用的上下文菜单。 在本教程的后面,您将看到如何启用这些菜单。 如果您想知道为什么有一个内联Dojo方法<script>块,这是由于DataGrid组件中的一个错误所致,在该错误中,如果不包含此空块,则不会显示上下文菜单。 值得指出的是,您实际上可以根据需要使用这些块将JavaScript代码直接放入布局代码中,但就我个人而言,我更倾向于将应用程序逻辑与UI元素分开。

网格使用只读数据存储区加载数据,该数据存储区连接到API data / contacts.php。 它会自动按姓氏和名字对客户端的数据进行排序。 网格一次只允许选择一行,并且定义了一条错误消息,如果找不到数据,则显示该错误消息。

在这个阶段,您的应用程序应如图5所示(再次假设您已从源代码包中添加了PHP脚本并正确配置了服务器端):

图5.运行中的DojoX数据网格
“ Dojo联系人”窗口的左窗格中有一个组列表,而右侧则水平拆分为2个窗格。右上方的窗格包含带有姓氏,名字和电子邮件地址的列。

隐藏的应用程序组件: dijit.Dialog窗口

至此,主应用程序UI已完成。 在本节中,您将添加一些隐藏的对话框窗口,当您选择菜单选项时,这些对话框窗口将以模态方式显示在主应用程序窗口上。 这些弹出窗口通常包含Web表单,可用于定义组或联系人的各种属性,但是您还将创建一个简单的OK消息窗口,该窗口看起来比标准JavaScript警报对话框更好。

这些对话框共有五个。 这些对话框窗口包含各种Dijit表单字段,包括ValidationTextBoxesFilteringSelectsButtons 。 要包括所有这些其他小部件,必须使用dojo.require加载它们,因此将清单11中的代码添加到script.js文件中。

清单11.加载对话框和表单组件
dojo.require("dijit.Dialog");
dojo.require("dijit.form.Form");
dojo.require("dijit.form.ValidationTextBox");
dojo.require("dijit.form.TextBox");
dojo.require("dijit.form.FilteringSelect");
dojo.require("dijit.form.Button");

您现在可以开始添加对话框。 这些组件应添加到主应用程序BorderContainer之外,以确保它们出现在页面上的所有其他元素上。 在index.html文件底部附近的第一个<script>元素上方开始添加它们。

首先,添加“新建组”和“重命名组”对话框。 这些窗口的代码如清单12所示。

清单12. New Group and Rename Group对话框代码
<div id="newGroupDialog" jsId="newGroupDialog" dojoType="dijit.Dialog" title="Create 
New Group" draggable="false">
    <div dojoType="dijit.form.Form" id="newGroupForm" 
jsId="newGroupForm" action="data/new_group.php" method="post">
        <input type="hidden" name="new_group_ajax" 
id="new_group_ajax" value="0">
        <label for="new_group_name">Group Name:</label>
        <input type="text" name="new_group_name" 
id="new_group_name" required="true" dojoType="dijit.form.ValidationTextBox" />
        <button dojoType="dijit.form.Button" type="submit">Submit</button>
        <button dojoType="dijit.form.Button" jsId="newGroupCancel" 
type="button">Cancel</button>
    </div>
</div>

<div id="editGroupDialog" jsId="editGroupDialog" dojoType="dijit.Dialog" 
title="Rename Group" draggable="false">
    <div dojoType="dijit.form.Form" id="editGroupForm" jsId="editGroupForm" 
action="data/edit_group.php" method="post">
        <input type="hidden" name="edit_group_ajax" id="edit_group_ajax" 
value="0">
        <input type="hidden" name="edit_group_id" id="edit_group_id">
        <table cellspacing="4" cellpadding="4">
            <tr>
               <td><label for="edit_group_old">Old Group 
Name:</label></td>
               <td><input type="text" name="edit_group_old" id="edit_group_old"
disabled="true" dojoType="dijit.form.TextBox" /></td>
            </tr>
            <tr>
                <td><label for="edit_group_name">New Group 
Name:</label></td>
                <td><input type="text" name="edit_group_name" 
id="edit_group_name" required="true" dojoType="dijit.form.ValidationTextBox" 
style="margin-bottom: 6px" /></td>
            </tr>
            <tr>
                <td colspan="2" align="center">
                    <button dojoType="dijit.form.Button" 
type="submit">Submit</button>
                    <button dojoType="dijit.form.Button" jsId="editGroupCancel"
type="button">Cancel</button>
                </td>
            </tr>
        </table>
    </div>
</div>

“新建组”对话框包含一个表单,该表单将提交到PHP脚本data / new_group.php。 此窗体包含ValidationTextBox窗体控件,它是具有自动验证功能的Dijit样式的文本框。 在这种情况下,通过将必需的属性设置为true,该控件已标记为强制性的。 该对话框还包含“提交”和“取消”按钮。 您将在本教程的后面部分实现处理此表单(以及本节中创建的所有其他表单)的代码。

“重命名组”对话框允许用户更改组的名称。 它显示具有当前组名的只读TextBox控件,并为要提供的新组名提供ValidationTextBox 。 同样,它还具有一对按钮。

接下来,添加“移动联系人”对话框窗口,如清单13所示。

清单13.“移动联系人”对话框标记
<div id="moveContactDialog" jsId="moveContactDialog" dojoType="dijit.Dialog" 
title="Move Contact" draggable="false">
    <div dojoType="dijit.form.Form" id="moveContactForm" jsId="moveContactForm" 
action="data/move_contact.php" method="post">
        <input type="hidden" name="move_contact_ajax" 
id="move_contact_ajax" value="0">
        <input type="hidden" name="move_contact_id" id="move_contact_id">
        <table cellspacing="4" cellpadding="4">
            <tr>
                <td><label for="move_contact_name">Contact Name:
</label></td>
                <td><input type="text" name="move_contact_name" 
id="move_contact_name" disabled="true" dojoType="dijit.form.TextBox" /></td>
            </tr>
            <tr>
                <td><label for="move_contact_old">Current 
Group:</label></td>
                <td><input type="text" name="move_contact_old" 
id="move_contact_old" disabled="true" dojoType="dijit.form.TextBox" /></td>
            </tr>
            <tr>
                <td><label for="move_contact_new">New Group:</label>
</td>
                <td><input dojoType="dijit.form.FilteringSelect" 
name="move_contact_new" store="groupsStore" searchAttr="name" query="{type:'node'}" 
id="move_contact_new" required="true" style="margin-bottom: 6px" /></td>
            </tr>
            <tr>
                <td colspan="2" align="center">
                    <button dojoType="dijit.form.Button" 
type="submit">Submit</button>
                    <button dojoType="dijit.form.Button" jsId="moveContactCancel" 
type="button">Cancel</button>
                </td>
            </tr>
        </table>
    </div>
</div>

此对话框显示所选联系人的姓名及其所属的当前组。 然后,它显示一个FilteringSelect下拉列表,该列表允许用户选择一个新组以将联系人移至该组。 该下拉列表实际上由与Tree控件相同的数据存储支持,该Tree控件在界面的左侧显示组的列表。 但是, FilteringSelect组件的查询属性使您可以告诉Dojo不要包括根Groups元素,因此用户无法将其选择为将联系人移至的选项。

下一个对话框是“编辑联系人”对话框,该对话框实际上用于添加新联系人和修改现有联系人。 清单14中显示了此对话框的代码。

清单14.“编辑/添加联系人”对话框标记
<div id="editContactDialog" jsId="editContactDialog" dojoType="dijit.Dialog" 
title="Edit Contact" draggable="false">
    <div dojoType="dijit.form.Form" id="editContactForm" jsId="editContactForm"
action="data/edit_contact.php" method="post">
        <input type="hidden" name="edit_contact_ajax" id="edit_contact_ajax" 
value="0">
        <input type="hidden" name="edit_contact_real_id" 
id="edit_contact_real_id">
        <table cellspacing="4" cellpadding="4">
            <tr><td><label for="edit_contact_id">Contact ID:
</label></td>
            <td><input type="text" name="edit_contact_id" id="edit_contact_id" 
disabled="true" dojoType="dijit.form.TextBox" /></td></tr>
            <tr><td><label for="move_contact_new">Group:
</label></td>
            <td><input dojoType="dijit.form.FilteringSelect" 
name="edit_contact_group" store="groupsStore" searchAttr="name" query="{type:'node'}" 
id="edit_contact_group" required="true" /></td></tr>
            <tr><td><label for="edit_contact_first_name">First Name:
</label></td>
            <td><input type="text" name="edit_contact_first_name" 
id="edit_contact_first_name" required="true" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td><label for="edit_contact_last_name">Last Name:
</label></td>
            <td><input type="text" name="edit_contact_last_name" 
id="edit_contact_last_name" required="true" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td><label for="edit_contact_email_address">
Email Address:</label></td>
            <td><input type="text" name="edit_contact_email_address" 
id="edit_contact_email_address" required="true" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td><label for="edit_contact_home_phone">Home 
Phone:</label></td>
            <td><input type="text" name="edit_contact_home_phone" 
id="edit_contact_home_phone" required="false" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td><label for="edit_contact_work_phone">Work 
Phone:</label></td>
            <td><input type="text" name="edit_contact_work_phone" 
id="edit_contact_work_phone" required="false" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td><label for="edit_contact_twitter">Twitter:
</label></td>
            <td><input type="text" name="edit_contact_twitter" 
id="edit_contact_twitter" required="false" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td><label for="edit_contact_facebook">Facebook:
</label></td>
            <td><input type="text" name="edit_contact_facebook" 
id="edit_contact_facebook" required="false" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td><label for="edit_contact_linkedin">LinkedIn:
</label></td>
            <td><input type="text" name="edit_contact_linkedin" 
id="edit_contact_linkedin" required="false" dojoType="dijit.form.ValidationTextBox" 
/></td></tr>
            <tr><td colspan="2" align="center">
                <button dojoType="dijit.form.Button" 
type="submit">Submit</button>
                <button dojoType="dijit.form.Button" jsId="editContactCancel" 
type="button">Cancel</button>
            </td></tr>
        </table>
    </div>
</div>

此表单有很多标记,但这仅是因为它包含的字段数量。 实际上,它没有“移动联系人”对话框更复杂。 它具有“联系人ID”值的仅显示字段,如果创建新的联系人,则在JavaScript中将其设置为[NEW] (ID将在MySQL中自动生成)。 它还包含组的表单字段(与“移动联系人”对话框相同的下拉列表),名字,姓氏,电子邮件地址,家庭电话,工作电话,Twitter帐户,Facebook帐户和LinkedIn帐户。

需要创建的最后一个对话框是标准的“确定”对话框,每当使用XHR / Ajax执行保存操作时,该对话框都将用作成功或错误消息显示窗口。 清单15显示了此对话框的代码。

清单15. OK消息对话框的标记
<div id="okDialog" jsId="okDialog" dojoType="dijit.Dialog" title="Title" 
draggable="false">
    <p id="okDialogMessage" style="margin-top: 5px">Message</p>
    <div align="center">
        <button dojoType="dijit.form.Button" jsId="okDialogOK" 
type="button">OK</button>
    </div>
</div>

该对话框没有什么复杂的—它只包含一条消息和一个关闭它的按钮。 现在创建此对话框,从而结束了应用程序的接口代码。 您可以根据需要在浏览器中重新加载界面,但与图5中的屏幕快照没有任何区别,因为这些对话框需要使用JavaScript进行连接。 下一节将说明如何执行此操作。

应用程序接口逻辑

创建了用户界面标记后,您现在就可以开始使用JavaScript将各个组件相互连接。

加载逻辑

您添加的所有JavaScript代码都应在DOM完成加载后执行,因此在执行任何操作之前,请将清单16中的代码添加到应用程序js子目录中script.js文件的末尾。

清单16.加载时添加逻辑
dojo.addOnLoad(function() {
    //Application code goes here
});

从此处添加的所有源代码都应添加到此功能块内。 首先,让我们连接树和数据网格,以便在单击树中的节点时,网格将显示所选组中的联系人。

将树连接到网格

在清单17中的dojo.addOnLoad函数块中添加以下函数。

清单17.从树中更新数据网格
function refreshGrid() {
    contactsGrid.selection.clear();
    mnuEditContact.set("disabled", true);
    mnuMoveContact.set("disabled", true);
    mnuDeleteContact.set("disabled", true);
    ctxMnuEditContact.set("disabled", true);
    ctxMnuMoveContact.set("disabled", true);
    ctxMnuDeleteContact.set("disabled", true);
    dijit.byId("contactView").set("content", '<em>Select a contact to 
view above.</em>');
}

function updateDataGrid(item) {
    var newURL = "data/contacts.php?group_id="+item.id;
    var newStore = new dojo.data.ItemFileReadStore({url: newURL});
    contactsGrid.setStore(newStore);
    refreshGrid();
}

function selectNode(e) {
    var item = dijit.getEnclosingWidget(e.target).item;
    if(item !== undefined) {
        groupsTree.set("selectedItem",item);
        if(item.id != 0) {
            mnuRenameGroup.set("disabled",false);
            mnuDeleteGroup.set("disabled",false);
            ctxMnuRenameGroup.set("disabled",false);
            ctxMnuDeleteGroup.set("disabled",false);
        } else {
            mnuRenameGroup.set("disabled",true);
            mnuDeleteGroup.set("disabled",true);
            ctxMnuRenameGroup.set("disabled",true);
            ctxMnuDeleteGroup.set("disabled",true);
        }
    }
}

refreshGrid函数仅清除网格和主联系人视图窗格中的所有选择,并禁用任何相关的菜单选项。 updateGrid函数刷新网格的数据存储,告诉它在所选组中查找联系人。 最后, selectNode函数在树上设置选定的节点,并适当地启用/禁用菜单选项(如果选择了“真实”组,则应将其启用;如果选择根“ Groups节点,则应将其禁用)。

现在是时候将这些函数连接到事件了,以便树和网格可以相互通信(参见清单18)。

清单18.将树连接到网格
dojo.connect(groupsTree, "onClick", null, updateDataGrid);
dojo.connect(groupsTree, "onMouseDown", null, selectNode);

这些事件可确保选择树节点,即使您右键单击某个节点也是如此。 当您单击树节点时,它还会更新数据网格。 在浏览器中重新加载应用程序,现在您应该能够选择树并在网格中查看相关的群组联系人。 您还应该看到选择一个组(而不是“ Groups根节点)时,上下文菜单选项和应用程序菜单选项将变为可用。 当然,菜单实际上还没有做任何事情。 您将在本教程的稍后部分中看到如何实现这些功能。 图6显示了选择了一个组并且正在使用上下文菜单的应用程序。

图6.连接的树和数据网格
“ Dojo联系人”窗口的左窗格中有一个组列表,而右侧则水平拆分为2个窗格。右上角的窗格包含姓,名,列的电子邮件地址,并带有弹出框以重命名或删除该组。

将网格连接到显示窗格

接下来,您需要连接网格和主联系人显示窗格(界面右侧的底部)。 首先,将清单19中给出的两个函数添加到script.js文件中,在清单18中添加的代码下面。

清单19.从网格更新联系人视图
function selectRow(e) {
    if(e.rowIndex != null) {
        this.selection.clear();
        this.selection.setSelected(e.rowIndex, true);

        mnuEditContact.set("disabled", false);
        mnuMoveContact.set("disabled", false);
        mnuDeleteContact.set("disabled", false);
        ctxMnuEditContact.set("disabled", false);
        ctxMnuMoveContact.set("disabled", false);
        ctxMnuDeleteContact.set("disabled", false);
    }
}

function displayContact(idx) {
    var item = this.getItem(idx);
    var contactId = item.id;
    contactView.set("href", "data/contact.php?contact_id="+contactId);

    mnuEditContact.set("disabled", false);
    mnuMoveContact.set("disabled", false);
    mnuDeleteContact.set("disabled", false);
    ctxMnuEditContact.set("disabled", false);
    ctxMnuMoveContact.set("disabled", false);
    ctxMnuDeleteContact.set("disabled", false);
}

当用户右键单击网格时,使用selectRow函数设置行,而displayContact函数设置要在其中显示联系人详细信息的ContentPanehref值。 This content is loaded asynchronously from the PHP script contact.php, which is passed the ID of the selected contact. This function also enables menus as appropriate. These functions don't do anything unless they are connected to events, so let's wire them up now (see Listing 20).

Listing 20. Connecting the grid to the display pane
dojo.connect(contactsGrid, "onRowContextMenu", null, selectRow);
dojo.connect(contactsGrid, "onSelected", null, displayContact);

You can now reload the application in your browser and you should be able to select a contact in the grid and see the contact's details in the view pane. The relevant options should also be enabled in both the application and context menus. This is shown in Figure 7.

Figure 7. Connected grid and display pane
Dojo contacts window with a group     listing in the left pane, and the right side split horizontally into 2 panes. The top     right pane contains columns with last name, first name, then email addresses. The     bottom right pane shows details for Joe Lennon.

Connecting menu options to dialog windows

Up until this point, none of the menu options in the application actually do anything. Similarly, there are a series of dialog windows that never appear. In this section, you will connect the menus to open these dialog windows accordingly.

Creating functions for configuring dialog windows

First up, create the functions given in Listing 21, which will configure the dialog windows correctly for the selected operation.

Listing 21. Functions for configuring dialog windows for selected operation
function renameGroup() {
    var group = groupsTree.get("selectedItem");
    var groupId = group.id;
    var groupName = group.name;

    dojo.byId("edit_group_id").value = groupId;
    dijit.byId("edit_group_old").set("value", groupName);
    editGroupDialog.show();
}
function refreshGroupDropDown() {
    var theStore = dijit.byId("edit_contact_group").store;
    theStore.close();
    theStore.url = "data/groups.php";
    theStore.fetch();
}
function newContact() {
    var contact = contactsGrid.selection.getSelected()[0];
    refreshGroupDropDown();
    dojo.byId("edit_contact_real_id").value = "";
    dojo.byId("edit_contact_id").value = "[NEW]";
    dijit.byId("edit_contact_group").reset();
    dijit.byId("edit_contact_first_name").reset();
    dijit.byId("edit_contact_last_name").reset();
    dijit.byId("edit_contact_email_address").reset();
    dijit.byId("edit_contact_home_phone").reset();
    dijit.byId("edit_contact_work_phone").reset();
    dijit.byId("edit_contact_twitter").reset();
    dijit.byId("edit_contact_facebook").reset();
    dijit.byId("edit_contact_linkedin").reset();
    
    dijit.byId("editContactDialog").set("title", "New Contact");
    dijit.byId("editContactDialog").show();
}
function editContact() {
    refreshGroupDropDown();
    var contact = contactsGrid.selection.getSelected()[0];
    dojo.byId("edit_contact_real_id").value = contact.id;
    dojo.byId("edit_contact_id").value = contact.id;
    dijit.byId("edit_contact_group").set("value", contact.group_id);
    dojo.byId("edit_contact_first_name").value = contact.first_name;
    dojo.byId("edit_contact_last_name").value = contact.last_name;
    dojo.byId("edit_contact_email_address").value = contact.email_address;
    dojo.byId("edit_contact_home_phone").value = contact.home_phone;
    dojo.byId("edit_contact_work_phone").value = contact.work_phone;
    dojo.byId("edit_contact_twitter").value = contact.twitter;
    dojo.byId("edit_contact_facebook").value = contact.facebook;
    dojo.byId("edit_contact_linkedin").value = contact.linkedin;
    
    dijit.byId("editContactDialog").set("title", "Edit Contact");
    dijit.byId("editContactDialog").show();
}
function moveContact() {
    var contact = contactsGrid.selection.getSelected()[0];
    var contactName = contact.first_name+" "+contact.last_name;
    var groupName = contact.name;
    
    dojo.byId("move_contact_id").value = contact.id;
    dojo.byId("move_contact_name").value = contactName;
    dojo.byId("move_contact_old").value = groupName;
    
    dijit.byId("moveContactDialog").show();
}

Next, you need to connect these functions to the relevant menu options using dojo.connect (see Listing 22).

Listing 22. Connecting menu options to functions
dojo.connect(mnuNewGroup, "onClick", null, function(e) {
    newGroupDialog.show();
});
dojo.connect(mnuRenameGroup, "onClick", null, renameGroup);
dojo.connect(ctxMnuRenameGroup, "onClick", null, renameGroup);
dojo.connect(mnuNewContact, "onClick", null, newContact);
dojo.connect(mnuEditContact, "onClick", null, editContact);
dojo.connect(ctxMnuEditContact, "onClick", null, editContact);
dojo.connect(mnuMoveContact, "onClick", null, moveContact);
dojo.connect(ctxMnuMoveContact, "onClick", null, moveContact);

In Listing 22, the New Group menu option is connected to an anonymous function, as it only has a single line to open the New Group dialog, so it doesn't really warrant a function of its own. Of course, if you prefer, you could put all of the functions used here in anonymous blocks like this, but I've separated them out, as it's easier to explain this way.

You can now reload the application and load any menu (except for the Delete options) and see the relevant dialog window open and load as appropriate. You'll probably notice right away that none of the buttons in these dialogs (except the hide "X" icon in the top right) actually do anything yet. You'll implement those features shortly. Figure 8 shows an example of the Move Contact window in action.

Figure 8. Move Contact dialog window working
Move Contact window with the     contact's name, current group, and a field to enter the group you want to move the     contact to.

Deleting groups and contacts

In the previous section, you learned how to connect all menus to their relevant dialogs, except for the delete actions. That is because the delete actions do not have a dialog window. Instead, the application should prompt the user to confirm that they want to delete a group or contact, and if they click the appropriate button, it should proceed with the deletion.

Dojo's shortcomings are few and far between, but, unfortunately, one of them is an out-of-the-box confirmation dialog box. Luckily, it's quite simple to build a custom dialog that does just that. To create a confirmation dialog box, add the function shown in Listing 23 to the script.js file.

Listing 23. Function to create a confirmation dialog box
function confirmDialog(title, body, callbackFn) {
    var theDialog = new dijit.Dialog({
        id: 'confirmDialog',
        title: title,
        draggable: false,
        onHide: function() {
            theDialog.destroyRecursive();
        }
    });

    var callback = function(mouseEvent) {
        theDialog.hide();
        theDialog.destroyRecursive(false);

        var srcEl = mouseEvent.srcElement ? mouseEvent.srcElement : mouseEvent.target;

        if(srcEl.innerHTML == "OK") callbackFn(true);
        else callbackFn(false);
    };

    var message = dojo.create("p", {
        style: {
            marginTop: "5px"
        },
        innerHTML: body
    });
    var btnsDiv = dojo.create("div", {
        style: {
            textAlign: "center"
        }
    });
    var okBtn = new dijit.form.Button({label: "OK", id: "confirmDialogOKButton", 
onClick: callback });
    var cancelBtn = new dijit.form.Button({label: "Cancel", 
id: "confirmDialogCancelButton", onClick: callback });

    theDialog.containerNode.appendChild(message);
    theDialog.containerNode.appendChild(btnsDiv);
    btnsDiv.appendChild(okBtn.domNode);
    btnsDiv.appendChild(cancelBtn.domNode);

    theDialog.show();
}

This function accepts three arguments: the title to display in the dialog, the message to show, and the function that should be called back after the user clicks OK or Cancel. This function is called with a true value if OK is pressed and a false value if Cancel is pressed.

You can now use this function to create confirmation dialogs for the Delete Group and Delete Contact menu options. The results of deleting (and other CRUD operations you'll see in the next section) cause the okDialog box to be shown. So, let's get a handle to the message in that box now, while also getting the OK button to hide the dialog (see Listing 24).

Listing 24. Use the OK button to hide the dialog
var okDialogMsg = dojo.byId("okDialogMessage");
dojo.connect(okDialogOK, "onClick", null, function(e) {
dijit.byId("okDialog").hide();
});

Listing 25 contains the code for the function that handles deleting groups.

Listing 25. deleteGroup function
function deleteGroup() {
    confirmDialog("Confirm delete", "Are you sure you wish to delete this group? 
This will also delete any contacts in this group.<br />This action cannot 
be undone.", function(btn) {
        if(btn) {
            var group = groupsTree.get("selectedItem");
            var groupId = group.id;
            var groupName = group.name;

            dojo.xhrPost({
                url: "data/delete_group.php",
                handleAs: "json",
                content: {
                    "group_id": groupId
                },
                load: function(data) {
                    if(data.success) {
                        groupsStore.fetch({
                            query: {"id": groupId.toString()},
                            onComplete: function (items, request) {
                                if(items) {
                                    var len=items.length;
                                    for(var i=0;i<len;i++) {
                                        var item = items[i];
                                        groupsStore.deleteItem(item);
                                    }
                                }
                            },
                            queryOptions: { deep: true}
                        });
                        groupsStore.save();

                        groupsTree.set("selectedItem", groupsModel.root);
                        updateDataGrid(groupsModel.root);
                        okDialog.set("title","Group deleted successfully");
                        okDialogMsg.innerHTML = "The group <strong>"+groupName+"
</strong> was deleted successfully.";
                        okDialog.show();
                    }
                    else {
                        okDialog.set("title","Error deleting group");
                        okDialogMsg.innerHTML = data.error;
                        okDialog.show();
                    }
                },
                error: function(data) {
                    okDialog.set("title","Error deleting group");
                    okDialogMsg.innerHTML = data;
                    okDialog.show();
                }
            });
        }
    });
}

There's a bit of new ground to cover here, but the deleteContact function is very similar, so let's add that now too, and I'll discuss both in tandem.

Listing 26. deleteContact function
function deleteContact() {
    var confirmed = false;
    confirmDialog("Confirm delete", "Are you sure you wish to delete this 
contact?<br />This action cannot be undone.", function(btn) {
        if(btn) {
            var contact = contactsGrid.selection.getSelected()[0];
            var contactId = contact.id;
            var contactName = contact.first_name+" "+contact.last_name;

            dojo.xhrPost({
                url: "data/delete_contact.php",
                handleAs: "json",
                content: {
                    "contact_id": contactId
                },
                load: function(data) {
                    if(data.success) {
                        var treeSel = groupsTree.get("selectedItem");
                        var groupId;
                        if(treeSel) {
                            groupId = treeSel.id;
                        } else {
                            groupId = 0;
                        }
                        var url = contactsStore.url+"?group_id="+groupId;
                        var newStore = new dojo.data.ItemFileReadStore({url:url});
                        contactsGrid.setStore(newStore);
                        refreshGrid();

                        okDialog.set("title","Contact deleted successfully");
                        okDialogMsg.innerHTML = "The contact <strong>"
+contactName+"</strong> was deleted successfully.";
                        okDialog.show();
                    }
                    else {
                        okDialog.set("title","Error deleting contact");
                        okDialogMsg.innerHTML = data.error;
                        okDialog.show();
                    }
                },
                error: function(data) {
                    okDialog.set("title","Error deleting contact");
                    okDialogMsg.innerHTML = data;
                    okDialog.show();
                }
            });
        }
    });
}

Both of these functions use the new confirmation dialog function to ask the user to confirm the deletion. Deleting a group will cascade delete any contacts in that group, so the user is warned about that in the case of attempting to delete a group. If the user confirms, the functions go to either the group's tree or contacts grid as required and gets the detail of the item that needs to be deleted. An Ajax dojo.xhrPost function call is then used to asynchronously call the relevant server-side PHP API. The JSON response received from this is then parsed and used to display a relevant success or error message.

Finally, you need to connect these functions to the relevant events (that is, the menu options). The code in Listing 27 does just that.

Listing 27. Connecting menu options to delete functions
dojo.connect(mnuDeleteContact, "onClick", null, deleteContact);
dojo.connect(ctxMnuDeleteContact, "onClick", null, deleteContact);
dojo.connect(mnuDeleteGroup, "onClick", null, deleteGroup);
dojo.connect(ctxMnuDeleteGroup, "onClick", null, deleteGroup);

You can now save and reload your application. If you try to delete a contact, you should see the message shown in Figure 9.

Figure 9. Delete confirmation dialog box
A message box asking you to confirm     the deletion.

Pressing OK will actually go ahead and delete the contact, and the following message will subsequently be displayed (see Figure 10).

Figure 10. Deletion successful message
A message box telling you the     contact was successfully deleted.

You'll also notice that the relevant contact has been removed from the underlying grid. If you delete a group, you should see similar functionality (except that deleting a group also deletes all the contacts in that group).

Saving data from dialog boxes using Ajax

The only function that has yet to be added to the application is the implementation of the dialog boxes for creating new groups and contacts, renaming groups, and editing and moving existing contacts. Let's wrap up the application by implementing these functions now.

In the previous section, you learned how to implement the delete function. This used the dojo.xhrPos t function to call a PHP script using Ajax. You will be adding similar function calls to perform the other operations, except you will tap into the form submit actions to override the default functions of the forms.

Adding and renaming groups

Let's start with the adding and renaming of groups. Add the two functions in Listing 28 to your script.js file.

Listing 28. Functions to add and rename groups
function doNewGroup(e) {
    e.preventDefault();
    e.stopPropagation();
    dojo.byId("new_group_ajax").value = "1";
    if(this.isValid()) {
        dojo.xhrPost({
            form: this.domNode,
            handleAs: "json",
            load: function(data) {
                if(data.success) {
                    okDialog.set("title","Group created successfully");
                    okDialogMsg.innerHTML = "The group <strong>"+data.name+
"</strong> was created successfully.";

                    groupsStore.newItem({"id":data.id.toString(),"name":data.name},
{"parent": groupsModel.root, "attribute":"groups"});
                    groupsStore.save();

                    newGroupDialog.hide();
                    okDialog.show();
                }
                else {
                    okDialog.set("title","Error creating group");
                    okDialogMsg.innerHTML = data.error;
                    okDialog.show();
                }
            },
            error: function(error) {
                okDialog.set("title","Error creating group");
                okDialogMsg.innerHTML = error;
                okDialog.show();
            }
        });
    }
}

//Process the editing of an existing group in the database
function doEditGroup(e) {
    e.preventDefault();
    e.stopPropagation();
    dojo.byId("edit_group_ajax").value = "1";
    if(this.isValid()) {
        dojo.xhrPost({
            form: this.domNode,
            handleAs: "json",
            load: function(data) {
                if(data.success) {
                    okDialog.set("title","Group renamed successfully");
                    okDialogMsg.innerHTML = "The group <strong>"+data.name+
"</strong> was renamed successfully.";

                    var group = groupsTree.get("selectedItem");
                    groupsStore.setValue(group, "name", data.name);
                    groupsStore.save();

                    editGroupDialog.hide();
                    okDialog.show();
                }
                else {
                    okDialog.set("title","Error renaming group");
                    okDialogMsg.innerHTML = data.error;
                    okDialog.show();
                }
            },
            error: function(error) {
                okDialog.set("title","Error renaming group");
                okDialogMsg.innerHTML = error;
                okDialog.show();
            }
        });
    }
}

These functions will be called when the user tries to submit the New Group or Rename Group forms. They override the default submit action and update a flag to say that the form is being submitted using Ajax and should receive a JSON response. An XHR POST is then performed to the URL in the form's action attribute. When the response is received, the tree is updated accordingly, and a success dialog message is displayed. Before you can test the functions, however, you need to connect them to the relevant forms. This is shown in Listing 29.

Listing 29. Connecting form submit to relevant functions
dojo.connect(newGroupDialog, "onShow", null, function(e) {
    dijit.byId("new_group_name").reset();
});
dojo.connect(newGroupForm, "onSubmit", null, doNewGroup);
dojo.connect(newGroupCancel, "onClick", null, function(e) {
    newGroupDialog.hide();
});        

dojo.connect(editGroupDialog, "onShow", null, function(e) {
    dijit.byId("edit_group_name").reset();
});
dojo.connect(editGroupForm, "onSubmit", null, doEditGroup);
dojo.connect(editGroupCancel, "onClick", null, function(e) {
    editGroupDialog.hide();
});

When the dialog boxes are displayed, the form values will be reset. Also, when you click Cancel in a dialog box, the dialog is hidden. You should now be able to add and rename groups in the application.

Adding, editing, and moving contacts

Adding and editing contacts are actually done using the same dialog, and you have already provided the functions to distinguish between new and existing contacts. As a result, only a single function is required to take care of this operation. You will also create a function now for handling moving contacts from one group to another. These functions are defined in Listing 30.

Listing 30. Functions to add, edit, and move contacts
function doMoveContact(e) {
    e.preventDefault();
    e.stopPropagation();
    dojo.byId("move_contact_ajax").value = "1";
    if(this.isValid()) {
        dojo.xhrPost({
            form: this.domNode,
            handleAs: "json",
            load: function(data) {
                if(data.success) {
                    okDialog.set("title","Contact moved successfully");
                    okDialogMsg.innerHTML = "The contact was moved successfully.";

                    var treeSel = groupsTree.get("selectedItem");
                    var groupId;
                    if(treeSel) {
                        groupId = treeSel.id;
                    } else {
                        groupId = 0;
                    }
                    var url = contactsStore.url+"?group_id="+groupId;
                    var newStore = new dojo.data.ItemFileReadStore({url:url});
                    contactsGrid.setStore(newStore);
                    refreshGrid();
                        
                    moveContactDialog.hide();
                    okDialog.show();
                }
                else {
                    okDialog.set("title","Error moving contact");
                    okDialogMsg.innerHTML = data.error;
                    okDialog.show();
                }
            },
            error: function(error) {
                okDialog.set("title","Error moving contact");
                okDialogMsg.innerHTML = error;
                okDialog.show();
            }
        });
    }
}

//Process the editing of an existing contact in the database
function doEditContact(e) {
    e.preventDefault();
    e.stopPropagation();
    dojo.byId("edit_contact_ajax").value = "1";
    if(this.isValid()) {
        dojo.xhrPost({
            form: this.domNode,
            handleAs: "json",
            load: function(data) {
                if(data.success) {
                    if(data.new_contact) {
                        okDialog.set("title","Contact added successfully");
                        okDialogMsg.innerHTML = "The contact was added successfully.";
                    } else {
                        okDialog.set("title","Contact edited successfully");
                        okDialogMsg.innerHTML = "The contact was edited successfully.";
                    }

                    var treeSel = groupsTree.get("selectedItem");
                    var groupId;
                    if(treeSel) {
                        groupId = treeSel.id;
                    } else {
                        groupId = 0;
                    }
                    var url = contactsStore.url+"?group_id="+groupId;
                    var newStore = new dojo.data.ItemFileReadStore({url:url});
                    contactsGrid.setStore(newStore);
                    refreshGrid();
                        
                    editContactDialog.hide();
                    okDialog.show();
                }
                else {
                    if(data.new_contact) {
                        okDialog.set("title","Error adding contact");
                    } else {
                        okDialog.set("title","Error editing contact");
                    }
                    okDialogMsg.innerHTML = data.error;
                    okDialog.show();
                }
            },
            error: function(error) {
                okDialog.set("title","Error editing contact");
                okDialogMsg.innerHTML = error;
                okDialog.show();
            }
        });
    }
}

These functions work in a similar fashion to their corresponding "group" functions, so I won't go into too much detail. Rather than update the tree, however, these functions reload the grid control asynchronously. It's also worth noting that the doEditContact function has some additional logic to determine if the user has just added a new contact or edited an existing one, so that the correct message can be displayed to the user.

The final bit of code that's required is the code to connect these functions to the relevant forms. The code for this is shown in Listing 31.

Listing 31. Connecting the add, edit, and move contact actions
dojo.connect(moveContactDialog, "onShow", null, function(e) {
    var theStore = dijit.byId("move_contact_new").store;
    theStore.close();
    theStore.url = "data/groups.php";
    theStore.fetch();
    dijit.byId("move_contact_new").reset();
});
dojo.connect(moveContactForm, "onSubmit", null, doMoveContact);
dojo.connect(moveContactCancel, "onClick", null, function(e) {
    moveContactDialog.hide();
});

dojo.connect(editContactForm, "onSubmit", null, doEditContact);
dojo.connect(editContactCancel, "onClick", null, function(e) {
    editContactDialog.hide();
});

When the Move Contact dialog is shown, it reloads the store behind the drop-down list of groups so that it includes any newly added, renamed groups, and excludes any deleted ones. With this code added, you can save your application and load it in your browser to see it working in all its glory.

Suggested improvements

Although the sample application created in this tutorial is fully functional, there are a number of features that could be added to enhance the user experience and make the application even more impressive. Unfortunately, I did not have the scope to add these in this tutorial, but they should be relatively straightforward to add should you want to do so yourself. Some of these enhancements might include:

  • Allow subgroups to be added to groups
  • Allow contacts to be added to more than one group
  • Drag and drop moving of contacts
  • Re-ordering groups with drag and drop
  • Flexible contact fields (let users add their own fields for individual contacts)
  • Inline contact editing
  • Inline grid editing
  • Multiple row selection and deletion
  • Add a "Trash" feature for deleted items, allowing for recovery and drag and drop deletion
  • Implement Dojo and PHP object orientation to tidy up code
  • Allow the user to move contacts to another group when deleting groups, rather than just deleting them
  • Use MySQLi in PHP scripts for additional security
  • Add more complex validation rules
  • Add log-in system to allow for more than one user

摘要

This tutorial was centered on the creation of a full-featured sample application that lets you manage contacts. It provided a step-by-step guide to creating a user interface using Dojo widgets, and communicating with a MySQL database using XHR/Ajax calls to a series of PHP API scripts. The tutorial should have given you a head start so that you can take the sample application and amend it to create your own complex Dojo applications.


翻译自: https://www.ibm.com/developerworks/web/tutorials/wa-dojotoolkit/index.html

dojo toolkit

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值