Dragging LayerList Items to Re-order Layers

1494
3
09-09-2019 11:55 PM
JerryChen
New Contributor III

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?

3 Replies
VictorTey
Esri Contributor

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); },
0 Kudos
DavidWilton
Occasional Contributor

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

  • Because wab doesn't nest the expanded items (the layer swatch) it only supports dragging above an item.
  • Feature layers can't be moved, this is a limitation of 3x.
  • there well may be edge layer types I haven't tested.
  • tested and working with 2.20

rjk3zSC07D.gif

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;
}

 

 

0 Kudos
DavidWilton
Occasional Contributor

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

0 Kudos