Control allowed drop locations for jQuery sortable - javascript

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 {
...
}
}
})

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

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.

Hide parent div if list is empty

I have the following....
<div class="validationbox">
<h1>Errors</h1>
<ul class="validationerrors">
<li>Error 1</li>
<li>Error 2</li>
<li>Error 3</li>
</ul>
</div>
The error list items are generated dynamically so sometimes there are none, I would like to hide the 'validationbox' div if there are no list items.
I imagine it is javascript or jquery that I should be looking at, does anyone have any examples?
In jQuery you can do it as simple as:
$(".validationbox:not(:has(li))").hide();
In pure JavaScript you need to iterate ".validationbox" elements and search for <li> nodes inside:
var div = document.getElementsByClassName("validationbox");
for (var i = 0, len = div.length; i < len; i++) {
if (!div[i].getElementsByTagName("li").length) {
div[i].style.display = "none";
}
}
$('.validationbox').toggle( $('.validationerrors li').length );
toggle() accepts a boolean value as an argument. $('.validationerrors li').length will evaluate to false if there is no <li> elements, else true, which will show the error list.
you can use not .
$('div .validationbox').not(':has(li)').hide();
hope it's help to you

How do I simulate hover with Javascript on keydown?

First of, I'd like to use only native JavaScript to complete this task.
Let's say I am to make a custom dropdown, and the HTML code looks kind of like this.
<div class="dropdown">
<span class="dropdown-label" style="display:block">Select a thing</span>
<ul class="dropdownItemContainer">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
</ul>
</div>
In the CSS file I have something close to this:
ul.dropdownItemContainer li:hover {
background-color: #FF0000;
}
Yeah, there's really no dropdownish behavior, but it's not the point of discussion actually. The problem is that I couldn't think of a decent way to enable keyboard control for this dropdown. The desired outcome is the following: I press the down key, and the first option is highlighted; I press it again, and the second option is highlighted and so on.
The only option that I see at this point (just started studying JS) is to fetch all of the ul's children, stick'em into an array and assign the tags a background color through JS methods in a proper way whenever the down key is pressed.
On the other hand, I still have the :hover behavior described in the CSS for mouse countrol. Is there a smart way of simulating hovers?
I would go with a simple assignment of a class on your li-elements and steer it with a keydown handler. The following code is not meant to be complete but give you something you can work with.
var active = document.querySelector(".hover") || document.querySelector(".dropdownItemContainer li");
document.addEventListener("keydown",handler);
document.addEventListener("mouseover",handler);
function handler(e){
console.log(e.which);
active.classList.remove("hover");
if (e.which == 40){
active = active.nextElementSibling || active;
}else if (e.which == 38){
active = active.previousElementSibling || active;
}else{
active = e.target;
}
active.classList.add("hover");
}
You can see a working example here
You may want to use a library instead of coding this from scratch.
http://vebersol.net/demos/jquery-custom-forms/
http://www.dreamcss.com/2009/05/15-jquery-plugins-to-enhance-your-html.html
I would suggest removing the hover attribute from css.
And add only a hovered class which is applied on keypresses and on mouseover
This could look like this in Code
var dropDown = document.getElementsByClassName("dropdownItemContainer")[0]
document.addEventListener("keydown",function (e) {
if(e.keyCode == 38 || e.keyCode == 40 ) {
var key = e.keyCode
var hovered = dropDown.getElementsByClassName("hovered")
if(hovered.length != 0 ) {
cur = hovered[0]
cur.className = ""
cur = cur[(key==38?"previous":"next")+"ElementSibling"] || dropDown.children[key==38?dropDown.children.length-1:0]
} else {
cur = dropDown.children[key==38?dropDown.children.length-1:0]
}
cur.className="hovered"
}
});
dropDown.addEventListener("mouseover",function (e) {
for( var i = 0,j; j = dropDown.getElementsByClassName("hovered")[i];i++)
j.className = "";
e.srcElement.className = "hovered";
});
Heres an example on JSFiddle
Reality you didn't need any js for dropdown but You can use JavaScript Event for simulating it. You can use event like hover, focus, onclick
In JS You Can use This For Set Event
document.getElementById('id').addEventListener('focus',function(e){
//place code that want ran at event happened
}
In JQuery you can use bind, click ,...
$('#id')bind('focus',function(e){
//place code that want ran at event happened
}
List of Event
http://www.quirksmode.org/dom/events/index.html

Clicking on some menu while others closing

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

Categories