jQuery 输入时的可排序更新输出

jQuery Sortable update output on input

我们使用 jQuery Sortable 库来进行类似于 WordPress 的动态菜单管理。并且多级管理。

<ol id="my-nav">
    <li data-id="unique-id-here" data-label="">
        <span>My Label</span>
        <input type="text" class="label-change">
        <ol><!-- PLACEHOLDER FOR SUBMENU --></ol>
    </li>
</ol>

<textarea id="output"></textarea>

我们想使用继承的输入字段更新菜单标签。所以我们做了如下的事情:

$('body').on('keyup change', '.label-change', function () {
    var this_menu_label_field = $(this);
    var this_field_val        = this_menu_label_field.val();
    var this_menu_nav         = this_menu_label_field.parents('li');

    // Update the text string inside the <li>
    this_menu_nav.find('span').html(this_field_val);

    // Update the data-label attribute
    this_menu_nav.attr('data-label', this_field_val).sortable('refresh');

    var serialized_data = menu_container.sortable('serialize').get();
    $('#output').val(JSON.stringify(serialized_data));
});

代码正在更新相应 <li><span> 中的字符串,并且还会更改 data-label。但不幸的是,它只更新了进入 #output 文本区域的第一次击键。

例如:如果我们键入 "Whatever",它可能需要 "W" 或 "Wha"。并且没有进一步的击键更新到 #output。但是 <span>data-label 中的更新总是工作正常。

我们非常需要这种功能。但是我们怎样才能做到这一点呢?

演示版Fiddle

https://jsfiddle.net/mayeenulislam/Lsrgu0qy/38/

我 fiddle 研究了这个并且能够让它工作。在此处检查我的分叉 fiddle:jsfiddle.net/qf7n89oe

我做的唯一改变是替换

this_menu_nav.attr('data-label', this_field_val).sortable('refresh');

this_menu_nav.data('label', this_field_val).sortable('refresh');

似乎使用 attr() 会导致一些缓存问题,所以我将其替换为 data() 似乎没有这个问题。

jQuery Sortable 将数据存储在数据对象中。所以它不处理 data-* 属性。如果您想使用 jQuery 数据对象,您可以使用 Kodos Johnson 回答。另一种方法是拥有自己的序列化函数定义。看这个例子:

/**
 * ---------------------------------------
 * OUR CODE STARTS HERE
 * ---------------------------------------
 */

jQuery(document).ready(function($) {
  var menu_container = $('#my-nav');
  var menu_data_field = $('#output');

  var sortable = menu_container.sortable({
    delay: 500,
    group: 'serialization',
    onDrop: function($item, container, _super) {
      var data = sortable.sortable('serialize').get();

      var jsonString = JSON.stringify(data);

      menu_data_field.val(jsonString);
      _super($item, container);
    },
    serialize: function($parent, $children, parentIsContainer) {
      let result = $parent.map(function(e) {
        let attr = {};
        $.each(this.attributes, function() {
          const name = this.name.replace("data-", "");
          attr[name] = this.value;
        });
        return attr;
      });

      if (parentIsContainer)
        return [$children]
      else if ($children[0]) {
        result[0].children = $children
      }

      delete result.subContainers
      delete result.sortable

      return result.get();
    },
  });

  $('body').on('keyup', '.label-change', function() {
    var this_menu_label_field = $(this);
    var this_field_val = this_menu_label_field.val();
    var this_menu_nav = this_menu_label_field.parent('li');

    // Update the text string inside the <li>
    this_menu_nav.find('> span').html(this_field_val);

    // Update the data-label attribute
    this_menu_nav.attr('data-label', this_field_val).sortable('refresh');

    var serialized_data = menu_container.sortable('serialize').get();
    console.log(serialized_data);
    $('#output').val(JSON.stringify(serialized_data));
  });
});
body.dragging,
body.dragging * {
  cursor: move !important;
}

input[type="text"] {
  border-color: red;
}

#my-nav {
  padding-left: 0;
  list-style-type: none;
  overflow: hidden;
}

#my-nav ol {
  padding-left: 0;
  margin-left: 20px;
  list-style-type: none;
}

