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

asked Apr 2, 2013 at 16:35
\$\endgroup\$

1 Answer 1

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

answered Apr 2, 2013 at 19:12
\$\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\$ Commented Apr 3, 2013 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\$ Commented Apr 3, 2013 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\$ Commented Apr 3, 2013 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\$ Commented Apr 3, 2013 at 9:12

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.