BerniWittmann/cape-frontend

View on GitHub
src/components/context-factor/AttributeRules.vue

Summary

Maintainability
Test Coverage
<template>
  <div class="attr-rules-margin">
    <div v-if="attrExists">
      <el-row :gutter="20">
        <el-form ref='newStateForm' :model="newState" label-position="top" :rules="rules" status-icon inline
                 @submit.native.prevent>
          <el-form-item prop="stateName">
            <el-input class="input" ref="stateInput" size="small"
                      v-model="newState.stateName"
                      :placeholder="$t('context_factor.edit.states.name')"
                      @change="checkStateName"
            ></el-input>
          </el-form-item>
          <el-form-item>
            <el-button class="b-margin" icon="el-icon-plus" @click="addNewState" size="small">
              {{ $t('context_factor.edit.states.add') }}
            </el-button>
          </el-form-item>
        </el-form>
      </el-row>
      <el-row :gutter="20">
        <el-table :data="tableData" border>
          <el-table-column
                  v-for="(ca, index) in contextFactor.attributes" :prop="ca.id"
                  align="center" :key="index" :resizable="false"
                  :min-width="200">
            <template slot="header" slot-scope="scope">
              <span v-if="show">{{ ca.key }}</span>
            </template>
            <template slot-scope="scope">
              <RulesCell v-if="show" :value="scope.row[ca.id]" :type="ca.type"
                         @change="(nv) => {scope.row[ca.id] = nv}"></RulesCell>
            </template>
          </el-table-column>

          <el-table-column
                  :label="$t('context_factor.edit.state')"
                  sortable
                  :sort-method=nameCompare
                  fixed="right"
                  align="center"
                  :min-width="150"
                  :resizable="false"
                  class-name="state-column">
            <template slot-scope="scope">
              <div class="parent">
                <input-edit class="state-input" :value="scope.row.state" @change="(nst) => {scope.row.state = nst}"
                            :rules="rules.stateName"
                            size="mini"></input-edit>
                <span class="btn">
              <el-button size="mini" icon="el-icon-delete" type="danger" class="delete-btn"
                         @click="deleteState(scope.$index)"></el-button>
            </span></div>
            </template>
          </el-table-column>
        </el-table>
      </el-row>
    </div>
    <div v-else>
      <i class="el-icon-warning"></i>
      <span>{{ $t('context_factor.edit.states.no_attributes') }}</span>
    </div>
  </div>
</template>

<script>
/* ============
 * AttributeRules
 * ============
 *
 *  The component to allow to view and edit the Attributes and Rules for the States of a Context Factor
 */

import RulesCell from '@/components/context-factor/RulesCell'
import InputEdit from '@/components/InputEdit.vue'

/*
 * @vuese
 * @group Components
 *
 * The component to allow to view and edit the Attributes and Rules for the States of a Context Factor
 */

