5

We have list of players, which is represented in form like this:

<div class="players_list">
   <div class="player" data-points="500">
        <div class="name">Player1</div>
        <div class="position">1</div>
   </div>     
   <div class="player" data-points="400">
        <div class="name">Player2</div>
        <div class="position">2</div>
   </div>
   <div class="player" data-points="300">
        <div class="name">Player3</div>
        <div class="position">3</div>
   </div>
   <div class="player" data-points="200">
        <div class="name">Player4</div>
        <div class="position">4</div>
   </div>  
</div> 

Something happens, and Player 4 recieves 600 points. So it goes on 1st place. With him - player3 recieves 300 points and goes on 2nd place.

It happens after same event - let's assume, that they get these points from server, but that's all load that is allowed on server and server does not calculate positions.

What is most efficent way to get view like this:

<div class="players_list">
   <div class="player" data-points="800">
        <div class="name">Player4</div>
        <div class="position">1</div>
   </div>
   <div class="player" data-points="700">
        <div class="name">Player3</div>
        <div class="position">2</div>
   </div>

   <div class="player" data-points="500">
        <div class="name">Player1</div>
        <div class="position">3</div>
   </div>     
   <div class="player" data-points="400">
        <div class="name">Player2</div>
        <div class="position">4</div>
   </div>
</div> 

It's not question about code, it's question about:

  • What kind of task it is? Just sort?
  • Is it ok to use JavaScript to solve this task if lists are less than 500 rows?
  • Is it ok to re-render whole players list, or there are some ways to just change div element position(index in DOM or something like this)?
  • Are there any tools and libraries around, that can help solve this task efficiently?

P.S.: If we want to animate all this story - we should create a queue of animation tasks each time scores for all players recalculates?

Update:

I have followed @Jalayn's answer, and stuck on following problem:

Let's assume that list [1,2,3,4] transforms to [2,3,4,1]. If we map it, we will get following position diffs [+1, +1, +1, -3].. Now, if we move one by one elements to it's position change, all will be ok, till we get to negotiate position diff - 1st position diff can't move down any more..

So, my update to question - how we can animate rows more than twice?

And final problem, on which I am stucked:

Not sure, if it most be solved in this question. BTW, if 2 players have same points - animation plan brakes and element_ids duplicates.

