将 Knockout observable 传递给 jQueryUI sortable

Passing Knockout observable to jQueryUI sortable

在这里,我试图将对 Knockout observable 的引用传递给一个可排序对象,该对象是 jQueryUI 的一部分,默认情况下无法访问该变量。

在代码示例中,我试图在行 self.dragMode(true); 上通过 self 引用 viewModel。该行在代码示例中突出显示。

尝试的调用失败,每当我调用引用的对象时,我都会得到 undefined。这样做的正确方法是什么?

var viewModel = function() {
  var self = this;
  self.gridItems = ko.observableArray(
    [{
      "rowItems": [{
        "name": "Item 1"
      }, {
        "name": "Item 2"
      }, {
        "name": "Item 3"
      }]
    }, {
      "rowItems": [{
        "name": "Item 4"
      }]
    }, {
      "rowItems": [{
        "name": "Item 5"
      }, {
        "name": "Item 6"
      }]
    }]
  );
  self.selectedRowItem = ko.observable();
  self.dragMode = ko.observable(false);

  console.log(self.gridItems());

  self.gridItems().splice(0, 0, {
    "rowItems": [{
      "placeholder": true,
      "name": ""
    }]
  });

  for (var i = 1; i < self.gridItems().length; i++) {
    console.log(self.gridItems()[i]);
    self.gridItems()[i].rowItems.splice(0, 0, {
      "placeholder": true,
      "name": ""
    });
    for (var j = 1; j < self.gridItems()[i].rowItems.length; j++) {
      self.gridItems()[i].rowItems.splice(j + 1, 0, {
        "placeholder": true,
        "name": ""
      });
      j++;
    }
    self.gridItems().splice(i + 1, 0, {
      "rowItems": [{
        "placeholder": true,
        "name": ""
      }]
    });
    i++;
  }

  console.log(self.gridItems());
};

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
  self: this,
  init: function(element, valueAccessor, allBindingsAccessor, context) {
    $(element).data("sortList", valueAccessor()); //attach meta-data
    $(element).sortable({
      start: function(event, ui) {
       self.dragMode(true); // HERE NEED TO ACCESS VIEWMODEL
      },
      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");
          //figure out its new position
          var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
          if (position >= 0) {
            if (newParent[position].placeholder) {
             originalParent.remove(item);
             newParent.splice(position, 0, item);
            } else {
             return;
            }
          }

          ui.item.remove();
        }
      },
      cancel: ':input,button,.contenteditable,.sortable-placeholder-horizontal,.sortable-placeholder-vertical',
      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);
  }
};

ko.applyBindings(new viewModel());
.sortable-grid .sortable {
  list-style-type: none;
  margin: 0;
  padding: 0;
  width: 100% !important;
  display: table !important;
  table-layout: auto;
}

.sortable-grid .sortable .sortable-item {
  margin: 0 3px 3px 3px;
  padding: 0.4em;
  font-size: 1.4em;
  cursor: move;
}

.sortable-grid .sortable div.fixed {
  cursor: default;
  color: #959595;
  opacity: 0.5;
}

.sortable-grid {
  
}

.sortable-grid .sortable .sortable-row {
  height: 100% !important;
  padding: 0 !important;
  margin: 0 !important;
  display: table-row !important;
}

.sortable-grid .sortable .sortable-item {
  border: 1px solid green;
  //width:initial;
  display: table-cell;
  margin: 0 !important;
}

.sortable-grid .sortable .sortable-item > p {
  //width:100%;
  display: inline;
  margin: 0 !important;
  z-index: 9999;
  cursor: text;
}

.sortable-grid .sortable .sortable-placeholder-horizontal {
  background-color: red;
}

.sortable-grid .sortable .sortable-placeholder {
  background-color: red;
  display: table-cell;
  margin: 0 !important;
  width:10px !important;
}

.sortable-grid .sortable .sortable-placeholder:hover {
  background-color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.0-beta.1/themes/smoothness/jquery-ui.css" rel="stylesheet" />
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" rel="stylesheet" />
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div class="" data-bind="template: { name: 'gridTmpl', foreach: gridItems, templateOptions: { parentList: gridItems} }, sortableList: gridItems">
</div>

<script id="gridTmpl" type="text/html">
  <div class="sortable-grid">
    <div class="sortable sortable-container">
      <div class="sortable-row sortable sortable-container" data-bind="template: { name: 'rowTmpl', foreach: rowItems, templateOptions: { parentList: rowItems} }, sortableList: rowItems">
      </div>
    </div>
  </div>
</script>

<script id="rowTmpl" type="text/html">
  <!-- ko if: !$data.placeholder -->
  <div class="sortable-item" data-bind="sortableItem: { item: $data, parentList: $data.parentList }">
    <p class="contenteditable" contenteditable="true" data-bind="text: name"></p>
  </div>
  <!-- /ko -->
  <!-- ko if: $data.placeholder && $root.dragMode -->
  <div class="sortable-placeholder" data-bind="sortableItem: { item: $data, parentList: $data.parentList }">
    </p>
  </div>
  <!-- /ko -->
</script>

ko.bindingHandlers.yourBindingName 的参数是:element, valueAccessor, allBindings, viewModel, bindingContext(从这里 - knockoutjs manual)。

在您的代码中,您可能将其作为 context 参数。

因此您可以将 self.dragMode(true); 更改为 context.dragMode(true),它应该可以工作。

在这种情况下,有必要使用 bindingContext,其中包含对全局 viewModel 的引用。无法使用 viewModel 参数,因为上下文根据本地使用模板的位置而改变。

最后,这些是获取 viewModel 并将其传递给 jQuery 可排序所需的所有更改:

  1. $(element).data("viewModel", bindingContext.$root);
  2. var viewModel = ui.item.parent().data("viewModel");

更新绑定处理程序:

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    $(element).data("sortList", valueAccessor()); //attach meta-data
    $(element).data("viewModel", bindingContext.$root);// ? bindingContext.$root : bindingContext); //attach meta-data
    $(element).sortable({
      start: function(event, ui) {
        //identify viewModel
        var viewModel = ui.item.parent().data("viewModel");
        viewModel.dragMode(true);
      },
      change: function(event, ui) {
        //identify viewModel
        var viewModel = ui.item.parent().data("viewModel");
        viewModel.dragMode(true);
      },
      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 = ui.item.parent().data("viewModel");
          //figure out its new position
          var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
          if (position >= 0) {
            console.log(newParent[position]);
            if (newParent[position].placeholder) {
              console.log(originalParent);
                originalParent.remove(item);
                newParent.splice(position, 0, item);
                viewModel.dragMode(false);
            } else {
                return;
            }
          }

          ui.item.remove();
        }
      },
      cancel: ':input,button,.contenteditable,.sortable-placeholder-horizontal,.sortable-placeholder-vertical',
      connectWith: '.sortable-container'
    });
  }
};