在上面的两篇文章之中,第一篇讲解了SFData所需要的数据结构,而在第二篇文章之中,介绍了如何为SFData提供这种数据(数据适配器),在这篇文章,我们将介绍系统集成的两个数据适配器SFDataXml和SFDataProject。
这两个数据适配器都是基于XML格式的,虽然SFData根本不关心数据是不是来源于XML,但是我们也很难找到比xml更好的传递数据的方法,每个XML的数据适配器需要完成以下内容:
1.将SFData需要的每一个元素和XML之中的节点对应起来,在请求特定的实体的时候,找到这个实体的数据在XML之中的节点位置;
2.将XML文件之中的字符串转化为实体需要的属性格式
基于这两点,我们有了两个数据适配器SFDataXml和SFDataProject,这两个数据适配器在第二点上基本上是相同的,也就是说,每一个实 体(例如任务),从XML之中读写其属性的方式基本上是类似的,两个适配器主要的不同在于文档主体结构的不同,不同点大致如下:
1.SFDataProject主要是为了能直接支持从Microsoft Project里面导出的XML格式而提供的,而SFDataXml是在这个格式基础上的改进,改进的部分主要是考虑到分块加载和网络应用的性能;
2.SFDataProject之中,任务都是在根文档的Tasks节点下,通过OutlineNumber属性来确定相互之间的树形从属关系,而在 SFDataXml之中,跟文档只有一个Task节点,而每个节点的字节点都应该在其父节点之下,这样就可以很容易的实现分块按需加载;
3.考虑到资源甘特图的使用,SFDataXml之中的资源也是采用和上面说的任务一样的树形结构编排,而SFDataProject之中因为 Microsoft Project没有支持资源甘特图,资源仅仅是一个列表,这意味着SFDataProject将不能很好的支持资源甘特图的显示。
4.SFDataXml支持ChildrenDataUrl和NextSiblingDataUrl这两个属性进行分块加载;
5.SFDataProject实际上并没有支持所有的适配器接口,因为太多复杂,容易造成混乱,没有支持将moveTask和 moveResource接口,这意味着当用户存在移动操作的后,通过adapter.getXml()方法获取到的文档未必是正确的格式,而 SFDataXml没有这个问题,当然,在分块加载的模式下,SFDataXml的getXml()方法返回的文档仅仅包含已经下载的内容。
以上是这两种适配器的主要不同,而这两个适配器在使用之中,都会遇到将XML节点之中的文本内容解析成为SFData指定的实体属性格式的问题(关于 SFData支持的属性列表,在第一篇文章里面可以找到),因此,在适配器之中,都存在一个实体的属性列表(当然,这两个列表实际上是相同的),我们可以 先看看这个列表:
实体类型 | 属性名称 | XML节点名称 | 读写方法 |
任务 | UID | UID | String |
任务 | Summary | Summary | Bool2Int |
任务 | ID | ID | Int |
任务 | OutlineNumber | OutlineNumber | String |
任务 | OutlineLevel | OutlineLevel | Int |
任务 | Start | Start | Time |
任务 | Finish | Finish | Time |
任务 | Name | Name | String |
任务 | ReadOnly | ReadOnly | Bool2Int |
任务 | PercentComplete | PercentComplete | Int |
任务 | Notes | Notes | String |
任务 | ConstraintType | ConstraintType | Int |
任务 | ConstraintDate | ConstraintDate | Time |
任务 | ActualStart | ActualStart | Time |
任务 | ActualFinish | ActualFinish | Time |
任务 | Hyperlink | Hyperlink | String |
任务 | HyperlinkAddress | HyperlinkAddress | String |
任务 | ClassName | ClassName | String |
任务 | Collapse | Collapse | Bool2Int |
任务 | LineHeight | LineHeight | Int |
任务 | Critical | Critical | Bool2Int |
任务 | BaselineStart | BaselineStart | Time |
任务 | BaselineFinish | BaselineFinish | Time |
资源 | UID | UID | String |
资源 | Summary | Summary | Bool2Int |
资源 | Collapse | Collapse | Bool2Int |
资源 | Name | Name | String |
资源 | ID | ID | Int |
资源 | OutlineNumber | OutlineNumber | String |
资源 | OutlineLevel | OutlineLevel | Int |
资源 | ReadOnly | ReadOnly | Bool2Int |
资源 | Notes | Notes | String |
链接 | UID | UID | String |
链接 | PredecessorUID | PredecessorUID | String |
链接 | SuccessorUID | SuccessorUID | String |
链接 | Type | Type | Int |
资源分配 | UID | UID | String |
资源分配 | TaskUID | TaskUID | String |
资源分配 | ResourceUID | ResourceUID | String |
资源分配 | Units | Units | Float |
从上表可以知道以下几点:
1.目前支持的所有属性,系统默认读取的XML节点名称和属性名称都是保持一致的,例如任务有一个属性叫Start,则要求这个属性的内容在XML文件之中的节点名称也叫Start;
2.目前SFDataXml和SFDataProject支持了前面提到的SFData的所有的属性,这意味着大部分情况下,不需要为这两个适配器添加新的属性支持
3.每一种属性的读写方式必须和SFData之中指定的类型对应,例如"Start"属性,在SFData之中定义为“日期时间”类型的属性,这里的读写类型就只能是"Time"
当然,假如要添加属性支持也是很容易的,只需要调用适配器的addTaskProperty(proName,tagName,type) , addResourceProperty(proName,tagName,type) , addLinkProperty(proName,tagName,type) , addAssignmentProperty(proName,tagName,type)这四个方法就可以为特定类型的实体添加新的属性支持,例如,我 们要增加任务的“创建时间”属性的支持,这当然是一个日期时间类型的属性,我们假设它在xml之中的节点名称为"CreateDate",我们可以这样添 加此属性:
adapter.addTaskProperty("CreateDate","CreateDate",SFDataRender.getType("Time"));
上面的第一个参数是属性名称,第二个参数是XML的节点名称(很多时候这两个参数是一致的,不过例如你的XML文件之中"StartTime"代表开始 时间而不是系统默认的"Start",就会不同了),第三个参数是XML内容读写类型,这个参数确定在读取实体的时候XML文件之中的字符串怎样转化成为 属性的值,也确定在更新实体的时候怎样将实体的属性怎样写入到XML文件之中,目前系统支持如下5种读写类型:
读写类型名称 | 说明 |
Int | 读取XML时将XML节点内容使用parseInt(str)转化为整数,写入XML时将也保证必须写入整数字符串 |
Float | 读取XML时将XML节点内容使用parseFloat(str)转化为浮点数,写入XML时将也保证写入浮点数字符串 |
String | 不进行任何转化 |
Time | 读取XML时将XML节点内容使用SFGlobal.getDate(str)转化为日期对象,写入XML时将日期属性使用SFGlobal.getDateString(value,"s")转化成2009-05-03T14:21:09的格式写入XML |
Bool2Int | 读取XML的时候将"1"转化为true,"0"转化为false,写入XML的时候相反 |
虽然支持的读写方式比较少,可是实际上已经能够满足需求,毕竟,在这个转化的过程之中,不太可能去实现太复杂的逻辑,不过,如果实在需要自定义读写类 型,也是可以的,只需要定义readFunc和writeFunc即可,其中readFunc(node)的参数是一个XML节点,需要返回从该XML节 点读到的值,writeFunc(node,value)有两个参数,XML节点和属性值,需要将该属性写入到XML节点之中,例如,我们假设要实现一个 这样的读写方式: xml文件之中存储的是字符串"true"和"false",要求,而这个属性(例如Collapse)是一个布尔值,我们可以这么用:
function readFunc(node)
{
if(SFAjax.getNodeValue(node)=="true"){return true;}
return false;
}
function writeFunc(node,value)
{
var str=value?"true":"false";
SFAjax.setNodeValue(node,str);
}
renderType=new SFDataRender(readFunc,writeFunc)
adapter.addTaskProperty("Collapse","Collapse",renderType);
这样,Collapse属性就改成了支持用字符串"true"和"false"来保存,而不是Project之中支持的"0"和"1"了。
这篇文章之中,我们简单的讲解了SFDataXml和SFDataProject的实现,主要花精力说明了用户关心的属性自定义的问题,在这里有一点需 要补充:就是SFDataXml和SFDataProject都支持通过saveChange参数来指定是不是接收SFData的更改通知,如果 saveChange参数为true,你可以随时通过适配器的getXml()返回获得更改后的XML文件,如果为false,适配器则不会将用户的更改 保存下来,因为SFDataProject并没有完美的支持将用户的更改保存到文件,因此默认情况下,SFDataProject的saveChange 参数为false,而SFDataXml的saveChange参数为true.