2010年2月5日,星期五

功能区自定义-下拉控件,客户端对象模型和JavaScript页面组件

在本系列文章中:

  1. 定制功能区–创建选项卡,组和控件
  2. 将功能区项目添加到现有选项卡/组中
  3. 功能区自定义-下拉控件,客户端对象模型和JavaScript页面组件(本文)
  4. 通过Web部件和现场控件以编程方式自定义功能区

一旦了解了如何将自定义项放置在功能区中的正确位置,您可能会发现不仅仅需要添加按钮并使用其他控件(例如下拉菜单,复选框,弹出锚点等)之外。这种功能区开发确实涉及其中,但是与SharePoint开发中的许多事情一样,一旦您’我第一次知道整体“template”对于以后的场合-希望我’此处显示的内容是您可能想做的许多高级事情的良好起点。关键是JavaScript“page component”除了声明性XML,通常还需要’在我以前的帖子中看到过。

[Beta旁注]在撰写本文时(2010年2月,仍处于测试阶段),’他们认为功能区开发可能是SP2010中较复杂的开发领域之一。也许Microsoft的一些出色文档可能会改变这一点,但是现在有许多难以理解的死角–目前,SDK中几乎没有涵盖这些内容(实际上几乎没有任何内容),因此,除非您有内部信息,否则’一路主要是鲜血,汗水和眼泪。一世’ve在产品小组的产品经理(伊丽莎白·奥尔森(Elisabeth Olson))中提到了这一点,听起来更多的MS指南即将推出,因此’s hope.

的sample

我的示例显示了功能区中自定义下拉菜单的使用– I’稍后会详细介绍,但我’此处显示的m可用于 许多 功能区中的控件,而不仅仅是下拉列表。所以如果你’重新想做不’专门涉及下拉菜单,我’d建议无论如何都要阅读,因为这种技术可能仍然是您将要使用的技术。

第一次单击时,它将使用客户端对象模型来获取当前Web中的列表集合,然后在下拉列表中将其显示为选项:

CustomDropdownInRibbon

CustomDropdownInRibbonExpanded

当选择了一个项目,一个简单的JavaScript发出警报与选择列表的名称,但现实生活中的实施当然会做一些与价值更为有用。这里的目的是说明如何使用除按钮之外的功能区控件,以及如何在它们后面编写代码–一旦你可以做到这一点,你’能够构建广泛的解决方案。  

要注意的一件事–可以完全以XML将项目添加到下拉列表或其他控件中。一世’m choosing to use a 的JavaScript页面组件 to illustrate what happens when you need “code-behind”例如以我的情况遍历网络中的所有列表。

