span should have 9 li - javascript

Iam trying to make thymeleaf page into angular js .Below is my code
</ul>
<ul class="list">
<span class="columns">
<li ng-repeat="item in list">{{item.name}}</li>
</span>
</ul>
Each span should have 9 elements. after that another span should be open. But right now all items are in single span.

An <li> element can only be a child of a <ul> or <ol>.
The only children a <ul> can have are <li>
… so you can't group them with span elements.
If you want to organise them in groups of 9 for rendering purposes, then consider using CSS grid (and setting up the ul as a grid with 9 columns).
ul {
list-style: none;
display: grid;
grid-template-columns: repeat(9, 1fr);
}
li:nth-child(odd) {
background: #afa;
}
<ul><li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x<li>x</ul>

The <span> element is not valid content for <ul> elements.
From the Docs:
The HTML <ul> element represents an unordered list of items, typically rendered as a bulleted list.
Permitted content: zero or more <li> elements, which in turn often contain nested <ol> or <ul> elements.
For more information, see
MDN HTML Reference: <ul> Element

In your controller, you can make a new array out of list:
let newList = [];
let tempList = [];
for (let i=0; i<list.length; i++) {
tempList.push(list[i]);
if (i%9 === 0) {
newList.push(tempList);
tempList = [];
}
}
newList will be like this:
[
[
obj1,
obj2,
obj3,
obj4,
obj5,
obj6,
obj7,
obj8,
obj9,
],
[
obj10,
obj11,
obj12,
obj13,
obj14,
obj15,
obj16,
obj17,
obj18
],
[
obj19,
obj20,
obj21,
obj22,
obj23,
obj24,
obj25,
obj26,
obj27
],
so on...
]
and then you can use this newly formed array to suit your purpose
<span ng-repeat="item in list" class="columns">
<li ng-repeat="item2 in item">{{item2.name}}</li>
</span>
However, as many people have mentioned here, you should be using <ul> to group your list element like so:
<div class="columns">
<ul ng-repeat="item in list" class="list">
<li ng-repeat="item2 in item">{{item2.name}}</li>
</ul>
</div>
I haven't tested the code but you should get the idea.

Related

Can I change a function that uses for loop to a faster one with only pure JavaScript, (no jQuery)?

I have the following function that is used to show one div and hide all others.
async function showHide(showId, hideList) {
let ele = document.getElementById(showId);
await hideList.forEach(item => {
item.style.display = 'none';
});
ele.style.display = 'block';
}
#c2,
#c3,
#c4 {
display: none;
}
li {
cursor: pointer;
}
<div>
<li onclick="showHide('c1', [c2,c3,c4]);">Show One</li>
<li onclick="showHide('c2', [c1,c3,c4]);">Show Two</li>
<li onclick="showHide('c3', [c1,c2,c4]);">Show Three</li>
<li onclick="showHide('c4', [c1,c2,c3]);">Show Four</li>
<div id="c1">Showing Item 1</div>
<div id="c2">Showing Item 2</div>
<div id="c3">Showing Item 3</div>
<div id="c4">Showing Item 4</div>
</div>
The function works great, however I was wondering if there is a faster, shorter or better way to do this? Ideally all 3 of the above combined would be great.
It's syntactically invalid for a <li> to be a child of a <div> - a list item should only be a child of a list element. Either use one of the list elements to surround the <li>s (and only the <li>s), or use a different tag, like a button or span.
forEach does not return anything, so awaiting it doesn't help at all.
Rather than iterating through an array of the other IDs, consider selecting them all at once with a different selector method. A nice way would be to put them all in the same container, then select children of the container. You can then use the index of the clicked element in its container to determine the matching element to be shown below.
Don't use inline handlers - they're quite terrible practice. Attach listeners properly using JavaScript instead.
Instead of assigning to the style of elements directly, consider toggling a CSS class instead.
const ul = document.querySelector('ul');
const items = document.querySelectorAll('.item-container > div');
ul.addEventListener('click', (e) => {
if (!e.target.matches('li')) return;
const indexToShow = [...ul.children].indexOf(e.target);
items.forEach((item, i) => {
item.classList.toggle('show', i === indexToShow);
});
});
li {
cursor: pointer;
}
.item-container > div:not(.show) {
display: none;
}
<ul>
<li>Show One</li>
<li>Show Two</li>
<li>Show Three</li>
<li>Show Four</li>
</ul>
<div class="item-container">
<div class="show">Showing Item 1</div>
<div>Showing Item 2</div>
<div>Showing Item 3</div>
<div>Showing Item 4</div>
</div>
The JavaScript is a bit longer to account for the generic functionality without hard-coding IDs, but that's the cost for making the HTML and CSS less repetitive.

Javascript remove all empty innerHTML elements children

