How to hide buttons in Multi-Row Variable Sets (MRVS) without DOM manipulation

Rich Dennis
Tera Expert

MRVSs are increasingly being pre-populated with server data that we want the end user to interact with, but not necessarily have full access to all MRVS features, such as adding rows, editing rows, removing all rows, or deleting rows. In the Community I've read many solutions to address this that required DOM manipulation. As we all (should) know, ServiceNow (the company) discourages DOM manipulation. DOM manipulation isn't guaranteed to be upgrade safe, so I avoid it as much as possible. What follows is my non-DOM way to hide MRVS buttons. FWIW, I haven't tried this method in NextUI/NowUI, so I cannot say whether it is future-proof too. 

 

Steps:

  1. Create standard (non-MRVS) Variable Sets for each button type.
    1. MRVS Hide "Remove All" button
    2. MRVS Hide "Add" button
    3. MRVS Hide "Edit Row" buttons
    4. MRVS Hide "Remove Row" buttons
  2. Create a variable of type "Rich Text Label" in each Variable Set.
  3. Edit the "Rich Text Label" Source Code to include <style> tags.
  4. Insert CSS inside the <style> tags to hide the button
    1. Example:

 

 

<style>
 .btn-default {
  display: none;
 }
</style>
<p>&nbsp;</p>

 

 

  • Change the CSS to match the class that you are hiding. 
    • "Remove All" button = .btn-default
    • "Add" button = .btn.btn-primary.m-r
    • "Edit Row" buttons = .wrapper-xs.fa.fa-pencil
    • "Remove Row" buttons = .wrapper-xs.fa.fa-close
  • Any text within the <p> tags would be displayed in the Catalog Item if there isn't a UI Policy to hide the variable.
  • After editing the Source code do not make any changes to the text in the "Rich Text" field because it will remove the <style> tags.
  • Create a UI Policy in each Variable Set to hide the "Rich Text Label" variable.
  • Add any combination of these Variable Sets to Catalog Items in order to hide a given button type.

 

10 REPLIES 10

Sheldon  Swift
ServiceNow Employee
ServiceNow Employee

Hi @Rich Dennis - Thank you for sharing this option. Personally, I have some concerns about implementing a solution that doesn’t appear to align with the intent of the product team(s). In other words, it might work now, but there’s no test case behind it—and no expectation that it should continue working in future releases.

 

Keeping in mind that the main reason we typically discourage DOM manipulation is because there's no guarantee that element structures or classes won't change—which means future upgrades might break DOM-based solutions—the approach you’ve shared essentially carries the same upgrade risk as the DOM manipulation you're trying to avoid.

 

As an alternative, for Mobile/Service Portal specifically, you could create a new style sheet in sp_css and add it as a widget dependency for the appropriate widget(s) (e.g., SC Catalog Item [widget-sc-cat-item-v2]). This approach is both simple and consistent with the platform’s intended use.

 

Example 1: Always hide the Add and Remove All buttons

span.type-sc_multi_row {
  button {
    display: none !important;
  }
}

-- OR --

span.type-sc_multi_row {
  .form-group {
    button {
      &.btn-primary {
        display: none !important;
      }

      &.btn-default {
        display: none !important;
      }
    }
  }
}

Example 2: Selectively hide the Add and Remove All buttons

The multi-row variable sets have their own element 'sp-sc-multi-row-element' — so you can target that and with the variable set's internal name traverse down the DOM tree keep your styling scoped, readable, and performant.

sp-sc-multi-row-element#sp_formfield_<VARIABLE_SET_INTERNAL_NAME> {

  // Hide all buttons
  button {
    display: none !important;
  }

  // Hide just the "Remove All" button
  button[aria-label^="Remove all rows"] {
    display: none !important;
  }

  // Hide just the "Add" button
  button[aria-label^="Add a row"] {
    display: none !important;
  }

  // Hide the "Actions" column header and cells
  th:first-child,
  td:first-child {
    display: none !important;
  }

  // Hide just the "Remove Row" icon (X)
  a[aria-label^="Remove Row"],
  a.fa-close {
    display: none !important;
  }

  // Hide just the "Edit Row" icon (pencil)
  a[aria-label^="Edit Row"],
  a.fa-pencil {
    display: none !important;
  }
}

Note: The // style comments supported by the SCSS preprocessor, so they won’t appear in the final CSS. If 'Turn off SCSS compilation' is ticked, // comments will cause a syntax error because standard CSS only supports /* */ style comments.

 

Unfortunately, to address for UI16/Next Experience, I think you would still need to use a client script:

function onLoad() {
    // TODO: Implement a proper readiness check, instead of relying on timing
    setTimeout(function() {
        var itemId = ''; // Catalog item sys_id
        var setId = ''; // Variable set sys_id

        // HIDE CONTAINER (sc-table-variable-buttons)
        /*
        var container = g_form.getElement('table_variable_buttons_' + setId);
        if (container) {
            container.addClassName('hide'); // or: container.style.visibility = 'hidden';
        }
        */

        // HIDE BUTTONS
        var buttons = [
            setId + 'Add', // Add button
            itemId + setId + 'removeAllRows', // Remove All button
        ];

        buttons.forEach(function(
            buttonId) {
            var button = g_form
                .getElement(buttonId);
            if (button) {
                button.addClassName('hide'); // or: container.style.visibility = 'hidden';
            }
        });
    }, 1000);
}

 

In the big picture, while I don’t love the idea of managing both a style sheet and a client script to control the visibility of these MRVS actions without DOM manipulation, I do prefer a CSS-based solution here for a few reasons:

  • More reliable on initial render: CSS ensures the element is hidden before it's ever displayed

  • Graceful failure: If a selector stops matching, nothing breaks...the style just isn’t applied

  • Declarative efficiency: CSS is declarative, so the browser applies it efficiently as part of the rendering process, with no execution overhead