通过使用资源文件使SharePoint用户界面本地化

[原文发表地址]Localizing the SharePoint User Interface by Using Resource Files (Sanjay Arora)
 
[原文发表时间] 2011-07-01 1:26 PM

SharePoint本地化主要包括转换基于特定文化或语言的用户界面的文本。这种转换依赖于系统可用的资源文件。本地化解决方案,就要从代码中移除代码中的字符串,并将其抽象到资源文件中。

资源文件的类型

在SharePoint中,有两种类型的资源文件:配置资源,在配置站点和功能时使用,另一种是运行资源,在ASP.Net framework运行时使用。

第一种类型的资源文件,SharePoint在配置过程中会充分利用到,位于下列位置之一:

· $SharePointRoot$\TEMPLATE\FEATURES\<FeatureName>\Resources\

· $SharePointRoot$\Resources\

注意

使用这些资源文件的位置取决于你的解决方案所需的的共享级别。如果这些资源文件具体到某个功能,它们将被归属到特定功能目录。如果资源文件之间被多个功能共享,例如列表定义,网站定义,自定义操作,内容类型等等,这些文件将会出现在SharePoint的资源文件夹根目录下。

第二种类型的资源文件,在ASP.Net运行过程中(aspx/ascx)被利用到,出现在下列位置中:

· $SharePointRoot$\Config\Resources

重点

ASP.Net运行时不知道Sharepoint。因此,这些文件可以在运行时消耗掉,它们在各自的Web应用程序的虚拟目录下的App_GlobalResources路径中都是可用的。

在解决方案中添加资源文件

配置资源文件在SharePoint解决方案的部署过程中被复制到各个位置。那么,SharePoint解决方案是怎样确定部署过程中资源文件的目标位置的?该解决方案清单文件(manifest.xml),定义在解决方案文件的根文件夹中,指定了这些资源文件的目标位置。manifest.xml文件引用了解决方案中所有的组件,并在目标机器中定义它们的位置。如果您用的是Visual Studio 2010,你可以通过右击解决方案的包文件夹预览清单文件,选择试图设计器,然后选择清单标签。

功能特定的资源文件

特定功能资源文件夹中的资源文件的配置,在manifest.xml文件中定义如下:

<Solution> 

    <Resources> 
 
      <Resource Location="NewFeature\NewResources.resx"/> 

    </Resources>

</Solution>

在Visual Studio 2010中,您可以通过右击相应的功能,然后选择添加功能资源,来添加资源文件。

clip_image002

共享的资源文件

SharePoint根目录下的资源文件夹中的资源文件配置,在manifest.xml文件中定义如下:

<Solution>

    <RootFiles> 
 
        <RootFile Location="Resources\NewResources.resx"/> 

    </RootFiles>

<Solution>

在Visual Studio 2010中,你可以通过右击你的解决方案项目,选择添加,然后选择SharePoint映射文件夹,把此资源文件添加到SharePoint根目录下的SharePoint根目录映射文件夹中。最后,将此资源文件添加到新建的资源文件夹中。

clip_image004

另外,你可以添加一个空元素,并用“RootFile”部署类型在该空元素下添加一个资源文件。这个方法和前面方法的不同之处在于改变部署类型的灵活度。

clip_image006

想了解部署类型的更多信息,请参阅部署类型枚举

运行库资源文件

Web应用程序的资源文件配置在部署过程中,把您的文件资源复制到Web应用程序的IIS目录/ App_GlobalResources。它的定义如下:

<Solution>

    <ApplicationResourceFiles>

        <App_GlobalResourceFile Location="NewResource.resx"/>
 
    </ApplicationResourceFiles>

</Solution>

在Visual Studio 2010中,你可以在一个空元素下添加此资源文件。一旦添加了此资源文件,文件的部署类型应设置为AppGlobalResource

clip_image008

注意

如果你在SharePoint映射文件夹Resources中添加一个资源文件,部署类型就不能被编辑。要改变部署类型,您必须添加一个空元素SharePoint项目项到您的项目中,然后在下面添加资源文件。

重点

还有一些其它的资源文件位置是我在这篇文章中没提到的:

virtual _wpresources or $SharePointRoot$\CONFIG\AdminResources

_wpResources:这是全局程序集缓存(GAC)Web部件资源的位置。