ul element that has dynamically loaded li a children, sometimes the li a populate empty innerHTML. How do I remove all of the li elements that have an empty a child?
Current (errors Uncaught TypeError: Cannot read property 'innerHTML' of null)
var stepList = document.querySelector(".parent"),
listItems = stepList.children;
for (var i = 0; i < listItems.length; i++) {
if (listItems[i].firstElementChild.innerHTML === "") {
listItems[i].remove();
}
}
Starting
<ul class="parent">
<li>
One
</li>
<li>
</li>
<li>
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
Goal
<ul class="parent">
<li>
One
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
Problem with your code is when you remove items, children will actually reduce, so you are shifting everything down one index. People typically loop backwards to stop this error.
Perosnally I would just use a empty pseudo class selector with querySelectorAll and a forEach loop.
var emptyAnchors = document.querySelectorAll("li > a:empty")
emptyAnchors.forEach(function (a) {
a.parentNode.remove()
})
<ul class="parent">
<li>
One
</li>
<li>
</li>
<li>
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
When you're looping through a list and removing things, it throws the index off. Loop through it backwards instead:
let lis = document.querySelector(".parent").children
for (let i = lis.length - 1; i >= 0; i--) {
if (lis[i].firstElementChild.innerHTML === "") {
lis[i].remove()
}
}
<ul class="parent">
<li>One</li>
<li></li>
<li></li>
<li>Two</li>
<li>Three</li>
</ul>
<script>
//list of all anchor elements in your HTML DOM
onload = function() {
var anchors = document.getElementsByTagName("a");
for(var i = 0; i < anchors.length; i++) {
var anchor = anchors[i];
if(anchor.innerHTML == "") {
anchor.parentNode.removeChild(anchor); //removes the anchor
}
}
}
</script>
Something
Something
Something
VERY CHEEKY pure-css solution: cause the a tags, and not the li tags, to be the ones displaying the list-item bullet-points. Then simply apply display: none to all empty a tags! (Their containing li elements will naturally flatten to height: 0 since they will be display: block, and entirely without content.)
li {
position: relative;
display: block;
}
a {
position: relative;
display: list-item;
}
a:empty {
display: none;
}
<ul class="parent">
<li>
One
</li>
<li>
</li>
<li>
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
Note that css solutions aren't always ideal, because even if things look fine visually, the semantic markup of the page may still be in an undesirable state.

How to query a data attribute value in javascript (no jquery)?

