davestewart
11/13/2017 - 4:41 PM

Trifecta of Parent, Child and Layout components for reusable Form family

Trifecta of Parent, Child and Layout components for reusable Form family

<template>

  <ui-form name="address">

    <div class="fields">

      <ui-input
        name="house_no"
        label="House name / no"
        :value.sync="values.house_no"
        :rules="rules.house_no"
        classes="four wide"
      ></ui-input>

      <ui-input
        name="street"
        label="Street"
        :value.sync="values.street"
        :rules="rules.street"
        classes="twelve wide"
      ></ui-input>

    </div>

    <div class="two fields">
      <ui-input
        name="town"
        label="Town / City"
        :value.sync="values.town"
        :rules="rules.town"
        classes="six wide"
      ></ui-input>

      <ui-input
        name="district"
        label="District"
        :value.sync="values.district"
        :rules="rules.district"
        classes="six wide"
      ></ui-input>

    </div>

    <div class="three fields">
      <ui-dropdown
        name="county"
        label="County"
        searchable
        :rules="rules.county"
        :value.sync="values.county"
        classes="six wide"
      >
        <uk-counties/>
      </ui-dropdown>

      <ui-input
        name="postcode"
        label="Postcode"
        :value.sync="values.postcode"
        :rules="rules.postcode"
        classes="six wide"
      ></ui-input>

    </div>

    <div class="four fields">
      <ui-dropdown
        name="time_at_address"
        label="Time at address"
        :value.sync="values.time_at_address"
        :options="options.time"
      ></ui-dropdown>

    </div>

    <ui-populator/>

  </ui-form>

</template>

<script>

  import Form from 'ui/base/Form'
  import UkCounties from 'ui/partials/UkCounties.vue'

  import fields from 'data/fields'
  import rules from 'data/validation'
  import data from 'data/forms'

  export default {

    components: {
      UkCounties,
    },

    extends: Form,

    props: {
      name: {
        type: String,
        default: null,
      },
    },

    data: function () {
      return {
        rules: rules.address,
        options: {
          time: fields.get('address.time_at_address'),
          county: fields.get('address.time_at_address'),
        },
        values: this.$store.initial('account/address'),
        dummy: data.address
      }
    },

  }

</script>

<style>


</style>
/**
 * Base element or mixin for form components
 */
export default {

  data () {
    return {
      fields: [],
    }
  },

  computed: {
    form () {
      return this.$el.tagName.toLowerCase() === 'form'
        ? this.$el
        : this.$el.querySelector('form')
    },

    errors () {
      return this.fields
        .map(child => child.error)
        .filter(error => !!error)
    },

    validated () {
      return this.errors.length === 0
    }
  },

  mounted () {
    this.form.addEventListener('submit', this.onSubmit)
  },

  beforeDestroy () {
    this.form.removeEventListener('submit', this.onSubmit)
  },

  methods: {
    registerField (node) {
      if (this.fields.indexOf(node) === -1) {
        this.fields.push(node)
      }
    },

    unregisterField (node) {
      this.fields.splice(this.fields.indexOf(node), 1)
    },

    validate () {
      const errors = this.fields
        .map(field => field.validate())
        .filter(error => !!error)
      return errors.length === 0
        ? Promise.resolve(this.values)
        : Promise.reject(errors)
    },

    populate (data = 'dummy') {
      data = typeof data === 'string'
        ? this[data]
        : data
      if (typeof this.values === 'object' && data) {
        Object.assign(this.values, data)
      }
      else {
        console.warn('[Form] Unable to populate values: invalid data / key supplied')
      }
    },

    clear () {
      Object.keys(this.values)
        .map(key => {
          this.values[key] = null
        })
      this.reset()
    },

    reset () {
      // seems to be a delay to reset, so call on next tick
      this.$nextTick(() => {
        this.fields.forEach(child => child.reset())
      })
    },

    onSubmit () {
      this.$emit('submit')
    }

  }
}
<template>

  <form data-layout="form"
        :data-name="name"
        class="ui-form ui form"
        @submit.prevent="submit">

    <div class="ui segment">
      <slot>
        <!-- content goes here -->
      </slot>

      <div data-name="buttons" class="ui unit">
        <slot name="buttons">
          <!-- buttons go here -->
        </slot>
        <button v-show="false">Submit</button>
      </div>
    </div>

  </form>

</template>

<script>

  export default {

    props: {
      name: {
        type: String,
        required: true,
      },
      submit: {
        type: Function,
        default: function () {
          this.$emit('submit')
        }
      }
    },

  }

</script>