From 28b2773ca6ad6a10032cf85d8ca3fbe1bba75f73 Mon Sep 17 00:00:00 2001 From: Hermann <blumh@ethz.ch> Date: Sun, 5 Aug 2018 22:12:11 +0200 Subject: [PATCH] basics for filtering in lists --- src/listcontroller.js | 7 ++++ src/views/elements.js | 20 ++++++----- src/views/tableView.js | 82 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 90 insertions(+), 19 deletions(-) diff --git a/src/listcontroller.js b/src/listcontroller.js index 36ef1d5..a527ece 100644 --- a/src/listcontroller.js +++ b/src/listcontroller.js @@ -14,6 +14,7 @@ export default class DatalistController { } this.query = query || {}; this.search = null; + this.filter = null; // state pointer that is counted up every time the table is refreshed so // we can tell infinite scroll that the data-version has changed. this.stateCounter = Stream(0); @@ -45,6 +46,7 @@ export default class DatalistController { const query = Object.assign({}, this.query); query.max_results = 10; query.page = pageNum; + query.where = { ...this.filter, ...this.query.where }; return new Promise((resolve) => { this.handler.get(query).then((data) => { @@ -100,6 +102,11 @@ export default class DatalistController { } } + setFilter(filter) { + this.filter = filter; + this.refresh(); + } + setQuery(query) { this.query = query; this.query.search = this.search; diff --git a/src/views/elements.js b/src/views/elements.js index 6635895..24cd5ca 100644 --- a/src/views/elements.js +++ b/src/views/elements.js @@ -289,33 +289,37 @@ export class chip { view({ attrs: { svg, - color = '#000000', - background = '#dddddd', - ...styleAttrs + background = '#ffffff', + textColor = '#000000', + svgColor = '#000000', + svgBackground = '#dddddd', + ...attrs }, children, }) { return m('div', { style: { height: '32px', - 'background-color': '#ffffff', + 'background-color': background, + color: textColor, 'border-radius': '16px', // if there is a border, things are weirdly shifted - padding: styleAttrs.border ? '3px 8px 4px 6px' : '4px 8px', + padding: attrs.border ? '3px 8px 4px 6px' : '4px 8px', display: 'inline-flex', - ...styleAttrs, + ...attrs, }, + ...attrs.onclick ? { onclick: attrs.onclick } : {}, }, [ svg && m('div', { style: { - 'background-color': background, + 'background-color': svgBackground, 'border-radius': '12px', margin: '0px 4px 0px -2px', height: '24px', width: '24px', padding: '2px 2px 2px 4px', }, - }, m(Icon, { svg: { content: m.trust(svg) }, size: 'small', style: { color } })), + }, m(Icon, { svg: { content: m.trust(svg) }, size: 'small', style: { svgColor } })), m('div', { style: { 'line-height': '24px' } }, children), ]); } diff --git a/src/views/tableView.js b/src/views/tableView.js index 2baf072..d24975b 100644 --- a/src/views/tableView.js +++ b/src/views/tableView.js @@ -3,22 +3,14 @@ import infinite from 'mithril-infinite'; import { List, ListTile, Toolbar, Search, Button } from 'polythene-mithril'; import 'polythene-css'; import { styler } from 'polythene-core-css'; +import { chip, icons } from './elements'; const tableStyles = [ { - '.tabletool': { - display: 'grid', - height: '100%', - 'grid-template-rows': '48px calc(100% - 78px)', - 'background-color': 'white', - }, '.toolbar': { 'grid-row': 1, display: 'flex', }, - '.scrollTable': { - 'grid-row': 2, - }, '.tableTile': { padding: '10px', 'border-bottom': '1px solid rgba(0, 0, 0, 0.12)', @@ -29,6 +21,22 @@ const tableStyles = [ styler.add('tableview', tableStyles); + +class FilterChip { + view({ attrs: { selected = false, onclick = () => {} }, children }) { + return m(chip, { + 'margin-left': '5px', + 'margin-right': '5px', + background: selected ? '#aaaaaa' : '#dddddd', + svgBackground: '#aaaaaa', + textColor: selected ? '#000000' : '#999999', + svgColor: '#000000', + svg: selected ? icons.checked : null, + onclick, + }, children); + } +} + export default class TableView { /* Shows a table of objects for a given API resource. * @@ -40,11 +48,16 @@ export default class TableView { * Works with embedded resources, i.e. if you add * { embedded: { event: 1 } } to a list of eventsignups, * you can display event.title_de as a table key + * - filters: list of list of objects, each inner list is a group of mutual exclusive + * filters. + * A filter can have properties 'name', 'query' and optionally 'selected' for + * the initial selection state. */ constructor({ attrs: { keys, tileContent, + filters = null, clickOnRows = (data) => { m.route.set(`/${data._links.self.href}`); }, }, }) { @@ -53,6 +66,9 @@ export default class TableView { this.tileContent = tileContent; this.clickOnRows = clickOnRows; this.searchValue = ''; + // make a copy of filters so we can toggle the selected status + this.filters = filters ? filters.map(filterGroup => + filterGroup.map(filter => Object.assign({}, filter))) : null; } getItemData(data) { @@ -93,7 +109,15 @@ export default class TableView { tableHeight = false, }, }) { - return m('div.tabletool', [ + return m('div.tabletool', { + style: { + display: 'grid', + height: '100%', + 'grid-template-rows': this.filters ? + '48px 40px calc(100% - 78px)' : '48px calc(100% - 78px)', + 'background-color': 'white', + }, + }, [ m(Toolbar, { className: 'toolbar', compact: true, @@ -119,9 +143,45 @@ export default class TableView { }) : '', ], }), + // please beare with this code, it is the only way possible to track the selection + // status of all the filters of the same group and make sure that they are really + // mutually exclusive (that way when you click on one filter in the group, the other + // ones in this group will be deselected) + this.filters && m('div', { + style: { + height: '40px', + 'overflow-x': 'scroll', + 'white-space': 'nowrap', + padding: '0px 5px', + }, + }, [].concat(['Filters: '], ...[...this.filters.keys()].map(filterGroupIdx => + [...this.filters[filterGroupIdx].keys()].map((filterIdx) => { + const thisFilter = this.filters[filterGroupIdx][filterIdx]; + return m(FilterChip, { + selected: thisFilter.selected, + onclick: () => { + if (!thisFilter.selected) { + // set all filters in this group to false + [...this.filters[filterGroupIdx].keys()].forEach((i) => { + this.filters[filterGroupIdx][i].selected = false; + }); + // now set this filter to selected + this.filters[filterGroupIdx][filterIdx].selected = true; + console.log('filter set: ', thisFilter.query); + controller.setFilter(thisFilter.query); + } else { + this.filters[filterGroupIdx][filterIdx].selected = false; + controller.setFilter({}); + } + }, + }, thisFilter.name); + })))), m(List, { className: 'scrollTable', - style: tableHeight ? { height: tableHeight } : {}, + style: { + 'grid-row': this.filters ? 3 : 2, + ...tableHeight ? { height: tableHeight } : {}, + }, tiles: [ m(ListTile, { className: 'tableTile', -- GitLab