#my-nav li {
  padding: 5px 10px;
  background-color: #f8f8f8;
  color: #333;
  border: 1px solid #999;
  border-radius: 3px;
  margin-bottom: 10px;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
  cursor: move;
}

#my-nav li.placeholder {
  position: relative;
  border-style: dashed;
  background-color: #ededed;
  min-height: 34px;
}

#my-nav li.placeholder:before {
  position: absolute;
}

#my-nav li.dragged {
  position: absolute;
  top: 0;
  opacity: .5;
  z-index: 2000;
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
}

#my-nav li.dragged i.icon-move {
  color: var(--primary);
}

#my-nav li.highlight {
  background: gray;
  color: lightgray;
}

#my-nav li:first-of-type {
  margin-top: 10px;
}

#my-nav i.icon-move {
  cursor: pointer;
  color: #999;
  padding: 5px;
}

.dd {
  position: relative;
  display: block;
  margin: 0;
  padding: 0;
  max-width: 600px;
  list-style: none;
  font-size: 13px;
  line-height: 20px;
}

.dd-list {
  display: block;
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none;
}

.dd-list .dd-list {
  padding-left: 30px;
}

.dd-item,
.dd-empty,
.dd-placeholder {
  display: block;
  position: relative;
  margin: 0;
  padding: 0;
  min-height: 20px;
  font-size: 13px;
  line-height: 20px;
}

.dd-handle {
  display: block;
  height: 30px;
  margin: 5px 0;
  padding: 5px 10px;
  color: #333;
  text-decoration: none;
  font-weight: bold;
  border: 1px solid #ccc;
  background: #fafafa;
  border-radius: 3px;
  box-sizing: border-box;
}

.dd-handle:hover {
  color: #2ea8e5;
  background: #fff;
}

.dd-item>button {
  position: relative;
  cursor: pointer;
  float: left;
  width: 25px;
  height: 20px;
  margin: 5px 0;
  padding: 0;
  text-indent: 100%;
  white-space: nowrap;
  overflow: hidden;
  border: 0;
  background: transparent;
  font-size: 12px;
  line-height: 1;
  text-align: center;
  font-weight: bold;
}

.dd-item>button:before {
  display: block;
  position: absolute;
  width: 100%;
  text-align: center;
  text-indent: 0;
}

.dd-item>button.dd-expand:before {
  content: '+';
}

.dd-item>button.dd-collapse:before {
  content: '-';
}

.dd-expand {
  display: none;
}

.dd-collapsed .dd-list,
.dd-collapsed .dd-collapse {
  display: none;
}

.dd-collapsed .dd-expand {
  display: block;
}

.dd-empty,
.dd-placeholder {
  margin: 5px 0;
  padding: 0;
  min-height: 30px;
  background: #f2fbff;
  border: 1px dashed #b6bcbf;
  box-sizing: border-box;
  -moz-box-sizing: border-box;
}

.dd-empty {
  border: 1px dashed #bbb;
  min-height: 100px;
  background-color: #e5e5e5;
  background-size: 60px 60px;
  background-position: 0 0, 30px 30px;
}

.dd-dragel {
  position: absolute;
  pointer-events: none;
  z-index: 9999;
}

.dd-dragel>.dd-item .dd-handle {
  margin-top: 0;
}

.dd-dragel .dd-handle {
  box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1);
}

.dd-nochildren .dd-placeholder {
  display: none;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-sortable/0.9.13/jquery-sortable-min.js"></script>

<ol id="my-nav">
  <li data-id="unique-id-here" data-label="My Label">
    <span>My Label</span>
    <input type="text" class="label-change form-control" placeholder="Type here to see the impact">
    <ol>
      <li data-id="unique-id-here" data-label="My Sub Label">
        <span>My Sub Label</span>
        <input type="text" class="label-change form-control" placeholder="Type here to see the impact">
        <ol>
          <!-- PLACEHOLDER FOR SUBMENU -->
        </ol>
      </li>
    </ol>
  </li>
</ol>

<h4>OUTPUT HERE</h4>
<textarea id="output" class="form-control" placeholder="">[[{"label":"My Label","id":"unique-id-here","children":[[{"label":"My Sub Label","id":"unique-id-here","children":[[]]}]]}]]</textarea>