Convert plain li texts to clickable links with pure javascript, randomly - javascript

I have a dynamic list of things. How to select only 3 items (that has 2 words or less) randomly, and convert it to clickable links. So everytimes the page is reloaded, random 3 links are created. Im expecting to use something like:
innerHTML = '' + text + ''
From this:
list 1 list 2 222 list 3 list 4
list 5 555 list .. list 99 list 100
To this:
list 1 list 2 222 (*ignore 3 or more words) list 3 (*selected randomly and pointed to mysite.com/folder/list-3) list 4 (*selected randomly and pointed to mysite.com/folder/list-3)
list 5 55 (*ignore 3 or more words) list .. list 99 (*selected randomly and pointed to mysite.com/folder/list-99) list 100

Once the page has loaded, loop three times, each time grabbing a random li and refreshing that grab until it meets the criteria of having fewer than three words and not having been used yet. Each time you find an li that is valid, change the innerHTML.
window.addEventListener('load', (event) => {
for (var i = 0; i < 3; i++) {
var randomLI;
while (!randomLI || randomLI.getElementsByTagName("a").length > 0 || randomLI.innerHTML.trim().split(" ").length > 2) {
randomLI = rando(document.getElementsByTagName("li")).value;
}
randomLI.innerHTML = "" + randomLI.innerHTML + "";
}
});
<script src="https://randojs.com/1.0.0.js"></script>
<ul>
<li>list 1</li>
<li>list 2 222</li>
<li>list 3</li>
<li>list 4</li>
<li>list 5 555</li>
<li>list ..</li>
<li>list 99</li>
<li>list 100</li>
</ul>
I used randojs.com to simplify the randomness and make it more readable. If you want to use this code, make sure this is in the head tag of your html document:
<script src="https://randojs.com/1.0.0.js"></script>

You can clean up the code a bit, but below can get you started.
Try the live demo at https://codepen.io/baadaa/pen/povOJJP
<!-- Your list here. -->
<ul class="listItems">
<li>list 1</li>
<li>list 2 222</li>
<li>list 3</li>
<li>list 4</li>
<li>list 5 555</li>
<li>list ..</li>
<li>list 99</li>
<li>list 100</li>
</ul>
<!-- Button to refresh the list -->
<button class="refresh">Refresh</button>
// Store the DOM elements in variables
const ul = document.querySelector('.listItems');
const listItems = Array.from(document.querySelectorAll('.listItems li'));
// Check if three items with links are present, otherwise run the logic again
function updateList(list, pickCount) {
if (pickCount === 3) {
ul.innerHTML = list.join(' ');
} else {
randomize();
}
}
function randomize() {
let pickCount = 0;
// Iterate over the list array to transform the items
const newList = listItems.map(item => {
// 50% chance of picking the item to include link
const isPicked = Math.random() > .5 ? true : false;
// Check if the item contains two or more words
if (item.innerHTML.split(' ').length > 2 ) {
// If containing more than two words, skip.
return item.outerHTML;
} else if (isPicked) {
// If containing two or less words, and passed the random pick, transform the item
pickCount += 1;
return `<li>${item.innerHTML}</li>`;
} else {
// Otherwise, skip.
return item.outerHTML;
}
});
return updateList(newList, pickCount);
}
document.querySelector('button.refresh').addEventListener('click', randomize);

Related

How to change the style of HTML list items using setInterval in JavaScript?