什么’s required – summary

  1. 声明式XML,以配置功能区控件
  2. 的JavaScript“page component”,通常在外部.js文件中声明
  3. 向页面添加JavaScript(使用最适合功能区自定义范围的技术–Web部件/委托控件中的代码(添加到AdditionalPageHead等)中。这将:
    1. Link 的external .js file
    2. 确保已加载核心相关的.js文件,例如SP.js,CUI.js
    3. Call into 的initialization function within 我们的 页面组件 –这将向功能区框架注册我们的组件,并确保将我们的组件添加到页面中。

1.声明式XML

我使用了以下XML– here I’m实际上显示的是一个简短的摘录,仅适用于包含我的控件的组。真的’s just 的‘Controls’最有趣的部分,周围的环境取决于您是否要 创建一个新标签 要么 add 的items into an existing tab/group,有关这些详细信息,请参见我以前的文章。

Key points of note are 的Command, PopulateQueryCommand, and QueryCommandattributes on 的dropdown – these will link into 我们的 的JavaScript页面组件:

<Group
  Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup"
  Description="Contains advanced ribbon controls"
  Title="Page component sample"
  Sequence="53"
  Template="Ribbon.Templates.COB.OneLargeExample">
  <控制项 Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Controls">
    <Label Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Label" 
           ForId="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown" 
           Command="LabelCommand"
           LabelText="Select list:"
           Sequence="16" 
           TemplateAlias="c1"/>
    <DropDown
      Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown"
      Sequence="17"
      Command="COB.PageComponent.Command.DoAction"
      动态地填充="true"
      PopulateOnlyOnce="true"
      PopulateQueryCommand="COB.PageComponent.Command.PopulateDropDown"
      QueryCommand="COB.PageComponent.Command.QueryDoAction"
      Width="75px"
      TemplateAlias="c2" />
  </控制项>
</Group>

2. 的JavaScript页面组件

这是复杂的位,至少是第一次。我们正在有效地编写面向对象的JavaScript,其中包含为功能区控件提供支持的类。考虑这样的JavaScript‘template’ to use for 页面组件s, where you’ll每次修改实际的实现位。一世’ve评论了一些关键点,建议进行滚动浏览,然后我们’ll浏览重点:

Type.registerNamespace('COB.SharePoint.Ribbon.PageComponent');
 
COB.SharePoint.Ribbon.PageComponent = function  (){ 
    COB.SharePoint.Ribbon.PageComponent.initializeBase(this);
}
 
// 的初始化 function needs to be called by some  脚本  added to 的page elsewhere - in 的end, it does 的important work 
// of calling PageManager.addPageComponent()..
COB.SharePoint.Ribbon.PageComponent.initialize = function  (){ 
    ExecuteOrDelayUntilScriptLoaded(Function.createDelegate(null, COB.SharePoint.Ribbon.PageComponent.initializePageComponent), 'SP.Ribbon.js');
}
COB.SharePoint.Ribbon.PageComponent.initializePageComponent = function() {
    
    var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
    if (null !== ribbonPageManager) {
        ribbonPageManager.addPageComponent(COB.SharePoint.Ribbon.PageComponent.instance);
    }
}
 
COB.SharePoint.Ribbon.PageComponent.prototype = {
    init: function  (){   }, 
 
    getFocusedCommands: function  (){ 
         返回  ['COB.PageComponent.Command.FieldControl.GroupCommand', 'COB.PageComponent.Command.FieldControl.TabCommand', 'COB.PageComponent.Command.FieldControl.ContextualGroupCommand', 'COB.PageComponent.Command.FieldControl.RibbonCommand'];
     }, 
 
    getGlobalCommands:功能  (){ 
         返回  ['COB.PageComponent.Command.DoAction', 'COB.PageComponent.Command.PopulateDropDown', 'COB.PageComponent.Command.QueryDoAction'];
     }, 
 
    canHandleCommand: function (commandId) {
        if ((commandId === 'COB.PageComponent.Command.DoAction') ||
            (commandId === 'COB.PageComponent.Command.PopulateDropDown') || (commandId === 'COB.PageComponent.Command.QueryDoAction')) {
             返回  true;        
        }
        else {
             返回  false;
        }
     }, 
 
    handleCommand: function (commandId, properties, sequence) {
        if (commandId === 'COB.PageComponent.Command.FieldControl.GroupCommand') {
            alert("COB.PageComponent.Command.FieldControl.GroupCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.FieldControl.TabCommand') {
            alert("COB.PageComponent.Command.FieldControl.TabCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.FieldControl.ContextualGroupCommand') {
            alert("COB.PageComponent.Command.FieldControl.ContextualGroupCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.FieldControl.RibbonCommand') {
            alert("COB.PageComponent.Command.FieldControl.RibbonCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.QueryDoAction') {
            // this command executes as soon as tab is requested, so do initialization here ready for if  我们的  dropdown gets requested..
            loadCurrentWebLists();
        }
        if (commandId === 'COB.PageComponent.Command.PopulateDropDown') {
            // actually build 的dropdown contents by setting 的PopulationXML property to a value with 的expected format. We have to deal with possible 
            // timing issues/dependency on core SharePoint JS code with an ExecuteOrDelay..
            ExecuteOrDelayUntilScriptLoaded(Function.createDelegate(null, getDropdownItemsXml), 'SP.js');
 
            properties.PopulationXML = getDropdownItemsXml();
        }
        if (commandId === 'COB.PageComponent.Command.DoAction') {
            // here we're using 的SourceControlId to detect 的selected item, but more normally each item would have a unique commandId (rather than 'DoAction'). 
            // However this isn't possible in this case since each item is a list in 的current web, and this can change..
            var selectedItem = properties.SourceControlId.toString();
            var listName = selectedItem.substring(selectedItem.lastIndexOf('.') + 1);
            alert("You selected 的list: " + listName);
        }
     }, 
 
    isFocusable: function  (){ 
         返回  true;
     }, 
 
    receiveFocus: function  (){ 
         返回  true;
     }, 
 
    yieldFocus: function  (){ 
         返回  true;
    }
}
 
// **** BEGIN: helper code specific to this sample ****
 
// some global variables which we'll use with 的async processing..
var lists = null;
var querySucceeded = false;
 
// use 的Client Object Model to fetch 的lists in 的current site..        
function loadCurrentWebLists() {
    var clientContext = new SP.ClientContext.get_current();
    var web = clientContext.get_web();
    this.lists = web.get_lists();
 
    clientContext.load(lists);
    clientContext.executeQueryAsync(
           Function.createDelegate(this, this.onQuerySucceeded),
           Function.createDelegate(this, this.onQueryFailed));
}
 
function onQuerySucceeded() {
    querySucceeded = true;
}
 
function onQueryFailed(sender, args) {
    querySucceeded = false;
}
 
function getDropdownItemsXml() {
    var sb = new Sys.StringBuilder();
    sb.append('<Menu Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu\'>');
    sb.append('<MenuSection DisplayMode=\'Menu\' Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu.Manage\'>');
    sb.append('<Controls Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu.Manage.Controls\'>');
    if (querySucceeded)
    {
        var listEnumerator = lists.getEnumerator();
 
        while (listEnumerator.moveNext()) {
            var oList = listEnumerator.get_current();
            
            sb.append('<Button');
            sb.append(' Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu.Manage.');
            sb.append(oList.get_title());
            sb.append('\'');
            sb.append(' Command=\'');
            sb.append('COB.PageComponent.Command.DoAction');
            sb.append('\'');
            sb.append(' LabelText=\'');
            sb.append(SP.Utilities.HttpUtility.htmlEncode(oList.get_title()));
            sb.append('\'');
            sb.append('/>');
        }
    }
    sb.append('</Controls>');
    sb.append('</MenuSection>');
    sb.append('</Menu>');
     返回  sb.toString();
}
  
// **** END: helper code specific to this sample ****
 
COB.SharePoint.Ribbon.PageComponent.registerClass('COB.SharePoint.Ribbon.PageComponent', CUI.Page.PageComponent);
COB.SharePoint.Ribbon.PageComponent.instance = new COB.SharePoint.Ribbon.PageComponent();
 
NotifyScriptLoadedAndExecuteWaitingJobs("COB.SharePoint.Ribbon.PageComponent.js");
 
  • 的‘initialize’函数通常负责调用‘addPageComponent’在功能区PageManager上(但不是在SP.Ribbon.js加载之前)
  • 的commands referenced in 的JS are those specified in 的control XML e.g. for my dropdown
    • 的‘getFocusedCommands’函数返回一个命令数组,当控件具有焦点时应执行
    • 的‘getGlobalCommands’函数返回应执行的命令数组 而不管 专注
    • 我们需要列出可以在‘canHandleCommand’ function, and 为以下各项提供实际的实现‘handleCommand’
  • 请注意以下有关使用的各种命令的要点:
    • PopulateQueryCommand–用于构建控件中的项目列表。这是我的地方’m使用客户端对象模型(ECMAScript版本)来获取当前网站的列表。
    • QueryCommand–当父容器(例如标签)被激活时调用。记住功能区就是关于“script on demand”(延迟加载),所以我’我选择这里作为进行实际客户端OM请求初始化工作的更好位置– more on this later.
    • 命令–当用户实际选择项目时调用
  • 重要–这些命令适用于除下拉菜单以外的许多功能区控件,例如FlyoutAnchor,SplitButton,ToggleButton,TextBox,Checkbox,Spinner等。这就是即使此信息也很重要的原因’不是专门的下拉控件’re working with.
  • 的key to populating controls which take collections is to use 的‘properties’ object passed to handleCommand, using either: ul>
  • properties.PopulationXML
  • properties.PopulationJSON
  • I’m使用properties.PopulationXML提供应该显示在我的下拉菜单中的项目,所需格式为:
       1: <Menu Id="">
       2:   <MenuSection Id="">
       3:     <控制项 Id="">
       4:       <Button Command="" Id="" LabelText="" />
       5:       ..a 'Button' element here for each item in 的collection..
       6:     </控制项>
       7:   </MenuSection>
       8: </Menu>
    我没有’尚未看到有关如何使用.PopulationJSON属性的示例,所以请不要’不知道要在JSON中使用的确切名称。
  • 将客户端OM与功能区JS框架结合起来有一些有趣的方面–有效地使用了异步模型,这意味着在功能区框架需要它时,方法调用的结果可能尚未准备好(毕竟它发生在不同的请求上)。一世’在本文结尾处的示例中,我将解释如何处理此问题。

3.页面级JavaScript

的final element is 的的JavaScriptyou need to add to 的page to call into 的页面组件. In my example I’我很高兴将此JavaScript添加到我网站的每个页面中(因为’是我的功能区自定义范围),因此我使用了 委托控制 在AdditionalPageHead中添加一个自定义用户控件,其主体如下所示: 

<SharePoint:ScriptLink Name="sp.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink1" />
<SharePoint:ScriptLink Name="CUI.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink3" />
<SharePoint:ScriptLink Name="/_layouts/COB.SharePoint.Demos.Ribbon/COB.SharePoint.Ribbon.PageComponent.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink2" />
    < 脚本  type="text/javascript">
     
        //<![CDATA[
            function initCOBRibbon() {
                COB.SharePoint.Ribbon.PageComponent.initialize();
            }
     
            ExecuteOrDelayUntilScriptLoaded(initCOBRibbon, 'COB.SharePoint.Ribbon.PageComponent.js');
    //    
    //]]>
</ 脚本 >

的important things here are that we ensure required system JS files are loaded with 的ScriptLink tag, do 的same for 我们的 JS file, then call 的.initialize() function of 我们的 页面组件.

因此,功能区中用于复杂控件的那些组件!通过根据需要定制此信息/示例代码,应该可以进行各种功能区自定义(请记住‘handleCommand’ is 的key implementation hook), and I definitely think that starting from such a 模板 is 的way to go.

附录-在功能区中使用客户端对象模型的注意事项

使用功能区时,很快就会发现,如果客户端对象模型没有’不存在,事情就会 许多 棘手的–它们是满足许多需求的自然选择。尽管如此,仍然存在一些挑战–考虑一个控件(例如下拉菜单)将拥有它’的项目集合尽可能晚地填充‘PopulateDynamically’设置为true(通常是个好主意),即当实际上单击下拉列表选择一个项目时! 这是因为色带是围绕“script on demand” model (you’ll often see “SOD”Microsoft中的引用’s JavaScript) –这样可以确保仅将所需的JavaScript下载到客户端,而不会下载更多。这解决了在SharePoint 2007 WCM网站上,我们将为匿名用户隐藏core.js文件的问题,因为该文件很大,而这些用户不需要。 无论如何,当单击下拉列表时,此时功能区框架将调用‘handleCommand’使用为指定的命令‘PopulateQueryCommand’值。如果您在此处运行客户端OM代码’不好,因为你赢了’无法获得结果,然后由于异步模型–结果将在很长时间后提供给回调函数‘handleCommand’已经完成,所以最终结果是您的控件将为空。

因此,您需要进行实际处理 之前 的‘PopulateQueryCommand’ is called. You 可以 选择在页面加载后立即执行操作,但是在大多数情况下,这可能效率不高–如果用户不这样做怎么办’不会在此页面加载时接近功能区控件吗?在这种情况下,我们将需要进行一些客户端处理 and an extra request to 的server 完全没有必要–在人流量大的页面上,这可能是个坏消息。没有任何文档’目前尚无法确定,但似乎‘QueryCommand’是放置此类客户端OM代码的好地方–当父容器(例如标签)显示为可见时(在此处,’现在用户可以使用我们的控件了)。在我的代码中,我在此处运行了实际的客户端OM查询,并将结果存储在页面级变量中-然后对其进行选择并对其进行迭代‘PopulateQueryCommand’。在脚本运行此命令以填充控件时,查询已执行并且数据已准备就绪– happy days. I’我会很感兴趣地看到这方面发生了什么,看看这是否是预期的模式,或者,如果我’我完全错了。

概要

Complex ribbon customizations are likely to require a 的JavaScript页面组件 - in general 页面组件s are somewhat complex (partly because of 的current lack of documentation perhaps), but once you have a suitable 模板, subsequent implementations should be easier. 如果你 need to use Client Object Model code, beware of 的“异步/生命周期问题”在此处进行了讨论,并确保您的代码具有可用于功能区框架的数据。

14条评论:

奈杰尔·普莱斯(Nigel Price) 说过...

如果在浏览器中关闭了Java,所有这些方法是否仍然有效?

克里斯·奥'Brien 说过...

啊-请记住,色带是由可以 有助于 到SharePoint网站,而不是匿名用户。因此,JavaScript是 绝对 这些用户所需,并且一直追溯到Sharepoint 2003。

伊卡斯坦 说过...

克里斯,你好

我找到了一种在运行时激活javascript中的Ribbon标签的方法。

请参阅我的博客文章:

http://ikarstein.wordpress.com/2010/06/15/how-to-activate-sharepoint-ribbon-tab-by-javascript-code/

克里斯·奥'Brien 说过...

@ikarstein,

这看起来非常有用-很好找到,感谢您的发帖!

克里斯。

说过...

克里斯,你好

您能快速告诉我如何将功能区编辑工具的fontsize下拉列表从pt更改为px吗?

克里斯·奥'Brien 说过...

@抢,

我不'认为这是一个简单的练习-当下拉列表更改时,某些Microsoft's code will run. You'd需要以某种方式覆盖它,但是我的猜测是MS没有'因为它可以轻松地提供执行此操作的方法'不太可能成为常见的要求。

谢谢,

克里斯。

匿名 said...

可以为此发布解决方案文件吗?

克里斯·奥'Brien 说过...

@匿名,

真的很抱歉,但我可以'不能轻松提供原始解决方案文件-令我感到羞耻的是,在我进行自动备份时,我丢失了虚拟机't actually working.

我有什么特别需要帮助的吗?

谢谢,

克里斯。

匿名 said...

克里斯,你好

这又是匿名的... :)解决方案文件没有问题。我也必须每180天转储一次我的VM。我有两个问题-
1)启用和禁用状态按钮对我不起作用。第一次加载页面时它确实起作用。我通过反转正确/错误条件进行了测试。但是随后单击“添加状态”按钮不会导致EnabledScript触发...不确定原因。

2)在第3部分中,使用pageComponent。我正在尝试仅针对我正在测试的页面添加Web控件。那么,我应该只做一个编辑页面,然后将Web控件添加到该页面吗?这足以使其在页面加载时运行吗?

提前致谢,
杰克

未知 说过...

我有自己的上下文组/选项卡/控件,它们是通过RegisterDataExtension从自定义Web部件动态创建的。

的problem is also sporadic but fairly frequent. When problem occurs, all Ribbon buttons renders but were disabled and 的JavaScripterror shows as:

预期对象:pagecomponent.js错误

它在以下位置(返回语句)的PageComponent.js上失败:



getGlobalCommands:功能

(){
ULS_SP();
返回

getGlobalCommands();
},
任何想法?

克里斯·奥'Brien 说过...

@KS,

嗯,我'm not sure I'我使用了您提到的技术。无论如何,我可以'看不到你有什么不对劲'已发布。你可以试试 威克多·威伦 -Wictor对Web部件中的功能区自定义进行了相当深入的研究。

HTH,

克里斯。

菲利普·苏萨(Filipe Sousa) 说过...

克里斯你好,

首先,我'd感谢您的帖子和所有有益的工作。一世'我按照你的榜样,但我可以't使下拉菜单起作用-它's disabled. I'已检查所有javascript是否均已正确加载。有什么想法吗?

菲利普

克里斯·奥'Brien 说过...

@mente,

如果你'确保所有内容都已加载,并且您'重新调用init方法'这是我想到的一件事。我认为Microsoft从编写此代码(测试版2)起就对RTM进行了内部更改-您需要使用私有JavaScript变量来帮助功能区了解是否可以调用命令。见安德鲁·康内尔's post 在 http://www.andrewconnell.com/blog/archive/2010/10/14/asynchronously-checking-if-a-command-is-available-in-the-sharepoint.aspx

HTH,

克里斯。

泰勒 said...

克里斯,您好,我想看看您是否知道如何强制所有上下文组在页面上可见。具体情况是,我们希望在列表视图页面上有一个内容编辑器Webpart,但我们仍然希望页面上下文列表上下文组在页面上可见,而用户不知道他们必须单击列表。