你也许已经是一个PHP的高手或老手了.或许你已经在PHP项目中经验丰富.如果是这样的话,你是不是更加感觉PHP的工程中使用层次结构的难度?在这篇文章中,我通过对JPSpan一点粗浅的研究,谈谈如何在WEB工程实现GUI设计的方法.
1.使用DHTML
WEB页面的编写是一件很复杂的事情,不管是从审美角度还是从功能角度来看,页面的设计师也许很关注如何使用图片以及CSS使得他看起来象一副画.然而,我们从工程角度,追求的最大目标是,象写JAVA/C++代码一样生成我们需要的界面.打开一个页面的源文件,大多被HTML标签所充斥.不管是否漂亮,每每都杂乱无章.DHTML语言,毫无疑问,就是动态HTML语言,可以使得我们动态生成WEB页面的内容.他使得我们可以利用JavaScript语言构建一个生成页面元素的函数库.在功能界面中,我们最关心的是FORM,因为在FORM中有我们需要的交互控件.我们最常使用的是三类Select,Text,Button.
其次,我们的动态页面生成的过程基于以下几步:
1.设计一个页面的总体框架,这一部分我们可以静态的写在body区内,可以对整个页面进行一个分配.一般我们使用 table来构成.其中最关键的是div标签,下面的例子中,我们就可以看出界面分为三部分:
用于数据存储的不可见区域 id=invisible
用于显示交互控件并进行交互的的区域 id=disarea
用于显示结果表格的区域(我们使用table的形式显示结果) id=tablearea
<body leftMargin=50 topMargin=10 onload='initView()'>
<table>
<!--------------------------------------数据储存区------------------------------------->
<tr colspan="2">
<div id="invisble" style="display:none;">
</div>
</tr>
<tr>
<td><div id="disarea"></div></td>
<td width="100" align="center"> => </td>
<td><div id="tablearea"></div></td>
</tr>
</table>
</body>
2.注意到整个界面的初始化工作的入口就是body标签中的onload='initView()',我们看看initView:
/**
* 初始化界面函数
*/
function initView(){
/*----创建Form----*/
createForm(fmName,'教员信息管理','disarea');
/*----在Form中添加元素----*/
addElement(fmName,'select','dqxx','当前学校:',true);
addElement(fmName,'text','yszxx','原所在学校:',false);
addElement(fmName,'text','jyxm','教员姓名:',true);
addElement(fmName,'select','zzmm','政治面貌:',true);
addElement(fmName,'select','mz','民族:',true);
addElement(fmName,'text','cjgzsj','参加工作时间:',true);
buts = {'csh':'button','zj':'button','sc':'button','cx':'button'};
addGroup(fmName,buts,'按钮:',false);
/*----------设定元素的属性和值以及事件响应--------------*/
setElementValue(fmName,'csh','初始化');
setElementValue(fmName,'zj','增加');
setElementValue(fmName,'sc','删除');
setElementValue(fmName,'cx','查询');
setElementClick(fmName,'csh',cshClick);
setElementClick(fmName,'zj',zjClick);
setElementClick(fmName,'sc',scClick);
setElementClick(fmName,'cx',cxClick);
initForm();
}
initView有点需要说明:createForm,addElement,addGroup,setElementValue等函数都是被定义在一个 函数库utils.js中的函数.(最后会把utils.js贴上)
3.initForm()函数是对界面的必要的控件的数据的初试化.在这个函数中,我们用到了和服务器的交互.
/**
* 代码-控件对照表
*/
var CodeToElement = {'xx':'dqxx','zzmm':'zzmm','mz':'mz','xb':'xb','drfw':'drfw','xl':'xl','zc':'zc',
'gzdc':'gzdc','xzzw':'xzzw','ygzt':'ygzt','ygsf':'ygsf'};
/**
* 给Form初始化数据,一般
*/
function initForm(){
for(key in CodeToElement){
var xmlReader = new menureader(cbHandler);
xmlReader.readcodes(key);
}
}
/**
* 读取服务器的数据的回调函数
*/
var cbHandler={
readcodes:function(res){
//alert(res);
fillOptions(res);
}
}
其中,CodeToElement数组定义了界面需要代码表的名称和我们界面控件名称的一个对照表.这些控件都是Select下拉框,可供用户选择的代码.在服务器端,我们定义了代码表XML文件就像
<?xml version="1.0" encoding="UTF-8"?>
<codes>
<code name="xb" mean="性别">
<element name="男" value="0"/>
<element name="女" value="1"/>
</code>
<code name="xx" mean="学校代码表">
<element name='学校1' value='0'/>
<element name='学校2' value='1'/>
</code>
<code name="mz" mean="民族代码">
<element name='汉' value='0'/>
<element name='蒙' value='1'/>
</code>
<code name="xl" mean="学历">
<element name='大专' value='0'/>
<element name='大本' value='1'/>
</code>
<code name="zc" mean="职称">
<element name='小高' value='0'/>
<element name='中高' value='1'/>
</code>
</codes>
在服务器端我们定义了类MenuReader,他会读取该XML文件,返回给前台,得益于JPSpan提供的结构,因此我们可以在Javascript代码中定义MenuReader对象,并在JS中调用MenuReader的readcodes方法,并且让cbHandler作为回调函数来处理返回的结果,我们的处理就是把结果集设置到所有的select下拉框中.我们先不理会后台如何完成工作,观察前台,我们便能察觉,该WEb页面的工作方式,已经变成了标准的View-Server结构,而不需要在前台有任何PHP脚本,也不需要有任何的页面Reload.完全在一个页面就完成所有的交互的工作,实现了一次加载,多次交互的功能.
4.对于所有的服务器代码我们在下一篇文章中解释,下面,我们看看前台的完整代码.
<!--------------------------jyxxgl.php----------------------------------->
<!--------------------------jyxxgl.php----------------------------------->
<html>
<head>
<title>教员信息管理</title>
<link href="main.css" rel="stylesheet">
<!--------------------------------用例定义的Javascript函数----------------------------------->
<script type="text/javascript" src='utils.js'></script>
<script type="text/javascript" src="viewServer.php?client"></script>
<script type="text/javascript">
/**
* ----定义常量----
*/
var fmName = 'fmJyxx';
/**
* 初始化界面函数
*/
function initView(){
/*----创建Form----*/
createForm(fmName,'教员信息管理','disarea');
/*----在Form中添加元素----*/
addElement(fmName,'select','dqxx','当前学校:',true);
addElement(fmName,'text','yszxx','原所在学校:',false);
addElement(fmName,'text','jyxm','教员姓名:',true);
addElement(fmName,'select','zzmm','政治面貌:',true);
addElement(fmName,'select','mz','民族:',true);
addElement(fmName,'text','cjgzsj','参加工作时间:',true);
buts = {'csh':'button','zj':'button','sc':'button','cx':'button'};
addGroup(fmName,buts,'按钮:',false);
/*----------设定元素的属性和值以及事件响应--------------*/
setElementValue(fmName,'csh','初始化');
setElementValue(fmName,'zj','增加');
setElementValue(fmName,'sc','删除');
setElementValue(fmName,'cx','查询');
setElementClick(fmName,'csh',cshClick);
setElementClick(fmName,'zj',zjClick);
setElementClick(fmName,'sc',scClick);
setElementClick(fmName,'cx',cxClick);
initForm();
}
/**
* 代码-控件对照表
*/
var CodeToElement = {'xx':'dqxx','zzmm':'zzmm','mz':'mz','xb':'xb','drfw':'drfw','xl':'xl','zc':'zc',
'gzdc':'gzdc','xzzw':'xzzw','ygzt':'ygzt','ygsf':'ygsf'};
/**
* 给Form初始化数据,一般
*/
function initForm(){
for(key in CodeToElement){
var xmlReader = new menureader(cbHandler);
xmlReader.readcodes(key);
}
}
/**
* 读取服务器的数据的回调函数
*/
var cbHandler={
readcodes:function(res){
//alert(res);
fillOptions(res);
}
}
/**
* 根据返回的数据填充Select域
*/
function fillOptions(res){
var cn = res[0];
var elemName = getElemByCode(cn);
var cvArr = res[1];
for(var i=1;i<cvArr.length;i++){ //str 格式: [name:sd][value:1]
var str = cvArr[i];
pos = str.indexOf("]");
name = str.substring(str.indexOf(":")+1,pos)
str = str.substring(pos+1,str.length);
pos = str.indexOf("]");
value = str.substring(str.indexOf(":")+1,pos);
addSelectOption(fmName,elemName,name,value);
}
}
/**
* 通过代码的名称取得对应的元素名称
* 是架设代码和前台数据一致的方法
*/
function getElemByCode(codeName){
var cnLc = codeName.toLowerCase();
var cnArr = {'xx':'dqxx','zzmm':'zzmm','mz':'mz','xb':'xb','drfw':'drfw','xl':'xl','zc':'zc',
'gzdc':'gzdc','xzzw':'xzzw','ygzt':'ygzt','ygsf':'ygsf'};
return cnArr[cnLc];
}
/**
* 初始化按钮按下的处理
*/
function cshClick(){
alert('初始化');
}
/**
* 增加按钮按下的处理
*/
function zjClick(){
alert('增加');
}
/**
* 删除按钮按下的处理
*/
function scClick(){
alert('删除');
}
/**
* 查询按钮按下的处理
*/
function cxClick(){
alert('查询');
}
/**
* 遍历Form中的每一个元素
*/
function overGo(){
var elems = document.forms[fmName].elements;
for(i=0;i<elems.length;i++){
if(elems[i].type != 'button' && elems[i].type != 'submit' && elems[i].type != 'reset'){
alert(elems[i].name);
}
}
}
/**
* 把Form的数据封装成一个数组,数组的下标为名称
*/
function encapData(){
var elems = document.forms[fmName].elements;
var arr = new Array(elems.length);
for(i=0;i<elems.length;i++){
arr[elems[i].name] = elems[i].value;
}
return arr;
}
</script>
</head>
<body leftMargin=50 topMargin=10 onload='initView()'>
<table>
<!--------------------------------------数据储存区------------------------------------->
<tr colspan="2">
<div id="invisble" style="display:none;">
</div>
</tr>
<tr>
<td><div id="disarea"></div></td>
<td width="100" align="center"> => </td>
<td><div id="tablearea"></div></td>
</tr>
</table>
</body>
</html>
其中建立和服务器交互的方法:
<script type="text/javascript" src="viewServer.php?client"></script>
这样就可以在JavaScript中使用viewServer.php中定义的PHP对象了,因为JPSpan已经把这些代码进行的转换.
引用到的utils.js
<!-------------------------utils.js--------------------------->
<!-------------------------utils.js--------------------------->
/**
* 建立一个生成一个Form的JavaScript方法
* fmName:Form的名称,默认名称和Id相同
* title :指定一个Form的标题
* posId:指定Form放置的位置,一般设计好界面结构后的某个ID上
* actionUrl:Form的action
* methodName:Form的提交方式POST 或 GET
*/
function createForm(fmName,title,posId,actionUrl,methodName){
var htmlStr = "<form name="+fmName+" id="+fmName+" method="+methodName+" action="+actionUrl+">";
htmlStr += "<div id='table_"+fmName+"'><table border=0>";
htmlStr += "<tr>";
htmlStr += "<td style='white-space: nowrap; background-color: #CCCCCC;' align=center valign=top colspan=2>";
htmlStr += "<b>"+title+"</b>";
htmlStr += "</td>";
htmlStr += "</tr></table></div></form>";
document.getElementById(posId).innerHTML += htmlStr;
}
/**
* 为某个Form增加一个元素
* fmName:Form的名称
* elemType:元素的类型
* elemName:元素的名称
* elemLabel:元素的前面的标签
* isRequired:是否是必录项
*/
function addElement(fmName,elemType,elemName,elemLabel,isRequired){
var tbId = "table_"+fmName;
var htmlStr="<tr>";
htmlStr += "<td align='right' valign='top'>"
if(isRequired){
htmlStr += "<span style='color: #ff0000'>*</span>";
}
htmlStr += "<b>"+elemLabel+"</b></td>";
htmlStr += "<td valign='top' align='left'>";
if(elemType.toLowerCase() == 'select'){
htmlStr += "<select name="+elemName+"></select>";
}else{
htmlStr += "<input name="+elemName+" type="+elemType+"/>";
}
htmlStr += "</td></tr>";;
var tmpStr = document.getElementById(tbId).innerHTML.toLowerCase();
tmpStr = tmpStr.substring(0,tmpStr.lastIndexOf("</table>"));
document.getElementById(tbId).innerHTML = tmpStr+htmlStr+"</table>";
}
/**
* 增加一组元素,这样可以做到一组元素可以在同一行中,而不用折行
* fmName:Form的名称
* elemArr:元素的类型和名称的数组 其中名称作为键,类型为值
* elemLabel:组前面的标签
* isRequired:是否是必录项
*/
function addGroup(fmName,elemArr,elemLabel,isRequired){
var tbId = "table_"+fmName;
var htmlStr="<tr>";
htmlStr += "<td align='right' valign='top'>"
if(isRequired){
htmlStr += "<span style='color: #ff0000'>*</span>";
}
htmlStr += "<b>"+elemLabel+"</b></td>";
htmlStr += "<td valign='top' align='left'>";
for(key in elemArr){
htmlStr += "<input name="+key+" type="+elemArr[key]+" /> ";
}
htmlStr += "</td></tr>";
var tmpStr = document.getElementById(tbId).innerHTML.toLowerCase();
tmpStr = tmpStr.substring(0,tmpStr.lastIndexOf("</table>"));
document.getElementById(tbId).innerHTML = tmpStr+htmlStr+"</table>";
}
/**
* 设定元素的值
* fmName :Form名称
* elemName : 元素名称
* elemValue : 元素值
*/
function setElementValue(fmName,elemName,elemValue){
document.forms[fmName][elemName].value = elemValue;
}
/**
* 设定控件的宽度
*/
function setElementWidth(fmName,elemName,elemWidth){
document.forms[fmName][elemName].width = elemWidth;
}
/**
* 获取元素的值
*/
function getElementValue(fmName,elemName){
return document.forms[fmName][elemName].value;
}
/**
* 为下拉框设置可选的部分
* fmName :Form的名称
* elemName : 元素的名称
* optionArr :Option可选项的数组 key为值 value为名称显示
*/
function setSelectOptions(fmName,elemName,optionArr){
i=0;
for(key in optionArr){
document.forms[fmName][elemName].options[i]=new Option(optionArr[key],key);
i++;
}
document.forms[fmName][elemName].options.lengths=i;
}
/**
* 为下拉框增加一个可选项
*/
function addSelectOption(fmName,elemName,optionName,optionValue){
i = document.forms[fmName][elemName].options.lengths;
if(undefined == i){
i=0;
}
document.forms[fmName][elemName].options.lengths = i+1;
document.forms[fmName][elemName].options[i] = new Option(optionName,optionValue);
}
/**
* 为元素控件设定事件响应
* fmName :Form的名称
* elemName : 元素的名称
* eventName : 事件名称
* funcName : 处理该事件的函数名称
*/
function setElementEventHandler(fmName,elemName,eventName,funcName){
document.forms[fmName][elemName].eventName = funcName;
}
/**
* 设定元素的click事件,对上一个函数的补充
*/
function setElementClick(fmName,elemName,funcName){
//setElementEventHandler(fmName,elemName,'onclick',funcName);
document.forms[fmName][elemName].onclick = funcName;
}
/**
* 设定元素的keypress事件,对上一个函数的补充
*/
function setElementKeypress(fmName,elemName,funcName){
//setElementEventHandler(fmName,elemName,'onkeypress',funcName);
document.forms[fmName][elemName].onkeypress = funcName;
}
我们的viewServer.php是这样的
<?php
//-----------------------------------------------------------------------------------
require_once 'JPSpan/JPSpan.php';
require_once JPSPAN . 'Server/PostOffice.php';
require_once JPSPAN . 'Types.php';
//-----------------------------------------------------------------------------------
class MenuReader{
//读取代码表参数
function readcodes($cn){
$xmlParser = new codesxml();
$xmlParser->setCodename($cn);
$xmlfile = "./codes.xml";
$fd = fopen( $xmlfile, "r" );
$contents = fread($fd, filesize($xmlfile));
fclose( $fd );
$xmlParser->parse($contents);
return array($cn,$xmlParser->objs);
}
}
/**
* 读取存放代码的XML文件的解析类
*/
class codesxml{
var $parser; //解析器
var $objs; //解析后的数组
var $canParse; //是否可以开始解析
var $codeName; //代码表的名称
/**
* 构造函数
*/
function codesxml() {
$this->parser = xml_parser_create();
xml_set_object($this->parser,&$this);
xml_set_element_handler($this->parser,"tag_open","tag_close");
xml_set_character_data_handler($this->parser,"cdata");
$this->objs = array("");
$this->canParse = false;
}
/**
* 设定代码名称
*/
function setCodename($cn){
$this->codeName = $cn;
}
/**
* 解析数据
*/
function parse($data) {
xml_parse($this->parser,$data);
}
/**
* 标签开始
*/
function tag_open($parser,$tag,$attributes) {
if(strcasecmp($tag,'code')==0 && strcasecmp($attributes['NAME'],$this->codeName)==0){
$this->canParse = true;
}
if(strcasecmp($tag,"element")!=0 || !$this->canParse){
return;
}
$str = "";
foreach($attributes as $key=>$value){
$str = $str."[$key:$value]";
}
array_push($this->objs,$str);
}
function cdata($parser,$cdata) {}
function tag_close($parser,$tag) {
if(strcasecmp($tag,'code')==0 && $this->canParse){
$this->canParse = false;
}
}
}
/*-----------------------set up Listener-----------------------------------*/
$S = & new JPSpan_Server_PostOffice();
$S->addHandler(new MenuReader());
$S->addHandler(new LogReader());
//-----------------------------------------------------------------------------------
if (isset($_SERVER['QUERY_STRING']) && strcasecmp($_SERVER['QUERY_STRING'], 'client')==0) {
define('JPSPAN_INCLUDE_COMPRESS',TRUE);
$S->displayClient();
} else {
require_once JPSPAN . 'ErrorHandler.php';
$S->serve();
}
?>
好了,不多作解释,这就是JPSpan提供的框架,当然,还有另外一种方式和服务端进行交互,我们在下一篇JPSpan的深入的文章慢慢讨论.这篇文章,仅仅用一个实例来表达这种清晰分层的方法和体系结构.希望对你的认识有所帮助.对于更深入的细节以后再说.