diff --git a/src/models/studydocs.js b/src/models/studydocs.js
index 7869724f91b60b9dddd318a732c4a88bd0846729..48b8cc2928e26802e6b5d3b7747e60482bbbb015 100644
--- a/src/models/studydocs.js
+++ b/src/models/studydocs.js
@@ -27,6 +27,25 @@ export function load(query = {}) {
   });
 }
 
+export function getInputSuggestions(field, input) {
+  const query = {};
+  query[field] = { $regex: `^(?i).*${input}.*` };
+  // TODO: debug Error 502 Bad Gateway returned by API
+  // const projection = {};
+  // projection[field] = 1;
+  const queryEncoded = m.buildQueryString({
+    where: JSON.stringify(query),
+    // projection: JSON.stringify(projection),
+  });
+  return m.request({
+    method: 'GET',
+    url: `${apiUrl}/studydocuments?${queryEncoded}`,
+    headers: {
+      Authorization: `Token ${getToken()}`,
+    },
+  });
+}
+
 export function reload() {
   return load(querySaved);
 }
diff --git a/src/views/eventDetails.js b/src/views/eventDetails.js
index 27cacf5e137fd46f6a2d067da2f6709e5298c2d5..3408450e7c0a1cb44da80cb7414b0ba0b9a3e4e0 100644
--- a/src/views/eventDetails.js
+++ b/src/views/eventDetails.js
@@ -4,7 +4,7 @@ import * as events from '../models/events';
 import { log } from '../models/log';
 import { isLoggedIn } from '../models/auth';
 import inputGroup from './form/inputGroup';
