Chinaunix首页 | 论坛 | 博客
  • 博客访问: 530577
  • 博文数量: 576
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5020
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(576)

文章存档

2011年(1)

2008年(575)

我的朋友

分类:

2008-10-14 14:54:34

用XML和XSLT进行高级的Web UI设计(三)

编译:

目录树的管理

本文要求读者熟悉JScript,MSXML,XSL/XSLT,DOM。



    这是系列文章的第三篇,主要介绍如何扩展前面两篇文章所介绍的技术,在目录树中插入、修改、删除和改名目录树节点项。所有操作都在客户端实现,这样就降低了服务器的负担。本文所指的客户端用的是IE5.5+。
    在上一篇文章我们所讨论的技术的基础上。我们拟在本文对目录树做如下的更新,更新后的目录树会更加实用。这些新特性包括:
  • 自动化的IDs
  • 没有限制的元数据支持
  • 插入/更新功能
  • 改名功能
  • 删除功能
自动化的IDs

大家已经看到,在我们所讨论的目录树中的每一个元素或者节点都需要一个唯一的ID号。在前面的文章中,这个ID号都由用户来指定,这样做很难保证ID号的唯一性。本文将把这个任务交给脚本来自动完成。
    在目录树的这个版本中,唯一的ID号是在显示树之前通过XSLT来进行即时产生和赋值。请看下面的XSLT代码:

  
  
    
      
    
  

  
    
      
        
      
      
        
            
            
          
        
        
            
            
          
        
      
    
  

    这个式样页生成脚本遍历tree.xml文件并用generate-id()方法将一个唯一的ID赋值给每一个元素或节点。 应该注意到,如果我们的应用是一个N层应用并且节点内容都从数据库里采集的话,我们时不需要这个ID号自动产生过程的,数据库的记录号本身就可以拿来当作我们的ID号。

没有限制的元数据支持

    所有对目录树的操作都允许按需进行元数据描述。也就是说元素名称和值可以任意修改,以便XML文档元素得到充分描述。 例如,下面的客户XML文件是上一篇文章用到过的。此外,你还会注意到两个文件中用户特定的元数据的差别,象publisher,address和phone等。我们的目录树将自动读取并解释这些新的元数据。还有就是作为保留元素"image",可以将"image"元素名随意改为"imageBase"。

  ATL编程
  images/book.gif
  images/bookOpen.gif
  
  context/contextVckb.xml
  
  
下面是本文客户XML文件的一个例子

  COM编程指南
  images/book.gif
  images/bookOpen.gif
  displayTOC(12345)
  context/contextTOC.xml
  VCKBASE OnlineJournal
  1234
  Suite 123
  Beautiful City
  CN
  12345
  (123)132-1234
  
  
    有了目前这个目录树的结构,子元素可从它们的父元素继承。例如,当你在"数据库"节点上单击右键并选择"插入"操作,你会得到一个子元素,同时还得到"数据库"节点所有元数据的描述。

插入/更新功能

    插入与更新很类似,因为两者所操作的是完全一样的数据集,使用的表单也完全一样。所不同的是插入操作的表单初始化为空,而更新操作则用某个值初始化。利用这种相似性,我们可以在一个XSLT式样页中合并插入和更新功能,并用一个显示函数,然后再进行分开的调用。下面是用于插入/更新的XSLT脚本,其功能就是创建表单:

  
  

  
    
        
          
