用ASP技术开发 WEB 调查(投票)系统 (2)
网络整理 - 09-14
二、调查项目的创建和维护
作 者 : 仙人掌工作室
本节我们说明调查项目创建和维护功能的实现。
创建与维护调查项目的起始页面是Startup.html,该页面负责设定PollMaker.asp和ItemMaker.asp等页面使用的帧结构(从上到下共分三个帧)。起始页面所引用的Blank.html只用于指定背景颜色,StartMsg.html提供启动时显示在最下面帧的提示信息。
PollMaker.asp和ItemMaker.asp都包含运行于服务器的ASP脚本,同时也包含(和创建)浏览器脚本以支持客户端操作。运行于服务器的脚本由VBscript写成,而客户端脚本则是Javascript,这使得它既可以运行于Netscape Navigator,也适合于IE。
PollMaker.asp通过查找Poll数据库的MSysObjects系统表获得已经定义的调查项目名字。因此,ASP应用必须具有读取该表的权限。在Access97中的设定方法是:先选择菜单“工具/选项”设置系统对象可见,然后在“工具/安全/用户与组的权限”下设定。在本文所附代码中Poll.mdb已经设置了这个权限。如果要在不同的RDBMS上实现这个调查系统,这部分代码必须改写,使它适应目标数据库上的系统表结构。
要将这些已定义的调查项目名字显示到下拉列表框,首先需要从S_表的表名中除去“S_”前缀,然后将这些字符串格式化为< SELECT>元素的< OPTION>字符串:
< SELECT>
< OPTION VALUE="StartPoll" SELECTED>NEW POLL
< %
' 获取当前已经定义的调查项目名字
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open "poll"
Set objRS = _
objConn.Execute("SELECT Name FROM MSysObjects " & _
"WHERE Type=1 AND Name LIKE 'S_%' ORDER BY Name")
Do While Not objRS.EOF
PollName = Mid(objRS("Name"), 3)
Response.Write("< OPTION VALUE=""" & PollName & """>" & PollName)
objRS.MoveNext
Loop
%>
< /SELECT>
ItemMaker将指定调查项目的所有定义信息下载到浏览器端的Javascript数组中,从而实现问题的快速编辑和创建。所有的问题编辑操作都在浏览器内通过修改两个Javascript数组完成,只有单击“保存” 按钮才可将修改结果写入数据库。
ItemMaker.asp首先将Response.Buffer设置为True,因此在页面生成完成之前HTML输出将一直缓冲在服务器上,这使得产生错误或执行非编辑功能时ItemMaker.asp可以不下载那些Javascript函数而退出。在Sub Main的开始处,程序通过检查表单变量OpType来决定是创建新的调查项目,还是删除调查项目或调查结果。这三个操作均在ItemMaker.asp内完成,即所有受密码保护的功能均在同一脚本内实现,这使得整个系统中密码只在一个地方存取。上述三个操作均需动态地生成与执行SQL语句。不论是脚本执行出现错误,还是指定的操作执行成功,都调用Response.Redirect语句重新加载PollMaker.asp,并把一个说明字符串传递给它:
< %
Dim PollName
Dim Password
Dim objConn
Dim objRS
PollName = Request("PollName")
Password = UCase(Request("Password"))
OpType = Request("OpType") ' 要求ItemMaker.asp执行的操作
SName = "[S_" & PollName & "]"
AName = "[A_" & PollName & "]"
RName = "[R_" & PollName & "]"
Call Main
Sub Main
If Password < > "WEBPOLL" Then ' 如密码错误返回PollMaker.asp并提示
Response.Redirect "PollMaker.asp?Msg=密码错误,请再试一次。"
Exit Sub '从ItemMaker.asp返回
End If
If OpType = "Edit" Then ' 编辑或创建调查调查项目
If PollName = "StartPoll" Then
If Not newPoll() Then '用户没有给出调查项目的名字
Response.Redirect "PollMaker.asp?Msg=请输入调查项目名字"
Exit Sub
End If
SName = "[S_" & PollName & "]"
AName = "[A_" & PollName & "]"
RName = "[R_" & PollName & "]"
End If
ElseIf OpType = "DelResp" Then ' 删除调查项目的已有结果
If PollName = "StartPoll" Then
Response.Redirect "PollMaker.asp?Msg=请输入调查项目名字"
Exit Sub
End If
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open "poll"
Set objRS = Server.CreateObject("ADODB.Recordset")
objConn.Execute "DELETE FROM " & RName
Response.Redirect "PollMaker.asp?Msg=已经删除指定调查项目的投票结果"
ElseIf OpType = "DelPoll" Then ' 删除整个调查项目
If PollName = "StartPoll" Then
Response.Redirect "PollMaker.asp?Msg=不能删除新调查项目"
Exit Sub
End If
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open "poll"
Set objRS = Server.CreateObject("ADODB.Recordset")
objConn.Execute "DROP TABLE " & SName
objConn.Execute "DROP TABLE " & AName
objConn.Execute "DROP TABLE " & RName
Response.Redirect "PollMaker.asp?Msg=已经删除指定的调查项目。"
End If
%>
在运行ItemMaker.asp编辑调查项目时,程序提取Poll数据库中定义的调查项目及其问题定义数据初始化数组stemArray[]和ansArray[]。这些用来初始化的字符串在写入时经过“转义”处理(即调用Escape()函数),从而避免了由于嵌入的引号或其它控制字符可能导致的问题,在使用这些数组之前这些字符串被还原(initPoll()函数):
< script LANGUAGE="Javascript">
var stemIx = 0;
var INumber;
var NoOpinion;
// 初始化数组stemArray[]与ansArray[]
var stemArray = new Array(
< %
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.Open "poll"
Set objRS = Server.CreateObject("ADODB.Recordset")
' 从数据库读取数组内容
objRS.Open "Select * FROM " & SName & " ORDER BY ID", objConn
Do While Not objRS.EOF
' 转换问题与回答字符串以允许使用所有字符
stemStr = Escape(objRS("Stem"))
Response.Write("""" & objRS("ID") & objRS("IType") & _
Left(objRS("NoOpinion"),1) & stemStr & """")
objRS.MoveNext
If Not objRS.EOF Then
Response.Write(",")
End If
Loop
objRS.Close
%>
);
var ansArray = new Array(
< %
' 从数据库读取数组内容
objRS.Open "Select * FROM " & AName & " ORDER BY ID, ALabel", objConn
Do While Not objRS.EOF
ansStr = Escape(objRS("Answer"))
Response.Write("""" & objRS("ID") & objRS("ALabel") & ansStr & """")
objRS.MoveNext
If Not objRS.EOF Then
Response.Write(",")
End If
Loop
objRS.Close
objConn.Close
%>
);
// 初始化
function initPoll() {
writeMsgFrame('< HTML>< BODY BGCOLOR="#c0c0c0">' +
'< CENTER>已经读取或创建调查项目"< %=PollName%>"' +
'< /CENTER>< /BODY>< /HTML>');
if (stemArray.length > 0) INumber = stemArray[0].substr(0,3);
for (i=0; i< stemArray.length; i++) stemArray[i] = unescape(stemArray[i]);
for (i=0; i< ansArray.length; i++) ansArray[i] = unescape(ansArray[i]);
doNav("< "); // 显示第1个问题
} // initPoll()
如前所述,图2所显示的页面由三个帧构成。最上面的帧(ControlFrame)包含由ItemMaker.asp输出的文档,其中大部分代码是用来支持浏览器内的编辑操作的Javascript。这些代码通过动态生成HTML表单在位于中间的帧(ItemFrame)显示问题。在编辑问题的时候,编辑事件将触发保存更新数据到stemArray[]和ansArray[]数组的Javascript函数。最下面的一个帧(MsgFrame)用来显示提示信息。
Javascript函数doNew()在调查项目的最后加入新的问题,它先在stemArray[]的最后加入一行,然后在ansArray[]数组中加入对应于问题类型的合适行数,最后调用doNav()函数显示这个问题:
// 创建新问题并将它加入到调查项目的最后
function doNew() {
stemLen = stemArray.length;
INumber = (stemLen > 0) ?
parseInt(stemArray[stemLen-1].substr(0,3)) : 0;
INumber = " " + (++INumber).toString();
INumber = INumber.substr(INumber.length-3, 3);
for (IType = 0; IType < 5; IType++) {
if(document.ItemType.IType[IType].checked) break;
}
IType++;
NoOpinion =
(document.ItemType.NoOpinion.checked) ? "Y" : "N";
stemArray[stemLen] = INumber + IType + NoOpinion;
if (IType > 3) ACount = parseInt(document.ItemType.nChoices.value);
else if (IType == 3) ACount = 2; // 语义区别
else ACount = 0;
if (ACount > 26) Acount = 26;
if (ACount > 0) {
j = ansArray.length;
for (i=0; i< ACount; i++)
ansArray[j+i] = INumber + String.fromCharCode(65+i);
}
stemIx = stemLen;
doNav(""); // 显示空白的问题
} // doNew()
doMove()函数修改问题在调查项目中的位置(序号),它先将指定问题与答案在各自的数组中重新编号,然后将这两个数组排序:
// 将问题移到指定位置之后
function doMove() {
if (stemArray.length < = 0) return; // 没有已定义的问题
ID = stemArray[stemIx].substr(0,3); // 要移动问题的ID
QTemp = document.ItemType.IAfter.value; // 移到哪里
if (QTemp >= 0 && QTemp < stemArray.length)
NID = stemArray[QTemp].substr(0,3); // 被移动问题新的ID
else if (QTemp == stemArray.length)
NID = stemArray[stemArray.length-1].substr(0,3);
else return;
minID = (ID< NID) ? ID : NID;
maxID = (ID< NID) ? NID : ID;
offset = (ID< NID) ? -1 : 1; // 移动方向
for (i=0; i< stemArray.length; i++) {
thisID = stemArray[i].substr(0,3);
if (thisID>=minID && thisID< =maxID) {
if (thisID == ID)
stemArray[i] = NID + stemArray[i].substr(3);
else {
tempID = " " +
(parseInt(thisID) + offset).toString();
tempID = tempID.substr(tempID.length-3, 3);
stemArray[i] = tempID + stemArray[i].substr(3);
}
}
}
stemArray.sort();
for (i=0; i< ansArray.length; i++) {
thisID = ansArray[i].substr(0,3);
if (thisID>=minID && thisID< =maxID) {
if (thisID == ID)
ansArray[i] = NID + ansArray[i].substr(3);
else {
tempID = " " +
(parseInt(thisID) + offset).toString();
tempID = tempID.substr(tempID.length-3, 3);
ansArray[i] = tempID + ansArray[i].substr(3);
}
}
}
ansArray.sort();
document.ItemType.INumber.value = NID;
doNav("Go->"); // 显示移动后的问题
} // doMove()
doDelete()函数通过重新编号并压缩这两个数组实现问题的删除(将最后几行去掉):
// 确认删除,然后在stemArray[]和ansArray[]中执行删除操作
function doDelete() {
if (confirm("是否删除当前显示的问题?")) {
if (stemArray.length > 0) {
ID = stemArray[stemIx].substr(0,3); // 被删除问题的ID
for (i=stemIx; i< stemArray.length-1; i++)
stemArray[i] = stemArray[i].substr(0,3) +
stemArray[i+1].substr(3); // 调整删除后的数组内容
stemArray.length--;
}
if (ansArray.length > 0) {
delCount = 0;
for (i=0; i< ansArray.length; i++) {
if (ansArray[i].substr(0,3) == ID) {
ansArray[i] = "ZZZ"; // 删除该项(排到最后面)
delCount++;
} else if (ansArray[i].substr(0,3) > ID) {
tempID = " " +
(parseInt(ansArray[i].substr(0,3)) - 1).toString();
tempID = tempID.substr(tempID.length-3, 3);
ansArray[i] = tempID + ansArray[i].substr(3);
}
}
ansArray.sort();
ansArray.length-=delCount;
}
doNav("< "); // 尝试退回前一问题
}
} // doDelete()
doNav()函数支持问题的导航,实现方法是改变需要显示问题的索引号,然后调用writeItem完成输出,代码略。
writeItem是这里最为复杂的函数。它按照问题类型的不同,将HTML代码以字符串的形式输出,这个字符串即为显示与编辑该问题的表单。表单为问题的每个可编辑部件提供了onChange()函数定义。当这个表单被写入页面中间的帧(ItemFrame),用户的编辑操作就会引起对saveStem()或saveAnswer()函数的调用,这些函数负责修改当前调查项目的stemArray[]或ansArray[]数组。
// 在浏览器中显示指定的问题
function writeItem(stemIx) {
var IType = parseInt(stemArray[stemIx].substr(3,1)); // 问题类型
var NoOpinion = stemArray[stemIx].substr(4,1); // 是否允许不回答
var ID = stemArray[stemIx].substr(0,3); // 问题的ID
for (ansIx=0; ansIx< ansArray.length; ansIx++) {
if (ansArray[ansIx].substr(0,3) == ID) break; // 问题的回答
}
if (ansIx >= ansArray.length) ansIx = -1; // 当前问题没有回答结果
outStr = '< HTML>< BODY BGCOLOR="#c0c0c0">' +
'< FORM>' +
'【当前问题编号】' + (stemIx + 1) + '【总问题数】 ' + stemArray.length +
' 【调查项目名字】 < %=PollName%>< /B>< BR>< /table>< TEXTAREA ROWS=2 COLS=85 '+
'onBlur="parent.ControlFrame.saveStem()">' +
stemArray[stemIx].substr(5) + '< /TEXTAREA>< BR>';
if (NoOpinion == "Y" && IType != 5) outStr+=
' < INPUT TYPE=radio>暂不回答 ';
switch (IType) {
case 1: // 是/否
outStr+= '< INPUT TYPE=radio>是 ' +
'< INPUT TYPE=radio>否 ';
break;
case 2: // 赞同程度
...略...
break;
case 3: // 语义区别
...略...
break;
case 4: // 多个选择项
...略...
break;
case 5: // 允许复选
...略...
break;
default:
outStr+= '错误:' + IType + " "
}
outStr+= '< /FORM>';
writeItemFrame(outStr);
} // writeItem()
最后,当用户单击“保存”按钮时执行的是doSave()函数。该函数先创建一个表单(表单中包含的隐藏变量描述了stemArray[]和ansArray[]数组的内容),然后把这个表单提交给PollMaker.asp,在这里完成数据库的更新:
// 将问题保存到数据库
function doSave() {
if (stemArray.length < = 0) return;
outStr = '< HTML>< BODY BGCOLOR="#c0c0c0">< CENTER>正在保存 ' +
'"< %=PollName%>"< /CENTER>' +
'< FORM METHOD=post ACTION="PollMaker.asp">' +
'< INPUT TYPE=hidden VALUE="SavePoll">' +
'< INPUT TYPE=hidden VALUE="< %=PollName%>">'
for (i=0; i< stemArray.length; i++)
outStr+='< INPUT TYPE=hidden VALUE="' +
escape(stemArray[i]) + '">';
for (i=0; i< ansArray.length; i++)
outStr+='< INPUT TYPE=hidden VALUE="' +
escape(ansArray[i]) + '">';
outStr+= '< /FORM>';
with (parent.MsgFrame.document) {
open("text/html");
writeln(outStr);
close();
}
parent.MsgFrame.document.Save.submit();
} // doSave()