Skip to content

Commit 4c5e050

Browse files
committed
1 parent f3952c7 commit 4c5e050

File tree

12 files changed

+179
-25
lines changed

12 files changed

+179
-25
lines changed

services/client/controllers/api/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import assignment from './routes/assignment.js';
33
import picklist from './routes/picklist.js';
44
import form from './routes/form.js';
55
import field from './routes/field.js';
6+
import formEntry from './routes/form-entry.js';
67

78
const router = express.Router();
89
router.use('/assignment', assignment);
910
router.use('/field', field);
1011
router.use('/form', form);
1112
router.use('/picklist', picklist);
13+
router.use('/form-entry', formEntry);
1214

1315
export default router;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Router, json } from 'express';
2+
import handleError from '../utils/handleError.js';
3+
import { validate, schema } from '../utils/validation/index.js';
4+
import models from '#models';
5+
import logger from '#lib/logger.js';
6+
7+
const router = Router();
8+
9+
router.post('/:idOrName', json(), validate(schema.formIdOrNameSchema, {reqParts: ['params']}), async (req, res) => {
10+
try {
11+
12+
res.status(200).json({ message: 'Form entry created' });
13+
} catch (e) {
14+
return handleError(res, req, e);
15+
}
16+
});
17+
18+
export default router;

services/client/controllers/api/utils/validation/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,4 @@ const schema = {
8484
assignment: assignmentSchema
8585
};
8686

87-
export { validate, schema };
87+
export { validate, schema, formatErrorResponse };

services/client/dev/controllers/FormEntryController.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,31 @@ export default class FormEntryController {
202202
return html``;
203203
}
204204

205+
renderActionButtons(){
206+
if ( this.form?.is_archived ) return html``;
207+
return html`
208+
<div class="form-entry-action-buttons">
209+
<button type="submit" class="btn btn--primary">Submit</button>
210+
<button type="button" class="btn btn--invert" @click=${this._onReset.bind(this)}>Reset</button>
211+
</div>
212+
`;
213+
}
214+
215+
_onSubmit(e){
216+
e.preventDefault();
217+
this.submit();
218+
}
219+
220+
async submit(){
221+
console.log('Form Submitted', this.payload);
222+
const r = await this.models.FormEntryModel.create(this.form.name, this.payload);
223+
console.log('Form Entry Result', r);
224+
}
225+
226+
_onReset(){
227+
this.setPayload({});
228+
}
229+
205230
_onAppStateUpdate(e) {
206231
this.update(e);
207232
}

