TFS 2012 Web Access Customizations, Part 4: Sample Extensions

In this post I will show you sample code for several Web Access controls. This should help you grasp all the concepts fairly quickly and get started developing your own extensions.

Here is the screenshot of what we are trying to achieve today:

 

First extension that we are going to look at is a progress bar that reflects state of a bug. It will be empty for a new bug, half full for a complete bug and full when the bug gets closed. You could also modify it to reflect your work item’s states more appropriately. For the progressbar, I am using the jQuery UI and the default theme that comes with TFS.

 TFS.module("TFS.WA.ProgressBar",
 [
 "TFS.WorkItemTracking.Controls",
 "TFS.WorkItemTracking",
 "TFS.Core"
 ],
 function () {
 var WITOM = TFS.WorkItemTracking,
 WITCONTROLS = TFS.WorkItemTracking.Controls,
 delegate = TFS.Core.delegate,
 moduleBaseUrl = TFS.getModuleBase("TFS.WA.ProgressBar");
 
 // Constructor for ProgressBar
 function ProgressBar(container, options, workItemType) {
 this.baseConstructor.call(this, container, options, workItemType);
 }
 
 // ProgressBar inherits from WorkItemControl
 ProgressBar.inherit(WITCONTROLS.WorkItemControl, {
 _control: null,
 
 // Initialize the control UI without data (in "blank" state).
 _init: function () {
 this._base();
 
 //add css style
 //$("<link />").attr("href", moduleBaseUrl + "style.css")
 //.attr("type", "text/css").attr("rel", "stylesheet").appendTo($("head").first());
 
 this._control = $("<div id='progressbar'></div>").appendTo(this._container);
 },
 
 // Update the control data
 invalidate: function (flushing) { 
 var a = this._workItem.getField('System.State');//same as this._getField() because this control is associated with System.State field
 
 switch(a.getDisplayText())
 {
 case "Active":
 $(this._control).progressbar({ value: 0 });
 break;
 case "Resolved":
 $(this._control).progressbar({ value: 50 });
 break;
 case "Closed":
 $(this._control).progressbar({ value: 100 });
 break;
 }
 },
 
 // Clear the control data
 clear: function () {
 
 }
 
 });
 
 // Register this module as a work item custom control called "ProgressBar"
 WITCONTROLS.registerWorkItemControl("TFS.WA.ProgressBar", ProgressBar);
 
 return {
 ProgressBar: ProgressBar
 };
 });

Before deploying this extension, you will need to update your work item (most likely Bug) definition (please refer to Part 3) to include the control in the WebAccess form layout:

 <Control FieldName="System.State" Type="ProgressBar" Label="State:" LabelPosition="Left" />
 

 

