Home Reference Source Repository

js/components/form/base-form.js

import config from 'config';
import API from 'api';
import {_} from 'i18n';
import {setattr, isObject} from 'utils';
import log from 'logger';
import moment from 'moment';
import $ from 'jquery'
import 'jquery-validation-dist';

// jQuery validate
$.extend($.validator.messages, {
    required: _('valid-required'),
    remote: _('valid-remote'),
    email: _('valid-email'),
    url: _('valid-url'),
    date: _('valid-date'),
    dateISO: _('valid-date-iso'),
    number: _('valid-number'),
    digits: _('valid-digits'),
    creditcard: _('valid-creditcard'),
    equalTo: _('valid-equal-to'),
    maxlength: $.validator.format(_('valid-maxlength')),
    minlength: $.validator.format(_('valid-minlength')),
    rangelength: $.validator.format(_('valid-range-length')),
    range: $.validator.format(_('valid-range')),
    max: $.validator.format(_('valid-max')),
    min: $.validator.format(_('valid-min'))
});


/**
 *  Rule for depend dates, should be greater that param.
 */
$.validator.addMethod('dateGreaterThan', function(value, element, param) {
    var start = moment($(param).val());
    return this.optional(element) || moment(value).isAfter(start);
}, $.validator.format(_('Date should be after start date')));


function empty_schema() {
    return {properties: {}, required: []};
}


export default {
    name: 'base-form',
    replace: true,
    props: {
        fields: Array,
        model: Object,
        defs: Object,
        readonly: {
            type: Boolean,
            default: false
        },
        fill: Boolean
    },
    computed: {
        schema: function() {
            if (!this.fields || !(this.model || this.defs)) {
                return empty_schema();
            }
            var s = empty_schema(),
                schema = this.defs || this.model.__schema__ || empty_schema();

            this.fields.forEach((field) => {
                if (schema.hasOwnProperty('properties') && schema.properties.hasOwnProperty(field.id)) {
                    s.properties[field.id] = schema.properties[field.id];
                    if (schema.required && schema.required.indexOf(field.id) >= 0) {
                        s.required.push(field.id);
                    }
                    return;
                }

                let properties = field.id.split('.'),
                    currentSchema = schema,
                    required = true,
                    path = '',
                    prop;

                for (prop of properties) {
                    path += path === '' ? prop : ('.' + prop);

                    // Handle root level $ref
                    if (currentSchema.hasOwnProperty('$ref')) {
                        currentSchema = API.resolve(currentSchema.$ref);
                    }

                    if (!currentSchema.properties || !currentSchema.properties.hasOwnProperty(prop)) {
                        log.warn('Property "'+ prop +'" not found in schema');
                        return;
                    }

                    required = (
                        required
                        && currentSchema.hasOwnProperty('required')
                        && currentSchema.required.indexOf(prop) >= 0
                    );

                    if (required && s.required.indexOf(path) < 0) {
                        s.required.push(path);
                    }

                    // Handle property level $ref
                    if (currentSchema.properties[prop].hasOwnProperty('$ref')) {
                        currentSchema = API.resolve(currentSchema.properties[prop].$ref);
                    }
                }

                s.properties[field.id] = currentSchema.properties[prop];
                if (required && !field.id in s.required) {
                    s.required.push(field.id);
                }
            });

            return s;
        },
        $form: function() {
            return this.$el.tagName.toLowerCase() === 'form'
                ? this.$el
                : this.$el.querySelector('form');
        }
    },
    attached: function() {
        $(this.$form).validate({
            ignore: '',
            errorClass: "help-block",
            highlight: function(element) {
                $(element).closest('.form-group').removeClass('has-success').addClass('has-error');
            },
            unhighlight: function(element) {
                $(element).closest('.form-group').removeClass('has-error');
            },
            success: function(label) {
                label.closest('.form-group').addClass('has-success');
                if (!label.text()) {
                    label.remove();
                }
            },
            errorPlacement: function(error, element) {
                $(element).closest('.form-group,.field-wrapper').append(error);
            }
        });
    },
    detached: function() {
        $(this.$form).data('validator', null);
    },
    methods: {
        /**
         * Trigger a form validation
         */
        validate: function() {
            return $(this.$form).valid();
        },
        /**
         * Serialize Form into an Object
         *
         * @return {Object}
         */
        serialize: function() {
            let elements = this.$form.querySelectorAll('input,textarea,select'),
                out = {};

            Array.prototype.map.call(elements, function(el) {
                let value;
                if (el.tagName.toLowerCase() === 'select') {
                    value = el.value || undefined;
                } else if (el.type === 'checkbox') {
                    value = el.checked;
                } else {
                    value = el.value;
                }
                return {name: el.name, value: value};
            }).forEach(function(item) {
                setattr(out, item.name, item.value);
            });

            // Filter out empty optionnal objects
            // TODO: handle recursion
            for (let prop in out) {
                if (isObject(out[prop]) && this.schema.required.indexOf(prop) < 0) {
                    let falsy = true;
                    for (let attr in out[prop]) {
                        falsy &= !out[prop][attr];
                    }
                    if (falsy) {
                        delete out[prop];
                    }
                }
            }

            return out;
        }
    }
};