I have a HTML code with 3 list items and the ids for my ul is 'exercise6-list'
<ul id="exercise6-list">
<li>List 1</li>
<li>List 2</li>
<li>List 3</li>
</ul>
I need to make each li glow for three seconds and repeat itself
So far I have written:
var moveGlow = function() {
var element = document.querySelector("#exercise6-list");
// ... to make it glow i've so far used .setAttribute("class", "glow")
};
clearInterval(window.interval);
window.interval = setInterval(moveGlow, 3000);
*I'm very new to programming, but thank you for your help
To make a timed function we use setInterval, and add the class "glow" to the current element, and remove that class from the previous one.
To cycle through the elements, we use an index-variable, which increases per cycle and loops back around when necessary.
let index = 0;
let elements = document.querySelector("#exercise6-list").children;
let glowFunc = () => {
if (elements.length <= 0) { // Actually not needed since we know there are 3 elements
index = 0;
return;
}
index %= elements.length; // Map index to valid values
elements[(elements.length + index - 1) % elements.length].classList.remove("glow"); // Remove "glow" from previous element
elements[index].classList.add("glow"); // Add "glow" to current element
++index; // Increase 'index'
};
glowFunc(); // Initial call to "start" it without initial delay
setInterval(glowFunc, 3000); // Make it a timed function
li {transition: background-color 0.2s}
.glow {background-color: orange}
<ul id="exercise6-list">
<li>List 1</li>
<li>List 2</li>
<li>List 3</li>
</ul>
Array.from(element.children).forEach(child => {
child.classList.add('glow');
setTimeout(() => child.classList.remove('class'), 3000);
});
However, I think weird things will happen if you make each li glow for longer than the interval, which you have currently set to 300.
If you use something like jQuery you don't need to iterate manually.
Here is an recursive solution with async await.
var moveGlow = async function() {
var element = document.querySelector(".box");
element.style.background = "green";
await sleep(3000)
element.style.background = "red"
await sleep(3000);
return moveGlow()
}
let sleep = (time) => new Promise(res => setTimeout(res, time))
moveGlow()
.box {
width: 100px;
height: 100px;
background: red;
}
<div class="box">
</div>
Instead of setInterval I used setTimeout since interval keeps running until you stop it, where as timeout only happens once then you have to rerun it.
This code is a recursive function that uses a counter to determine where in the list the loop is currently at.
var parent = document.querySelector("#exercise6-list");
var children = parent.querySelectorAll("li");
var total = children.length;
function glow(counter) {
children.forEach(function(el) {
el.classList.remove("highlight");
});
el = children[counter];
el.classList.add("highlight");
setTimeout(function() {
if (counter < total - 1) {
counter++;
glow(counter);
}
else{
el.classList.remove("highlight");
}
}, 3000);
}
glow(0);
.highlight {
background: red;
color: #fff
}
<ul id="exercise6-list">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
var d=document.querySelector("#exercise6-list");
d.style="background-color:yellow;"
setTimeout(()=>{
d.style=""
},3000)
//time is measured in milliseconds.. simple math
<div style="background-color:yellow" id="exercise6-list">
<li>Text 1</li>
<li>Text 2</li>
<li>Text 3</li>
</div>

append / appendChild not working on all items