export default {
  name: 'AttributeRules',
  components: {
    RulesCell,
    InputEdit
  },

  props: {
    // gets the Context Factor
    contextFactor: {
      type: Object,
      required: true
    }
  },

  data() {
    return {
      // Data in Backend
      newState: {
        stateName: undefined
      },
      rules: {
        stateName: [
          { required: true, message: this.$t('context_factor.edit.validation.stateName.required'), trigger: 'blur' }
        ]
      },
      // Data for Table used in Frontend, after Parsing
      tableData: [],
      show: true,
      separator: '_'
    }
  },

  computed: {
    attrExists() {
      return this.contextFactor.attributes.length > 0
    }
  },

  methods: {
    addNewState() {
      this.$refs.newStateForm.validate((valid) => {
        if (valid) {
          let newRow = { state: this.newState.stateName }
          for (let ca of this.contextFactor.attributes) {
            newRow[ca.id] = undefined
          }
          this.tableData.push(newRow)
          this.newState.stateName = ''
        }
      })
    },

    // @vuese
    // Converts the context rules to a representation that can be used in the table
    convertToTableData() {
      // on tab click
      if (this.contextFactor.contextRules !== undefined) {
        let tableD = []
        for (let i = 0; i < this.contextFactor.contextRules.length; i++) {
          let row = { state: this.contextFactor.contextRules[i].state }
          for (let cr of this.contextFactor.contextRules[i].rule.split('&&')) {
            // possible separators are: == or <= or >=
            if (cr.indexOf('==') >= 0) {
              let ct = cr.split('==')[0]
              row[ct] = cr.split('==')[1]
            } else {
              if (cr.indexOf('<=') >= 0) {
                let ct = cr.split('<=')[0]
                row[ct] = (row[ct] !== undefined) ? row[ct] + cr.split('<=')[1] : this.separator + cr.split('<=')[1]
              }
              if (cr.indexOf('>=') >= 0) {
                let ct = cr.split('>=')[0]
                row[ct] = (row[ct] !== undefined) ? cr.split('>=')[1] + row[ct] : cr.split('>=')[1] + this.separator
              }
            }
          }
          tableD.push(row)
        }
        this.tableData = tableD
      }
    },

    nameCompare(a, b) {
      if (!a || !a.state || !b || !b.state) return 0
      const lca = a.state.toLowerCase()
      const lcb = b.state.toLowerCase()
      return lca > lcb ? 1 : (lca < lcb ? -1 : 0)
    },

    // @vuese
    // Converts the context rules table representation to a string that can be stored
    convertFromTableData() {
      // on save and tab click
      let cRules = []
      for (let cs of this.tableData) {
        let cr = {
          state: cs.state,
          rule: ''
        }
        for (let ca of this.contextFactor.attributes) {
          if (cs[ca.id] !== undefined && cs[ca.id].length > 0) {
            if (cr.rule.length > 0) {
              cr.rule += '&&'
            }
            switch (ca.type) {
              case 'Boolean':
                cr.rule += ca.id + '==' + cs[ca.id]
                break
              case 'String':
                cr.rule += ca.id + '==' + cs[ca.id]
                break
              default:
                let sPos = cs[ca.id].indexOf(this.separator)
                if (sPos < 0) {
                  if (cs[ca.id] + '' !== 'undefined') {
                    cr.rule += ca.id + '==' + cs[ca.id]
                  }
                } else {
                  let ct = cs[ca.id].split(this.separator)
                  if (ct[0] === ct[1]) {
                    if (ct[0] + '' !== 'undefined') {
                      cr.rule += ca.id + '==' + ct[0]
                    }
                  } else {
                    let k = 0
                    for (let i = 0; i < ct.length; i++) {
                      if (ct[i].length > 0) {
                        if (k > 0) {
                          cr.rule += '&&'
                        }
                        if (cs[ca.id].indexOf(ct[i]) < sPos) {
                          // >=
                          if (ct[i] + '' !== 'undefined') {
                            cr.rule += ca.id + '>=' + ct[i]
                          }
                          k++
                        } else {
                          // <=
                          if (ct[i] + '' !== 'undefined') {
                            cr.rule += ca.id + '<=' + ct[i]
                          }
                          k++
                        }
                      }
                    }
                  }
                }
            }
          }
        }
        cRules.push(cr)
      }
      this.contextFactor.contextRules = cRules
    },

    // @vuese
    // Deletes a State after asking for confirmation
    // @arg the index of the state to be deleted
    deleteState(stateIndex) {
      this.$confirm(this.$t('context_factor.edit.states.delete.message'), this.$t('context_factor.edit.states.delete.warning'), {
        confirmButtonText: this.$t('context_factor.edit.states.delete.ok'),
        cancelButtonText: this.$t('context_factor.edit.states.delete.cancel'),
        type: 'warning',
        cancelButtonClass: 'is-plain el-button--info',
        confirmButtonClass: 'el-button--danger'
      }).then(() => {
        this.tableData.splice(stateIndex, 1)
        this.convertFromTableData()
        this.$message({
          type: 'success',
          message: this.$t('context_factor.edit.states.delete.confirmation')
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: this.$t('context_factor.edit.states.delete.cancellation')
        })
      })
    },

    // @vuese
    // Triggers a new render of the component
    reRender() {
      // needed to ensure that data shown is updated
      this.show = false
      this.$nextTick(() => {
        this.show = true
        this.$nextTick(() => {
        })
      })
    },

    // @vuese
    // =, |, . and & are not allowed to be part of the name
    checkStateName() {
      this.newState.stateName = this.newState.stateName.replace(/=/g, '')
      this.newState.stateName = this.newState.stateName.replace(/&/g, '')
      this.newState.stateName = this.newState.stateName.replace(/\|/g, '')
      this.newState.stateName = this.newState.stateName.replace(/\./g, '')
    }
  },

  beforeMount() {
    this.convertToTableData()
  }
}
</script>

<style scoped lang="scss">

.attr-rules-margin {
  margin-left: 10px;
  margin-right: 10px;
}

.state-input {
  width: 100%;
}

.parent {
  display: flex;
}

.btn {
  display: inline-block;
  text-align: right;
  align-self: center;
}

.delete-btn {
  padding: 4px;
}

</style>
<style>
.el-table .state-column {
  background-color: #f5f6fa;
}
</style>