RSS

Monthly Archives: 九月 2009

SharePoint解决方案开发模型系列 – 团队的建立

大约一年前,我曾经在blog上写过一篇文章,讲述了我对于SharePoint解决方案开发模型的一些想法,其中包括了SharePoint解决方案开发的方方面面,从开发团队,到开发环境的建立、物理与逻辑架构的设计、开发流程、信息架构、测试等等等等。这些主题我相信对于SharePoint开发人员、架构师、项目经理而言,都是非常有价值的。

既然直到现在,国内仍然没有任何SharePoint开发书籍(当然也包括我的《SharePoint 2007 开发入门指南》)讲述上面这些主题,所以决定开始在自己的blog上面,陆续就每个主题,撰写一些文章。希望这些文章能帮助到SharePoint技术社区。

每每涉及到比“具体”技术细节更高一层的架构、流程、方法论的东西,通常都很容易引起争议。这是很正常的现象。这些主题和具体的诸如C#语法、怎么做一个Web Part等等都不相同,因为这些主题根本就不会有一个标准答案。每个人心中都对如何设计一个架构、如何实施某个流程都有自己的想法,而环境与条件的不一致,更是使得所谓的“最佳实践”在很多场景中都不适用。所以,如果你对我撰写的文章中的某些内容不认可,没有关系,这些文章中的内容本来就不是“金科玉律”,甚至在你所处的场景中根本就是错误的。这些文章只代表我个人的意见(但其中肯定有不少想法,来自MSDN以及其他人所撰写的博客和文档),同时我也希望它们能成为交流的一个平台。如果你对文章中的内容有意见和想法,欢迎留言。高质量、有价值的留言,通常都能让后来的阅读者受益良多。

任何软件项目的实施,都必须从实施团队开始。所以,首先要讲述的主题,是如何建立一个SharePoint实施团队。

从本质上来说,实施一个SharePoint项目,与其他类型的软件项目,诸如ASP.NET、PHP,都不会有根本的差别。所以一个SharePoint实施团队的组成,也基本上和一个标准的Web项目实施团队相同。在下面的角色描述中,我基本上只会描述和SharePoint相关的部分,而其他通用的内容则会尽量省略。

项目经理

项目经理是整个项目的管理者。他负责指派任务、记录和跟踪进度、向老板们汇报…总之,项目经理的工作就是要保证整个项目处于正常状态,并能顺利完成。在小型团队中,项目经理有可能同时兼任业务分析人员。

业务分析人员

业务分析人员应当与客户充分交流,弄清楚客户的业务需求、流程等等信息(越详细越好)。业务分析人员与架构师一起工作,撰写出应用系统的功能规格说明书(也可能叫其他名字),不管我们叫这份文档什么名字,它都应当至少包含有:
1、整个项目要解决的问题、目标
示例:“ABC公司是一家卖汽车的企业,它总共有300名销售人员,销售人员希望通过一个“客户管理系统”对他们各自的客户进行管理。在这个系统中,销售人员能够查看自己负责的客户、每个客户的详细信息、每个客户的订单历史记录等信息。同时,销售人员还需要通过这个“客户管理系统”提交季度销售预测报告…”
2、针对用户使用场景的User Cases
这里的User Cases信息,应当详尽的描述出用户使用系统的每个具体场景,它将作为整个团队的一个“基础文档”,架构师和开发人员根据这些信息,才能知道程序最终要实现的效果。每个User Case里面都要包含用户几乎每个操作的描述和说明,以及每个主要界面的图示(使用Visio或其他工具绘制),也就是说,它不能含糊,而应该清晰、明确、有针对性。
示例:“User Case 15 – 销售人员Dashboard”
“销售人员在“客户管理系统”主页上点击“Dashboard”按钮(参考User Case 5),就能够打开自己的Dashboard…Dashboard会自动校验当前浏览用户的身份和权限,如果的Dashboard以两栏的方式来展现信息(参考图15-3),其中左栏自上而下会包含5个链接,分别是…销售人员点击了左栏的“历史订单数据”链接之后,页面将转向到“历史订单数据”页面(参考User Case 26)…中间的向右箭头是一个可以允许销售人员将右栏折叠起来的图标,在点击它之后…销售人员可以点击右上角的“返回”图标,以返回到“客户管理系统”主页…”