If I have a grid made up of ul's (the rows) and li's (the cells), I wanted to get a specific cell based on the data attribute values of the ul and the li:
document.querySelectorAll(div.grid ul[data-${this.y}] li[data-${this.x}]'_
When I searched on MDN, I only found how to retrieve the html element based on the data attribute, but not it's value.
Any pointers would be greatly appreciated - also no jQuery please.
You could use the String interpolation and get it worked.
Here is what you could do.
document.addEventListener('DOMContentLoaded', function() {
let ulData = 2,
liData = 4;
document.querySelector(`div.grid ul[data="${ulData}"] li[data="${liData}"]`).classList.add("red");
});
.red {
color: red;
}
<div class="grid">
<ul data="2">
<li data="1">
One
</li>
<li data="2">
Two
</li>
<li data="3">
Three
</li>
<li data="4">
Four
</li>
<li data="5">
Five
</li>
</ul>
</div>

Using jQuery .nextUntil() on split lists

Is it possible to get the .nextUntil() to work on split lists, or get the same functionality?
So I am trying to implement the ever so popular shift select for my items, and since they are ordered in a list in my application I want to be able to select across <ul> borders.
I have the following set of DOM elements:
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
And using something like this:
$('li.clicked').nextUntil('li.selected');
I'd like a list containing the following elements
[ <li class="item">third</li>,
<li class="item">fourth</li>,
<li class="item">fifth</li> ]
However all I get is the elements leading up to the split </ul>. Is there any way of doing this? I have also tried to first selecting all items with $('.item')and then using .nextUntil() on them without any luck.
Is this what you are looking for?
$('li').slice($('li').index($('.clicked'))+1,$('li').index($('.selected')));
For reference
Jquery.Index
Jquery.Slice
Edit
So if you do
$('li')
you will get an array of all elements 'li' getting:
[<li class=​"item">​first​</li>​,
<li class=​"item clicked">​second​</li>​,
<li class=​"item">​third​</li>​,
<li class=​"item">​fourth​</li>​,
<li class=​"item">​fifth​</li>​,
<li class=​"item selected">​sixth​</li>​,
<li class=​"item">​seventh​</li>​]
Since it is an array you can slice him to get an sub array you just need two positions, where to start and here to finish.
//start
$('li').index($('.clicked'))+1 // +1 because you dont want to select him self
//end
$('li').index($('.selected'))
For better preformance you should before create an array with all li so it will not search all dom 3 times for the array of 'li'
var array = $('li');
var subarray = array.slice(array.index($('.clicked'))+1,array.index($('.selected')));
Assuming these lists cannot be merged into one, it is impossible using the nextUntil method. This is because of how jQuery performs traversing. According to the documentation,
Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed.
fifth is not a sibling of the clicked element, but rather it is a child of the sibling of the element's parents.
I came up with two possible solutions.
Solution 1: Combine NEXT and PREV traversals
Assuming that .clicked is always in the first list and .selected is always in the second list, combining prevAll() with nextAll() should do the trick. This assumes that the order is the same.
var siblings = $("li.clicked").nextAll()
Get all siblings of the current element AFTER the element itself.
var distantSiblings = $("li.selected").prevAll();
Get all distant siblings after the first element, but before the second one.
siblings.push(distantSiblings);
Combine them into two and then iterate over each element.
var siblings = $("li.clicked").nextAll()
var distantSiblings = $("li.selected").prevAll();
siblings.push(distantSiblings);
siblings.each(function() {
$(this).addClass("blue");
});
.blue { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
http://jsfiddle.net/r15z10o4/
Note:
You will notice that the above code works, however it might not be the optimal solution. This is only confirmed to work for your example above. There may also be a less verbose solution.
Solution 2 (Find index of all list items)
Another idea is to find the index of all items, and collect the elements that are sandwiched between those two indices. You will then want to use the 'slice' selector to get the range in between.
var items = $(".item");
var clicked = $(".clicked");
var selected = $(".selected");
var clickIndex = items.index(clicked);
var selectIndex = items.index(selected);
$("li").slice(clickIndex + 1, selectIndex).addClass("blue");
var clicked = $(".clicked");
var selected = $(".selected");
var clickIndex = $("li").index(clicked);
var selectIndex = $("li").index(selected);
$("li").slice(clickIndex+1, selectIndex).addClass("blue");
.blue { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
You can do it manually by selecting all these items at once, and using loops.
Consider the parent element, let's say "container":
<div id="container">
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
</div>
Now, you can select all these items:
var $items = $("#container > ul > li.item"); // or simply $("#container .item");
And iterate through them:
var $items = $(".item"), $result = $(), found = false;
for (var i = 0; i < $items.length; i++)
{
$currentItem = $items.eq(i);
if ($currentItem.is('.clicked')) {
found = true;
continue;
}
if ($currentItem.is('.selected'))
break;
if (found)
$result = $result.add($currentItem);
}
console.log($result);
Here is the working JSFiddle demo.
In any case it feels like you will need to define groups of li.
I think the easiest is to create a function getting a list of lis that you can request any way you want then to filter the el you are interested in.
function elRange(elList, start, end){
// we do not use indexOf directly as elList is likely to be a node list
// and not an array.
var startI = [].indexOf.call(elList, start);
var endI = [].indexOf.call(elList, end);
return [].slice.call(elList, startI, endI + 1);
}
// request the group of *ordered* elements that can be selected
var liList = document.querySelectorAll('ul.current > li, ul.later > li');
var selectionEnd = document.querySelector('.selected');
[].forEach.call(liList, function(li){
li.addEventListener('click', function(){
var selected = elRange(liList, li, selectionEnd);
selected.forEach(function(el){
el.classList.add('red');
})
});
});
.selected {
color: blue;
}
.red {
color: red;
}
<ul class="current">
<li class="item">first</li>
<li class="item">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>

How to stack <li> into divs using ng-repeat

I am using ng-repeat to iterate over an array to generate <li>. But i want to stack every 2 li into div.
<li ng-repeat="item in items">{{item.name}}</li>
I want the output to be like this:
<ul>
<div>
<li>A</li>
<li>B</li>
</div>
<div>
<li>C</li>
<li>D</li>
</div>
</ul>
How should i achieve this with ng-repeat?
Thanks in advance for any help.
Although it is invalid to put div inside ul but to illustrate. i have made a function to split your array into group of 2 element array contained in wrapper array
Or You can also use splice method
Working Demo 1
angular.module('testApp',[]).controller('testCtrl', function($scope) {
$scope.items=['1','2','3'];
$scope.groupByTwo = function (array)
{
var newarray =[];
index=0;
for(a=0;a<array.length;a++){
if(a==0 || a%2==0){
newarray[index]=[];
newarray[index].push(array[a]);
}else{
newarray[index].push(array[a]);
}
if(a!=0)
index++;
}
return newarray;
}
$scope.items=$scope.groupByTwo($scope.items);
});
div {
background:pink;
border:1px dotted black;
margin-top:15px;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp" ng-controller="testCtrl">
<ul>
<div ng-repeat="item in items">
<li ng-repeat="i in item">{{i}}</li>
</div>
</ul>
Working Demo 2
You can also use splice method
angular.module('testApp',[]).controller('testCtrl', function($scope) {
$scope.items=['1','2','3'];
var i,j,temparray=[],chunk = 2;
for (index=0,i=0,j=$scope.items.length; i<j; i+=chunk,index++) {
temparray [index]= $scope.items.slice(i,i+chunk);
}
$scope.items =temparray;
});
div {
background:pink;
border:1px dotted black;
margin-top:15px;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp" ng-controller="testCtrl">
<ul>
<div ng-repeat="item in items">
<li ng-repeat="i in item">{{i}}</li>
</div>
</ul>
Apply ng-repeat on ul and li will act as a template which will be generated for each item.
Ex :
<ul ng-repeat="item in items">
<li>{{item.name}}</li>
</ul>
This will result in
<ul>
<li>name1</li>
<li>name2</li>
<li>name2</li>
</ul>
If you have a collection consisting of 10 items , you can split them in multiple collections each of size 2 and then apply ng-repeat with each collection on div to get the output you want.

Categories