Student Slicer

You can customize the UI of your slicer with standard DOM elements or 3rd party components.

Here we show you a student slicer which uses noUiSlider on the left.
import * as React from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; import { AppFunc } from './app-func'; // import { App } from './app-class'; // 1. Functional Component sample createRoot(document.getElementById('app')).render(<AppFunc />); // 2. Class Component sample // createRoot(document.getElementById('app')).render(<App />);
import * as React from 'react'; import GC from '@mescius/spread-sheets'; function _getElementById(id) { return document.getElementById(id); } // add data source function init(datas, columnNames) { let table = document.createElement('table'); table.setAttribute('border', 1); table.setAttribute('cellpadding', 0); table.setAttribute('cellspacing', 0); let tableStr = '<thead><tr>'; for (let i = 0; i < columnNames.length; i++) { tableStr += '<th>' + columnNames[i] + '</th>'; } tableStr += '</tr></thead><tbody>'; for (let i = 0; i < datas.length; i++) { tableStr += '<tr>'; for (let j = 0; j < datas[i].length; j++) { tableStr += '<td>' + datas[i][j] + '</td>'; } tableStr += '</tr>'; } tableStr += '</tbody>'; table.innerHTML = tableStr; _getElementById('ss').appendChild(table); } class StudentSlicer { constructor(container) { this.container = container; } setData(slicerData, columnName) { this.slicerData = slicerData; this.columnName = columnName; this.data = slicerData.getData(columnName); this.exclusiveDatas = slicerData.getExclusiveData(columnName); this.slicerData.attachListener(this); this.onDataLoaded(); } onDataLoaded() { let self = this, name = self.columnName; // get column data and build slicer UI. let datas = self.exclusiveDatas, domString = '<strong>' + name + ':</strong><br>'; for (let i = 0; i < datas.length; i++) { let id = name + (i + 1); domString += '<input type="checkbox" id="' + id + '" name="' + name + '" value="' + datas[i] + '" >' + '<label for="' + id + '">' + datas[i] + '</label>'; } self.container.innerHTML = domString; document.querySelector('.options-container').addEventListener('change', function () { let slicer = self, exclusiveData = slicer.slicerData.getExclusiveData(slicer.columnName), children = this.querySelectorAll('input'), indexes = []; for (let i = 0, length = children.length; i < length; i++) { if (children[i].checked) { let value = children[i].value; if (!isNaN(parseInt(value))) { value = parseInt(value); } if (exclusiveData.indexOf(value) != -1) { indexes.push(exclusiveData.indexOf(value)); } } } // respond UI event and invoke doFilter function and doUnfilter function. if (indexes.length === 0) { slicer.slicerData.doUnfilter(slicer.columnName); } else { slicer.slicerData.doFilter(slicer.columnName, { exclusiveRowIndexes: indexes }); } }); } onFiltered() { let slicerdata = this.slicerData; let filteredRowIndexs = slicerdata.getFilteredRowIndexes(); let trs = _getElementById('ss').querySelectorAll('tr'); for (let i = 0; i < slicerdata.data.length; i++) { if (filteredRowIndexs.indexOf(i) !== -1) { trs[i + 1].style.display = ''; } else { trs[i + 1].style.display = 'none'; } } } } class ScoreSlicer { constructor(container) { this.container = container; } setData(slicerData, columnName) { this.slicerData = slicerData; this.columnName = columnName; this.data = slicerData.getData(columnName); this.exclusiveDatas = slicerData.getExclusiveData(columnName); this.slicerData.attachListener(this); this.onDataLoaded(); } onDataLoaded() { let self = this; let connectSlider = document.createElement('div'); connectSlider.style.width = '120px'; connectSlider.style.marginTop = '16px'; connectSlider.style.marginLeft = '10px'; let max = 100; if (self.columnName === 'Total Score') { max = 200; } noUiSlider.create(connectSlider, { start: [0, 200], connect: false, range: { min: 0, max: max } }); let connectBar = document.createElement('div'), connectBase = connectSlider.getElementsByClassName('noUi-base')[0]; // Give the bar a class for styling and add it to the slider. connectBar.className += 'connect'; connectBase.appendChild(connectBar); let span = document.createElement('span'); span.innerHTML = self.columnName; self.container.appendChild(span); let $textSpan = document.createElement('span'); self.container.appendChild($textSpan); self.container.appendChild(connectSlider); let connectHandles = connectSlider.getElementsByClassName('noUi-origin'); connectSlider.noUiSlider.on('update', function (values, handle) { // Pick left for the first handle, right for the second. let side = handle ? 'right' : 'left', // Get the handle position and trim the '%' sign. offset = connectHandles[handle].style.left.slice(0, -1); // Right offset is 100% - left offset if (handle === 1) { offset = 100 - offset; } connectBar.style[side] = offset + '%'; let min = parseInt(values[0]), max = parseInt(values[1]); $textSpan.innerText = min + '-' + max; let slicer = self, exclusiveData = self.slicerData.getExclusiveData(slicer.columnName), indexes = []; for (let i = 0, length = exclusiveData.length; i < length; i++) { if (exclusiveData[i] >= min && exclusiveData[i] <= max) { indexes.push(i); } } slicer.slicerData.doFilter(slicer.columnName, { exclusiveRowIndexes: indexes }); }); } onFiltered() { let slicerdata = this.slicerData; let filteredRowIndexs = slicerdata.getFilteredRowIndexes(); let trs = _getElementById('ss').querySelectorAll('tr'); for (let i = 0; i < slicerdata.data.length; i++) { if (filteredRowIndexs.indexOf(i) !== -1) { trs[i + 1].style.display = ''; } else { trs[i + 1].style.display = 'none'; } } } } export function AppFunc() { React.useEffect(() => { const columnNames = ['Name', 'Class', 'Gender', 'Math', 'English', 'Total Score']; const datas = [ ['Student1', 1, 'female', '69', '66', '135'], ['Student2', 1, 'female', '99', '85', '184'], ['Student3', 1, 'male', '78', '77', '155'], ['Student4', 1, 'male', '54', '80', '134'], ['Student7', 2, 'male', '87', '98', '185'], ['Student8', 2, 'male', '78', '85', '163'], ['Student9', 2, 'female', '100', '90', '190'], ['Student10', 2, 'female', '68', '54', '122'], ['Student11', 2, 'female', '97', '100', '197'], ['Student12', 2, 'female', '81', '90', '171'], ['Student15', 2, 'female', '90', '68', '158'], ['Student16', 3, 'male', '86', '70', '156'], ['Student17', 3, 'male', '88', '89', '177'], ['Student18', 3, 'male', '54', '80', '134'], ['Student19', 3, 'male', '81', '75', '156'], ['Student23', 3, 'female', '78', '98', '176'], ['Student24', 3, 'female', '90', '98', '188'], ['Student25', 3, 'male', '60', '30', '90'], ['Student26', 3, 'female', '0', '0', '0'], ['Student27', 3, 'female', '100', '100', '200'], ['Student28', 3, 'male', '89', '78', '167'] ]; init(datas, columnNames); // create GeneralSlicerData let dataSource = new GC.Spread.Slicers.GeneralSlicerData(datas, columnNames); // attach slicer to GeneralSlicerData. let genderSlicer = new StudentSlicer(_getElementById('slicer_Gender')); genderSlicer.setData(dataSource, 'Gender'); let classSlicer = new StudentSlicer(_getElementById('slicer_Class')); classSlicer.setData(dataSource, 'Class'); let mathSlicer = new ScoreSlicer(_getElementById('slicer_Math')); mathSlicer.setData(dataSource, 'Math'); let enSlicer = new ScoreSlicer(_getElementById('slicer_Englise')); enSlicer.setData(dataSource, 'English'); let totalSlicer = new ScoreSlicer(_getElementById('slicer_Total')); totalSlicer.setData(dataSource, 'Total Score'); }, []); return ( <div class="sample-tutorial"> <div id="ss" class="sample-spreadsheets"></div> <div class="options-container"> <p class="desc">Try changing these options to change the filter on the table.</p> <div id="slicer_Gender" class="control"></div> <div id="slicer_Class" class="control"></div> <div id="slicer_Math" class="control"></div> <div id="slicer_Englise" class="control"></div> <div id="slicer_Total" class="control"></div> </div> </div> ); }
import * as React from 'react'; import GC from '@mescius/spread-sheets'; const Component = React.Component; function _getElementById(id) { return document.getElementById(id); } // add data source function init(datas, columnNames) { let table = document.createElement('table'); table.setAttribute('border', 1); table.setAttribute('cellpadding', 0); table.setAttribute('cellspacing', 0); let tableStr = '<thead><tr>'; for (let i = 0; i < columnNames.length; i++) { tableStr += '<th>' + columnNames[i] + '</th>'; } tableStr += '</tr></thead><tbody>'; for (let i = 0; i < datas.length; i++) { tableStr += '<tr>'; for (let j = 0; j < datas[i].length; j++) { tableStr += '<td>' + datas[i][j] + '</td>'; } tableStr += '</tr>'; } tableStr += '</tbody>'; table.innerHTML = tableStr; _getElementById('ss').appendChild(table); } class StudentSlicer { constructor (container) { this.container = container; } setData (slicerData, columnName) { this.slicerData = slicerData; this.columnName = columnName; this.data = slicerData.getData(columnName); this.exclusiveDatas = slicerData.getExclusiveData(columnName); this.slicerData.attachListener(this); this.onDataLoaded(); } onDataLoaded () { let self = this, name = self.columnName; // get column data and build slicer UI. let datas = self.exclusiveDatas, domString = '<strong>' + name + ':</strong><br>'; for (let i = 0; i < datas.length; i++) { let id = name + (i + 1); domString +='<input type="checkbox" id="' +id +'" name="' +name +'" value="' +datas[i] +'" >' +'<label for="' +id +'">' +datas[i] +'</label>'; } self.container.innerHTML = domString; document.querySelector('.options-container').addEventListener('change', function() { let slicer = self, exclusiveData = slicer.slicerData.getExclusiveData(slicer.columnName), children = this.querySelectorAll('input'), indexes = []; for (let i = 0, length = children.length; i < length; i++) { if (children[i].checked) { let value = children[i].value; if (!isNaN(parseInt(value))) { value = parseInt(value); } if (exclusiveData.indexOf(value) != -1) { indexes.push(exclusiveData.indexOf(value)); } } } // respond UI event and invoke doFilter function and doUnfilter function. if (indexes.length === 0) { slicer.slicerData.doUnfilter(slicer.columnName); } else { slicer.slicerData.doFilter(slicer.columnName, { exclusiveRowIndexes: indexes }); } }); } onFiltered () { let slicerdata = this.slicerData; let filteredRowIndexs = slicerdata.getFilteredRowIndexes(); let trs = _getElementById('ss').querySelectorAll('tr'); for (let i = 0; i < slicerdata.data.length; i++) { if (filteredRowIndexs.indexOf(i) !== -1) { trs[i + 1].style.display = ''; } else { trs[i + 1].style.display = 'none'; } } } } class ScoreSlicer { constructor (container) { this.container = container; } setData (slicerData, columnName) { this.slicerData = slicerData; this.columnName = columnName; this.data = slicerData.getData(columnName); this.exclusiveDatas = slicerData.getExclusiveData(columnName); this.slicerData.attachListener(this); this.onDataLoaded(); } onDataLoaded () { let self = this; let connectSlider = document.createElement('div'); connectSlider.style.width = '120px'; connectSlider.style.marginTop = '16px'; connectSlider.style.marginLeft = '10px'; let max = 100; if (self.columnName === 'Total Score') { max = 200; } noUiSlider.create(connectSlider, { start: [ 0, 200 ], connect: false, range: { min: 0, max: max } }); let connectBar = document.createElement('div'), connectBase = connectSlider.getElementsByClassName('noUi-base')[0]; // Give the bar a class for styling and add it to the slider. connectBar.className += 'connect'; connectBase.appendChild(connectBar); let span = document.createElement('span'); span.innerHTML = self.columnName; self.container.appendChild(span); let $textSpan = document.createElement('span'); self.container.appendChild($textSpan); self.container.appendChild(connectSlider); let connectHandles = connectSlider.getElementsByClassName('noUi-origin'); connectSlider.noUiSlider.on('update', function(values, handle) { // Pick left for the first handle, right for the second. let side = handle ? 'right' : 'left', // Get the handle position and trim the '%' sign. offset = connectHandles[handle].style.left.slice(0, -1); // Right offset is 100% - left offset if (handle === 1) { offset = 100 - offset; } connectBar.style[side] = offset + '%'; let min = parseInt(values[0]), max = parseInt(values[1]); $textSpan.innerText = min + '-' + max; let slicer = self, exclusiveData = self.slicerData.getExclusiveData(slicer.columnName), indexes = []; for (let i = 0, length = exclusiveData.length; i < length; i++) { if (exclusiveData[i] >= min && exclusiveData[i] <= max) { indexes.push(i); } } slicer.slicerData.doFilter(slicer.columnName, { exclusiveRowIndexes: indexes }); }); } onFiltered () { let slicerdata = this.slicerData; let filteredRowIndexs = slicerdata.getFilteredRowIndexes(); let trs = _getElementById('ss').querySelectorAll('tr'); for (let i = 0; i < slicerdata.data.length; i++) { if (filteredRowIndexs.indexOf(i) !== -1) { trs[i + 1].style.display = ''; } else { trs[i + 1].style.display = 'none'; } } } } export class App extends Component { constructor(props) { super(props); } render() { return ( <div class="sample-tutorial"> <div id="ss" class="sample-spreadsheets"></div> <div class="options-container"> <p class="desc">Try changing these options to change the filter on the table.</p> <div id="slicer_Gender" class="control"></div> <div id="slicer_Class" class="control"></div> <div id="slicer_Math" class="control"></div> <div id="slicer_Englise" class="control"></div> <div id="slicer_Total" class="control"></div> </div> </div> ); } componentDidMount () { const columnNames = [ 'Name', 'Class', 'Gender', 'Math', 'English', 'Total Score' ]; const datas = [ [ 'Student1', 1, 'female', '69', '66', '135' ], [ 'Student2', 1, 'female', '99', '85', '184' ], [ 'Student3', 1, 'male', '78', '77', '155' ], [ 'Student4', 1, 'male', '54', '80', '134' ], [ 'Student7', 2, 'male', '87', '98', '185' ], [ 'Student8', 2, 'male', '78', '85', '163' ], [ 'Student9', 2, 'female', '100', '90', '190' ], [ 'Student10', 2, 'female', '68', '54', '122' ], [ 'Student11', 2, 'female', '97', '100', '197' ], [ 'Student12', 2, 'female', '81', '90', '171' ], [ 'Student15', 2, 'female', '90', '68', '158' ], [ 'Student16', 3, 'male', '86', '70', '156' ], [ 'Student17', 3, 'male', '88', '89', '177' ], [ 'Student18', 3, 'male', '54', '80', '134' ], [ 'Student19', 3, 'male', '81', '75', '156' ], [ 'Student23', 3, 'female', '78', '98', '176' ], [ 'Student24', 3, 'female', '90', '98', '188' ], [ 'Student25', 3, 'male', '60', '30', '90' ], [ 'Student26', 3, 'female', '0', '0', '0' ], [ 'Student27', 3, 'female', '100', '100', '200' ], [ 'Student28', 3, 'male', '89', '78', '167' ] ]; init(datas, columnNames); // create GeneralSlicerData let dataSource = new GC.Spread.Slicers.GeneralSlicerData(datas, columnNames); // attach slicer to GeneralSlicerData. let genderSlicer = new StudentSlicer(_getElementById('slicer_Gender')); genderSlicer.setData(dataSource, 'Gender'); let classSlicer = new StudentSlicer(_getElementById('slicer_Class')); classSlicer.setData(dataSource, 'Class'); let mathSlicer = new ScoreSlicer(_getElementById('slicer_Math')); mathSlicer.setData(dataSource, 'Math'); let enSlicer = new ScoreSlicer(_getElementById('slicer_Englise')); enSlicer.setData(dataSource, 'English'); let totalSlicer = new ScoreSlicer(_getElementById('slicer_Total')); totalSlicer.setData(dataSource, 'Total Score'); } }
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/react/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/spread/source/css/external/noUISlider/nouislider.min.css"> <script src="$DEMOROOT$/spread/source/js/external/noUISlider/nouislider.min.js" type="text/javascript"></script> <!-- SystemJS --> <script src="$DEMOROOT$/en/react/node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('$DEMOROOT$/en/lib/react/license.js').then(function () { System.import('./src/app'); }); </script> </head> <body> <div id="app"></div> </body> </html>
.sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: calc(100% - 280px); height: 100%; overflow: auto; float: left; } .options-container { float: right; width: 280px; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; overflow: auto; } .option-group { margin-bottom: 6px; } .control { padding-bottom: 24px; } .noUi-origin { right: auto; width: 0; } .connect { position: absolute; top: 0; bottom: 0; background: #80C9F5; box-shadow: inset 0 0 3px rgba(51, 51, 51, 0.45); } .noUi-state-tap .connect { -webkit-transition: left 300ms, right 300ms; transition: left 300ms, right 300ms; } strong { display: inline-block; margin-bottom: 10px; } input + label { margin-right: 12px; } table th, table td { padding: 4px 8px; } .desc { padding:2px 10px; margin-top: 0; background-color:#F4F8EB; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } #app { height: 100%; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-react': 'npm:@mescius/spread-sheets-react/index.js', '@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js', 'react': 'npm:react/cjs/react.production.js', 'react-dom': 'npm:react-dom/cjs/react-dom.production.js', 'react-dom/client': 'npm:react-dom/cjs/react-dom-client.production.js', 'scheduler': 'npm:scheduler/cjs/scheduler.production.js', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);