Javascript-форум (https://javascript.ru/forum/)
-   (X)HTML/CSS (https://javascript.ru/forum/xhtml-html-css/)
-   -   querySelectorAll / is not a valid selector (https://javascript.ru/forum/xhtml-html-css/62458-queryselectorall-not-valid-selector.html)

Alexander Belov 11.04.2016 22:42

querySelectorAll / is not a valid selector
 
Привет!

Делаю динамические табы.
Структура такая:
<ul>

    <li data-attr="a">....</li> 
    <li data-attr="b">....</li>
    <li data-attr="c">....</li>
     ...
    <li data-attr="z">....</li>

</ul>

<div>

    <div data-attr="a"></div>
    <div data-attr="b"></div>
    <div data-attr="c"></div>
     ...  
    <div data-attr="z"></div>
 
</div>


К каждому табу li привязан свой контект div. Табы создаются динамически, при создании им (li и div) задаётся data-attr, по которому потом при удалении они в связке и должны удаляться.

"Выборка" на удаление производится при помощи document.querySelectorAll(). Проблема в том, что в отличие от HTML5, в CSS3 не валидны названия селекторов, начинающихся с цифр.
При создании пользователь сам задаёт значение data-attr, которое и является названием самой вкладки таб.

Какими способами можно решить такую задачу?
Видятся следующие варианты:
1. Делать привязку к классу и выбирать потом по классу, а не по data-attr.
2. Присваивать автоматически значеніе для data-attr при создании табов, чтобы там по умолчанию не было в начале цифр. Например, "tab" + "название, введённое пользователем"
3. Блокировать ввод цифр для названия табов (только буквенные символы). Но такой вариант совсем не нравится.

рони 11.04.2016 23:04

Alexander Belov,
зачем data, разве индекса недостаточно?

рони 11.04.2016 23:05

Цитата:

Сообщение от Alexander Belov
К каждому табу li привязан свой контект div.

зачем ещё-то нужно делать выборку?

Alexander Belov 11.04.2016 23:10

рони,
1. Каким образом по индексу можно связать li и div? Учитывая, что табы могут добавляться и удаляться постоянно? Т.е. если например хранить всё в массиве, то индекс №..., например, не всегда будет соответствовать одному и тому же табу.

2. "Выборка" для удаления. Кликая на таб, мы находим div с таким же data-attr и удаляем их.

рони 11.04.2016 23:22

Alexander Belov,

сделайте макет ...

Alexander Belov 11.04.2016 23:47

JS Bin

рони 12.04.2016 00:10

Alexander Belov, удаление по индексу
if (target == x[i]) {
                var li = document.querySelectorAll('.tab')[i],
                div =  document.querySelectorAll('.content')[i];
		 		li.parentNode.removeChild(li);
                div.parentNode.removeChild(div);
		 	}

Alexander Belov 12.04.2016 00:25

рони,
Благодарю!
Всё намного проще, оказывается. Такой способ ещё не встречал.
:agree:

рони 12.04.2016 00:30

Alexander Belov,
проще будет при создании добавлять удаление -- тогда и цикла и индекса ненадо будет

Alexander Belov 12.04.2016 01:02

рони,
Не понимаю, что Вы имеете в виду. При создании мы создаём элемент и прикрепляем его к родителю, нажав на кнопку Ок. Как туда удаление увязать?

+

Разве не должны функции разбиваться максимально по логике? Одна на добавление, вторая на удаление и т.д., каждая для своего предназначения.

рони 12.04.2016 01:18

Alexander Belov,
tabSpan.addEventListener('click', removeTab([newTab, newContent]));


//Remove tab
function removeTab(nodeArr) {
     return function() {
         nodeArr.forEach(function(el) {
           el.parentNode && el.parentNode.removeChild(el);
         });
     }
 };

Alexander Belov 12.04.2016 09:59

рони,
Да, сразу не сообразил, о чём речь. Благодарю!

Alexander Belov 17.04.2016 01:00

рони,
дописал код на добавление / удаление вкладок. По итогу получается, что при удалении всё равно нужно выборку делать, потому подойдёт тот код, что Вы посоветовали в посте №11.

Получается следующее:
//Create a new Tab + Content
	var tabs = document.querySelector("#tabs"),
		defaultDiv = document.querySelector("#defaultDiv"),
		ok = document.querySelector("#ok"),
		cancel = document.querySelector("#cancel"),
		name = document.querySelector("#name"),
		note = document.querySelector("#note"),
		activeTab = document.getElementsByClassName("tab active"),
		activeDiv = document.getElementsByClassName("content active"),
		tabsArr = [],
		contentArr = [];


tabsArr и contentArr - пустые массивы для добавления новых вкладок и нового контента соответственно.

//OK button onclick
	ok.onclick = function createTab(){

		var newTab = document.createElement("li"),
			tabSpan = document.createElement("span"),
			newContent = document.createElement("div");

		activeTab.length ? activeTab[0].classList.remove("active") : 0;
		activeDiv.length ? activeDiv[0].classList.remove("active") : 0;



		newTab.innerHTML = name.value;

		if (newTab.innerHTML == "") {
			alert ("Gimme a name!");

			showBgr();
		}
	
		else {

			tabs.appendChild(newTab);
			newTab.classList.add("tab", "item", "active");
			tabs.insertBefore(newTab, defaultTab);

			tabSpan.innerHTML = "&#10005;";
			newTab.appendChild(tabSpan);
			tabSpan.classList.add("removeTab");

			defaultDiv.appendChild(newContent);
			newContent.innerHTML = note.value;
			newContent.classList.add("content", "item", "active");

			tabsArr.push(newTab);
			contentArr.push(newContent);
		}

		foldPopup();
	};


Перед закрывающей скобкой else мы добавляем новую созданную вкладку + контент в соответствующие массивы.

Теперь код на удаление вкладки + контента по клику
//Remove tab
	document.getElementsByClassName("removeTabs").onclick = function removeTab(tabsArr, contentArr) {
     return function() {
        tabsArr.forEach(function(el) {
           		el.parentNode && el.parentNode.removeChild(el);
        	 });
    	 }

    	 contentArr.forEach(function(el) {
           		el.parentNode && el.parentNode.removeChild(el);
        	 });
    	 };


Проблема: такой код не работает, консоль не выводит ошибку.
Прошу помочь разобраться.

Ссылка на JS Bin

Vlasenko Fedor 17.04.2016 01:07

getElementsByClassName возвращает коллекцию а не элемент

Alexander Belov 17.04.2016 01:24

Poznakomlus,
По моей ситуации мне не нужно знать конкретный элемент. Т.к. возвращается массив, клик должен срабатывать на любом из его элементов.

рони 17.04.2016 01:27

Цитата:

Сообщение от Alexander Belov
getElementsByClassName("removeTabs").onclick

у коллекции нет клика!!!

Alexander Belov 17.04.2016 01:35

Ок, перебираем массив при клике. Но это не меняет ситуацию - по-прежнему не работает ожидаемо и не выдаёт консоль ошибку.

//Remove tab
	var x = document.getElementsByClassName("removeTab");

	for (var i = 0, max = x.length; i < max; i += 1) {

		x[i].onclick = function removeTab(tabsArr, contentArr) {

	    	return function() {

	        tabsArr.forEach(function(el) {

	           		el.parentNode && el.parentNode.removeChild(el);

	        	 });
    	 }

    	 contentArr.forEach(function(el) {

           		el.parentNode && el.parentNode.removeChild(el);

        	 });
    	 };
	}

рони 17.04.2016 10:14

Alexander Belov,
вам же показали решение (пост 11), чем неустроило?

Alexander Belov 17.04.2016 11:11

рони,
Всё устроило. Ещё раз благодарю!
Добавлял eventListener просто не при создании, а отдельно, рядом с функцией потом на удаление. Потому выдавало ошибку. Всё отлично работает.

Alexander Belov 19.04.2016 23:35

Вложений: 1
Снова я со своими табами.
Теперь возникла необходимость добавить функционал назначения active tab и active div (таб + контент) после удаления узла таб + контент при помощи функции, предоставленной Рони.
Вот слушатель и функция:
tabSpan.addEventListener("click", removeTab([newTab, newContent]));

	//Remove tab
	function removeTab(nodeArr) {
	    return function() {


	        nodeArr.forEach(function(el) {

	           el.parentNode && el.parentNode.removeChild(el);
	      
	        });

	        console.log(tabs.firstChild);
	    };
	 }


консолью пытаюсь вывести firstChild от
<ul id="tabs">

			<li id="defaultTab" class="tab default">
					<span>+</span>
			</li>


		</ul><!--Tabs ul End-->

		<div id="defaultDiv" class="content default">
			
		</div>


Default - это таб, выполняющий функцию кнопки-добавления нового таба и контента. Новый контент записывается внутрь defaultDiv.

Т.е. при удалении узла таб + контент должно ничего не произойти, если у них не было класса active, а если такой класс был, то мы его у этой связки удаляем и назначаем "первым детям" для ul#tabs и div#defaultDiv, если такие дети существуют (если были ещё табы до удаления "активного" таба), если нет, то ничего не происходит.

Законсоленный "firstChild" при наличии созданного уже до этого таба выдаёт странную картину (см.рисунок). Как-будто его нет вовсе.

Почему так? И как тогда реализовать такую функцию в моём случае? Подскажите, пожалуйста.

Актуальный JS Bin

рони 19.04.2016 23:41

Alexander Belov,
забыть что есть getElementsByClassName и переписать код.

Alexander Belov 20.04.2016 00:08

рони,
А что тогда использовать в качестве селектора по классам, ведь насколько мне известно getElementsByClassName может отображать динамически состояние массива с заданным классом против querySelector, который отображает лишь статичный набор элементов массива.

element.classList.contains("smth") ?

рони 20.04.2016 01:31

Alexander Belov,
если сложно с динамическим списком ... а так пишите как хотите ... плохая локализация проблемы ... сложно угадать, что вы не так творите.

Alexander Belov 01.05.2016 22:59

Чтобы не создавать новую тему, отпишу сюда. Табы на добавление-удаление работают как нужно. Единственное, если удалить последнюю табу, то консоль выдаёт ошибку:
Uncaught TypeError: Cannot read property 'classList' of null
//Remove tab
	function removeTab(nodeArr) {
		return function(){
			nodeArr.forEach(function(el) {
                el.parentNode && el.parentNode.removeChild(el);
       		 });	

			setTimeout( function() {
		                tabs.querySelector("*").classList.add("active");
				defaultDiv.querySelector("*").classList.add("active");
		    }, 0);
	    };
	}


Это она ругается на строчки в функции setTimeout, когда последнюю табу удаляем, их же больше не остаётся. Логично было предположить, что создав условие типа
if (tabs.childNodes.length) {
          tabs.querySelector("*").classList.add("active");
   }


можно было бы избежать ошибки, но консоль как игнорирует условие и однозначно выдаёт ошибку и с if, и без него.
Что в данном случае сделано не правильно?
Как избежать ошибки в консоли при удалении последней табы?

Ссылка на JS Bin

рони 01.05.2016 23:14

Alexander Belov,
а что происходит в setTimeout, откуда и зачем это возникло, и что вы хотите найти * этим селектором?

рони 01.05.2016 23:25

Цитата:

Сообщение от Alexander Belov
if (tabs.childNodes.length)

children

Alexander Belov 02.05.2016 01:49

Цитата:

Сообщение от рони (Сообщение 415457)
Alexander Belov,
а что происходит в setTimeout, откуда и зачем это возникло, и что вы хотите найти * этим селектором?

Честно говоря, не совсем сам понимаю, почему оно работает. setTimeout для того, чтобы функция сработала после прорисовки DOM.
А селектор по факту выбирает первый child у tabs, если таковой имеется. Но почему использован *, я не понимаю. По логике он ведь должен выбирать всех детей вообще. Это решение мне подсказали, но не объяснили.

С children всё отлично работает - благодарю!


Часовой пояс GMT +3, время: 03:28.