Entity('''')
    XSLT式样页接受两个参数,一个是"action",另一个是"selectedEntity"。参数"action"的有效值只能是"insert"和"update"。而"selectedEntity"的值则为元素的ID号。

图一XSLT 转换结果

插入/更新显示函数

    此函数带有一个参数,用来接收"insert"或者"update"。然后它用selectedEntity 来确定插入或更新哪一个实体元素。它使用上面的XSLT式样页来显示相应的表单。
function insertUpdateDisplay(action) {
  var xslDoc
  var xslTemplate;
  var xslProc;
  var entity;

  xslDoc = new ActiveXObject(''MSXML2.FreeThreadedDOMDocument'')
  xslDoc.async = false;

  xslTemplate = new ActiveXObject(''MSXML2.XSLTemplate'')

  xslDoc.load("admin/insertUpdate.xslt");
  xslTemplate.stylesheet = xslDoc;
  xslProc = xslTemplate.createProcessor();
  entity = xmlDoc.documentElement.selectSingleNode("//entity[@id=''" + selectedEntity +"'']");
  xslProc.input = entity;

  xslProc.addParameter("action", action);
  xslProc.addParameter("selectedEntity", selectedEntity);

  xslProc.transform();
  
  content.innerHTML = xslProc.output;
}           
插入操作/函数

    这个函数的参数是要插入元素的父元素的元素ID。一旦子元素被创建,它便被添加到复原素的contents集合中。
function insertEntity(parentEntityID) {
  var entity;
  var newEntity;
  var element;
  var attribute;
  var xslDoc;
  var i;

  xslDoc = new ActiveXObject(''MSXML2.FreeThreadedDOMDocument'')
  xslDoc.async = false;
  
  xslDoc.load("admin/tree.xslt");

  entity = xmlDoc.documentElement.selectSingleNode("//entity[@id=''" + parentEntityID +"'']");
  newEntity = xmlDoc.createElement("entity");
  attribute = xmlDoc.createAttribute("id");
  attribute.text = document.uniqueID;
  newEntity.attributes.setNamedItem(attribute);

  for(i=0; i < entity.childNodes.length; i++) {
    element = xmlDoc.createElement(entity.childNodes(i).baseName);
    if(entity.childNodes(i).baseName != "contents") {
      element.text = eval(entity.childNodes(i).baseName + ".value")
    }
    newEntity.appendChild(element)
  }
  entity.selectSingleNode("contents").appendChild(newEntity);
  document.all[parentEntityID].insertAdjacentHTML("beforeEnd", newEntity.transformNode(xslDoc));
  document.all[parentEntityID].lastChild.style.display = "block";

  if(document.all[parentEntityID].open == "false") {
    document.all[parentEntityID].onclick();
  }
  saveXML();
}      
更新操作/函数

    函数参数为需要更新的元素的ID,函数遍历当前表单,从中读出数据。修改后,重新转换并它在浏览器的DOM中与现存的节点进行交换。
function updateEntity(entityID) {
  var entity;
  var xslDoc;
  var container;
  var i;

  xslDoc = new ActiveXObject(''MSXML2.FreeThreadedDOMDocument'')
  xslDoc.async = false;
  
  xslDoc.load("admin/tree.xslt");

  entity = xmlDoc.documentElement.selectSingleNode("//entity[@id=''" + entityID +"'']");

  for(i=0; i < entity.childNodes.length; i++) {
    if(entity.childNodes(i).baseName != "contents") {
      entity.childNodes(i).text = eval(entity.childNodes(i).baseName + ".value")
    }
  }
  container = document.createElement("DIV");
  container.innerHTML = entity.transformNode(xslDoc)
  
  container.childNodes(0).style.display = "block";
  document.all[entityID].swapNode(container.childNodes(0));
  container = null;
  saveXML();
}
改名功能

    当用户选择更改元素/节点名称时,调用函数renameEntityBegin()。然后它使用selectedEntity变量来确定要改哪一个元素。 这个方法首先获取所选节点文档对象的name,找到后将contentEditable属性置为true,这样便允许用户敲入。
我们还将输入焦点放到此元素上,将光标移到name 的开始处,设置了几个限定按键输入的事件后,用户就可以修改实体名称了。
function renameEntityBegin() {
  var name;
  
  name = document.all[selectedEntity + "name"]
  name.contentEditable = true;
  name.focus();
  name.style.cursor = "text";
  name.onkeypress = function anonymous() { renameKeyPress(selectedEntity) };
  name.onblur = function anonymous() { renameEntityEnd(selectedEntity) };
  name.onselectstart = null;
  
  document.all[selectedEntity].onclick = null;
}
如果用户按下"回车"键或者激活onBlur事件,renameEntityEnd方法被调用。此方法的参数为要修改名字的元素ID。它对XML文档对象模型(DOM)和浏览器的DOM两者作相应修改。
function renameEntityEnd(entityID) {
  var entity;
  var name;

  entity = xmlDoc.documentElement.selectSingleNode("//entity[@id=''" + entityID +"'']");
  name = document.all[entityID + "name"]
  name.style.cursor = "hand";
  name.contentEditable = false;
  name.onselectstart = function anonymous() { return false };

  entity.selectSingleNode("description").text = name.innerText;

  document.all[entityID].onclick = function anonymous() { clickOnEntity(document.all[entityID]) };
  document.body.onselectstart = returnFalse;
  saveXML();
}
删除功能

    删除功能需要被删除实体的ID作为参数。此函数首先检查此实体没有子孙。否则报错。检查完成后,函数在XML DOM和浏览器的DOM中分别进行删除。
function deleteEntity() {
  var entity;
  
  entity = xmlDoc.documentElement.selectSingleNode("//entity[@id=''" + selectedEntity +"'']");

  if(entity.selectSingleNode("contents").childNodes.length > 0) {
    displayError("You cannot remove an entity that contains children. First remove all children."

., 8000);
  }
  else {
    entity = entity.parentNode.removeChild(entity)
    document.all[selectedEntity].removeNode(true)
	saveXML();
  }
}
重定向用户
    这个版本的目录树还包括一个简单的重定向方法,其参数为一个URL串。在你的目录树XML文件中,你必须在onClick元素的位置调用重定向方法,如图三之高亮部分。


图三在onClick 元素中调用redirect方法

下面图四的高亮部分描述了在tree.xslt文件中如何插入onClick 事件。


图四 插入onClick 事件

与数据库的接口
    如果你需要与数据库的接口,我推荐你在init()、insertEntity()、updateEntity、deleteEntity和renameEntityEnd方法中使用XMLHTTP对象。这些方法应该与你的Web服务联系起来,并且只发送和接收需要实现所请求的XML文件。这个方法将降低大大降低服务器的负载。不用在客户端和服务器之间来回折腾, 一而再,再而三地传输并显示整个目录树XML文件。你可以在幕后轻松实现一次调用来更新你的数据,并根据所执行的动作,只更新客户浏览器需要更新的部分。 在下一部分我将介绍如何更酷功能--实现目录树中元素的拖拽(drag and drop)功能。(待续)

--------------------next---------------------

阅读(272) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~