I'm trying to move all the list items from an list to another using only javascript but for some reason only half of them are actually moved.
Heres a working example of what I'm doing:
var results_ul = document.getElementById('results');
var stores_li = document.getElementsByClassName('store-list-item');
for (var x = 0; x < stores_li.length; x++) {
document.getElementById('hide').appendChild(stores_li[x]);
stores_li[x].className += ' teste';
}
<p>results</p>
<ul id="results">
<li class="store-list-item">Teste 1</li>
<li class="store-list-item">Teste 2</li>
<li class="store-list-item">Teste 3</li>
<li class="store-list-item">Teste 4</li>
</ul>
<p>Hide:</p>
<ul id="hide"></ul>
What seems to be the problem?
getElementsByClassName returns a live list.
When you append the element to a different element, you change its position in the list.
So it starts off as:
1 2 3 4
Then you move the first one:
2 3 4 1
Then you access the second one … but the second one is now 3 because everything has shuffled down the list.
You could copy each element into an array (which will not be a live list) and then iterate over that array to move them (so they won't change positions as you go).
Alternatively, you could use querySelectorAll which returns a non-live list.
You should better use querySelectorAll than getElementsByClassName
var results_ul = document.getElementById('results');
var stores_li = document.querySelectorAll('.store-list-item');
stores_li.forEach((item)=>{
document.getElementById('hide').appendChild(item);
item.className += ' teste';
});
<p>results</p>
<ul id="results">
<li class="store-list-item">Teste 1</li>
<li class="store-list-item">Teste 2</li>
<li class="store-list-item">Teste 3</li>
<li class="store-list-item">Teste 4</li>
</ul>
<p>Hide:</p>
<ul id="hide"></ul>
Try use querySelectorAll . It'll returns a non-live list. That's what you need.
var stores_li = document.querySelectorAll('.store-list-item');
To increase more information:
Live : when the changes in the DOM are reflected in the collection. The content suffers the change when a node is modified.
Non-Live : when any change in the DOM does not affect the content of the collection.
document.getElementsByClassName() is an HTMLCollection, and is live.
document.querySelectorAll() is a NodeList and is not live.
In your code you are removing each element from the first list and inserting into the new list. After you remove 2 elements it will have only 2 elements in the first list but now you are searching the 3 rd index in the loop which is not there. So to make it work i have prepended each element from the last.
var results_ul = document.getElementById('results');
var stores_li = document.getElementsByClassName('store-list-item');
var hide_ul = document.getElementById('hide');
for (var x = 0, y = stores_li.length; x < y; x++) {
hide_ul.insertBefore(stores_li[y-x-1],hide_ul.firstChild);
stores_li[x].className += ' teste';
}
<p>results</p>
<ul id="results">
<li class="store-list-item">Teste 1</li>
<li class="store-list-item">Teste 2</li>
<li class="store-list-item">Teste 3</li>
<li class="store-list-item">Teste 4</li>
</ul>
<p>Hide:</p>
<ul id="hide"></ul>
Or you may want to clone the element with Jquery and you can push into the clonned ones then delete the orginals from top. I could not find any equivalent of clone() for js but if you want to check link is here
var results_ul = document.getElementById('results');
var stores_li = document.getElementsByClassName('store-list-item');
while(stores_li.length>0) {
document.getElementById('hide').appendChild(stores_li[0]);
stores_li[x].className += ' teste';
}

Divide js array in 2 components

I have an array and I want to be able to divide it in 2 components, so like the first 5 elements go in one element and whatever is left go in the second element. I know I have to use slice and I can get the first component but not sure about the second one.
I have something like this:
if (item.children) {
return item.children.slice(0, 5).map((childItem) => {
const navItem = (
<Link activeClassName={styles.active} to={childItem.url}>{childItem.text}</Link>
)
return (
<li key={childItem.id}>
{navItem}
</li>
)
})
}
So how do I get another component to appear after, like
<li key={childItem.id}>
{navItem}
</li>
{navExtra}
that contains all the remaining elements from the array?
The final html should look something like this:
<ul>
<!-- // first five elements of the array -->
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<!-- // new component -->
<li>
<button>Extra</button>
<ul>
<!-- // remaning elements of the array -->
<li>Item 6</li>
<li>Item 7</li>
<!-- // etc -->
</ul>
</li>
</ul>
Variables will really help your code readability here. slice(beginIndex, endIndex) and slice(begin) are the keys.
You already know slice(0,5) will get you the first half of the array.
var x = slice(5) will get you the components of the array from the fifth element to the end. You can call map() and do any other processing on these elements to get something to display.
MSDN docs for slice
You can create an HTML element with appendChild. Here is an example of how you could do what you are trying to do:
<!DOCTYPE html>
<html>
<body>
<div id="div1">
</div>
<script>
var array = ["this", "is", "a", "random", "array", "that", "i", "just", "made"];
var splitBy = Math.round(array.length / 2);
//for first half
for (var i = 0; i <= splitBy; i++){
var li = document.createElement("li");
var node = document.createTextNode(array[i]);
li.appendChild(node);
var element = document.getElementById("div1");
element.appendChild(li);
}
//for second half
for (var i = splitBy + 1; i < array.length; i++){
var li = document.createElement("li");
var node = document.createTextNode(array[i]);
li.appendChild(node);
var element = document.getElementById("div1");
element.appendChild(li);
}
</script>
</body>
</html>
Hope this helps!
When you use splice, it alters the original array, essentially leaving you item.children with all of the elements you want to include in navExtra
We can create a return object that holds navItems and navExtra separately for you to render separately, adding the wrapping <ul> in the render, etc.
if (item.children) {
let ret = {
navItems: [],
navExtra: []
};
navItems = item.children.splice(0, 5);
ret.navItems.push(
navItems.map(navItem => {
return (
<li key={navItem.id}>
<Link activeClassName={styles.active} to={navItem.url}>{navItem.text}</Link>
</li>
)
})
)
ret.navExtra.push(
item.children.map(navExtra => {
<li key={navExtra.id}>
<Link activeClassName={styles.active} to={navExtra.url}>{navExtra.text}</Link>
</li>
})
)
}

How do I randomly apply CSS style using plain Javascript? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I have a few list entries that I would like to randomly highlight, 1 item only with different background and text color.
As shown below with "list 2" highlighted.
<div id="entries">
<ul style="list-style-type: none;">
<li>list 1</li>
<li>list 2</li>
<li>list 3</li>
<li>list 4</li>
<li>list 5</li>
</ul>
</div>
How can I achieve this using vanilla javascript without the use of jQuery?
Pretty easy (vanilla JS):
// Query entries:
var entries = document.querySelectorAll('#entries ul li a');
// Clean up entries styles
for(var i = 0; i < entries.length; i++){
var style = entries[i].style;
style.backgroundColor = null;
style.color = null;
}
// Pick random index
var randomIdx = Math.floor(Math.random() * entries.length);
// Pick random entry's style
var randomEntryStyle = entries[randomIdx].style;
// Set styles
randomEntryStyle.backgroundColor = '#000';
randomEntryStyle.color = '#fff';
JS Bin
With the following code you can get a random colour for a random element's background and text.
var
elements = document.querySelectorAll('#entries ul li a'),
index = Math.floor(Math.random() * elements.length),
props = ["color", "backgroundColor"],
colour = function() {
return "#" + (Math.random() * 0xFFFFFF << 0).toString(16);
};
for (var i = 0; i < props.length; i++) elements[index].style[props[i]] = colour();
Snippet:
/* ----- JavaScript ----- */
var
elements = document.querySelectorAll('#entries ul li a'),
index = Math.floor(Math.random() * elements.length),
props = ["color", "backgroundColor"],
colour = function() {
return "#" + (Math.random() * 0xFFFFFF << 0).toString(16);
};
for (var i = 0; i < props.length; i++) elements[index].style[props[i]] = colour();
<!----- HTML ----->
<div id="entries">
<ul style="list-style-type: none;">
<li>list 1</li>
<li>list 2</li>
<li>list 3</li>
<li>list 4</li>
<li>list 5</li>
</ul>
</div>
First of all, you are going to need all these list elements inside an array to choose from. You can accomplish this by writing:
var elements = document.getElementById("entries").getElementsByTagName("li");
better however would it be, if you gave them a class name "entry", then you could do:
var elements = document.getElementsByClassName("entry")
which would be even safer, cause you can't get any nested li-elements.
Next up, we want to choose one from it randomly. You can write the following:
var chosen = elements[Math.floor(Math.random()*elements.length)];
And now just apply the css you need:
choosen.setAttribute("style", "color:white; background-color: black");
Or, to even make it cleaner, create a css class called "chosen", define the css, and apply it to the chosen element.
Greets!

Control allowed drop locations for jQuery sortable

I have a sortable list where the user can dynamically attach items to each other. In the following example "Item 3" is attached to "Item 2". If two items are attached I want to prevent the user from dropping items between the two items ie in the example the user should not be allowed to drop an item between "Item 2" and "Item 3".
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li class="attached">Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
Is there a callback which allows me to control which drop locations are allowed? Alternatively is there a different plugin similar to jQuery sortable which can provide this functionality?
You can optionally pass sortable an items option which lets you specify which items you want to be sortable. Note that with this method you will not be able to move items two and three around.
Html
<ul id="list">
<li>Item 1</li>
<li class="attached">Item 2</li>
<li class="attached">Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
JavaScript
$("#sortable").sortable({ items: ':not(.attached)'});
Hope that gets you on the right track. Here's a working demo: http://jsfiddle.net/SPPVc/
The jQuery sortable widget does not provide the capability for controlling the allowed drop zone behaviour. The problem can however be solved hackish-ly by subclassing the the widget:
$.widget("ui.custom_list", $.ui.sortable, {
_mouseDrag: function(event) {
// copy this code from the source code of jquery.ui.sortable.js
//Rearrange
for (var i = this.items.length - 1; i >= 0; i--) {
//Cache variables and intersection, continue if no intersection
var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
if (!intersection) continue;
if(itemElement != this.currentItem[0] //cannot intersect with itself
&& this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
&& !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
&& (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true)
// add this line
&& this._allowDropping(itemElement, (intersection == 1 ? "down" : "up"))
//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
) {
this.direction = intersection == 1 ? "down" : "up";
// Rest of the function
},
_allowDropping: function(itemElement, direction) {
if(this.options.allowDropping) {
return this.options.allowDropping(itemElement, direction);
}
return true;
}
});
The _mouseDrag function is mostly copied from the sortable source. The only adjustment is the line:
&& this._allowDropping(itemElement, (intersection == 1 ? "down" : "up"))
The allowed drop zone behaviour can then be customized by providing a function for the allowDropping parameter:
$("ul").custom_list({
allowDropping: function(element, direction) {
// element refers to the item that would be moved but not the one being dragged
if(direction == "up") {
...
} else {
...
}
}
})

Categories