views:

477

answers:

1

I have a project that requires the following.

Four arrays will be declared in the code as such:

var ROW1 = ['module1'];
var ROW2 = ['module2', 'module3'];
var ROW3 = ['module4', 'module5', 'module6'];
var ROW4 = ['module7', 'module8'];

Each element of these arrays represents a module in a configurable HTML widget. Based on what elements are specified in each array, the code will build an HTML that represents these "modules" - each "ROWx" array will be a table row (), and each module will be a table cell () in the table.

ROW1 can be empty (in which case it won't be included in the HTML) or have one module, "module1."

ROW2, ROW3, and ROW4 can each have between 0 and 3 modules (any of module2 - module8, in any order).

Using the example arrays above, I'd programmatically generate an HTML table like this:

<table>
  <tr id="row1">
    <td colspan="6">[module1]</td>
  </tr>
  <tr id="row2">
    <td colspan="3">[module2]</td>
    <td colspan="3">[module3]</td>
  </tr>
  <tr id="row3">
    <td colspan="2">[module4]</td>
    <td colspan="2">[module5]</td>
    <td colspan="2">[module6]</td>
  </tr>
  <tr id="row4">
    <td colspan="3">[module7]</td>
    <td colspan="3">[module8]</td>
  </tr>
</table>

(The colspans are just there to preserve the proper table layout; 6 is the lowest common multiple between 1, 2, and 3.)

The problem is, the content for these modules is populated dynamically (I get it from an AJAX call to a URL that returns a JSON object), and content is not always available for every module. Rather than creating an empty cell () for a module with no content, I want to remove that cell completely from the layout, acting like it was never in the original configuration (i.e. the arrays at top).

I've already spent a lot of time creating code that abstracts the module creation - it creates a "shell" HTML layout based on how many modules are in each row, and then appends content to the appropriate table cell based on which module should be there. I'd like to be able to continue using that code, so I think the best way to do this is to go through each element in the arrays before building the "shell" , and if I don't have content, remove that element from the array. Then I can use the newly modified array to build the "shell" per usual - I'm basically just processing it beforehand to check for content in each module, and if none is available, acting like that module was never set in the initial array (by removing it in the new array and then using that to build the "shell" ).

For example, let's say the configuration is set up as such:

var ROW1 = ['module1'];
var ROW2 = ['module4', 'module2'];
var ROW3 = ['module5'];
var ROW4 = ['module7', 'module3', 'module8'];

I want to go through each element in each array and check for available content in the module. Let's say that there is no content available for "module3" and "module5." I want to then end up with these arrays:

var ROW1 = ['module1'];
var ROW2 = ['module4', 'module2'];
var ROW3 = ['module7', 'module8'];

Please note what happened to the rows - since "module5" was removed, the elements from the ROW4 array were shifted into ROW3, and ROW4 was then deleted. Also, in the new ROW3 (formerly ROW4), "module3" was removed, sliding "module8" from position [2] into position [1].

So:

  • For any rows that have one or more elements remaining after removing modules that don't have any content, I'd like to keep the row intact, even with only one module.
  • For any rows that end up with no elements remaining, I'd like to shift each successive row "up" one, into the previous array, basically pulling a row out and shifting the lower ones up. The HTML from this example would look like this:

BEFORE (as requested by configuration):

<table>
  <tr id="row1">
    <td colspan="6">[module1]</td>
  </tr>
  <tr id="row2">
    <td colspan="3">[module4]</td>
    <td colspan="3">[module2]</td>
  </tr>
  <tr id="row3">
    <td colspan="6">[module5]</td>
  </tr>
  <tr id="row4">
    <td colspan="2">[module7]</td>
    <td colspan="2">[module3]</td>
    <td colspan="2">[module8]</td>
  </tr>
</table>

AFTER (based on what content is actually available)

<table>
  <tr id="row1">
    <td colspan="6">[module1]</td>
  </tr>
  <tr id="row2">
    <td colspan="3">[module4]</td>
    <td colspan="3">[module2]</td>
  </tr>
  <tr id="row3">
    <td colspan="3">[module7]</td>
    <td colspan="3">[module8]</td>
  </tr>
</table>

Recall that ROW1 is a special case, in that it doesn't affect other rows. If content is available for "module1" (the only module that can go in ROW1), then ROW1 should exist untouched. If no content is available, then ROW1 can be deleted, but still leaving ROW2, ROW3 and ROW4 as-is (i.e. not shifting them each up a row). I really only need a solution for handling ROW2, ROW3, and ROW4, since the logic for ROW1 is pretty simple.

I'm a beginner/intermediate JavaScript programmer, and don't have a lot of experience with arrays. I've been working on this on and off for hours, but just don't feel that what I'm coming up with is a robust or elegant/compact method.

If any JavaScript gurus can share a solution for this, I would VERY much appreciate it! Thanks a bunch.

+4  A: 

First of all, store your rows in an array as well, your ROW1 - ROW4 are going to give you headaches.

var ROWS = [
 ['module1'],
 ['module4', 'module2'],
 ['module5'],
 ['module7', 'module3', 'module8']
];

You also want a way to remove an element. .splice() is built into arrays, and works great to remove elements.

// lets clear out our disabled modules

var disabledModules = ['module3', 'module5'];

// quick sample function - yours might check for empty content or whatever else...
function isDisabled(module) {  
  for (var i=0, test; test=disabledModules[i]; i++) {
    if (test === module) return true;
  }
  return false;
}

for(var i=0,row; row=ROWS[i]; i++) {
  for (var j=0,module; module=row[j]; j++) {
    // is this module in our disabled modules list?

    if (isDisabled(module)) { // the module is disabled
      row.splice(j,1);
      j--; // so we recheck this point in the array
    }
  }
  if (row.length < 1) {
    // all items were deleted
    ROWS.splice(i,1);
    i--; // so we recheck this point..
  }
}

// ROWS === [["module1"], ["module4", "module2"], ["module7", "module8"]]

Your comment asked about the for loop syntax used. It's a pretty quick way to loop through arrays you know won't have "false" values. Breaking one apart and turning on verbose logging:

 for(
  // Part 1 of for loop: Setup, create two variables, i=0, and row.
  var i=0,row; 
  // Part 2: The test - when false, loop will exit, gets executed pre-loop every time
  // Assignment operator returns the value assigned, so row=ROWS[i] will be undefined
  // (false) when we reach then end of the array.
  row=ROWS[i]; 
  // Part 3: Post loop - increment i
  i++) {

The dangers of using this method do exist though, if its an array of numbers for instance, and one of those numbers is 0, the loop will exit early. The alternative would be to write:

for(var i=0; i<ROWS.length; i++) { 
  var row = ROWS[i];
}
gnarf
You can grab the Array prototypal indexOf method here for use in browsers which don't implement it: https://developer.mozilla.org/Talk:en/Core_JavaScript_1.5_Reference/Global_Objects/Array
meder
Thanks for reminding me on that one (its a part of my global include script) - rewrote to use a different function anyway.
gnarf
Thanks SO much, gnarf - this is very helpful. I appreciate you taking the time. I think I was on the right track - I had begun to implement a pair of nested loops, but I got nervous when considering the implications of decrementing the counter within the loop if an array element was deleted. Looks like that's not a problem after all, though.I'm going to implement this in my project now, and will add another comment if I get stuck.Incidentally, it's probably JavaScript 101, but I haven't seen the syntax you used in setting up the for loops before - what exactly are those doing?
Bungle
Updated to show what exactly those for loops are doing.
gnarf
gnarf - I implemented this based on your example, and it works like a charm! I also appreciate the explanation of the for loop setup syntax. I think I get it now, and the caveat makes sense. Thanks again.
Bungle