交换两个<li>元素

10

我有一个带有两列无序列表的页面。当我点击其中一个列表项时,它会放大,只有一列,并且在其位置上有一个空白行,我想把下一个li移到它的前面,这样就不会有那个空白了。

图片显示了点击之前的divs(它们不是空的,我只是为了这个目的而擦除了内容),以及在单击li索引1的div后如何更改以及我想交换索引1和2的li。

我找到了一些解决方案,但都没有对我起作用。我最终得到了:

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

我已经尝试了一个解决方案。希望它能解决你的问题。请查看jsfiddle - Abhisek Malakar
我不禁认为这只是一个糟糕的想法,最终会让你的用户感到困惑。我不会期望页面因为一个元素增加了大小而被彻底重新排序。 - Anthony Grist
应该有两列,当其中一列只填了一半时,它看起来很奇怪。 - Mish.k.a
4个回答

12
要让它起作用的诀窍是意识到活动元素的新位置应该是其当前位置的“第一行”。
要查找第一行的元素,只需查找以下元素之一:
- 第一个子元素; - 具有左偏移小于其前一个兄弟(至少在从左到右的语境中)。(`candidate.offset().left < candidate.prev().offset().left`)
因此,以下内容将起作用:
- 在激活(点击)时,记录当前位置,并查找下一个“第一行”的元素(包括单击的元素)。 - 交换这两个元素。 - 在取消激活时,将每个活动元素移回其原始位置。
为了方便使用,我已将原始答案重写为 jQuery 插件。由于我找不到一个好名字,它目前被称为 `foobar`。
用法:
// '.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);
  }
});

插件:

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

以下内容是关于it技术的,如果您愿意使用jQuery,以下代码可以帮助您实现目标。虽然这种方法比较复杂,但是它适用于超过两列的情况。请注意,代码样式易于跟踪。此外,以下是演示链接:http://plnkr.co/edit/8ARXgq2pLSzm9aqHI8HL?p=preview

$('.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>


3

如果你想要一个不使用jQuery的答案,这里有一个解决方案:

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>

解决方案的关键是使用被点击列表项作为参考节点调用 Node.insertBefore

Codepen 版本


0

你可以轻松地使用jQuery来实现这一点。

我为你创建了一个JSFiddle 在这里

这个想法是定义两个类,一个用于小矩形,一个用于大矩形。然后,在单击小矩形之一时,将所有矩形设置为小的,并将单击的矩形设置为大的。 然后,如果必要,重新排序列表。

HTML

<ul class="container">
 <li class="small item">1</div>
 <li class="small item">2</div>
 <li class="small item">3</div>
 <li class="small item">4</div>
 <li class="small item">5</div>
 <li class="small item">6</div>
</ul>

CSS

.container{
width: 130px;
list-style-type: none;
}
.small{
margin: 5px;
width: 50px;
height: 20px;
background-color: red;
float: left;
}
.big{
margin: 5px;
width: 110px;
height: 60px;
background-color: red;
float: left;
}

Jquery:

$(function(){

//ON LI CLICKED
$(".small").click(function() {

    //LI REMOVE BIG ONE
    $( ".item" ).removeClass( "big" ).addClass( "small" );

    //ADD BIG CLASS
    $(this).removeClass( "small" ).addClass( "big" );

    //IF NECESSARY THAN REORDER
    if($( "li" ).index( $(this) )%2==1){
        alert("reorder");
        //CHANGE INDEX OF LI
        $(this).siblings().eq(1).after(this);
    }
});

});

点击“6”会有一些奇怪的行为。 - bezmax
我已经在做类似的事情了 - 我有解包和封装的类,可以改变大小,但是我仍然无法改变索引。 - Mish.k.a
1
.addClass.removeClass函数适用于整个匹配元素集合;无需使用.each()显式迭代$(".item") - Anthony Grist

0

你可以检查被点击的元素是否在奇数位置,然后在它之前插入下一个元素。检查一下:

$('.box').on('click', function() {
    if($(this).index()%2 != 0) {
        $(this).next().insertBefore($(this));
    }
    $('.box').removeClass('fullwidth');
    $(this).addClass('fullwidth');
});

工作演示

更新:添加了一个sorter()函数,以按其原始位置对所有div进行排序。

$('.box').each(function(i) {
    $(this).attr('i', i);
});
function sorter() {
    for(var x=0; x<$('.box').length; x++) {
        $('.box').each(function() {
            if($(this).attr('i') > $(this).next().attr('i')) {
                $(this).insertAfter($(this).next());
            }
        });
    }
}

每当点击.box时,首先调用它。

更新的 FIDDLE


@MiškaKrížová,请查看我的更新答案,其中我添加了排序所有元素的代码。 - Rohit Kumar
@MiškaKrížová,您是否会同时打开多个div?如果不是,则可以使用以下链接:http://jsfiddle.net/2dzt7kym/3/ - Rohit Kumar
@RohitKumar 从不 - 我可以打开一个或零个。 - Mish.k.a
@RohitKumar 所以...在关闭 div 后,它是错误的,直到单击某个 div - 然后它跳到正确的位置。 - Mish.k.a
@RohitKumar 我在调用close函数的地方添加了一个sorter(),现在它立即正常工作。 - Mish.k.a
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接