Skip to content
Snippets Groups Projects
editView.js 6.09 KiB
import Ajv from 'ajv';
import { Checkbox, IconButton, Toolbar, ToolbarTitle, Button } from 'polythene-mithril';
import { apiUrl } from 'networkConfig';
import ItemView from './itemView';
import { textInput, datetimeInput, numInput, icons } from './elements';

const m = require('mithril');

// Mapper for resource vs schema-object names
const objectNameForResource = {
  users: 'User',
  groupmembershipds: 'Groupmembership',
  groups: 'Group',
  eventsignups: 'Eventsignup',
  events: 'Event',
};

export class EditLayout {
  view({ attrs: { title, onSubmit = () => {}, onCancel = () => {} }, children }) {
    return m('div', { style: { 'background-color': 'white' } }, [
      m(Toolbar, [
        m(IconButton, {
          icon: { svg: { content: m.trust(icons.clear) } },
          events: { onclick: onCancel },
        }),
        m(ToolbarTitle, title),
        m(Button, { label: 'submit', events: { onclick: onSubmit } }),
      ]),
      children,
    ]);
  }
}

export class EditView extends ItemView {
  /* Extension of ItemView to edit a data item
   *
   * Requires:
   * - call constructor with vnode, resource, (valid, true by default)
   * - vnode.attrs.onfinish has to be a callback function that is called after
   *   the edit is finished
   *
   * Provides Methods:
   * - bind(attrs): binds a form-field against this.data
   * - submit
   */
  constructor(vnode, resource, embedded, valid = true) {
    super(resource, embedded);
    this.changed = false;
    this.resource = resource;

    // state for validation
    this.valid = valid;
    this.ajv = new Ajv({
      missingRefs: 'ignore',
      errorDataPath: 'property',
      allErrors: true,
    });
    this.errors = {};
    this.data = {};

    // callback when edit is finished
    if (vnode.attrs.onfinish) this.callback = vnode.attrs.onfinish;
    else {
      this.callback = (item) => {
        console.log(item);
        if (item) m.route.set(`/${resource}/${item._id}`);
        else m.route.set(`/${resource}`);
      };
    }
  }

  oninit() {
    // if this.id is set, this is an edit view of an existing event.
    // Therefore, we load the current state of the event from the API.
    if (this.id) {
      this.handler.getItem(this.id, this.embedded).then((item) => {
        this.data = item;
        m.redraw();
      });
    }
    // load schema
    m.request(`${apiUrl}/docs/api-docs`).then((schema) => {
      const objectSchema = schema.definitions[
        objectNameForResource[this.resource]];
      // console.log(objectSchema);
      // filter out any field that is not understood by the validator tool
      Object.keys(objectSchema.properties).forEach((property) => {
        if (objectSchema.properties[property].type === 'media' ||
            objectSchema.properties[property].type === 'json_schema_object') {
          objectSchema.properties[property].type = 'object';
        }
        if (objectSchema.properties[property].format === 'objectid') {
          delete objectSchema.properties[property];
        }
      });
      // delete objectSchema.properties['_id'];
      console.log(this.ajv.addSchema(objectSchema, 'schema'));
    }).catch((error) => { console.log(error); });
  }

  // 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: (name, value) => {
        this.changed = true;
        // bind changed data
        this.data[name] = value;

        console.log(this.data);

        // validate against schema
        const validate = this.ajv.getSchema('schema');
        this.valid = validate(this.data);

        console.log(validate.errors);
        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;
  }
  renderPage(page) {
    return Object.keys(page).map((key) => {
      const field = page[key];
      if (field.type === 'text') {
        field.name = key;
        field.floatingLabel = true;
        delete field.type;
        return m(textInput, this.bind(field));
      } else if (field.type === 'number') {
        field.name = key;
        field.floatingLabel = true;
        delete field.type;
        return m(numInput, this.bind(field));
      } else if (field.type === 'checkbox') {
        field.checked = this.data[key] || false;
        field.onChange = (state) => {
          this.data[key] = state.checked;
        };
        delete field.type;
        return m(Checkbox, field);
      } else if (field.type === 'datetime') {
        field.name = key;
        delete field.type;
        return m(datetimeInput, this.bind(field));
      }
      return `key '${key}' not found`;
    });
  }

  submit(formData = false) {
    if (Object.keys(this.data).length > 0) {
      let request;
      if (this.id) {
        // if id is known, this is a patch to an existing item
        request = this.handler.patch(this.data, formData);
      } else {
        request = this.handler.post(this.data);
      }
      request.then((response) => {
        this.callback(response);
      }).catch((error) => {
        console.log(error);
        // Process the API error
        const { response } = error;
        if (response.status === 422) {
          // there are problems with some fields, display them
          Object.keys(response.data._issues).forEach((field) => {
            this.errors[field] = [response.data._issues[field]];
          });
          m.redraw();
        } else {
          console.log(error);
        }
      });
    } else {
      this.callback();
    }
  }
}