ExtJS 4 Grid custom columns sorting (file manager style)
Asked Answered
E

3

7

I was about to make a basic file manager using ExtJS 4. The problem I faced now is: how to make custom sorting for grid panel when clicking the columns.

Imagine we have store fields:

[
    { name: "is_dir", type: "boolean" },
    { name: "name",   type: "string"  },
    { name: "size",   type: "int"     }
]

And the data which comes from an array:

[
    { is_dir: true,  name: "..",        size: 0    },
    { is_dir: false, name: "file2.txt", size: 512  },
    { is_dir: true,  name: "folder2",   size: 0    },
    { is_dir: false, name: "file3.txt", size: 1024 },
    { is_dir: true,  name: "folder1",   size: 0    },
    { is_dir: true,  name: "file1.txt", size: 1024 },
    // ...
]

The idea is to make sorting like in any file manager (e.g. Total Commander, MC, FAR, etc) so, that:

  • the item with name ".." is always placed at the top
  • dirs go after ".." (if exists) in sorted order
  • files go after dirs (if exist) in sorted order

For example, the output with sorting by name and by size should be:

^ Name           | Size               Name             | ^ Size
-----------------------               -------------------------
..               | 0                  ..               | 0
folder1          | 0                  folder1          | 0
folder2          | 0                  folder2          | 0
file1.txt        | 1024               file2.txt        | 512
file2.txt        | 512                file1.txt        | 1024
file3.txt        | 1024               file3.txt        | 1024

I've tried to write custom sorterFn for store sorters property, however it didn't help. I believe there should be some easy solution for that.

Egestion answered 17/10, 2012 at 10:12 Comment(0)
E
2

I have forked @Alexey's answer with short sorting algorithm. Additionally, I fixed the problem with double sorting (because of this.callParent(arguments); which runs the basic sorting from the parent method). Here is the solution that works perfectly for me:

sort: function(sorters) {
    sorters = sorters || { property: "name", direction: "ASC" };
    var mod = sorters.direction.toUpperCase() === "DESC" ? -1 : 1;

    this.sorters.clear();        // these lines are needed
    this.sorters.add(sorters);   // to update the column state

    this.doSort(function(a, b) {
        var a_type = a.get("is_dir"),
            b_type = b.get("is_dir"),
            a_name = a.get("name"),
            b_name = b.get("name");

        if (a_name === "..") return -1;
        if (a_type === b_type) {
            var a_prop = a.get(sorters.property),
                b_prop = b.get(sorters.property);

            if (a_prop === b_name) {
                return (a_name < b_name ? -1 : 1) * mod;
            } else {
                return (a_prop < b_prop ? -1 : 1) * mod;
            }
        } else {
            return b_type - a_type;
        }
    });
}

DEMO: http://jsfiddle.net/cvdNW/186/

Egestion answered 24/10, 2012 at 8:2 Comment(3)
Thanks for doing the demo...made it very easy to try it out with my own unique data.Railroad
I see no declaration of ' b_name 'Yehudi
@BogdanM. Very true, I've missed that above. Fixed now and updated the DEMO.Egestion
L
9

You can override the sort method of your store:

Ext.define('My.store.FileStore', {
    extend: 'Ext.data.Store',

    sort: function () {
        this.doSort(function() {
            // Custom sorting function
            console.log(arguments);
            return Math.random() > 0.5 ? 1 : -1; // :)
        });
    }
});

UPDATE

Ext.define('FileModel', {
    extend: 'Ext.data.Model',
    fields: [
        { name: "is_dir", type: "boolean" },
        { name: "name",   type: "string"  },
        { name: "size",   type: "int"     }
    ]
});