My question now is how to make following transformation of structures list: [(#id1, 1, 100),(#id2, 2, 50), (#id3, 2, 50), (#id4, 4, 25)] => [(#id3, 1, 150), (#id1, 2, 100), (#id4, 3, 75), (#id2, 4, 50)]

For now my solution is to refresh whole list, but not completly regenerate it - just move all elements to the first position in order they sorted in backed JSON. Animation is too complex to be done cause of final problem.

7
  • 3
    I am interested to see what others opinions are, but I would imagine that trying to sort and move DOM elements will be more inefficent than sorting a JSON data model.
    – maple_shaft
    Aug 20, 2012 at 13:56
  • @maple_shaft, it's generated from JSON. BTW, should I regenerate whole list of players, after I've generated it already? Move div on one position up costs more than regenerate whole list? Aug 20, 2012 at 14:28
  • Thats just it, I don't know and it is a good question. I am hoping that someone posts a good answer.
    – maple_shaft
    Aug 20, 2012 at 14:47
  • Nikolay, I don't understand why you say that on the end "element_ids" duplicate ? If each user has a unique nickname, each element id you base on a nickname should be unique. So what's the problem ?
    – Jalayn
    Aug 22, 2012 at 14:07
  • @Jalayn, no problems with ids. Sorry. Problem happens when I move player up. Where I should put player, that I replace with new one? Aug 22, 2012 at 14:13

2 Answers 2

8
+50

Well I hope, I'll answer all your questions and be precise enough.

The answer to all your questions is yes, it is possible to do all of that in Javascript:

What kind of task it is? Just sort?

You have to sort data, but you also have to update some data (points, name?, position) and display it to the user.

Is it ok to use JavaScript to solve this task if lists are less than 500 rows?

You can rearrange tables composed of 500 rows with JavaScript, no problem (using a bit of JQuery), and I don't think you would have any performance trouble with that. The only problem you could have is if you attempt to animate 500 moving rows (more on that later)

Is it ok to re-render whole players list, or there are some ways to just change div element position(index in DOM or something like this)?

Well, I think it is ok to re-render the whole players list, but if you do that, how will perform position-change animations ?

Are there any tools and libraries around, that can help solve this task efficiently?

Obvious answer: JQuery and JQuery UI (or your favorite JavaScript framework for performing animations)

If we want to animate all this story - we should create a queue of animation tasks each time scores for all players recalculates?

I think you should perform the animations one by one after you know the final position of each player. Otherwise, you could perform animations when nothing has changed (player3 may move from 3 to 1, then player2 moves from 2 to 1, then player1 moves from 1 to ... 1, nothing has changed!)

DOM only vs DOM backed by a JSON Data Model

Now, regardless of the tools, or algorithm you are going to use, if you work only with DOM elements, without having a "backend" JSON data model (please someone complete here if you see more problems):

  • Finding the new position of a given player will be a pain
  • Updating the positions of every player will be a pain

If you work with DOM elements for rendering, backed by a JSON data model, and provided you make the following changes to your individual player display, you will have a much easier time:

   <div class="player" id="player2">
        <div class="name">Player2</div>
        <div class="points" id="playerPoints2">400</div>
        <div class="position" id="playerPosition2">2</div>
   </div>
  • Notice that I've added an id attribute to the player <div> and to the elements displaying the points ("playerPoints2") and the position ("playerPosition2"). Doing that will allow you to quickly find the position of a player or its points, given its unique id. You could do without, but that would affect performance and you would have to use more complex queries (see JQuery's .find())

My attempt:

In short, what I try to do is moving as few DOM elements as possible, and decorrelating the data from the display.

Imagine having the following JSON data model in addition to your DOM model for displaying the players:

  • player (array of players)
    • id
    • position (you can write a custom function for sorting the array given the "position" attribute if each item of the array)
    • points
    • name

and the following JSON data model for performing animations later:

  • playerChanges (array of player changes)
    • id
    • deltaPosition (example: +3 = 3 ranks higher than before) . * isComputed (boolean)

After receiving data from the server, here's what I would do:

  • sort the list of backend players, given their respective position. The first element of the array should be the top ranked player.
  • loop over the data received from the server
  • if the points of a player change, then:
    • look up the player in the array
    • update its points
    • move the player to the position in the array where it should fit, but don't change the "position" attribute
    • create a new entry in the "playerChanges" array, storing only the id of the player (we will compute the delta only at the end)
  • loop on the array of backend players, and for each player in "playerChanges" (this is important) that is nit yet conputed, compute the position attribute regarding to the position of the player in the array. Since you have his "old" position, and the position in the array, you can compute the "deltaPosition". Edit: after you compute the delta, mark the "playerChange" as computed and restart looping on the array of backend players. Do that as long as there is an uncomputed ""playerChange"" in the array.

In the end you should have an array of "playerChanges". This is the list of rows that you should move, given the "deltaPosition" of each player. Notice that it could be 0, which means that the player received new points, but his position did not change. To perform the animation, I would suggest looking at this brilliant answer.

After you have done these animations, you can loop again on the list of backend players in order to update the displayed position ("playerPositionXXX" where XXX is the id of the player) of each player.

What I've attempted to do here is to decorrelate the "update and sort" from what happens on the screen, the animation that the user sees.

Edit: here is a working code sample (on Chrome at least) of an animation with JQuery and JQueryUI.

<html>
    <head>
        <title>Temp test</title>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"></script>
        <style type="text/css">
            * { font-family: Calibri;}
            .player { border: 1px solid grey; background: #f0f0f0; width: 300px; 
                        padding: 10px; margin-bottom: 5px; margin-top: 5px; }
                .name { margin-left: 30px; font-size: 20px; font-weight: bold; }
                .points { float: right; color: whitesmoke; font-size: 24px; font-weight: bold; margin-top: -30px; 
                            background-color: #999; padding: 3px;}
                .position { clear: both; display: block; float: left; 
                            font-size: 20px; font-weight: bold; margin-top: -29px; padding-right: 10px; 
                            border-right: 1px solid black;}
        </style>
        <script type="text/javascript">

            function movePlayer(playerToMoveId, delta) {
                var $old = $(playerToMoveId);
                var $new = $old.clone();

                // TODO: here I don't take into account the delta
                // 
                // you need to write a simple method for determining the player
                // that will be replaced (here I hardcoded "p4"). 
                $("#p4").before($new);

                var newOffset = $new.offset();
                //Get the old position relative to document
                var oldOffset = $old.offset();
                //we also clone old to the document for the animation
                var $temp = $old.clone().appendTo('body');
                //hide new and old and move $temp to position
                //also big z-index, make sure to edit this to something that works with the page
                $temp
                  .css('position', 'absolute')
                  .css('left', oldOffset.left)
                  .css('top', oldOffset.top)
                  .css('zIndex', 1000);
                $new.hide();
                $old.hide();
                //animate the $temp to the position of the new img
                $temp.animate( {'top': newOffset.top, 'left':newOffset.left}, 'slow', function(){
                   //callback function, we remove $old and $temp and show $new
                   $new.show();
                   $old.remove();
                   $temp.remove();
                });
            }
        </script>
    </head>
    <body>
        <div class="players_list" id="plist">
           <div class="player" id ="p4">
                <div class="name" id="pName4">Player4</div>
                <div class="points" id="pPts4">800</div>
                <div class="position" id="pPos4">1</div>
           </div>
           <div class="player" id="p3">
                <div class="name" id="pName3">Player3</div>
                <div class="points" id="pPts3">500</div>
                <div class="position" id="pPos3">2</div>
           </div>

           <div class="player" id="p1">
                <div class="name" id="pName1">Player1</div>
                <div class="points" id="pPts1">300</div>
                <div class="position" id="pPos1">3</div>
           </div>     
           <div class="player" id="p2">
                <div class="name" id="pName2">Player2</div>
                <div class="points" id="pPts2">250</div>
                <div class="position" id="pPos2">4</div>
           </div>
        </div> 

        <input type="button" onclick="movePlayer($('#p2'), 3)" value="Animate"/>
    </body>
</html>
4
  • Thank you, player position delta - is what I was looking for. Aug 20, 2012 at 15:30
  • I have updated question - some quest there. Aug 20, 2012 at 19:22
  • You're right, I've updated the algorithm and added a loop on the last step, I think it should be alright now.
    – Jalayn
    Aug 20, 2012 at 20:25
  • Well, looks like this animation fits. Aug 22, 2012 at 16:09
1

Assuming you have multiplayers on different systems and some type of hosts (could be node.js).

I would say, that this is a typical update-score event. Something that gets triggered on the server. Since when the scores are changing, the server should send to each player the new (ranked) scores.

I would use javascript (jquery) on the client side for rendering the score-list. With HTML5 you could use web sockets, and stream the new scores the moment the server (done calculating) sends out the event (or just notify the client that new score list is available).

I would keep as much as logic (as reasonable possible) on the server (MVC).

3
  • The author explicitly wrote that ranks are not computed on the server, which is why it's an interesting challenge.
    – Jalayn
    Aug 22, 2012 at 12:54
  • Keeping as much as possible logic on server is not related to MVC, in case when we have VC on client and very light M on server. Aug 22, 2012 at 13:05
  • In a (IMO) saner thick client approach, you basically have two MVC implementations with the server-side view and controller treating the browser and http requests as the user and the client-side treating the dope in the chair and DOM events fired by input as the user. That tends to make the Model on the client side almost exclusively about data and ajax communication and not at all about business logic but that strikes me as the most maintainable/modular/sane way to go. Oct 11, 2012 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.