AdminResources:此位置有资源扩展管理中心的用户界面使用的本地化资源。

检索本地化字符串

配置资源

使用SPUtility. GetLocalized字符串

这种方法可以从位于SharePoint根目录或相应功能目录项下的资源文件夹下的资源文件中检索字符串值。该方法签名定义在这里

当定义一个功能时,你可以设置DefaultResourceFile属性,指定一个与使用应用程序中的所有功能的本地化资源的共享文件。设置好DefaultResourceFile属性后,SharePoint会在$SharePointRoot$\Resources路径中寻找一个特定文化的资源文件。SharePoint设想该文件会以下面的模式命名<DefaultResourceFile>.<CultureName>.resx,其中,DefaultResourceFileDefaultResourceFile属性值。CultureNameCultureInfo.Name属性所使用的格式。

DefaultResourceFile属性返回的字符串是该文件名的第一部分。

如果从功能定义中省略DefaultResourceFile属性,DefaultResourceFile属性将返回一个空字符串。在这种情况下,Sharepoint从文件系统下的资源文件夹中名为Resources的子文件夹中为该功能检索资源–也就是说,在$SharePointRoot$\TEMPLATES\FEATURES\<FeatureName>\Resources路径下。SharePoint设想该资源文件命名为<CultureName>.resx的模式资源。

重点

SharePoint在检索资源值时,它会寻找一个具有基本文件名的资源文件,该文件名后面是与当前执行线程的CurrentUICulture属性相对应的语言标识符。SharePoint期望找到完全匹配的文件。如果找不到与语言标识符完全匹配的资源文件,则默认为使用通过SPWeb.Language找到的网站的默认语言。这种行为不同于ASP.Net,ASP.Net的最初回退是基本语言,最终回退则是特定区域性语言。

使用表达式生成器语法

在XML标记或协作应用程序标记语言(CAML)中,使用下面的格式的值替换硬编码字符串:

<%$Resources:Resource File Name, String ID%>

如果使用功能文件夹内部的特定功能配置资源文件,记得要在字符串表达式( <%$Resources:String ID%> )中省略资源文件的名称。

运行库资源

使用g HttpContext. GetGlobalResource对象

这种方法可以从Web应用程序的虚拟目录下的App_GlobalResouces文件夹下的Resources文件夹中检索字符串值。该方法签名如这里定义的:

如果使用资源时,要对除ASPX标记以外的代码本地化的话,要将每个文件的生成操作属性设置保留为嵌入资源。这样就使资源被编译进附属程序集。然而,如果你使用资源文件的目的仅仅是使标记本地化,你也可以选择将生成操作转变成内容,以防止文件被编入主要的应用程序部件集。

使用表达式生成器语法

在ASPX页面或控件(ascx)的XML标记中,使用下面格式的值替换硬编码字符串:

<%$Resources:Resource File Name, String ID%>

自动发布具有沙盒解决方案的文件

[原文发表地址]Automatically publishing files provisioned with Sandboxed Solutions[原文发表时间] 2011-01-29 09:48

两天前我在Waldek Mastykarz的博客(顺便说下是一个很棒的博客)上看过这篇博文,现在你们也正在阅读我的这篇同名博文,虽然它也针对了相同的问题,但是却给出了不同的解决方案。那么,具有沙盒解决方案的文件的确切问题是什么?简单地说——当在一个功能中使用“模块”元素把文件配置到SharePoint文档库中时,就出现问题了。不是配置到任一个文档库,而是那个由以下之一配置的文档库:

1. 需要签出文件

2. 启用内容审批

3. 允许次要版本

这些文件当然会被配置到目标库,但是根据启用的配置和以上三条在库中的实际启用组合,他们将是签出和/或草稿或挂起状态。这意味着文件都会被呈现出来,但是站点的一般用户不能访问。这种行为明显是有意和设计好的—我猜想主要原因是网站和网站集管理员要能够审核后赞同或反对沙盒解决方案的内容。

话虽如此,我要指出这个问题我已经知道很久了,甚至还在我以前的一篇博文中解决了一个这个问题的独立案例——它是关于沙盒解决方案的发布页面的设置。发布页面的问题甚至是更严重的—不仅页面以草稿的形式生成及被签出,而且“模块”清单中定义的网站组件也永远不会被配置(更多详情请查阅博客)。发布页面的解决方案是相当简洁的(它并不需要功能接收器及其代码),但是不幸的是它只适用于发布页面。

