Home Reference Source

src/components/selectboxes/SelectBoxes.js

import _ from 'lodash';
import RadioComponent from '../radio/Radio';

export default class SelectBoxesComponent extends RadioComponent {
  static schema(...extend) {
    return RadioComponent.schema({
      type: 'selectboxes',
      label: 'Select Boxes',
      key: 'selectBoxes',
      inline: false
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Select Boxes',
      group: 'basic',
      icon: 'plus-square',
      weight: 60,
      documentation: '/userguide/#selectboxes',
      schema: SelectBoxesComponent.schema()
    };
  }

  constructor(...args) {
    super(...args);
    this.validators = this.validators.concat('minSelectedCount', 'maxSelectedCount');
  }

  init() {
    super.init();
    this.component.inputType = 'checkbox';
  }

  get defaultSchema() {
    return SelectBoxesComponent.schema();
  }

  get inputInfo() {
    const info = super.elementInfo();
    info.attr.name += '[]';
    info.attr.type = 'checkbox';
    info.attr.class = 'form-check-input';
    return info;
  }

  get emptyValue() {
    return this.component.values.reduce((prev, value) => {
      if (value.value) {
        prev[value.value] = false;
      }
      return prev;
    }, {});
  }

  get defaultValue() {
    let defaultValue = this.emptyValue;

    if (!_.isEmpty(this.component.defaultValue)) {
      defaultValue = this.component.defaultValue;
    }
    if (this.component.customDefaultValue && !this.options.preview) {
      defaultValue = this.evaluate(
        this.component.customDefaultValue,
        { value: '' },
        'value'
      );
    }

    return defaultValue;
  }

  /**
   * Only empty if the values are all false.
   *
   * @param value
   * @return {boolean}
   */
  isEmpty(value = this.dataValue) {
    let empty = true;
    for (const key in value) {
      if (value.hasOwnProperty(key) && value[key]) {
        empty = false;
        break;
      }
    }

    return empty;
  }

  getValue() {
    if (this.viewOnly || !this.refs.input || !this.refs.input.length) {
      return this.dataValue;
    }
    const value = {};
    _.each(this.refs.input, (input) => {
      value[input.value] = !!input.checked;
    });
    return value;
  }

  /**
   * Normalize values coming into updateValue.
   *
   * @param value
   * @return {*}
   */
  normalizeValue(value) {
    value = value || {};
    if (typeof value !== 'object') {
      if (typeof value === 'string') {
        value = {
          [value]: true
        };
      }
      else {
        value = {};
      }
    }
    if (Array.isArray(value)) {
      _.each(value, (val) => {
        value[val] = true;
      });
    }

    return value;
  }

  /**
   * Set the value of this component.
   *
   * @param value
   * @param flags
   */
  setValue(value, flags = {}) {
    const changed = this.updateValue(value, flags);
    value = this.dataValue;
    _.each(this.refs.input, (input) => {
      if (_.isUndefined(value[input.value])) {
        value[input.value] = false;
      }
      input.checked = !!value[input.value];
    });
    return changed;
  }

  getValueAsString(value) {
    if (!value) {
      return '';
    }
    return _(this.component.values || [])
      .filter((v) => value[v.value])
      .map('label')
      .join(', ');
  }

  checkComponentValidity(data, dirty, rowData, options) {
    const minCount = this.component.validate.minSelectedCount;
    const maxCount = this.component.validate.maxSelectedCount;

    if ((maxCount || minCount) && !this.isValid(data, dirty)) {
      const count = Object.keys(this.validationValue).reduce((total, key) => {
        if (this.validationValue[key]) {
          total++;
        }
        return total;
      }, 0);

      if (maxCount && count >= maxCount) {
        if (this.refs.input) {
          this.refs.input.forEach(item => {
            if (!item.checked) {
              item.disabled = true;
            }
          });
        }
        if (maxCount && count > maxCount) {
          const message = this.t(
            this.component.maxSelectedCountMessage || 'You can only select up to {{maxCount}} items.',
            { maxCount }
          );
          this.setCustomValidity(message, dirty);
          return false;
        }
      }
      else if (minCount && count < minCount) {
        if (this.refs.input) {
          this.refs.input.forEach(item => {
            item.disabled = false;
          });
        }
        const message = this.t(
          this.component.minSelectedCountMessage || 'You must select at least {{minCount}} items.',
          { minCount }
        );
        this.setCustomValidity(message, dirty);
        return false;
      }
      else {
        if (this.refs.input) {
          this.refs.input.forEach(item => {
            item.disabled = false;
          });
        }
      }
    }

    return super.checkComponentValidity(data, dirty, rowData, options);
  }
}