直接在html中使用ES6模块中定义的参数化函数



在HTML脚本中嵌入的ES6模块中定义的函数对该脚本不可用。因此,如果您有这样的语句:

<button onclick="doSomething();">Do something</button>

中的doSomething()函数存在于HTML脚本中嵌入的ES6模块中,你会得到一个"doSomething() is undefined"运行脚本时出错。

直接在html中使用ES6模块中定义的函数对当前问题提出了一个很好的解决方案,建议您使用"bind">

<button id="dosomethingbutton">Do something</button>

和使用模块本身创建一个链接,这样:

document.getElementById('dosomethingbutton').addEventListener('click', doSomething);

这工作得很好,但如果你原来的按钮有点复杂,是参数化的呢?例如:

<button onclick="doSomething('withThisString');">Do Something with String</button>

最能说明"binding"所能提供的似乎仅限于与事件相关的情况-我找不到将其与数据联系起来的方法。我完全卡住了,试图找到一个解决这个问题的方法,如果能得到帮助,我将非常感激。

我想补充一点,虽然这个问题看起来有点晦涩,但我认为任何迁移到Firebase 9的人都会对它感兴趣。在其他更改中,迁移要求您将javascript代码移动到ES6模块中(其中名称空间不能直接用于HTML DOM),因此最简单的HTML可能会立即遇到这些问题。欢迎多多指教。

这很好,但是如果您的原始按钮更复杂一些并且是参数化的呢?

有两个解决方案:

  1. data-*属性:

    <button id="the-button" data-string="withThisString">Do Something with String</button>
    
    document.getElementById("the-button").addEventListener("click", function() {
    doSomething(this.getAttribute("data-string"));
    });
    

    (后面会详细介绍)

  2. 绑定事件时绑定字符串

    <button id="the-button">Do Something with String</button>
    
    document.getElementById("the-button").addEventListener("click", () => {
    doSomething("withThisString");
    });
    

上面有很多变化,如果你使用doSomething和不同字符串的多个按钮,你可以用一个类和一个循环来做#1,而不是用id,但这是一般的想法。


关于data-*属性的事情:如果您愿意,您可以通过data-*属性和一个连接事物的单一函数使这个过程完全由html驱动。例如,假设您有这些按钮:

<button data-click="doThisx@module1">Do This</button>
<button data-click="doThat@module2">Do That</button>
<button data-click="doTheOther@module3">Do The Other</button>

你可以用一个可重用的函数把它们连接起来:

class EventSetupError extends Error {
constructor(element, msg) {
if (typeof element === "string") {
[element, msg] = [msg, element];
}
super(msg);
this.element = element;
}
}
export async function setupModuleEventHandlers(eventName) {
try {
const attrName = `data-${eventName}`;
const elements = [...document.querySelectorAll(`[${attrName}]`)];
await Promise.all(elements.map(async element => {
const attrValue = element.getAttribute(`data-${eventName}`);
const [fname, modname] = attrValue ? attrValue.split("@", 2) : [];
if (!fname || !modname) {
throw new EventSetupError(
element,
`Invalid '${attrName}' attribute "${attrValue}"`
);
}
// It's fine if we do import() more than once for the same module,
// the module loader will return the same module
const module = await import(`./${modname}.js`);
const fn = module[fname];
if (typeof fn !== "function") {
throw new EventSetupError(
element,
`Invalid '${attrName}': no '${fname}' on module '${modname}' or it isn't a function`
);
}
element.addEventListener(eventName, fn);
}));
} catch (error) {
console.error(error.message, error.element);
}
}

使用它来查找和连接点击处理程序:

import { setupModuleEventHandlers } from "./data-attr-event-setup.js";
setupModuleEventHandlers("click")
.catch(error => {
console.error(error.message, error.element);
});

这是一次性管道,但在HTML中为您提供相同的基于属性的体验(事件处理程序仍然可以从另一个data-*属性获取参数信息,或者您可以将其放入设置函数中)。(该示例依赖于动态import,但所有主流浏览器的最新版本都支持它,并且在不同程度上支持捆绑器。

有几十种方法来旋转它,我不是在推广它,只是举一个例子,如果你想的话,你可以很容易地这样做。

但实际上,这是React, Vue, Ember, Angular, Lit等库发挥作用的地方。

虽然t.j. Crowder已经回答了这个问题,但我想我可能会补充一些难以作为评论的观点。

当我进一步研究Firebase V9转换时,我开始发现"模块名称空间"的一些后果问题非常深刻。我在最初的问题中引用的例子很容易处理,如上所述,但我发现我还需要弄清楚如何处理"动态"。响应来自数据库的可变情况的HTML。在这种情况下,我的javascript最初会创建一个包含HTML块的字符串,如:

realDiv = `
<div>
<button onclick = "function fn1 (param1a, param1b,...);">button1</button>
<button onclick = "function fn2 (param2a, param2b,...);">button2</button>
etc
</div>
`

然后把它扔进"real"在应用程序的HTML框架中使用

定义的realdivdocument.getElementById("realdiv") = realDiv;

现在,由于上面描述的原因,一旦javascript在模块中,这种安排就不再有效了。

我学会采用的模式(再次感谢t.j. Crowder)是这样的:

realDiv = `
<div>
<button id = "button1" data-param1a="param1a" data-param1b="param1b";">button1</button>
<button id = "button2" data-param2a="param2a" data-param2b="param2b";">button2</button>
etc
</div>
`

然后我将生成的代码像以前一样用 扔到我的HTML框架中document.getElementById("realdiv") = readlDiv;

,现在您已经将代码嵌入到DOM中(并假设我已经保留了您生成的按钮数量),我将使用最后一点javascript为它们创建绑定,如:

for (let i = 0; i>buttonCount; i++) {
document.getElementById('button' + i).onclick = function() { fn1 (this.getAttribute('data-param1a'), this.getAttribute('data-param1b') };
etc
}

我发现,当我需要让onclick启动多个不同的功能时,用这种模式创建onclick在保持清晰度方面特别有帮助。

最新更新