至于解决方案本身—据了解SPFile.CheckIn, SPFile.PublishSPFile.Approve方法在沙盒解决方案中也都可以使用,因此含有功能接收器和好几行代码的解决方案就会变得很繁琐。显然在本例中,最好的办法就是你有一个可以重复使用的功能接收器,你只写一次,然后可以在任何地方使用而不用修改或调整代码。由于“模块”功能的应用在SharePoint中是非常频繁的,因此这种通用的功能接收器会帮我们节省大量时间和精力。该通用功能接收器的主要技术困难是接收器中的代码应该能够“知道”究竟哪些文件的功能配置到了目标库。内建SPFeatureDefinition.GetElementDefinitions方法在OOB(out of box)提供了这种“意识”类型,但不幸的是,该方法在沙盒对象模型的子集中是找不到的。因此我们需要一个不同的解决方案,我会先以简要解释Waldek博客中的解决方案为开端—它非常简单,只要花一点点额外的精力—只需要在你的“模型”清单文件中的每个“File”元素里添加一个额外的“Property”元素即可,如下所示:

<File Path="Images\someimage.png" Url="someimage.png" Type="GhostableInLibrary">

  <Property Name="FeatureId" Value="$SharePoint.Feature.Id$" Type="string"/>

</File>

想法如下——由于你不会给目标库的列取名为“FeatureId”,这个属性元素的值会被保存在所配置文件的底层SPFile实例的属性包里。属性元素的值不是别的,正是父功能的Guid(注:Visual Studio标志语法的智能使用)。所以基本上,向清单文件中所有“File”元素添加这个额外的“Property”元素之后,你就书面上“标志”了你的 功能所配置的所有文件。接下来功能接收器的逻辑将会迭代目标库中的所有文件,并且在找到所有标记的文件后签入它们,在适当的地方批准或发布它们。此外,你还必须设法向功能接收器提供目标库的名字,这可以通过使用功能的feature.xml文件的功能属性实现。

现在我们来讲为什么我着手这篇文章并想到一个不同的解决方案的原因——上面的解决方案基本上是相当整洁且直截了当的,几乎没有什么额外的实现工作,但是我不喜欢它的地方是目标库中所有文件的迭代,也许碰巧包含成千 的文件(这实际上是非常难以置信的,但是当我想起SharePoint性能的一些方面时,这个想法总是让我浑身起鸡皮疙瘩)。当然有一些方法可以优化迭代—比如,迭代SPList.Items集合而不是SPFolder/SPFile层(前者通常较快),使用内部系统的MetaInfo列表栏,它包括底层SPFile实例序列化的属性包(很不幸,你不能使用CAML过滤本列)。另外,在“Created”, “_Level”和“_ModerationStatus” 域上用CAML过滤将会是一个非常简单有效的优化方法,这样你就可以捕获最近创建的项目(即你往过去设五分钟或十分钟的时间;注意即使文件已经在库中了,当它再次被功能配置时,它的“Created”日期还是会被更新的),它们或是被签出或是草稿或挂起状态。

迄今为止一切顺利,但我还是想找到一个和带有SPFeatureDefinition.GetElementDefinitions方法的“自我意识”接近的方法,以使功能接收器可以准确地掌握功能配置了哪些文件而不必遍历目标库并搜索它们。我突然想到如果需要功能接收器中清单文件的内容,为什么不能简单地把它设置到目标网站上(它在功能定义中作为一个元素文件,但是如果它在清单文件的一个“文件”元素中也被引用的话也没有错),然后从功能接收器中读它的内容,最后当文件被发布之后功能接收器能够安全地将它删除。让我来展示一个简单的“模块”清单文件以使你能够更好地理解我刚才解释的技巧:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Module Name="TestModule" Url="Style Library">

    <File Path="TestModule\Sample.txt" Url="Sample.txt" Type="GhostableInLibrary" />

    <File Path="TestModule\Sample2.txt" Url="Sample2.txt" Type="GhostableInLibrary" />

    <File Path="TestModule\Sample3.txt" Url="test/Sample3.txt" />

  </Module>

  <Module Name="TestModule2">

    <File Path="TestModule\Elements.xml" Url="Elements_$SharePoint.Feature.Id$.xml" Type="Ghostable" />

  </Module>

</Elements>

你可以看到清单文件(它的名字很普通的“elements.xml”)包含了两个“模块”元素――第一个为标准的“Style Library”库设置了若干文件(在SharePoint开发中是一个周期性任务),仔细检查第二个-这是在清单文件中你仅所需要的一个额外部分(另一个部分是可重复使用的功能接收器)――这是一个自己设置清单文件的“模块”元素。注意两件事――清单文件被设置到目标网站的根文件夹――我们不想把这个文件放在一个文档库中,因为这样一来,文件就会对网站用户直接可见。其次――检查这个文件的URL ――它包含了功能的Guid,功能接收器会使用这个确切的Guid,使得当它被执行时就能定位文件。URL的样式应该是这样的:[任意长度并在父功能的 “模块”清单文件中唯一的 字符]_$SharePoint.Feature.Id$.xml(下划线之后的字符部分应该总是相同的)――基本目的是我们想为这个功能中的每个“模块”清单文件获取一个唯一的目标名字。这儿是功能接收器本身的代码:

public class TestFeatureEventReceiver : SPFeatureReceiver
{

    // The SharePoint elements file namespace

    private static readonly XNamespace WS = "http://schemas.microsoft.com/sharepoint/";

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {

        // make it work for both ‘Site’ and ‘Web’ scoped features

        SPWeb web = properties.Feature.Parent as SPWeb;

        if (web == null && properties.Feature.Parent is SPSite) web = ((SPSite)properties.Feature.Parent).RootWeb;

        if (web != null) CheckinFiles(web, properties.Feature.DefinitionId);

    }

    private void CheckinFiles(SPWeb web, Guid featureID)
    {

        // create a regular expression pattern for the manifest files

        string pattern = string.Format(@"^.+_{0}.xml$", featureID);

        Regex fileNameRE = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);

        // get the manifest files from the root folder of the site

        SPFile[] manifestFiles = web.RootFolder.Files.Cast<SPFile>().Where(f => fileNameRE.IsMatch(f.Name)).ToArray();

        try
        {

           // iterate the manifest files

            foreach (SPFile manifestFile in manifestFiles)
            {

                // load the contents of the manifest file in an XDocument

                MemoryStream mStream = new MemoryStream(manifestFile.OpenBinary());

                StreamReader reader = new StreamReader(mStream, true);

                XDocument manifestDoc = XDocument.Load(reader, LoadOptions.None);

               // iterate over the ‘Module’ and ‘File’ elements in the XDocument, concatenating their Url attributes in a smart way so that we grab the site relative file Url-s

                string[] fileUrls = manifestDoc.Root.Elements(WS + "Module")

                    .SelectMany(me => me.Elements(WS + "File"), (me, fe) => string.Join("/", new XAttribute[] { me.Attribute("Url"), fe.Attribute("Url") }.Select(attr => attr != null ? attr.Value : null).Where(val => !string.IsNullOrEmpty(val)).ToArray())).ToArray();

               // iterate the file url-s

                foreach (string fileUrl in fileUrls)
                {

                   // get the file

                    SPFile file = web.GetFile(fileUrl);

                    // depending on the settings of the parent document library we may need to check in and/or (publish or approve) the file

                    if (file.Level == SPFileLevel.Checkout) file.CheckIn("", SPCheckinType.MajorCheckIn);

                    if (file.Level == SPFileLevel.Draft)
                    {

                        if (file.DocumentLibrary.EnableModeration) file.Approve("");

                        else file.Publish("");

                    }

                }

            }

        }

        finally
        {

            // finally delete the manifest files from the site root folder

            foreach (SPFile manifestFile in manifestFiles) manifestFile.Delete();

        }

    }

}

正如你所看到的,接收器代码是非常简短和直接的(也可以参考代码中的注解)。简单地说,它按如下执行:首先迭代网站根文件夹的文件,把它们的名字和上面提到的名字样式相匹配,然后在XDocument对象中一个接一个地载入清单文件,在每个清单文件中提取文件的URL-s。之后如果必要,配置好的文件将被签入,发布或批准(根据它们的状态和容纳它们的文档库的设置)。最后一步当然是删除站点根文件夹中的清单文件,因为我们已经不再需要它们了。

