Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019
Modal dialogs provide a powerful way to create focused user experiences in Azure DevOps extensions. The HostDialogService lets you present a modal dialog that blocks user interaction with the entire Azure DevOps interface until the dialog gets dismissed. This action ensures that users complete important tasks or provide required information.
Use modal dialogs in your extensions to:
- Collect user input through forms
- Display confirmation messages for critical actions
- Show detailed information that requires user attention
- Guide users through multi-step processes
Important
When you create modal dialogs with HostDialogService
, they block interaction with the entire Azure DevOps page, not just your extension. This approach provides a true modal experience but you should use it thoughtfully to avoid disrupting the user workflow.
Prerequisites
Before you can create modal dialogs in your Azure DevOps extension, ensure you have the following:
Category | Requirement | Details |
---|---|---|
Extension setup | Working extension project | A valid vss-extension.json manifest file |
Marketplace registration | Extension registered with Visual Studio Marketplace for testing and deployment | |
Development knowledge | Understanding of Azure DevOps extension development basics | |
Development environment | Node.js and npm | Node.js version 14 or later with npm installed |
Code editor | Visual Studio Code or other IDE recommended | |
Azure DevOps access | Access to an Azure DevOps organization for testing | |
Required packages | Extension SDK | Install: npm install azure-devops-extension-sdk |
Extension API | Install: npm install azure-devops-extension-api |
|
Extension permissions | Manifest scopes | Include appropriate scopes in vss-extension.json , for example: "vso.work" , "vso.project" |
SDK imports | Required modules | Import SDK and services: import * as SDK from "azure-devops-extension-sdk" and import { CommonServiceIds, IHostDialogService } from "azure-devops-extension-api" |
Dialog contents
To start, declare a contribution of type ms.vss-web.control
in your extension manifest. This contribution represents the content displayed within the dialog.
{
"id": "registration-form",
"type": "ms.vss-web.control",
"description": "The content to be displayed in the dialog",
"targets": [],
"properties": {
"uri": "registration-form.html"
}
}
The uri
property references a page that is rendered within the content area of the dialog:
<!DOCTYPE html>
<html>
<head>
<script src="node_modules/azure-devops-extension-sdk/lib/SDK.js"></script>
</head>
<body>
<h2 id="header">Register now</h2>
<p>
<label>Name:</label>
<input id="inpName" />
</p>
<p>
<label>Date of birth:</label>
<input id="inpDob" />
</p>
<p>
<label>Email address:</label>
<input id="inpEmail" />
</p>
<script type="module">
import * as SDK from "azure-devops-extension-sdk";
SDK.init();
const registrationForm = (function() {
const callbacks = [];
function inputChanged() {
// Execute registered callbacks
for(let i = 0; i < callbacks.length; i++) {
callbacks[i](isValid());
}
}
function isValid() {
// Check whether form is valid or not
return !!(name.value) && !!(dateOfBirth.value) && !!(email.value);
}
function getFormData() {
// Get form values
return {
name: name.value,
dateOfBirth: dateOfBirth.value,
email: email.value
};
}
const name = document.getElementById("inpName");
const dateOfBirth = document.getElementById("inpDob");
const email = document.getElementById("inpEmail");
name.addEventListener("change", inputChanged);
dateOfBirth.addEventListener("change", inputChanged);
email.addEventListener("change", inputChanged);
return {
isFormValid: function() {
return isValid();
},
getFormData: function() {
return getFormData();
},
attachFormChanged: function(cb) {
callbacks.push(cb);
}
};
})();
// Register form object to be used across this extension
SDK.register("registration-form", registrationForm);
</script>
</body>
</html>
Show the dialog
To show the dialog (for example, when a user selects an action on a toolbar or menu), call the openDialog
function on an instance of the HostDialogService, passing the fully qualified identifier of the dialog content, for example my-publisher.my-extension.registration-form
and any dialog options:
import * as SDK from "azure-devops-extension-sdk";
SDK.getService<IHostDialogService>(CommonServiceIds.HostDialogService).then((dialogService) => {
const extensionCtx = SDK.getExtensionContext();
// Build absolute contribution ID for dialogContent
const contributionId = `${extensionCtx.publisherId}.${extensionCtx.extensionId}.registration-form`;
// Show dialog
const dialogOptions = {
title: "My Dialog",
width: 800,
height: 600
};
dialogService.openDialog(contributionId, dialogOptions);
});
Advanced dialog features
A function can be called when the OK button is selected. This function is specified by getDialogResult
in the options you provide when showing the dialog.
If a call to getDialogResult
returns a non-null value, this value is then passed to the function specified by okCallback
(also in the options) and the dialog is closed.
In this example, the attachFormChanged
callback gets called when inputs on the form change. Based on whether the form is valid or not, the OK button is enabled or disabled.
import * as SDK from "azure-devops-extension-sdk";
import { CommonServiceIds, IHostDialogService } from "azure-devops-extension-api";
SDK.getService<IHostDialogService>(CommonServiceIds.HostDialogService).then((dialogService) => {
let registrationForm: any;
const extensionCtx = SDK.getExtensionContext();
const contributionId = `${extensionCtx.publisherId}.${extensionCtx.extensionId}.registration-form`;
const dialogOptions = {
title: "Registration Form",
width: 800,
height: 600,
getDialogResult: () => {
// Get the result from registrationForm object
return registrationForm ? registrationForm.getFormData() : null;
},
okCallback: (result: any) => {
// Log the result to the console
console.log(JSON.stringify(result));
}
};
dialogService.openDialog(contributionId, dialogOptions).then((dialog) => {
// Get registrationForm instance which is registered in registrationFormContent.html
dialog.getContributionInstance("registration-form").then((registrationFormInstance) => {
// Keep a reference of registration form instance (to be used previously in dialog options)
registrationForm = registrationFormInstance;
// Subscribe to form input changes and update the Ok enabled state
registrationForm.attachFormChanged((isValid: boolean) => {
dialog.updateOkButton(isValid);
});
// Set the initial ok enabled state
registrationForm.isFormValid().then((isValid: boolean) => {
dialog.updateOkButton(isValid);
});
});
});
});
Control the OK button
Initially, the OK button is disabled. However, you can enable/disable this button by calling the updateOkButton
method on the dialog:
dialogService.openDialog(contributionId, dialogOptions).then((dialog) => {
// Set true/false to enable/disable ok button
dialog.updateOkButton(true);
});
Pass values to the dialog
It's possible to pass initial values to dialog content when it is opened in the host dialog.
{
"id": "registration-form",
"type": "ms.vss-web.control",
"description": "The content displayed in the dialog",
"targets": [],
"properties": {
"uri": "registration-form.html?id={{myId}}"
}
}
When the dialog is opened, following options need to be specified to pass myId
:
const dialogOptions = {
title: "My Dialog Title",
width: 800,
height: 600,
urlReplacementObject: { myId: new Date().getTime() }
};
Customize dialog buttons
The okText
and cancelText
attributes can be used to specify alternate titles for the OK and Cancel buttons:
const dialogOptions = {
title: "My Dialog Title",
width: 800,
height: 600,
okText: "Yes",
cancelText: "No"
};
To not show any buttons on the dialog, you can set the buttons
attribute to []
:
const dialogOptions = {
title: "My Dialog Title",
width: 800,
height: 600,
buttons: []
};
Related resources
If you have a question or are looking for more information, consider going to one of the following areas: