ServiceNow Glide 基于纯 JavaScript 中下拉选择的数据列表选项动态过滤

问题描述 投票:0回答:1

我正在 JavaScript 和 Glide 中构建一个过滤器界面,其中有两个下拉菜单:

区域:用于选择区域的下拉列表。 交通:应显示特定于所选区域的交通的数据列表。 当用户选择一个区域时,我希望“公交”选项动态更新以仅显示与该区域相关的公交。我可能要处理数百种交通选择,因此我需要一个既高效又用户友好的解决方案。

我不允许使用任何外部库,如 jQuery 或 Select2。我还希望“交通”选项可搜索,以便用户可以轻松找到特定的交通。

有人可以帮我使用纯 JavaScript 进行设置吗?

当前:

<?xml version="1.0" encoding="utf-8"?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
  <!-- Server-Side Data Collection -->
  <g:evaluate>
    // Gather transits grouped by region
    var gr = new GlideRecord('x_desgr_cspoh_consignation');
    var regionsWithTransits = {};

    gr.query();
    while (gr.next()) {
      var regionValue = gr.getDisplayValue('institution_transit.u_territoire_affaires');
      var transitValue = gr.getDisplayValue('institution_transit.u_inst_transit');

      if (regionValue) {
        if (!regionsWithTransits[regionValue]) {
          regionsWithTransits[regionValue] = [];
        }
        if (transitValue && regionsWithTransits[regionValue].indexOf(transitValue) === -1) {
          regionsWithTransits[regionValue].push(transitValue);
        }
      }
    }

    // Serialize the data as JSON strings
    var regionsJSON = JSON.stringify(Object.keys(regionsWithTransits));
    var transitsByRegionJSON = JSON.stringify(regionsWithTransits);
  </g:evaluate>

  <!-- Embedding Server-Side Data Directly into JavaScript Variables -->
  <j:var var="regionsJSON" value="${regionsJSON}" />
  <j:var var="transitsByRegionJSON" value="${transitsByRegionJSON}" />

  <script>
    try {
      // Parse the JSON strings provided by the server-side variables
      const regions = JSON.parse("${regionsJSON}");
      const transitsByRegion = JSON.parse("${transitsByRegionJSON}");

      let dateDebut = '';
      let dateFin = '';
      let region = '';
      let transit = '';

      function changeQuery(e) {
        const id = e.target.id;
        const value = e.target.value;

        if (id === 'periode-debut') {
          dateDebut = value;
        } else if (id === 'periode-fin') {
          dateFin = value;
        } else if (id === 'region') {
          region = value;
          updateTransitOptions(region);
          transit = ''; // Reset transit when region changes
          document.getElementById('transit').value = '';
        } else if (id === 'transit') {
          transit = value;
        }

        const query = [];
        if (dateDebut && dateFin) {
          query.push(
            `date_supervisionBETWEENjavascript:gs.dateGenerate('${dateDebut}', 'start')@javascript:gs.dateGenerate('${dateFin}', 'end')`
          );
        }
        if (region && region !== 'Toute') {
          query.push(`institution_transit.u_territoire_affaires=${region}`);
        }
        if (transit && transit !== 'Toute') {
          query.push(`institution_transit.u_inst_transit=${transit}`);
        }

        const filter = handler.getFilterMessage('x_desgr_cspoh_consignation', query.join('^'));
        SNC.canvas.interactiveFilters.setDefaultValue({ id: ID_HANDLER, filters: [filter] }, true);
        handler.publishFilter('x_desgr_cspoh_consignation', filter);
      }

      // Function to update transit options based on selected region
      function updateTransitOptions(selectedRegion) {
        const transitDatalist = document.getElementById('transitList');
        transitDatalist.innerHTML = ''; // Clear existing options

        let options = [];
        if (selectedRegion && selectedRegion !== 'Toute') {
          options = transitsByRegion[selectedRegion] || [];
        } else {
          // If 'Toute' is selected, aggregate all transits
          const allTransitsSet = new Set();
          for (const transits of Object.values(transitsByRegion)) {
            transits.forEach(t => allTransitsSet.add(t));
          }
          options = Array.from(allTransitsSet);
        }

        // Populate transit options in datalist
        options.forEach(transitOption => {
          const optionElement = document.createElement('option');
          optionElement.value = transitOption;
          transitDatalist.appendChild(optionElement);
        });
      }

      // Initial setup
      document.addEventListener('DOMContentLoaded', () => {
        // Populate region options
        const regionSelect = document.getElementById('region');
        regionSelect.innerHTML = '<option value="Toute">Toute</option>';
        regions.forEach(regionOption => {
          const optionElement = document.createElement('option');
          optionElement.value = regionOption;
          optionElement.textContent = regionOption;
          regionSelect.appendChild(optionElement);
        });

        // Initialize transit options with all transits
        updateTransitOptions('Toute');

        // Event listeners
        document.getElementById('periode-debut').addEventListener('change', changeQuery);
        document.getElementById('periode-fin').addEventListener('change', changeQuery);
        document.getElementById('region').addEventListener('change', changeQuery);
        document.getElementById('transit').addEventListener('input', changeQuery); // Trigger on input for search
      });
    } catch (e) {
      console.error(e);
    }
  </script>

  <!-- HTML for filter UI -->
  <style>
    .filter-container {
      padding: 10px 0 0 10px;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
    }
    .filter-item {
      margin-right: 20px;
      min-width: 200px;
    }
    .datalist-wrapper {
      position: relative;
      width: 100%;
    }
    input[type="text"] {
      width: 100%;
      box-sizing: border-box;
      padding: 6px;
    }
    .select-wrapper {
      width: 100%;
    }
  </style>

  <div class="filter-container">
    <div class="filter-item">
      <label>Période de supervision</label><br />
      De <input type="date" id="periode-debut" name="periode-debut" style="width: 150px;" /> à
      <input type="date" id="periode-fin" name="periode-fin" style="width: 150px;" />
    </div>
    <div class="filter-item select-wrapper">
      <label for="region">Territoire d'affaire</label><br />
      <select name="region" id="region" style="width: 100%;">
        <!-- Options will be populated by JavaScript -->
      </select>
    </div>
    <div class="filter-item datalist-wrapper">
      <label for="transit">Transit</label><br />
      <input type="text" id="transit" list="transitList" placeholder="Search or select a transit" />
      <datalist id="transitList">
        <!-- Options will be populated by JavaScript -->
      </datalist>
    </div>
  </div>
</j:jelly>
servicenow
1个回答
0
投票

如果

DOMContentLoaded
事件或
DOMPurify
包含未触发或按预期运行,让我们系统地进行故障排除,以确保代码设置正确,并调查可能影响执行的任何环境因素,尤其是在 ServiceNow 或类似上下文中。

分步故障排除指南

  1. 验证脚本放置和顺序

    • 确保您的
      <script>
      标签位于结束
      </body>
      标签之前。这个位置至关重要,因为它确保 DOM 在 JavaScript 运行时已完全加载,这可能会影响
      DOMContentLoaded
    • 在 ServiceNow 环境中,Jelly 文件通常在多个位置包含脚本。确认冲突位置中没有其他脚本可能会阻止
      DOMContentLoaded
      的执行。
  2. 简化和测试事件监听器

    • 简化代码以隔离

      DOMContentLoaded
      功能并确保其按预期触发。

    • DOMContentLoaded
      中的代码替换为简单的日志语句:

      document.addEventListener("DOMContentLoaded", function() {
        console.log("DOMContentLoaded event fired!");
      });
      
    • 如果您没有看到此日志消息,则确认

      DOMContentLoaded
      确实未触发,或者脚本可能会提前终止。

  3. 使用

    window.onload
    作为后备

    • 如果

      DOMContentLoaded
      仍未触发,请尝试使用
      window.onload
      ,它会在加载所有资源(包括图像)后运行。虽然它晚于
      DOMContentLoaded
      ,但它可以帮助验证脚本是否正在运行。

      window.onload = function() {
        console.log("Window onload event fired!");
      };
      
  4. 在脚本早期检查 JavaScript 错误或控制台日志

    • 如果DOMContentLoaded之前的脚本中存在JavaScript错误

      任何地方
      ,可能会阻止后续代码执行。

    • console.log("Script is running")
      标签的最开头放置一个
      <script>
      以确认脚本正在执行:

      console.log("Script is running");
      
  5. 在独立 HTML 文件中进行测试

    • 将 HTML、CSS 和 JavaScript 代码复制到 ServiceNow 外部的独立 HTML 文件中。这将有助于确认问题是否特定于 ServiceNow 或 Jelly 处理。
    • 此测试可以澄清
      DOMContentLoaded
      在标准 Web 环境中是否正常触发,这表明 ServiceNow 对 Jelly 或脚本执行的处理是根本问题。
  6. 检查 CSP(内容安全策略)限制

    • 在 ServiceNow 等环境中,内容安全策略 (CSP) 限制可能会阻止某些脚本执行。
    • CSP 问题通常可以通过检查浏览器控制台是否有阻止脚本警告来识别。如果 CSP 阻止
      DOMContentLoaded
      DOMPurify
      执行,您可能需要调整实例上的设置(通常由管理员完成)。
  7. 确认 ServiceNow 特定行为

    • ServiceNow 有时会以意想不到的方式处理和解释 Jelly 代码。如果
      DOMContentLoaded
      在 ServiceNow 中无法可靠触发,则可能值得将关键代码移至
      window.onload
      函数中,因为这具有更高的执行可能性。
    • 此外,如果可能,请尝试将代码放置在 ServiceNow 小部件或 UI 操作中,因为它们可以更可预测地处理 JavaScript。

使用
window.onload
作为主要事件的替代解决方案

由于

DOMContentLoaded
在您的上下文中可能不可靠,因此这里有一个使用
window.onload
的替代结构,它可能会绕过您面临的问题:

window.onload = function() {
  console.log("Window onload event fired!");

  // Initialize the JavaScript logic here
  const regions = JSON.parse("${regionsJSON}");
  const transitsByRegion = JSON.parse("${transitsByRegionJSON}");

  let dateDebut = '';
  let dateFin = '';
  let region = '';
  let transit = '';

  function changeQuery(e) {
    const id = e.target.id;
    const value = e.target.value;

    if (id === 'periode-debut') {
      dateDebut = value;
    } else if (id === 'periode-fin') {
      dateFin = value;
    } else if (id === 'region') {
      region = value;
      updateTransitOptions(region);
      transit = ''; // Reset transit when region changes
      document.getElementById('transit').value = '';
    } else if (id === 'transit') {
      transit = value;
    }

    const query = [];
    if (dateDebut && dateFin) {
      query.push(
        `date_supervisionBETWEENjavascript:gs.dateGenerate('${dateDebut}', 'start')@javascript:gs.dateGenerate('${dateFin}', 'end')`
      );
    }
    if (region && region !== 'Toute') {
      query.push(`institution_transit.u_territoire_affaires=${region}`);
    }
    if (transit && transit !== 'Toute') {
      query.push(`institution_transit.u_inst_transit=${transit}`);
    }

    const filter = handler.getFilterMessage('x_desgr_cspoh_consignation', query.join('^'));
    SNC.canvas.interactiveFilters.setDefaultValue({ id: ID_HANDLER, filters: [filter] }, true);
    handler.publishFilter('x_desgr_cspoh_consignation', filter);
  }

  function updateTransitOptions(selectedRegion) {
    const transitDatalist = document.getElementById('transitList');
    transitDatalist.innerHTML = ''; // Clear existing options

    let options = [];
    if (selectedRegion && selectedRegion !== 'Toute') {
      options = transitsByRegion[selectedRegion] || [];
    } else {
      const allTransitsSet = new Set();
      for (const transits of Object.values(transitsByRegion)) {
        transits.forEach(t => allTransitsSet.add(t));
      }
      options = Array.from(allTransitsSet);
    }

    options.forEach(transitOption => {
      const optionElement = document.createElement('option');
      optionElement.value = transitOption;
      transitDatalist.appendChild(optionElement);
    });
  }

  document.getElementById('periode-debut').addEventListener('change', changeQuery);
  document.getElementById('periode-fin').addEventListener('change', changeQuery);
  document.getElementById('region').addEventListener('change', changeQuery);
  document.getElementById('transit').addEventListener('input', changeQuery);

  // Initialize with default options
  const regionSelect = document.getElementById('region');
  regionSelect.innerHTML = '<option value="Toute">Toute</option>';
  regions.forEach(regionOption => {
    const optionElement = document.createElement('option');
    optionElement.value = regionOption;
    optionElement.textContent = regionOption;
    regionSelect.appendChild(optionElement);
  });
  updateTransitOptions('Toute');
};

总结

  • 通过确认基本事件侦听器功能来测试
    DOMContentLoaded
  • 如果
    window.onload
    在您的环境中仍然不可靠,请使用
    DOMContentLoaded
     作为主要方法
  • 检查可能阻止脚本执行的 CSP 限制或 JavaScript 错误
  • 如果可能,通过将问题隔离在独立的 HTML 文件中来简化您的测试。
请告诉我这个修改后的方法是否有助于解决问题!

	

© www.soinside.com 2019 - 2024. All rights reserved.