Я относительно новичок в JavaScript, поэтому я не уверен, делаю ли я здесь что-то традиционно, или есть ли лучший способ сделать то, что я пытаюсь сделать.
У меня есть функция JavaScript, которая берет около 3600 предложений из документа JSON и автоматически вставляет их в мой HTML-код. Уникальный идентификатор генерируется для каждого раза в HTML.
Я хочу создать событие onclick для каждого предложения, чтобы при нажатии на него отображалась дополнительная информация о предложении. Это означает, что мне нужно объявить тысячи переменных, по одной для каждого предложения и по одной для каждого информационного блока, связанного с этим предложением:
var sent1 = document.getElementById('s1');
var sent1info = document.getElementById('s1info');
var sent2 = document.getElementById('s2');
var sent2info = document.getElementById('s2info');
var sent3 = document.getElementById('s3');
var sent3info = document.getElementById('s3info');
...
Это слишком много, чтобы делать вручную. Есть ли способ автоматизировать процесс объявления этих переменных или есть лучший способ сделать то, что я делаю?
Для контекста мое намерение с каждой переменной состоит в том, чтобы передать ее в эту функцию:
sent1.onclick = function(){
if (sent1info.className == 'open'){
sent1info.className = 'close';
} else{
sent1info.className = 'close';
}
};
Отсюда CSS уменьшит информационное поле до высоты 0, когда имя класса «закрыто», и расширит его, когда имя класса «открыто». Но, опять же, это потребует от меня написания этой функции тысячи раз.
Есть ли способ сделать это автоматически? Или я все неправильно делаю?
Изменить, чтобы показать HTML:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<div id = "everything">
<header id = "theheader" class = "clearfix">...</header>
<div id = "thebody" class = "box clearfix">
<aside id = "page" class = "side">...</aside>
<div class = "items">
<article id = "content" class = "article">
<img id = "sentpic" src = "sentpic.jpg">
<h1>Sentences</h1>
<div id = "sentences">
*** This is where the JS inserts sentences and information ***
<ul id='sent1' class='sentcontent'><li class='number'>1.</li><li class='thesent'>...</li></ul>
<div id='sent1info' class='infobox'>
<ul class='sentinfo'><li class='information'>Info:</li><li class='infotext'><em>...</em></li></ul>
<ul class='sentinfo'><li class='information'>Line:</li><li class='line'>...</li></ul>
</div>
<ul id='sent2' class='sentcontent'><li class='number'>2.</li><li class='thesent'>...</li></ul>"
<div id='sent2info' class='infobox'>
<ul class='sentinfo'><li class='information'>Info:</li><li class='infotext'><em>...</em></li></ul>
<ul class='sentinfo'><li class='information'>Line:</li><li class='line'>...</li></ul>
</div>
*** it goes on like this for each sent inserted ***
</div>
</article>
</div>
</div>
<div class = "associates clearfix">...</div>
<footer class = "foot">...</footer>
</div>
<script type = "text/javascript" src = "index.js"></script>
</body>
</html>
Просмотрите массивы.. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Просто индексируйте элементы, имеющие общий класс, по индексу в массиве предложений. Покажите, как вы генерируете html из данных
Было бы полезно увидеть созданную структуру HTML. Например: являются ли предложение и деталь одним и тем же родителем? Они где-то еще на странице? Кроме того, какова функция, которая генерирует это? Если вы отредактируете свой вопрос, включив в него эти вещи, вы, вероятно, получите лучший ответ.
Не могли бы вы показать фрагмент HTML, который вы используете в настоящее время?
Кроме того, вы создаете HTML динамически (используя JS)?
onClick занимает 3-4 строки, чтобы установить className в closed во всех случаях if/else. Просто установите closed.
ОК, спасибо за предоставление контекста HTML-обертки. Теперь понятно, что вы вставляете элементы «предложения» динамически. Не могли бы вы теперь показать HTML для раскрывающихся списков предложения? Так что просто укажите один из "Здесь JS вставляет предложения."
@AdeDoyle Проверьте ответ ниже, который показывает альтернативный подход к добавлению/удалению имен классов с динамически добавляемыми элементами.
@RokoC.Buljan RokoC.Buljan Я добавил это сейчас. Я должен был сделать это в первую очередь, но мне пришлось копаться в JS, чтобы найти.
@AdeDoyle, да, хорошо! Спасибо - добавил ответ в надежде :)



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


