Skip to content

Commit a9c5371

Browse files
committed
wip: matrix input
1 parent 7079bd4 commit a9c5371

File tree

7 files changed

+313
-2
lines changed

7 files changed

+313
-2
lines changed
+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<template>
2+
<input-wrapper v-bind="inputWrapperProps">
3+
<template #label>
4+
<slot name="label" />
5+
</template>
6+
<div class="flex mb-2">
7+
<div class="w-1/4"></div>
8+
<div class="">
9+
<div class="flex space-x-6 py-2" :class="columnGrids">
10+
<div v-for="column in columns" :key="column" class="w-4 flex justify-center">
11+
{{ column }}
12+
</div>
13+
</div>
14+
</div>
15+
</div>
16+
<div v-for="row, index in matrixData" class="w-full flex items-center" :key="row">
17+
<div class="w-1/4">
18+
{{row.label}}
19+
</div>
20+
<div class="flex space-x-6 py-2" :class="columnGrids">
21+
<div v-for="option in row.options" :key="row.label+option" class="w-4 flex justify-center">
22+
<input
23+
type="radio"
24+
:value="option"
25+
:aria-checked="true"
26+
:name="row.label"
27+
v-model="selection[row.label]"
28+
@update:model-value="onSelection(row, $event)"
29+
class="styled-radio"
30+
id="radio-{{ row.label }}-{{ option }}"
31+
>
32+
</div>
33+
</div>
34+
</div>
35+
36+
<template #help>
37+
<slot name="help" />
38+
</template>
39+
<template #error>
40+
<slot name="error" />
41+
</template>
42+
</input-wrapper>
43+
</template>
44+
<script>
45+
import { inputProps, useFormInput } from "./useFormInput.js"
46+
import InputWrapper from "./components/InputWrapper.vue"
47+
48+
export default {
49+
name: "MatrixInput",
50+
components: { InputWrapper },
51+
52+
props: {
53+
...inputProps,
54+
rows: {type: Array, required: true},
55+
columns: {type: Array, required: true},
56+
selectionData: {type: Object, required: true},
57+
},
58+
data() {
59+
return {
60+
selection: {}
61+
}
62+
},
63+
setup(props, context) {
64+
return {
65+
...useFormInput(props, context),
66+
}
67+
},
68+
computed: {
69+
matrixData() {
70+
const options = this.columns
71+
return this.rows?.map(row => {
72+
return {
73+
label: row,
74+
options
75+
}
76+
})
77+
},
78+
columnGrids() {
79+
return 'grid-cols-' + this.columns?.length
80+
}
81+
82+
},
83+
methods: {
84+
onSelection() {
85+
this.compVal = this.selection
86+
},
87+
handleCompValChanged() {
88+
this.selection = this.compVal ?? this.selectionData
89+
}
90+
},
91+
mounted() {
92+
this.handleCompValChanged()
93+
},
94+
95+
watch: {
96+
compVal: {
97+
handler(newVal, oldVal) {
98+
if (!oldVal) {
99+
this.handleCompValChanged()
100+
}
101+
},
102+
immediate: false
103+
}
104+
},
105+
}
106+
</script>
107+
108+
<style scoped>
109+
.styled-radio {
110+
appearance: none;
111+
-webkit-appearance: none;
112+
background-color: #fff;
113+
border: 2px solid #000;
114+
padding: 10px;
115+
border-radius: 4px;
116+
display: inline-block;
117+
position: relative;
118+
}
119+
120+
.styled-radio:checked {
121+
background-color: transparent;
122+
border-color: #2563EB;
123+
}
124+
125+
.styled-radio:checked::after {
126+
content: '';
127+
position: absolute;
128+
top: 3px;
129+
left: 3px;
130+
width: 14px;
131+
height: 14px;
132+
background-color: #2563EB;
133+
display: block;
134+
}
135+
</style>

client/components/open/forms/OpenFormField.vue

+8-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ export default {
193193
checkbox: 'CheckboxInput',
194194
url: 'TextInput',
195195
email: 'TextInput',
196-
phone_number: 'TextInput'
196+
phone_number: 'TextInput',
197+
matrix: 'MatrixInput'
197198
}[field.type]
198199
},
199200
isPublicFormPage() {
@@ -292,6 +293,12 @@ export default {
292293
isDark: this.darkMode
293294
}
294295
296+
if(field.type == 'matrix'){
297+
inputProperties.rows = field.rows
298+
inputProperties.columns = field.columns,
299+
inputProperties.selectionData = field.selection_data
300+
}
301+
295302
if (['select', 'multi_select'].includes(field.type)) {
296303
inputProperties.options = (_has(field, field.type))
297304
? field[field.type].options.map(option => {

client/components/open/forms/components/form-components/AddFormBlock.vue

+5
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ export default {
204204
title: "Signature Input",
205205
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />',
206206
},
207+
{
208+
name: "matrix",
209+
title: "Matrix Input",
210+
icon: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />',
211+
},
207212
],
208213
layoutBlocks: [
209214
{

client/components/open/forms/fields/components/FieldOptions.vue

+15-1
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@
187187
/>
188188
</div>
189189

190+
<MatrixFieldOptions :field="field" />
191+
190192
<!-- Text Options -->
191193
<div
192194
v-if="field.type === 'text' && displayBasedOnAdvanced"
@@ -408,6 +410,9 @@
408410
label="Pre-filled value"
409411
:multiple="field.type === 'multi_select'"
410412
/>
413+
<template v-else-if="field.type === 'matrix'">
414+
<MatrixPrefilledValues :field="field"/>
415+
</template>
411416
<date-input
412417
v-else-if="field.type === 'date' && field.prefill_today !== true"
413418
name="prefill"
@@ -581,12 +586,14 @@ import countryCodes from '~/data/country_codes.json'
581586
import CountryFlag from 'vue-country-flag-next'
582587
import FormBlockLogicEditor from '../../components/form-logic-components/FormBlockLogicEditor.vue'
583588
import CustomFieldValidation from '../../components/CustomFieldValidation.vue'
589+
import MatrixFieldOptions from './MatrixFieldOptions.vue'
590+
import MatrixPrefilledValues from './MatrixPrefilledValues.vue'
584591
import { format } from 'date-fns'
585592
import { default as _has } from 'lodash/has'
586593
587594
export default {
588595
name: 'FieldOptions',
589-
components: { CountryFlag, FormBlockLogicEditor, CustomFieldValidation },
596+
components: { CountryFlag, FormBlockLogicEditor, CustomFieldValidation, MatrixFieldOptions, MatrixPrefilledValues },
590597
props: {
591598
field: {
592599
type: Object,
@@ -824,6 +831,13 @@ export default {
824831
},
825832
date: {
826833
date_format: this.dateFormatOptions[0].value
834+
},
835+
matrix: {
836+
rows:['Row 1'],
837+
columns: [1 ,2 ,3],
838+
selection_data:{
839+
'Row 1': null
840+
}
827841
}
828842
}
829843
if (this.field.type in defaultFieldValues) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<template>
2+
<div
3+
v-if="field.type === 'matrix'"
4+
class="border-b py-2 px-4"
5+
>
6+
<h3 class="font-semibold block text-lg">
7+
Matrix
8+
</h3>
9+
<p class="text-gray-400 mb-3 text-xs">
10+
Advanced options for matrix.
11+
</p>
12+
13+
<div class="grid grid-cols-2 gap-4">
14+
<div class="">
15+
<div v-for="row, i in field.rows" class="flex items-center space-x-2" :key="row+i">
16+
<text-input
17+
name="rows"
18+
class="mb-0"
19+
v-model="field.rows[i]"
20+
/>
21+
<button @click="removeMatrixRow(i)">
22+
<Icon name="heroicons:trash" class="text-gray-300 w-4 h-4"/>
23+
</button>
24+
</div>
25+
<button @click="addMatrixRow" class="space-x-1 flex items-center bg-gray-200 rounded text-xs p-1 px-2">
26+
<Icon name="heroicons:plus" class="w-4 h-4"/>
27+
<span>Add rows</span>
28+
</button>
29+
</div>
30+
<div class="">
31+
<div v-for="column, i in field.columns" class="flex items-center space-x-2" :key="i">
32+
<text-input
33+
class="mb-0"
34+
v-model="field.columns[i]"
35+
/>
36+
<button @click="removeMatrixColumn(i)">
37+
<Icon name="heroicons:trash" class="text-gray-300 w-4 h-4"/>
38+
</button>
39+
</div>
40+
<button @click="addMatrixColumn" class="space-x-1 flex items-center bg-gray-200 rounded text-xs p-1 px-2">
41+
<Icon name="heroicons:plus" class="w-4 h-4"/>
42+
<span>Add column</span>
43+
</button>
44+
</div>
45+
</div>
46+
</div>
47+
</template>
48+
49+
<script>
50+
export default {
51+
name: 'MatrixFieldOptions',
52+
props: {
53+
field: {
54+
type: Object,
55+
required: false
56+
},
57+
},
58+
computed: {
59+
selectionData() {
60+
return this.field.rows?.reduce((obj, row) => {
61+
obj[row] = '';
62+
return obj;
63+
}, {});
64+
}
65+
},
66+
67+
methods: {
68+
addMatrixRow() {
69+
this.field.rows.push(this.generateUniqueLabel(this.field.rows, 'Row'))
70+
this.field.selection_data = this.selectionData
71+
},
72+
removeMatrixRow(index) {
73+
this.field.rows.splice(index, 1)
74+
this.field.selection_data = this.selectionData
75+
},
76+
77+
addMatrixColumn() {
78+
this.field.columns.push(this.generateUniqueLabel(this.field.columns, null))
79+
this.field.selection_data = this.selectionData
80+
},
81+
removeMatrixColumn(index) {
82+
this.field.columns.splice(index, 1)
83+
this.field.selection_data = this.selectionData
84+
},
85+
86+
generateUniqueLabel(array, prefix = null) {
87+
let uniqueNumber = 1; // Start checking from 1
88+
let label = prefix ? `${prefix} ${uniqueNumber}` : uniqueNumber
89+
while (array.includes(label)) {
90+
uniqueNumber++; // Increment if the number is found in the array
91+
label = prefix ? `${prefix} ${uniqueNumber}` : uniqueNumber
92+
}
93+
return label; // Return the first unique number found
94+
}
95+
}
96+
}
97+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<template>
2+
<p class="font-semibold">Prefilled values</p>
3+
<select-input
4+
v-for="row, i in matrixData"
5+
:key="row.label"
6+
name="prefill"
7+
class="mt-3"
8+
:options="row.options"
9+
:label="row.label"
10+
v-model="selection[row.label]"
11+
@update:model-value="onSelection()"
12+
/>
13+
</template>
14+
<script>
15+
export default {
16+
name: 'MatrixPrefilledValues',
17+
props: {
18+
field: {
19+
type: Object,
20+
required: false
21+
},
22+
form: {
23+
type: Object,
24+
required: false
25+
}
26+
},
27+
data() {
28+
return {
29+
selection: {}
30+
}
31+
},
32+
computed: {
33+
matrixData() {
34+
const options = this.field.columns
35+
return this.field.rows?.map(row => {
36+
return {
37+
label: row,
38+
options: options?.map(option => ({ name: option, value: option }))
39+
}
40+
})
41+
},
42+
},
43+
mounted() {
44+
this.selection = this.field.prefill ?? this.field.selection_data
45+
},
46+
methods: {
47+
onSelection() {
48+
this.field.prefill = this.selection
49+
}
50+
}
51+
}
52+
</script>

client/stores/working_form.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const defaultBlockNames = {
1717
multi_select: "Multi Select",
1818
files: "Files",
1919
signature: "Signature",
20+
matrix: "Matrix",
2021
"nf-text": "Text Block",
2122
"nf-page-break": "Page Break",
2223
"nf-divider": "Divider",

0 commit comments

Comments
 (0)