脚本无法在页面加载JS上获取元素

时间:2018-09-02 15:44:57

标签: javascript

我对此代码有疑问:

var coll = document.getElementsByClassName("collapsible");
var i;

for (i = 0; i < coll.length; i++) {
    coll[i].addEventListener("click", function() {
        this.classList.toggle("active");
        var content = this.nextElementSibling;
        if (content.style.display === "block") {
            content.style.display = "none";
        } else {
            content.style.display = "block";
        }
    });
}
/

当页面完全加载时,页面代码中尚不存在类名(“可折叠”),仅在用户请求时才从远程源加载它,因此,长话短说-它不起作用。

懒惰的解决方案是在具有自定义类名的每个页面中包含此代码,但是我不希望这样

如果可能的话,我想坚持使用此JS代码,而不是jQuery

有人可以帮助我修改此代码以在这种情况下工作吗?

用于远程内容的加载程序(更新1):

$.get('t.html', {}, function(data) {
      var $response = $('<div />').html(data);
      var $div1 = $response.find('#div1');
      $('#div-for-remote-content').append($div1);
    },'html');

//更新2

谢谢大家的帮助,我确切地知道我需要解决这个问题,但是我需要您的帮助:)

我需要一个脚本来检查页面上的所有.get()请求是否已成功完成,并且所有内容都已加载到主文档中,如果是,则将脚本文件加载到主文档中。

我在这里进行跟进,这个文件中的脚本会自动在主文档上工作吗?

5 个答案:

答案 0 :(得分:2)

如果您不知道何时以及如何将“可折叠”类添加到DOM中,则可能需要考虑使用MutationObserver接口,该接口可准确监视对接口进行的更改。 DOM树。

例如:

// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
    if (document.getElementsByClassName('collapsible').length > 0) {
        // YOUR CODE HERE !!!
        this.disconnect();
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Select the node that will be observed for mutations
// this is targeting the whole html tree, you can narrow it if you want
var targetNode = document.documentElement;

// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true, subtree: true };

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

};

通过这种方式,您的代码实际上将在执行之前将等待“可折叠”类添加到DOM中。

顺便说一句,我不会生成依赖于他人代码的代码:如果您无法控制“可折叠”类,那么您正在为失败做准备...

答案 1 :(得分:0)

有一种解决方案可以完成任务。 提供要加载的所有资源均以HTML格式返回的方法

function load_content(p_rsrc)
  {
var l_request = new XMLHttpRequest();

  l_request.addEventListener('readystatechanged', function(p_event) {
    if(this.readystate == 4)
// We wind up here once AJAX reports the request to be completed (either because of a timeout or because of a reaction from the server)
      if(this.status == 200)
        {
let l_col;
let l_target = document.getElementById('div-for-remote-content');
let l_this;

        if(l_target == null)
          {
          console.error('FATAL: Could not find container for remote content!');
          throw "internal error";
          }

// Assign the received contents to the container
// This clears out any previous content, including any event handlers, before adding the new content.
        l_target.innerHTML = l_request.responseText;
// Once we are here we can attach event handlers to any element that brings along the "collapsible" class...
// The following statement digs up all elements of the collapsible class that are present in their container.
        l_col = document.querySelectorAll('#div-for-remote-content .collapsible');

// Now dig through the entire list of items...
        for(l_this of l_col)
          {
          l_this.addEventListener("click", function(p_event) {
            p_event.target.classList.toggle("active");
            if (p_event.target.style.display === "block")
              p_event.target.style.display = "none";
            else
              p_event.target.style.display = "block";
            }, false);
          }
        }
// You can do some additional status handling here, like dealing with any errors
    }, false);
  l_request.open('GET', p_rsrc, true);
  l_request.send();
  }

如您所见,当且仅当AJAX请求成功返回(即状态码200)时,事件侦听器才附加在已加载的新内容的范围之外。任何其他状态代码都不允许脚本修改您容器的内容,并且我还添加了一些基本的健全性检查。

请注意,将内容(必须为HTML才能正常工作)分配给.innerHTML会清除容器的DOM子树,以便您可以为其分配新的内容而不必担心任何事件处理程序或过时的DOM节点。这避免了事件处理程序的重复调用,并且还允许将此函数作为事件处理程序附加到按钮(或从另一个脚本块进行调用)。请注意,您必须将所需资源的路径传递给此函数。

编辑:

更正了我的代码段中的错误

编辑2:

当您将此代码导入到文档中时,需要确保每当有人将额外的内容加载到文档中时,该代码就会被调用。您可以通过将包装函数附加到实现此目的所需的任何机制上来做到这一点(即触发事件处理程序的按钮,该事件处理程序又调用该函数并提供要加载的资源的URL)。

一旦调用load_content(),就会触发以下事件顺序:

  • 创建XMLHttpRequest(需要对所需资源执行AJAX请求)。
  • 在XMLHttpRequest对象上附加一个事件处理程序,以侦听就绪状态的转换(以便我们确定何时可以使用响应)。
  • 打开资源进行检索。请注意,第三个参数设置为 true ,以使其成为异步请求。
  • 发送请求。 XMLHttpRequest立即返回,但是由于我们正在监听readystatechange事件,因此一旦请求被处理,我们就会检测到。同时,其他事情也可能得到照顾。
  • 最终,该请求得到了答复,readystatechange事件被触发,并且我们的请求报告该请求已完成(this.readyState == 4)。这是我们可以开始处理资源的信号。
  • 检查请求是否成功(即this.status == 200)。如果不是,则说明出现了问题,应保持一切正常。
  • 否则,请查看该容器是否存在。如果不是,请登录到控制台并引发异常(防范未知元素)。
  • 将响应文本分配到我们容器的.innerHTML。废弃旧内容,并在所述容器中构造一个新的DOM子树。
  • 获取属于collapsible类并驻留在我们容器中的所有元素的列表。
  • 对于因此找到的每个元素重复:附加一个事件处理程序,该事件处理程序侦听click事件,并在调用该事件处理程序时切换有问题的元素的可见性。

从远程源加载的任何资源都根本不参与此过程,因此您不必冒险多次运行此功能,并且每次将新内容加载到容器中时,旧内容都会被有效覆盖。 / p>

答案 2 :(得分:0)

解决方案1:将侦听器添加到在获取的html中找到的“ .collapsible”中,然后将其添加到文档中:

imports

function addCollapsible(container /* $div1 in your code */ ) {
  [...container.querySelectorAll(".collapsible")].forEach(el => {
    el.addEventListener("click", (event) => {
      const content = el.nextElementSibling;
      el.textContent = el.textContent.trim() === "collapse" ? "uncolapse" : "collapse";
      toggleDisplay(content);
    })
  })
}
[...document.querySelectorAll(`[data-js="fetch-html"]`)].forEach(el => {
  el.addEventListener("click", async function(event) {
    const target = event.currentTarget;
    const url = target.getAttribute("data-url");
    const html = await fakeFetch(url);
    target.textContent = target.textContent.slice(target.textContent.indexOf("content"));
    const container = document.createElement("div");
    container.innerHTML = html;
    addCollapsible(container);
    target.insertAdjacentElement('afterend', container)
  }, {
    once: true
  });
});

function addCollapsible(container) {
  [...container.querySelectorAll(".collapsible")].forEach(el => {
    el.addEventListener("click", (event) => {
      const content = el.nextElementSibling;
      el.textContent = el.textContent.trim() === "collapse" ? "uncolapse" : "collapse";
      toggleDisplay(content);
    })
  })
}

function toggleDisplay(el) {
  if (!el.style.display || el.style.display === "block") {
    el.style.display = "none";
  } else {
    el.style.display = "block";
  }
}

async function fakeFetch(url) {
  const urlMap = {
    "content1.html": content1Html,
    "content2.html": content2Html
  }

  await wait(200)
  return urlMap[url];
}

async function wait(waitTime) {
  return new Promise((resolve) => {
    setTimeout(resolve, waitTime)
  })
}

const content1Html = `
<div class="collapsible">
  collapse
</div>
<div>
  visible 1
</div>
<div class="collapsible">
  collapse
</div>
<div>
  visible 2
</div>
`;

const content2Html = `
<div class="collapsible">
  collapse
</div>
<div>
  visible 3
</div>
<div class="collapsible">
  collapse
</div>
<div>
  visible 4
</div>
`;
body {
  font-family: sans-serif;
}

.collapsible {
  background-color: mediumseagreen;
}

解决方案2:使用事件委托:

<h2 data-js="fetch-html" data-url="content1.html">fetch content 1</h2>
<h2 data-js="fetch-html" data-url="content2.html">fetch content 2</h2>

/* use on document.body or even better the closets common ancestor: '#div-for-remote-content' */
document.body.addEventListener("click", (event) => {
  const target = event.target;
  const el = target.classList.contains("collapsible") ? target : target.closest(".collapsible");
  if (el) {
    const content = el.nextElementSibling;
      el.textContent = el.textContent.trim() === "collapse" ? "uncolapse" : "collapse";
      toggleDisplay(content);
  }
})
[...document.querySelectorAll(`[data-js="fetch-html"]`)].forEach(el => {
  el.addEventListener("click", async function(event) {
    const target = event.currentTarget;
    const url = target.getAttribute("data-url");
    const html = await fakeFetch(url);
    target.textContent = target.textContent.slice(target.textContent.indexOf("content"));
    const container = document.createElement("div");
    container.innerHTML = html;
    target.insertAdjacentElement('afterend', container)
  }, {
    once: true
  });
});

document.body.addEventListener("click", (event) => {
	const target = event.target;
  const el = target.classList.contains("collapsible") ? target : target.closest(".collapsible");
  if (el) {
  	const content = el.nextElementSibling;
      el.textContent = el.textContent.trim() === "collapse" ? "uncolapse" : "collapse";
      toggleDisplay(content);
  }
})

function toggleDisplay(el) {
  if (!el.style.display || el.style.display === "block") {
    el.style.display = "none";
  } else {
    el.style.display = "block";
  }
}

async function fakeFetch(url) {
  const urlMap = {
    "content1.html": content1Html,
    "content2.html": content2Html
  }

  await wait(200)
  return urlMap[url];
}

async function wait(waitTime) {
  return new Promise((resolve) => {
    setTimeout(resolve, waitTime)
  })
}

const content1Html = `
<div class="collapsible">
  collapse
</div>
<div>
  visible 1
</div>
<div class="collapsible">
  collapse
</div>
<div>
  visible 2
</div>
`;

const content2Html = `
<div class="collapsible">
  collapse
</div>
<div>
  visible 3
</div>
<div class="collapsible">
  collapse
</div>
<div>
  visible 4
</div>
`;
body {
  font-family: sans-serif;
}

.collapsible {
  background-color: mediumseagreen;
}

专业提示:

要检查元素有多少个侦听器,可以在chrome控制台中的给定元素上运行<h2 data-js="fetch-html" data-url="content1.html">fetch content 1</h2> <h2 data-js="fetch-html" data-url="content2.html">fetch content 2</h2>

getEventListeners

答案 3 :(得分:0)

此函数将继续在目标元素上寻找特定的类:

function checkElement(selector, cb) {
    var raf;
    var found = false;
    (function check() {
        const el = document.querySelector(selector);
        if (el) {
            found = true;
            cancelAnimationFrame(raf);
            // Do something here
            console.log('Found it');
            // Include your logic in the callBack
            cb();
        } else {
            raf = requestAnimationFrame(check);
        }
    })();
    return found;
}

如何使用它:

checkElement('collapsibile', elementReady);

function elementReady() {
    var coll = document.getElementsByClassName("collapsible");
    var i;

    for (i = 0; i < coll.length; i++) {
        coll[i].addEventListener("click", function() {
            this.classList.toggle("active");
            var content = this.nextElementSibling;
            if (content.style.display === "block") {
                content.style.display = "none";
            } else {
                content.style.display = "block";
            }
        });
    }
};

它就像一个“观察者”。您可以根据需要修改check函数。使用document.querySelector(selector);将仅匹配DOM中的第一个元素。

在您的情况下,我可能会使用EventDelegation,但我的理解是您在追随其他事情。

希望它会有所帮助!

答案 4 :(得分:-1)

答案很简短:

1)不要害怕使用jQuery,它仍然是操纵DOM(例如您要在问题中实现的目标)的一种古老的好方法。

2)完成“ 从远程源加载 ”之后,您需要执行代码段。

$.get('t.html', {}, function (data) {
  var $response = $('<div />').html(data);
  var $div1 = $response.find('#div1');
  $('#div-for-remote-content').append($div1);
}, 'html').then(function () {
  var coll = document.getElementsByClassName("collapsible");
  var i;

  for (i = 0; i < coll.length; i++) {
    coll[i].addEventListener("click", function () {
      this.classList.toggle("active");
      var content = this.nextElementSibling;
      if (content.style.display === "block") {
        content.style.display = "none";
      } else {
        content.style.display = "block";
      }
    });
  }
});

不确定这是否是您要查找的内容,因为我无法完全理解您遇到的问题。

多亏了我认为讨厌jQuery的选民,但我仍然坚持认为jQuery在2018年是一个有用的库,即使我在最近的几个项目中并未真正使用它。