Скрипт не может получить элементы при загрузке страницы JS

У меня проблема с этим кодом:

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 () на странице и все ли содержимое загружено в основной документ, если это так, загрузите файлы сценария в основной документ.

У меня есть вопрос, будут ли скрипты в этих файлах автоматически работать с основным документом?

Используйте делегирование событий.

Sebastian Simon 02.09.2018 17:47

Я новичок в JS, не могли бы вы уточнить или показать мне, как это нужно было сделать? @Xufox

user10202925 02.09.2018 17:50

Присвоили ли вы этот класс какому-либо элементу на своей странице, и если да, то где находится этот код по отношению к первому вхождению элемента класса «сворачиваемый»?

Robidu 02.09.2018 17:50

Убедитесь, что ваш элемент <script> расположен непосредственно перед </body>, чтобы он не выполнялся до тех пор, пока DOM не будет готов.

Scott Marcus 02.09.2018 17:52

Код находится в отдельном файле внизу главной страницы перед закрывающим тегом body. Только удаленный контент содержит это имя класса, поэтому это имя класса отсутствует на главной странице при загрузке главной страницы @Robidu

user10202925 02.09.2018 17:54

@ScottMarcus: Это не решит проблему. Элементы загружаются после запроса сервера; их вообще не будет в DOM при запуске скрипта.

Sebastian Simon 02.09.2018 17:54

Затем вам нужно убедиться, что этот код запускается только после получения удаленного содержимого. Кстати: как вы получаете указанные удаленные ресурсы? Пожалуйста, отредактируйте это в своем вопросе, так как это поможет прояснить ситуацию.

Robidu 02.09.2018 17:56

@AndrewK В вашем случае делегирование событий будет выглядеть примерно так: document.addEventListener("click", function(e){ if (e.target.hasClass("collapsible")){код вашего прослушивателя событий} });, но замените каждый this на e.target. Таким образом, не имеет значения, когда появятся элементы, и вы привязываете только один прослушиватель событий.

Sebastian Simon 02.09.2018 17:57

По jQuery .get () получение происходит только в том случае, если пользователь «запросил» этот контент, в противном случае он вообще не будет загружен @Robidu

user10202925 02.09.2018 17:58

Я не могу просто добавить этот скрипт в удаленный контент, потому что он вызовет дублирование скриптов (когда пользователь запрашивает не одну, а несколько удаленных страниц), и это вызовет проблемы на главной странице @Robidu

user10202925 02.09.2018 18:00

Тем не менее было бы чрезвычайно полезно увидеть логику управления, которая запускает действие GET. Может быть, это дает важнейшую подсказку, которая заставит ваш код работать после получения нового контента.

Robidu 02.09.2018 18:02

Выглядит многообещающе, не могли бы вы изменить мой код своим и изложить его в качестве ответа, пожалуйста? Как я уже сказал, я новичок в этом штате @Xufox

user10202925 02.09.2018 18:04

Альтернатива - сделать запрос после вставки внешнего html

charlietfl 02.09.2018 18:05

@Andrew K: В качестве подсказки я бы порекомендовал вам попробовать применить предложенные модификации самостоятельно (Xufox предоставил возможный метод) и посмотреть, сможете ли вы заставить его работать. Это был бы лучший способ узнать больше об этом предмете, потому что, пробуя разные вещи, вы действительно чувствуете их. Если вы все же столкнетесь с неудачей, вы можете отредактировать продолжение своего вопроса, чтобы мы могли проверить новое решение, которое вы, возможно, придумали.

Robidu 02.09.2018 18:16

Вопрос обновлен, теперь присутствует "код загрузки"

user10202925 02.09.2018 18:41

Похоже на JQuery. Достаточно ли собственного решения с использованием AJAX? Мне нужно только знать, возвращается ли простой HTML.

Robidu 02.09.2018 19:14

Да, это jQuery @Robidu

user10202925 02.09.2018 19:16

Я придумал решение, которое должно удовлетворить ваши потребности, а также использует только собственный JS.

Robidu 02.09.2018 20:35

Я обновил вопрос, теперь он отражает мою текущую ситуацию

user10202925 02.09.2018 23:03
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
19
560
5

Ответы 5

Длинный ответ короткий:

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 году, даже если я фактически не использовал ее в нескольких последних проектах.

Даже если я вставляю скрипт в каждый вызов get, он все равно вызывает дублирующуюся проблему.

user10202925 02.09.2018 18:45

Есть решение, чтобы все было сделано. Этот метод поможет при условии, что любые загружаемые ресурсы возвращаются как 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). Любой другой код состояния не позволит сценарию изменять содержимое вашего контейнера, и я также добавил несколько элементарных проверок работоспособности.

Обратите внимание, что присвоение .innerHTML содержимого (которое должно быть HTML для правильной работы) очищает поддерево DOM вашего контейнера, так что вы можете назначать ему новое содержимое, не беспокоясь о каких-либо обработчиках событий или устаревших узлах DOM. Это позволяет избежать дублирования вызова обработчика событий, а также позволяет прикрепить эту функцию к кнопке в качестве обработчика события (или вызова из другого блока сценария). Обратите внимание, что вы должны передать этой функции путь к нужному ресурсу.