新的 SharePoint 2010 SDK现在可以下载了:现在还包括SharePoint Online !

[原文发表地址] New SharePoint 2010 SDK Available for Download: Now Includes SharePoint Online![原文发表时间] 2011-04-25 10:47

大家好!

SharePoint 2010 Software Development Kit (SDK)已经更新,包括新的文档,代码示例,以及IntelliSense XML文件。你们可以从 Microsoft 下载中心获取它。和以往一样,SDK是免费下载的。

现在SDK包含了开发SharePoint Online解决方案的信息。更多信息请查阅MSDN上的SharePoint Online 开发中心。推荐起步的地方是开发SharePoint Online解决方案180秒讲解SharePoint Online开发 (在线视频)。你们也可以在这儿下载:针对Office 365的SharePoint Online : 开发者指南.

clip_image001

安装详情

下面的内容描述了如何下载及安装 SDK。你们也可以观看这里的一个视频。目前SDK的版本是4763.1086。在安装这个新版本之前你需要卸载SDK以前的版本。

clip_image002

你可以在已经安装过SharePoint的机器上安装该SDK,也可以在远程开发机器上安装SDK(比如Windows 7)。参考这篇文章在Windows Vista, Windows 7, 以及 Windows Server 2008上创建开发环境

附加资源

如何在模式对话框中打开List Form

[原文发表地址] How to Open a List Form in a Modal Dialog Box[原文发表时间] 2011-06-23 07:00 在SharePoint 2010对用户界面做的许多改进中 ,模式对话框(Modal Dialog Box)尤为突出。优势很明显,你会发现页面过渡可以转移,当你想留在当前上下文时这一点就帮了你大忙了。举个例子来说,你选择诸如列表项目(list items),显示框(display),编辑器(edit)和新框架(new form)时,如图1所示,都会打开一个新的模式对话框,主页面的背景变灰,表示它暂时无效。而在主页面上浮现的对话框则有输入光标提示,只有关闭这个对话框才会让光标消失。想要回到主页面,就必须先完成对话框操作。

clip_image002

图1 – SharePoint2010 模式对话框

你可能以为图1所示的模式对话框是通过调用window.showModalDialog函数创建的,但事实并非如此。SharePoint的模式对话框不是window对象。它们是iframe对象,由SharePoint 2010中新增的客户端对话框框架创建。这个框架完全由JavaScript(ECMAScript)写成,并扩展使用了微软Ajax库。

新的对话框核心在于SP.UI.ModalDialog.showModalDialog方法。(想要简单快速地了解相关信息,请参考Dalla Tester的博文:使用对话平台)showModalDialog方法给你提供了新的选择,即你可以通过创建动态HTML或者为现有页面提供URL来填充对话框内容。第二个选择就是SharePoint所用来创建列表项目对话框的方法。它们是通过将列表(list form)的URL传递给showModalDialog方法创建的。比如,图1中所示对话框中加载的页面是NewForm.aspx,你可以用以下JavaScript代码调用对话框:

var options = {

      title: "Announcements – New Item",

      url: "../Lists/Announcements/NewForm.aspx"

};

SP.UI.ModalDialog.showModalDialog( options );

在这篇博文中,我将阐述如何做到和SharePoint用户界面所显示的一样—在模式对话框中打开一个列表(list form)—但又能在站点上的任意页面做到这点,而不是只能在显示列表的页面上做到。我所有的实例都是用JavaScript写成,经Content Editor Web Part部署的。完成这个任务有3个方法,从一个简单的例子开始—对话框的“hello world”—然后再慢慢深入其他复杂的技术。

准备

这篇博文中的例子是为给调查列表添加一个新项目。我选择这个列表类型是因为有时候你可能想要在主列表页外的位置启用添加新列表项,而这个类型囊括了这种情景。实际上,你可能根本不想你的用户看到其他的调查结果,这样你就会想到要在列表内容外调用添加项目对话框。

我在SharePoint Foundation 2010开发实例中创建了一个叫做GBE(等级浏览器引擎)的简短查询。你可以运用任何你喜欢的查询,但是你得改变它在样本代码中出现时所有标题。另外,如果你的查询列表的设置允许同一用户的多种反应,那它的开发和测试就会简单许多。

