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
1 Answer 1
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;
}
}
-
\$\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 acharts[]
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– Andy2013年04月03日 08:00:40 +00:00Commented 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 ofi=0;i<data.dashboards.length;
\$\endgroup\$Andy– Andy2013年04月03日 08:52:19 +00:00Commented 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 asl
is faster, because the loop can do thei < l
comparison on each iteration, instead of getting the array'slength
each time. Gettinglength
is slower, sincelength
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– Flambino2013年04月03日 09:10:01 +00:00Commented 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\$Andy– Andy2013年04月03日 09:12:28 +00:00Commented Apr 3, 2013 at 9:12