Implementing Drag and Drop with react-absolute-grid

February 27, 2016 0 Comments

Most of the heavy lifting has been done by jrowny in his react-absolute-grid component. We're just finishing it off here.

  • Implement onMove method in react-absolute-grid
  • react-absolute-grid full example

Handling Move Events

As of time of this writing, the Github README.md for the aforementioned component, tells you to update the sort items when a move happens.

This function is called when an item is dragged over another item. It is your responsibility to update the sort of all items when this happens.

If you're trying to implement this, it's nice to have a full example. Here are some examples of how to do this. Here's one version of the method pulled from source.

  //Change the item's sort order
  var onMove = function(source, target){
      //If we're in the same group, we can just swap orders
    var targetSort = target.sort;

    //CAREFUL, For maximum performance we must maintain the array's order, but change sort
    sampleItems.forEach(function(item){
      //Decrement sorts between positions when target is greater
      if(target.sort > source.sort && 
(item.sort <= target.sort && item.sort > source.sort)){
        item.sort --;
      //Increment sorts between positions when source is greater
      }
      else if(item.sort >= target.sort && item.sort < source.sort){
        item.sort ++;
      }
    });

    source.sort = targetSort;
    render();
  };

view entire file:

Implementation Details

First we need a reference to our array. Each object has a sort key. This is the value which determines the sequence. We need to know where we're coming from and where we're going. What item are you dragging, and where are you dropping it? We'll call this source and target, or from and to.

Iterate and Increment or Decrement

Loop through all items.

Increment if all of these hold true:

  • the target is greater than the source
  • the item being iterated upon is less than or equal to the value of the target
  • the item being iterated upon is greater than the source

Decrement if all these hold true:

  • the item being iterated upon is greater than the target
  • the item being iterated upon is less than the source

Find Target

When looping through all of our items, we'll take the target. When we get to that index, we'll insert the source if it's not already there. For every proceeding item, we'll increment the index.

Manipulating Arrays

We'll perform a splice to remove the item at the index of the source, and insert it at the index of the target. We iterate through our array to assign it's sort value. This method may inihibit performance as it may trigger a render.

Working with Meteor

There is often more than one way to do things in React and Meteor.

Using State

You could choose to represent changes in your component data as state. In the GetInitialState method, we'll return the states we will be using in the component. After rearranging our items, we figure out the order it's in. In doing so, we'll create some new data, an array containing a list of objects we've just sorted. The values for the sort property will be different, because we've dragged around some of the items. Because we're using state, we will need to tell Meteor we want to save these changes.

// define the state to use in the component
getInitialState(){return mySortedList})

// invoke method to handle save
Meteor.call('saveSortedList', sortedListValues)

// set the new state for your component to trigger a render
this.setState('sortedList', mySortedList)  

Using Props

When using the official ReactMeteorData mixin, we define the mixin and a getMeteorData method.

mixin: [ReactMeteorData]

// return a reference to the list when our subscription is ready
getMeteorData(){  
  subscribe('list', query).ready() ? return {list:list} : setTimeout()
}

// called from rendered component with event data
updateData(event){  
  // assumes you've defined updatedList with the new data
  Meteor.call('updateList', updatedList)
}

render(){  
  this.data.list.map(()=>{
    <myComponent onClick={ this.updateData } />
  })
}

Handling Data in React and Meteor

Here are a couple ways you may choose to handle communication when creating, reading, updating, or deleting data from your database.

Stateful

When the component loads, define the data and push this down to your render method. Information is represented in React as this.state. When this data changes, use this.setState to update the values and trigger your view to render. The user needs a means to trigger an event, which will then update the data on your server. This is done by defining a function in Meteor.methods which is invoked from the UI via Meteor.call('myMethod', someData). This should make sense to everybody, nothing new here.

Reactive

When your component loads, it may or may not define the data. It defines a subscription handler to a collection of documents. This is passed down from parent to child components with this.props. To update the data on the server, we use this.setProps and a mixin in our component. This will trigger another render, and the data will be represented in your render method as this.data.

Dynamic

At some point the amount of data you're working with may become too large to maintain a full copy on the client and the server. In this case you need to make a small change in what you publish to your minimongo dataset. This higher-order function takes an argument to determine to define a query and constructs a new query. The returned value is published to all subscribers. With this technique we can build real-time applications.

// server
Meteor.publish('myAction', (query) => {  
  return myCollection.update(data, updated)
})

// client
Meteor.call('myAction', query)  

Tag cloud