Real time html sort

First a little background for this article. When I was starting as junior dev I had a task to implement real time drag and drop. Back then we used vue.js on front end and asp mvc on backend with signalR implementation of socket.io that was synchronizing items in the grid among the all conntected users in the same group. I somehow managed to make it work but as far as i remember solution was rather ugly and not so reliable.

This occantionaly came to mine mind because I know that i can sure do much better solution now. I wanted to do it again, just for mine personal satisfaction. I just needed something simple, just enough for proof of concept so i can finally get away. After few years it turns out to be super simple..

How it works

We just get Ids of items in exact order as they are displayed for the user that triggered and finished the ordering. Then push that order for all of the connected users. In last step reorder items in DOM based on that order we received. It is possible to store order of Ids in database in case we would like to persist the order of items.

You can check out working example on github

Prerequisites

  • node.js
  • socket.io and jQuery libaries, that is acctually all we need.

Setting up

Create new folder and cd to that folder.

Install required packages.
– npm install –save express@4.15.2
– npm install –save socket.io

Server side

To be able to run socket.io we will need to have server running. Socket.io will emit events with payload to browser of all connected users. We will hook socket.io to express node module, which will act as server and listen to the incoming requests. Implementation is pretty straightforward.

Create server.js file in root directory and copy paste following code.

var express = require('./node_modules/express');

// Create a express application
var app = express();

// Serve static files from public directory e.g. scripts, pages, css...
app.use(express.static('public'));

/* 
Pass express application to HTTP server module, and then pass http server to the socket.io
Http server is needed because if you try to pass express directly to socket.io you will recieve  
Error: You are trying to attach socket.io to an express request handler function. Please pass a http.Server instance
*/
var http = require('http').Server(app);
var io = require('./node_modules/socket.io')(http);

// Render index.html as startup page
app.get('/', function (req, res) {
  res.sendFile(__dirname + '/public/index.html');
});

// This is event will be triggered once any of the users will load the page
io.on('connection', function (socket) {

  // When event to change order is triggered from sender
  socket.on('changed_order', function (itemIds) {

    // Then emit event to every user that is connected with payload of sender (that is itemIds)
    io.emit('changed_order', itemIds);
  });
});

// Bind server to listen on default socket.io port
http.listen(3000, function () {
  console.log('listening on *:3000');
});

 

Front end 

First we will create html page that will be served to the client. Create new folder in root directory called public, create new index.html file in that folder and copy paste following html.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>Sortable demo</title>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
  <script src="/socket.io/socket.io.js"></script>

  <!-- Custom scripts -->
  <script src="/socket_client.js"></script>

  <!-- Styles -->
  <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
  <ul id="sortable">
    <li id="ef7002fe-5611-4235-a6b3-3f8ee02caada">Item 1</li>
    <li id="f1855aff-1f4b-442f-aa55-ee30bd7f347f">Item 2</li>
    <li id="54c22255-c0fe-4a24-aa2c-d228df3f438d">Item 3</li>
    <li id="0fea02ea-772b-40e7-bf1c-d12f86bb11ca">Item 4</li>
    <li id="80ddede4-8397-423d-9238-84c093312ebf">Item 5</li>
  </ul>
</body>

</html>

Once we have page created, we will need to connect socket.io client to the server so they can transmit the messages between each other. Actually this one-liner do all the magic. You don’t have to even specify url to connect to, by default it will use the url of server that is rendering the page.

 
const socket = io();

 

Last step is to create server.js file in public directory. We will put all the logic of reordering items and transmitting events there.

$(document).ready(function () {
  // Connect socket client to the server that served the page
  const socket = io();

  socket.on('changed_order', function (itemIds) {
    orderItemsInDom(itemIds);
  });

  $('#sortable').sortable({
    // Ensure items can be dragged only verticaly
    axis: 'y',

    // Event is triggered when the user stopped sorting and the DOM position has changed.
    update: function (event, ui) {

      const DOMItems = $('#sortable').find('li');
      const itemIds = getIds(DOMItems);

      // When one user changed order of items, reorder items in DOM for all connected users 
      socket.emit('changed_order', itemIds);
    }
  });

  function getIds(items) {
    return $.map(items, function (item) {
      return $(item).attr("id");
    });
  }

  // Order items in DOM based on the order of Ids arg
  function orderItemsInDom(itemIds) {
    let lastSeenItem = null;

    for (let itemId of itemIds) {

      // Select item in DOM by ID
      let item = $("#" + itemId)[0];

      // Remove that item from DOM
      $(item).remove();

      if (lastSeenItem === null) {
        // Add item to the top of the grid because we know that if lastSeenItem is null item is first on the list
        $('#sortable').prepend(item);
      } else {
        $(item).insertAfter(lastSeenItem);
      }

      lastSeenItem = item;
    }
  }
});

Optionally we can add a little bit of css to make sortable list look more polished. Create new style.css file in public directory, reference it in index.html and copy following css.

#sortable li {
width: 300px;
margin-top: 10px;
padding: 5px;
border: 1px solid #95979b;
text-align: center;
list-style: none;
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *