You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2017/07/04 08:19:37 UTC

[14/28] ignite git commit: IGNITE-5627 Refactored grid columns menu.

IGNITE-5627 Refactored grid columns menu.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/4a1fef5c
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/4a1fef5c
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/4a1fef5c

Branch: refs/heads/ignite-2.1.2-exchange
Commit: 4a1fef5cb2442833e1110fc45b341cb146592bd3
Parents: 531a0de
Author: Ilya Borisov <ib...@gridgain.com>
Authored: Fri Jun 30 16:47:18 2017 +0700
Committer: Alexey Kuznetsov <ak...@apache.org>
Committed: Fri Jun 30 16:47:18 2017 +0700

----------------------------------------------------------------------
 modules/web-console/frontend/.eslintrc          |   2 +-
 modules/web-console/frontend/app/app.js         |   4 +
 .../grid-column-selector/component.js           |  29 ++
 .../grid-column-selector/controller.js          | 111 +++++
 .../grid-column-selector/controller.spec.js     | 435 +++++++++++++++++++
 .../components/grid-column-selector/index.js    |  24 +
 .../components/grid-column-selector/style.scss  |  24 +
 .../grid-column-selector/template.pug           |  28 ++
 .../list-of-registered-users.categories.js      |  22 +-
 .../list-of-registered-users.column-defs.js     |   4 +-
 .../list-of-registered-users.controller.js      |  46 --
 .../list-of-registered-users.scss               |   3 +
 .../list-of-registered-users.tpl.pug            |  26 +-
 .../components/pcbProtectFromBsSelectRender.js  |  32 --
 .../components/page-configure-basic/index.js    |   2 -
 .../page-configure-basic/template.pug           |   4 +-
 .../protect-from-bs-select-render/directive.js  |  32 ++
 .../protect-from-bs-select-render/index.js      |  24 +
 .../frontend/app/helpers/jade/mixins.pug        |   1 -
 .../frontend/app/modules/sql/sql.controller.js  |  21 +-
 .../frontend/app/primitives/btn/index.scss      |   8 +-
 .../frontend/app/primitives/dropdown/index.pug  |   2 +-
 .../app/primitives/ui-grid-settings/index.pug   |  33 --
 .../frontend/public/stylesheets/style.scss      |  13 +
 .../web-console/frontend/views/sql/sql.tpl.pug  |   6 +-
 25 files changed, 764 insertions(+), 172 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/.eslintrc
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/.eslintrc b/modules/web-console/frontend/.eslintrc
index 6778b86..d88fb97 100644
--- a/modules/web-console/frontend/.eslintrc
+++ b/modules/web-console/frontend/.eslintrc
@@ -178,7 +178,7 @@ rules:
     prefer-const: 1
     prefer-spread: 2
     quote-props: [2, "as-needed"]
-    quotes: [2, "single"]
+    quotes: [2, "single", {"allowTemplateLiterals": true}]
     radix: 1
     semi: [2, "always"]
     semi-spacing: [2, {"before": false, "after": true}]

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/app.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/app.js b/modules/web-console/frontend/app/app.js
index 61372a2..7b8196e 100644
--- a/modules/web-console/frontend/app/app.js
+++ b/modules/web-console/frontend/app/app.js
@@ -123,7 +123,9 @@ import userNotifications from './components/user-notifications';
 import pageConfigure from './components/page-configure';
 import pageConfigureBasic from './components/page-configure-basic';
 import pageConfigureAdvanced from './components/page-configure-advanced';
+import gridColumnSelector from './components/grid-column-selector';
 import bsSelectMenu from './components/bs-select-menu';
+import protectFromBsSelectRender from './components/protect-from-bs-select-render';
 
 // Inject external modules.
 import IgniteModules from 'IgniteModules/index';
@@ -189,7 +191,9 @@ angular
     pageConfigure.name,
     pageConfigureBasic.name,
     pageConfigureAdvanced.name,
+    gridColumnSelector.name,
     bsSelectMenu.name,