<details>:const json = [
{thesent:"Lol", info:"This is some info 1", line:"Whatever 1..."},
{thesent:"Lorem", info:"Some info 2", line:"Something here 2..."},
];
const template_sentence = (ob, i) => `
<details class = "sentence">
<summary>${i+1} ${ob.thesent}</summary>
<h3>${ob.info}</h3>
<div>${ob.line}</div>
</details>`;
document.querySelector("#sentences").innerHTML = json.map(template_sentence).join('');<div id = "sentences"></div>В противном случае, используя вашу текущую несемантическую разметку:
Таргетинг по ID (в вашем конкретном случае) не нужен. Существуют и другие методы, такие как + Next Adjacent селектор брата и сестры в CSS.
А вот пример JS — он не требует пояснений, но не стесняйтесь спрашивать.
.active в этом примере) на интерактивный элемент UL.+, чтобы сделать DIV Информацияdisplay: block/* Just a sample... you'll know how to modify this with the right properties I hope */
const json = [
{thesent:"Lol", info:"This is some info 1", line:"Whatever 1..."},
{thesent:"Lorem", info:"Some info 2", line:"Something here 2..."},
];
// The toggle function:
const toggleInfobox = ev => ev.currentTarget.classList.toggle("active");
// A single sentcontent template
const template_sentence = (ob, i) =>
`<ul class='sentcontent'>
<li class='number'>${i+1}</li>
<li class='thesent'>${ob.thesent}</li>
</ul>
<div class='infobox'>
<ul class='sentinfo'>
<li class='information'>Info:</li>
<li class='infotext'><em>${ob.info}</em></li>
</ul>
<ul class='sentinfo'>
<li class='information'>Line:</li>
<li class='line'>${ob.line}</li>
</ul>
</div>`;
// Get target element
const el_sentences = document.querySelector("#sentences");
// Loop JSON data and create HTML
el_sentences.innerHTML = json.map(template_sentence).join('');
// Assign listeners
const el_sentcontent = el_sentences.querySelectorAll(".sentcontent");
el_sentcontent.forEach(el => el.addEventListener('click', toggleInfobox));/* BTW, why do you use <ul> ? That's not a semantic list! */
.sentcontent { padding: 0; cursor: pointer;}
.sentcontent li { display: inline-block; }
/* Arrows are cool, right? */
.sentcontent:before { content: "\25BC"; }
.sentcontent.active:before { content: "\25B2"; }
/* Hide adjacent .infobox initially,
/* and show adjacent .infobox on JS click */
.sentcontent + .infobox { display: none; }
.sentcontent.active + .infobox { display: block; }<div id = "sentences"></div>В этом Ответ на переполнение стека вы можете узнать больше о переключении элемента при нажатии какой-либо кнопки.
Идентификаторы определенно в целом не бесполезны (не уверен, что вы это имели в виду, но так показалось). Для определенных требований доступности идентификаторы являются абсолютной необходимостью.
@AndyHoffman Я согласен с тем, что идентификаторы полезны, когда нужно запросить параметризацию якорей, чтобы разрешить прокрутку, как в: //example.com#sent1 или //example.com#sent1content, но ни ответы, ни вопрос не предполагают такого поведения - не говоря уже о describedby aria и т. д. - Это еще одно расширение этого ответа - возможно, если ОП интересуется этой темой...
Верно, но вы сделали общее заявление об удостоверениях личности, которое, учитывая ваш здесь ранг, могло бы послужить плохим советом.
Поскольку браузеры сопоставляют селекторы CSS справа налево, эта строка: .sentcontent + .infobox { display: none; } может быть оптимизирована до .infobox { display: none; }.
@AndyHoffman да ... в качестве общего примечания ... но важно инкапсулировать поведение. .infobox может появиться в другом месте DOM. Мы не хотим display: none; для этих элементов, только те, которые в какой-то момент обрабатываются точным воссоединением при выборе родственного элемента В:.sentcontent + .infobox → .sentcontent.active + .infobox — Разделение задач и т. д. Большая тема. Так что нет. Предложенный вариант абсолютно предпочтителен, на мой взгляд.
Этот вопрос больше относится к архитектуре, чем к необходимости создания динамических переменных. Рассмотрим этот пример:
ids удалены (используются существующие имена классов)nhandleClick мы переключаем класс open на элемент, по которому щелкнули, что позволяет нам использовать селектор соседнего брата через CSS.close, так как отсутствие класса open означает закрытое состояние.let outerUL = document.querySelectorAll('.sentcontent')
function handleClick() {
this.classList.toggle('open');
}
outerUL.forEach(ul => {
ul.addEventListener('click', handleClick);
}).sentcontent {
cursor: pointer;
}
.sentcontent.open + .infobox {
display: block;
}
.infobox {
background-color: #eee;
display: none;
padding: .25em .5em;
}<ul class='sentcontent'>
<li class='number'>1.</li>
<li class='thesent'>Sent</li>
</ul>
<div class='infobox'>
<ul class='sentinfo'>
<li class='information'>Info</li>
<li class='infotext'><em>Info text</em></li>
</ul>
<ul class='sentinfo'>
<li class='information'>Line info</li>
<li class='line'>Line</li>
</ul>
</div>
<ul class='sentcontent'>
<li class='number'>2.</li>
<li class='thesent'>Sent</li>
</ul>
<div class='infobox'>
<ul class='sentinfo'>
<li class='information'>Info</li>
<li class='infotext'><em>Info text</em></li>
</ul>
<ul class='sentinfo'>
<li class='information'>Line info</li>
<li class='line'>Line</li>
</ul>
</div>https://jsfiddle.net/d91va7tq/2/
Энди, теперь это выглядит круто! (PS: было бы неплохо, вместо переключения класса на элемент DIV, переключать класс на элемент target/trigger! Таким образом, мы можем не только управлять DIV display с помощью оператора +, но и изменять стили для триггера !) Молодец
@RokoC.Buljan Хороший совет. Сделанный.
@AndyHoffman Мне трудно заставить это работать. Нужна ли мне загрузка где-то там? Когда я тестирую, все загружается, но щелчок на самом деле ничего не делает.
@AdeDoyle Поместите JavaScript непосредственно перед </body> внизу разметки: <script>js code here</script>.
@AdeDoyle Если это поможет, вот полная демонстрация (через Plunkr) со всем вашим исходным кодом (плюс мой).
@AndyHoffman У меня уже есть <script type = "text/javascript" src = "index.js"></script> прямо перед </body>, указывающий на файл, в котором находится JS. Пробовал ставить напрямую, просто для уверенности, но безрезультатно.
@AdeDoyle Я обновил плункр демо, чтобы использовать внешний файл script, index.js. Как видите, я пропустил type = "text/javascript", потому что он больше не нужен.
@AdeDoyle Возможно, вы добавляли теги <script></script> во внешний файл, что могло привести к его поломке.
@AndyHoffman Я думаю, это как-то связано с тем, что внешний файл также используется для динамической вставки всех предложений из документа .json. Если бы это было полезно, я мог бы создать ветку на плункере и загрузить полный html и, например, данные json?
@AdeDoyle Это имеет смысл.
@AndyHoffman Теперь я добавил плунжерная вилка, и он воссоздает мою проблему. Приносим извинения за задержку, пришлось много редактировать, чтобы уменьшить размер.
@AdeDoyle Думаю, мы готовы! Посмотрите здесь.
Работает, как задумано, с этим последним исправлением. Идеально. На всякий случай, если это будет полезно кому-то еще в будущем, мне пришлось объединить JS из этого ответа в отдельную функцию и вызвать эту функцию в конце функции, которая динамически создает предложения сразу после их вставки. в HTML.
Когда у вас есть очень большие данные json, то лучше иметь в виду, что не следует отображать все данные сразу, это повлияет на производительность веб-браузера. Вместо этого визуализируйте, когда это необходимо. И именно тогда пользователь нажимает для получения дополнительной информации.
Я сделал несколько примеров ниже, обязательно прочитайте комментарий
const json = [
{thesent:"Lol", info:"This is some info 1", line:"Whatever 1..."},
{thesent:"Lorem", info:"Some info 2", line:"Something here 2..."},
];
const container = document.querySelector(".container");
json.forEach((item)=> {
let x= item;
let el = document.createElement("li");
el.innerHTML = x.thesent;
container.appendChild(el);
el.addEventListener("click",()=> {
var infoContainer= el.querySelector(".info");
// dont create all html element at once, instead create them
//when the user click on it. this is better when you have a very large data.
if (!infoContainer){ // not created, then create
infoContainer = document.createElement("div");
infoContainer.className = "info";
var info = document.createElement("div");
var line = document.createElement("div");
info.innerHTML = x.info;
line.innerHTML = x.line;
infoContainer.appendChild(info);
infoContainer.appendChild(line);
el.appendChild(infoContainer);
} else if (infoContainer.style.display == "none") // created and hidden, then display it
infoContainer.style.display = "block";
else infoContainer.style.display= "none"; // already displayed then hide it
});
}).container li >div.info >div:first-child{
font-size: 12px;
}
.container li >div.info >div:last-child{
font-size: 10px;
}<ul class = "container">
</ul>Не совсем та разметка, которую использует OP. Он использует UL (странным образом, но да... :) - Хорошая идея насчет рендерить когда надо!
Я скопировал способ, которым сделал навигационное меню в шапке. Теперь я понимаю, что должен был использовать табличный формат (да?), но я не очень хорошо разбираюсь в JS, и технически это работает. Я мог бы реализовать это как часть исправления.
Спасибо @Roko C. Buljan, у меня был некоторый опыт в таких вещах. а насчет markup ему все равно надо его переписать. Поэтому я хотел, чтобы он понял, как правильно или хорошо это делать.
Рад слышать, что это помогло вам тогда. Но вы знаете, что использование jquary или vue значительно упрощает рендеринг данных. использование таблицы не лучше, вы не можете контролировать дизайн столько, сколько хотите. использование div или ul намного лучше, но тогда это то, что я думаю. И напоследок не забудьте проголосовать :)
К сожалению, использование jquery, vue и т. д. не вариант. Я все еще пытаюсь исправить работу, прежде чем голосовать, но все равно ценю всю полезную информацию, которую я получаю.
почему бы не использовать цикл forEach или for?