Share via


Work Item Custom Control Development in TF Web Access 2012 – Deployment

In the previous post, we talked about the development changes of a Work Item Custom Control in the new version of Team Foundation Web Access. In this post, we’ll dive into details of Work Item Custom Control deployment which is completely different than the previous version.

In TF Web Access 2010, you needed to place the assembly which contains the Work Item Custom Control implementation along with a manifest file in one of the search folders under Web Access installation folder. The manifest file (.wicc) was necessary for Web Access to resolve details about the custom control like assembly name the control lives in and the control type name.

New Web Access requires a zip package to be created which includes JavaScript files and a manifest file. The name of the manifest file must be manifest.xml and a typical content of it is as follows:

 <WebAccess version="11.0">
  <plugin name="Voting Button Custom Control" vendor="Acme" moreinfo="https://www.acme.com" version="2.0.0" >
    <modules>
      <module namespace="Acme.VoteButton" kind="TFS.WorkItem.CustomControl"/>  
    </modules>
  </plugin>
</WebAccess>

Plugin name, vendor, moreInfo and version attributes will appear in the extensions list of Web Access. It is possible to specify multiple modules inside a single extension package. Also please note that, the namespace attribute of the module should match the highlighted value below which exists in your JavaScript file and was explained in the previous post:

 TFS.module("Acme.VoteButton",
    [
        "TFS.WorkItemTracking.Controls",
        "TFS.WorkItemTracking",
        "TFS.Core"
    ],
    function () {
        // custom control implementation
    }
);

The kind attribute of a module specifies the type of the module. Specify TFS.WorkItem.CustomControl as the kind value which ensures Web Access will load your module whenever the custom control is needed.

As mentioned in the previous post, you have to have two versions of the .js file, debug and min. During the development and testing phase, you can use the debug version of the .js file by cloning and renaming it to min.js but it is suggested that, you minify your .js file when the custom control is ready for the production.

Now you have the .js files and the manifest file ready, you can create your package. The simplest way of doing this is to place these three files in a temporary folder, select them all, right click and send to compressed folder.

image

You are free to specify any name for your package as it will not be used by Web Access.

After you created your package, you need to upload the package to Web Access and this is going to be done inside Web Access. Please note that, the scope of an extension package is for the application (or account for hosted TFS) meaning that it will be available to all project collections and projects. Extensions are not supported for TFS Hosted Preview because there is no way to customize the work item types for now.

In order to navigate to extensions management page you can either navigate directly to https://tfsinstance:8080/tfs/_admin/_Extensions page or navigate to https://tfsinstance:8080/tfs and click the administration icon on the upper right corner first.

image

And then click to extensions link to go to extensions management page.

image

Inside the extensions management page, click the plus icon to install your package.

image

From the dialog, click the browse button and specify the package you previously created. Then click OK button to upload the package.

image

By default, newly installed extensions are disabled. You need to activate the extension package by clicking “Enable” button on the right. Then click “OK” to pass the confirmation.

image

Also note that, as we mentioned earlier, you can see the extension details you specified in the manifest file in this list.

After having the extension package installed, you need to change the definition of the work item type which you want to add the custom control to. You can use witadmin command line tool to export and import work item type definitions.

On a computer which has Visual Studio installed, open the developer command prompt from All Programs\Visual Studio\Visual Studio Tools. Then export the desired work item type definition using the following command:

witadmin exportwitd /n:Bug /f:c:\temp\bug.xml /collection:https://tfsinstance:8080/tfs/collectionname /p:projectname

Open the exported work item type definition from the file path you specified in the command and add the field definition to Fields section if necessary (you can use the existing fields).

 <FIELD name="VoteButton" refname="ACME.VoteButton" type="Integer">
  <HELPTEXT>Vote button</HELPTEXT>
</FIELD>

Then add the necessary entry to Form\Layout section for your custom control.

 <Control FieldName="ACME.VoteButton" Type="VoteButton" Label="Vote" LabelPosition="Left" />

Save the file and import the work item type definition using the following command to make your custom control appear in the work item form.

witadmin importwitd /f:c:\temp\bug.xml /collection:https://tfsinstance:8080/tfs/collectionname /p:projectname

You’re done. After connecting to web access and opening a work item which has the custom control, you can see it in the form.

image