The other sample is much more complex. While the scenario to use this extension is purely artificial, it shows well some more advanced techniques such as work item querying or progress indicator usage. Btw, I will dedicate one separate post entirely to TFS Progress Indicator, so don't worry if you do not fully understand how it works. The control itself is a combo box that lists all bugs in the project. It remembers what bug is selected for each work item and displays who the bug is assigned to.

 TFS.module("TFS.WA.BugList",
 [
 "TFS.WorkItemTracking.Controls",
 "TFS.WorkItemTracking",
 "TFS.Core"
 ],
 function () {
 
 // module content
 
 var WITOM = TFS.WorkItemTracking,
 WITCONTROLS = TFS.WorkItemTracking.Controls,
 delegate = TFS.Core.delegate,
 moduleBaseUrl = TFS.getModuleBase("TFS.WA.BugList");
 
 // Constructor for URIs
 function BugList(container, options, workItemType) {
 this.baseConstructor.call(this, container, options, workItemType);
 }
 
 // BugList inherits from WorkItemControl
 BugList.inherit(WITCONTROLS.WorkItemControl, {
 _control: null,
 _loaded: false,
 
 // Initialize the control UI without data (in "blank" state).
 _init: function () {
 this._base();
 
 //add css style
 $("<link />").attr("href", moduleBaseUrl + "TFS.WA.BugList.styles.css")
 .attr("type", "text/css").attr("rel", "stylesheet").appendTo($("head").first());
 
 this._control = $("<div><select id='BugListList'/><span id='BugListRefreshButton' class='icon icon-refresh-2' /><br /><label class='workitemcontrol-label'>Assigned To:</label><input readonly='readonly' type='text' id='BugListAssignedTo'></div>")
 .appendTo(this._container);
 $(this._control).children("#BugListList").bind("change", delegate(this, this._changed));
 $(this._control).children("#BugListRefreshButton").bind("click", delegate(this, this._updateList));
 },
 
 // Update the control data
 invalidate: function (flushing) {
 if (!this._loaded)
 this._updateList(delegate(this, this._updateListSelection));
 else
 this._updateListSelection();
 },
 
 // Clear the control data
 clear: function () {
 
 },
 
 _updateListSelection: function() {
 var list = $(this._control).children("#BugListList");
 
 $(list).val(this._getField().getValue());
 
 this._updateAssignedTo();
 },
 
 _changed: function () {
 var list = $(this._control).children("#BugListList");
 var val = list.val();
 
 if (this._getField().getValue() == val)
 return; //no need to update the underlying field
 
 //store new value in the underlying field
 this._getField().setValue(val);
 },
 
 _updateAssignedTo: function () {
 var progressId = TFS.globalProgressIndicator.actionStarted("TFS.WA.BugList_updateAssignedTo", true);
 
 var list = $(this._control).children("#BugListList");
 var field = $(this._control).children("#BugListAssignedTo");
 var val = list.val();
 
 //clear 
 $(field).val('');
 
 //call query and fill the field
 var query = "Select [Assigned To] From WorkItems Where [Id] = '" + val + "'";
 this._runQuery(query, function (res) {
 if (res.payload.rows.length == 1) {
 $(field).val(res.payload.rows[0][1]);
 }
 
 TFS.globalProgressIndicator.actionCompleted(progressId);
 });
 
 },
 
 _updateList: function (callback) {
 var progressId = TFS.globalProgressIndicator.actionStarted("TFS.WA.BugList_updateList", true);
 
 var list = $(this._control).children("#BugListList");
 
 //clear options
 $(list).text('');
 
 var that = this;
 
 //call query and fill the list with options
 var query = "Select [State], [Title] From WorkItems Where [Work Item Type] = 'Bug' Order By [State] Asc, [Changed Date] Desc";
 this._runQuery(query, function (res) {
 $.each(res.payload.rows, function (i, v) {
 $(list).append($('<option />').attr('value', v[0]).text(v[2] + " [" + v[1] + "]"));
 });
 
 this._loaded = true;
 
 if ($.isFunction(callback)) callback.call();
 
 TFS.globalProgressIndicator.actionCompleted(progressId);
 });
 },
 
 _runQuery: function (query, callback) {
 this._workItem.store.beginQuery(this._workItem.project, query, callback, null, null);
 }
 
 });
 
 
 // Register this module as a work item custom control called "BugList"
 WITCONTROLS.registerWorkItemControl("TFS.WA.BugList", BugList);
 
 return {
 BugList: BugList
 };
 });

Again, before deploying this extension add following to the work item definition:

  1. Add new field to hold the selection:
 <FIELD name="SelectedBug" refname="TFS.WA.SelectedBug" type="Integer">
 <HELPTEXT>Bug individually picked from the list of all project bugs</HELPTEXT>
 </FIELD>

       2. Add the control itself:

 <Control FieldName="TFS.WA.SelectedBug" Type="TFS.WA.BugList" Label="Project Bugs:" LabelPosition="Left" />
 

I hope these samples were useful to you and you. In the next post, I will show you how to use Fiddler to speed up your development by surpassing the need to re-deploy the extension every time you make even the slightest modification to the source code.