2

I found an example here https://jdanyow.github.io/aurelia-converters-sample/ (search for SortValueConverter) - Just need to somehow extend the toView function:

export class SortValueConverter {
  toView(array, propertyName, direction) {
    var factor = direction === 'ascending' ? 1 : -1;
    return array
      .slice(0)
      .sort((a, b) => {
        return (a[propertyName] - b[propertyName]) * factor
      });
  }
}

so that it does not only sort each row, but so that it:

  1. sorts each row group object - inside the array of table rows - which has the boolean isGroup: true set
  2. Together with sorting the row itself, it should however also sort the row group children belonging under each group
  3. And of course these rows would also need to be moved together with their group rows, so they stay below the group row.

In my js code I've flattened the array, so I can show it in a table. isGroup:true/false marks when a new group begins.

  • robots (isGroup: true)
  • R2-D2
  • terminator
  • wall-e
  • robocop
  • C-3PO
  • animals (isGroup:true)
  • dog
  • shark
  • cat
  • monkey

How could you rewrite the sort value converter (ideally) to sort both row groups and row group children eg. alphabetically asc/desc. I guess it doesn't have to be done using a value converter but could in theory just be done directly on the array in the js code, but I think the best solution would be to extend the sort value converter to support this row grouping also.

To keep this example as "simple" as possible, let's reduce the row objects to a bare minimum of:

{
   name: 'group or robot name'
   isGroup: true/false
}

so eg.

[
    {
        name: 'robots',
        isGroup: true
    },
    {
        name: 'R2-D2',
        isGroup: false
    },
    {
        name: 'terminator',
        isGroup: false
    },
    {
        name: 'wall-e',
        isGroup: false
    },
    {
        name: 'robocop',
        isGroup: false
    },
    {
        name: 'C-3PO',
        isGroup: false
    },
    {
        name: 'animals',
        isGroup: true
    },
    {
        name: 'dog',
        isGroup: false
    },
    {
        name: 'shark',
        isGroup: false
    },
    {
        name: 'cat',
        isGroup: false
    },
    {
        name: 'monkey',
        isGroup: false
    }
]

Any ideas?

Dac0d3r
  • 2,176
  • 6
  • 40
  • 76

2 Answers2

2

I would recommend slightly changing your data structure. The data structure as you currently have it is somewhat ambiguous, since the ordering of the list determines which group an item is in. I would change the structure to look like this:

[
  {
    name: 'R2-D2',
    group: 'robots'
  },
  //...

Then, we can group and sort this array using a value converter that looks like this:

export class GroupedSortValueConverter {
  toView(value) {

    let sortedArray = value.splice(0).sort((a, b) => {
      const left = a.name,
        right = b.name;

      return caseInsensitiveStringComparer(left, right);
    });

    const groups = new Map();

    for (let item of sortedArray) {
      let group = groups.get(item.group);
      if (group === undefined) {
        groups.set(item.group, [item]);
      } else {
        group.push(item);
      }
    }

    let sortedGroups = new Map(Array.from(groups.entries()).sort((a, b) => {
      const left = a[0];
      const right = b[0];

      return caseInsensitiveStringComparer(left, right);
    }));

    return sortedGroups;
  }
}

function caseInsensitiveStringComparer(left, right) {
  const lowerLeft = left.toLowerCase(),
    lowerRight = right.toLowerCase();

  if (lowerLeft < lowerRight) { //sort string ascending
    return -1;
  } else if (lowerLeft > lowerRight) {
    return 1;
  } else {
    return 0;
  }
}

And we can iterate this Map using code like this. Note that I'm having to use a template element to wrap the group repeater as there is not a single "natural wrapping element" for the repeater:

<template>
  <require from="./grouped-sort"></require>
  <table>
    <template repeat.for="[group, items] of myArray | groupedSort">
      <tr>
        <td>
          ${group}
        </td>
      </tr>
      <tr repeat.for="item of items">
        <td>
          ${item.name}
        </td>
        </tr>
    </template>
  </table>
</template>

A runnable gist is here: https://gist.run/?id=27a6e61996cb884d97fbeed0acb310bf

Note that I used this StackOverflow answer to help produce this answer: Is it possible to sort a ES6 map object?

Community
  • 1
  • 1
Ashley Grant
  • 10,879
  • 24
  • 36
  • Hi Ashley, thanks a lot for your solution - we're almost there. I noticed it doesnt sort the items inside the groups, so I thought I could just use the SortValueConverter I linked to in the original post like this: `
  • ${item.name}
  • ` - didn't seem to work though. What would be the best way to also sort the items inside the groups? – Dac0d3r Feb 22 '17 at 16:41
  • 1
    Oops, I forgot to do that. I'll add that in. – Ashley Grant Feb 22 '17 at 16:44
  • Thanks! Also I just noticed. I need to display this as rows in a table, at the same level (flattened) like jeff is doing, – Dac0d3r Feb 22 '17 at 16:49
  • I've updated the answer. Another option might be to sort the full array before grouping it. – Ashley Grant Feb 22 '17 at 17:07
  • Amazing. Thank you so much! – Dac0d3r Feb 22 '17 at 18:41
  • While your code works great when running the gist, I can't seem to get it working in my aurelia webpack skeleton (latest). It almost works, but taking your exact example and implementing it in webpack skeleton (latest) it fails where you do this: `groups.entries()` - I'm getting: http://imgur.com/a/6osSV instead. :/ I've spent 12 hours so far trying everything I could to figure this out, but I'm lost here. The array (value) is the same, the map (groups) looks exactly the same but something is different in the conversion with the spread operator on the map. – Dac0d3r Feb 24 '17 at 13:50
  • I've put a running example up here which confirms the issue. https://github.com/BruceL33t/skeleton-esnext-webpack-map-entries-not-working – Dac0d3r Feb 24 '17 at 14:46
  • 1
    I'm gonna spend some time playing with this. It seems to be transpiling incorrectly or something. – Ashley Grant Feb 24 '17 at 15:54
  • Thanks again Ashley. Appreciate it a lot. I've created at new question for this: http://stackoverflow.com/questions/42442779/es6-map-entries-doesnt-work-in-aurelia-webpack-skeleton – Dac0d3r Feb 24 '17 at 16:04
  • 1
    I've updated my answer above to use `Array.from()` instead of the spread operator. The way the spread operator is being transpiled is apparently broken. – Ashley Grant Feb 24 '17 at 17:47
  • 2
    Also, I want to note that this answer really isn't great from a performance standpoint if the list you are working with grows large, or happens to be poorly ordered to begin with. If you are dealing with large lists of data, and it is at all possible, you should consider doing this sorting and grouping on the server for better client-side performance. – Ashley Grant Feb 24 '17 at 18:02
  • Works like a charm. Thanks again... I guess I was a bit "tunnel visioned" and saw it as the entries() (iterator) which was wrong, when it was indeed the spread operator. I realize this is maybe not super performant, however the real live statistics and aggregated sums per row group (and per column in each row group) is quite advanced, so I'd need the flexibility of Javascript and Aurelia to be able make all those demanding db requests compute into 1 table, recalculating sums after each query. Thank you for following up and helping yet again. – Dac0d3r Feb 24 '17 at 18:53