作者: Trond Ulseth (www.waterswing.com/blog) 译者: Franks
绪言
略(译者按:主要是作者回顾这几年的编程生活,以及为什么写这篇教程的原因。原因主要是,关于Mach-II的实战教程太少了。)
准备
首先点击进入mach-ii的代码下载页面(http://www.mach-ii.com/code.cfm),下载最新版本的框架代码(目前是
1.0.10
版),以及程序骨架代码(名为:MachAppSkeleton_3.zip)。解压它们道你网站服务器的根目录下(译者按:一般是wwwroot文件夹)。在我的及其上,它们如下放置:
C:/Inetpub/wwwroot/MachII
C:/Inetpub/wwwroot/MachAppSkeleton
(译者的存放地址为:
D:/CFusionMX7/wwwroot/MachII
D:/CFusionMX7/wwwroot/MachAppSkeleton)
这样的放置方法是我为这个教程假定的。换成其他设置也不是很麻烦,不过我在此不想修改它(或许以后版本的教程我会该吧)。
现在把MachAppSkeleton文件夹名称改为MyGuestbook。
打开MyGuestbook文件夹里的Application.cfm文件,将cfapplication标签的name属性改为“My Guestbook”。
<cfapplication name="My Guestbook" sessionmanagement="yes" />
打开config目录下的mach-ii.xml文件,修改applicationRoot为/MyGuestbook。
<properties>
<property name="applicationRoot" value="/MyGuestbook" />
<property name="defaultEvent" value="DEFAULT_EVENT" />
<property name="eventParameter" value="event" />
<property name="parameterPrecedence" value="form" />
<property name="maxEvents" value="10" />
<property name="exceptionEvent" value="exceptionEvent" />
</properties>
(在第三节内容最后,又更全的例子)
现在,你可以访问http://localhost/MyGuestbook,会得到一个空白页面。
然而,空白页不久之后就会使你厌倦,因为guestbook在无详细信息的状态下会经常要调用这页,所以即使沙发都开始召唤你,也要多坚持一会儿哦。
Mach-II中的所有东西都是由事件控制,起码我是这么认为~。或许以后的某个操作里我们会发现这个想法是错的。但现在我得假设它是这样的。为了显示一些内容,我们需要在mach-ii.xml文件(mach-ii.xml文件可以说只mach-ii框架的心脏,所以先不急着把它关闭,我们对它还有很多操作要完成)定义defaultEvent。把defaultEvent值改为“showMain”。
<properties>
<property name="applicationRoot" value="/MyGuestbook" />
<property name="defaultEvent" value="showMain" />
<property name="eventParameter" value="event" />
<property name="parameterPrecedence" value="form" />
<property name="maxEvents" value="10" />
<property name="exceptionEvent" value="exceptionEvent" />
</properties>
现在我们来确定showMain的任务。在mach-ii.xml文件里,找到下面的事件句柄(event handlers):
<!-- EVENT-HANDLERS -->
<event-handlers>
<event-handler event="DEFAULT_EVENT" access="public">
<!-- any legal elements -->
</event-handler>
我们来做写修改。将event值改为showMain(这是因为我们之前将这个事件已经声明为默认事件),然后放入一个叫做“view-page”的元素:
<!-- EVENT-HANDLERS -->
<event-handlers>
<event-handler event="showMain" access="public">
<view-page name="mainTemplate" />
</event-handler>
正如你可能猜到的那样(之后我将全面解释),view-page是用来告诉程序该在浏览器窗口显示什么内容。
这儿还有一点工作要在mach-ii.xml文件(已经说过了哦,我们要做很多工作在这里)做。找到page views部分:
<!-- PAGE-VIEWS -->
<page-views>
<page-view name="NEW_VIEW_NAME" page="/views/NEW_VIEW_FILE.cfm" />
<page-view name="exception" page="/views/exception.cfm" />
</page-views>
(这里,我们可以看到叫做exception另一个page view,先不用管它。它可能因为某种原因存在,以后我们可能会发现。)
<!DOCTYPE HTML PUBLIC "-//W
3C
//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>My Guestbook</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body>
<h2>Welcome to my Guestbook!</h2>
</body>
</html>
现在再打开http://localhost/MyGuestbook,看看有没有什么变化。哇!看到效果了吧,实际上它和单个显示的mainTemplate.cfm文件效果没什么不同。但是,我们要一定相信付出终有回报的。
好了,现在我们要给框架添加一些实用的东西了。
开始mach-ii开发
现在提供数据给我们的程序了。首先,我们要为访问者提供留言的机会。
在mainTemplate.cfm的</head> 标签下添加下面的基本表单:
<form method=”post” name=”msgForm”>
<fieldset>
<legend>Add your message</legend>
Name:<br>
<input name="name" type="text"><br>
Email:<br>
<input name="email" type="text"><br>
Message:<br>
<textarea name="message" cols="40" rows="5" wrap="virtual"></textarea><br>
<input type="submit" name="Submit" value="Post message">
</fieldset>
</form>
在研究如何传送登录数据(注意,我们还没有添加任何action属性给表单)给mach-ii之前,我们得有个存放数据的地方。在这个示例里,我们将采用数据库技术(xml文件存储是另一个比较好的选择)。
我在MySQL数据库里添加了MyGuestbook数据录文件,目前其中只用一张表。
Guestbook
id
int(11) auto_increment, primary key
date datetime
name varchar(50)
email
varchar(50)
message
longtex t
这里有数据表生成脚本:
CREATE TABLE `guestbook` (
`id` int(11) NOT NULL auto_increment,
`date` datetime NOT NULL default '0000-00-00 00:00:00',
`name` varchar(50) NOT NULL default '',
`email` varchar(50) NOT NULL default '',
`message` longtext NOT NULL,
PRIMARY KEY (`id`)
) TYPE=MyISAM AUTO_INCREMENT=1 ;
建好数据库了吧?好。然后你需要在ColdFusion administrator里注册你的数据库。起名为MyGuestbook(好,我在这里先预言,我要让mach-ii框架自己给你一个惊喜)。
如果你准备依葫芦画瓢的话,你必须习惯在你的application.cfm文件里的昂一你的dsn(一般像这样:<cfset db=”MyGuestbook”>)。注意这一步不是在mach-ii做。回来看mach-ii.xml文件,在properties部分添加一个变量:
<properties>
<property name="applicationRoot" value="/MyGuestbook" />
<property name="defaultEvent" value="showMain" />
<property name="eventParameter" value="event" />
<property name="parameterPrecedence" value="form" />
<property name="maxEvents" value="10" />
<property name="exceptionEvent" value="exceptionEvent" />
<!-- Application variables -->
<property name="dsn" value="MyGuestbook" />
</properties>
尝试一下,向数据库添加留言。第一步,添加一个action参数给表单。应该还记得我说过的mach-ii里万物都由事件控制吧?记得要像下面在这样设置action参数:
<form action="index.cfm?event=message.create" method="post" name="msgForm">
同样记得事件结合事件句柄的工作方式,为事件message.create添加一个事件句柄:
<event-handler event="message.create" access="public">
<event-bean name="message" type="MyGuestbook.model.message.message" />
<notify listener="messageListener" method="createMessage" />
</event-handler>
哇欧,发生了什么?在之前我们完成的事件句柄中,我们有一个view-page方法。
现在介绍两个新东西,我们即将更深入地研究下去:
Bean
事件句柄里的第一个新方法是“event-bean”。Bean是模拟简单对象实例的ColdFusion组件。在我们的实例里,bean将依照表单信息创建一个message对象。每个bean含有一个init()方法,以及各个对象属性相对应的接收器(getter)和设置器(setter)。
在我讲解前先看看代码:
<cfcomponent displayname="message" hint="I model a single message">
<cffunction name="init" access="public" returntype="message" output="false" displayname="Message Constructor"
hint="I initialize a message.">
<cfargument name="id" type="numeric" required="false" default="0" displayname="" hint="" />
<cfargument name="date" type="date" required="false" default="#Now()#" displayname="" hint="" />
<cfargument name="name" type="string" required="false" default="" displayname="" hint="" />
<cfargument name="email" type="string" required="false" default="" displayname="" hint="" />
<cfargument name="message" type="string" required="false" default="" displayname="" hint="" />
<cfscript>
variables.instance = structNew();
setID(arguments.id);
setDate(arguments.date);
setName(arguments.name);
setEmail(arguments.email);
setMessage(arguments.message);
</cfscript>
<cfreturn this />
</cffunction>
<!--- GETTERS/SETTERS --->
<cffunction name="getID" access="package" returntype="numeric" output="false" displayname="" hint="I return ID">
<cfreturn variables.instance.id />
</cffunction>
<cffunction name="setID" access="package" returntype="void" output="false" displayname="" hint="I set ID">
<cfargument name="id" type="numeric" required="true" />
<cfset variables.instance.id = arguments.id />
</cffunction>
<cffunction name="getDate" access="package" returntype="date" output="false" displayname="" hint="I return date">
<cfreturn variables.instance.date />
</cffunction>
<cffunction name="setDate" access="package" returntype="void" output="false" displayname="" hint="I set date">
<cfargument name="date" type="date" required="true" />
<cfset variables.instance.date = arguments.date />
</cffunction>
<cffunction name="getName" access="package" returntype="string" output="false" displayname="" hint="I return name">
<cfreturn variables.instance.name />
</cffunction>
<cffunction name="setName" access="package" returntype="void" output="false" displayname="" hint="I set name">
<cfargument name="name" type="string" required="true" />
<cfset variables.instance.name = arguments.name />
</cffunction>
<cffunction name="getEmail" access="package" returntype="string" output="false" displayname="" hint="I return email">
<cfreturn variables.instance.email />
</cffunction>
<cffunction name="setEmail" access="package" returntype="void" output="false" displayname="" hint="I set email">
<cfargument name="email" type="string" required="true" />
<cfset variables.instance.email = arguments.email />
</cffunction>
<cffunction name="getMessage" access="package" returntype="string" output="false" displayname="" hint="I return
message">
<cfreturn variables.instance.message />
</cffunction>
<cffunction name="setMessage" access="package" returntype="void" output="false" displayname="" hint="I set
message">
<cfargument name="message" type="string" required="true" />
<cfset variables.instance.message = arguments.message />
</cffunction>
</cfcomponent>
保存bean为c://inetpub/wwwroot/MyGuestbook/model/message/message.cfc
现在注意看我们是怎样调用这个bean的:
<event-bean name="message" type="MyGuestbook.model.message.message" />
type属性实际上是这个cf组件在网络根目录下的相对路径,使用 . 标记法。
看看bean的内部结构。当我们使用“event-bean”方法,mach-ii通过可用的表单和url参数传递变量,以及调用init()方法。
这时候,init()方法调用内部设置器方法,后者使用从表单或者/和url得到的变量来处理数据。
所以,我们现在已经通过传递表单数据哟哦那个有了一个message对象的实例。
Listener
首先,listener必须在mach-ii.xml文件里注册。
<!-- LISTENERS -->
<listeners>
<!--
<listener name="yourListenerName" type="fullyQualifiedDotDelimitedPathToCFC">
<invoker type="MachII.framework.invokers.CFCInvoker_Event" />
<parameters>
<parameter name="yourParameterName" value="yourParameterValue" />
</parameters>
</listener>
-->
</listeners>
根据注释,很容易将它配置成我们需要的:
<!-- LISTENERS -->
<listeners>
<listener name="messageListener" type="MyGuestbook.model.message.messageListener">
<invoker type="MachII.framework.invokers.CFCInvoker_Event" />
</listener>
</listeners>
这里,我们给listener一个适当的名称,以及像之前event-bean那样的以点标记形式出现的用来定位该组件路径的type属性(××)。
Invoker type属性先不做修改,以后我们将修改它的参数。这看上去有点陌生,不过正如我们即将看到的,mach-ii框架使用我们刚刚建立的bean来支持listener,而这个bean已经包含了我们目前需要的所有参数。
是时候看看messageListener.cfc的代码了:
<cfcomponent extends="MachII.framework.Listener" displayname="Message Listener" hint="I am the listener for
messages">
<cffunction name="configure" access="public" returntype="void" output="true" displayname="Listener Constructor"
hint="I initialize this listener as part of the framework startup.">
<cfscript>
var dsn = getAppManager().getPropertyManager().getProperty("dsn");
variables.messageCRUD = CreateObject("component", "MyGuestbook.model.message.messageCRUD").init(dsn,
"MyGuestbook");
</cfscript>
</cffunction>
<cffunction name="createMessage" access="public" returntype="void" output="false" displayname="Create Message"
hint="I cause message to be created from the current event object.">
<cfargument name="event" type="MachII.framework.Event" required="yes" displayname="Event" hint="I am the current
event" />
<cfset var message = arguments.event.getArg("message") />
<cfset variables.messageCRUD.create(message) />
</cffunction>
</cfcomponent>
框架在启动的时候(本例中就是我们刚打开http://localhost/MyGuestbook的时候),初始化listener的configure(配置)方法。总之,我们这里的配置方法“钓”出我们在mach-ii.xml文件里定义的数据库,将它“喂”CRUD组件(一分钟以后你就会知道什么是CRUD)。
回顾createMessage方法,在名为event的变量可以看到,框架把当前的bean看作一个变量收集器。我们在配置方法里创建一个叫message的变量(var),用它传递变量给messageCRUD对象的create方法。
CRUD
如果你感觉bean和listener的概念太新颖和富有挑战性,那么CRUD组件的话题会让你轻松一点。CRUD组件的任务是完成与数据库的交流,所以包含了很多常见的SQL语句。然而CRUD组件不会触发所有与数据库的通讯,它们每次只处理一条记录,主要采用四种SQL 方法(INSERT,SELECT,UPDATE和DELETE)。对应的CRUD有四个方法:Create,Read,Update以及Delete。
到现在,我们的CRUD组件已经使用了第一个方法,Create(它同样有init方法在顶端,这个方法被listener的配置(configure)方法调用,进而访问数据库)。
<cfcomponent displayname="" hint="I abstract data access for messages">
<cffunction name="init" access="public" returntype="MyGuestbook.model.message.messageCRUD" output="false" >
<cfargument name="dsn" type="string" required="true" />
<cfset variables.dsn = arguments.dsn />
<cfreturn this />
</cffunction>
<cffunction name="create" returntype="void" output="false" hint="CRUD method">
<cfargument name="message" type="MyGuestbook.model.message.message" required="yes"
displayname="create" hint="I am the message from which to create a record" />
<cfset var messageInsert = 0 />
<cfquery name="messageInsert" datasource="#variables.dsn#" >
INSERT INTO guestbook (
date,
name,
email,
message
)
VALUES (
#arguments.message.getDate()#,
'#trim(arguments.message.getName())#',
'#trim(arguments.message.getEmail())#',
'#trim(arguments.message.getMessage())#'
)
</cfquery>
<cfreturn />
</cffunction>
</cfcomponent>
最后,打开http://localhost/MyGuestbook,填写好表单然后提交。如果你的代码和我们的一样,你将得到一个空白页。当你查看你的数据库的时候,你将发现你提交的信息已经给记录在其中了(以后我们还要delete等方法)。
准备好应付今后的任务了吗?我们将用首页来代替这个空白页。我敢肯定以已经心里有数了吧。
6个默认变量
还记得我们一开始修改mach-ii的6个默认变量吗?
在结束教程上半部分的时候,我们来看看这全部的6个变量以及它们的作用。
在我们修改前,它们像这样:
<property name="applicationRoot" value="/MyGuestbook" />
<property name="defaultEvent" value="showMain" />
<property name="eventParameter" value="event" />
<property name="parameterPrecedence" value="form" />
<property name="maxEvents" value="10" />
<property name="exceptionEvent" value="exceptionEvent" />
applicationRoot:
这是程序相对于服务器根目录的路径;
defaultEvent:
还记得我说过mach-ii里万物皆受控于事件吗?事件被url或者表单变量声明。但是如果没有url或者表单变量提供呢?答案就是:defaultEvent
eventParameter:
我们已经知道事件是由url或者表单变量触发的。但它会是什么样的url或表单变量呢?如果所有的url或表单变量都可以触发事件,事情将便得很混乱。因此,eventParameter决定触发变量的名称。默认状况下是“event”。更进一步,我们设置表单的开头为:
<form action="index.cfm?event=message.create" method="post" name="msgForm">
我们还可以将eventParameter该成其他,比如“go”,那么表单页该做相应调整:
<form action="index.cfm?go=message.create" method="post" name="msgForm">
parameterPrecedence:
既然事件会被url和表单的eventParameter触发,那么何时是eventParameter出现,而不是其他复本呢?有了parameterPrecedence,我们就可以确定哪一个域具有优先级。
maxEvents:
有个东西我们一直没提到,事件可以通告新的事件,在某些情况下这可能导致事件调用发生递规。这就是maxEvents出现的原因了。
exceptionEvent:
异常事件在框架发生非人为触发的错误时被调用。
id
int(11) auto_increment, primary key
date datetime
name varchar(50)
varchar(50)
message
longtex t