Skip to content
Snippets Groups Projects
Commit b5d69e7e authored by Sandro Lutz's avatar Sandro Lutz Committed by scmoritz
Browse files

Implement additional fields for event signup

parent 84bf07e4
No related branches found
No related tags found
1 merge request!24Complete event signup
......@@ -17,6 +17,8 @@
"author": "AMIV IT team",
"license": "ISC",
"dependencies": {
"ajv": "^5.5.0",
"email-validator": "^1.1.1",
"babel-core": "^6.26.0",
"babel-cli": "^6.26.0",
"babel-loader": "^7.1.2",
......@@ -30,4 +32,4 @@
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.3"
}
}
\ No newline at end of file
}
......@@ -45,11 +45,18 @@ export function checkCurrentSignup() {
}).then((result) => {
[this.currentSignup] = result._items;
this.currentSignupLoaded = true;
log(this.currentSignup);
});
}
export function signupCurrent(email = '') {
export function signupCurrent(additionalFields, email = '') {
let additionalFieldsString;
if (this.current.additional_fields === undefined ||
additionalFields === null || typeof additionalFields !== 'object') {
additionalFieldsString = undefined;
} else {
additionalFieldsString = JSON.stringify(additionalFields);
}
if (isLoggedIn()) {
log(`UserId: ${getUserId()}`);
m.request({
......@@ -57,6 +64,7 @@ export function signupCurrent(email = '') {
url: `${apiUrl}/eventsignups`,
data: {
event: this.current._id,
additional_fields: additionalFieldsString,
user: getUserId(),
},
headers: getToken() ? {
......@@ -64,19 +72,19 @@ export function signupCurrent(email = '') {
} : {},
}).then(() => { this.checkCurrentSignup(); });
} else if (this.current.allow_email_signup) {
if (email.length > 0) {
m.request({
method: 'POST',
url: `${apiUrl}/eventsignups`,
data: {
event: this.current._id,
email,
},
headers: getToken() ? {
Authorization: `Token ${getToken()}`,
} : {},
}).then(() => { this.checkCurrentSignup(); });
}
log(`Email: ${email}`);
m.request({
method: 'POST',
url: `${apiUrl}/eventsignups`,
data: {
event: this.current._id,
additional_fields: additionalFieldsString,
email,
},
headers: getToken() ? {
Authorization: `Token ${getToken()}`,
} : {},
}).then(() => { this.checkCurrentSignup(); });
}
}
......
import * as events from '../models/event';
import * as EmailValidator from 'email-validator';
import * as events from '../models/events';
import { log } from '../models/log';
import { isLoggedIn } from '../models/auth';
import { inputGroup, submitButton } from './formFields';
import JSONSchemaForm from './jsonSchemaForm';
const m = require('mithril');
let signupEmail = '';
class EventSignupForm {
static oninit() {
class EventSignupForm extends JSONSchemaForm {
oninit(vnode) {
super.oninit(vnode);
this.emailErrors = [];
this.emailValid = false;
if (isLoggedIn()) {
events.checkCurrentSignup();
}
}
static view() {
if (typeof events.getCurrent() === 'undefined') {
return m('div');
submit() {
if (isLoggedIn()) {
events.signupCurrent(super.getValue());
} else {
events.signupCurrent(super.getValue(), this.email);
}
}
view() {
// do not render anything if there is no data yet
if (typeof events.getCurrent() === 'undefined') return m();
if (isLoggedIn()) {
if (!events.currentSignupHasLoaded()) {
return m('span', 'Loading...');
} else if (typeof events.getCurrentSignup() === 'undefined') {
return m('button', { onclick() { events.signupCurrent(); } }, 'signup');
// do not render form if there is no signup data of the current user
if (!events.currentSignupHasLoaded()) return m('span', 'Loading...');
if (typeof events.getCurrentSignup() === 'undefined') {
const elements = this.renderFormElements();
elements.push(m(submitButton, {
active: super.isValid(),
args: {
onclick: () => this.submit(),
},
text: 'Signup',
}));
return m('form', elements);
}
return m('div', 'You have already signed up for this event.');
} else if (events.getCurrent().allow_email_signup) {
return m('div', [
m('input', {
const elements = this.renderFormElements();
elements.push(m(inputGroup, {
name: 'email',
title: 'Email',
args: {
type: 'text',
placeholder: 'Email',
oninput: m.withAttr('value', (value) => { signupEmail = value; }),
value: signupEmail,
}),
m('button', { onclick() { events.signupCurrent(signupEmail); } }, 'signup'),
]);
},
onchange: (e) => {
// bind changed data
this.email = e.target.value;
// validate if email address has the right structure
if (EmailValidator.validate(this.email)) {
this.emailValid = true;
this.emailErrors = [];
} else {
this.emailValid = false;
this.emailErrors = ['Not a valid email address'];
}
},
getErrors: () => this.emailErrors,
value: this.email,
}));
elements.push(m(submitButton, {
active: this.emailValid && super.isValid(),
args: {
onclick: () => this.submit(),
},
text: 'Signup',
}));
return m('form', elements);
}
return m('div');
return m('div', 'This event is for AMIV members only.');
}
}
......@@ -44,7 +88,27 @@ export default class EventDetails {
static view() {
if (typeof events.getCurrent() === 'undefined') {
return m('div');
return m();
}
log(events.getCurrent());
let eventSignupForm;
const now = new Date();
const registerStart = new Date(events.getCurrent().time_register_start);
const registerEnd = new Date(events.getCurrent().time_register_end);
log(`Now: ${now}`);
log(`Start: ${registerStart}`);
log(`End: ${registerEnd}`);
if (registerStart <= now) {
if (registerEnd >= now) {
eventSignupForm = m(EventSignupForm, {
schema: events.getCurrent().additional_fields === undefined ?
undefined : JSON.parse(events.getCurrent().additional_fields),
});
} else {
eventSignupForm = m('div', 'The registration period is over.');
}
} else {
eventSignupForm = m('div', `The registration starts at ${registerStart}`);
}
return m('div', [
m('h1', events.getCurrent().title_de),
......@@ -52,7 +116,7 @@ export default class EventDetails {
m('span', events.getCurrent().signup_count),
m('span', events.getCurrent().spots),
m('p', events.getCurrent().description_de),
m(EventSignupForm),
eventSignupForm,
]);
}
}
import * as events from '../models/event';
import * as events from '../models/events';
const m = require('mithril');
......@@ -16,6 +16,13 @@ export default class EventList {
});
}
static onbeforeupdate(vnode, old) {
// when attrs are different it means we changed route
if (vnode.attrs.id !== old.attrs.id) {
events.reload();
}
}
static view() {
return m('table', [
m('thead', [
......
const m = require('mithril');
export class inputGroup {
constructor(vnode) {
// Link the error-getting function from the binding
this.getErrors = () => [];
if (vnode.attrs.getErrors) {
this.getErrors = vnode.attrs.getErrors;
}
}
view(vnode) {
// set display-settings accoridng to error-state
let errorField = null;
let groupClasses = vnode.attrs.classes ? vnode.attrs.classes : '';
const errors = this.getErrors();
if (errors.length > 0) {
errorField = m('span', `Error: ${errors.join(', ')}`);
groupClasses += ' has-error';
}
let { args } = vnode.attrs;
if (args === undefined) {
args = {};
}
args.value = vnode.attrs.value;
args.onchange = vnode.attrs.onchange;
if (['radio', 'checkbox'].includes(args.type)) {
return m('div', { class: groupClasses }, [
m(`input[name=${vnode.attrs.name}][id=${vnode.attrs.name}]`, args),
m(`label[for=${vnode.attrs.name}]`, vnode.attrs.title),
errorField,
]);
}
return m('div', { class: groupClasses }, [
m(`label[for=${vnode.attrs.name}]`, vnode.attrs.title),
m(`input[name=${vnode.attrs.name}][id=${vnode.attrs.name}]`, args),
errorField,
]);
}
}
export class selectGroup {
oninit() {
this.value = [];
}
view(vnode) {
switch (vnode.attrs.args.type) {
case 'buttons': {
if (vnode.attrs.args.multipleSelect) {
return m('div', { class: vnode.attrs.classes }, [
m(`label[for=${vnode.attrs.name}]`, vnode.attrs.title),
m('div', vnode.attrs.args.options.map(option =>
m(inputGroup, {
name: vnode.attrs.name,
title: option,
value: option,
onchange: (e) => {
if (e.target.checked) {
this.value.push(e.target.value);
} else {
this.value = this.value.filter(item => item !== e.target.value);
}
vnode.attrs.onchange({ target: { name: e.target.name, value: this.value } });
},
args: { type: 'checkbox' },
}))),
]);
}
return m('div', { class: vnode.attrs.classes }, [
m('div', vnode.attrs.options.map(option =>
m(inputGroup, {
name: vnode.attrs.name,
title: option,
onchange: vnode.attrs.onchange,
args: { type: 'radio' },
}))),
m(`label[for=${vnode.attrs.name}]`, vnode.attrs.title),
]);
}
case 'select':
default: {
if (vnode.attrs.args.multipleSelect) {
return m('div', { class: vnode.attrs.classes }, [
m(`label[for=${vnode.attrs.name}]`, vnode.attrs.title),
m(
`select[name=${vnode.attrs.name}][id=${vnode.attrs.name}]`,
{
onchange: (e) => {
const value = [];
let opt;
for (let i = 0; i < e.target.options.length; i += 1) {
opt = e.target.options[i];
if (opt.selected) {
value.push(opt);
}
}
vnode.attrs.onchange(e);
},
multiple: true,
},
vnode.attrs.options.map(option => m('option', option)),
),
]);
}
return m('div', { class: vnode.attrs.classes }, [
m(`label[for=${vnode.attrs.name}]`, vnode.attrs.title),
m(
`select[name=${vnode.attrs.name}][id=${vnode.attrs.name}]`,
{
value: vnode.attrs.value,
onchange: vnode.attrs.onchange,
multiple: false,
},
vnode.attrs.args.options.map(option => m('option', option)),
),
]);
}
}
}
}
export class submitButton {
static view(vnode) {
const { args } = vnode.attrs;
if (!vnode.attrs.active) {
args.disabled = 'disabled';
}
return m('button[type=button]', args, vnode.attrs.text);
}
}
import Ajv from 'ajv';
import { inputGroup, selectGroup } from './formFields';
const m = require('mithril');
const jsonSchemaDraft04 = require('ajv/lib/refs/json-schema-draft-04.json');
export default class JSONSchemaForm {
constructor() {
this.data = {};
this.errors = {};
this.ajv = new Ajv({
missingRefs: 'ignore',
errorDataPath: 'property',
allErrors: true,
});
this.ajv.addMetaSchema(jsonSchemaDraft04);
}
oninit(vnode) {
this.schema = vnode.attrs.schema;
if (this.schema === null || typeof this.schema !== 'object') {
this.schema = undefined;
} else {
this.ajv.addSchema(this.schema, 'schema');
}
}
// bind form-fields to the object data and validation
bind(attrs) {
// initialize error-list for every bound field
if (!this.errors[attrs.name]) this.errors[attrs.name] = [];
const boundFormelement = {
onchange: (e) => {
// bind changed data
this.data[e.target.name] = e.target.value;
// validate against schema
const validate = this.ajv.getSchema('schema');
this.valid = validate(this.data);
if (this.valid) {
Object.keys(this.errors).forEach((field) => {
this.errors[field] = [];
});
} else {
// get errors for respective fields
Object.keys(this.errors).forEach((field) => {
const errors = validate.errors.filter(error =>
`.${field}` === error.dataPath);
this.errors[field] = errors.map(error => error.message);
});
}
},
getErrors: () => this.errors[attrs.name],
value: this.data[attrs.name],
};
// add the given attributes
Object.keys(attrs).forEach((key) => { boundFormelement[key] = attrs[key]; });
return boundFormelement;
}
view() {
const elements = this.renderFormElements();
return m('form', elements);
}
isValid() {
return this.schema === undefined || this.valid;
}
getValue() {
return this.data;
}
// render all schema properties to an array of form-fields
renderFormElements() {
const elements = [];
if (this.schema !== undefined) {
Object.keys(this.schema.properties).forEach((key) => {
elements.push(this._renderProperty(key, this.schema.properties[key]));
});
}
return elements;
}
// render schema property to form-fields
_renderProperty(key, item) {
if ('enum' in item) {
return m(selectGroup, this.bind({
name: key,
title: item.description,
args: {
options: item.enum,
type: 'select',
multipleSelect: false,
},
}));
}
switch (item.type) {
case 'integer': {
return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'number', step: 1 } }));
}
case 'number': {
return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'number' } }));
}
case 'boolean': {
return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'checkbox' } }));
}
case 'array': {
return m(selectGroup, this.bind({
name: key,
title: item.description,
args: {
options: item.items.enum,
type: item.items.enum.length > 8 ? 'select' : 'buttons',
multipleSelect: true,
},
}));
}
case 'string':
default: {
return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'text' } }));
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment