From d4dd473a8c55f7135049cc4da6c775fbd6e8ec04 Mon Sep 17 00:00:00 2001 From: Hermann Blum <hermannsblum@yahoo.de> Date: Sat, 27 Jan 2018 19:57:48 +0100 Subject: [PATCH] split tables and lists between View and Controller In order to be able to refresh single data lists (e.g. the groupmemberships after a membership got added), this commit disentangles data from view and enables the outside userTool to refresh single lists. --- src/listcontroller.js | 34 +++++++++++++++++++ src/userTool.js | 73 +++++++++++++++++++++++++++-------------- src/views/selectList.js | 40 ++++++++++------------ src/views/tableView.js | 49 ++++++++------------------- 4 files changed, 113 insertions(+), 83 deletions(-) create mode 100644 src/listcontroller.js diff --git a/src/listcontroller.js b/src/listcontroller.js new file mode 100644 index 0000000..d2fbdb6 --- /dev/null +++ b/src/listcontroller.js @@ -0,0 +1,34 @@ +import * as m from 'mithril'; +import { ResourceHandler } from './auth'; + +export default class DatalistController { + constructor(resource, query = {}, searchKeys = false) { + this.handler = new ResourceHandler(resource, searchKeys); + this.query = query; + this.items = []; + this.refresh(); + } + + addItem(item) { + this.items.push(item); + m.redraw(); + } + + refresh() { + this.handler.get(this.query).then((data) => { + this.items = data._items; + m.redraw(); + }); + } + + setSearch(search) { + this.query.search = search; + this.refresh(); + } + + setQuery(query) { + this.query = query; + this.refresh(); + } +} + diff --git a/src/userTool.js b/src/userTool.js index 8e302c3..93c8b1e 100644 --- a/src/userTool.js +++ b/src/userTool.js @@ -4,15 +4,44 @@ import TableView from './views/tableView'; import { inputGroup, selectGroup, submitButton } from './views/elements'; import SelectList from './views/selectList'; import { users as config } from './config.json'; -import { getSession } from './auth'; +import DatalistController from './listcontroller'; const m = require('mithril'); class UserView extends ItemView { constructor() { super('users'); - this.memberships = []; + // a controller to handle the groupmemberships of this user + this.groupmemberships = new DatalistController('groupmemberships', { + where: { user: this.id }, + embedded: { group: 1 }, + }); + // a controller to handle the eventsignups of this user + this.eventsignups = new DatalistController('eventsignups', { + where: { user: this.id }, + embedded: { event: 1 }, + }); + // initially, don't display the choice field for a new group + // (this will be displayed once the user clicks on 'new') this.groupchoice = false; + // a controller to handle the list of possible groups to join + this.groupcontroller = new DatalistController('groups', {}, ['name']); + // exclude the groups where the user is already a member + this.groupmemberships.handler.get({ where: { user: this.id } }) + .then((data) => { + const groupIds = data._items.map(item => item.group); + this.groupcontroller.setQuery({ + where: { _id: { $nin: groupIds } }, + }); + }); + } + + oninit() { + this.handler.getItem(this.id, this.embedded).then((item) => { + this.data = item; + m.redraw(); + }); + this.groupmemberships.refresh(); } view() { @@ -31,18 +60,20 @@ class UserView extends ItemView { const detailKeys = [ 'email', 'phone', 'nethz', 'legi', 'rfid', 'department', 'gender']; + // Selector that is only displayed if "new" is clicked in the + // groupmemberships. Selects a group to request membership for. const groupSelect = m(SelectList, { - resource: 'groups', - searchKeys: ['name'], + controller: this.groupcontroller, itemView: { view({ attrs }) { return m('span', attrs.name); }, }, onSubmit: (group) => { - getSession().then((apiSession) => { - apiSession.post('groupmemberships', { - user: this.data._id, - group: group._id, - }); + this.groupmemberships.handler.post({ + user: this.data._id, + group: group._id, + }).then((data) => { + this.groupmemberships.refresh(); + this.groupchoice = false; }); }, }); @@ -57,25 +88,16 @@ class UserView extends ItemView { m('h2', 'Memberships'), m('br'), this.groupchoice ? groupSelect : '', m(TableView, { - resource: 'groupmemberships', + controller: this.groupmemberships, keys: ['group.name', 'expiry'], - searchKeys: ['group.name'], - query: { - where: { user: this.id }, - embedded: { group: 1 }, - }, - onAdd: () => { - this.groupchoice = true; - }, + titles: ['groupname', 'expiry'], + onAdd: () => { this.groupchoice = true; }, }), m('h2', 'Signups'), m('br'), m(TableView, { - resource: 'eventsignups', + controller: this.eventsignups, keys: ['event.title_de'], - query: { - where: { user: this.id }, - embedded: { event: 1 }, - }, + titles: ['event'], }), ]); } @@ -179,9 +201,12 @@ export class UserModal { } export class UserTable { + constructor() { + this.ctrl = new DatalistController('users', {}, config.tableKeys); + } view() { return m(TableView, { - resource: 'users', + controller: this.ctrl, keys: config.tableKeys, titles: config.tableKeys.map(key => config.keyDescriptors[key] || key), onAdd: () => { m.route.set('/newuser'); }, diff --git a/src/views/selectList.js b/src/views/selectList.js index bbb56c1..b322a2c 100644 --- a/src/views/selectList.js +++ b/src/views/selectList.js @@ -1,4 +1,3 @@ -import TableView from './tableView'; import { submitButton } from './elements'; const m = require('mithril'); @@ -22,56 +21,46 @@ function debounce(func, wait, immediate) { timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; -}; +} -export default class SelectList extends TableView { - constructor({ - attrs: { - resource, - searchKeys, - itemView, - onSubmit = () => {}, - }, - }) { - super({ attrs: { resource, keys: searchKeys } }); - this.itemView = itemView; +export default class SelectList { + constructor() { this.selected = null; this.showList = false; - this.onSubmit = onSubmit; } - view() { + view({ attrs: { controller, itemView, onSubmit = () => {} } }) { // input search and select field const updateList = debounce(() => { - this.buildList(); + controller.refresh(); }, 500); let input = m('input.form-control', { onfocus: () => { this.showList = true; }, onblur: debounce(() => { this.showList = false; m.redraw(); }, 100), onkeyup: (e) => { - this.query.search = e.target.value; + controller.setSearch(e.target.value); updateList(); }, }); if (this.selected !== null) { input = m('div.btn-group', [ - m('div.btn.btn-default', m(this.itemView, this.selected)), + m('div.btn.btn-default', m(itemView, this.selected)), m('div.btn.btn-primary', { onclick: () => { this.selected = null; - this.query = {}; - this.buildList(); + controller.setSearch(''); + controller.refresh(); }, }, m('span.glyphicon.glyphicon-remove-sign')), ]); } // list of items - const list = m('ul.list-group', this.items.map(item => + const list = m('ul.list-group', controller.items.map(item => m('button.list-group-item', { onclick: () => { this.selected = item; this.showList = false; }, - }, m(this.itemView, item)))); + }, m(itemView, item)))); return m('div', { }, [ @@ -83,7 +72,12 @@ export default class SelectList extends TableView { active: this.selected !== null, args: { class: 'btn-primary', - onclick: () => { this.onSubmit(this.selected); }, + onclick: () => { + onSubmit(this.selected); + this.selected = null; + controller.setSearch(''); + controller.refresh(); + }, }, }), ]), diff --git a/src/views/tableView.js b/src/views/tableView.js index 33c4a00..03c748d 100644 --- a/src/views/tableView.js +++ b/src/views/tableView.js @@ -1,5 +1,3 @@ -import { ResourceHandler } from '../auth'; - const m = require('mithril'); class TableRow { @@ -23,7 +21,6 @@ class TableRow { } } - export default class TableView { /* Shows a table of objects for a given API resource. * @@ -39,61 +36,41 @@ export default class TableView { * https://docs.mongodb.com/v3.2/reference/operator/query/ * e.g. : { where: {name: somename } } */ - constructor({ + constructor() { + this.search = ''; + } + + view({ attrs: { keys, titles, - resource, - query = false, - searchKeys = false, + controller, onAdd = () => {}, }, }) { - this.items = []; - this.showKeys = keys; - this.titles = titles || keys; - this.handler = new ResourceHandler(resource, searchKeys); - // the querystring is either given or will be parsed from the url - this.query = query || m.route.param(); - this.onAdd = onAdd; - } - - buildList() { - this.handler.get(this.query).then((data) => { - this.items = data._items; - console.log(this.items); - m.redraw(); - }); - } - - oninit() { - this.buildList(); - } - - view() { return m('div', [ m('div.row', [ m('div.col-xs-4', [ m('div.input-group', [ m('input[name=search].form-control', { - value: this.query.search, - onchange: m.withAttr('value', (value) => { this.query.search = value; }), + value: this.search, + onchange: m.withAttr('value', (value) => { this.search = value; }), }), m('span.input-group-btn', m('button.btn.btn-default', { - onclick: () => { this.buildList(); }, + onclick: () => { controller.setSearch(this.search); }, }, 'Search')), ]), ]), m('div.col-xs-4', [ m('div.btn.btn-default', { - onclick: () => { this.onAdd(); }, + onclick: () => { onAdd(); }, }, 'New'), ]), ]), m('table.table.table-hover', [ - m('thead', m('tr', this.titles.map(title => m('th', title)))), - m('tbody', this.items.map(item => - m(TableRow, { showKeys: this.showKeys, data: item }))), + m('thead', m('tr', titles.map(title => m('th', title)))), + m('tbody', controller.items.map(item => + m(TableRow, { showKeys: keys, data: item }))), ]), ]); } -- GitLab