功能规格说明书不涉及具体的技术细节,不包含如何实现每个场景的技术说明,不包含系统的设计内容。我们可以这样想象,假设团队中突然来了一个陌生人,他希望能了解这个团队到底在做一个什么项目,这个项目是干嘛的,实现了什么功能,我们可以将这份功能规格说明书给他,而他确实可以从这份文档中了解到他希望了解的这些信息。

这份文档不能由业务分析人员一个人独自写成,而一定要有技术人员(以架构师为主)的共同参与。一方面,架构师的参与可以保证规格说明书中的内容,在技术上是可行且合理的,另一方面,也有助于架构师从业务角度了解系统,明白客户的需求。

我个人对功能规格说明书的重要性非常推崇。在项目中,可能我们不会撰写详细的设计文档,可能我们不会撰写详细的部署文档,但一份详细的功能规格说明书确是不可缺少的。它的重要性体现在:
1、让团队中的所有人都明白我们要构建的是一个什么东西。如果没有这样的一份清晰的功能规格说明书,就不能保证团队的所有人都一致了解团队的目标。如果没有它,业务分析人员会根据客户的描述,在自己的大脑中想象出系统应该有的样子,然后口述给开发人员,并假设开发人员完全明白了自己大脑中的想法,而开发人员则会根据自己从业务分析人员那里听到的,在自己的大脑中又试图去想象系统做出来应该是什么样子的,并假设这就是业务分析人员想要的样子…总之,每个人都会根据自己的“想象”,去猜测别人的意图。而最后当开发人员把最好的东西给业务分析人员演示的时候,通常是业务分析人员大吃一惊:“我kao,怎么是这个样子?这根本和我告诉你的是完全不一样的东东…”而开发人员则会辩解:“胡说,这分明就是我根据你告诉我的要求做出来的…”
2、我们有了一个可以和客户讨论的东西。这份文档可以尽早的交给客户审阅,客户可以根据这份文档,了解到系统做出来会是什么样子,如果不满意,客户也可以及早的和开发团队进行沟通:“嗯,不对,在这个地方,其实我们更希望看到一个选择框,而不是用户自己填…”
3、它是业务分析人员与开发人员之间的一份“合同”。业务分析人员可以充分的假定,开发人员最后交付的,就是功能规格文档中所描述的样子,而开发人员也可以充分的假定,业务分析人员需要的,也是文档中所描述的样子。

值得一提的是,功能规格说明书并不会(也不应该)限制开发人员的“自由”。它仅仅包含对业务场景、系统功能的详细描述,但是不会写上应该如何实现。它肯定不会包含诸如“在这里,我们要创建XYZ类和ZYX类,前者用于从数据库中查询QWE表…”

 

Posted by on 2009/09/27 in 未分类

4 Comments

Tags:

用JavaScript实现一个Timeline

这两天在忙着修饰自己部门的Team Site,老板提出了一个期望,想在Team Site首页上放一个Timeline,这样部门有什么新的事件、日程,都能在Timeline上展现出来。这件事本身并非特别麻烦,但是我们的Team Site是放在公司Hosting的SharePoint系统之中(公司提供SharePoint Hosting服务,每个人/部门可以根据自由要求,以自助的方式申请Site来使用),而公司Hosting的系统,是不允许各个网站的所有者使用任何Server Code(服务器端代码)的。从IT管理的角度来说,这也是非常合理的,但这的确大大限制了各个网站的使用者对各自的网站进行定制的能力。换句话说,我能使用的工具,只有SharePoint内置的各个Web Part,以及SharePoint Designer。(在SharePoint 2010中,提供了一个新的特性:Sandboxed Solution,来解决这个问题,各个网站集的管理员可以通过upload的方式,部署功能与权限受限的Solution Package到网站集之中,但又不会影响其他网站集和整个服务器场的安全与稳定。)

在经过一番考虑之后,我确认在不允许服务器端代码的前提下,是可以通过JavaScript和HTML的能力,实现一个Timeline的。下面这张图是最终实现后的效果:
image

这个Timeline分成了3栏,从上至下分别是日、周、月视图,用户可以使用鼠标对每个栏进行滑动操作,以查看之前和之后日期的各个事件。Timeline中的事件,来自于网站中一个日历类别列表中的数据,这样网站使用者只用在列表中添加新的事件,Timeline中就会自动显示出来:
image

