Clicking on some menu while others closing - javascript

<ul>
<li>Menu 1
<ul>
<li>submenu 1</li>
<li>submenu 2
<ul>
submenu 3
<li>submenu 4</li>
</ul>
</li>
</ul> Menu 2
<ul>
<li>submenu 1</li>
<li>submenu 2
<ul>
submenu 3
<li>submenu 4</li>
</ul>
</li>
</ul>
<li>
</ul>
script:
if(!Array.indexOf){
Array.prototype.indexOf = function(obj){
for(var i=0; i<this.length; i++){
if(this[i]==obj){
return i;
}
}
return -1;
}
}
function categoryAdd(id) {
var ids = new String($.cookie('expanded')).split(',');
if (ids.indexOf(id) == -1){
ids.push(id);
$.cookie('expanded', ids.join(','), {path: '/'});
}
}
function categoryRemove(id) {
var ids = new String($.cookie('expanded')).split(',');
// bug #7654 fixed
while (ids.indexOf(id) != -1) {
ids.splice(ids.indexOf(id), 1);
}
$.cookie('expanded', ids.join(','), {path: '/'});
}
$('.category_button').click(function(e){
var change = '<?= $change; ?>';
var current = $(this).attr('current');
if(change == 'on')
{
var ids = new String($.cookie('expanded')).split(',');
var exceptions = ''
for(var i = 0; i < ids.length; i++)
{
id = ids[i];
current = $('category_' + ids[i]).attr('current');
if($('category_' + ids[i]).css('display') != 'none')
{
if(id != $(this).attr('id').split('-')[1] && $(this).parent().parent().attr('id').split('-')[1] == 'undefined')
{
hideAll(id, '256');
}
}
}
}
function hideAll(id, except)
{
if(id == except){return;}
var button = $('#image-'+ id);
button.attr('src', 'catalog/view/theme/default/image/btn-expand.png');
$('#category_' + id).hide(200);
}
function showMenu(id)
{
var button = $('#image-'+ id);
button.attr('src', 'catalog/view/theme/default/image/btn-collapse.png');
$('#category_' + id).show(200);
}
function toggleMenu(e,id, current)
{
if(current == '1')
{
e.preventDefault()
var button = $('#image-'+ id);
if ($('#category_'+id).css('display') == 'none'){
button.attr('src', 'catalog/view/theme/default/image/btn-collapse.png');
categoryAdd(id);
} else {
button.attr('src', 'catalog/view/theme/default/image/btn-expand.png');
categoryRemove(id);
}
$('#category_'+id).toggle(200);
}
else
{
var button = $('#image-'+ id);
if ($('#category_'+id).css('display') == 'none'){
categoryAdd(id);
} else {
categoryRemove(id);
}
}
}
How can I make a menu where i click on some item and it opens, and others OPENED menu <ul> tags will close e.g. display: none, but also the parent menu need to not to be closed, only the menu in the same level, but not the parent, and also the brother menu of the parent, but not his parent, i think you understand what i am talking about..i really don't have an idea how to do that, what i've made before its working bad...maybe its some kind of recursion here?, but how?
any ideas?
UPDATE:
So now here we have 2 functions that are adding or deleting from the cookies the lists of the menu that has been opened/closed,
for example in the cookies we save menus with id: 100, 200, 300, 250, 160
so how can i make that in a loop closing all the menus with that ids, but not the current menu that we are clicking now, and not his parent...

This can be done using a javascript/jquery plugin you will need to just do some googling to find one. You will just need to adjust the plugin according to your specifications.Once you find plugin and try to work with that, then you could come back here if you need help. It shows more effort when you have some solid code to show you have exhausted your talents. Study some of these I think you want an accordion menu if I understand correctly. Jquery

You would probably be better off googling some different CSS menus and what not. However given your basic HTML there (provided its cleaned up, your missing a closing li tag or two) you could use the following:
jsFiddle
Script [Updated to show how i support the sub tags on the fiddle as well, keep in mind, you can edit this code to do as you please, ffor more information on how each part works, please see jQuery API]
$("ul > li > ul").hide();
$("ul > li").click(function(e) {
e.stopPropagation();
$(this).children().toggle(function(e) {
if (!$(this).is(":visible")) {
$(this).find("ul").hide();
$(this).find("sub").show();
};
});
$(this).siblings().each(function(i) {
if ($(this).children("ul").length > 0) {
if ($(this).children("ul").css("display").toLowerCase() == "block") {
$(this).children().toggle(function(e) {
if (!$(this).is(":visible")) {
$(this).find("ul").hide();
$(this).find("sub").show();
};
});
}
}
});
});
$("ul > li").each(function(i) {
if ($(this).children("ul").length > 0) {
$(this).css("cursor", "pointer").prepend($("<sub />").text("[has submenu]"));
}
else {
$(this).css("cursor", "default");
};
});
Clean HTML
<ul>
<li>Menu 1
<ul>
<li>submenu 1</li>
<li>submenu 2
<ul>
<li>submenu 3</li>
<li>submenu 4</li>
</ul>
</li>
</ul>
</li>
<li>Menu 2
<ul>
<li>submenu 1</li>
<li>submenu 2
<ul>
<li>submenu 3</li>
<li>submenu 4
<ul>
<li>subsubmenu 1</li>
<li>subsubmenu 2</li>
</ul>
</li>
</ul>
</li>
</ul>
<li>
</ul>

Related

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

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

Javascript Acting on Bullet List

I have a bulletted list like this:
<ol>
<li>Point 1</li>
<li>Point 2</li>
<li>Point 3 has sub-points
<ul>
<li>Sub-point about something</li>
<li>Sub-point about something else</li>
</ul>
</li>
</ol>
I am transliterating from one writing script to another writing script, so I have to run something against the text in each tag. Here is what my jQuery looks like:
$("li").each(function() {
text = dev2latin($(this).text());
}
It makes it look like this:
Point 1
Point 2
Point 3 has sub-points Sub-point about something
Sub-point about something else
I have tried several iterations of jQuery selectors including ol>li, etc, but none of them give the desired result. Thoughts?
The problem is calling .text() in the parent li will return the text content of the child li also
$("li").contents().each(function () {
if (this.nodeType == 3 && this.nodeValue.trim()) {
this.nodeValue = dev2latin(this.nodeValue)
}
})
Demo: Fiddle
You can fetch text nodes using .contents(), then you can .filter() them and perform desired operation.
$(document).ready(function() {
$("li")
.contents()
.filter(function() {
return this.nodeType == 3;
}) //Filter text nodes
.each(function() {
this.nodeValue = dev2latin(this.nodeValue); //You can also textContent
});
});
Here is an example:
$(document).ready(function() {
$("li").contents().filter(function() {
return this.nodeType == 3;
}).each(function() {
this.textContent = this.textContent + 'Updated';
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ol>
<li>Point 1</li>
<li>Point 2</li>
<li>Point 3 has sub-points
<ul>
<li>Sub-point about something</li>
<li>Sub-point about something else</li>
</ul>
</li>
</ol>
$('ol > li').each(function(){ console.log($(this)[0].childNodes[0]); });
$('ul > li').each(function(){ console.log($(this)[0].childNodes[0]); });
Try this way.

Reiterate only through the first 5 element using jQuery .each

does anybody know how to reiterate through only the first 5 elements using jQuery's each?
$(".kltat").each(function() {
// Only do it for the first 5 elements of .kltat class
}
From the documentation:
We can break the $.each() loop at a particular iteration by making the callback function return false.
Furthermore, the same documentation says of the callback you give to .each:
In the case of an array, the callback is passed an array index and a corresponding array value each time.
So try something like this:
$(".kltat").each(function(index, element) {
// do something
// ...
return index < 4;
});
So after you execute the loop on the fifth time (index will equal 4), the loop will stop. Note that using this n-1 logic is needed because you execute the body of the loop before you evaluate the breaking condition.
$(".kltat").each(function(index, element) {
$(element).css('color', 'red');
return index < 4;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="kltat">Item 1</li>
<li class="kltat">Item 2</li>
<li class="kltat">Item 3</li>
<li class="kltat">Item 4</li>
<li class="kltat">Item 5</li>
<li class="kltat">Item 6</li>
<li class="kltat">Item 7</li>
</ul>
You can implement a counter such as this:
var counter = 1;
$(".kltat").each(function() {
// Only do it for the first 5 elements of .kltat class
if (counter==5) {
return false;
} else {
counter++;
}
}
or something of this sort.
How about using .filter():
$(".kltat").filter(function (i) { return i < 5; })
.each(function () {
// ...
});
$(".kltat").slice(0,5).each(function() {
// Only do it for the first 5 elements of .kltat class
})
And without jquery:
[].slice.call(document.querySelectorAll('.kltat')).slice(0,5).forEach(function (element) {
doStuff(element)
})

Cycling through a jQuery object with elements grouped together

I have a set of list items that contain nested lists, sort of like this:
<ul class="searchselectors">
<li class="group">Group name 1
<ul>
<li class="item selected">List item 1.1</li>
<li class="item">List item 1.2</li>
<li class="item">List item 1.3</li>
</ul>
</li>
<li class="group">Group name 2
<ul>
<li class="item">List item 2.1</li>
<li class="item">List item 2.2</li>
<li class="item">List item 2.3</li>
</ul>
</li>
<li class="group">Group name 3
<ul>
<li class="item">List item 3.1</li>
</ul>
</li>
</ul>
I want to cycle through all of the .item elements using up/down arrow keys (which I already have set up by using on('keydown') and catching key codes 38 and 40) and set .selected on the next item before or after the currently selected item, wrapping around to the top/bottom as necessary.
Using $().next() and $().prev() will not work, since it will only work on siblings, and not on a jQuery object such as $('.searchselectors .item').\
I was working on the same problem but in my project I'm using KnockoutJS. In fact, the original logic was written with pure jQuery and I refactored it with Knockout. Here's a solution for your problem using jQuery:
Fiddle - http://jsfiddle.net/6QN77/2/
I didn't spend too much time cleaning up the JavaScript, but I'm leaving that to you now.
HTML
<ul id="searchselectors">
<li class="group">Group name 1
<ul>
<li class="item selected">List item 1.1</li>
<li class="item">List item 1.2</li>
<li class="item">List item 1.3</li>
</ul>
</li>
<li class="group">Group name 2
<ul>
<li class="item">List item 2.1</li>
<li class="item">List item 2.2</li>
<li class="item">List item 2.3</li>
</ul>
</li>
<li class="group">Group name 3
<ul>
<li class="item">List item 3.1</li>
</ul>
</li>
</ul>
jQuery
$(function () {
var $menu = $("#searchselectors"),
$items = $menu.find(".item"),
$selectedItem = $menu.find(".selected"),
selectedIndex = $selectedItem.length - 1;
$(document).on("keydown", function (e) {
switch(e.keyCode) {
case 40: // down arrow
$selectedItem.removeClass("selected");
selectedIndex = (selectedIndex + 1) % $items.length;
$selectedItem = $items.eq(selectedIndex).addClass("selected");
break;
case 38: // up arrow
$selectedItem.removeClass("selected");
selectedIndex = (selectedIndex - 1) % $items.length;
$selectedItem = $items.eq(selectedIndex).addClass("selected");
break;
}
});
});
UPDATE: My solution revolves around getting a reference to a wrapper element (#searchselectors) and getting all the LI elements marked with the CSS class .item. Next I get a reference to the currently selected element and its index. Finally, the code listens to the down and up arrow keys being pressed (keydown), decrementing if going up and incrementing if going up. Cycling is achieved via the modulus operator. The selected item's CSS class is removed and put back. The selected item reference is used for performance reasons so I don't have to write $items.removeClass(".selected").eq(selectedIndex).addClass("selected");
In my quest to provide native JS answers to those looking for it, here is #Mario j Vargas' good answer adapted in native Javascript. It only takes 2 lines of extra code.
http://jsfiddle.net/kevinvanlierde/7tQSW/2/
Only putting the JS up here, HTML is the same.
(function () {
var $menu = document.getElementById('searchselectors'),
items = $menu.getElementsByClassName('item'),
$selectedItem = $menu.getElementsByClassName('selected')[0],
selectedIndex = Array.prototype.indexOf.call($selectedItem, items)+1;
document.body.addEventListener('keydown', function (e) {
switch(e.keyCode) {
case 40: // down arrow
$selectedItem.className = $selectedItem.className.replace(' selected','');
selectedIndex = selectedIndex < items.length - 1 ? selectedIndex + 1 : selectedIndex;
$selectedItem = items[selectedIndex];
$selectedItem.className += ' selected';
break;
case 38: // up arrow
$selectedItem.className = $selectedItem.className.replace(' selected','');
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : selectedIndex;
$selectedItem = items[selectedIndex];
$selectedItem.className += ' selected';
break;
}
}, false);
}());
Easiest way to get all the list items with class "item" is:
var items = $('.item');
for (var i = 0; i < items.length; i++) {
var item = items[i]; // iterate
}
And then, you can select the next item from the list
OR
you can do this
if (!$().next) {
var nextUL= $().parent.find('ul')
iterateThruList(nextUL);
}
You could use something like
var UL = $(".searchselectors").children().length; //number of UL (group names)
to iterate through items.

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