Example: Color Sorting and Filtering

DataTable sorted by colors This example builds on the Column Sort example. It makes use of the Y.Color module. It also shows how to filter the Model List of the DataTable to get a subset of the data.

Click on the column headers to sort the color swatches in different ways. Filter the data with the radio buttons on the right.

Note: be sure to add the yui3-skin-sam classname to the page's <body> element or to a parent element of the widget in order to apply the default CSS skin. See Understanding Skinning.

<body class="yui3-skin-sam"> <!-- You need this skin class -->

To add column sorting functionality to any DataTable, simply include the datatable-sort module in your use() line and add the sortable: true configuration to the configuration objects of the columns you want to be sortable, as shown in the Column Sorting example.

Note, if you want all columns to be sortable, simply set sortable: true on the DataTable instance.

YUI().use("datatable-sort", function(Y) {
    // code goes here
});

This example also uses the color-harmony module

YUI().use("datatable-sort", 'color-harmony', function(Y) {
    // code goes here
});

Color Data

The color data for this example comes from http://www.w3.org/TR/css3-color/#svg-color. The data contains only the name of the color and its hex value.
var myData = [
        {name: 'antiquewhite', hex: 'faebd7'},
        {name: 'aqua', hex: '00ffff'},
        {name: 'aquamarine', hex: '7fffd4'},
        // ... a lot more colors
        {name: 'whitesmoke', hex: 'f5f5f5'},
        {name: 'yellow', hex: 'ffff00'},
        {name: 'yellowgreen', hex: '9acd32'}
    ];

Generating a Color Number

Sorting and filtering can help users find the right color. Hex values don't help much in sorting the colors in a visually logical way. Later in the code, the following function will use the Y.Color module to generate a special color number for each hex value. This color number will be more useful in sorting and filtering.

/* This takes a hex value and generates a numeric string
*  in the format "01.23.45"
*  Each pair of digits represents the Hue, Saturation, or Brightness
*  of the color in a range of 00 to 99 (always 2 digits).
*  These strings sort in a more visually logical way for colors.
*  The sequence of digits can be changed to sort in different ways.
*  such as hh.ss.bb for sorting first by hue, then saturation,
*  or bb.hh.ss for sorting first by brightness, then hue, then saturation.
*  The dots are only for human readability
*/
function getColorNum(hex, sortType) {
        // An array of color property values
        // [hue, saturation, lightness]
    var HSL = Y.Color.toArray(Y.Color.toHSL(hex)),
        hNum, // hue
        sNum, // saturation
        // get the computed brightness from the hex value
        bNum = Y.Color.getBrightness(hex).toString();
    
    // isolate hue part from HSL. converted to 2 digits
    hNum = Math.floor((HSL[0] * 10) / 36.01).toString();
    hNum = (hNum.length < 2) ? ('0' + hNum) : hNum;

    // isolate sat part from HSL. converted to 2 digits
    sNum = Math.floor((HSL[1] * 10) / 10.01).toString();
    sNum = (sNum.length < 2) ? ('0' + sNum) : sNum;

    // assure brightness is 2 digits
    bNum = (bNum.length < 2) ? ('0' + bNum) : bNum;
    bNum = (bNum === "100") ? "99" : bNum;

    if (sortType === "hbs") {
        return hNum + '.' + bNum + '.' + sNum; // hue.bright.sat
    } else if (sortType === "bhs") {
        return bNum + '.' + hNum + '.' + sNum; // bright.hue.sat
    }
}

The DataTable Instance

The DataTable instance will include:
  • Column configs that add new columns
  • A recordType that will populate the DataTable's ModelList so that some of the additional columns can be sortable
  • Data
  • An initial sorting config
myTable = new Y.DataTable({
    columns: [
        // code goes here
    ],
    recordType: {
        // code goes here
    },
    data: myData,
    sortBy: { hbs: 'desc' }
}).render("#cTable");

We'll now add the details to the DataTable instance.