————————————————————–

提示:要允许出现多种反应,导航至查询预览页面(../Lists/<ListName>/overview.aspx)。点击设置,然后选择查询设置。在总设置(General Settings)下点击标题,概述和导航(description and navigation)。在查询选项中,在允许多种反应选项下选择“是”(Yes)。

————————————————————–

为便于测试,我创建了一个新的Web Part 页面,并将此页面加入了站点页面库中。我也把Content Editor Web Part(CEWP)加到了页面里。

与其和CEWP编辑器纠缠不休,我还是喜欢用Content Link指向我上传到Site Assets库中的内容文件。我的Site Assets库中有一个叫做JavaScript的文件夹,对创建者/所有者开放完全控制,对其他人只允许只读。我把Content Link指向的我上传的文件都放在这个文件夹中。

如何获取列表的URL

在前两个例子中,如果你想在模式对话中打开列表,你就需要这个列表的站点相关URL。(第三个例子使用SharePoint 2010 客户对象模型API来获取URL。)

列表的新建,编辑和显示是由Schema.xml文件列表中的表单元素来定义命名的。你可以在你SharePoint安装中的{SharePointRoot}\TEMPLATE\FEATURES\ListName文件夹中找到这个文件。列表表单是ASPX页面,通常存储在列表的根文件夹下。比如,Announcement列表的Schema.xml文件将新建表单定义为NewForm.aspx。这样你就可以在以下的站点相关的URL中到新项目的表单了:“/Lists/Announcements/NewForm.aspx”。

如果你使用的是火狐,你还有个更快捷的选择。只要在显示目标列表的页面,照常添加,编辑或者显示列表项目就可以了。打开模式对话框,在对话框中加载的页面上右键,指向内容菜单上的This Frame,在新窗口中点击打开表单。在浏览器地址栏中获取URL,移除URL中附带的查询字符串,就搞定了。

————————————————————–

注意:如果你想把这个方法运用到图1所示的对话框中,你要获取的URL要以“/Lists/Announcements/NewForm.aspx?RootFolder=&IsDlg=1”结尾。SharePoint在模式对话框中打开页面时,会添加查询字符串参数“IsDlg=1”。如你所想的那样,参数指示了目标是一个对话框。这样,页面显示时,像标题行这样的页面元素和左导航板就会被设为隐藏或省略。那些不需要在对话框中显示的页面元素会在NewForm.aspx附带的母板页上被贴上“s4-notdlg”标签—一种特殊的CSS 类。

————————————————————–

尝试一》你好,模式对话框

SharePoint模式对话框是由在单一JavaScript库中调用SP.UI.Dialog.js创建的。你可以在{SharePointRoot}\TEMPLATE\LAYOUTS文件夹中找到这个文件。(由于性能安排,该文件被最小化,如果你想浏览,获取相应的可读文件SP.UI.Dialog.debug.js.)每一个把v4.master作为母板页的页面都含有SP.UI.Dialog.js的链接。

这里关系到我们工作中最重要的库成员就是SP.UI.ModalDialog.showModalDialog方法了。在你调用这个方法前,你要先定义一些选项,比如对话框的标题,以及你想加载的页面的URL。虽然你也可以通过创建一个新的SP.UI.DialogOptions对象,设置属性来定义对话框选项,这个函数同样也支持普通的JavaScript对象。重要的是你传递给showModalDialog的对象会有函数所认可辨识的命名属性。查看属性名称的完整列表,请见Dallas Tester的博文:使用对话平台

如果你的选项列表不长,你可以像下述实例onclick handler一样,简单地把对象传递给showModalDialog:

<div id="displayDiv">

       <p>Please help us with our survey. Take our poll now!</p>

       <input onclick="javascript:SP.UI.ModalDialog.showModalDialog({ url: ‘../Lists/GBE/NewForm.aspx’, title: ‘User Survey’ }); return false;" id="btnVote" type="button" value="Vote" />

</div>

当样本上传至Site Assets,加载到CEWP时,它就会生成如图2所示的模式对话。

clip_image002[4]

图2 – 你好,模式对话框

样本运行得很不错,但在考虑到好的用户界面标准时它就有一个重大的缺陷了。它不会给用户任何反馈信息。你点击完成后,对话框关闭,页面上不会有任何提示告诉你查询已被接受。要让它提供可视的对话信息反馈实现起来并不难,但是要做到这个我们需要callback函数。

尝试二》使用Callback函数及对话结果

showModalDialog的callback原理其实很简单。你只需给你传递到showModalDialog的选项对象的dialogReturnValueCallback属性分配一个函数。这样对话框关闭后callback函数就会立即被调用。

通过调用SP.UI.ModalDialog.commonModalDialogClose方法,列表会关闭对话框。比如,表中的“OK”按钮所控制的点击事件运用的可能就是以下代码:

SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.OK, someValue);

表中的“取消”按钮控制的点击事件则是:

SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.cancel);

任意情况下,你的callback函数都可能有两种参数。第二个参数可以是任意值,可以不确定,它取决于列表的设计。第一个参数对我们更有用一些。SP.UI.DialogResult是一个枚举类型,SP.UI.Dialog.debug.js将它定义如下:

SP.UI.DialogResult = function() {}

SP.UI.DialogResult.prototype = {

        invalid: -1,

        cancel: 0,

        OK: 1

}

SP.UI.DialogResult.registerEnum(‘SP.UI.DialogResult’, false);

换句话说,如果我们的callback函数中的第一个参数大于0,那么用户就点击确定或等价按钮。有了这个认知,我们就可以修改投票按钮的点击事件的处理程序了。

<div id="displayDiv">

       <p>Please help us with our survey. Take our poll now!</p>

       <input onclick="javascript:showDialog(‘../Lists/GBE/NewForm.aspx’);" id="btnVote" type="button" value="Vote" />

</div>

<script language="javascript" type="text/javascript">

function showDialog( /* string */ url ) {
          // Do some argument checking.
          if ( typeof url != "string" )
             throw new Error.argument("url", "Expected a string.");               
          // Define dialog box options.
          var options = {}; 
          options.title = "User Survey";
          options.url = url;
          options.dialogReturnValueCallback =
                 function (dialogResult, returnValue) {   
                     // If the user clicked the Finish button (equivalent to OK)
                      if (dialogResult) {
                         // Use an Ajax shortcut method to modify the DIV element.
                          $get(‘displayDiv’).innerHTML = "<p>Thank you for voting!</p>";
                      }       
                 }        
 
           // Display the dialog box.
           SP.UI.ModalDialog.showModalDialog(options);
       }

</script>

Callback函数被定义为函数文本(第十四行)。你可以使用命名函数。我直接用函数体因为我不想在代码的其他地方调用函数。这个函数的唯一作用就是要测试对话结果,看看用户是否点击了“完成”,如果是,那么函数就要用写有反馈信息的Div标签来取代原先的内容。

注意第20行为document.getElementById函数调用了一个Ajax快捷。任意使用v4.master的SharePoint页面都是可以使用Ajax的。

样本上传到Site Assets,加载到CEMP时,它会创建与图2中显示一样的模式对话框。如果你点击“完成”,对话框就会自动关闭,页面更新如图3所示。

clip_image004

图3 – 反馈信息

这是基于先前样本的一个改进,但还需要进一步加工。一来,在它不需要的时候,列表的URL是硬编码。这个代码可以使用客户对象模型来查询列表,并返回List.DefaultNewFormUrl属性的值。这个改进会让代码更强壮。但是还有一个更大的潜在问题。如果查询列表的设置固定导致同一用户的多种回复被拒绝,这样要怎么办呢?当用户第二次点击“投票”按钮时,他就会收到如图4所示的恶心的消息。

clip_image006

图4 – 你无权再次对此查询作出回应

如果我们是在写服务器代码(就像这个列表的后台代码),我们就能预见到这点并很好地处理它。但是我们在写的是客户端的代码,所以我们需要做的就是决定当前用户是否已经投票过,如果是,那就移除页面上的投票按钮,显示一个不同的用户界面。那些选择过的用户就没有第二次选择的机会了,这样他们就不会看到提示错误的信息了。这样问题也就解决了。

要施行这些改进项,我们要通过调用SharePoint 客户对象模型,让我们代码与列表数据。

尝试三》列表数据驱动用户界面

