【问题标题】:JavaScript element creation differences resulting in jquery.ui.autocomplete element undefined errorJavaScript 元素创建差异导致 jquery.ui.autocomplete 元素未定义错误
【发布时间】:2021-02-17 00:14:01
【问题描述】:

我正在尝试动态创建一个输入元素并在将其添加到 DOM 之前为其附加一个 jquery.ui 自动完成控件。我通常创建动态元素的方式似乎是创建 Cannot read property 'element' of undefined 错误,我不知道为什么。

$(document).ready(() => {
  const content = document.getElementById('content');
  const input = document.createElement('input');
  const inputFromTemplate = createFromTemplate(`<input></input>`);

  console.log(input);
  console.log(inputFromTemplate);

  $(input).autocomplete({
    source: ['one', 'two', 'three']
  });

  $(inputFromTemplate).autocomplete({
    source: ['one', 'two', 'three']
  });

  content.append(input);
  content.append(inputFromTemplate);
});

function createFromTemplate(template) {
  const htmlTemplate = document.createElement('template');
  htmlTemplate.innerHTML = template;

  return htmlTemplate.content.firstElementChild.cloneNode(true);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

<div id="content"></div>

在上面的代码示例中,当我使用document.createElement('input') 方法创建input 元素时,自动完成控件会毫无问题地附加。

当我使用自己的createFromTemplate 方法创建input 元素时,会引发上述错误。

注销到控制台时,这两个元素看起来相同,但似乎在console.log 触发之前附加了自动完成控件(您可以通过按原样运行,然后注释掉.autocomplete 代码行来查看这一点并查看登录到控制台的input 代码的差异)。

我认为我对 jquery 如何将控件附加到元素的理解是有缺陷的,因为我不明白为什么两者之间会有差异。

(注意:是的,这是一个简单的复制,我想使用像 createFromTemplate 这样的函数的原因是因为实际输入是更大的 html 模板的一部分,所以将 document.createElement 方法链接在一起是行不通的发生)。

编辑: 只是为了解决当元素首先附加到 DOM 时它工作的问题,不幸的是,在我正在处理的项目的情况下,这不是一个选项。 Something akin to this issue was reported and fixed in a previous version of jquery.ui back in 2012,所以很可能是库的错误

【问题讨论】:

  • 如果你重新安排你的逻辑,以便定义自动完成 after 将元素附加到 DOM 它工作正常:jsfiddle.net/9bsyemzd - 我现在无法不过,请解释为什么在创建元素的方法之间会出现这种差异。
  • @RoryMcCrossan 是的,但就我而言,这不是一个选择。这是一项涉及重新编写非常大的块代码的工作,我不明白为什么这是一个问题。 2013 年(我认为)在 jquery 错误板上列出了一个错误,列出了与未附加元素相同类型的问题,但已解决。这是我创建似乎导致问题的元素的方式所特有的。
  • 这是你想要的@DarkHippo 吗? jsfiddle.net/6k2do9vb
  • @CarstenLøvboAndersen 不错的尝试,但不行,因为在附加自动完成控件之前我无法将生成的元素附加到 DOM(见上面的评论)
  • 当我运行你的代码时,我确实看到了错误:jQuery.Deferred exception: this.menu is undefined 小部件通常需要 DOM 中的元素。将检查是否可以预先创建该元素。

标签: javascript jquery jquery-ui jquery-ui-autocomplete


【解决方案1】:

我建议快速阅读:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template

DocumentFragment 不是各种事件的有效目标,因此通常最好克隆或引用其中的元素。

HTML

<div id="container"></div>

<template id="template">
  <div>Click me</div>
</template>

JavaScript

const container = document.getElementById("container");
const template = document.getElementById("template");

function clickHandler(event) {
  alert("Clicked a div");
}

const firstClone = template.content.cloneNode(true);
firstClone.addEventListener("click", clickHandler);
container.appendChild(firstClone);

const secondClone = template.content.firstElementChild.cloneNode(true);
secondClone.addEventListener("click", clickHandler);
container.appendChild(secondClone);

结果

firstClone 是一个 DocumentFragment 实例,因此当它按预期附加到容器中时,单击它不会触发单击事件secondClone 是一个 HTMLDivElement 实例,点击它可以正常工作。

尝试初始化自动完成时,事件绑定将失败。这可能就是为什么您会看到各种不同的错误以及其他元素正常工作而 template 中的元素不工作的原因。

所以让我们按照他们的建议、您尝试的方式以及其他一些可能的解决方案进行测试。

$(document).ready(() => {
  const content = document.getElementById('content');
  var inA = document.createElement('input');
  var inB = createTempObj("<input>");
  var inC = createFromTemplate("<input>");
  var inD = document.getElementById("template").content.firstElementChild.cloneNode(true);
  var m1 = menuFromTemp();

  console.log("Init Autocomplete 1");
  console.log("HTML", inA);

  initAutoComplete(inA);

  console.log("Init Autocomplete 2");
  console.log("HTML", inB);

  initAutoComplete(inB);

  console.log("Init Autocomplete 3");
  console.log("HTML", typeof inC, inC);
  
  initAutoComplete(inC);

  console.log("Init Autocomplete 4");
  console.log("HTML", inD);

  //initAutoComplete(inD);

  console.log("Init Menu 1");
  console.log("HTML", m1);
  var menu = $(m1).menu({
    role: null
  }).hide().menu("instance");

  content.append(inA);
  content.append(inB);
  content.append(inC);
  content.append(inD);
  content.append(m1);
});

function createFromTemplate(item) {
  var base = document.createElement('template');
  var real = document.createElement('input');
  console.log("item:", typeof item, item);
  console.log("real:", typeof real, real);
  base.innerHTML = item;
  var clone = base.content.children[0].cloneNode(true);
  console.log("clone:", typeof clone, clone);
  return clone;
};

function createTempObj(el) {
  var base = $("<template>");
  $(el).appendTo(base);
  return base.children(0).clone("true")[0];
}

function initAutoComplete(el) {
  $(el).autocomplete({
    source: ['one', 'two', 'three']
  });
}

function menuFromTemp() {
  var base = document.createElement('template');
  base.innerHTML = "<ul><li><div>Books</div></li><li><div>Clothing</div></li> <li><div>Electronics</div></li></ul>";
  var menu = base.content.children[0].cloneNode("true");
  return menu;
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<div id="content"></div>
<hr />
<template id="template">
  <input>
</template>

输入 C 和 D 初始化失败。我把它们注释掉了,但你可以自己测试一下。根据文档,它们应该按预期工作。如果您想进一步深入研究,我敢打赌那里有答案。如果您想完成工作,请不要在这部分使用 template

不清楚您为什么要尝试动态创建template,为什么不根据JavaScript 的需要动态创建input 元素。

【讨论】:

  • “根据文档,它们应该按预期工作”这是首先提出这个问题的原因,我不明白为什么根据我阅读的文档它们不能正常工作。像这样动态创建它们的原因是它是一个更大项目的一部分,这个问题中的代码是一个易于复制的 sn-p。与此同时,我实际上已经找到了解决这个问题的方法:)
  • @DarkHippo 好的,我会尝试进一步深入研究。该函数在所有情况下都返回一个 HTML 元素,因此不清楚为什么会出现初始化错误。我希望这是在只读片段上。
  • @DarkHippo 当我深入研究它时,这是失败的行:this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );code.jquery.com/ui/1.12.1/jquery-ui.js 第 5784 行)。错误提示this.menu的创建和初始化在上一个语句块中失败。
  • 查看 this.menu = $( "&lt;ul&gt;" ).appendTo( this._appendTo() ).menu( { role: null } ).hide().menu( "instance" ); 第 5774 行。在这种情况下,它是默认的 null 元素。它应该触发第 5939 行:if ( !element.length ) { element = this.document[ 0 ].body; } 所以下一个测试是在模板克隆元素上测试菜单初始化,看看我们是否得到类似的结果。
  • @DarkHippo 能够毫无问题地从模板克隆的元素构建菜单。需要更多调查。
【解决方案2】:

如果您创建 div 元素而不是 template 元素,它会起作用。

$(document).ready(() => {
  const content = document.getElementById('content');
  const input = document.createElement('input');
  const inputFromTemplate = createFromTemplate(`<input></input>`);

  console.log(input);
  console.log(inputFromTemplate);

  $(input).autocomplete({
    source: ['one', 'two', 'three']
  });

  $(inputFromTemplate).autocomplete({
    source: ['one', 'two', 'three']
  });

  content.append(input);
  content.append(inputFromTemplate);
});

function createFromTemplate(template) {
  const htmlTemplate = document.createElement('div');
  htmlTemplate.innerHTML = template;

  return htmlTemplate.firstElementChild.cloneNode(true);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

<div id="content"></div>

【讨论】:

  • 对于这个特定的实例,它可以工作,但是 createFromTemplate 函数也用于动态创建表行,这些行不是 div 的有效子元素,因此抛出错误,因此使用 '模板'。
  • @Dark Hippo。好吧,那我就删掉它,因为我也想不出真正的原因。使用DOMParser 或 Fragments 怎么样?
  • 我会留下它,它可能会帮助其他没有表格行要求的人:)
  • 我实际上只是在寻找从字符串生成 HTML 元素的其他选项。由于需要进行大量的回归测试,我有点不愿意更改函数,但如果这是唯一的方法......
猜你喜欢
  • 1970-01-01
  • 2017-11-27
  • 2018-05-01
  • 1970-01-01
  • 2013-01-09
  • 2020-07-14
  • 1970-01-01
  • 1970-01-01
  • 2021-11-24
相关资源
最近更新 更多