Formatter - Adding Columns

The provided data from var = myData only has two properties per row to use as columns, but we are adding more columns in the columns config. A Swatch column allows users to see the colors. We used a formatter to set the content to be a <div> with the background color accessed from the hex value of each row. Since there's no swatch property in myData, this column cannot be made sortable.

We're also adding 2 columns that will allow sorting by Hue and Brightness priorities respectively. The DataTables ModelList (where myData is stored) doesn't contain any data for these columns yet. That will be added by the recordType. See next snippet.

myTable = new Y.DataTable({
    columns: [
        { key: "swatch",
          label: 'Swatch',
          // Use formatter to add a div swatch in each cell
          // Color the background-color from hex value in each row
          formatter: function(o) {
            return '<div class="swatch" style="' +
            'background-color: #' + o.data.hex + ';' +
            '"></div>';
          },
          sortable: false,
          allowHTML: true
        },
        { key: "name",
          label: 'Color Name',
          sortable: true
        },
        { key: "hex",
          label: 'Hex',
          sortable: true
        },
        { key: "hbs",
          label: 'Hue', // hue.bright.sat
          sortable: true
        },
        { key: "bhs",
          label: 'Bright', // bright.hue.sat
          sortable: true
        }
    ],
    recordType: {
        // see the next code snippet below
    },
    data: myData,
    sortBy: { hbs: 'desc' }
}).render("#cTable");

RecordType - Sortable Generated Columns

As described in the Sortable Generated Columns example, we're using a getter to return a color number string from the getColorNum function shown above. This puts these values in the DataTable's ModelList so they'll be filterable and the columns can be sortable.

myTable = new Y.DataTable({
    columns: [
        // see the previous code snippet above
    ],
    recordType: {
        swatch: {},
        name: {},
        hex: {},
        hbs: {
            getter: function() {
                // create a new attribute in the model
                // and generate a sortable color number
                // from the hex value for each Model
                return getColorNum(this.get('hex'), 'hbs');
            }
        },
        bhs: {
            getter: function() {
                // do the same for bright.hue.sat
                return getColorNum(this.get('hex'), 'bhs');
            }
        }
    },
    data: myData,
    sortBy: { hbs: 'desc' } // initial sorting
}).render("#cTable");

Filtering

The filterModel function filters the original data of the DataTable to get a subset. See the example Filtering the Model List for a simpler example of filtering.

// This filters the DataModel to have a subset of it's original
// data
function filterModel(filterType) {
    // reset model list to include all colors to prepare for filter
    myTable.set('data', myData);

    var list = myTable.data,
        filteredData = list.filter({asList: true}, function (list) {
            var hbsStr = list.get('hbs'), // get the hue emphasized color number
                bright = hbsStr.substring(3, 5), // only the 2 brightness digits
                sat = hbsStr.substring(6, 8), // only the 2 saturation digits
                type; // clicked filter type

            // Depending on which filter radio was clicked,
            // filter the DataTable's modelList to include a
            // subset of models/rows/colors
            if (filterType === 'filter-all') {
                // Don't show black, white, or grays
                type = sat >= 1;
            } else if (filterType === 'filter-tint') {
                // Bright colors that are not gray or white
                type = (bright >= 75) && (sat >= 1);
            } else if (filterType === 'filter-shade') {
                // Darker colors that are not grayish
                type = (bright <= 50) && (sat >= 20);
            } else if (filterType === 'filter-midtone') {
                // Middle brightness colors that are not grayish
                type = (bright >= 50) && (bright <= 75) && (sat >= 20);
            } else if (filterType === 'filter-mute') {
                // Low saturation (grayish) but not pure grays
                type = (sat <= 55) && (sat >= 1);
            } else if (filterType === 'filter-gray') {
                // Only colors that are completely gray
                type = sat === '00';
            }

            return type;
        });
    myTable.set('data', filteredData);
}

The filterModel function is used in these ways.

// initial filtering of the dataTable's modelList to muted colors
filterModel('filter-mute');

// listen for filter change
Y.all('.filters').on('click', function(e) {
    filterModel(e.target.get('id'));
});

Full Code Listing

The CSS

.example {
    zoom: 1;
}
.example:after {
    content: "";
    display: block;
    clear: both;
}
.left-column {
    overflow: hidden;
    float: left;   
}
.right-column {
    margin-left: 390px;
}
/* Swatch styling */
.yui3-skin-sam td.yui3-datatable-cell {
    /* override vertical cell padding so swatches fill cells */
    padding: 0 0.5em;
}
.yui3-skin-sam td.yui3-datatable-col-swatch {
    padding: 0;
}
.swatch {
    height: 1.4em;
}
#cTable .yui3-datatable-cell {
    font-size: 70%;
}
#cTable .view-small .yui3-datatable-cell {
    font-size: 25%;
}

/* layout styles */
#small-swatch-checkbox {
    margin-top: 4em;
}
.filter-radios label {
    display: block;
}
.example .content {
    padding: 0 5px;
}

/* Styles for UI sample swatches in filtering radios */
.filter-radios label span {
    background: url(../assets/datatable/img/swatch_colors.png) no-repeat;
    display: inline-block;
    vertical-align: middle;
    height: 30px;
    width: 80px;
    margin: 0.3em;
}
.filter-radios .examples-all {
    background-position: 0px 0px;
}
.filter-radios .examples-tint {
    background-position: 0px -30px;
}
.filter-radios .examples-midtone {
    background-position: 0px -60px;
}
.filter-radios .examples-shade {
    background-position: 0px -90px;
}
.filter-radios .examples-mute {
    background-position: 0px -120px;
}
.filter-radios .examples-gray {
    background-position: 0px -150px;
}

The HTML

<div class="left-column">
        <div id="cTable"></div>
</div>
<div class="right-column">
    <div class="filter-radios">
        <!-- Radios for filtering colors -->
        <label for="filter-all">
            <input id="filter-all" class="filters" type="radio" name="filters"/>
            <span class="examples-all"></span>All Colors
        </label>
        <label for="filter-tint">
            <input id="filter-tint" class="filters" type="radio" name="filters"/>
            <span class="examples-tint"></span>Tints
        </label>
        <label for="filter-midtone">
            <input id="filter-midtone" class="filters" type="radio" name="filters"/>
            <span class="examples-midtone"></span>Mid-tones
        </label>
        <label for="filter-shade">
            <input id="filter-shade" class="filters" type="radio" name="filters"/>
            <span class="examples-shade"></span>Shades
        </label>
        <label for="filter-mute">
            <input id="filter-mute" class="filters" type="radio" checked name="filters"/>
            <span class="examples-mute"></span>Muted
        </label>
        <label for="filter-gray">
            <input id="filter-gray" class="filters" type="radio" name="filters"/>
            <span class="examples-gray"></span>Black, white, and grays
        </label>
    </div>
    <!-- Small swatches checkbox -->
    <label for="small-swatch-checkbox">
        <input id="small-swatch-checkbox" type="checkbox"/>
        Small swatches (fit more on the page)
    </label>
</div>

The JavaScript

YUI().use('datatable-sort', 'color-harmony', function (Y) {

    // The source of this data is http://www.w3.org/TR/css3-color/
    var filterBy,
        myData = [
            {name: 'antiquewhite', hex: 'faebd7'},
            {name: 'aqua', hex: '00ffff'},
            {name: 'aquamarine', hex: '7fffd4'},
            {name: 'beige', hex: 'f5f5dc'},
            {name: 'bisque', hex: 'ffe4c4'},
            {name: 'black', hex: '000000'},
            {name: 'blanchedalmond', hex: 'ffebcd'},
            {name: 'blue', hex: '0000ff'},
            {name: 'blueviolet', hex: '8a2be2'},
            {name: 'brown', hex: 'a52a2a'},
            {name: 'burlywood', hex: 'deb887'},
            {name: 'cadetblue', hex: '5f9ea0'},
            {name: 'chartreuse', hex: '7fff00'},
            {name: 'chocolate', hex: 'd2691e'},
            {name: 'coral', hex: 'ff7f50'},
            {name: 'cornflowerblue', hex: '6495ed'},
            {name: 'cornsilk', hex: 'fff8dc'},
            {name: 'crimson', hex: 'dc143c'},
            {name: 'cyan', hex: '00ffff'},
            {name: 'darkblue', hex: '00008b'},
            {name: 'darkcyan', hex: '008b8b'},
            {name: 'darkgoldenrod', hex: 'b8860b'},
            {name: 'darkgray', hex: 'a9a9a9'},
            {name: 'darkgreen', hex: '006400'},
            {name: 'darkkhaki', hex: 'bdb76b'},
            {name: 'darkmagenta', hex: '8b008b'},
            {name: 'darkolivegreen', hex: '556b2f'},
            {name: 'darkorange', hex: 'ff8c00'},
            {name: 'darkorchid', hex: '9932cc'},
            {name: 'darkred', hex: '8b0000'},
            {name: 'darksalmon', hex: 'e9967a'},
            {name: 'darkseagreen', hex: '8fbc8f'},
            {name: 'darkslateblue', hex: '483d8b'},
            {name: 'darkslategray', hex: '2f4f4f'},
            {name: 'darkturquoise', hex: '00ced1'},
            {name: 'darkviolet', hex: '9400d3'},
            {name: 'deeppink', hex: 'ff1493'},
            {name: 'deepskyblue', hex: '00bfff'},
            {name: 'dimgray', hex: '696969'},
            {name: 'dodgerblue', hex: '1e90ff'},
            {name: 'firebrick', hex: 'b22222'},
            {name: 'forestgreen', hex: '228b22'},
            {name: 'fuchsia', hex: 'ff00ff'},
            {name: 'gainsboro', hex: 'dcdcdc'},
            {name: 'gold', hex: 'ffd700'},
            {name: 'goldenrod', hex: 'daa520'},
            {name: 'gray', hex: '808080'},
            {name: 'green', hex: '008000'},
            {name: 'greenyellow', hex: 'adff2f'},
            {name: 'hotpink', hex: 'ff69b4'},
            {name: 'indianred', hex: 'cd5c5c'},
            {name: 'indigo', hex: '4b0082'},
            {name: 'khaki', hex: 'f0e68c'},
            {name: 'lawngreen', hex: '7cfc00'},
            {name: 'lemonchiffon', hex: 'fffacd'},
            {name: 'lightblue', hex: 'add8e6'},
            {name: 'lightcoral', hex: 'f08080'},
            {name: 'lightgoldenrodyellow', hex: 'fafad2'},
            {name: 'lightgray', hex: 'd3d3d3'},
            {name: 'lightgreen', hex: '90ee90'},
            {name: 'lightpink', hex: 'ffb6c1'},
            {name: 'lightsalmon', hex: 'ffa07a'},
            {name: 'lightseagreen', hex: '20b2aa'},
            {name: 'lightskyblue', hex: '87cefa'},
            {name: 'lightslategray', hex: '778899'},
            {name: 'lightsteelblue', hex: 'b0c4de'},
            {name: 'lime', hex: '00ff00'},
            {name: 'limegreen', hex: '32cd32'},
            {name: 'maroon', hex: '800000'},
            {name: 'mediumaquamarine', hex: '66cdaa'},
            {name: 'mediumblue', hex: '0000cd'},
            {name: 'mediumorchid', hex: 'ba55d3'},
            {name: 'mediumpurple', hex: '9370db'},
            {name: 'mediumseagreen', hex: '3cb371'},
            {name: 'mediumslateblue', hex: '7b68ee'},
            {name: 'mediumspringgreen', hex: '00fa9a'},
            {name: 'mediumturquoise', hex: '48d1cc'},
            {name: 'mediumvioletred', hex: 'c71585'},
            {name: 'midnightblue', hex: '191970'},
            {name: 'moccasin', hex: 'ffe4b5'},
            {name: 'navajowhite', hex: 'ffdead'},
            {name: 'navy', hex: '000080'},
            {name: 'oldlace', hex: 'fdf5e6'},
            {name: 'olive', hex: '808000'},
            {name: 'olivedrab', hex: '6b8e23'},
            {name: 'orange', hex: 'ffa500'},
            {name: 'orangered', hex: 'ff4500'},
            {name: 'orchid', hex: 'da70d6'},
            {name: 'palegoldenrod', hex: 'eee8aa'},
            {name: 'palegreen', hex: '98fb98'},
            {name: 'paleturquoise', hex: 'afeeee'},
            {name: 'palevioletred', hex: 'db7093'},
            {name: 'papayawhip', hex: 'ffefd5'},
            {name: 'peachpuff', hex: 'ffdab9'},
            {name: 'peru', hex: 'cd853f'},
            {name: 'pink', hex: 'ffc0cb'},
            {name: 'plum', hex: 'dda0dd'},
            {name: 'powderblue', hex: 'b0e0e6'},
            {name: 'purple', hex: '800080'},
            {name: 'red', hex: 'ff0000'},
            {name: 'rosybrown', hex: 'bc8f8f'},
            {name: 'royalblue', hex: '4169e1'},
            {name: 'saddlebrown', hex: '8b4513'},
            {name: 'salmon', hex: 'fa8072'},
            {name: 'sandybrown', hex: 'f4a460'},
            {name: 'seagreen', hex: '2e8b57'},
            {name: 'sienna', hex: 'a0522d'},
            {name: 'silver', hex: 'c0c0c0'},
            {name: 'skyblue', hex: '87ceeb'},
            {name: 'slateblue', hex: '6a5acd'},
            {name: 'slategray', hex: '708090'},
            {name: 'springgreen', hex: '00ff7f'},
            {name: 'steelblue', hex: '4682b4'},
            {name: 'tan', hex: 'd2b48c'},
            {name: 'teal', hex: '008080'},
            {name: 'thistle', hex: 'd8bfd8'},
            {name: 'tomato', hex: 'ff6347'},
            {name: 'turquoise', hex: '40e0d0'},
            {name: 'violet', hex: 'ee82ee'},
            {name: 'wheat', hex: 'f5deb3'},
            {name: 'white', hex: 'ffffff'},
            {name: 'whitesmoke', hex: 'f5f5f5'},
            {name: 'yellow', hex: 'ffff00'},
            {name: 'yellowgreen', hex: '9acd32'}
        ];

    /* This takes a hex value and generates a numeric string
    *  in the format "01.23.45"
    *  Each pair of digits represents the Hue, Saturation, or Brightness
    *  of the color in a range of 00 to 99 (always 2 digits).
    *  These strings sort in a more visually logical way for colors.
    *  The sequence of digits can be changed to sort in different ways.
    *  such as hh.ss.bb for sorting first by hue, then saturation,
    *  or bb.hh.ss for sorting first by brightness, then hue, then saturation.
    *  The dots are only for human readability
    */
    function getColorNum(hex, sortType) {
            // An array of color property values
            // [hue, saturation, lightness]
        var HSL = Y.Color.toArray(Y.Color.toHSL(hex)),
            hNum, // hue
            sNum, // saturation
            // get the computed brightness from the hex value
            bNum = Y.Color.getBrightness(hex).toString();
        
        // isolate hue part from HSL. converted to 2 digits
        hNum = Math.floor((HSL[0] * 10) / 36.01).toString();
        hNum = (hNum.length < 2) ? ('0' + hNum) : hNum;

        // isolate sat part from HSL. converted to 2 digits
        sNum = Math.floor((HSL[1] * 10) / 10.01).toString();
        sNum = (sNum.length < 2) ? ('0' + sNum) : sNum;

        // assure brightness is 2 digits
        bNum = (bNum.length < 2) ? ('0' + bNum) : bNum;
        bNum = (bNum === "100") ? "99" : bNum;

        if (sortType === "hbs") {
            return hNum + '.' + bNum + '.' + sNum; // hue.bright.sat
        } else if (sortType === "bhs") {
            return bNum + '.' + hNum + '.' + sNum; // bright.hue.sat
        }
    }

    myTable = new Y.DataTable({
        columns: [
            { key: "swatch",
              label: 'Swatch',
              // Use formatter to add a div swatch in each cell
              // Color the background-color from hex value in each row
              formatter: function(o) {
                return '<div class="swatch" style="' +
                'background-color: #' + Y.Escape.html(o.data.hex) + ';' +
                '"></div>';
              },
              sortable: false,
              allowHTML: true
            },
            { key: "name",
              label: 'Color Name',
              sortable: true
            },
            { key: "hex",
              label: 'Hex',
              sortable: true
            },
            { key: "hbs",
              label: 'Hue', // hue.bright.sat
              sortable: true
            },
            { key: "bhs",
              label: 'Bright', // bright.hue.sat
              sortable: true
            }
        ],
        recordType: {
            swatch: {},
            name: {},
            hex: {},
            hbs: {
                getter: function() {
                    // create a new attribute in the model
                    // and generate a sortable color number
                    // from the hex value for 
                    // hue.bright.sat
                    return getColorNum(this.get('hex'), 'hbs');
                }
            },
            bhs: {
                getter: function() {
                    // do the same for bright.hue.sat
                    return getColorNum(this.get('hex'), 'bhs');
                }
            }
        },
        data: myData,
        sortBy: { hbs: 'desc' } // initial sorting
    }).render("#cTable");


    // listener for swatch size
    Y.one('#small-swatch-checkbox').on('click', function(e) {
        var datatable = Y.one('#cTable .yui3-datatable');

        if (e.target.get('checked')) {
            datatable.addClass('view-small');
        } else {
            datatable.removeClass('view-small');
        }
    });

    // This filters the DataModel to have a subset of it's original
    // data
    function filterModel(filterType) {
        // reset model list to include all colors to prepare for filter
        myTable.set('data', myData);

        var list = myTable.data,
            filteredData = list.filter({asList: true}, function (list) {
                var hbsStr = list.get('hbs'), // get the hue emphasized color number
                    bright = hbsStr.substring(3, 5), // only the 2 brightness digits
                    sat = hbsStr.substring(6, 8), // only the 2 saturation digits
                    type; // clicked filter type

                // Depending on which filter radio was clicked,
                // filter the DataTable's modelList to include a
                // subset of models/rows/colors
                if (filterType === 'filter-all') {
                    // Don't show black, white, or grays
                    type = sat >= 1;
                } else if (filterType === 'filter-tint') {
                    // Bright colors that are not gray or white
                    type = (bright >= 75) && (sat >= 1);
                } else if (filterType === 'filter-shade') {
                    // Darker colors that are not grayish
                    type = (bright <= 50) && (sat >= 20);
                } else if (filterType === 'filter-midtone') {
                    // Middle brightness colors that are not grayish
                    type = (bright >= 50) && (bright <= 75) && (sat >= 20);
                } else if (filterType === 'filter-mute') {
                    // Low saturation (grayish) but not pure grays
                    type = (sat <= 55) && (sat >= 1);
                } else if (filterType === 'filter-gray') {
                    // Only colors that are completely gray
                    type = sat === '00';
                }

                return type;
            });
        myTable.set('data', filteredData);
    }

    // initial filtering of the dataTable's modelList to muted colors
    filterModel('filter-mute');

    // listen for filter change
    Y.all('.filters').on('click', function(e) {
        filterModel(e.target.get('id'));
    });
});