I have a page with an unordered list in two columns. After I click on one, it enlarges so there's only one column for it and there is a space on a row where it was so I wanted to move the next li before it, so there wouldn't be that space.
The picture shows divs before click (they are not empty, I just erased the content for this purpose), how it changes after click on the div with li index 1 and how I would like to swap the li with indexes 1 and 2.
I found some solutions but nothing has worked for me. I ended up with:
function swap(n) {
var l = n.ancestor("li.msg-box-wrapper");
var m = n.ancestor("#doubleColumnList").all("li.msg-box-wrapper");
var k = m.indexOf(n.ancestor("li.msg-box-wrapper"));
if ((k%2 != 0)) {
$(l).before( $(l).next() );
}
}
def swap(A,B,i,j): TEMP_B = B[j] B[j] = A[i] A[i] = TEMP_B return A,B X = np. array([[1.25,3],[1.5,2],[2,2.75],[2.25,2],[2,0.5],[3.25,0.75], [3.5,2.25],[4.25,0.75]]) Y = np. array([[2.75,3.5],[3.25,3],[4.5,2.75],[3.5,4.75]]) X,Y = swap(X,Y,1,1) OUTPUT::: Temp = [ 3.25 3. ]
To swap two list elements x and y by value, get the index of their first occurrences using the list. index(x) and list. index(y) methods and assign the result to variables i and j , respectively. Then apply the multiple assignment expression lst[i], lst[j] = lst[j], lst[i] to swap the elements.
The trick to get this to work, is realising that the new position of the active element should be the "first-in-line" from it's current position.
To find an element that is first-in-line simply look for an element that is either:
candidate.offset().left < candidate.prev().offset().left
)So the following will work:
on activation (click) note the current position, and
on de-activation simply move every active element back to it's original position.
For ease-of-use I've rewritten my original answer as a jquery plugin. As I couldn't find a good name, it's currently called foobar
.
usage:
// '.wrapper' is the element containing the *toggle-able* elements.
$('.wrapper').foobar({
// the element-selector
elements: 'li',
// the toggle-selector (if a *deeper* element should be used to toggle state)
triggerOn: '.toggle',
// indicates an active element
activeClass: 'active',
// get's called on activation [optional]
onActivate: function ($el) {
console.log('activating', $el);
},
// get's called on de-activation [optional]
onDeactivate: function ($el) {
console.log('de-activating', $el);
}
});
the plugin:
(function ($, pluginName) {
'use strict';
/**
* Plugin behavior
*/
$.fn[pluginName] = function (options) {
var settings = $.extend(true, {}, $.fn[pluginName].defaults, options);
// triggerOn-selector is required
if (null === settings.triggerOn) {
throw 'the `triggerOn` must be set.';
}
// without an element-selector
if (null === settings.elements) {
// use triggerOn-selector as default
settings.elements = settings.triggerOn;
}
// apply behavior to each element in the selection
return this.each(function() {
var
$wrapper = $(this),
$elements = $wrapper.find(settings.elements)
;
$wrapper.on(settings.event, settings.triggerOn, function () {
var
$el = $(this).closest(options.elements),
isActive = $el.hasClass(settings.activeClass)
;
reset($elements, settings.activeClass, settings.onDeactivate);
if (!isActive) {
activate($el, $elements, settings.activeClass, settings.onActivate);
}
});
});
};
/**
* Plugin defaults
*/
$.fn[pluginName].defaults = {
// required
triggerOn: null,
// defaults
elements: null,
event: 'click',
activeClass: 'active',
onActivate: function () {},
onDeactivate: function () {}
};
/**
* Reset all currently active elements
*
* @param {jQuery} $elements
* @param {String} activeIndicator
* @param {Function} onDeactivate
*/
function reset($elements, activeIndicator, onDeactivate)
{
$elements
.filter(function () {
return $(this).hasClass(activeIndicator);
})
.each(function () {
deactivate($(this), $elements, activeIndicator, onDeactivate);
})
;
}
/**
* Deactivate the given element by moving it back to it's original position and removing the active-indicator.
*
* @param {jQuery} $el
* @param {jQuery} $elements
* @param {String} activeIndicator
* @param {Function} onDeactivate
*/
function deactivate($el, $elements, activeIndicator, onDeactivate)
{
var originalIndex = $el.index();
$el.removeClass(activeIndicator).insertBefore(
$elements.eq(originalIndex)
);
onDeactivate($el);
}
/**
* Activate the given element by moving it to a suitable position while applying the required indicator.
*
* @param {jQuery} $el
* @param {jQuery} $elements
* @param {String} activeIndicator
* @param {Function} onActivate
*/
function activate($el, $elements, activeIndicator, onActivate)
{
$el
.insertAfter(
$elements.eq(findSuitablePosition($elements, $el.index()))
)
.addClass(activeIndicator)
;
onActivate($el);
}
/**
* @param {jQuery} $elements
* @param {Number} originalIndex
*/
function findSuitablePosition($elements, originalIndex)
{
// short-circuit simple case
if (0 === originalIndex) {
return originalIndex;
}
var
candidateIndex = originalIndex,
lim = $elements.length,
$candidate
;
for (; candidateIndex < lim; candidateIndex += 1) {
$candidate = $elements.eq(candidateIndex);
if ($candidate.offset().left < $candidate.prev().offset().left) {
return candidateIndex;
}
}
throw 'could not find a suitable position.';
}
})(jQuery, 'foobar');
Demo: http://plnkr.co/edit/8ARXgq2pLSzm9aqHI8HL?p=preview
original answer:
The following will work, if you're willing to use jQuery.
It's a bit more complicated than need be, but this way it works for more than two columns as well. Note the code style is so that it can be easily followed.
$('.wrapper').each(function () {
var $wrapper = $(this);
$wrapper.on('click', 'li', function () {
var
$el = $(this),
isOpen = $el.is('.open')
;
reset();
if (!isOpen) {
open($el);
}
});
function open($el)
{
var originalIndex = $el.index();
// note index and move to suitable position
$el
.data('original-index', originalIndex)
.insertAfter(
$wrapper.find('li').eq(findSuitablePosition(originalIndex))
)
.addClass('open')
;
}
function reset()
{
$wrapper.find('.open').each(function () {
var
$el = $(this),
originalIndex = $el.data('original-index')
;
$el.removeClass('open').insertBefore(
$wrapper.find('li').eq(originalIndex)
);
});
}
function findSuitablePosition(originalIndex)
{
// short-circuit simple case
if (0 === originalIndex) {
return originalIndex;
}
var
$candidates = $wrapper.find('li'),
candidateIndex = originalIndex,
lim = $candidates.length,
candidate
;
for (; candidateIndex < lim; candidateIndex += 1) {
candidate = $candidates.eq(candidateIndex);
if (candidate.offset().left < candidate.prev().offset().left) {
return candidateIndex;
}
}
throw 'could not find a suitable position.';
}
});
ul {
list-style: none;
margin: 0;
padding: 5px 10px;
width: 300px;
border: 1px solid #ccc;
overflow: hidden;
font-family: sans-serif;
margin-bottom: 5px;
}
li {
float: left;
margin: 10px 5px;
padding: 3px;
border: 1px solid #ccc;
box-sizing: border-box;
}
ul li.open {
width: calc(100% - 10px);
height: 40px;
border-color: green;
}
.two li {
width: calc(50% - 10px);
}
.three li {
width: calc(33% - 10px);
}
.four li {
width: calc(25% - 10px);
}
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<ul class="wrapper two">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<ul class="wrapper three">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
</ul>
<ul class="wrapper four">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
<li>11</li>
<li>12</li>
</ul>
In case you wanted an answer that doesn't use jQuery here is one such solution:
var list = document.getElementById('list');
var listItems = list.children;
function select(e) {
// Remove the selected class from the previously selected list item
var selectedEl = document.querySelector('.selected');
if (selectedEl) {
selectedEl.classList.remove('selected');
}
// Add the selected class to the current list item
var targetEl = e.target;
targetEl.classList.add('selected');
// Find the current li's position in the node list
var targetPosition = Array.prototype.indexOf.call(listItems, targetEl);
// If it is in an odd position, and there is a sibling after it
// move that sibling before it
if (targetPosition % 2 > 0 && targetEl.nextElementSibling) {
list.insertBefore(targetEl.nextElementSibling, targetEl);
}
}
// Add click listeners
for(var i = 0, len = listItems.length; i < len; i++) {
listItems[i].addEventListener('click', select);
}
ul {
position: relative;
width: 400px;
}
li {
text-align: center;
float: left;
display: block;
height: 20px;
width: 190px;
margin: 5px;
background: red;
}
.selected {
width: 390px;
}
<ul id="list">
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
The crux of the solution is the call to Node.insertBefore using the clicked list item as the reference node.
Codepen version
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With