Skip to content

Commit 754a730

Browse files
authored
Updated for One to many Relationships (#3)
* Updated trait to accommodate one to many relationships * Added a Custom Validation Rule * Created Field Object * Created Form, Detail and Index Views * Refactored Detail Field * Pass settings to Detail and Form view * webpack updates
1 parent a201e69 commit 754a730

11 files changed

Lines changed: 15831 additions & 99 deletions

dist/js/field.js

Lines changed: 6677 additions & 56 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"laravel-nova": "^1.0"
1616
},
1717
"dependencies": {
18-
"vue": "^2.5.0"
18+
"vue": "^2.5.0",
19+
"vuedraggable": "^2.20.0"
1920
}
2021
}
Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
11
<template>
2-
<panel-item :field="field" />
2+
<div class="flex border-b border-40">
3+
<div class="w-1/4 py-4">
4+
<slot>
5+
<h4 class="font-normal text-80">
6+
{{ field.name }}
7+
</h4>
8+
</slot>
9+
</div>
10+
<div class="w-3/4 py-4">
11+
<div>
12+
<RelationshipDetailItem
13+
v-for="(item, index) in field.value"
14+
:id="index"
15+
:key="index"
16+
:value="item"
17+
:label="field.name"
18+
:settings="field.settings"
19+
:collapsed="collapsed"
20+
/>
21+
</div>
22+
</div>
23+
</div>
324
</template>
425

526
<script>
27+
import RelationshipDetailItem from "./RelationshipDetailItem";
628
export default {
29+
components: {RelationshipDetailItem},
30+
731
props: ['resource', 'resourceName', 'resourceId', 'field'],
32+
33+
computed:{
34+
collapsed:function(){
35+
return this.field.collapsed === true
36+
}
37+
}
838
}
939
</script>
Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,93 @@
11
<template>
2-
<default-field :field="field" :errors="errors">
3-
<template slot="field">
4-
<input
5-
:id="field.name"
6-
type="text"
7-
class="w-full form-control form-input form-input-bordered"
8-
:class="errorClasses"
9-
:placeholder="field.name"
10-
v-model="value"
11-
/>
12-
</template>
13-
</default-field>
2+
<default-field
3+
:field="field"
4+
:errors="errors"
5+
>
6+
<template slot="field">
7+
<draggable
8+
v-model="value"
9+
handle=".relationship-item-handle"
10+
@start="drag=true"
11+
@end="drag=false"
12+
>
13+
<relationship-form-item
14+
v-for="(item, index) in value"
15+
:id="index"
16+
:key="index"
17+
:value="item"
18+
:errors="errorList"
19+
:name="field.attribute"
20+
:label="field.name"
21+
:singular="field.singular"
22+
:settings="field.settings"
23+
@deleted="removeItem(index)"
24+
/>
25+
</draggable>
26+
<div
27+
v-if="!field.singular"
28+
class="bg-30 flex p-2 border-b border-40 rounded-lg"
29+
>
30+
<div class="w-full text-right">
31+
<button
32+
type="button"
33+
class="btn btn-default bg-transparent hover:bg-primary text-primary hover:text-white border border-primary hover:border-transparent inline-flex items-center relative mr-3"
34+
@click="addItem()"
35+
>
36+
Add new {{ field.name }}
37+
</button>
38+
</div>
39+
</div>
40+
</template>
41+
</default-field>
1442
</template>
1543

