英文:
How to add a drop-down list for data entry in Azure DevOps Server 2022?
问题
I've recently switched process models from XML to Inherited, to allow for easier customization of Work Item Types, etc.
Under the old XML model we could modify, for example, a PBI's Effort
field to create a pick list of numbers for easier data entry. I'm not seeing how to do this with the Inheritance model:
The documentation for doing so under XML is here, but there doesn't seem to be an equivalent under Inheritance.
For example, here's the Effort
field in my XML-modeled collection:
Is it possible to modify a PBI to this extent under the Inheritance model? If so, how does one go about doing it?
英文:
I've recently switched process models from XML to Inherited, to allow for easier customization of Work Item Types, etc.
Under the old XML model we could modify, for example, a PBI's Effort
field to create a pick list of numbers for easier data entry. I'm not seeing how to do this with the Inheritance model:
The documentation for doing so under XML is here, but there doesn't seem to be an equivalent under Inheritance.
For example, here's the Effort
field in my XML-modeled collection:
Is it possible to modify a PBI to this extent under the Inheritance model? If so, how does one go about doing it?
答案1
得分: 0
这是您提供的代码的中文翻译:
"使用内置方法无法完成。这是微软支持工程师的说法。
将这个笨拙的解决方法拼凑在一起并不愉快,但至少它实现了目标(我这么说是因为我讨厌在JavaScript中工作)。
在浏览器中安装TamperMonkey扩展并加载下面的脚本。在URL中用您自己的值替换example
、host
、collection
和project
。编辑PBI并感受爱意。
请注意,这仅适用于Scrum模板下的PBI以及它们的Effort和Business Value字段;这是我目前需要涵盖的全部内容。脚本需要调整以支持其他工作项类型和字段。"
// ==UserScript==
// @name PBI PickLists
// @namespace https://example.com/
// @version 0.1
// @description 尝试统治世界!
// @author You
// @match http://host/collection/*
// @icon none
// @grant none
// ==/UserScript==
(function() {
"use strict";
// 这里是您的代码...
function setSelected(item) {
item.setAttribute("aria-selected", "true");
item.classList.add("selected");
item.style.backgroundColor = "#deecf8";
item.style.border = "1px solid #c7dff3";
}
function clearSelected(item) {
item.setAttribute("aria-selected", "false");
item.classList.remove("selected");
item.style.backgroundColor = "white";
item.style.border = "1px solid white";
}
function getFibonacciList(textBox, container) {
var pickList = document.createElement("ul");
var items = ["1", "2", "3", "5", "8", "13"];
pickList.setAttribute("role", "listbox");
pickList.classList.add("items");
pickList.style.marginBottom = "0px";
pickList.style.marginTop = "0px";
for (var i = 0; i < items.length; i++) {
var item = document.createElement("li");
item.setAttribute("role", "option");
item.setAttribute("aria-posinset", i + 1);
item.setAttribute("aria-setsize", items.length);
item.setAttribute("data-id", i);
item.style.padding = "3px";
item.style.cursor = "pointer";
item.textContent = items[i];
if (item.textContent == textBox.value) {
setSelected(item);
} else {
clearSelected(item);
}
pickList.appendChild(item);
}
pickList.childNodes.forEach(function(item) {
item.addEventListener("mouseenter", function() {
setSelected(item);
});
item.addEventListener("mouseleave", function() {
clearSelected(item);
});
});
return pickList;
}
function clearList(textBox, uniqueId) {
var container = document.getElementById(uniqueId);
if (container) {
var items = container.querySelectorAll("li");
var item = container.querySelector("li.selected");
if (item) {
textBox.value = item.textContent;
textBox.dispatchEvent(new Event("change"));
}
items.forEach(function(item) {
item.removeEventListener("mouseenter", function() {
setSelected(item);
});
item.removeEventListener("mouseleave", function() {
clearSelected(item);
});
});
container.remove();
}
}
function buildList(textBox, uniqueId) {
var container = document.getElementById(uniqueId);
if (!container) {
var rect = textBox.getBoundingClientRect();
var width = rect.width + 5
var x = rect.left - 3;
var y = rect.top + rect.height + 1;
container = document.createElement("div");
container.setAttribute("aria-label", textBox.getAttribute("aria-label"));
container.setAttribute("id", uniqueId);
container.classList.add("combo-drop-popup");
container.style.backgroundColor = "#fff";
container.style.transition = 'height 0.25s ease';
container.style.overflow = 'hidden';
container.style.opacity = "1";
container.style.zIndex = "1910887";
container.style.border = "1px solid #c8c8c8";
container.style.height = '0';
container.style.width = width + "px";
container.style.left = x + "px";
container.style.top = y + "px";
var pickList = getFibonacciList(textBox, container);
container.appendChild(pickList);
textBox.parentNode.parentNode.appendChild(container);
setTimeout(function() {
container.style.height = "auto"
}, 250);
}
}
function addPickList(textBox, uniqueId) {
if (textBox) {
var arrow = textBox.parentNode.nextSibling
arrow.style.marginLeft = textBox.offsetWidth - 23 + "px";
arrow.style.marginTop = "3px";
arrow.style.display = "block";
if (!document.getElementById(uniqueId)) {
textBox.addEventListener("focus", function() { buildList(textBox, uniqueId); });
textBox.addEventListener("blur", function() { clearList(textBox, uniqueId); });
arrow.addEventListener("click", function() { buildList(textBox, uniqueId); });
};
}
}
function isPbiEditor() {
var isPbiEditor = false;
var anchors = document.querySelectorAll("a");
for (var i = 0; i < anchors.length; i++) {
var anchor = anchors[i];
if (anchor.href.startsWith("http://host/collection/project/_workitems/edit/")) {
if (anchor.textContent.startsWith("Product Backlog Item")) {
isPbiEditor = true;
break;
}
}
}
return isPbiEditor;
}
var observer = new MutationObserver(function(mutations) {
var effort = document.querySelector("input[aria-label='Effort']");
var value = document.querySelector("input[aria-label='Business Value']");
for (var mutation of mutations) {
if (mutation.type === "childList") {
if (isPbiEditor()) {
addPickList(effort, "58KJ76F");
addPickList(value, "37L9Q9P");
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();
希望这对您有所帮助!
英文:
It can't be done using in-built methods. So say Microsoft Support Engineers.
Tossing together this clunky workaround wasn't pleasant, but at least it accomplishes the goal (I say that because I loathe working in JavaScript).
Install the TamperMonkey extension in your browser and load up the script below. Replace example
, host
, collection
and project
in the URLs with your own values. Edit a PBI and feel the love.
Note that this only addresses PBIs and their Effort and Business Value fields under the Scrum template; that's all I needed to cover at this point. The script will need to be adjusted to support other Work Item Types and fields.
// ==UserScript==
// @name PBI PickLists
// @namespace https://example.com/
// @version 0.1
// @description Try to take over the world!
// @author You
// @match http://host/collection/*
// @icon none
// @grant none
// ==/UserScript==
(function() {
"use strict";
// Your code here...
function setSelected(item) {
item.setAttribute("aria-selected", "true");
item.classList.add("selected");
item.style.backgroundColor = "#deecf8";
item.style.border = "1px solid #c7dff3";
}
function clearSelected(item) {
item.setAttribute("aria-selected", "false");
item.classList.remove("selected");
item.style.backgroundColor = "white";
item.style.border = "1px solid white";
}
function getFibonacciList(textBox, container) {
var pickList = document.createElement("ul");
var items = ["1", "2", "3", "5", "8", "13"];
pickList.setAttribute("role", "listbox");
pickList.classList.add("items");
pickList.style.marginBottom = "0px";
pickList.style.marginTop = "0px";
for (var i = 0; i < items.length; i++) {
var item = document.createElement("li");
item.setAttribute("role", "option");
item.setAttribute("aria-posinset", i + 1);
item.setAttribute("aria-setsize", items.length);
item.setAttribute("data-id", i);
item.style.padding = "3px";
item.style.cursor = "pointer";
item.textContent = items[i];
if (item.textContent == textBox.value) {
setSelected(item);
} else {
clearSelected(item);
}
pickList.appendChild(item);
}
pickList.childNodes.forEach(function(item) {
item.addEventListener("mouseenter", function() {
setSelected(item);
});
item.addEventListener("mouseleave", function() {
clearSelected(item);
});
});
return pickList;
}
function clearList(textBox, uniqueId) {
var container = document.getElementById(uniqueId);
if (container) {
var items = container.querySelectorAll("li");
var item = container.querySelector("li.selected");
if (item) {
textBox.value = item.textContent;
textBox.dispatchEvent(new Event("change"));
}
items.forEach(function(item) {
item.removeEventListener("mouseenter", function() {
setSelected(item);
});
item.removeEventListener("mouseleave", function() {
clearSelected(item);
});
});
container.remove();
}
}
function buildList(textBox, uniqueId) {
var container = document.getElementById(uniqueId);
if (!container) {
var rect = textBox.getBoundingClientRect();
var width = rect.width + 5
var x = rect.left - 3;
var y = rect.top + rect.height + 1;
container = document.createElement("div");
container.setAttribute("aria-label", textBox.getAttribute("aria-label"));
container.setAttribute("id", uniqueId);
container.classList.add("combo-drop-popup");
container.style.backgroundColor = "#fff";
container.style.transition = 'height 0.25s ease';
container.style.overflow = 'hidden';
container.style.opacity = "1";
container.style.zIndex = "1910887";
container.style.border = "1px solid #c8c8c8";
container.style.height = '0';
container.style.width = width + "px";
container.style.left = x + "px";
container.style.top = y + "px";
var pickList = getFibonacciList(textBox, container);
container.appendChild(pickList);
textBox.parentNode.parentNode.appendChild(container);
setTimeout(function() {
container.style.height = "auto"
}, 250);
}
}
function addPickList(textBox, uniqueId) {
if (textBox) {
var arrow = textBox.parentNode.nextSibling
arrow.style.marginLeft = textBox.offsetWidth - 23 + "px";
arrow.style.marginTop = "3px";
arrow.style.display = "block";
if (!document.getElementById(uniqueId)) {
textBox.addEventListener("focus", function() { buildList(textBox, uniqueId); });
textBox.addEventListener("blur", function() { clearList(textBox, uniqueId); });
arrow.addEventListener("click", function() { buildList(textBox, uniqueId); });
};
}
}
function isPbiEditor() {
var isPbiEditor = false;
var anchors = document.querySelectorAll("a");
for (var i = 0; i < anchors.length; i++) {
var anchor = anchors[i];
if (anchor.href.startsWith("http://host/collection/project/_workitems/edit/")) {
if (anchor.textContent.startsWith("Product Backlog Item")) {
isPbiEditor = true;
break;
}
}
}
return isPbiEditor;
}
var observer = new MutationObserver(function(mutations) {
var effort = document.querySelector("input[aria-label='Effort']");
var value = document.querySelector("input[aria-label='Business Value']");
for (var mutation of mutations) {
if (mutation.type === "childList") {
if (isPbiEditor()) {
addPickList(effort, "58KJ76F");
addPickList(value, "37L9Q9P");
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();
答案2
得分: 0
你可以将自定义字段的数据类型设置为下拉列表(picklist),底层数据类型可以是文本或整数。这样,你就可以提供一个可供选择的值列表。
作为另一种选择,Azure DevOps Marketplace 上提供了许多自定义下拉列表控件,可以在这里找到:Azure DevOps Marketplace。这些控件可以用于将字段与 REST API 或不同的数据类型等进行数据绑定。
如果需要的话,你可以隐藏现有字段并在表单上放置一个新的控件。
英文:
You can set the datatype for the custom field to picklist and the underlying datatype to text or integer.
That way you can provide a list of values to select from.
As an alternative, there are a number of custom picklist controls available on the Azure DevOps Marketplace. These can be used to databind a field to a REST API or different datatypes etc.
If needed you can hide the existing field and put a new control on the form.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论