Обновлено:

Исправлена ​​ошибка в моем фрагменте кода

Обновлено еще раз:

Когда вы импортируете этот код в свой документ, вам необходимо убедиться, что он вызывается всякий раз, когда кто-то загружает дополнительный контент в документ. Вы можете сделать это, прикрепив функцию-оболочку к любому механизму, необходимому для облегчения этого (т. Е. Кнопка, которая запускает обработчик событий, который, в свою очередь, вызывает эту функцию, а также предоставляет URL-адрес загружаемого ресурса).

После вызова load_content() запускается следующая последовательность событий:

  • Создайте XMLHttpRequest (требуется для выполнения запроса AJAX для желаемого ресурса).
  • Присоедините обработчик событий к объекту XMLHttpRequest, который прослушивает переходы состояния готовности (чтобы мы могли определить, когда ответ доступен для использования).
  • Откройте ресурс для поиска. Обратите внимание, что третий параметр установлен на истинный, чтобы сделать это асинхронным запросом.
  • Отправьте заявку. XMLHttpRequest немедленно возвращается, но поскольку мы прослушиваем события readystatechange, мы обнаруживаем, как только запрос был обработан. А пока можно позаботиться о других вещах.
  • В конце концов на запрос получен ответ, срабатывает событие readystatechange, и наш запрос сообщает, что запрос был завершен (this.readyState == 4). Это наш сигнал о том, что мы можем начать обработку ресурса.
  • Проверьте, был ли запрос успешным (например, this.status == 200). Если нет, значит, что-то пошло не так, и все нужно оставить нетронутым.
  • В противном случае посмотрите, существует ли контейнер. Если нет, войдите в консоль и создайте исключение (защита от неизвестного элемента).
  • Назначьте текст ответа .innerHTML нашего контейнера. Старое содержимое отбрасывается, и в указанном контейнере создается новое поддерево DOM.
  • Получите список всех элементов, которые принадлежат классу collapsible и находятся в нашем контейнере.
  • Для каждого найденного таким образом элемента повторите: прикрепите обработчик событий, который прослушивает события щелчка и при вызове переключает видимость рассматриваемого элемента.

Любые ресурсы, загруженные из удаленного источника, вообще не участвуют в этом процессе, поэтому вы не рискуете многократным запуском этой функции, и каждый раз, когда новый контент загружается в контейнер, старый фактически перезаписывается.

Я оценил вашу помощь, проблема больше, чем я думаю в начале, и основная проблема - дублирование, исходный вопрос не был задан правильно, после переосмысления моей ситуации с этим конкретным случаем и другим возможным будущим разработчиком я пришел к возможному решению. Можно ли проверять и снова запускать сценарии специфический в основном файле сценария после каждого успешно выполненного запроса на получение?

user10202925 02.09.2018 20:43

Это так, и без проблемы дублирования. Это похоже на то, что я сделал здесь, прикрепив код, который устанавливает любые обработчики событий на результат запроса AJAX. Поскольку для этого не требуется загружать скрипт вместе с удаленными ресурсами, вы не рискуете дублироваться.

Robidu 02.09.2018 20:48

Я не получил ваш последний комментарий, исходная проблема с самого начала заключалась в том, что скрипт из внешнего файла на главной странице не запускался на удаленном контенте после загрузки, я просто хочу, чтобы скрипты из внешнего файла на главной странице работали с новым добавленным контентом в эта страница, если предыдущий ответ каким-то образом решает эту проблему - я не понял

user10202925 02.09.2018 21:54

Хорошо, тогда позвольте мне отредактировать последовательность событий - может быть, это проясняет ситуацию.

Robidu 02.09.2018 21:56

Лучше всего проверить, работает ли это для вас, - это поместить этот код во внешний файл JS и подключить функцию к любым элементам, которые загружают ваши внешние ресурсы. Единственное, что вы должны гарантировать, - это передать функции URL-адрес желаемого ресурса.

Robidu 02.09.2018 22:28

Решение 1. Добавьте слушателей в .collapsible, которые находятся в извлеченном HTML, а затем добавьте их в документ:

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;
}
<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>

Решение 2.Используйте делегирование событий:

/* 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;
}
<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 для данного элемента в консоли Chrome.

[...document.querySelectorAll("*")].map(el => 
    [el, getEventListeners(el)]
).filter(([_,o]) => Object.keys(o).length)

Эта функция будет продолжать поиск определенного класса в целевом элементе:

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.info('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, вероятно, в вашем случае, но я понимаю, что вам нужно что-то еще.

Надеюсь, это все равно поможет!

Если вы не знаете, когда и как класс «сворачиваемый» добавляется в вашу 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);

};

Таким образом, ваш код фактически будет ждать, пока класс 'collapsible' будет добавлен в вашу DOM, прежде чем будет выполнен.

Кстати, я бы не стал создавать код, который полагается на чужой код: если вы не контролируете класс «сворачиваемый», вы готовите свой код для сбоя ...

Другие вопросы по теме