services/client/dev/css/forms.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,21 @@
1212
textarea[disabled] {
1313
background-color: #f0f0f0;
1414
}
15+
.form-entry-action-buttons {
16+
display: flex;
17+
gap: 1rem;
18+
margin-top: 2rem;
19+
align-items: center;
20+
}
21+
fieldset.radio, fieldset.checkbox {
22+
border: none;
23+
margin: 0;
24+
padding: 0;
25+
}
26+
fieldset.radio > legend, fieldset.checkbox > legend {
27+
all: unset;
28+
display: block;
29+
padding-bottom: 0.25rem;
30+
color: var(--ucd-blue-100, #022851);
31+
font-weight: 700;
32+
}

services/client/dev/elements/components/cork-field-container.tpl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export function styles() {
66
display: block;
77
}
88
cork-field-container[invalid] > label,
9-
cork-field-container[invalid] legend {
9+
cork-field-container[invalid] > fieldset > legend {
1010
color: var(--cork-field-container-invalid-label-color, var(--double-decker, #C10230 ));
1111
}
1212
cork-field-container > .cork-field-container__errors {

services/client/dev/elements/templates/fields.js

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ function checkboxMulti(ctl) {
55
const field = ctl.fields.find( f => this.field === f.name );
66
return html`
77
<cork-field-container schema=${ctl.form?.name} path=${field.name} class=${this.noFieldContainer ? '' : 'field-container'}>
8-
<label>${field.label}</label>
9-
<div class='checkbox'>
8+
<fieldset class='checkbox'>
9+
<legend>${field.label}</legend>
1010
${ctl.fieldPicklistItems.map( item => html`
1111
<div class='checkbox-item'>
1212
<div>
@@ -23,7 +23,7 @@ function checkboxMulti(ctl) {
2323
<div class='field-description' ?hidden=${!item.description}>${item.description}</div>
2424
</div>
2525
`)}
26-
</div>
26+
</fieldset>
2727
</cork-field-container>
2828
`;
2929
}
@@ -32,16 +32,16 @@ function radio(ctl) {
3232
const field = ctl.fields.find( f => this.field === f.name );
3333
return html`
3434
<cork-field-container schema=${ctl.form?.name} path=${field.name} class=${this.noFieldContainer ? '' : 'field-container'}>
35-
<label>${field.label}</label>
36-
<div class='radio'>
35+
<fieldset class='radio'>
36+
<legend>${field.label}</legend>
3737
${ctl.fieldPicklistItems.map( item => html`
3838
<div class='radio-item'>
3939
<div>
4040
<input
4141
type="radio"
4242
name=${field.name}
4343
id=${ctl.idGen.get(`field-${field.name}-item-${item.value}`)}
44-
.checked=${(ctl.payload?.[field.name] || []).includes(item.value)}
44+
.checked=${ctl.payload?.[field.name] === item.value}
4545
@input=${() => ctl.setPayloadField(field.name, item.value)}>
4646
<label for=${ctl.idGen.get(`field-${field.name}-item-${item.value}`)}>
4747
${item.label}
@@ -50,7 +50,7 @@ function radio(ctl) {
5050
<div class='field-description' ?hidden=${!item.description}>${item.description}</div>
5151
</div>
5252
`)}
53-
</div>
53+
</fieldset>
5454
</cork-field-container>
5555
`;
5656
}
@@ -156,12 +156,50 @@ function textarea(ctl) {
156156
`;
157157
}
158158

159+
function date(ctl) {
160+
const field = ctl.fields.find( f => this.field === f.name );
161+
return html`
162+
<cork-field-container schema=${ctl.form?.name} path=${field.name} class=${this.noFieldContainer ? '' : 'field-container'}>
163+
<label for=${ctl.idGen.get(`field-${field.name}`)}>${field.label}</label>
164+
<input
165+
type="date"
166+
id=${ctl.idGen.get(`field-${field.name}`)}
167+
.value=${ctl.payload?.[field.name] || ''}
168+
placeholder=${ifDefined(this.placeholder)}
169+
min=${ifDefined(this.min)}
170+
max=${ifDefined(this.max)}
171+
@input=${(e) => ctl.setPayloadField(field.name, e.target.value)}>
172+
<div class='field-description' ?hidden=${!field.description}>${field.description}</div>
173+
</cork-field-container>
174+
`;
175+
}
176+
177+
function datetime(ctl) {
178+
const field = ctl.fields.find( f => this.field === f.name );
179+
return html`
180+
<cork-field-container schema=${ctl.form?.name} path=${field.name} class=${this.noFieldContainer ? '' : 'field-container'}>
181+
<label for=${ctl.idGen.get(`field-${field.name}`)}>${field.label}</label>
182+
<input
183+
type="datetime-local"
184+
id=${ctl.idGen.get(`field-${field.name}`)}
185+
.value=${ctl.payload?.[field.name] || ''}
186+
placeholder=${ifDefined(this.placeholder)}
187+
min=${ifDefined(this.min)}
188+
max=${ifDefined(this.max)}
189+
@input=${(e) => ctl.setPayloadField(field.name, e.target.value)}>
190+
<div class='field-description' ?hidden=${!field.description}>${field.description}</div>
191+
</cork-field-container>
192+
`;
193+
}
194+
159195
export default {
160196
'checkbox-multiple': checkboxMulti,
161197
'radio': radio,
162198
'select': select,
163199
'checkbox-single': checkbox,
164200
'number': number,
165201
'text': text,
166-
'textarea': textarea
202+
'textarea': textarea,
203+
'date': date,
204+
'datetime': datetime
167205
}

services/client/dev/elements/templates/forms/instruction-statistics.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@ import { html } from 'lit';
88
*/
99
function render(ctl) {
1010
return html`
11-
<ref-stats-form-entry-field field="affiliation"></ref-stats-form-entry-field>
12-
<ref-stats-form-entry-field field="instructor-session-type"></ref-stats-form-entry-field>
13-
<ref-stats-form-entry-field field="department" multiple></ref-stats-form-entry-field>
14-
<ref-stats-picklist-item-quick-add
15-
placeholder="Add New Department"
16-
toast-error-text="Error adding department."
17-
toast-success-text="Department added successfully."
18-
@picklist-item-added=${ctl._onPicklistItemAdded.bind(ctl)}
19-
picklist-name-or-id="department">
20-
</ref-stats-picklist-item-quick-add>
21-
<ref-stats-form-entry-field field="dei-focus"></ref-stats-form-entry-field>
22-
<ref-stats-form-entry-field field="participant-count" step='1' min='0'></ref-stats-form-entry-field>
23-
<ref-stats-form-entry-field field="course"></ref-stats-form-entry-field>
24-
<ref-stats-form-entry-field field="notes" rows="5"></ref-stats-form-entry-field>
11+
<form @submit=${ctl._onSubmit.bind(ctl)}>
12+
<ref-stats-form-entry-field field="affiliation"></ref-stats-form-entry-field>
13+
<ref-stats-form-entry-field field="instructor-session-type"></ref-stats-form-entry-field>
14+
<ref-stats-form-entry-field field="department" multiple></ref-stats-form-entry-field>
15+
<ref-stats-picklist-item-quick-add
16+
placeholder="Add New Department"
17+
toast-error-text="Error adding department."
18+
toast-success-text="Department added successfully."
19+
@picklist-item-added=${ctl._onPicklistItemAdded.bind(ctl)}
20+
picklist-name-or-id="department">
21+
</ref-stats-picklist-item-quick-add>
22+
<ref-stats-form-entry-field field="dei-focus"></ref-stats-form-entry-field>
23+
<ref-stats-form-entry-field field="participant-count" step='1' min='0'></ref-stats-form-entry-field>
24+
<ref-stats-form-entry-field field="course"></ref-stats-form-entry-field>
25+
<ref-stats-form-entry-field field="notes" rows="5"></ref-stats-form-entry-field>
26+
<ref-stats-form-entry-field field="date"></ref-stats-form-entry-field>
27+
${ ctl.renderActionButtons() }
28+
</form>
2529
`;}
2630

2731

services/lib/cork/models/FormEntryModel.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {BaseModel} from '@ucd-lib/cork-app-utils';
22
import FormEntryService from '../services/FormEntryService.js';
33
import FormEntryStore from '../stores/FormEntryStore.js';
44

5+
import clearCache from '../utils/clearCache.js';
6+
57
class FormEntryModel extends BaseModel {
68

79
constructor() {
@@ -11,6 +13,17 @@ class FormEntryModel extends BaseModel {
1113
this.service = FormEntryService;
1214

1315
this.register('FormEntryModel');
16+
17+
this.inject('ValidationModel');
18+
}
19+
20+
async create(formId, data) {
21+
const res = await this.service.create(formId, data);
22+
this.ValidationModel.notify(formId, res);
23+
if ( res.state === 'loaded' ) {
24+
clearCache();
25+
}
26+
return res;
1427
}
1528

1629
}

services/lib/cork/services/FormEntryService.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,47 @@
1-
import {BaseService} from '@ucd-lib/cork-app-utils';
1+
import {BaseService, digest} from '@ucd-lib/cork-app-utils';
22
import FormEntryStore from '../stores/FormEntryStore.js';
33

4+
import serviceUtils from '../utils/serviceUtils.js';
5+
46
class FormEntryService extends BaseService {
57

68
constructor() {
79
super();
810
this.store = FormEntryStore;
911
}
1012

13+
get baseUrl(){
14+
return `/api/form-entry`;
15+
}
16+
17+
async create(formId, data){
18+
let id = digest({formId, data});
19+
const store = this.store.data.create;
20+
21+
const appStateOptions = {
22+
errorSettings: {message: 'Error during form submission'}
23+
};
24+
25+
await this.checkRequesting(
26+
id, store,
27+
() => this.request({
28+
url : `${this.baseUrl}/${formId}`,
29+
json: true,
30+
fetchOptions: {
31+
method: 'POST',
32+
body: data
33+
},
34+
onUpdate : resp => this.store.set(
35+
{...resp, id},
36+
store,
37+
null,
38+
appStateOptions
39+
)
40+
})
41+
);
42+
return store.get(id);
43+
}
44+
1145
}
1246

1347
const service = new FormEntryService();

0 commit comments

Comments
 (0)