I'm working on making LayerList items to be draggable so that users would be able to reorder the layers by dragging them. I've found Shopify Draggable might be the closest approach. However, the application(open CodePen) loses some of its functionalities after the LayerList is dragged. It's a shame that v4.x doesn't support dragging natively like v3.x did. Is there any other way to perfectly reorder LayerList layers with dragging?
Hi,
I used the dojo Drag and Drop package in the LayerListView.js file.
This code is probably 2 year old Below are some code snippets. Hope it helps. This is done for js 3
_setUpDnD: function () { this.dndList = new Source(this.layerListTable, { skipForm: true, withHandles: true, singular:true, creator: dojo.hitch(this, this.createItem) }); dojo.connect(this.dndList, "onDropInternal", this, "onDrop"); this.dndContainer = new Container(this.layerListTable); }, onDrop: function (nodes) { var layertrArr = dojo.query('.jimu-widget-wslayerList .dojoDndHandle') //VT: we need to loop through everything to ensure the structure integrity in the <table>. array.forEach(layertrArr,lang.hitch(this,function(layertr){ let layertrNodeId=dojo.attr(layertr,'layertrnodeid'); let nextSiblingNodeId = layertr.nextSibling?dojo.attr(layertr.nextSibling,'layercontenttrnodeid'):''; if(!nextSiblingNodeId || nextSiblingNodeId!==layertrNodeId){ let node = dojo.query('[layercontenttrnodeid="'+ layertrNodeId +'"]') this.layerListTable.insertBefore(node[0], layertr.nextSibling); } })) this.emit('dnd-drop', nodes); var previousNode=null; var allNodes = this.dndList.getAllNodes(); for(var i = 0; i < allNodes.length; i ++){ if(allNodes.id===nodes[0].id){ break }else{ previousNode = allNodes; } } var layerInfo = this.dndList.getItem(nodes[0].id).data; var steps = this._getMovedStepsAfterDnD(layerInfo , previousNode?this.dndList.getItem(previousNode.id).data:null); if(steps.movedown){ this.moveDownLayer(layerInfo,steps.steps) }else{ this.moveUpLayer(layerInfo,steps.steps) } console.log(steps); },
I had to do this and Victors sample is definitely missing functions (e.g. createItem) and things with the laterlist look like they have changed since.
I have wrapped up the code into blocks that can just be pasted into layerListview.
The css will give you the map viewer style dotted line.
Notes
LayerListView.js
_setUpDnD: function () {
this.dndList = new Source(this.layerListTable.parentNode, {
skipForm: true,
singular: true
});
dojo.connect(this.dndList, "onDropInternal", this, "onDrop");
this.dndContainer = new Container(this.layerListTable);
// listen for drag and check we can drop
this.onMouseMoveHandler = this.own(dojo.connect(this.dndList, "onMouseMove", lang.hitch(this, "handleDragMoveDnd")));
this.onMouseMoveHandler = this.own(dojo.connect(this.dndList, "onDndStart", lang.hitch(this, "handleDragStart")));
aspect.around(this, "addLayerNode", function (orig) {
return function(){
var args = orig.apply(this, arguments);
// ignore any layer which isn't a parent
if(arguments[0].parentLayerInfo){
return args;
}
// we need to add a node and a class as the dnd requires it
var id = args.layerTrNode.getAttribute("layerTrNodeId");
var layerInfo = this.operLayerInfos.getLayerInfoById(id);
// you can't move feature layers!
var lyr = layerInfo.originOperLayer.layerObject;
if (lyr.type && lyr.type.toLowerCase() === "feature layer") {
return args;
}
args.layerTrNode.setAttribute("id", args.layerTrNode.getAttribute("layerTrNodeId"));
// we also need the d&d class
domClass.add(args.layerTrNode, "dojoDndItem");
this.dndList.setItem(args.layerTrNode.id, args.layerTrNode)
return args;
}
}, true)
},
handleDragMoveDnd: function (a) {
Manager.manager().canDropFlag = true;
if (this.dndList.isDragging) {
// var layer2ContentTrNode = query("tr[layerContentTrNodeId='" + this.dndList.current.id + "']", this.layerListTable)[0];
query(".dndDashedLine", this.layerListTable).forEach(function (n) {
domClass.remove(n, "dndDashedLine");
})
if (this.dndList.current) {
if (this.dndList.currentDragInfo && this.dndList.currentDragInfo != this.dndList.current.id) {
// if its the next node don't let it drop it before/after depending on direction
// turn the map into an array
var layerInfoArray = this.operLayerInfos.getLayerInfoArray();
var ids = []
array.forEach(layerInfoArray, function (currentLayerInfo, index) {
if (this.dndList.map[currentLayerInfo.id]) {
ids.push(currentLayerInfo.id);
}
}, this);
var currentIdx = ids.indexOf(this.dndList.currentDragInfo);
var nextIdx = ids.indexOf(this.dndList.current.id);
if (domClass.contains(this.dndList.current, "dojoDndItemAfter")) {
// do not let users drop before. This is the issue with the jimu list which doesn't
// have the content (eg the layer swatch) is not a child of the layer item. we let this
// go if its the very bottom item
domClass.add(this.dndList.current, "dndDashedLine");
if(!nextIdx == ids.length - 1){
Manager.manager().canDropFlag = false;
domClass.remove(this.dndList.current, "dndDashedLine");
}
}
if (domClass.contains(this.dndList.current, "dojoDndItemBefore")) {
domClass.add(this.dndList.current, "dndDashedLine");
if (nextIdx - currentIdx === 1) {
Manager.manager().canDropFlag = false;
domClass.remove(this.dndList.current, "dndDashedLine");
}
}
} else if (this.dndList.currentDragInfo === this.dndList.current.id) {
Manager.manager().canDropFlag = false; // don't let it drop on top of itself
}else{
Manager.manager().canDropFlag = false;
}
}
else {
// this will happen when user mouses over a feature layer
Manager.manager().canDropFlag = false;
}
dojo.toggleClass(Manager.manager().avatar.node, "dojoDndAvatarCanDrop", Manager.manager().canDropFlag);
}
},
handleDragStart: function (a, c, e) {
if (this.dndList.current) {
this.dndList.currentDragInfo = this.dndList.current.id;;
}else{
this.dndList.currentDragInfo = null;
}
},
onDrop: function (nodes) {
this.dndList.getAllNodes().forEach(function (n) {
domClass.remove(n, "dndDashedLine");
})
// new position
var allNodes = this.dndList.getAllNodes();
var newPos = -1;
for (var i = 0; i < allNodes.length; i++) {
if (allNodes[i].id === nodes[0].id) {
newPos = i;
break;
}
}
// old position
var layerInfo = this.operLayerInfos.getLayerInfoById(nodes[0].id);
var oldPos = -1;
var layerInfoArray = this.operLayerInfos.getLayerInfoArray();
array.forEach(layerInfoArray, function (currentLayerInfo, index) {
if (layerInfo.id === currentLayerInfo.id) {
oldPos = index;
}
}, this);
// if we have less d&d nodes its because there are feature layers
if (layerInfoArray.length !== allNodes.length) {
newPos += (layerInfoArray.length - allNodes.length);
}
var steps = -1;
if (oldPos < newPos) {
steps = newPos - oldPos;
steps = this.operLayerInfos.moveDownLayer(layerInfo, steps)
} else {
steps = oldPos - newPos;
steps = this.operLayerInfos.moveUpLayer(layerInfo, steps)
}
this.emit('dnd-drop', nodes);
// just for good look
this.refresh();
}
style.css
.jimu-widget-layerList .dojoDndItemBefore.dndDashedLine{
border-width: 0 !important;
border-top: #009cff !important;
border-style: dashed !important;
border-top-width: 2px !important;
}
.jimu-widget-layerList .dojoDndItemAfter.dndDashedLine{
border-width: 0 !important;
border-bottom: #009cff !important;
border-style: dashed !important;
border-bottom-width: 2px !important;
}
.jimu-widget-layerList .dojoDndItem{
border-width: 0 !important;
}
.jimu-widget-layerList .layer-list-table {
border-collapse: collapse !important;
}