JavaScript元素创建差异导致jquery.ui.autocomplete元素未定义错误

问题描述

我正在尝试动态创建输入元素,并将jquery.ui自动完成控件附加到该元素,然后再将其添加到DOM。我通常创建动态元素的方式似乎正在创建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','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>

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

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

两个元素在登录到控制台后看起来都一样,但是似乎在console.log触发之前附加了自动完成控件(您可以按原样运行,然后注释掉{{1} }行代码,并看到.autocomplete代码中的差异已注销到控制台。

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

(注意:是的,这是一个简单的复制,我想使用类似input之类的函数的原因是因为实际输入是更大的html模板的一部分,因此将createFromTemplate方法链接在一起不会发生)。

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

解决方法

我建议您快速阅读: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实例,因此,尽管按预期将其添加到了容器内,但单击它不会触发click事件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元素。

,

如果您创建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','three']
  });

  $(inputFromTemplate).autocomplete({
    source: ['one','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>