# Fields

A Field provides information and methods to work with your data. An information can be the label which is used to display a name of the field for the user (e.g. as column header). A RenderableField inherits from Field and additionally provides a handling to render to field value to the user or render an input field.

# Field definition

When instantiating a new field you can provide a specific definition for the field.

class Album extends BaseModel {
  [...]

  static fieldsDef = {
    id: new Field({primaryKey: true}),
    title: new Field()
  }
}

Field definition structure:

{
  // String which key should be used to retrieve value from.
  // See Attribute name for more information
  // Optional: default uses key from fieldsDef 
  attributeName: 'title',

  // Label of field. See Field label for more information
  // Optional: Can either be a string, function or promise
  label: () => Promise.resolve('Title'),

  // Boolean flag whether field is a primary key
  // Optional: default is false
  primaryKey: true,

  // Optional field type specific options.
  // Can either be an object, a function or a promise.
  // See field Types
  options: {}
}

# Attribute name (attributeName)

By default the key of your field in your fieldsDef (e.g. first_name) will be used to retrieve the value from the model data. You can also set the attributeName when instantiating the field. It is also possible to access nested data when using dot-notation in attributeName. If you need a more specific way to retrieve the value of a field from your data then have a look at Custom/Computed fields.

class MyModel extends BaseModel {
  [...]

  static fieldsDef = {
    name: new Field({attributeName: 'username'}),
    address_city: new Field({attributeName: 'address.city'}),
    address_street: new Field({attributeName: 'address.street.name'})
  }
}

const myObj = new MyModel({
  username: 'joe_bloggs',
  address: {
    city: 'New York',
    street: {
      name: 'Fifth Avenue'
    } 
  }
})

await myObj.val.name // output: joe_bloggs
await myObj.val.address_city // output: New York
await myObj.val.address_street // output: Fifth Avenue

# Field label (label)

With the label property you can set a descriptive name for your field. The label can either be a string or a function which should return a string or a Promise. You can access your label with the label property of your field instance which will always return a Promise.

class MyModel extends BaseModel {
  [...]

  static fieldsDef = {
    first_name: new Field({
      label: 'First name'
    })
  }
}

[...]

const firstNameField = myObj.getField('first_name')

await firstNameField.label // output: First name

# Standalone Field

In some cases you just want to work with a single field and don't need a model. So it is possible to create a standalone field by directly passing an initial value.

// Pass initial value when creating a new instance
const myField = new Field({label: 'Description'}, {value: 'My description'})

await myField.value // output: My description

note

The value will be stored in a data object to keep the field reactive. Your can retrieve the data object with myField.data

You can also directly pass a data object instead of an initial value

const data = {description: 'My description'}
// Bind data when creating a new instance
const myField = new Field({attributeName: 'description'}, {data: data})

await myField.value // output: My description

You can also use the components to render your standalone field. Be sure to use a RenderableField.

<template>
  [...]
    <display-field :field="myField"/>
  [...]
</template>

# Custom/Computed fields

In case you want to define your own field class you just need to extend from Field. By overwriting the valueGetter method you are able to map the field value by yourself and create computed values.

class FullNameField extends Field {
  valueGetter (data) {
    return data ? `${data.first_name} ${data.last_name}` : null
  }
}

class MyModel extends BaseModel {
  [...]

  static fieldsDef = {
    full_name: new FullNameField()
  }
}

const myObj = new MyModel({
  first_name: 'Joe',
  last_name: 'Bloggs'
})

await myObj.val.full_name // output: Joe Bloggs

# Value parsing

To avoid unexpected data types of your field value, then you should use the setParseValue method of your field. This will ensure that your Field (e.g. IntegerField) always contains a number.

// Set value without parsing
myIntegerField.value = '5'
// -> Field value will be string with value '5'

// Parse and set value
myIntegerField.setParseValue('5')
// -> Field value will be number with value 5

You can customize your parsing logic by overwriting the valueParser.

class StringField extends Field {
  valueparser (rawValue) {
    return String(rawValue)
  }
}

# Field vs RenderableField

In case you don't need the rendering functions, then you can use the Field class and avoid unnecessary code which will make your bundle smaller. The RenderableField provides additional methods like displayRender to allow rendering of the field.

# Rendering

WARNING

Be sure to use a field which inherits from RenderableField. The default Field class does not support rendering.

By using the DisplayField component you can render the value of a field for displaying purpose and InputField when you want to render the input component for this field. To customize the different output which should be rendered you can either set a displayRender/inputRender or a custom displayComponent/inputComponent.

The displayRender/inputRender can be used for small changes of the output and is a simple render function (See Vue.js render function (opens new window)).

class RedTextField extends RenderableField {
  // Render a red text for display
  displayRender (h, resolvedValue) {
    return h('span', {
      style: {
        color: 'red'
      }
    }, resolvedValue)
  }

  // Render an text input field for input
  inputRender (h, { value, inputProps }) {
    // Common input properties
    const { disabled, readonly } = inputProps

    return h('input', {
      attrs: {
        type: 'text',
        // Set current value to input
        value,
        // Implement common input properties
        disabled,
        readonly
      },
      on: {
        input: (event) => {
          // Parse and set input value to field.value
          this.setParseValue(event.target.value)
        }
      }
    })
  }
}

TIP

In case you need to achieve more reactivity, then take a look at Integration of vue-async-computed.

# Resolving asynchronous values before rendering

In some cases you need to prepare or fetch other data before the field should be rendered. This can be done with the methods prepareDisplayRender/prepareInputRender. They will be called before displayRender/inputRender and will give any async data to as 2. argument to them.

class AlbumTitleField extends RenderableField {
  // Do any asynchronous operations before rendering with displayRender
  async prepareDisplayRender (renderProps) {
    const albumId = await this.value
    const album = await Album.objects.detail(albumId)
    return album.val.title
  }

  // Render the asynchronous fetched data
  // renderData will contain the value returned by prepareDisplayRender
  displayRender (h, renderData) {
    return h('span', renderData)
  }
}

TIP

You can optionally use the renderProps argument which can be used to pass additional properties from DisplayField component to prepareDisplayRender

# Custom field component

In case you need to do more specific rendering you can also set your own component which will be rendered when using DisplayField on your custom field. If you want to change the input component you can extend from BaseInputFieldRender and overwrite the inputComponent method of your field.

// CustomField.js
class CustomField extends RenderableField {
  get displayComponent () {
    return import('./CustomFieldComponent')
  }
}

// CustomFieldComponent.vue
<template>
  <span>{{ fieldValue }}</span>
</template>

<script>
  import {BaseDisplayFieldRender} from 'vue-service-model'

  export default {
    extends: BaseDisplayFieldRender,
    data: () => ({
      fieldValue: null    
    }),
    created () {
      this.field.value.then(value => (this.fieldValue = value))      
    }
  }
</script>

To keep it simple and reactive you can install the package vue-async-computed (opens new window). See Integration of vue-async-computed.

[...]

<script>
  import {BaseDisplayFieldRender} from 'vue-service-model'

  export default {
    extends: BaseDisplayFieldRender,
    asyncComputed: {
      fieldValue () {
        return this.field.value      
      }  
    }
  }
</script>