实现的关键是两点:
1、使用JS从网站列表中,获取到所需的列表项数据;
2、使用JS和HTML,在SharePoint页面上渲染出Timeline。

首先,我在网站中使用“日历”列表模板,创建了一个新列表。由于在Timeline控件上,我只希望能够显示当天前后30天之内发生的事件,为了更容易的取到当天前后30天的事件列表项,我在列表中创建了一个新的视图,在这个视图中只显示事件开始时间是位于当天前后30天的事件。为什么创建这样的一个视图就能方便我们在页面上用JS获取想要的数据,看到后面大家就明白了。

为了让这个新视图能进行查询过滤,我为列表创建了两个新的计算类型字段,"30DaysBeforeStartTime"和"30DaysAfterStartTime",下图显示了"30DaysBeforeStartTime"的定义方法:
image

"30DaysAfterStartTime"的定义方法也类似,只是公式变成了"=[Start Time]+30"。

很多人都不知道在计算字段中,应该如何使用公式。在这个页面上,有能够使用的所有公式和函数的说明,这里还有一些最常用的公式的示范。

有了这两个字段,我们就可以为新的视图来设置过滤条件了,通过下图中的条件,就能过滤出当天的前后30天之类的事件:
image

有了这个新的视图之后,我们就能保证,我们需要在Timeline中显示的数据,肯定都会被这个视图所包含。接下来我们进入到JS阶段…

如果要用JS获取SharePoint网站中的数据,比较靠谱的方法是用JS调用SharePoint的Web Services接口。SharePoint提供了不少Web Services接口,让我们可以在各种平台和语言中调用,其中就包括运行在页面上的JS。我们需要的是能够从列表中获取数据的Web Services接口,这个接口位于Lists Web Service里面,它提供了一个GetListItems()方法,让我们拿到列表项数据。其中,GetListItems()方法的第二个参数:"viewName",就可以让我们指定列表的一个视图,作为取数据的筛选条件。当然,我们也可以使用GetListItems()方法后面的参数来重新指定筛选条件,但是通过列表视图来制定筛选条件,要简单很多,而且修改起来也容易得多。

如何在JS中调用Web Services的方法在网络上能找到很多很多的文章,我就不再重复了。但我要推荐一个不错的JS库,使用这个JS库,可以免去手工构建SOAP包的麻烦,而且使用也相当的简捷。它包含许多的.js文件,将这些.js文件上载到网站的某个文档库中即可(实际上,并不一定需要复制所有的.js文件,比如,对于我的要求,我只用复制"SPAPI_Core.js"、"SPAPI_Types.js"和"SPAPI_Lists.js"即可)。我将所有这些乱七八糟的文件都放在一个名叫"SupportingFiles"的文档库中:
image

然后,用SharePoint Designer打开网站的母版页文件(默认是"default.master"),添加上对这几个.js文件的引用(图片上显示出还添加了对"http://simile.mit.edu/timeline/api/timeline-api.js"的引用,这个东东下面会讲到):
image

使用上面所介绍的那个JS库,下面所示的代码就可以让我从一个列表中,将列表项取出来:

function getCalendarListItems()
{
    var lists = new SPAPI_Lists("网站URL
");
    var items = lists.getListItems(
        "Timeline", // 要获取数据的列表的显示名称
        "{14CB7B04-46AA-421C-B6B2-C5FBEEBA9F5B}", // 视图的GUID,注意两边要加上大括号
        "", // 查询条件
        "", // 要返回的字段
        100, // 要返回的数据的最大行数
        "", // 查询选项
        null // 网站的GUID,null表示使用上面的SPAPI_Lists构造函数里面的网站URL所对应的网站
        );

    if (items.status == 200)
    {
        var rows = items.responseXML.getElementsByTagName("z:row");       
        return rows; // 如果获取数据成功,将所有数据放在一个数组中,然后返回
    }
    else
    {
        return null;
    }
}

通过JS拿到所需的日历事件数据之后,接下来,就是如何在页面上用HTML+JS渲染出一个Timeline。作为一个典型的ELC(Exist Library Caller),我首先想到的是到网上找找是否已经有人做过类似的东东,果然,在Google Code上就被我找到了一个,嘿嘿嘿…

在这个名为SIMILE Widgets的工具集中,包含了一个用JS实现的Timeline。经过在文档中一阵乱翻,下面的JS代码就能够帮我实现想要的效果(第一行不是JS代码,而是JS代码里面会使用的一个div元素,Timeline就是通过它显示出来):

<div id="my-timeline" style="height: 120px; border: 1px solid #aaa; font-size: 9pt"></div>

var resizeTimerID = null;
function onResize() {
    if (resizeTimerID == null) {
        resizeTimerID = window.setTimeout(function() {
            resizeTimerID = null;
            tl.layout();
        }, 500);
    }
}
window.onresize = onResize;

function formatDateString(originDateStr)
{
    var yearStr = originDateStr.substr(0, 4);
    var monthStr = originDateStr.substr(5, 2);
    var dayStr = originDateStr.substr(8, 2);       
    return monthStr + "/" + dayStr + "/" + yearStr + " " + originDateStr.substr(11);
}

function createTimeLineAndEvents()
{
    var items = getCalendarListItems();
    if (items == null)
    {
        alert("cannot got items from list.");
        return;
    }
    var eventSource = new Timeline.DefaultEventSource();
    for (var i = 0; i < items.length; ++i)
    {
        var ows_EventDate = formatDateString(items[i].getAttribute("ows_EventDate"));
        var ows_EndDate = formatDateString(items[i].getAttribute("ows_EndDate"));
        var ows_Title = items[i].getAttribute("ows_Title");
        var ows_Location = items[i].getAttribute("ows_Location");       
        var eventDate = new Date(ows_EventDate);
        var endDate = new Date(ows_EndDate);
        var event = new Timeline.DefaultEventSource.Event(
            eventDate, //start
            endDate , //end
            eventDate, //latestStart
            endDate , //earliestEnd
            false, //instant
            ows_Title, //text
            ows_Location //description
        );
        eventSource.add(event);
    }

    var bandInfos = [
        Timeline.createBandInfo({
            trackGap:       0.2,
            width:          "60%",
            intervalUnit:   Timeline.DateTime.DAY,
            intervalPixels: 100,
            timeZone : 8,
            eventSource: eventSource
        }),
        Timeline.createBandInfo({
            showEventText:  false,
            trackHeight:    0.5,
               trackGap:       0.2,
            width:          "25%",
            intervalUnit:   Timeline.DateTime.WEEK,
            intervalPixels: 150,
            timeZone : 8,
            eventSource: eventSource
        }),
        Timeline.createBandInfo({
            showEventText:  false,
            trackHeight:    0.5,
               trackGap:       0.2,
            width:          "15%",
            intervalUnit:   Timeline.DateTime.MONTH,
            intervalPixels: 400,
            timeZone : 8,
            eventSource: eventSource
        })
      ];
      bandInfos[1].syncWith = 0;
    bandInfos[2].highlight = true;
    bandInfos[2].syncWith = 1;

    var timeLine = Timeline.create(document.getElementById("my-timeline"), bandInfos);
}

_spBodyOnLoadFunctionNames.push("createTimeLineAndEvents");

唉,实在是有点长,本来不想全部贴出来,可想到也许有人要用的话,所以就…另外,别忘了在母版页里面,添加对"http://simile.mit.edu/timeline/api/timeline-api.js"的引用(如上面的截图所示)。

把上面的这些JS代码(以及一个"div"标签)都放到一个单独的.htm文件中,然后在想要显示Timeline的页面上放一个内容编辑Web部件。通过设置内容编辑Web部件的属性,告诉Web部件从那个.htm文件中去拿要显示出来的HTML源码(这种方式能让我们直接使用SharePoint Designer编辑那个.htm文件中的HTML和JS源码,而不必使用内容编辑Web部件内置的那个笨拙编辑器):
image 

OK,完成。另外提一下,在SharePoint 2010中提供了专门的Client OM,它直接支持使用ECMAScript(标准名词解释:ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。)来访问SharePoint。

 

Posted by on 2009/09/24 in 未分类

3 Comments

Tags:

《Microsoft Office SharePoint Server 2007 管理员指南》配套CD

本书的中文版并没有附带配套CD,如果你需要第25、26章的代码,可以通过下面的链接下载原书的配套CD(由于在版权方面的考虑,其中删除了部分内容)。

 

Posted by on 2009/09/12 in 未分类

1 Comment

Tags: