我正在 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>
如果
DOMContentLoaded
事件或 DOMPurify
包含未触发或按预期运行,让我们系统地进行故障排除,以确保代码设置正确,并调查可能影响执行的任何环境因素,尤其是在 ServiceNow 或类似上下文中。
验证脚本放置和顺序
<script>
标签位于结束 </body>
标签之前。这个位置至关重要,因为它确保 DOM 在 JavaScript 运行时已完全加载,这可能会影响 DOMContentLoaded
。DOMContentLoaded
的执行。简化和测试事件监听器
简化代码以隔离
DOMContentLoaded
功能并确保其按预期触发。
将
DOMContentLoaded
中的代码替换为简单的日志语句:
document.addEventListener("DOMContentLoaded", function() {
console.log("DOMContentLoaded event fired!");
});
如果您没有看到此日志消息,则确认
DOMContentLoaded
确实未触发,或者脚本可能会提前终止。
使用
window.onload
作为后备
如果
DOMContentLoaded
仍未触发,请尝试使用 window.onload
,它会在加载所有资源(包括图像)后运行。虽然它晚于DOMContentLoaded
,但它可以帮助验证脚本是否正在运行。
window.onload = function() {
console.log("Window onload event fired!");
};
在脚本早期检查 JavaScript 错误或控制台日志
如果DOMContentLoaded
之前的脚本中存在JavaScript错误
任何地方,可能会阻止后续代码执行。
在
console.log("Script is running")
标签的最开头放置一个<script>
以确认脚本正在执行:
console.log("Script is running");
在独立 HTML 文件中进行测试
DOMContentLoaded
在标准 Web 环境中是否正常触发,这表明 ServiceNow 对 Jelly 或脚本执行的处理是根本问题。检查 CSP(内容安全策略)限制
DOMContentLoaded
或 DOMPurify
执行,您可能需要调整实例上的设置(通常由管理员完成)。确认 ServiceNow 特定行为
DOMContentLoaded
在 ServiceNow 中无法可靠触发,则可能值得将关键代码移至 window.onload
函数中,因为这具有更高的执行可能性。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
作为主要方法。