-import submitButton from './form/submitButton';
+import button from './form/button';
 import JSONSchemaForm from './form/jsonSchemaForm';
 
 class EventSignupForm extends JSONSchemaForm {
@@ -34,7 +34,7 @@ class EventSignupForm extends JSONSchemaForm {
       if (!events.currentSignupHasLoaded()) return m('span', 'Loading...');
       if (typeof events.getCurrentSignup() === 'undefined') {
         const elements = this.renderFormElements();
-        elements.push(m(submitButton, {
+        elements.push(m(button, {
           active: super.isValid(),
           args: {
             onclick: () => this.submit(),
@@ -68,7 +68,7 @@ class EventSignupForm extends JSONSchemaForm {
         getErrors: () => this.emailErrors,
         value: this.email,
       }));
-      elements.push(m(submitButton, {
+      elements.push(m(button, {
         active: this.emailValid && super.isValid(),
         args: {
           onclick: () => this.submit(),
diff --git a/src/views/form/button.js b/src/views/form/button.js
new file mode 100644
index 0000000000000000000000000000000000000000..20e6c78e9edb7aa44b2708e2ccee041bef24ad67
--- /dev/null
+++ b/src/views/form/button.js
@@ -0,0 +1,17 @@
+import m from 'mithril';
+
+export default class Button {
+  static view(vnode) {
+    let { args } = vnode.attrs;
+    if (args === undefined) {
+      args = {};
+    }
+    if (args.type === undefined) {
+      args.type = 'button';
+    }
+    if (!vnode.attrs.active) {
+      args.disabled = 'disabled';
+    }
+    return m('button', args, vnode.attrs.title);
+  }
+}
diff --git a/src/views/form/inputGroup.js b/src/views/form/inputGroup.js
index b795b2ee263e38b6c74427c83671437a0fc34f67..1890b2b23067c222135d11b22748f78501034a4f 100644
--- a/src/views/form/inputGroup.js
+++ b/src/views/form/inputGroup.js
@@ -1,5 +1,4 @@
 import m from 'mithril';
-import { log } from '../../models/log';
 
 export default class InputGroup {
   constructor(vnode) {
@@ -37,18 +36,16 @@ export default class InputGroup {
         errorField,
       ]);
     }
-    args.list = `${args.id}-datalist`;
-    log(args);
-    if (args.getSuggestions) {
-      log('test');
+    args.list = `${vnode.attrs.name}-datalist`;
+    if (typeof args.getSuggestions === 'function') {
       args.oninput_original = args.oninput;
       args.oninput = (e) => {
-        log(`get suggestions for '${e.target.value}'`);
-        args.getSuggestions(e.target.value, (result) => {
-          log('callback called!');
-          this.suggestions = result;
-        });
-        if (args.oninput_original) {
+        if (typeof args.getSuggestions === 'function') {
+          args.getSuggestions(e.target.value, (result) => {
+            this.suggestions = result;
+          });
+        }
+        if (typeof args.oninput_original === 'function') {
           args.oninput_original(e);
         }
       };
diff --git a/src/views/form/jsonSchemaForm.js b/src/views/form/jsonSchemaForm.js
index d8a731cdabc7315cbed45110b08028895d867307..8dc30b9317b2e1955050e5810c31a8ec9aa52c94 100644
--- a/src/views/form/jsonSchemaForm.js
+++ b/src/views/form/jsonSchemaForm.js
@@ -5,7 +5,6 @@ import { isNullOrUndefined } from 'util';
 import { log } from '../../models/log';
 import inputGroup from './inputGroup';
 import selectGroup from './selectGroup';
-import mediaGroup from './mediaGroup';
 
 export default class JSONSchemaForm {
   constructor() {
@@ -111,14 +110,6 @@ export default class JSONSchemaForm {
           multipleSelect: true,
         },
       }));
-    } else if (item.type === 'media') {
-      return m(mediaGroup, this.bind({
-        name: key,
-        title: item.description,
-        args: {
-          multiple: 'multiple',
-        },
-      }));
     }
     log('Unknown array property type');
     return m('');
diff --git a/src/views/form/mediaGroup.js b/src/views/form/mediaGroup.js
deleted file mode 100644
index f6cced54744f09681dd515fe3ebb6d088a823068..0000000000000000000000000000000000000000
--- a/src/views/form/mediaGroup.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import m from 'mithril';
-
-export default class MediaGroup {
-  constructor(vnode) {
-    this.suggestions = [];
-    // 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;
-    args.oninput = vnode.attrs.oninput;
-    args.type = 'file';
-
-    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,
-    ]);
-  }
-}
diff --git a/src/views/form/selectGroup.js b/src/views/form/selectGroup.js
index a834826b266b08130ca66bc27c833dab360d3d8a..e28bf2596880f88dd1236ecf58a4a9e7d689b706 100644
--- a/src/views/form/selectGroup.js
+++ b/src/views/form/selectGroup.js
@@ -8,16 +8,24 @@ export default class SelectGroup {
   }
 
   view(vnode) {
-    switch (vnode.attrs.args.type) {
+    let { args } = vnode.attrs;
+    if (args === undefined) {
+      args = {};
+    }
+    args.value = vnode.attrs.value;
+    args.onchange = vnode.attrs.onchange;
+    args.oninput = vnode.attrs.oninput;
+
+    switch (vnode.attrs.type) {
       case 'buttons': {
-        if (vnode.attrs.args.multipleSelect) {
+        if (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('div', vnode.attrs.options.map(option =>
               m(inputGroup, {
                 name: vnode.attrs.name,
-                title: option,
-                value: option,
+                title: option.text,
+                value: option.value,
                 onchange: (e) => {
                   if (e.target.checked) {
                     this.value.push(e.target.value);
@@ -51,12 +59,13 @@ export default class SelectGroup {
       }
       case 'select':
       default: {
-        if (vnode.attrs.args.multipleSelect) {
+        if (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}]`,
               {
+                args,
                 onchange: (e) => {
                   const value = [];
                   let opt;
@@ -79,9 +88,8 @@ export default class SelectGroup {
                   }
                   vnode.attrs.oninput({ target: { name: e.target.name, value } });
                 },
-                multiple: true,
               },
-              vnode.attrs.options.map(option => m('option', option)),
+              vnode.attrs.options.map(option => m('option', { value: option.value }, option.text)),
             ),
           ]);
         }
@@ -89,13 +97,8 @@ export default class SelectGroup {
           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,
-              oninput: vnode.attrs.oninput,
-              multiple: false,
-            },
-            vnode.attrs.args.options.map(option => m('option', option)),
+            args,
+            vnode.attrs.options.map(option => m('option', { value: option.value }, option.text)),
           ),
         ]);
       }
diff --git a/src/views/form/submitButton.js b/src/views/form/submitButton.js
deleted file mode 100644
index d6cd4062be6fb1ead93520e1c5ca4486ff62b066..0000000000000000000000000000000000000000
--- a/src/views/form/submitButton.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import m from 'mithril';
-
-export default 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);
-  }
-}
diff --git a/src/views/studydocNew.js b/src/views/studydocNew.js
index ab41b23f228d11780d496543c69d116015dd8364..14bad3c41d4713e10d9312b0ff977d4adf7c9e0e 100644
--- a/src/views/studydocNew.js
+++ b/src/views/studydocNew.js
@@ -1,116 +1,148 @@
 import m from 'mithril';
-// import Ajv from 'ajv';
 import * as studydocs from '../models/studydocs';
-// import { apiUrl } from '../models/config';
 import { isLoggedIn } from '../models/auth';
-import { log } from '../models/log';
 import { Error401 } from './errors';
-
-
-// TODO: add validate
+import inputGroup from './form/inputGroup';
+import selectGroup from './form/selectGroup';
+import button from './form/button';
 
 export default class studydocNew {
   oninit() {
-    // this.ajv = new Ajv({
-    //   missingRefs: 'ignore',
-    //   errorDataPath: 'property',
-    //   allErrors: true,
-    // });
-    // // load schema
-    // m.request(`${apiUrl}/docs/api-docs`).then((schema) => {
-    //   const objectSchema = schema.definitions.Studydocument;
-    //   this.ajv.addSchema(objectSchema, 'schema');
-    //   this.validate = this.ajv.getSchema('schema');
-    // });
+    this.doc = { type: 'exams' };
+    this.isValid = false;
+  }
 
-    this.doc = { semester: '1', department: 'itet', type: 'exams' };
+  static _getInputSuggestions(field, input, callback) {
+    if (input.length > 2) {
+      studydocs.getInputSuggestions(field, input).then((result) => {
+        const suggestions = new Set();
+        result._items.forEach((item) => {
+          suggestions.add(item[field]);
+        });
+        callback(Array.from(suggestions));
+      });
+    }
+  }
+
+  submit() {
+    if (this.isValid) {
+      studydocs.addNew(this.doc);
+      m.route.set('/studydocuments');
+    }
   }
 
   view() {
     if (!isLoggedIn()) return m(Error401);
 
-    return m('div', [
-      m('form', {
-        onsubmit: (e) => {
-          e.preventDefault();
-          log(this.doc);
-          studydocs.addNew(this.doc);
-          m.route.set('/studydocuments');
+    return m('form', [
+      m(inputGroup, {
+        name: 'title',
+        title: 'Title',
+        oninput: (e) => {
+          this.doc.title = e.target.value;
+        },
+        getSuggestions: (input, callback) =>
+          studydocNew._getInputSuggestions('title', input, callback),
+      }),
+      m(inputGroup, {
+        name: 'professor',
+        title: 'Professor',
+        oninput: (e) => {
+          this.doc.professor = e.target.value;
+        },
+        getSuggestions: (input, callback) =>
+          studydocNew._getInputSuggestions('professor', input, callback),
+      }),
+      m(inputGroup, {
+        name: 'author',
+        title: 'Author',
+        oninput: (e) => {
+          this.doc.author = e.target.value;
+        },
+        getSuggestions: (input, callback) =>
+          studydocNew._getInputSuggestions('author', input, callback),
+      }),
+      m(selectGroup, {
+        name: 'semester',
+        title: 'Semester',
+        type: 'select',
+        onchange: (e) => {
+          this.doc.semester = e.target.value;
+        },
+        options: [
+          { value: '1', text: '1' },
+          { value: '2', text: '2' },
+          { value: '3', text: '3' },
+          { value: '4', text: '4' },
+          { value: '5+', text: '5+' },
+        ],
+      }),
+      m(selectGroup, {
+        name: 'department',
+        title: 'Department',
+        type: 'select',
+        onchange: (e) => {
+          this.doc.department = e.target.value;
+        },
+        options: [
+          { value: 'itet', text: 'itet' },
+          { value: 'mavt', text: 'mavt' },
+        ],
+      }),
+      m(inputGroup, {
+        name: 'lecture',
+        title: 'Lecture',
+        oninput: (e) => {
+          this.doc.lecture = e.target.value;
+        },
+        getSuggestions: (input, callback) =>
+          studydocNew._getInputSuggestions('lecture', input, callback),
+      }),
+      m(inputGroup, {
+        name: 'course_year',
+        title: 'Course Year',
+        type: 'number',
+        args: {
+          placeholder: (new Date()).getFullYear(),
+        },
+        oninput: (e) => {
+          this.doc.course_year = e.target.value;
+        },
+      }),
+      m(selectGroup, {
+        name: 'type',
+        title: 'Type',
+        type: 'select',
+        onchange: (e) => {
+          this.doc.type = e.target.value;
+        },
+        options: [
+          { value: 'exams', text: 'Exam' },
+          { value: 'cheat sheets', text: 'Cheat sheet' },
+          { value: 'lecture documents', text: 'Lecture Document' },
+          { value: 'exercises', text: 'Exercise' },
+        ],
+      }),
+      m(inputGroup, {
+        name: 'files',
+        title: 'Files',
+        args: {
+          type: 'file',
+          multiple: 1,
         },
-      }, [
-        m('input[name=title]', {
-          placeholder: 'title',
-          onchange: (e) => {
-            this.doc.title = e.target.value;
-            // this.validate(this.doc);
-          },
-        }),
-        m('input[name=professor]', {
-          placeholder: 'professor',
-          onchange: (e) => {
-            this.doc.professor = e.target.value;
-            // this.validate(this.doc);
-          },
-        }),
-        m('input[name=author]', {
-          placeholder: 'author',
-          onchange: (e) => {
-            this.doc.author = e.target.value;
-            // this.validate(this.doc);
-          },
-        }),
-        m('select[name=semester]', {
-          onchange: (e) => {
-            this.doc.semester = e.target.value;
-            // this.validate(this.doc);
-          },
-        }, [
-          m('option', { value: '1' }, '1'),
-          m('option', { value: '2' }, '2'),
-          m('option', { value: '3' }, '3'),
-          m('option', { value: '4' }, '4'),
-          m('option', { value: '5+' }, '5+'),
-        ]),
-        m('select[name=department]', {
-          onchange: (e) => {
-            this.doc.department = e.target.value;
-            // this.validate(this.doc);
-          },
-        }, [
-          m('option', { value: 'itet' }, 'itet'),
-          m('option', { value: 'mavt' }, 'mavt'),
-        ]),
-        m('input[name=lecture]', {
-          placeholder: 'lecture',
-          onchange: (e) => {
-            this.doc.lecture = e.target.value;
-            // this.validate(this.doc);
-          },
-        }),
-        m('input[name=course_year]', {
-          type: 'number',
-          placeholder: 2018,
-          onchange: (e) => {
-            this.doc.course_year = e.target.value;
-            // this.validate(this.doc);
-          },
-        }),
-        m('select[name=type]', {
-          onchange: (e) => {
-            this.doc.type = e.target.value;
-            // this.validate(this.doc);
-          },
-        }, [
-          m('option', { value: 'exams' }, 'exams'),
-          m('option', { value: 'cheat sheets' }, 'cheat sheets'),
-          m('option', { value: 'lecture documents' }, 'lecture documents'),
-          m('option', { value: 'exercises' }, 'exercises'),
-        ]),
-        m('button.button[type=submit]', 'Submit'),
-      ]),
-      m('input[type=file][multiple]', {
         onchange: (e) => {
           this.doc.files = e.target.files;
+          if (this.doc.files.length > 0) {
+            this.isValid = true;
+          }
+        },
+      }),
+      m(button, {
+        name: 'submit',
+        title: 'Submit',
+        active: this.isValid,
+        args: {
+          onclick: () => this.submit(),
         },
       }),
     ]);