最后一个实例介绍了如何在从客户端查询列表数据获取信息的基础上,改变呈现给用户的界面。SharePoint 2010 Foundation SDK中有很多实例,告诉你如何查询列表数据。这里有两个非常好的资源,对我们现在所做的工作很有帮助,那就是如何:使用用户和组如何:检索列表项

我们需要三段信息:

• 查询列表默认新项目的URL

• 当前用户的登录名

• 查询列表中是否有项显示当前用户为Author。

我们也希望最小程度地绕弯路,所以我们试着在单一列表查询中获取到所有我们需要的信息。

修改后的用户界面代码如下:

<div id="displayDiv" style="visibility:hidden" >
       <p>Please help us with our survey. Take our poll now!</p>   
       <input id="btnVote" type="button" value="Vote" />
</div> 
<script language="javascript" type="text/javascript">
 
           
// Change the value to match the title of your list.
            var listTitle = "GBE";
          
            // Invokes the getData function.
            ExecuteOrDelayUntilScriptLoaded(getData, "sp.js");
         
            var currentUser, list, listItems;
            function getData() {
  
                 var ctx = new SP.ClientContext.get_current();
         
                 // Get the current user.
                 this.currentUser = ctx.get_web().get_currentUser();
                 ctx.load(this.currentUser);
            
                 // Get the URL for the default new item form.
                 this.list = ctx.get_web().get_lists().getByTitle(this.listTitle);     
                 ctx.load(this.list, "DefaultNewFormUrl");
               // Get the list item collection.
                 var caml = new SP.CamlQuery();
                 this.listItems = this.list.getItems(caml);
                 ctx.load(this.listItems, "Include(Author)");
                 ctx.executeQueryAsync(
                 Function.createDelegate(this, success),
                 Function.createDelegate(this, failure)
                 );
            }
            function success(sender, args) {
                if ((this.currentUser) && (this.listItems))
                {
                        var displayDiv = $get("displayDiv");
                        var userName = this.currentUser.get_title();
                        var hasVoted = false;

                        // Enumerate over the list items.
                        var itemsEnumerator = this.listItems.getEnumerator();
                        while (itemsEnumerator.moveNext()) {
                        var item = itemsEnumerator.get_current();
                        // If the list has an item where the Author is the current user
                        if (userName == item.get_item("Author").get_lookupValue()) {
                        // then the current user has voted.
                        hasVoted = true;
                        break;
                            }
                 }
                if (hasVoted)
                {
                        displayDiv.innerHTML = "You have already voted.";
                }
                else
                {
                    // Attach an event handler to the Vote button.
                    $addHandler($get("btnVote"), "click", Function.createDelegate(this, showDialog));
                }         
            // Show the UI.
                    displayDiv.style.visibility = "visible";
               }
            }
            function failure(sender, args) {
               // Display the error message on the page.
                var displayDiv = $get("displayDiv");
                displayDiv.innerHTML = args.get_message();
                displayDiv.style.visibility = "visible";
            }

    function showDialog() {
      
// Define dialog box options.
        var options = {};
        options.title = ‘User Survey’;
        options.url = this.list.get_defaultNewFormUrl();
        options.dialogReturnValueCallback = function (dialogResult, returnValue)
        {
        // If the user clicked the Finish button (equivalent to OK)
        if (dialogResult) {
        // Detach the Vote button’s click handler and dispose of the delegate.
        $clearHandlers($get("btnVote"));
        // Replace the content of the DIV element.
        $get(‘displayDiv’).innerHTML = ‘<p>Thank you for voting!</p>’;
        }  };
        // Display the dialog box.
        SP.UI.ModalDialog.showModalDialog(options);
    }
</script>

当样本上传至Site Assets,加载到CEWP时,它呈现的模式对话框和之前图片显示的一样,用户投票后将收到一样的反馈信息。唯一不同的是已经投票过的用户所看到的。结果如图5所示。

clip_image008

图5 – “你已经投票了。”

下一步

比起将这个解决方案部署到Content Editor Web Part里,我们应该设计一个用户Web Part,让终端用户能轻易地用任意查询列表进行配置。那样,就能用服务器端代码来查询列表数据,减少网络堵塞。我把这个任务作为作业留给你们独立完成。在你尝试之前,我推荐你们看一看如何:以模式对话框形式显示页面。文章很好的讲述了怎样利用它们部署JavaScript资源以及创建用户Web Part。