2
votes

After posting the question ExtJS checkcolumn grid - check columns to left, uncheck columns to right and thinking there were existing questions and answers for a "select all" option, I've read a little deeper and they don't actually cover what I need in relation to my other question's answer.

I need to know what code is required to generate a checkbox in each column header that, when selected/deselected, changes the checkboxes in the given column.

Existing code for reference:

Ext.Loader.setConfig({
    enabled: true
});
Ext.Loader.setPath('Ext.ux', '../ux');

Ext.require([
    'Ext.ux.CheckColumn'
]);

Ext.onReady(function(){
    Ext.define('ProcessModel', {
        extend: 'Ext.data.Model',
        fields: [
            'Item',
            'Phase1',
            'Phase2',
            'Phase3',
            'Phase4',
            'Phase5',
            'Phase6',
            'Phase7',
            'Phase8',
            'Phase9',
            'Phase10'
        ]
    });

    // create the Data Store
    var processStore = Ext.create('Ext.data.Store', {
        model: 'processModel',
        autoLoad: true,
        proxy: {
            // load using HTTP
            type: 'ajax',
            url: '<?= $site_url ?>/Production/Processes/<?= $itemId ?>',
            reader: {
                type: 'json',
                model: 'ProcessModel',
                root: data
            }
        }
    });

    function onCheckChange (column, rowIndex, checked, eOpts) {
        var record = processStore.getAt(rowIndex);
        var columnIndex = column.getIndex();
        for (var i = 1; i <= 10; i++) {
            if(checked) {
                if (i <= columnIndex) {
                    record.set('Phase'+i, true);
                }
                else
                {
                    record.set('Phase'+i, false);
                }
            }
            else {
                if (i >= columnIndex) {
                    record.set('Phase'+i, false);
                }
            }

        }
    }

    Ext.create('Ext.grid.Panel', {
        width: 800,
        store: processStore,
        title: 'Processes',
        tbar: [
            {
                xtype: 'button',
                text: 'Update',
                handler: function(){
                    //TODO: update by POST function
                }
            }
        ],
        columns: [
            {
                text: 'Item',
                dataIndex: 'Item'
            },{
                xtype: 'checkcolumn',
                text: 'Phase 1',
                dataIndex:'Phase1',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 2',
                dataIndex:'Phase2',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 3',
                dataIndex:'Phase3',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 4',
                dataIndex:'Phase4',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 5',
                dataIndex:'Phase5',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',,
                listeners: {
                    checkChange: onCheckChange
                }
                text: 'Phase 6',
                dataIndex:'Phase6',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 7',
                dataIndex:'Phase7',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 8',
                dataIndex:'Phase8',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 9',
                dataIndex:'Phase9',
                listeners: {
                    checkChange: onCheckChange
                }
            },{
                xtype: 'checkcolumn',
                text: 'Phase 10',
                dataIndex:'Phase10',
                listeners: {
                    checkChange: onCheckChange
                }
            }
        ],
        renderTo: Ext.get('sencha_processes')
    });
});

Imagined pseudo-code to handle select all function, for the kind of effect I'm looking for:

function selectAllInColumn (column, checked, eopts){
    var columnIndex = column.getIndex();
    for( var i = 0; i < processStore.getCount(); i++)
    {
        if(checked)
        {
            var record = processStore.getAt(i);
            for(var j = 1; j <= columnIndex; j++) {
                record.set('Phase'+columnIndex, true);
            }
            for(var j = columnIndex+1; j <= 10; j++) {
                record.set('Phase'+columnIndex, false);
            }
        }
        else
        {
            var record = processStore.getAt(i);
            for(var j = columnIndex; j <= 10; j++) {
                record.set('Phase'+columnIndex, false);
            }
        }
    }
}
3

3 Answers

6
votes

You can take a look at my variant of generating a checkbox in each column header. Check checkcolumn with select all with the discription or just fiddle with example.

My checkcolumn:

Ext.define('Fiddle.CheckColumn', {
    extend: 'Ext.grid.column.CheckColumn',
    alias: 'widget.fiddlecheckcolumn',

    renderTpl: [
        '<div id="{id}-titleEl" data-ref="titleEl" {tipMarkup}class="', Ext.baseCSSPrefix, 'column-header-inner<tpl if="!$comp.isContainer"> ', Ext.baseCSSPrefix, 'leaf-column-header</tpl>',
        '<tpl if="empty"> ', Ext.baseCSSPrefix, 'column-header-inner-empty</tpl>">',

        '<span class="', Ext.baseCSSPrefix, 'column-header-text-container">',
        '<span class="', Ext.baseCSSPrefix, 'column-header-text-wrapper">',
        '<span id="{id}-textEl" data-ref="textEl" class="', Ext.baseCSSPrefix, 'column-header-text',
        '{childElCls}">',
        '<img class="', Ext.baseCSSPrefix, 'grid-checkcolumn" src="' + Ext.BLANK_IMAGE_URL + '"/>',
        '</span>',
        '</span>',
        '</span>',
        '<tpl if="!menuDisabled">',
        '<div id="{id}-triggerEl" data-ref="triggerEl" role="presentation" class="', Ext.baseCSSPrefix, 'column-header-trigger',
        '{childElCls}" style="{triggerStyle}"></div>',
        '</tpl>',
        '</div>',
        '{%this.renderContainer(out,values)%}'
    ],

    constructor : function(config) {
        var me = this;

        Ext.apply(config, {
            stopSelection: true,
            sortable: false,
            draggable: false,
            resizable: false,
            menuDisabled: true,
            hideable: false,
            tdCls: 'no-tip',
            defaultRenderer: me.defaultRenderer,
            checked: false
        });

        me.callParent([ config ]);

        me.on('headerclick', me.onHeaderClick);
        me.on('selectall', me.onSelectAll);

    },

    onHeaderClick: function(headerCt, header, e, el) {
        var me = this,
            grid = headerCt.grid;

        if (!me.checked) {
            me.fireEvent('selectall', grid.getStore(), header, true);
            header.getEl().down('img').addCls(Ext.baseCSSPrefix + 'grid-checkcolumn-checked');
            me.checked = true;
        } else {
            me.fireEvent('selectall', grid.getStore(), header, false);
            header.getEl().down('img').removeCls(Ext.baseCSSPrefix + 'grid-checkcolumn-checked');
            me.checked = false;
        }
    },

    onSelectAll: function(store, column, checked) {
        var dataIndex = column.dataIndex;
        for(var i = 0; i < store.getCount(); i++) {
            var record = store.getAt(i);
            if (checked) {
                record.set(dataIndex, true);
            } else {
                record.set(dataIndex, false);
            }
        }
    }
});
1
votes

Worked out how to do it; hard code a checkbox with an id into the header text of each check column, move the scope of the grid and store to be initialised as global (but actually constructed on Ext.ready), then have global functions that operate on the datastore records via for loops:

outside of Ext.ready:

var processGrid = null;
var processStore = null;

function headerClick(col, int){
    if(document.getElementById(col).checked==true)
    {
        selectAllInColumn(int, true);
    }
    else
    {
        selectAllInColumn(int, false);
    }
}

function selectAllInColumn (column, checked, eOpts){
    //foreach record in data store
    for( var i = 0; i < processStore.getCount(); i++)
    {
        if(checked)
        {
            // get record
            var record = processStore.getAt(i);
            // for current column and each preceding column set process step to true and check the header
            for(var j = 1; j <= column; j++) {
                document.getElementById('HeaderPhase'+j).checked = true;
                record.set('Phase'+j, true);
            }
        }
        else
        {
            var record = processStore.getAt(i);
            for(var j = column; j <= 10; j++) {
                document.getElementById('HeaderPhase'+j).checked = false;
                record.set('Phase'+j, false);
            }
        }
    }
}

function startCheckHeaderCheckBox(){
    // foreach checkcolumn
    for(var i = 1; i <= 10; i++)
    {
        // start running tally per column
        var checkedTotal = 0;
        // foreach record in data store
        for (var j = 0; j < processStore.getCount();j++)
        {
            var record = processStore.getAt(j);
            if (record.get('Phase'+i) == "true"){
                checkedTotal++;
            }
        }
        if(checkedTotal==processStore.getCount())
        {
            document.getElementById('HeaderPhase'+i).checked=true;
        }
        else
        {
            document.getElementById('HeaderPhase'+i).checked=false;
        }
    }
}

function inProgressCheckHeaderCheckBox(columnIndex){
    for( var i = 1; i <=columnIndex; i++)
    {
        var checkedTotal = 0;
        for (var j = 0; j < processStore.getCount(); j++)
        {
            var record = processStore.getAt(j);
            if (record.get('Phase'+i)){
                checkedTotal++;
            }
        }
        if(checkedTotal==processStore.getCount())
        {
            document.getElementById('HeaderPhase'+i).checked=true;
        }
    }
}

Inside Ext.ready:

// before loading the grid.Panel; onCheckChange called 
function onCheckChange (column, rowIndex, checked, eOpts) {
    var record = processStore.getAt(rowIndex);
    var columnIndex = column.getIndex();
    for (var i = 1; i <= 10; i++) {
        if(checked) {
            if (i <= columnIndex) {
                record.set('Phase'+i, 'true');
                inProgressCheckHeaderCheckBox(columnIndex);
            }
        }
        else {
            if (i >= columnIndex) {
                record.set('Phase'+i, false);
                document.getElementById('HeaderPhase'+i).checked = false;
            }
        }
    }
}

// after loading the grid.Panel to govern setting header check boxes on load
processStore.on('load', startCheckHeaderCheckBox);

// put in each checkColumn, this is the first phase header, change the numbers to match each phase
header: 'Phase 1 <br /> <input type="checkbox" id="HeaderPhase1" style="x-grid-checkcolumn" onclick="headerClick(\'HeaderPhase1\', 1)"/>',

Hope this helps other people, bear in mind my implementation will need to be tailored to whatever dataset you're using, and is dependent on names and IDs being standardised as Phases/HeaderPhases with numbers appended.

1
votes

A plugin solution usable for ExtJS 6.

Here's how to use it.

{
    xtype: 'checkcolumn',
    text: 'Selected',
    dataIndex: 'selected',
    plugins: {
        ptype: 'selectallcheckcolumnheader',
        checked: false // initial state of the checkbox
    },
    sortable: false // for better UX*
}

Here's how to install it.

Ext.define('MyApp.ux.plugin.SelectAllCheckColumnHeader', {
    extend: 'Ext.AbstractPlugin',
    alias: 'plugin.selectallcheckcolumnheader',

    init: function (cmp) {
        /*
         * @cfg checked Boolean initial state of checkbox. Defaults to false
         */
        var me = this;
        me.checked = !!me.checked;

        // class that make the div look like a checkbox
        me._checkClass = Ext.baseCSSPrefix + 'grid-checkcolumn'
        cmp.afterText = function(out, values) {
          out.push(
            '<div class="', me._checkClass, '" src="' + Ext.BLANK_IMAGE_URL + '"></div>'
          )
        }

        // Position the checkbox
        // - Make sure that checkbox show right of the header text instead of under it
        // - Also horizontal align the checkbox better
        cmp.cls = 'select-all-checkcolumn-header'
        Ext.util.CSS.createStyleSheet([
          '.', cmp.cls, ' .', Ext.baseCSSPrefix, 'column-header-text {',
          '  display: inline;',
          '}',
          '.', cmp.cls, ' .', me._checkClass, ' {',
          '  display: inline;',
          '  padding-left: 5px;',
          '}',
        ].join(''), 'plugin_selectallcheckcolumnheader');

        // initial display of checkbox or not based on me.checked
        cmp.on('render', me.checkCheckbox, me);
        // listen to header clicks, but only handle the onces exactly on the checkbox
        cmp.on('headerclick', me.onHeaderClick, me);
        // event fired by clicking on the checkbox and (un)checks all checkboxes in the grid
        cmp.on('selectall', me.onSelectAll, me);
    },

    onHeaderClick: function(headerCt, header, e, clickedElement) {
        var me = this;
        var cmp = me.cmp;
        var grid = headerCt.grid;

        if (!Ext.get(clickedElement).hasCls(me._checkClass)) {
          // It was not the particular checkbox that was clicked inside the header.
          // The column header should proceed doing other stuff like sorting
          return
        }
        me.checked = !me.checked;

        cmp.fireEvent('selectall', grid.getStore(), header, me.checked);
        me.checkCheckbox()
    },

    checkCheckbox: function() {
      var me = this;
      var cmp = this.cmp;
      var checkboxEl = cmp.getEl().down('.' + me._checkClass);

      checkboxEl[me.checked ? 'addCls' : 'removeCls'](Ext.baseCSSPrefix + 'grid-checkcolumn-checked');
    },

    onSelectAll: function(store, column, checked) {
        var dataIndex = column.dataIndex;
        var recordCount = store.getCount();
        for (var i = 0; i < recordCount; i++) {
            var record = store.getAt(i);

            record.set(dataIndex, checked);
        }
    }
});

And don't forget to require the plugin in your component.

requires: [
    'MyApp.ux.plugin.SelectAllCheckColumnHeader'
    ...
],

* With sortable set to true, a click on the checkbox would (un)check all checkboxes AND sort the grid which is confusing. It's better to set sortable to false.