views:

1305

answers:

6

Greetings Y'all.

It's my first time asking a question but having perused the site for about 8 months I think it's an appropriate one.

Basically I'm trying to re-sort the child elements of the tag "input" by comparing their category attribute to the category order in the Javascript variable "category_sort_order". Then I need to remove divs whose category attribute does not appear in "category_sort_order".

The expected result should be:

any product1 product2 download


the code

<div id="input">
<div category="download">download</div>
<div category="video">video1</div>
<div category="video">video2</div>
<div category="product">product1</div>
<div category="any">any</div>
<div category="product">product2</div>
</div>

<script type="text/javascript">
var category_sort_order = ['any', 'product', 'download'];
</script>

I really don't even know where to begin with this task but if you could please provide ANY assistance whatsoever I would be extremely grateful. Thank you regardless.

+4  A: 

Here is how to do it. I used this SO question as a reference.

I tested this code and it works properly for your example:

$(document).ready(function() {
    var categories = new Array();
    var content = new Array();
    //Get Divs
    $('#input > [category]').each(function(i) {
     //Add to local array
     categories[i] = $(this).attr('category');
     content[i] = $(this).html();
    });

    $('#input').empty();

    //Sort Divs
    var category_sort_order = ['any', 'product', 'download'];
    for(i = 0; i < category_sort_order.length; i++) {
     //Grab all divs in this category and add them back to the form
     for(j = 0; j < categories.length; j++) {
      if(categories[j] == category_sort_order[i]) {
       $('#input').append('<div category="' + 
                           category_sort_order[i] + '">' 
                           + content[j] + '</div>');
      }
     };
    }
});

How it works

First of all, this code requires the JQuery library. If you're not currently using it, I highly recommend it.

