如何在Azure DevOps Server 2022中添加一个数据输入的下拉列表?

huangapple go评论53阅读模式
英文:

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:

如何在Azure DevOps Server 2022中添加一个数据输入的下拉列表?

The documentation for doing so under XML is here, but there doesn't seem to be an equivalent under Inheritance.

如何在Azure DevOps Server 2022中添加一个数据输入的下拉列表?

For example, here's the Effort field in my XML-modeled collection:

如何在Azure DevOps Server 2022中添加一个数据输入的下拉列表?

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:

如何在Azure DevOps Server 2022中添加一个数据输入的下拉列表?

The documentation for doing so under XML is here, but there doesn't seem to be an equivalent under Inheritance.

如何在Azure DevOps Server 2022中添加一个数据输入的下拉列表?

For example, here's the Effort field in my XML-modeled collection:

如何在Azure DevOps Server 2022中添加一个数据输入的下拉列表?

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中用您自己的值替换examplehostcollectionproject。编辑PBI并感受爱意。

请注意,这仅适用于Scrum模板下的PBI以及它们的EffortBusiness 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() {
    &quot;use strict&quot;;

    // Your code here...
    function setSelected(item) {
        item.setAttribute(&quot;aria-selected&quot;, &quot;true&quot;);
        item.classList.add(&quot;selected&quot;);
        item.style.backgroundColor = &quot;#deecf8&quot;;
        item.style.border = &quot;1px solid #c7dff3&quot;;
    }

    function clearSelected(item) {
        item.setAttribute(&quot;aria-selected&quot;, &quot;false&quot;);
        item.classList.remove(&quot;selected&quot;);
        item.style.backgroundColor = &quot;white&quot;;
        item.style.border = &quot;1px solid white&quot;;
    }

    function getFibonacciList(textBox, container) {
        var pickList = document.createElement(&quot;ul&quot;);
        var items = [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;5&quot;, &quot;8&quot;, &quot;13&quot;];

        pickList.setAttribute(&quot;role&quot;, &quot;listbox&quot;);
        pickList.classList.add(&quot;items&quot;);
        pickList.style.marginBottom = &quot;0px&quot;;
        pickList.style.marginTop = &quot;0px&quot;;

        for (var i = 0; i &lt; items.length; i++) {
            var item = document.createElement(&quot;li&quot;);
            item.setAttribute(&quot;role&quot;, &quot;option&quot;);
            item.setAttribute(&quot;aria-posinset&quot;, i + 1);
            item.setAttribute(&quot;aria-setsize&quot;, items.length);
            item.setAttribute(&quot;data-id&quot;, i);
            item.style.padding = &quot;3px&quot;;
            item.style.cursor = &quot;pointer&quot;;
            item.textContent = items[i];

            if (item.textContent == textBox.value) {
                setSelected(item);
            } else {
                clearSelected(item);
            }

            pickList.appendChild(item);
        }

        pickList.childNodes.forEach(function(item) {
            item.addEventListener(&quot;mouseenter&quot;, function() {
                setSelected(item);
            });

            item.addEventListener(&quot;mouseleave&quot;, function() {
                clearSelected(item);
            });
        });

        return pickList;
    }

    function clearList(textBox, uniqueId) {
        var container = document.getElementById(uniqueId);

        if (container) {
            var items = container.querySelectorAll(&quot;li&quot;);
            var item = container.querySelector(&quot;li.selected&quot;);

            if (item) {
                textBox.value = item.textContent;
                textBox.dispatchEvent(new Event(&quot;change&quot;));
            }

            items.forEach(function(item) {
                item.removeEventListener(&quot;mouseenter&quot;, function() {
                    setSelected(item);
                });

                item.removeEventListener(&quot;mouseleave&quot;, 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(&quot;div&quot;);
            container.setAttribute(&quot;aria-label&quot;, textBox.getAttribute(&quot;aria-label&quot;));
            container.setAttribute(&quot;id&quot;, uniqueId);
            container.classList.add(&quot;combo-drop-popup&quot;);
            container.style.backgroundColor = &quot;#fff&quot;;
            container.style.transition = &#39;height 0.25s ease&#39;;
            container.style.overflow = &#39;hidden&#39;;
            container.style.opacity = &quot;1&quot;;
            container.style.zIndex = &quot;1910887&quot;;
            container.style.border = &quot;1px solid #c8c8c8&quot;;
            container.style.height = &#39;0&#39;;
            container.style.width = width + &quot;px&quot;;
            container.style.left = x + &quot;px&quot;;
            container.style.top = y + &quot;px&quot;;

            var pickList = getFibonacciList(textBox, container);

            container.appendChild(pickList);
            textBox.parentNode.parentNode.appendChild(container);

            setTimeout(function() {
                container.style.height = &quot;auto&quot;
            }, 250);
        }
    }

    function addPickList(textBox, uniqueId) {
        if (textBox) {
            var arrow = textBox.parentNode.nextSibling

            arrow.style.marginLeft = textBox.offsetWidth - 23 + &quot;px&quot;;
            arrow.style.marginTop = &quot;3px&quot;;
            arrow.style.display = &quot;block&quot;;

            if (!document.getElementById(uniqueId)) {
                textBox.addEventListener(&quot;focus&quot;, function() { buildList(textBox, uniqueId); });
                textBox.addEventListener(&quot;blur&quot;, function() { clearList(textBox, uniqueId); });
                arrow.addEventListener(&quot;click&quot;, function() { buildList(textBox, uniqueId); });
            };
        }
    }

    function isPbiEditor() {
        var isPbiEditor = false;
        var anchors = document.querySelectorAll(&quot;a&quot;);

        for (var i = 0; i &lt; anchors.length; i++) {
            var anchor = anchors[i];
            if (anchor.href.startsWith(&quot;http://host/collection/project/_workitems/edit/&quot;)) {
                if (anchor.textContent.startsWith(&quot;Product Backlog Item&quot;)) {
                    isPbiEditor = true;
                    break;
                }
            }
        }

        return isPbiEditor;
    }

    var observer = new MutationObserver(function(mutations) {
        var effort = document.querySelector(&quot;input[aria-label=&#39;Effort&#39;]&quot;);
        var value = document.querySelector(&quot;input[aria-label=&#39;Business Value&#39;]&quot;);

        for (var mutation of mutations) {
            if (mutation.type === &quot;childList&quot;) {
                if (isPbiEditor()) {
                    addPickList(effort, &quot;58KJ76F&quot;);
                    addPickList(value, &quot;37L9Q9P&quot;);
                }
            }
        }
    });

    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.

huangapple
  • 本文由 发表于 2023年5月14日 14:23:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76246122.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定