Ext.define('FileStore', {
    extend: 'Ext.data.Store',
    model: 'FileModel',
    data: [
        { is_dir: true,  name: "..",        size: 0    },
        { is_dir: false, name: "file2.txt", size: 512  },
        { is_dir: true,  name: "folder2",   size: 0    },
        { is_dir: false, name: "file3.txt", size: 1024 },
        { is_dir: true,  name: "folder1",   size: 0    },
        { is_dir: false, name: "file1.txt", size: 1024 },
    ],

    sorters: [{
        property: 'name',
        direction: 'ASC'
    }],

    sort: function(params) {
        var dir = params ? params.direction : 'ASC';
        var prop = params ? params.property : 'name';

        this.callParent(arguments); // UPDATE 2                    

        this.doSort(function(rec1, rec2) {
            var rec1sort = '';
            var rec2sort = '';

            if (rec1.get('is_dir') && rec2.get('is_dir')) {
                // both dirs
                if (rec1.get('name') == '..') {
                    return -1;
                }
                else if (rec2.get('name') == '..') {
                    return 1;
                }
                else {
                    return rec1.get('name').localeCompare(rec2.get('name')) * (dir == 'ASC' ? 1 : -1);;
                }
            }
            else if (rec1.get('is_dir') != rec2.get('is_dir')) {
                // file and dir
                if (rec1.get('is_dir')) {
                    if (rec1.get('name') == '..') {
                        return -2;
                    }
                    else {
                        return -1;
                    }
                }
                else {
                    if (rec2.get('name') == '..') {
                        return 2;
                    }
                    else {
                        return 1;
                    }
                }
            }
            else if (!rec1.get('is_dir') && !rec2.get('is_dir')) {
                // both files
                var result;
                if (typeof rec1.get(prop) == 'number') {
                    result = rec1.get(prop) - rec2.get(prop);
                    if (result == 0) {
                        result = rec1.get('name').localeCompare(rec2.get('name'));
                    }
                }
                else {
                    result = rec1.get('name').localeCompare(rec2.get('name'));
                }
                return dir == 'ASC' ? result : result * -1;
            }
        });
    }
});    

var grid = Ext.create('Ext.grid.Panel', {
    title: 'Files',
    store: Ext.create('FileStore'),
    renderTo: Ext.getBody(),
    columns: [{
        header: 'Name',
        dataIndex: 'name'
    }, {
        header: 'Size',
        dataIndex: 'size'    
    }]
});
Landonlandor answered 19/10, 2012 at 5:20 Comment(5)
OK, but sorterFn of sorters property does almost the same, doesn't it? If so, how to use it taking a certain column into account?Egestion
Almost the same. sorters are applied when store is loaded. But when you click a column sorters cut no ice.Landonlandor
Seems to work fine: jsfiddle.net/cvdNW. Thanks! However, if you click "Size" column couple of times, the sorting direction image (triangle) for some reason disappears. But for "Name" column it works perfectly. Do you know how to fix it?Egestion
Found. this.callParent was needed :)Landonlandor
+100000. Wasted half a friggin' day trying to find a way to change the default sort property for a column. Like the dataIndex is 'customKey' but the sort should be 'display'. Ugh.Endbrain
E
2

I have forked @Alexey's answer with short sorting algorithm. Additionally, I fixed the problem with double sorting (because of this.callParent(arguments); which runs the basic sorting from the parent method). Here is the solution that works perfectly for me:

sort: function(sorters) {
    sorters = sorters || { property: "name", direction: "ASC" };
    var mod = sorters.direction.toUpperCase() === "DESC" ? -1 : 1;

    this.sorters.clear();        // these lines are needed
    this.sorters.add(sorters);   // to update the column state

    this.doSort(function(a, b) {
        var a_type = a.get("is_dir"),
            b_type = b.get("is_dir"),
            a_name = a.get("name"),
            b_name = b.get("name");

        if (a_name === "..") return -1;
        if (a_type === b_type) {
            var a_prop = a.get(sorters.property),
                b_prop = b.get(sorters.property);

            if (a_prop === b_name) {
                return (a_name < b_name ? -1 : 1) * mod;
            } else {
                return (a_prop < b_prop ? -1 : 1) * mod;
            }
        } else {
            return b_type - a_type;
        }
    });
}

DEMO: http://jsfiddle.net/cvdNW/186/

Egestion answered 24/10, 2012 at 8:2 Comment(3)
Thanks for doing the demo...made it very easy to try it out with my own unique data.Railroad
I see no declaration of ' b_name 'Yehudi
@BogdanM. Very true, I've missed that above. Fixed now and updated the DEMO.Egestion
Y
0

Sencha's Ext.Store contains now a new config object called

E.g.:

Store config:

   groupField: 'type', // here you can setup a the attribute of the model to group them by
   groupDir: 'DESC' // this is the direction

Model config:

   {
        name: 'createdByName',
        sortType: 'asUCText',
    },

Just by doing this and adding the sortType to the Models config, you are having now case insesnitive sorting on column name grouped by type

Yehudi answered 29/6, 2016 at 6:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.