While implementing the custom control, package creation and upload steps will probably occur several times and we know that it is not fun. However, we are planning to make improvements on this area to have a seamless development environment. At the same time, you can refer to this blog post about a Fiddler trick to be able to change your JavaScript file without ever needing to install your package again and again.

Let us know if you have any questions or feedback.

Comments

  • Anonymous
    November 07, 2012
    i try to deploy the sample code on my tfs 2012 sytem. if i enable the package on the extensions management page and change to register web access or control panel i keep an error message that the module 'Acme.VoteButton' cannot load and the connection is balked. what can i do to take the mistake?

  • Anonymous
    November 28, 2012
    #SBauhaus Are you getting the error when you try to upload the package?

  • Anonymous
    December 02, 2012
    No, if i enable the plugin and then change to wb access page.

  • Anonymous
    December 11, 2012
    #SBauhaus Can you please send me the source code of your javascript file at serkani |at| microsoft |dot| com (along with a screenshot of the error you get)?

  • Anonymous
    February 05, 2013
    I can not run, the error that the file was not found. WICC

  • Anonymous
    February 25, 2013
    @Fernando: Which team foundation server Version is used?

  • Anonymous
    June 20, 2013
    In this button only client side logic was implemented. I am trying to use some server side code like showing the project name, workitem ID etc. Not able to do that. Can u give some idea on this. Thanks In advance.... Regards, Niranjan

  • Anonymous
    July 18, 2013
    @Niranjan, First of all, the new extensibility model does not support server side programming, it’s client only. Secondly, you can still access the requested information inside your custom control using following:

  • Project name: this._workItem.project.name
  • Work Item Type Name: this._workItem.workItemType.name
  • Assigned to: this._workItem.getFieldValue(“Assigned To”) Please let me know if this is not what you are looking for. Serkan
  • Anonymous
    November 18, 2013
    Hi Serkan How do you get the information like project name etc. Is there any Intellisense possibilities or an object browser. For example I want to find a way to get anothers control id. Maybe you can guide me how I can find out this by my own? Many thanks Nicolas

  • Anonymous
    January 07, 2014
    @Nicolas this._workItem.project will give you access to the current project. Project has properties like name, guid, etc. Unfortunately, there is no intellisense support for now for custom control development. Browser debugger tools might help you here to figure out project details. Another alternative would be fiddler traces which will contain JSON representation of a project. For accessing another control id.. I don't know what's your scenario here but it's not a common pattern for controls to talk another controls directly. Communication is performed on fields usually. If you can provide more details about your scenario, I'd like to help you out with that. Regards Serkan

  • Anonymous
    January 15, 2014
    this two posts help me a lot, thank you Serkan

  • Anonymous
    January 16, 2014
    Hi, I create a new work item type and added a timesheet control to it. After installing, it worked on VS IDE but didn't work on web access. I am using TFS/VS 2012, installed extensions, and getting this error: Cannot create work item control of type "timesheet". This is js file: TFS.module("TimeSheetControl.WorkItemTimeSheetControl",    [        "TFS.WorkItemTracking.Controls",        "TFS.WorkItemTracking",        "TFS.Core"    ],    function () {        // module content        var WITOM = TFS.WorkItemTracking,            WITCONTROLS = TFS.WorkItemTracking.Controls,            delegate = TFS.Core.delegate;        // Constructor for WorkItemTimeSheetControl        function WorkItemTimeSheetControl(container, options, workItemType) {            this.baseConstructor.call(this, container, options, workItemType);        }        // WorkItemTimeSheetControl inherits from WorkItemControl        WorkItemTimeSheetControl.inherit(WITCONTROLS.WorkItemControl, {            _control: null,            _init: function () {                this._base();            }        });        // Register this module as a work item custom control called "WorkItemTimeSheetControl"        WITCONTROLS.registerWorkItemControl("WorkItemTimeSheetControl", WorkItemTimeSheetControl);    }); What am i missing here?

  • Anonymous
    January 16, 2014
    @Kevin, Please make sure that the control type you specify in the work item template matches the type you register in your custom control. In your case, the control type in the work item template must be "WorkItemTimeSheetControl" as you register it like below: WITCONTROLS.registerWorkItemControl("WorkItemTimeSheetControl", WorkItemTimeSheetControl);

  • Anonymous
    January 17, 2014
    Thanks Serkan. I have that statement (see above). Oh one more thing i forgot to ask, Do I have make separate web access control? And where the .wicc file should be placed? If it's not too much to ask, can i send you project zip so you can have a look?

  • Anonymous
    January 17, 2014
    @Kevin, You don't need a .wicc file any more. All you need is js file (debug and min version) along with manifest.xml. Then you need to pack them into a zip file as mentioned in the post. You can also find the details about how to register the extension above.

  • Anonymous
    January 20, 2014
    Thanks for reply. Serkan. I packaged .js files and manifest.xml file. Now i don't see the error but, i don't see custom control either.  Looks like this is because my user control uses datagridview control, which is not compatible to web access view. I tried several times making package including, dll, and without dll. No luck on both cases.I really wish there is some documentation. Now i am trying to make it work adding html controls on Js file. But again, i can't load workitem data. How do I  read workitem data here: WorkItemTimeSheetControl.inherit(WITCONTROLS.WorkItemControl,            {                _control: null,                _init: function () {                    this._base();                    var myId = this._options.controlId;                    var a = " <br/>Date: <input id =" + '"' + myId + "1" + '"' + " type=" + '"' + "date" + '"' + " name=" + '"' + "txtEntryDate" + '"' + ">";                    var b = " Hours: <input id =" + '"' + myId + "2" + '"' + " type=" + '"' + "number" + '"' + " name=" + '"' + "txtEntryHours" + '"' + " step=" + '"' + "any" + '"' + ">";                    var c = " <select =" + '"' + myId + "3" + '"' + ">  <option value=" + '"' + "Admin" + '"' + ">Admin</option>" +                                        "<option value=" + '"' + "Eng" + '"' + ">Engineering</option>" +                                        " <option value=" + '"' + "Meeting" + '"' + ">Meeting</option>" +                                        "<option value=" + '"' + "ProjectManagement" + '"' + ">Project Management</option>" +                                        "<option value=" + '"' + "Other" + '"' + ">Other</option></select>";                    var d = " Other:<input id =" + '"' + myId + "4" + '"' + " type=" + '"' + "text" + '"' + " name=" + '"' + "txtEntryType" + '"' + ">  ";                    var x = "  <input id =" + '"' + myId + "5" + '"' + " type=" + '"' + "button" + '"' + " name=" + '"' + "btnAdd" + '"' + "Value=" + '"' + "Add" + '"' + ">";                    var y = a + b + c + d;                    $(y).appendTo(this._container);                    $(x).appendTo(this._container);                    // read value from workitem and load it in control                  // ....................................................................                },                bind: function (workitem) {                    this.base.bind(workitem);                }            });

  • Anonymous
    January 20, 2014
    @Kevin, There is no work item around during _init. When a work item is being displayed, bind method called and then invalidate gets called which enables each control to update its data. An example is like below. You can access this._workItem in the below method as well as read the updated field value. invalidate: function(flushing) {    this._base(flushing);    // read value from workitem or field and load it in control }

  • Anonymous
    January 27, 2014
    thanks Serkan, That worked.

  • Anonymous
    January 31, 2014
    Thanks Serkan for Very much useful info. I have implemented the same and was able to get this working. I used this code sample to get my use case working a little bit. (do not close a parent if child is open) The issues i'm facing are,

  1. The Status field is hardcoded, how do I refer to this.
  2. the functionality is working as required, but when I move from one work item to another, and get back to the old work item, alert is firing multiple times. When debugged, I found that the event is staying back with the work item. It means the UnBind is not working. I also observed that the workItem.events . workitem-changed array is incremented when a new work item is opened. Hence the multiple process of _workItemChangeDelegate.
  3. Not able to consolidate the alert message to display all the open children in one message. Can send you the code, but not able to paste here.
  • Anonymous
    January 31, 2014
    // Register this module as "FF.WITEventHandler" and declare // dependencies on TFS.WorkItemTracking.Controls, TFS.WorkItemTracking and TFS.Core modules TFS.module("FF.WITEventHandler",    [        "TFS.WorkItemTracking.Controls",        "TFS.WorkItemTracking",        "TFS.Core"    ],    function () {        // module content        var WITOM = TFS.WorkItemTracking,            WITCONTROLS = TFS.WorkItemTracking.Controls,            delegate = TFS.Core.delegate;        // Constructor for WITEventHandler        function WITEventHandler(container, options, workItemType) {            this.baseConstructor.call(this, container, options, workItemType);        }        // WITEventHandler inherits from WorkItemControl        WITEventHandler.inherit(WITCONTROLS.WorkItemControl, {            _control: null,            _status: null,            // Initialize the control UI without data (in "blank" state).            // Framework calls this method when the control needs to render its initial UI            // Notes:            // - The work item data is NOT available at this point            // - Keep in mind that work item form is reused for multiple work items            // by binding/unbinding the form to work item data            _init: function () {                this._base();                ////$('body').click(function (event) {                ////    alert(event.target);                ////});                ////$('body').change(function (event) {                ////    alert(event.target);                ////});            },            // Update the control data            // Framework calls this method when the control needs to update itself, such as when:            // - work item form is bound to a specific work item            // - underlying field value has changed due to rules or another control logic            invalidate: function (flushing) {            },            // Clear the control data            // Framework calls this method when the control needs to reset its state to "blank", such as when:            // - work item form is unbound from a specific work item            clear: function () {            },            bind: function (workItem) {                this._base(workItem);                var oldStateValue = witc_110_txt.value;                this._changedDel = function (sender, args) {                }                this._workItemChangeDelegate = function (sender, args) {                    // this.TFS_WorkItemTracking.WorkItemChangeType.FieldChange                    //if (args.change === this.TFS_WorkItemTracking.WorkItemChangeType.FieldChange) {                    if (args.change === "field-change") {                        if (this.IsStateClosed(sender, args)) {                            this.isChildOpen(workItem, oldStateValue);                        }                    }                }

  • Anonymous
    January 31, 2014
                   // Delegate enables the specified function to run under current custom control's context                // so that you can access other methods inside _workItemChangeDelegate                workItem.attachWorkItemChanged(TFS.Core.delegate(this, this._workItemChangeDelegate));            },            unbind: function (workItem) {                // this._base(workItem);                if (this._workItemChangeDelegate) {                    this._workItem.detachWorkItemChanged(this._workItemChangeDelegate);                    //if (typeof workItem != 'undefined') {                    //    workItem.detachWorkItemChanged(this._workItemChangeDelegate);                    //}                    //delete this._workItemChangeDelegate;                }            },            IsStateClosed: function (sender, args) {                for (var i in args.changedFields) {                    if (args.changedFields[i].fieldDefinition.name === "State" && witc_110_txt.value === "Closed") {                        //sender.fieldUpdates[i].value = "New";                        return true;                    }                }                return false;            },            isChildOpen: function (parentWorkItem, oldStateValue) {                var links = parentWorkItem.getLinks();                var workItemDesc = "";                var isOpen = false;                var t = "        ";                for (var i in links) {                    var child = null;                    var state = "";                    if (links[i].baseLinkType === "WorkItemLink" && links[i].getLinkTypeEnd().name == "Child") {                        //alert("in is child open" + links[i].getTargetId());                        parentWorkItem.store.beginGetWorkItem(links[i].getTargetId(), function (child, workItemDesc, isOpen, state) {                            state = child.fieldMap.STATE.getValue();                            // if state is not closed, then return true                            if (state != "Closed") {                                //isOpen = true;                                workItemDesc = child.id + t + child.workItemType.name + t+t + child.fieldMap.TITLE.getValue();                                alert('nChild Work Item n' + workItemDesc+ 'nn'                                    + ' is not closed.n Click on All Links in the work item to see the Child items for this work item.'                                    );                                witc_110_txt.value = oldStateValue;                            }                        });                    }                }                //alert( workItemDesc);            }        });   // Register this module as a work item custom control called "WITEventHandler"        WITCONTROLS.registerWorkItemControl("WITEventHandler", WITEventHandler);        return {            WITEventHandler: WITEventHandler        };    });

  • Anonymous
    March 05, 2014
    I have written articles to get this working on visual studio as well as web refer to www.codeproject.com/.../Close-a-Work-Item-only-if-Child-Work-items-are-c-2 www.codeproject.com/.../Close-a-Work-Item-Only-if-Child-Work-Items-are-C

  • Anonymous
    June 04, 2014
    I am currently trying to create a Team Web Access custom control for my work item. We already have a functional custom control with the Visual Studio Interface (via Team Explorer). The custom control developed in c# adds a few buttons which will invoke some actions when pressing these buttons. I was wondering how to properly bind (or refer/link) the functionalities of the WICC to the Team Web Access Custom Control. If itès impossible, do I have to re-define this custom control in the javascript?