The code starts by getting all the child divs of the input div that contain a category attribute. Then it saves their html content and their category to two separate arrays (but in the same location.

Next it clears out all the divs under the input div.

Finally, it goes through your categories in the order you specify in the array and appends the matching child divs in the correct order.

The For loop section

@eyelidlessness does a good job of explaining for loops, but I'll also take a whack at it. in the context of this code.

The first line:

for(i = 0; i < category_sort_order.length; i++) {

Means that the code which follows (everything within the curly brackets { code }) will be repeated a number of times. Though the format looks archaic (and sorta is) it says:

  1. Create a number variable called i and set it equal to zero
  2. If that variable is less than the number of items in the category_sort_order array, then do whats in the brackets
  3. When the brackets finish, add one to the variable i (i++ means add one)
  4. Then it repeats step two and three until i is finally bigger than the number of categories in that array.

A.K.A whatever is in the brackets will be run once for every category.

Moving on... for each category, another loop is called. This one:

for(j = 0; j < categories.length; j++) {

loops through all of the categories of the divs that we just deleted from the screen.

Within this loop, the if statement checks if any of the divs from the screen match the current category. If so, they are appending, if not the loop continues searching till it goes through every div.

Michael La Voie
Thank you for your help.I'm a javascript novice and while I understand the first part of your code, I'm baffled by the 2nd part. What are the i=0 and j=0 lines for? I see these a lot and cannot understand.Thanks again.
hnovick
hnovic, those are iterator variables. They correspond to the numerical keys of the `Array` they iterate. A `for` loop (typically) takes as its first statement an initial key, its second statement a final key, and its final statement an instruction to move from the initial key to the final key (typically `i++` or `i--`).
eyelidlessness
This has the benefit of not manipulating the dom more than it has to, but it does use inline html which is a little yucky, especially when things start to change.
Alex Sexton
Thank you very much. You do a great job of explaining this.
hnovick
+1  A: 

I thought this was a really interesting problem, here is an easy, but not incredibly performant sorting solution that I came up with.

You can view the test page on jsbin here: http://jsbin.com/ocuta

function compare(x, y, context){
  if($.inArray(x, context) > $.inArray(y, context)) return 1; 
}
function dom_sort(selector, order_list) {
 $items = $(selector);
 var dirty = false;
 for(var i = 0; i < ($items.length - 1); i++) {
  if (compare($items.eq(i).attr('category'), $items.eq(i+1).attr('category'), order_list)) {
   dirty = true;
   $items.eq(i).before($items.eq(i+1).remove());
  }
 }
 if (dirty) setTimeout(function(){ dom_sort(selector, order_list); }, 0); 
};
dom_sort('#input div[category]', category_sort_order);

Note that the setTimeout might not be necessary, but it just feels safer. Your call.

You could probably clean up some performance by storing a reference to the parent and just getting children each time, instead of re-running the selector. I was going for simplicity though. You have to call the selector each time, because the order changes in a sort, and I'm not storing a reference to the parent anywhere.

Alex Sexton
This also will put categories that aren't found in the sort order array at the front of the list. If you want them at the end instead, check for -1 on the $.inArray function.
Alex Sexton
+3  A: 

Appending (or prepending) the DOM nodes again will actually sort them in the order you want.

Using jQuery, you just have to select them in the order you want and append (or prepend) them to their container again.

$(['any', 'product', 'video'])
 .map(function(index, category)
 { 
   return $('[category='+category+']');
 })
 .prependTo('#input');


Sorry, missed that you wanted to remove nodes not in your category list. Here is the corrected version:

// Create a jQuery from our array of category names, 
// it won't be usable in the DOM but still some 
// jQuery methods can be used
var divs = $(['any', 'product', 'video'])
// Replace each category name in our array by the
// actual DOM nodes selected using the attribute selector
// syntax of jQuery.
 .map(function(index, category)
 { 
  // Here we need to do .get() to return an array of DOM nodes
  return $('[category='+category+']').get();
 });
// Remove everything in #input and replace them by our DOM nodes.
$('#input').empty().append(divs);

// The trick here is that DOM nodes are selected 
// in the order we want them in the end.
// So when we append them again to the document,
// they will be appended in the order we want.
Vincent Robert
Thank you for your help. How does this work!? Can you please explain.Thanks again
hnovick
nice use of `map()`. The last item in the array should be `download` though :)
Russ Cam
I like it. It's a very elegant solution.
Borgar
Added some comments on where the tricks are :)
Vincent Robert
+5  A: 

I wrote a jQuery plugin to do this kind of thing that can be easily adapted for your use case.

The original plugin is here

Here's a revamp for you question

(function($) {

$.fn.reOrder = function(array) {
  return this.each(function() {

    if (array) {    
      for(var i=0; i < array.length; i++) 
        array[i] = $('div[category="' + array[i] + '"]');

      $(this).empty();  

      for(var i=0; i < array.length; i++)
        $(this).append(array[i]);      
    }
  });    
}
})(jQuery);

and use like so

var category_sort_order = ['any', 'product', 'download'];
$('#input').reOrder(category_sort_order);

This happens to get the right order for the products this time as product1 appears before product2 in the original list, but it could be changed easily to sort categories first before putting into the array and appending to the DOM. Also, if using this for a number of elements, it could be improved by appending all elements in the array in one go instead of iterating over the array and appending one at a time. This would probably be a good case for DocumentFragments.

Russ Cam
This is really elegant Russ. Nice work.
Alex Sexton
A: 

It's seems fairly direct to use the sort method for this one:

var category_sort_order = ['any', 'product', 'download'];

// select your categories
$('#input > div')

  // filter the selection down to wanted items
  .filter(function(){
    // get the categories index in the sort order list ("weight")
    var w = $.inArray( $(this).attr('category'), category_sort_order );
    // in the sort order list?
    if ( w > -1 ) {
      // this item should be sorted, we'll store it's sorting index, and keep it
      $( this ).data( 'sortindex', w );
      return true;
    }
    else {
      // remove the item from the DOM and the selection
      $( this ).remove();
      return false;
    }
  })

  // sort the remainder of the items
  .sort(function(a, b){
    // use the previously defined values to compare who goes first
    return $( a ).data( 'sortindex' ) - 
           $( b ).data( 'sortindex' );
  })

  // reappend the selection into it's parent node to "apply" it
  .appendTo( '#input' );

If you happen to be using an old version of jQuery (1.2) that doesn't have the sort method, you can add it with this:

jQuery.fn.sort = Array.prototype.sort;
Borgar
+2  A: 

Just note,

Since there is jQuery 1.3.2 sorting is simple without any plugin like:

$('#input div').sort(CustomSort).appendTo('#input');
function CustomSort( a ,b ){
     //your custom sort function returning -1 or 1
     //where a , b are $('#input div') elements
}

This will sort all div that are childs of element with id="input" .

Vaclav Kohout