Here I'm attempting to pass reference to Knockout observable to a sortable object, which is part of jQueryUI and doesn't have access to the variable by default.
In the code example I'm trying to make a reference to viewModel
through self
on the line self.dragMode(true);
. The line is highlighted in the code example.
The attempted calls have failed and whenever I call the referenced object I get undefined
. What is the right way of doing this?
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>
In this case it was necessary to use bindingContext
, which contained the reference to global viewModel. The viewModel
argument couldn't be used because the context changed depending on where the template is used locally.
Finally, these were all the changes needed to get the viewModel
and pass it to jQuery sortable:
$(element).data("viewModel", bindingContext.$root);
var viewModel = ui.item.parent().data("viewModel");
Updated binding handler:
//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'
});
}
};