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:
- 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.
Comments
Anonymous
June 24, 2013
hey, first of all this is a Great Post, i just wanted to know if there is also a possibility to customize the dashboard itself and not a Task/Bug for example i would like to add a new TAB with my own reports next to the main project tab thanks Jeremy souffirAnonymous
July 01, 2013
is his still current? my extension is complaining the 'define' methid is not called.