I want to be able to select multiple items and copy them from a list to the otherone (bidirectional) by using drag-and-drop and only drop if it doesnt exists already...
Some help would be appreciated alot.
EDIT: Regarding to Charlie's post and fiddle, how can i make it possible to select multiple items to drag and drop to the other list? As it is now, it only permit 1 item at same time.
HTML:
<div class="list">
<h2>Stored procedures In DB 1</h2>
<ul class="list" data-bind="sortable: { data: storedProceduresInDB1, beforeMove: checkAndCopy }">
<li class="item" data-bind="text: Name"></li>
</ul>
</div><br>
<div class="list">
<h2>Stored procedures In DB 2</h2>
<ul class="list" data-bind="sortable: { data: storedProceduresInDB2, beforeMove: checkAndCopy }">
<li class="item" data-bind="text: Name"></li>
</ul>
</div>
Code:
var ViewModel = function () {
var self = this;
self.storedProceduresInDB1 = ko.observableArray([
{ Name: 'SP1', Id: 1 },
{ Name: 'SP2', Id: 2 },
{ Name: 'SP3', Id: 3 }
]);
self.storedProceduresInDB2 = ko.observableArray([
{ Name: 'SP3', Id: 3 },
{ Name: 'SP4', Id: 4 },
{ Name: 'SP5', Id: 5 }
]);
self.selectedStoredProcedureInDB1 = ko.observable();
self.selectedStoredProcedureInDB2 = ko.observable();
self.selectStoredProcedureInDB1 = function (sp) {
self.selectedStoredProcedureInDB1(sp);
};
self.selectStoredProcedureInDB2 = function (sp) {
self.selectedStoredProcedureInDB2(sp);
};
self.checkAndCopy = function(event) {
var targetHasItem = ko.utils.arrayFilter(event.targetParent(), function(item) {
return item.Id == event.item.Id;
}).length;
if(!targetHasItem) {
event.targetParent.splice(event.targetIndex, 0, event.item);
}
if(event.targetParent != event.sourceParent) {
event.cancelDrop = true;
}
};
};
ko.applyBindings(new ViewModel());
Charlie's JSFiddle
Here's a solution that allows sorting multiple items at a time. Based on this related answer but modified to use knockout.js
and knockout-sortable
.
JSFiddle (tested in Chrome): http://jsfiddle.net/QWgRF/715/
HTML
Added IDs to the sortables and list items so that the jQuery sortable code could determine which items were being selected.
Added an options
property which is passed to the underlying jQuery Sortable plugin.
Added a click handler to each list item for selection and multi-selection.
<div class="list">
<h2>Stored procedures In DB 1</h2>
<ul class="list" id="sortableForDB_1" data-bind="sortable: {
data: storedProceduresInDB1,
beforeMove: checkAndCopy,
options: multiSortableOptions }">
<li class="item" data-bind="attr: { id: 'sp_'+Id }, text: Name,
click: $root.selectProcedure.bind($data, $parent.storedProceduresInDB1()),
css: { selected: Selected }">
</li>
</ul>
</div>
<div class="list">
<h2>Stored procedures In DB 2</h2>
<ul class="list" id="sortableForDB_2" data-bind="sortable: {
data: storedProceduresInDB2,
beforeMove: checkAndCopy,
options: multiSortableOptions }">
<li class="item" data-bind="attr: { id: 'sp_'+Id }, text: Name,
click: $root.selectProcedure.bind($data, $parent.storedProceduresInDB2()),
css: { selected: Selected }">
</li>
</ul>
</div>
JavaScript
Sortable options. Delay 150 to allow the click handler to function for selecting. Revert 0 since the animations would look strange with how we're messing with the list and helper.
The stop
function resets the observableArray
s which causes the list html to be refreshed.
The helper
function grabs all selected items and adds them into a new helper tag to allow for the visual of dragging multiple items around.
var multiSortableOptions = {
delay: 150,
revert: 0,
stop: function(event, ui) {
// Force lists to refresh all items
var db1 = myViewModel.storedProceduresInDB1,
db2 = myViewModel.storedProceduresInDB2,
temp1 = db1(),
temp2 = db2();
ui.item.remove();
db1([]);
db1(temp1);
db2([]);
db2(temp2);
},
helper: function(event, $item) {
// probably a better way to pass these around than in id attributes, but it works
var dbId = $item.parent().attr('id').split('_')[1],
itemId = $item.attr('id').split('_')[1],
db = myViewModel['storedProceduresInDB'+dbId];
// If you grab an unhighlighted item to drag, then deselect (unhighlight) everything else
if(!$item.hasClass('selected')) {
ko.utils.arrayForEach(db(), function(item) {
if(item.Id == itemId) {
item.Selected(true);
} else {
item.Selected(false);
}
});
}
// Create a helper object with all currently selected items
var $selected = $item.parent().find('.selected');
var $helper = $('<li class="helper"/>');
$helper.append($selected.clone());
$selected.not($item).remove();
return $helper;
}
};
View Model. Added a Selected
observable property to each stored procedure. Added a selectProcedure
method to update the new Selected
property based on clicks and ctrl+clicks.
Also heavily modified the checkAndCopy
method. The inline comments I think explain how it works pretty well.
var ViewModel = function () {
var self = this;
self.storedProceduresInDB1 = ko.observableArray([
{ Name: 'SP1', Id: 1, Selected: ko.observable(false) },
{ Name: 'SP2', Id: 2, Selected: ko.observable(false) },
{ Name: 'SP3', Id: 3, Selected: ko.observable(false) }
]);
self.storedProceduresInDB2 = ko.observableArray([
{ Name: 'SP3', Id: 3, Selected: ko.observable(false) },
{ Name: 'SP4', Id: 4, Selected: ko.observable(false) },
{ Name: 'SP5', Id: 5, Selected: ko.observable(false) }
]);
self.checkAndCopy = function(event) {
var items;
if(event.targetParent !== event.sourceParent) {
// Get all items that don't yet exist in the target
items = ko.utils.arrayFilter(event.sourceParent(), function(item) {
return item.Selected() && !ko.utils.arrayFirst(event.targetParent(), function(targetItem) {
return targetItem.Id == item.Id;
});
});
// Clone the items (otherwise the Selected observable is shared by the item in both lists)
items = ko.utils.arrayMap(items, function(item) {
var clone = ko.toJS(item);
clone.Selected = ko.observable(true);
// While we're here, let's deselect the source items so it looks cleaner
item.Selected(false);
return clone;
});
// Deselect everything in the target list now so when we splice only the new items are selected
ko.utils.arrayForEach(event.targetParent(), function(item) {
item.Selected(false);
});
} else {
// Moving items within list, grab all selected items from list
items = event.sourceParent.remove(function(item) {
return item.Selected();
});
}
// splice items in at target index
items = items.reverse();
for(var i=0; i<items.length; i++) {
event.targetParent.splice(event.targetIndex, 0, items[i]);
}
// always cancel drop now, since we're manually rearranging everything
event.cancelDrop = true;
};
self.selectProcedure = function(array, $data, event) {
if(!event.ctrlKey && !event.metaKey) {
$data.Selected(true);
ko.utils.arrayForEach(array, function(item) {
if(item !== $data) {
item.Selected(false);
}
});
} else {
$data.Selected(!$data.Selected());
}
};
};
Added a global reference to our View Model so the jQuery code can interact with it.
myViewModel = new ViewModel();
ko.applyBindings(myViewModel);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With