65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 TypeScript 的 TFS JS 扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (3投票s)

2013年9月30日

CPOL

5分钟阅读

viewsIcon

31225

downloadIcon

348

使用 TypeScript 的 TFS JS 扩展。

在开始之前,我想感谢 Tiago Pascal 在 JavaScript 扩展的基础知识、技巧和窍门方面给予我的帮助,以及感谢 Alexander Vanwynsberghe 提供的一个调试技巧,这使得一切都变得更加容易。这篇文章将为您提供创建 TFS Web Access JavaScript 扩展的一个基本起点,其中大部分内容使用正确的 TypeScript 编写。这篇文章将简要概述整个过程,并提供足够的信息供您开始。在以后的文章中,我将更详细地介绍不同的部分,并解释代码片段的作用,或者我如何知道代码片段可以在 Web 访问中使用。

所需文件

首先,您需要为您的扩展取一个名字,我选择了 B1n4ryD1g1t.Tfs.Extensions,所以当您看到它时,请将其替换为您自己的名字,但我会尽量记住指出需要替换的几个地方。我发现创建这些插件最简单的方法是创建一个新的带有 TypeScript 的 HTML 应用程序,这提供了一个良好的起点,并且对我来说更加熟悉,因为它是一个解决方案,允许我构建项目,并且任何 TypeScript 错误都会通过常规的错误列表窗口向我显示。

image

创建 2 个文件(并记住在这里替换名称):manifest.xmlB1n4ryD1g1t.Tfs.Extensions.min.js,然后将解决方案当前的 app.ts 重命名为 B1n4ryD1g1t.Tfs.Extensions.debug.ts。如果构建解决方案并显示解决方案文件夹中的所有文件,您会注意到现在有一个 B1n4ryD1g1t.Tfs.Extensions.debug.jsB1n4ryD1g1t.Tfs.Extensions.debug.js.map 文件,您可以将它们包含在您的解决方案中。设置解决方案的最后一步是删除 app.cssdefault.htmweb.config,因为我们不会用到它们。接下来,您需要使用 NuGet 为您的项目添加对 JQuery 的引用。

imageimage

现在右键单击您的 jquery-{version}.js 文件,然后单击搜索 TypeScript 类型… 并安装 jquery.TypeScript.DefinitelyTyped 类型。

imageimage

您的解决方案应该看起来像下面一样,我们不会在扩展中物理引用这些 jquery 文件。它们纯粹用于获取类型定义。

image

manifest.xml 中包含什么

manifest.xml 包含您的扩展的基本信息,TFS 将在 Web 访问的扩展页面上显示这些信息。

image

插件节点不言自明,目前,您将保持模块节点不变(记住替换名称)。下面的两个模块允许我们的扩展在任务板和组合板上运行。

<WebAccess version="12.0">
  <plug in name="B1n4ryD1g1t Tfs Extensions - Web Access" vendor="Gordon Beeming" 
   moreinfo="http://gbeeming.wordpress.com" version="1.7">
    <modules>
      <module namespace="B1n4ryD1g1t.Tfs.Extensions" loadAfter="TFS.Agile.TaskBoard.View"/>
      <module namespace="B1n4ryD1g1t.Tfs.Extensions" loadAfter="TFS.Agile.Boards.Controls"/>
    </modules>
  </plug in>
</WebAccess> 

B1n4ryD1g1t.Tfs.Extensions.debug.ts 所需的最少内容是什么?

我们扩展的最小内容将是纯 JavaScript,并在我们的解决方案中留下 4 个可以忽略的错误。

image

扩展的内容将如下所示

 /// <reference path="Scripts/typings/jquery/jquery.d.ts" />