1644
<script>
1745
import { FormField, HandlesValidationErrors } from 'laravel-nova'
46+
import draggable from 'vuedraggable'
47+
import RelationshipFormItem from './RelationshipFormItem.vue'
1848
1949
export default {
50+
components:{
51+
draggable,
52+
RelationshipFormItem
53+
},
54+
2055
mixins: [FormField, HandlesValidationErrors],
2156
2257
props: ['resourceName', 'resourceId', 'field'],
2358
59+
data: function(){
60+
return {
61+
errorBag: []
62+
}
63+
},
64+
65+
watch:{
66+
'errors': function (errors) {
67+
this.errorList = errors.errors.hasOwnProperty(this.field.attribute) ? errors.errors[this.field.attribute][0] : {};
68+
}
69+
},
70+
2471
methods: {
2572
/*
2673
* Set the initial, internal value for the field.
2774
*/
2875
setInitialValue() {
29-
this.value = this.field.value || ''
76+
this.value = Array.isArray(this.field.value) ? this.field.value : [];
77+
if(this.field.singular){
78+
this.value = this.value.splice(1);
79+
}
80+
81+
if(this.field.addChildAtStart && (this.value.length == 0)){
82+
this.value.push({...this.field.defaults});
83+
}
3084
},
3185
3286
/**
3387
* Fill the given FormData object with the field's internal value.
3488
*/
3589
fill(formData) {
36-
formData.append(this.field.attribute, this.value || '')
90+
formData.append(this.field.attribute, JSON.stringify(this.value) || '{}')
3791
},
3892
3993
/**
@@ -42,6 +96,18 @@ export default {
4296
handleChange(value) {
4397
this.value = value
4498
},
45-
},
99+
100+
removeItem (index) {
101+
let value = [...this.value];
102+
value.splice(index, 1);
103+
this.handleChange(value);
104+
},
105+
106+
addItem(){
107+
let value = [...this.value];
108+
value.push({...this.field.defaults});
109+
this.handleChange(value);
110+
},
111+
}
46112
}
47113
</script>

resources/js/components/IndexField.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<span>{{ field.value }}</span>
2+
<span>{{ Object.keys(field.value).length }} {{ field.name }}(s)</span>
33
</template>
44

55
<script>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<div class="card shadow-md mb-4">
3+
<div class="bg-30 flex p-2 border-b border-40">
4+
<span>
5+
<button
6+
v-if="collapsed"
7+
class="btn btn-default btn-icon btn-white mr-3 p-1"
8+
@click="collapsed=false"
9+
>
10+
<svg
11+
xmlns="http://www.w3.org/2000/svg"
12+
viewBox="0 0 24 24"
13+
width="24"
14+
height="24"
15+
><path
16+
class="heroicon-ui"
17+
d="M17 11a1 1 0 0 1 0 2h-4v4a1 1 0 0 1-2 0v-4H7a1 1 0 0 1 0-2h4V7a1 1 0 0 1 2 0v4h4z"
18+
/></svg>
19+
</button>
20+
<button
21+
v-else
22+
class="btn btn-default btn-icon btn-white mr-3 p-1"
23+
@click="collapsed=true"
24+
>
25+
<svg
26+
xmlns="http://www.w3.org/2000/svg"
27+
viewBox="0 0 24 24"
28+
width="24"
29+
height="24"
30+
><path
31+
class="heroicon-ui"
32+
d="M17 11a1 1 0 0 1 0 2H7a1 1 0 0 1 0-2h10z"
33+
/></svg>
34+
</button>
35+
</span>
36+
<span class="font-normal text-90 py-2 px-2">
37+
{{ label }} {{ id+1 }}
38+
</span>
39+
</div>
40+
<transition name="slide-fade">
41+
<div v-if="! collapsed">
42+
<div
43+
v-for="(parameter, attrib) in value"
44+
:key="attrib"
45+
class="flex p-2 border-b border-40"
46+
>
47+
<div class="w-1/4 py-2 px-2">
48+
<h4 class="font-normal text-80">
49+
{{ getLabel(attrib) }}
50+
</h4>
51+
</div>
52+
<div class="w-3/4 py-2 px-2">
53+
{{ parameter }}
54+
</div>
55+
</div>
56+
</div>
57+
</transition>
58+
</div>
59+
</template>
60+
61+
<script>
62+
export default {
63+
name: "RelationshipDetailItem",
64+
65+
props:{
66+
'value': Object,
67+
'settings': Object,
68+
'collapsed':{
69+
type: Boolean,
70+
default: false
71+
},
72+
'label': String,
73+
'id': Number
74+
},
75+
76+
methods:{
77+
getLabel:function(attrib){
78+
return this.getSettings(attrib, 'label') || attrib;
79+
},
80+
81+
getSettings:function(attrib, key){
82+
return this.settings && this.settings.hasOwnProperty(attrib) && this.settings[attrib].hasOwnProperty(key) ? this.settings[attrib][key] : ''
83+
},
84+
}
85+
}
86+
</script>

0 commit comments

Comments
 (0)