2
\$\begingroup\$

I have a dashboard like interface, that can have many tabs. There can be as many or as few tabs as the user likes, with the user being allowed to re order them as they wish. Exactly like browsers do.

My problem is moving the tabs position, for example, look at these tabs:

[Sales] [Admin] [Finance] [Development]

If the user wants to move the [Development](index 3) tag to be after [Sales], giving it a new index of 1, how should I bump Finance and Development along to 2 and 3 respectively?

I am using JSON to store it so data.dashboard[0].sequence merely means the sequence value for the first dashboard.

At the moment I am using the following bit of code, which seems a bit hacky and not as efficient as it could be. The function is called with the start index(3 in this case) and the desired or end index (1 in this case). I've tried my best to explain with the comments, but feel free to ask anything you don't understand:

function changeSequence(start, end){
  var newVal = 999;
  for(d=0;d<data.dashboards.length;d++){
    if(data.dashboards[d].sequence == start){    // set [Development] to 999 temporarily
      data.dashboards[d].sequence = newVal;
    } 
  }
  if(start > end){ 
    for(i=start; i>end; i--){
      var d = i;
      d--;
      for(j=0;j<data.dashboards.length;j++){     // if [Development] (index3) is after 
        if(data.dashboards[j].sequence == d){    // [Admin] (index1), bump indexes 
          data.dashboards[j].sequence = i;       // 1 and 2 along one (to 2 and 3)
        } 
      }
    }
  } else {
    for(i=start; i<end; i++){
      var d = i;
      d++;
      for(j=0;j<data.dashboards.length;j++){     // else if [Development] (index3) is
        if(data.dashboards[j].sequence == d){    // before [Admin] (index1), which it
          data.dashboards[j].sequence = i;       // isn't in this case, bump them all
        }                                        // down one
      }
    }
  }
  for(d=0;d<data.dashboards.length;d++){
    if(data.dashboards[d].sequence == newVal){   // then set [Development]'s new
      data.dashboards[d].sequence = end;         // index to what its meant to be
    } 
  } 
}

This code works, but I feel like I've gone the long way and simply going from

[Sales] [Admin] [Finance] [Development]

to

[Sales] [Development] [Admin] [Finance]

Should be that hard

Edit: Still learning, so even unrelated ways to improve my code would be great

\$\endgroup\$
1
\$\begingroup\$

Fundamentally, you should probably use the array's own ordering. It'd remove the need for a sequence number that you manually have to track and update. Instead, you can simply pluck index 3 out of the array, and splice it in at index 1, and not worry about sequence numbers at all.

But if you can't/won't get the data.dashboards array to behave like that, you can take a detour.

Now, I highly, highly doubt this is the most efficient way of doing things - it's certainly not elegant - but it's straightforward to follow.

Say you have this:

var data = {
    dashboards: [ // random ordering
        {name: "Development", sequence: 3},
        {name: "Sales", sequence: 0},
        {name: "Finance", sequence: 2},
        {name: "Admin", sequence: 1}
    ]
};

Get everything sorted first:

var sorted = data.dashboards.sort(function(a, b) {
    return a.sequence - b.sequence;
});

So now we have an array where the objects are ordered according to their sequence

seq | name
------------------
0   | Sales
1   | Admin
2   | Finance
3   | Development

Then, if we can assume that the sequence numbers are indeed sequential with no gaps or repeats, we could (again) just splice & splice to get the right order.
But let's say we can be sure that index equals sequence number. In that case, loop until we find it, pluck it, and reinsert it:

var currSequence = 3, // the target's current number
    newSequence  = 1, // the target's desired number
    dashboard;

// find and move the target
for( var i = 0, l = sorted.length ; i < l ; i++ ) {
    if( sorted[i].sequence === currSequence ) {
        dashboard = sorted[i];
        sorted.splice(i, 1); // remove the target
        sorted.splice(newSequence, 0, dashboard); // re-insert it at its new position
        break;
    }
}

Now, we have the right order, but not the right sequence numbers:

seq | name
------------------
0   | Sales
3   | Development
1   | Admin
2   | Finance 

Finally, reset the sequence numbers

for( i = 0, l = sorted.length ; i < l ; i++ ) {
    sorted[i].sequence = i;
}

And we get

seq | name
------------------
0   | Sales
1   | Development
2   | Admin
3   | Finance

Neatly sequential numbering and ordering, regardless of the original array's order and sequence values. The original data.dashboards array is still in the same scrambled order as before, but the sequence number of each object is now correct.

Here a jsfiddle


Actually, here's a simpler one that I believe works in all cases, regardless of array ordering, as long as the sequence numbers are sequential already:

var currSequence = 3, // the target's current number
    newSequence  = 1, // the target's desired number
    correction = currSequence > newSequence ? 1 : -1,
    lower = Math.min(currSequence, newSequence),
    upper = Math.max(currSequence, newSequence),
    dashboard;

for( var i = 0, l = data.dashboards.length ; i < l ; i++ ) {
    dashboard = data.dashboards[i];
    if( dashboard.sequence == currSequence ) {
        dashboard.sequence = newSequence;
    } else if( dashboard.sequence >= lower && dashboard.sequence <= upper ) {
        dashboard.sequence += correction;
    }
}

Here's a demo

\$\endgroup\$
4
  • \$\begingroup\$ Thanks for the reply, I can see you went to a lot of effort. The only issue I can is that you order the tabs then reset the sequence numbers. There was something I didn't mention, data.dashboards[i] also contains a charts[] property that contains data to generate charts specific to that tab, so unless I am mistaken, those charts would get shuffled around and placed on the wrong tabs, right? \$\endgroup\$ – Andy Apr 3 '13 at 8:00
  • \$\begingroup\$ Nevermind, I've used your later, simpler one and that works just as my current code works, yet obviously a lot cleaner. Thanks a lot! :) My only question is why var i = 0, l = data.dashboards.length ; i < l ; instead of i=0;i<data.dashboards.length; \$\endgroup\$ – Andy Apr 3 '13 at 8:52
  • \$\begingroup\$ @Andy Not sure I understand your first question (not that it matters now). Anyway, the var i = 0, l = data.dashboards.length is a habit of mine. Getting the length once and storing it as l is faster, because the loop can do the i < l comparison on each iteration, instead of getting the array's length each time. Getting length is slower, since length is calculated when you ask for it. Of course, it doesn't matter at all for a small array like this. For large arrays, it'll help, though. I just always do it out of habit. Good practice, but unnecessary in this case. \$\endgroup\$ – Flambino Apr 3 '13 at 9:10
  • \$\begingroup\$ Yeah don't worry about the first question. I'll stick to the shorter code inside the loop purely because there can never be more than 10 dashboards, so you're right it won't affect performance. Though I will definitely keep that in mind for the future. Thanks for all the help. \$\endgroup\$ – Andy Apr 3 '13 at 9:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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