var __extends = this.__extends || function (d, b) {     function __() { this.constructor = d; }     
__.prototype = b.prototype;     d.prototype = new __(); }; define(["require", "exports", 
"Presentation/Scripts/TFS/TFS", "Presentation/Scripts/TFS/TFS.Core", 
"Presentation/Scripts/TFS/TFS.OM", "Presentation/Scripts/TFS/TFS.UI.Controls", 
"WorkItemTracking/Scripts/TFS.WorkItemTracking"], function (require1, exports, tfs, core, tfsOM, 
tfsUiControls, tfsWorkItemTracking) {  var TFS = tfs;  var Core = core;  var TFS_OM = tfsOM;  
var TFS_UI_Controls = tfsUiControls;  var TFS_WorkItemTracking = tfsWorkItemTracking;  
var B1n4ryD1g1tTfsExtension = (function (_super) {  __extends(B1n4ryD1g1tTfsExtension, _super);  
function B1n4ryD1g1tTfsExtension(options) {  _super.call(this, options);  }  
B1n4ryD1g1tTfsExtension.prototype.initializeOptions = function (options) 
{  _super.prototype.initializeOptions.call(this, $.extend({  }, options));  };  
B1n4ryD1g1tTfsExtension.prototype.initialize = function () {  //alert('this is running');  };  
B1n4ryD1g1tTfsExtension._typeName = "B1n4ryD1g1t.Tfs.Extensions";  
return B1n4ryD1g1tTfsExtension;  })
(TFS_UI_Controls.BaseControl);  TFS.initClassPrototype(B1n4ryD1g1tTfsExtension, {});  
TFS_UI_Controls.Enhancement.registerEnhancement(B1n4ryD1g1tTfsExtension, ".taskboard");  
TFS_UI_Controls.Enhancement.registerEnhancement(B1n4ryD1g1tTfsExtension, ".agile-board"); });  
Add the extension to TFS Web Access

打开您的 debug.js 文件,将其所有内容复制到 min.js 文件中并进行最小化处理。Visual Studio 的 Web Essentials 扩展将对此有所帮助,因为它与 Ctrl + Alt + X 一样简单。打开您的扩展所在的文件夹,然后将 debug.jsmin.jsmanifest.xml 文件压缩。

image

现在,您需要将扩展上传到 TFS。浏览到 TFS 服务器主页,点击管理设置按钮,然后点击扩展选项卡。点击安装,浏览您新创建的 zip 文件,然后点击确定。现在您需要做的就是点击启用并点击确定以确认警告。现在只需刷新任何任务板或组合板,您就会看到扩展内部发出的警报。

imageimage

对扩展进行更改

正如您所见,添加扩展是一个非常漫长的过程,除非您使用 Alexander 在博客中介绍的一个技巧 调试 TFS Web Access 扩展,否则检查更改不会有任何不同。这基本上是使用 fiddler 告诉您的计算机,当它请求您的扩展的 js 文件时,它必须使用您解决方案中的版本 js 文件而不是 TFS 中的文件,这将大大加快开发速度,因为您可以简单地保存文件,这会触发 Visual Studio 更新 debug.js 文件,刷新 TFS 时现在将加载该文件。对于编辑器,我使用了 regex:http://labtfs01/_static/tfs/12/_scripts/TFS/.+//_plugins/.+/B1n4ryD1g1t.Tfs.Extensions.js 的值,其中 header:CachControl=no-cacheregex:http://labtfs01/_static/tfs/12/_scripts/TFS/.+//_plugins/.+/B1n4ryD1g1t.Tfs.Extensions.js 以及 C:\c\r\Apps\TfsJsExtensions\TfsJsExtensions\B1n4ryD1g1t.Tfs.Extensions.debug.js。与博客文章类似,只是在 TFS 中为 debug/min 差异添加了通配符。

添加一些真正的 TypeScript

在您的 .ts 文件的底部,添加下面的 TypeScript 代码(抱歉没有完整的 TS 高亮显示)。

 module B1n4ryD1g1tModule {
    export class Core {
        Require: any;
        Exports: any;
        TFS: any;
        Core: any;
        TFS_OM: any;
        TFS_WorkItemTracking: any;
        WorkItemManager: any;
        CurrentlyFetchingWorkItems: boolean;
        constructor(require1, exports, tfs, core, tfsom, tfsWorkItemTracking) {
            this.Require = require1;
            this.Exports = exports;
            this.TFS = tfs;
            this.Core = core;
            this.TFS_OM = tfsom;
            this.TFS_WorkItemTracking = tfsWorkItemTracking;
        }
        public init(): any {
            this.initWorkItemManagerEvents();
            var that = this;
            window.setTimeout(function () {
                if (that.isAgileBoard()) {
                    that.setAgileBoardIDs();
                }
                if (that.isTaskBoard()) {
                    that.setTaskBoardIDs();
                }
            }, 100);
        }
        private getCurrentTeamName(): string {
            return this.TFS.Host.TfsContext.getDefault().currentTeam.name;
        }
        private isTaskBoard(): boolean {
            return $(".taskboard").length > 0;
        }
        private isAgileBoard(): boolean {
            return $(".agile-board").length > 0;
        }
        private setAgileBoardIDs(): void {
            var idsToFetch = [];
            $(".board-tile").each(function () {
                var id = $(this).attr("data-item-id");
                idsToFetch.push(parseInt(id));
            });
            this.loadWorkItems(idsToFetch, this.setAgileBoardIDsWork);
        }
        private setAgileBoardIDsWork(index, row, that: Core): void {
            that.workWithAgileBoard(row[0], that);
        }
        private workWithAgileBoard(id, that: Core): void {
            if (that.workWithAgileBoard_WorkItem(id)) {
            }
        }
        private setTaskBoardIDs(): void {
            var idsToFetch = [];
            $("#taskboard-table .tbTile").each(function () {
                var id = $(this).attr("id");
                id = id.split('-')[1];
                idsToFetch.push(parseInt(id));
            });
            $("#taskboard-table .taskboard-row .taskboard-parent").each(function () {
                var id = $(this).attr("id");
                if (id != undefined) {
                    id = id.split('_')[1];
                    id = id.substring(1);
                    idsToFetch.push(parseInt(id));
                }
            });
            this.loadWorkItems(idsToFetch, this.setTaskBoardIDsWork);
        }
        private setTaskBoardIDsWork(index, row, that: Core): void {
            if (that.workWithTaskBoard(row[0], row[1], that)) {
            }
        }
        private workWithTaskBoard(id, state, that: Core): void {
            if (that.workWithTaskBoard_Task(id)) {
            }
            if (that.workWithTaskBoard_Requirement(id)) {
                that.taskboard_setRequirementState(id, state);
            }
        }
        private workWithTaskBoard_Requirement(id): boolean {
            return this.boards_setID("taskboard-table_p" + id, id);
        }
        private workWithTaskBoard_Task(id): boolean {
            return this.boards_setID("tile-" + id, id);
        }
        private workWithAgileBoard_WorkItem(id): boolean {
            var titleObj = $(".board-tile[data-item-id='" + id + "'] .title");
            if ($(titleObj).length > 0 && $(titleObj).find(".TitleAdded").length == 0) {
                var idHtml = "<span class='TitleAdded' 
                style='font-weight:bold;'>" + id + "</span> - ";
                $(titleObj).html(idHtml + $(titleObj).html());
                return true;
            }
            return false;
        }
        private boards_setID(idTagLookFor, id): boolean {
            var titleObj = $("#" + idTagLookFor + " .witTitle");
            if ($(titleObj).length > 0 && $(titleObj).find(".TitleAdded").length == 0) {
                var idHtml = "<span class='TitleAdded' 
                style='font-weight:bold;'>" + id + "</span> - ";
                $(titleObj).html(idHtml + $(titleObj).html());
                return true;
            }
            return false;
        }
        private taskboard_setRequirementState(id, state): boolean {
            var titleObj = $("#taskboard-table_p" + id + " .witTitle");
            if ($(titleObj).length > 0 && $(titleObj).find(".StateAdded").length == 0) {
                var stateHtml = "<br/><br/><span class='StateAdded' 
                style='color:#505050;font-size:smaller;font-weight:bold;'>" + state + "</span>";
                $(titleObj).html($(titleObj).html() + stateHtml);
                return true;
            }
            return false;
        }
        private workItemChanged(sender, workItemChangedArgs): void {
            if (workItemChangedArgs.change === this.TFS_WorkItemTracking.WorkItemChangeType.Reset || 
            workItemChangedArgs.change === this.TFS_WorkItemTracking.WorkItemChangeType.SaveCompleted) {
                var that = this;
                var id = workItemChangedArgs.workItem.id;
                var state = workItemChangedArgs.workItem.getFieldValue("System.State");
                if (that.isTaskBoard()) {
                    window.setTimeout(function () {
                        that.workWithTaskBoard(id, state, that);
                    }, 100);
                } else if (that.isAgileBoard()) {
                    window.setTimeout(function () {
                        that.workWithAgileBoard(id, that);
                    }, 100);
                }
            }
        }
        private loadWorkItems(idsToFetch: Array, onComplete): void {
            var that = this;
            that.loadWorkItemsWork(idsToFetch, onComplete, that);
        }
        private loadWorkItemsWork(idsToFetch: Array, onComplete, that: Core): void {
            var takeAmount = 100;
            if (takeAmount >= idsToFetch.length) {
                takeAmount = idsToFetch.length;
            }
            if (takeAmount > 0) {
                that.WorkItemManager.store.beginPageWorkItems(idsToFetch.splice(0, takeAmount), [
                    "System.Id",
                    "System.State"
                ], function (payload) {
                        that.loadWorkItemsWork(idsToFetch, onComplete, that);
                        $.each(payload.rows, function (index, row) {
                            onComplete(index, row, that);
                        });
                    }, function (err) {
                        that.loadWorkItemsWork(idsToFetch, onComplete, that);
                        alert(err);
                    });
            }
        }
        private initWorkItemManagerEvents(): void {
            var service = this.TFS_OM.TfsTeamProjectCollection.getDefaultConnection().getService
                          (this.TFS_WorkItemTracking.WorkItemStore);
            this.WorkItemManager = service.workItemManager;
            var that = this;
            this.WorkItemManager.attachWorkItemChanged(function (sender, workItemChangedArgs) {
                that.workItemChanged(sender, workItemChangedArgs);
            });
        }
    }
} 

这就是完成神奇效果所需的所有代码,剩下的就是将其连接到扩展。这可以通过替换扩展原始片段中的以下方法来完成。

B1n4ryD1g1tTfsExtension.prototype.initialize = function () {
	var bdCore = new B1n4ryD1g1tModule.Core
                 (require1, exports, TFS, Core, TFS_OM, TFS_WorkItemTracking);
	bdCore.init();
}; 

如果现在刷新页面,并且调试技巧正在运行,您将看到您的工作项有 ID,并且在任务板上,需求状态正在显示。

以前

imageimage

操作后

imageimage

希望这能像它在我身上一样激发他人的灵感。正如引言中所述,我将在以后的文章中扩展本文涵盖的内容,以提供更多细节。

© . All rights reserved.