+    protectFromBsSelectRender.name,
     // Ignite modules.
     IgniteModules.name
 ])

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/grid-column-selector/component.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-column-selector/component.js b/modules/web-console/frontend/app/components/grid-column-selector/component.js
new file mode 100644
index 0000000..957c821f
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-column-selector/component.js
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import template from './template.pug';
+import controller from './controller.js';
+import './style.scss';
+
+export default {
+    template,
+    controller,
+    transclude: true,
+    bindings: {
+        gridApi: '<'
+    }
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/grid-column-selector/controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-column-selector/controller.js b/modules/web-console/frontend/app/components/grid-column-selector/controller.js
new file mode 100644
index 0000000..1f04861
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-column-selector/controller.js
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import uniq from 'lodash/fp/uniq';
+import difference from 'lodash/difference';
+import findLast from 'lodash/findLast';
+
+const hasGrouping = (changes) => {
+    return changes.gridApi.currentValue !== changes.gridApi.previousValue && changes.gridApi.currentValue.grouping;
+};
+const id = (column) => column.categoryDisplayName || column.displayName || column.name;
+const picksrc = (state) => state.categories.length ? state.categories : state.columns;
+
+export default class GridColumnSelectorController {
+    static $inject = ['$scope', 'uiGridConstants'];
+
+    constructor($scope, uiGridConstants) {
+        Object.assign(this, {$scope, uiGridConstants});
+    }
+
+    $onChanges(changes) {
+        if (changes && 'gridApi' in changes && changes.gridApi.currentValue) {
+            this.applyValues();
+            this.gridApi.grid.registerDataChangeCallback(() => this.applyValues(), [this.uiGridConstants.dataChange.COLUMN]);
+            if (hasGrouping(changes)) this.gridApi.grouping.on.groupingChanged(this.$scope, () => this.applyValues());
+        }
+    }
+
+    applyValues() {
+        this.state = this.getState();
+        this.columnsMenu = this.makeMenu();
+        this.selectedColumns = this.getSelectedColumns();
+        this.setSelectedColumns();
+    }
+
+    getSelectedColumns() {
+        return picksrc(this.state).filter((i) => i.isVisible && i.isInMenu).map((i) => id(i.item));
+    }
+
+    makeMenu() {
+        return picksrc(this.state).filter((i) => i.isInMenu).map((i) => ({
+            name: id(i.item),
+            item: i.item
+        }));
+    }
+
+    getState() {
+        const api = this.gridApi;
+        const columns = api.grid.options.columnDefs;
+        const categories = api.grid.options.categories || [];
+        const grouping = api.grouping
+            ? api.grouping.getGrouping().grouping.map((g) => columns.find((c) => c.name === g.colName).categoryDisplayName)
+            : [];
+        const mapfn = (item) => ({
+            item,
+            isInMenu: item.enableHiding !== false && !grouping.includes(item.name),
+            isVisible: grouping.includes(item.name) || item.visible !== false
+        });
+        return ({
+            categories: categories.map(mapfn),
+            columns: columns.map(mapfn)
+        });
+    }
+
+    findScrollToNext(columns, prevColumns) {
+        if (!prevColumns) return;
+        const diff = difference(columns, prevColumns);
+        if (diff.length === 1 && columns.includes(diff[0])) return diff[0];
+    }
+
+    setSelectedColumns() {
+        const {selectedColumns} = this;
+        const scrollToNext = this.findScrollToNext(selectedColumns, this.prevSelectedColumns);
+        this.prevSelectedColumns = selectedColumns;
+        const all = this.state.categories.concat(this.state.columns);
+        const itemsToShow = uniq(all.filter((i) => !i.isInMenu && i.isVisible).map((i) => id(i.item)).concat(selectedColumns));
+        (this.gridApi.grid.options.categories || []).concat(this.gridApi.grid.options.columnDefs).forEach((item) => {
+            item.visible = itemsToShow.includes(id(item));
+        });
+        // Scrolls to the last matching columnDef, useful if it was out of view after being enabled.
+        this.refreshColumns().then(() => {
+            if (scrollToNext) {
+                const column = findLast(this.gridApi.grid.options.columnDefs, (c) => id(c) === scrollToNext);
+                this.gridApi.grid.scrollTo(null, column);
+            }
+        });
+    }
+
+    // gridApi.grid.refreshColumns method does not allow to wait until async operation completion.
+    // This method does roughly the same, but returns a Promise.
+    refreshColumns() {
+        return this.gridApi.grid.processColumnsProcessors(this.gridApi.grid.columns)
+        .then((renderableColumns) => this.gridApi.grid.setVisibleColumns(renderableColumns))
+        .then(() => this.gridApi.grid.redrawInPlace())
+        .then(() => this.gridApi.grid.refreshCanvas(true));
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/grid-column-selector/controller.spec.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-column-selector/controller.spec.js b/modules/web-console/frontend/app/components/grid-column-selector/controller.spec.js
new file mode 100644
index 0000000..d163a9c
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-column-selector/controller.spec.js
@@ -0,0 +1,435 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the License); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {suite, test} from 'mocha';
+import {assert} from 'chai';
+import {spy, stub} from 'sinon';
+
+import Controller from './controller';
+
+const mocks = () => new Map([
+    ['$scope', {}],
+    ['uiGridConstants', {
+        dataChange: {
+            COLUMN: 'COLUMN'
+        }
+    }]
+]);
+
+const apiMock = () => ({
+    grid: {
+        options: {
+            columnDefs: []
+        },
+        registerDataChangeCallback: spy(),
+        processColumnsProcessors: spy((v) => Promise.resolve(v)),
+        setVisibleColumns: spy((v) => Promise.resolve(v)),
+        redrawInPlace: spy((v) => Promise.resolve(v)),
+        refreshCanvas: spy((v) => Promise.resolve(v)),
+        scrollTo: spy()
+    },
+    grouping: {
+        on: {
+            groupingChanged: spy()
+        },
+        getGrouping: stub().returns({grouping: []})
+    }
+});
+
+suite('grid-column-selector component controller', () => {
+    test('$onChanges', () => {
+        const c = new Controller(...mocks().values());
+        c.applyValues = spy(c.applyValues.bind(c));
+        const api = apiMock();
+        c.gridApi = api;
+        c.$onChanges({gridApi: {currentValue: api}});
+
+        assert.equal(c.applyValues.callCount, 1, 'calls applyValues');
+        assert.isFunction(
+            c.gridApi.grid.registerDataChangeCallback.lastCall.args[0]
+        );
+
+        c.gridApi.grid.registerDataChangeCallback.lastCall.args[0]();
+
+        assert.equal(
+            c.applyValues.callCount,
+            2,
+            'registers applyValues as data change callback'
+        );
+        assert.deepEqual(
+            c.gridApi.grid.registerDataChangeCallback.lastCall.args[1],
+            [c.uiGridConstants.dataChange.COLUMN],
+            'registers data change callback for COLUMN'
+        );
+        assert.equal(
+            c.gridApi.grouping.on.groupingChanged.lastCall.args[0],
+            c.$scope,
+            'registers grouping change callback with correct $scope'
+        );
+
+        c.gridApi.grouping.on.groupingChanged.lastCall.args[1]();
+
+        assert.equal(
+            c.applyValues.callCount,
+            3,
+            'registers applyValues as grouping change callback'
+        );
+    });
+    test('applyValues', () => {
+        const c = new Controller(...mocks().values());
+        const mock = {
+            getState: stub().returns({}),
+            makeMenu: stub().returns({}),
+            getSelectedColumns: stub().returns({}),
+            setSelectedColumns: spy()
+        };
+        c.applyValues.call(mock);
+
+        assert.equal(
+            mock.state,
+            mock.getState.lastCall.returnValue,
+            'assigns getState return value as this.state'
+        );
+        assert.equal(
+            mock.columnsMenu,
+            mock.makeMenu.lastCall.returnValue,
+            'assigns makeMenu return value as this.columnsMenu'
+        );
+        assert.equal(
+            mock.selectedColumns,
+            mock.getSelectedColumns.lastCall.returnValue,
+            'assigns getSelectedColumns return value as this.selectedColumns'
+        );
+        assert.equal(
+            mock.setSelectedColumns.callCount,
+            1,
+            'calls setSelectedColumns once'
+        );
+    });
+    test('getSelectedColumns, using categories', () => {
+        const c = new Controller(...mocks().values());
+        c.state = {
+            categories: [
+                {isVisible: false},
+                {isVisible: true, isInMenu: false},
+                {isVisible: true, isInMenu: true, item: {categoryDisplayName: '1'}},
+                {isVisible: true, isInMenu: true, item: {displayName: '2'}},
+                {isVisible: true, isInMenu: true, item: {name: '3'}}
+            ],
+            columns: []
+        };
+
+        assert.deepEqual(
+            c.getSelectedColumns(),
+            ['1', '2', '3'],
+            'returns correct value, prefers categories over columns'
+        );
+    });
+    test('getSelectedColumns, using columnDefs', () => {
+        const c = new Controller(...mocks().values());
+        c.state = {
+            categories: [
+            ],
+            columns: [
+                {isVisible: false},
+                {isVisible: true, isInMenu: false},
+                {isVisible: true, isInMenu: true, item: {categoryDisplayName: '1'}},
+                {isVisible: true, isInMenu: true, item: {displayName: '2'}},
+                {isVisible: true, isInMenu: true, item: {name: '3'}}
+            ]
+        };
+
+        assert.deepEqual(
+            c.getSelectedColumns(),
+            ['1', '2', '3'],
+            'returns correct value, uses columns if there are no categories'
+        );
+    });
+    test('makeMenu, using categories', () => {
+        const c = new Controller(...mocks().values());
+        c.state = {
+            categories: [
+                {isVisible: false},
+                {isVisible: true, isInMenu: false},
+                {isVisible: true, isInMenu: true, item: {categoryDisplayName: '1'}},
+                {isVisible: true, isInMenu: true, item: {displayName: '2'}},
+                {isVisible: true, isInMenu: true, item: {name: '3'}}
+            ],
+            columns: []
+        };
+
+        assert.deepEqual(
+            c.makeMenu(),
+            [
+                {item: {categoryDisplayName: '1'}, name: '1'},
+                {item: {displayName: '2'}, name: '2'},
+                {item: {name: '3'}, name: '3'}
+            ],
+            'returns correct value, prefers categories over columns'
+        );
+    });
+    test('makeMenu, using columns', () => {
+        const c = new Controller(...mocks().values());
+        c.state = {
+            categories: [],
+            columns: [
+                {isVisible: false},
+                {isVisible: true, isInMenu: false},
+                {isVisible: true, isInMenu: true, item: {categoryDisplayName: '1'}},
+                {isVisible: true, isInMenu: true, item: {displayName: '2'}},
+                {isVisible: true, isInMenu: true, item: {name: '3'}}
+            ]
+        };
+
+        assert.deepEqual(
+            c.makeMenu(),
+            [
+                {item: {categoryDisplayName: '1'}, name: '1'},
+                {item: {displayName: '2'}, name: '2'},
+                {item: {name: '3'}, name: '3'}
+            ],
+            'returns correct value, uses columns if there are no categories'
+        );
+    });
+    test('getState', () => {
+        const c = new Controller(...mocks().values());
+        c.gridApi = apiMock();
+        c.gridApi.grouping.getGrouping = () => ({grouping: [{colName: 'a', categoryDisplayName: 'A'}]});
+        c.gridApi.grid.options.columnDefs = [
+            {visible: false, name: 'a', categoryDisplayName: 'A'},
+            {visible: true, name: 'a1', categoryDisplayName: 'A'},
+            {visible: true, name: 'a2', categoryDisplayName: 'A'},
+            {visible: true, name: 'b1', categoryDisplayName: 'B'},
+            {visible: true, name: 'b2', categoryDisplayName: 'B'},
+            {visible: true, name: 'b3', categoryDisplayName: 'B'},
+            {visible: true, name: 'c1', categoryDisplayName: 'C'},
+            {visible: true, name: 'c2', categoryDisplayName: 'C', enableHiding: false},
+            {visible: false, name: 'c3', categoryDisplayName: 'C'}
+        ];
+        c.gridApi.grid.options.categories = [
+            {categoryDisplayName: 'A', enableHiding: false, visible: true},
+            {categoryDisplayName: 'B', enableHiding: true, visible: false},
+            {categoryDisplayName: 'C', enableHiding: true, visible: true}
+        ];
+
+        assert.deepEqual(
+            c.getState(),
+            {
+                categories: [{
+                    isInMenu: false,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'A',
+                        enableHiding: false,
+                        visible: true
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: false,
+                    item: {
+                        categoryDisplayName: 'B',
+                        enableHiding: true,
+                        visible: false
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'C',
+                        enableHiding: true,
+                        visible: true
+                    }
+                }],
+                columns: [{
+                    isInMenu: true,
+                    isVisible: false,
+                    item: {
+                        categoryDisplayName: 'A',
+                        name: 'a',
+                        visible: false
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'A',
+                        name: 'a1',
+                        visible: true
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'A',
+                        name: 'a2',
+                        visible: true
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'B',
+                        name: 'b1',
+                        visible: true
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'B',
+                        name: 'b2',
+                        visible: true
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'B',
+                        name: 'b3',
+                        visible: true
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'C',
+                        name: 'c1',
+                        visible: true
+                    }
+                }, {
+                    isInMenu: false,
+                    isVisible: true,
+                    item: {
+                        categoryDisplayName: 'C',
+                        name: 'c2',
+                        visible: true,
+                        enableHiding: false
+                    }
+                }, {
+                    isInMenu: true,
+                    isVisible: false,
+                    item: {
+                        categoryDisplayName: 'C',
+                        name: 'c3',
+                        visible: false
+                    }
+                }]
+            },
+            'returns correct value'
+        );
+    });
+    test('findScrollToNext', () => {
+        assert.deepEqual(
+            Controller.prototype.findScrollToNext([1, 2, 3], [1, 2]),
+            3,
+            `returns new item if it's in selected collection end`
+        );
+        assert.deepEqual(
+            Controller.prototype.findScrollToNext([1, 2], [1, 3]),
+            2,
+            `returns new item if it's in selected collection middle`
+        );
+        assert.deepEqual(
+            Controller.prototype.findScrollToNext([1, 2, 3], [2, 3]),
+            1,
+            `returns new item if it's in selected collection start`
+        );
+        assert.equal(
+            Controller.prototype.findScrollToNext([1, 2, 3, 4, 5], [1]),
+            void 0,
+            `returns nothing if there's more than one new item`
+        );
+        assert.equal(
+            Controller.prototype.findScrollToNext([1, 2], [1, 2, 3]),
+            void 0,
+            `returns nothing if items were removed`
+        );
+    });
+    test('setSelectedColumns', () => {
+        const c = new Controller(...mocks().values());
+        const api = c.gridApi = apiMock();
+        c.refreshColumns = stub().returns(({then: (f) => f()}));
+        c.gridApi.grid.options.columnDefs = [
+            {name: 'a', visible: false, categoryDisplayName: 'A'},
+            {name: 'a1', visible: false, categoryDisplayName: 'A'},
+            {name: 'b', visible: true, categoryDisplayName: 'B'}
+        ];
+        c.gridApi.grid.options.categories = [
+            {categoryDisplayName: 'A', visible: false},
+            {categoryDisplayName: 'B'}
+        ];
+        c.$onChanges({gridApi: {currentValue: api}});
+        c.selectedColumns = ['A', 'B'];
+        c.setSelectedColumns();
+
+        assert.equal(
+            c.refreshColumns.callCount,
+            2,
+            'calls refreshColumns'
+        );
+        assert.deepEqual(
+            c.gridApi.grid.options.categories,
+            [
+                {categoryDisplayName: 'A', visible: true},
+                {categoryDisplayName: 'B', visible: true}
+            ],
+            'changes category visibility'
+        );
+        assert.deepEqual(
+            c.gridApi.grid.options.columnDefs,
+            [
+                {name: 'a', visible: true, categoryDisplayName: 'A'},
+                {name: 'a1', visible: true, categoryDisplayName: 'A'},
+                {name: 'b', visible: true, categoryDisplayName: 'B'}
+            ],
+            'changes column visibility'
+        );
+        assert.deepEqual(
+            c.gridApi.grid.scrollTo.lastCall.args,
+            [null, {name: 'a1', visible: true, categoryDisplayName: 'A'}],
+            'scrolls to last added column'
+        );
+    });
+    test('refreshColumns', () => {
+        const c = new Controller(...mocks().values());
+        c.gridApi = apiMock();
+        c.gridApi.grid.columns = [1, 2, 3];
+
+        return c.refreshColumns().then(() => {
+            assert.equal(
+                c.gridApi.grid.processColumnsProcessors.lastCall.args[0],
+                c.gridApi.grid.columns,
+                'calls processColumnsProcessors with correct args'
+            );
+            assert.equal(
+                c.gridApi.grid.setVisibleColumns.lastCall.args[0],
+                c.gridApi.grid.columns,
+                'calls setVisibleColumns with correct args'
+            );
+            assert.equal(
+                c.gridApi.grid.redrawInPlace.callCount,
+                1,
+                'calls redrawInPlace'
+            );
+            assert.equal(
+                c.gridApi.grid.refreshCanvas.lastCall.args[0],
+                true,
+                'calls refreshCanvas with correct args'
+            );
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/grid-column-selector/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-column-selector/index.js b/modules/web-console/frontend/app/components/grid-column-selector/index.js
new file mode 100644
index 0000000..16ef655
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-column-selector/index.js
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import angular from 'angular';
+
+import component from './component';
+
+export default angular
+    .module('ignite-console.grid-column-selector', [])
+    .component('gridColumnSelector', component);

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/grid-column-selector/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-column-selector/style.scss b/modules/web-console/frontend/app/components/grid-column-selector/style.scss
new file mode 100644
index 0000000..5e0d5d4
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-column-selector/style.scss
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+grid-column-selector {
+    display: inline-block;
+
+    .btn-ignite, .icon {
+        margin: 0 !important;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/grid-column-selector/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/grid-column-selector/template.pug b/modules/web-console/frontend/app/components/grid-column-selector/template.pug
new file mode 100644
index 0000000..86fd152
--- /dev/null
+++ b/modules/web-console/frontend/app/components/grid-column-selector/template.pug
@@ -0,0 +1,28 @@
+//-
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+button.btn-ignite.btn-ignite--link-dashed-secondary(
+    protect-from-bs-select-render
+    bs-select
+    ng-model='$ctrl.selectedColumns'
+    ng-change='$ctrl.setSelectedColumns()'
+    ng-model-options='{debounce: {default: 5}}',
+    bs-options='column.name as column.name for column in $ctrl.columnsMenu'
+    bs-on-before-show='$ctrl.onShow'
+    data-multiple='true'
+    ng-transclude
+)
+    svg(ignite-icon='gear').icon

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js
index 01b1fc8..e73dd17 100644
--- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.categories.js
@@ -16,15 +16,15 @@
  */
 
 export default [
-    {name: 'Actions', visible: false, selectable: false},
-    {name: 'User', visible: true, selectable: false},
-    {name: 'Email', visible: true, selectable: true},
-    {name: 'Company', visible: true, selectable: true},
-    {name: 'Country', visible: true, selectable: true},
-    {name: 'Last login', visible: false, selectable: true},
-    {name: 'Last activity', visible: true, selectable: true},
-    {name: 'Configurations', visible: false, selectable: true},
-    {name: 'Total activities', visible: true, selectable: true},
-    {name: 'Configuration\'s activities', visible: false, selectable: true},
-    {name: 'Queries\' activities', visible: false, selectable: true}
+    {name: 'Actions', visible: false, enableHiding: false},
+    {name: 'User', visible: true, enableHiding: false},
+    {name: 'Email', visible: true, enableHiding: true},
+    {name: 'Company', visible: true, enableHiding: true},
+    {name: 'Country', visible: true, enableHiding: true},
+    {name: 'Last login', visible: false, enableHiding: true},
+    {name: 'Last activity', visible: true, enableHiding: true},
+    {name: 'Configurations', visible: false, enableHiding: true},
+    {name: 'Total activities', visible: true, enableHiding: true},
+    {name: 'Configuration\'s activities', visible: false, enableHiding: true},
+    {name: 'Queries\' activities', visible: false, enableHiding: true}
 ];

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js
index baf5aed..493c239 100644
--- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.column-defs.js
@@ -49,8 +49,8 @@ const ACTIONS_TEMPLATE = `
 const EMAIL_TEMPLATE = '<div class="ui-grid-cell-contents"><a ng-href="mailto:{{ COL_FIELD }}">{{ COL_FIELD }}</a></div>';
 
 export default [
-    {name: 'actions', displayName: 'Actions', categoryDisplayName: 'Actions', cellTemplate: ACTIONS_TEMPLATE, field: 'actions', minWidth: 70, width: 70, enableFiltering: false, enableSorting: false, visible: false},
-    {name: 'user', displayName: 'User', categoryDisplayName: 'User', field: 'userName', cellTemplate: USER_TEMPLATE, minWidth: 160, enableFiltering: true, pinnedLeft: true, filter: { placeholder: 'Filter by name...' }},
+    {name: 'actions', enableHiding: false, displayName: 'Actions', categoryDisplayName: 'Actions', cellTemplate: ACTIONS_TEMPLATE, field: 'actions', minWidth: 70, width: 70, enableFiltering: false, enableSorting: false, visible: false},
+    {name: 'user', enableHiding: false, displayName: 'User', categoryDisplayName: 'User', field: 'userName', cellTemplate: USER_TEMPLATE, minWidth: 160, enableFiltering: true, pinnedLeft: true, filter: { placeholder: 'Filter by name...' }},
     {name: 'email', displayName: 'Email', categoryDisplayName: 'Email', field: 'email', cellTemplate: EMAIL_TEMPLATE, minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by email...' }},
     {name: 'company', displayName: 'Company', categoryDisplayName: 'Company', field: 'company', minWidth: 180, enableFiltering: true, filter: { placeholder: 'Filter by company...' }},
     {name: 'country', displayName: 'Country', categoryDisplayName: 'Country', field: 'countryCode', minWidth: 160, enableFiltering: true, filter: { placeholder: 'Filter by country...' }},

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js
index c006003..58e93b4 100644
--- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.controller.js
@@ -246,52 +246,6 @@ export default class IgniteListOfRegisteredUsersCtrl {
             this.selected = ids;
     }
 
-    _enableColumns(_categories, visible) {
-        _.forEach(_categories, (cat) => {
-            cat.visible = visible;
-
-            _.forEach(this.gridOptions.columnDefs, (col) => {
-                if (col.categoryDisplayName === cat.name)
-                    col.visible = visible;
-            });
-        });
-
-        // Check to all selected columns.
-        this.gridOptions.selectedAll = true;
-        _.forEach(this._selectableColumns(), ({ visible }) => this.gridOptions.selectedAll = visible);
-
-        // Workaround for this.gridApi.grid.refresh() didn't return promise.
-        this.gridApi.grid.processColumnsProcessors(this.gridApi.grid.columns)
-            .then((renderableColumns) => this.gridApi.grid.setVisibleColumns(renderableColumns))
-            .then(() => this.gridApi.grid.redrawInPlace())
-            .then(() => this.gridApi.grid.refreshCanvas(true))
-            .then(() => {
-                if (visible) {
-                    const categoryDisplayName = _.last(_categories).name;
-
-                    const col = _.findLast(this.gridOptions.columnDefs, {categoryDisplayName});
-
-                    this.gridApi.grid.scrollTo(null, col);
-                }
-            });
-    }
-
-    _selectableColumns() {
-        return _.filter(this.gridOptions.categories, (cat) => cat.selectable);
-    }
-
-    toggleColumns(category, visible) {
-        this._enableColumns([category], visible);
-    }
-
-    selectAllColumns() {
-        this._enableColumns(this._selectableColumns(), true);
-    }
-
-    clearAllColumns() {
-        this._enableColumns(this._selectableColumns(), false);
-    }
-
     exportCsv() {
         this.gridApi.exporter.csvExport('visible', 'visible');
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss
index d94fb2e..71d93a7 100644
--- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.scss
@@ -20,6 +20,9 @@ ignite-list-of-registered-users {
 }
 
 .list-of-registered-users {
+  .ui-grid-settings--heading {
+    display: flex;
+  }
   & > a {
     display: inline-block;
     margin: 10px;

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
index 85a2070..3558ab5 100644
--- a/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
+++ b/modules/web-console/frontend/app/components/list-of-registered-users/list-of-registered-users.tpl.pug
@@ -16,21 +16,6 @@
 
 include /app/helpers/jade/mixins
 
-mixin grid-settings()
-    .grid-settings
-        svg(ignite-icon='gear' data-animation='am-flip-x' bs-dropdown='' aria-haspopup='true' aria-expanded='expanded' data-auto-close='1' data-trigger='click')
-        ul.select.dropdown-menu(role='menu')
-            li
-                a(ng-click='$ctrl.gridOptions.selectedAll ? $ctrl.clearAllColumns() : $ctrl.selectAllColumns()')
-                    i.fa.fa-check-square-o.pull-left(ng-if='$ctrl.gridOptions.selectedAll')
-                    i.fa.fa-square-o.pull-left(ng-if='!$ctrl.gridOptions.selectedAll')
-                    span All
-            li(ng-repeat='item in $ctrl.gridOptions.categories|filter:{selectable:true}')
-                a(ng-click='$ctrl.toggleColumns(item, !item.visible)')
-                    i.fa.fa-check-square-o.pull-left(ng-if='item.visible')
-                    i.fa.fa-square-o.pull-left(ng-if='!item.visible')
-                    span {{::item.name}}
-
 .list-of-registered-users
     ul.tabs.tabs--blue
         li(role='presentation' ng-class='{ active: $ctrl.groupBy === "user" }') 
@@ -52,7 +37,14 @@ mixin grid-settings()
     .panel--ignite
         .panel-heading.ui-grid-settings
             .panel-title
-                +ignite-form-field-bsdropdown('Actions', '$ctrl.action', 'action', '!$ctrl.selected.length', false, '$ctrl.actionOptions')
+                +ignite-form-field-bsdropdown({
+                    label: 'Actions',
+                    model: '$ctrl.action',
+                    name: 'action',
+                    disabled: '!$ctrl.selected.length',
+                    required: false,
+                    options: '$ctrl.actionOptions'
+                })
                 button.btn-ignite.btn-ignite--primary-outline(ng-click='$ctrl.exportCsv()' bs-tooltip='' data-title='Export table to csv' data-placement='top')
                     svg(ignite-icon='csv')
                 form.ui-grid-settings-dateperiod(name=form novalidate)
@@ -67,7 +59,7 @@ mixin grid-settings()
                     span(ng-if='$ctrl.groupBy === "user"') List of registered users
                     span(ng-if='$ctrl.groupBy === "company"') List of registered companies
                     span(ng-if='$ctrl.groupBy === "country"') List of registered countries
-                    +grid-settings
+                    grid-column-selector(grid-api='$ctrl.gridApi')
                 .panel-selected(ng-show='$ctrl.selected.length')
                     | {{ $ctrl.selected.length }} item{{ $ctrl.selected.length > 1 ? 's' : '' }} selected            
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/page-configure-basic/components/pcbProtectFromBsSelectRender.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure-basic/components/pcbProtectFromBsSelectRender.js b/modules/web-console/frontend/app/components/page-configure-basic/components/pcbProtectFromBsSelectRender.js
deleted file mode 100644
index 5d0ef53..0000000
--- a/modules/web-console/frontend/app/components/page-configure-basic/components/pcbProtectFromBsSelectRender.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-export default function pcbProtectFromBsSelectRender() {
-    return {
-        link(scope, el, attr, ctrl) {
-            const {$render} = ctrl;
-
-            Object.defineProperty(ctrl, '$render', {
-                set() {},
-                get() {
-                    return $render;
-                }
-            });
-        },
-        require: 'ngModel'
-    };
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/page-configure-basic/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure-basic/index.js b/modules/web-console/frontend/app/components/page-configure-basic/index.js
index 1bec7cb..21ae777 100644
--- a/modules/web-console/frontend/app/components/page-configure-basic/index.js
+++ b/modules/web-console/frontend/app/components/page-configure-basic/index.js
@@ -20,12 +20,10 @@ import angular from 'angular';
 import component from './component';
 import service from './service';
 
-import pcbProtectFromBsSelectRender from './components/pcbProtectFromBsSelectRender';
 import pcbScaleNumber from './components/pcbScaleNumber';
 
 export default angular
     .module('ignite-console.page-configure-basic', [])
     .component('pageConfigureBasic', component)
-    .directive('pcbProtectFromBsSelectRender', pcbProtectFromBsSelectRender)
     .directive('pcbScaleNumber', pcbScaleNumber)
     .service('PageConfigureBasic', service);

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/page-configure-basic/template.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/page-configure-basic/template.pug b/modules/web-console/frontend/app/components/page-configure-basic/template.pug
index 36dfb20..ab8f43c 100644
--- a/modules/web-console/frontend/app/components/page-configure-basic/template.pug
+++ b/modules/web-console/frontend/app/components/page-configure-basic/template.pug
@@ -99,7 +99,7 @@ form(novalidate name=form)
                 bs-select
                 bs-options='size as size.label for size in $ctrl.sizesMenu'
                 ng-model='$ctrl.memorySizeScale'
-                pcb-protect-from-bs-select-render
+                protect-from-bs-select-render
             )
                 | {{ $ctrl.memorySizeScale.label }}
                 span.fa.fa-caret-down.icon-right
@@ -153,7 +153,7 @@ form(novalidate name=form)
                 bs-options='cache._id as cache.name for cache in $ctrl.cachesMenu'
                 data-multiple='true'
                 data-placement='top-left'
-                pcb-protect-from-bs-select-render
+                protect-from-bs-select-render
             )
                 | + Select from existing caches
         

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/protect-from-bs-select-render/directive.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/protect-from-bs-select-render/directive.js b/modules/web-console/frontend/app/components/protect-from-bs-select-render/directive.js
new file mode 100644
index 0000000..e51d477
--- /dev/null
+++ b/modules/web-console/frontend/app/components/protect-from-bs-select-render/directive.js
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default function protectFromBsSelectRender() {
+    return {
+        link(scope, el, attr, ctrl) {
+            const {$render} = ctrl;
+
+            Object.defineProperty(ctrl, '$render', {
+                set() {},
+                get() {
+                    return $render;
+                }
+            });
+        },
+        require: 'ngModel'
+    };
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/components/protect-from-bs-select-render/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/components/protect-from-bs-select-render/index.js b/modules/web-console/frontend/app/components/protect-from-bs-select-render/index.js
new file mode 100644
index 0000000..fd63000
--- /dev/null
+++ b/modules/web-console/frontend/app/components/protect-from-bs-select-render/index.js
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import angular from 'angular';
+
+import directive from './directive';
+
+export default angular
+    .module('ignite-console.protect-from-bs-select-render', [])
+    .directive('protectFromBsSelectRender', directive);

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/helpers/jade/mixins.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/helpers/jade/mixins.pug b/modules/web-console/frontend/app/helpers/jade/mixins.pug
index 9dfdab3..fc193eb 100644
--- a/modules/web-console/frontend/app/helpers/jade/mixins.pug
+++ b/modules/web-console/frontend/app/helpers/jade/mixins.pug
@@ -20,7 +20,6 @@ include ../../primitives/datepicker/index
 include ../../primitives/timepicker/index
 include ../../primitives/dropdown/index
 include ../../primitives/tooltip/index
-include ../../primitives/ui-grid-settings/index
 include ../../primitives/switcher/index
 
 //- Mixin for advanced options toggle.

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/modules/sql/sql.controller.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/sql/sql.controller.js b/modules/web-console/frontend/app/modules/sql/sql.controller.js
index 5c678d6..633f167 100644
--- a/modules/web-console/frontend/app/modules/sql/sql.controller.js
+++ b/modules/web-console/frontend/app/modules/sql/sql.controller.js
@@ -57,25 +57,6 @@ class Paragraph {
 
         _.assign(this, paragraph);
 
-        const _enableColumns = (categories, visible) => {
-            _.forEach(categories, (cat) => {
-                cat.visible = visible;
-
-                _.forEach(this.gridOptions.columnDefs, (col) => {
-                    if (col.displayName === cat.name)
-                        col.visible = visible;
-                });
-            });
-
-            this.gridOptions.api.grid.refresh();
-        };
-
-        const _selectableColumns = () => _.filter(this.gridOptions.categories, (cat) => cat.selectable);
-
-        this.toggleColumns = (category, visible) => _enableColumns([category], visible);
-        this.selectAllColumns = () => _enableColumns(_selectableColumns(), true);
-        this.clearAllColumns = () => _enableColumns(_selectableColumns(), false);
-
         Object.defineProperty(this, 'gridOptions', {value: {
             enableGridMenu: false,
             enableColumnMenus: false,
@@ -101,7 +82,7 @@ class Paragraph {
                     this.categories.push({
                         name: col.fieldName,
                         visible: self.columnFilter(col),
-                        selectable: true
+                        enableHiding: true
                     });
 
                     return cols;

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/primitives/btn/index.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/btn/index.scss b/modules/web-console/frontend/app/primitives/btn/index.scss
index 6fb76c8..94b5bd2 100644
--- a/modules/web-console/frontend/app/primitives/btn/index.scss
+++ b/modules/web-console/frontend/app/primitives/btn/index.scss
@@ -17,6 +17,9 @@
 
 @import "./../../../public/stylesheets/variables.scss";
 
+$btn-content-padding: 10px 12px;
+$btn-content-padding-with-border: 9px 11px;
+
 @mixin active-focus-shadows(
     $focus: (0 0 5px #095d9a, 0 0 5px #095d9a),
     $active: (inset 0 1px 3px 0 rgba(0, 0, 0, 0.5))
@@ -39,7 +42,6 @@
 }
 
 .btn-ignite {
-    $content-padding: 10px 12px;
     $icon-margin: 8px;
 
     display: inline-flex;
@@ -48,7 +50,7 @@
     align-items: center;
     box-sizing: border-box;
     margin: 0;
-    padding: $content-padding;
+    padding: $btn-content-padding;
 
     border: none;
     border-radius: $ignite-button-border-radius;
@@ -128,6 +130,7 @@
     border: 1px solid $accent-color;
     background: white;
     color: $accent-color;
+    padding: $btn-content-padding-with-border;
 
     &:hover, &.hover,
     &:active, &.active {
@@ -248,6 +251,7 @@
     background-color: white;
     color: #424242;
     border: 1px solid #dedede;
+    padding: $btn-content-padding-with-border;
 
     &:hover, &.hover,
     &:active, &.active {

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/primitives/dropdown/index.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/dropdown/index.pug b/modules/web-console/frontend/app/primitives/dropdown/index.pug
index 3a5f1b1..c145244 100644
--- a/modules/web-console/frontend/app/primitives/dropdown/index.pug
+++ b/modules/web-console/frontend/app/primitives/dropdown/index.pug
@@ -14,7 +14,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 
-mixin ignite-form-field-bsdropdown(label, model, name, disabled, required, options, tip)
+mixin ignite-form-field-bsdropdown({label, model, name, disabled, required, options, tip})
     .dropdown--ignite.ignite-form-field
         .btn-ignite.btn-ignite--primary-outline(
             data-ng-model=model

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/app/primitives/ui-grid-settings/index.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/primitives/ui-grid-settings/index.pug b/modules/web-console/frontend/app/primitives/ui-grid-settings/index.pug
deleted file mode 100644
index 8f1487e..0000000
--- a/modules/web-console/frontend/app/primitives/ui-grid-settings/index.pug
+++ /dev/null
@@ -1,33 +0,0 @@
-//-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
-mixin ui-grid-settings()
-    .ui-grid-settings
-        i.fa.fa-bars(data-animation='am-flip-x' bs-dropdown='' aria-haspopup='true' aria-expanded='expanded' data-auto-close='1' data-trigger='click')
-        ul.select.dropdown-menu(role='menu')
-            li(ng-repeat='item in paragraph.gridOptions.categories|filter:{selectable:true}')
-                a(ng-click='paragraph.toggleColumns(item, !item.visible)')
-                    i.fa.fa-check-square-o.pull-left(ng-if='item.visible')
-                    i.fa.fa-square-o.pull-left(ng-if='!item.visible')
-                    span {{::item.name}}
-            li.divider
-            li
-                a(ng-click='paragraph.selectAllColumns()') Select all
-            li
-                a(ng-click='paragraph.clearAllColumns()') Clear all
-            li.divider
-            li
-                a(ng-click='$hide()') Close

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/public/stylesheets/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/stylesheets/style.scss b/modules/web-console/frontend/public/stylesheets/style.scss
index 857cdce..305e7f7 100644
--- a/modules/web-console/frontend/public/stylesheets/style.scss
+++ b/modules/web-console/frontend/public/stylesheets/style.scss
@@ -604,6 +604,19 @@ button.form-control {
 
             padding: 5px 10px;
         }
+
+        grid-column-selector {
+            margin-right: 5px;
+
+            .btn-ignite {
+                padding: 5px 0;
+                box-shadow: none !important;
+
+                .fa {
+                    font-size: 14px;
+                }
+            }
+        }
     }
 }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/4a1fef5c/modules/web-console/frontend/views/sql/sql.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/sql/sql.tpl.pug b/modules/web-console/frontend/views/sql/sql.tpl.pug
index 94251f9..f215c43 100644
--- a/modules/web-console/frontend/views/sql/sql.tpl.pug
+++ b/modules/web-console/frontend/views/sql/sql.tpl.pug
@@ -124,7 +124,8 @@ mixin query-actions
 mixin table-result-heading-query
     .total.row
         .col-xs-4
-            +ui-grid-settings
+            grid-column-selector(grid-api='paragraph.gridOptions.api')
+                .fa.fa-bars.icon
             label Page: #[b {{paragraph.page}}]
             label.margin-left-dflt Results so far: #[b {{paragraph.rows.length + paragraph.total}}]
             label.margin-left-dflt Duration: #[b {{paragraph.duration | duration}}]
@@ -139,7 +140,8 @@ mixin table-result-heading-query
 mixin table-result-heading-scan
     .total.row
         .col-xs-4
-            +ui-grid-settings
+            grid-column-selector(grid-api='paragraph.gridOptions.api')
+                .fa.fa-bars.icon
             label Page: #[b {{paragraph.page}}]
             label.margin-left-dflt Results so far: #[b {{paragraph.rows.length + paragraph.total}}]
             label.margin-left-dflt Duration: #[b {{paragraph.duration | duration}}]