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?
I decided it would be better to separate the code from the esri code. To install rename the LayerListView.js to LayerListView_esri.js and copy over the files.
This version also adds a drag handle styled as per the map viewer
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; }
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); },
Signed in members can post, follow updates, and more. New here? Register a free account.
Find useful guides, FAQs, and documents to help you navigate and make the most of Esri Community.