Двумерный нокаут сортируемый не обновляющий интерфейс

Я создаю двумерный сортируемый контейнер с первым измерением (строки в таблице) и вторым измерением (ячейки в строке).

Ячейки должны перетаскиваться внутри строки, в существующие строки, в новые строки, создаваемые динамически. Пустые строки должны быть удалены динамически. Ячейки настроены так, чтобы занимать все пространство подряд.

Как редактировать пользовательскую сортируемую привязку Knockout (например, update событие)?

До:

До

После:

После

Проблемы с обновлением:

  • При перетаскивании ячейки (.sortable-cell) в новый ряд (.sortable-table / .sortable-row) viewModel обновляется, но не пользовательский интерфейс
  • Заполнитель (.highlight-horizontal) не отображается при перетаскивании ячейки (.sortable-cell) в новый ряд (.sortable-table / .sortable-row)

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    $(element).data("sortList", valueAccessor().data); //attach meta-data
    $(element).sortable({
      placeholder: valueAccessor().placeholder,
      start: function(event, ui) {},
      change: function(event, ui) {},
      update: function(event, ui) {
        var item = ui.item.data("sortItem");
        if (item) {
          //identify parents
          var originalParent = ui.item.data("parentList");
          var newParent = ui.item.parent().data("sortList");
          //identify viewModel
          var viewModel = bindingContext.$root;
          //figure out its new position
          var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);

          if (ui.item.parent()[0].classList.contains("sortable-row")) {
            //Row already exists
            console.log("true");
          } else {
            //Row doesn't exist, create new row (PROBLEM WITH UPDATE HERE)
            newParent().splice(position, 0, {
              "children": ko.observableArray([])
            });
            newParent = newParent()[position].children;
          }

          //Update item position
          originalParent.remove(item);
          newParent.splice(position, 0, item);
          
          //Remove empty lists
          var children = viewModel.children();
          for (var i = 0; i < children.length; i++) {
            if (children[i].children().length == 0) {
              viewModel.children.remove(children[i]);
              console.log(children);
            }
          }

          //Update UI
          ui.item.remove();

          //Debug data model
          console.log("final viewModel");
          var children = viewModel.children();
          for (var i = 0; i < children.length; i++) {
            console.log(children[i].children());
            for (var j = 0; j < children[i].children().length; j++) { 
             console.log(children[i].children()[j].children(),children[i].children()[j].content());
            }
          }
        }

      },
      connectWith: '.sortable-container'
    });
  }
};
//attach meta-data
ko.bindingHandlers.sortableItem = {
  init: function(element, valueAccessor) {
    var options = valueAccessor();
    $(element).data("sortItem", options.item);
    $(element).data("parentList", options.parentList);
  }
};
var self = this;
var viewModel = function() {
  var self = this;
  self.children = ko.observableArray(
    [{
      "children": ko.observableArray([{
        "content": ko.observable("Item 1"),
        "children": ko.observableArray([])
      }, {
        "content": ko.observable("Item 2"),
        "children": ko.observableArray([])
      }, {
        "content": ko.observable("Item 3"),
        "children": ko.observableArray([])
      }])
    }, {
      "children": ko.observableArray([{
        "content": ko.observable("Item 4"),
        "children": ko.observableArray([])
      }])
    }, {
      "children": ko.observableArray([{
        "content": ko.observable("Item 5"),
        "children": ko.observableArray([])
      }, {
        "content": ko.observable("Item 6"),
        "children": ko.observableArray([])
      }])
    }]
  );
};
ko.applyBindings(new viewModel());
.sortable-table {
  border: 1px red solid;
  padding: 10px 0px;
  list-style-type: none;
  width: 100% !important;
  display: table !important;
}
.sortable-table .sortable-row {
  height: 100% !important;
  display: table-row !important;
  padding: 5px 0px;
}
.sortable-table .sortable-cell {
  border: 1px solid green;
  display: table-cell !important;
  cursor: move;
}
.sortable-table .sortable-cell p {
  display: inline;
  margin: 0 !important;
}
.sortable-table .highlight-vertical {
  width: 5px !important;
  display: table-cell !important;
  background-color: blue !important;
}
.sortable-table .highlight-horizontal {
  height: 5px !important;
  width: 100% !important;
  display: block !important;
  background-color: blue !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>

<div class="sortable-container" data-bind="template: { name: 'rowTmpl', foreach: $data.children, templateOptions: { parentList: $data.children } }, sortableList: { data: $data.children, placeholder: 'highlight-horizontal' }">
</div>

<script id="rowTmpl" type="text/html">
  <div class="sortable-table">
    <div class="sortable-row sortable-container" data-bind="template: { name: 'cellTmpl', foreach: $data.children, templateOptions: { parentList: $data.children } }, sortableList: { data: $data.children, placeholder: 'highlight-vertical' }">
    </div>
  </div>
</script>

<script id="cellTmpl" type="text/html">
  <div class="sortable-cell" data-bind="sortableItem: { item: $data, parentList: $item.parentList }">
    <p data-bind="text: $data.content"></p>
  </div>
</script>

1 ответ

Решение

Проблема была на линии newParent.splice(position, 0, {"children": ko.observableArray([])});, newParent был назван как newParent(), который был причиной проблемы.

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    $(element).data("sortList", valueAccessor().data); //attach meta-data
    $(element).sortable({
      placeholder: valueAccessor().placeholder,
      start: function(event, ui) {},
      change: function(event, ui) {},
      update: function(event, ui) {
        var item = ui.item.data("sortItem");
        if (item) {
          //identify parents
          var originalParent = ui.item.data("parentList");
          var newParent = ui.item.parent().data("sortList");
          //identify viewModel
          var viewModel = bindingContext.$root;
          //figure out its new position
          var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);

          if (ui.item.parent()[0].classList.contains("sortable-row")) {
            //Row already exists
            console.log("true");
          } else {
            //Row doesn't exist, create new row (PROBLEM WITH UPDATE HERE)
            newParent.splice(position, 0, {
              "children": ko.observableArray([])
            });
            newParent = newParent()[position].children;
          }

          //Update item position
          originalParent.remove(item);
          newParent.splice(position, 0, item);
          
          //Remove empty lists
          var children = viewModel.children();
          for (var i = 0; i < children.length; i++) {
            if (children[i].children().length == 0) {
              viewModel.children.remove(children[i]);
              console.log(children);
            }
          }

          //Update UI
          ui.item.remove();

          //Debug data model
          console.log("final viewModel");
          var children = viewModel.children();
          for (var i = 0; i < children.length; i++) {
            console.log(children[i].children());
            for (var j = 0; j < children[i].children().length; j++) { 
             console.log(children[i].children()[j].children(),children[i].children()[j].content());
            }
          }
        }

      },
      connectWith: '.sortable-container'
    });
  }
};
//attach meta-data
ko.bindingHandlers.sortableItem = {
  init: function(element, valueAccessor) {
    var options = valueAccessor();
    $(element).data("sortItem", options.item);
    $(element).data("parentList", options.parentList);
  }
};
var self = this;
var viewModel = function() {
  var self = this;
  self.children = ko.observableArray(
    [{
      "children": ko.observableArray([{
        "content": ko.observable("Item 1"),
        "children": ko.observableArray([])
      }, {
        "content": ko.observable("Item 2"),
        "children": ko.observableArray([])
      }, {
        "content": ko.observable("Item 3"),
        "children": ko.observableArray([])
      }])
    }, {
      "children": ko.observableArray([{
        "content": ko.observable("Item 4"),
        "children": ko.observableArray([])
      }])
    }, {
      "children": ko.observableArray([{
        "content": ko.observable("Item 5"),
        "children": ko.observableArray([])
      }, {
        "content": ko.observable("Item 6"),
        "children": ko.observableArray([])
      }])
    }]
  );
};
ko.applyBindings(new viewModel());
.sortable-table {
  border: 1px red solid;
  padding: 10px 0px;
  list-style-type: none;
  width: 100% !important;
  display: table !important;
}
.sortable-table .sortable-row {
  height: 100% !important;
  display: table-row !important;
  padding: 5px 0px;
}
.sortable-table .sortable-cell {
  border: 1px solid green;
  display: table-cell !important;
  cursor: move;
}
.sortable-table .sortable-cell p {
  display: inline;
  margin: 0 !important;
}
.sortable-table .highlight-vertical {
  width: 5px !important;
  display: table-cell !important;
  background-color: blue !important;
}
.sortable-table .highlight-horizontal {
  height: 5px !important;
  width: 100% !important;
  display: block !important;
  background-color: blue !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>

<div class="sortable-container" data-bind="template: { name: 'rowTmpl', foreach: $data.children, templateOptions: { parentList: $data.children } }, sortableList: { data: $data.children, placeholder: 'highlight-horizontal' }">
</div>

<script id="rowTmpl" type="text/html">
  <div class="sortable-table">
    <div class="sortable-row sortable-container" data-bind="template: { name: 'cellTmpl', foreach: $data.children, templateOptions: { parentList: $data.children } }, sortableList: { data: $data.children, placeholder: 'highlight-vertical' }">
    </div>
  </div>
</script>

<script id="cellTmpl" type="text/html">
  <div class="sortable-cell" data-bind="sortableItem: { item: $data, parentList: $item.parentList }">
    <p data-bind="text: $data.content"></p>
  </div>
</script>