Field.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <template>
  2. <div class="control">
  3. <label for="name">{{ name }}</label>
  4. <button v-if="entries.length === 0" @click="addEntry()" type="button">+</button>
  5. <div class="control-row" v-for="(entry, entryIndex) in entries">
  6. <div class="control-col" v-for="(fieldType, fieldIndex) in fieldTypes" :class="{ 'fill-remaining': fieldType.fill }">
  7. <input name="name" type="text" v-if="fieldType.type === 'text'" v-model="entry[fieldType.fieldTypeId]" v-on:blur="blurInput(entryIndex, fieldType.fieldTypeId)" v-on:focus="focusInput(entryIndex, fieldType.fieldTypeId)" v-on:keydown="keydownInput(entryIndex, fieldType.fieldTypeId)" />
  8. <select name="name" v-if="fieldType.type === 'select'" class="fill-remaining" v-model="entry[fieldType.fieldTypeId]">
  9. <option v-for="option in fieldType.options" :value="option.value">{{option.text}}</option>
  10. </select>
  11. <div tabindex="0" v-on:keyup.enter="toggleCheckbox(entryIndex, fieldType.fieldTypeId)" v-on:keyup.space="toggleCheckbox(entryIndex, fieldType.fieldTypeId)" name="name" class="checkbox" v-if="fieldType.type === 'checkbox'" :class="{ checked: entry[fieldType.fieldTypeId] }" @click="toggleCheckbox(entryIndex, fieldType.fieldTypeId)"></div>
  12. <button v-if="fieldType.extraButtons" v-for="buttonInfo in fieldType.extraButtons" type="button" :class="[buttonInfo.style]">{{buttonInfo.icon}}</button>
  13. <button v-if="entryIndex + 1 === entries.length && entryIndex + 1 < maxEntries && fieldIndex + 1 === fieldTypes.length" @click="addEntry()" type="button">+</button>
  14. <button v-if="entries.length > minEntries && fieldIndex + 1 === fieldTypes.length" @click="removeEntry(entryIndex)" type="button">-</button>
  15. <div v-if="fieldType.autosuggestGroup && (focusedInput === `${entryIndex}.${fieldType.fieldTypeId}` || autosuggestHover === `${entryIndex}.${fieldType.fieldTypeId}`)" class="autosuggest-container" @mouseover="focusAutosuggestContainer(entryIndex, fieldType.fieldTypeId)" @mouseleave="blurAutosuggestContainer()">
  16. <div v-for="autosuggestItem in filter(autosuggest[fieldType.autosuggestGroup], entry[fieldType.fieldTypeId])" class="autosuggest-item" @click="selectAutosuggest(entryIndex, fieldType.fieldTypeId, autosuggestItem)">
  17. {{ autosuggestItem }}
  18. </div>
  19. </div>
  20. </div>
  21. </div>
  22. </div>
  23. </template>
  24. <script>
  25. function Field() {
  26. }
  27. export default {
  28. data: function() {
  29. return {
  30. entries: [...this.initialEntries],
  31. focusedInput: "",
  32. activeAutosuggestHover: ""
  33. };
  34. },
  35. props: {
  36. name: String,
  37. fieldTypes: Array,
  38. minEntries: Number,
  39. maxEntries: Number,
  40. initialEntries: Array,
  41. autosuggest: Object
  42. },
  43. mounted() {
  44. },
  45. methods: {
  46. addEntry() {
  47. let emptyEntry = {};
  48. this.fieldTypes.forEach((fieldType) => {
  49. if (fieldType.type === "text" || fieldType.type === "select") emptyEntry[fieldType.fieldTypeId] = "";
  50. else if (fieldType.type === "checkbox") emptyEntry[fieldType.fieldTypeId] = false;
  51. });
  52. this.entries.push(emptyEntry);
  53. },
  54. removeEntry(index) {
  55. this.entries.splice(index, 1);
  56. },
  57. toggleCheckbox(entryIndex, fieldTypeId) {
  58. this.entries[entryIndex][fieldTypeId] = !this.entries[entryIndex][fieldTypeId];
  59. },
  60. selectAutosuggest(entryIndex, fieldTypeId, autosuggestItem) {
  61. this.entries[entryIndex][fieldTypeId] = autosuggestItem;
  62. },
  63. blurInput(entryIndex, fieldTypeId) {
  64. this.focusedInput = "";
  65. },
  66. focusInput(entryIndex, fieldTypeId) {
  67. this.focusedInput = `${entryIndex}.${fieldTypeId}`;
  68. },
  69. keydownInput(entryIndex, fieldTypeId) {
  70. },
  71. focusAutosuggestContainer(entryIndex, fieldTypeId) {
  72. this.autosuggestHover = `${entryIndex}.${fieldTypeId}`;
  73. },
  74. blurAutosuggestContainer() {
  75. this.autosuggestHover = "";
  76. },
  77. filter(autosuggest, value) {
  78. return autosuggest.filter(autosuggestItem => autosuggestItem.toLowerCase().startsWith(value.toLowerCase())).sort();
  79. }
  80. }
  81. };
  82. </script>
  83. <style lang="scss" scoped>
  84. .control {
  85. width: 100%;
  86. margin-bottom: 16px;
  87. label {
  88. display: block;
  89. margin-bottom: 4px;
  90. }
  91. .control-row {
  92. height: 32px;
  93. display: flex;
  94. grid-column-gap: 12px;
  95. }
  96. .control-row:not(:last-of-type) {
  97. margin-bottom: 8px;
  98. }
  99. .control-col {
  100. display: flex;
  101. position: relative;
  102. button:last-of-type {
  103. border-radius: 0 5px 5px 0;
  104. }
  105. > input:not(:last-child) {
  106. border-right: none;
  107. border-top-right-radius: 0;
  108. border-bottom-right-radius: 0;
  109. }
  110. .autosuggest-container {
  111. position: absolute;
  112. top: 31px;
  113. width: 100%;
  114. border-radius: 5px 5px 5px 5px;
  115. border: solid .5px #464646;
  116. z-index: 10;
  117. .autosuggest-item {
  118. padding: 8px;
  119. cursor: pointer;
  120. background-color: white;
  121. &:hover, &:focus {
  122. background-color: lightgray;
  123. }
  124. }
  125. }
  126. }
  127. input, select {
  128. border: solid .5px #464646;
  129. border-radius: 5px;
  130. padding: 4px;
  131. }
  132. input {
  133. width: 100%;
  134. }
  135. button {
  136. width: 32px;
  137. height: 32px;
  138. border: none;
  139. font-size: 24px;
  140. background-color: green;
  141. color: white;
  142. cursor: pointer;
  143. }
  144. button:hover, button:focus {
  145. background-color: darkgreen;
  146. }
  147. }
  148. .fill-remaining {
  149. width: 100%;
  150. width: -moz-available; /* WebKit-based browsers will ignore this. */
  151. width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
  152. width: fill-available;
  153. }
  154. .checkbox {
  155. height: 32px;
  156. width: 32px;
  157. background-color: white;
  158. border: .5px #464646 solid;
  159. border-radius: 3px;
  160. cursor: pointer;
  161. position: relative;
  162. box-sizing: border-box;
  163. }
  164. .checkbox.checked::after {
  165. content: "";
  166. width: 26px;
  167. height: 26px;
  168. left: 2px;
  169. top: 2px;
  170. display: inline-block;
  171. position: absolute;
  172. border-radius: 3px;
  173. background-color: #69B862;
  174. }
  175. </style>