简体   繁体   English

如何将DOM选择器作为数据属性传递

[英]How to pass DOM selector as data-attribute

I'm writing some shared modal functionality (the user clicks a button and my modal method grabs some associated DOM element and clones it to the modal) and unfortunately there is some inconsistency in the HTML in the relationship between the trigger and content, for instance: 我正在编写一些共享的模态功能(用户单击一个按钮,我的模态方法获取一些关联的DOM元素并将其克隆到模态),不幸的是,例如,触发器和内容之间的关系在HTML中存在一些不一致之处:

// Sibling

<button class="modal__button">Trigger</button>
<div class="modal__content">Content</div>


// Separated by other elements

<button class="modal__button">Trigger</button>
<div>Other Elements</div>
<div>Other Elements</div>
<div class="modal__content">Content</div>


// Trigger's parent is content's sibling

<div>
    <button class="modal__button">Trigger</button>
</div>
<div class="modal__content">Content</div>


// Argh!

<div>
    <div>
        <button class="modal__button">Trigger</button>
    </div>
</div>
<div>Other Elements</div>
<div>Other Elements</div>
<div>
    <div>
        <div class="modal__content">Content</div>
    </div>
</div>

Unfortunately the obvious answer, using IDs to create a relationship, is not possible for my use case (as they are required to be unique and these components can appear multiple times within a single page, and each component has no awareness from a server side perspective of whether it has already been added to the page so there is no way to ensure this uniqueness). 不幸的是,对于我的用例,使用ID来建立关系的明显答案是不可能的(因为它们必须是唯一的,并且这些组件可以在单个页面中多次出现,并且从服务器端的角度来看每个组件都不知道)有关是否已将其添加到页面中的信息,因此无法确保这种唯一性)。

The only way around this I can see is if I can pass my modal method a DOM selector (relative to the trigger) as a data-attribute and then have my modal method execute this selector. 我能看到的唯一方法是,是否可以将模态方法作为数据属性传递给DOM选择器(相对于触发器),然后让模态方法执行此选择器。 I would also need to scope this so that it's relative to the button trigger. 我还需要确定范围,使其相对于按钮触发器。 However, I'm not sure if any of this is possible in JavaScript? 但是,我不确定在JavaScript中是否可以实现以上任何一项? Basically I want to be able to write my buttons something like this: 基本上,我希望能够编写如下所示的按钮:

<button data-modal-content="this.nextElementSibling">Trigger</button>
<button data-modal-content="this.parentNode.querySelectorAll('modal__content')">Trigger</button>
<button data-modal-content="this.parentNode.nextElementSibling">Trigger</button>

And then have my modal method find the selector. 然后让我的模态方法找到选择器。 I know I can potentially use eval() like so: 我知道我可以像这样使用eval()

 document.querySelectorAll('[data-modal-content]').forEach(el => { el.addEventListener('click', button => { const selector = button.target.getAttribute('data-modal-content').replace('this','button.target'); console.log(eval(selector)); }); }); 
 <button data-modal-content="this.nextElementSibling">Trigger</button> <div>Content</div> 

...but I'm also aware of the associated security issues with that, meaning I'd likely have to sanitise the string (I guess by checking each part of it was only doing selector y stuff before it goes anywhere near eval() ). ...但是我也知道与此相关的安全性问题,这意味着我可能必须对字符串进行清理(我想通过检查字符串的每个部分是否只在使用选择器 y进行处理之后,才能将其移到eval()附近)。

Is this the best approach, is there potentially a better way to tackle this that I've not considered? 这是最好的方法吗,是否有更好的方法可以解决我从未考虑过的问题? A way to do the selector stuff that doesn't rely on the risky eval() would be ideal. 一种不依赖风险eval()的选择器内容的方法将是理想的。

I would rather go for a simpler solution, and just select all the modals, then find the first one that comes after the clicked button. 我宁愿寻求一个更简单的解决方案,只需选择所有模态,然后找到单击按钮之后的第一个模态。

 document.querySelectorAll("button.modal__button").forEach(function(b) { b.addEventListener("click", clickHandler); }); function clickHandler() { const contents = document.querySelectorAll("div.modal__content"); const c = Array.from(contents).find(c => this.compareDocumentPosition(c) == Node.DOCUMENT_POSITION_FOLLOWING ); if (c) c.classList.add("found"); else console.log("Unable to find content from:", this); } 
 .found { background: red; color: white; } .modal__content { margin: 10px; padding: 10px; border: 2px solid orange; } 
 // Sibling <button class="modal__button">Trigger</button> <div class="modal__content">Content</div> // Separated by other elements <button class="modal__button">Trigger</button> <div>Other Elements</div> <div>Other Elements</div> <div class="modal__content">Content</div> // Trigger's parent is content's sibling <div> <button class="modal__button">Trigger</button> </div> <div class="modal__content">Content</div> // Argh! <div> <div> <button class="modal__button">Trigger</button> </div> </div> <div>Other Elements</div> <div>Other Elements</div> <div> <div> <div class="modal__content">Content</div> </div> </div> 


Another solution is to give the button a temporary ID when clicked, then include it in the DOM selection of the content elements. 另一种解决方案是在单击按钮时为按钮提供一个临时ID,然后将其包含在内容元素的DOM选择中。 That way, you only need to find the button in the collection, and you'll know that the content is the next (or previous) one, because the results are always returned in "document order". 这样,您只需要在集合中找到按钮,就知道内容是下一个(或上一个)按钮,因为结果始终按“文档顺序”返回。

 document.querySelectorAll("button.modal__button").forEach(function(b) { b.addEventListener("click", clickHandler); }); function clickHandler() { const temp = this.id; this.id = generateUniqueId(); const els = document.querySelectorAll(`#${this.id}, div.modal__content`); const idx = Array.from(els).findIndex(el => this.id == el.id); this.id = temp; if (idx !== -1) { const el = this.dataset.position === "before" ? els[idx - 1] : els[idx + 1]; el.classList.add("found"); } else console.log("Unable to find content from:", this); } function generateUniqueId() { do { const id = "__temp_id__" + (Math.floor(Math.random()) * Number.MAX_SAFE_INTEGER); if (!document.querySelector(id)) return id; } while (true); } 
 .found { background: red; color: white; } .modal__content { margin: 10px; padding: 10px; border: 2px solid orange; } 
 // Sibling <button class="modal__button">Trigger</button> <div class="modal__content">Content</div> // Separated by other elements <button class="modal__button">Trigger</button> <div>Other Elements</div> <div>Other Elements</div> <div class="modal__content">Content</div> // Content comes before trigger's parent <div class="modal__content">Content</div> <div> <button class="modal__button" data-position="before">Trigger</button> </div> // Trigger's parent is content's sibling <div> <button class="modal__button">Trigger</button> </div> <div class="modal__content">Content</div> // Argh! <div> <div> <button class="modal__button">Trigger</button> </div> </div> <div>Other Elements</div> <div>Other Elements</div> <div> <div> <div class="modal__content">Content</div> </div> </div> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM