Skip to content

Commit 79982c0

Browse files
author
Nils Henning
committed
[FEATURE] add multipart form data upload functionality
1 parent 5c13909 commit 79982c0

File tree

11 files changed

+403
-241
lines changed

11 files changed

+403
-241
lines changed
Lines changed: 162 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,216 @@
1-
import Vue from 'vue/dist/vue.esm'
2-
import Vuex from 'vuex'
3-
import axios from 'axios'
1+
import Vue from "vue/dist/vue.esm";
2+
import Vuex from "vuex";
3+
import axios from "axios";
44

5-
import matestackEventHub from '../js/event-hub'
6-
import componentMixin from '../component/component'
5+
import matestackEventHub from "../js/event-hub";
6+
import componentMixin from "../component/component";
77

88
const componentDef = {
99
mixins: [componentMixin],
10-
data: function(){
10+
data: function () {
1111
return {
1212
data: {},
1313
showInlineForm: false,
14-
errors: {}
15-
}
14+
errors: {},
15+
};
1616
},
1717
methods: {
18-
initDataKey: function(key, initValue){
18+
initDataKey: function (key, initValue) {
1919
this.data[key] = initValue;
2020
},
21-
inputChanged: function(key){
22-
this.resetErrors(key)
21+
inputChanged: function (key) {
22+
this.resetErrors(key);
2323
},
24-
updateFormValue: function(key, value){
24+
updateFormValue: function (key, value) {
2525
this.data[key] = value;
2626
},
27-
resetErrors: function(key){
28-
if (this.errors[key]){
27+
resetErrors: function (key) {
28+
if (this.errors[key]) {
2929
this.errors[key] = null;
3030
}
3131
},
32-
launchInlineForm: function(key, value){
32+
launchInlineForm: function (key, value) {
3333
this.showInlineForm = true;
3434
this.data[key] = value;
3535
const self = this;
3636
setTimeout(function () {
3737
self.$refs.inlineinput.focus();
3838
}, 300);
3939
},
40-
closeInlineForm: function(){
40+
closeInlineForm: function () {
4141
this.showInlineForm = false;
4242
},
43-
setProps: function(flat, newVal){
44-
for(var i in flat){
45-
if((typeof flat[i] === "object") && !(flat[i] instanceof Array)){
46-
setProps(flat[i], newVal);
47-
return;
48-
} else {
49-
flat[i] = newVal;
50-
}
43+
setProps: function (flat, newVal) {
44+
for (var i in flat) {
45+
if (typeof flat[i] === "object" && !(flat[i] instanceof Array)) {
46+
setProps(flat[i], newVal);
47+
return;
48+
} else {
49+
flat[i] = newVal;
50+
}
5151
}
5252
},
53-
initValues: function(){
53+
filesAdded: function (key) {
54+
const dataTransfer = event.dataTransfer || event.target;
55+
const files = dataTransfer.files;
56+
if (event.target.attributes.multiple) {
57+
this.data[key] = [];
58+
for (let index in files) {
59+
if (files[index] instanceof File) {
60+
console.log(files[index]);
61+
this.data[key].push(files[index]);
62+
}
63+
}
64+
} else {
65+
this.data[key] = files[0];
66+
}
67+
},
68+
initValues: function () {
5469
let self = this;
55-
let data = {}
70+
let data = {};
5671
for (let key in self.$refs) {
57-
let initValue = self.$refs[key]["attributes"]["init-value"]
58-
let valueType = self.$refs[key]["attributes"]["value-type"]
72+
let initValue = self.$refs[key]["attributes"]["init-value"];
73+
let valueType = self.$refs[key]["attributes"]["value-type"];
5974

60-
if (key.startsWith("input.")){
61-
if(initValue){
62-
data[key.replace('input.', '')] = initValue["value"]
63-
}else{
64-
data[key.replace('input.', '')] = null
75+
if (key.startsWith("input.")) {
76+
if (initValue) {
77+
data[key.replace("input.", "")] = initValue["value"];
78+
} else {
79+
data[key.replace("input.", "")] = null;
6580
}
6681
}
67-
if (key.startsWith("select.")){
68-
if (key.startsWith("select.multiple.")){
69-
if(initValue){
70-
data[key.replace('select.multiple.', '')] = JSON.parse(initValue["value"])
71-
}else{
72-
data[key.replace('select.multiple.', '')] = []
82+
if (key.startsWith("select.")) {
83+
if (key.startsWith("select.multiple.")) {
84+
if (initValue) {
85+
data[key.replace("select.multiple.", "")] = JSON.parse(initValue["value"]);
86+
} else {
87+
data[key.replace("select.multiple.", "")] = [];
7388
}
74-
}else{
75-
if(initValue){
76-
if(valueType && valueType["value"] == "Integer")
77-
data[key.replace('select.', '')] = parseInt(initValue["value"])
78-
else{
79-
data[key.replace('select.', '')] = initValue["value"]
89+
} else {
90+
if (initValue) {
91+
if (valueType && valueType["value"] == "Integer") data[key.replace("select.", "")] = parseInt(initValue["value"]);
92+
else {
93+
data[key.replace("select.", "")] = initValue["value"];
8094
}
81-
}else{
82-
data[key.replace('select.', '')] = null
95+
} else {
96+
data[key.replace("select.", "")] = null;
8397
}
8498
}
85-
8699
}
87100
}
88101
self.data = data;
89102
},
90103
shouldResetFormOnSuccessfulSubmit() {
91-
const self = this
92-
if (self.componentConfig['success'] != undefined && self.componentConfig['success']['reset'] != undefined) {
93-
return self.componentConfig['success']['reset']
104+
const self = this;
105+
if (self.componentConfig["success"] != undefined && self.componentConfig["success"]["reset"] != undefined) {
106+
return self.componentConfig["success"]["reset"];
94107
} else {
95-
return self.shouldResetFormOnSuccessfulSubmitByDefault()
108+
return self.shouldResetFormOnSuccessfulSubmitByDefault();
96109
}
97110
},
98111
shouldResetFormOnSuccessfulSubmitByDefault() {
99-
const self = this
112+
const self = this;
100113
if (self.componentConfig["method"] == "put") {
101-
return false
114+
return false;
102115
} else {
103-
return true
116+
return true;
104117
}
105118
},
106-
perform: function(){
107-
const self = this
108-
let payload = {}
109-
payload[self.componentConfig["for"]] = self.data
110-
axios({
111-
method: self.componentConfig["method"],
112-
url: self.componentConfig["submit_path"],
113-
data: payload,
114-
headers: {
115-
'X-CSRF-Token': document.getElementsByName("csrf-token")[0].getAttribute('content')
116-
}
117-
})
118-
.then(function(response){
119-
if (self.componentConfig["success"] != undefined && self.componentConfig["success"]["emit"] != undefined) {
120-
matestackEventHub.$emit(self.componentConfig["success"]["emit"], response.data);
121-
}
122-
if (self.componentConfig["success"] != undefined
123-
&& self.componentConfig["success"]["transition"] != undefined
124-
&& (
125-
self.componentConfig["success"]["transition"]["follow_response"] == undefined
126-
||
127-
self.componentConfig["success"]["transition"]["follow_response"] === false
128-
)
129-
&& self.$store != undefined
130-
) {
131-
let path = self.componentConfig["success"]["transition"]["path"]
132-
self.$store.dispatch('navigateTo', {url: path, backwards: false})
133-
return;
134-
}
135-
if (self.componentConfig["success"] != undefined
136-
&& self.componentConfig["success"]["transition"] != undefined
137-
&& self.componentConfig["success"]["transition"]["follow_response"] === true
138-
&& self.$store != undefined
139-
) {
140-
let path = response.data["transition_to"] || response.request.responseURL
141-
self.$store.dispatch('navigateTo', {url: path, backwards: false})
142-
return;
143-
}
144-
if (self.shouldResetFormOnSuccessfulSubmit())
145-
{
146-
self.setProps(self.data, null);
147-
self.initValues();
148-
}
149-
self.showInlineForm = false;
150-
})
151-
.catch(function(error){
152-
if(error.response && error.response.data && error.response.data.errors){
153-
self.errors = error.response.data.errors;
154-
}
155-
if (self.componentConfig["failure"] != undefined && self.componentConfig["failure"]["emit"] != undefined) {
156-
matestackEventHub.$emit(self.componentConfig["failure"]["emit"], error.response.data);
157-
}
158-
if (self.componentConfig["failure"] != undefined && self.componentConfig["failure"]["transition"] != undefined && self.$store != undefined) {
159-
let path = self.componentConfig["failure"]["transition"]["path"]
160-
self.$store.dispatch('navigateTo', {url: path, backwards: false})
119+
perform: function () {
120+
const self = this;
121+
let payload = {};
122+
payload[self.componentConfig["for"]] = self.data;
123+
console.log(this);
124+
let axios_config = {};
125+
// TODO check file uploads
126+
if (this.$vnode.data.attrs.enctype == "multipart/form-data") {
127+
let form_data = new FormData();
128+
for (let key in self.data) {
129+
if (key.endsWith("[]")) {
130+
for (let i in self.data[key]) {
131+
let file = self.data[key][i];
132+
console.log(file);
133+
form_data.append(self.componentConfig["for"] + "[" + key.slice(0, -2) + "][" + i + "]", file, "test_" + i);
134+
}
135+
} else {
136+
form_data.append(self.componentConfig["for"] + "[" + key + "]", self.data[key]);
137+
}
161138
}
162-
})
163-
}
139+
axios_config = {
140+
method: self.componentConfig["method"],
141+
url: self.componentConfig["submit_path"],
142+
data: form_data,
143+
headers: {
144+
"X-CSRF-Token": document.getElementsByName("csrf-token")[0].getAttribute("content"),
145+
"Content-Type": "multipart/form-data",
146+
},
147+
};
148+
} else {
149+
axios_config = {
150+
method: self.componentConfig["method"],
151+
url: self.componentConfig["submit_path"],
152+
data: payload,
153+
headers: {
154+
"X-CSRF-Token": document.getElementsByName("csrf-token")[0].getAttribute("content"),
155+
"Content-Type": "application/json",
156+
},
157+
};
158+
}
159+
axios(axios_config)
160+
.then(function (response) {
161+
if (self.componentConfig["success"] != undefined && self.componentConfig["success"]["emit"] != undefined) {
162+
matestackEventHub.$emit(self.componentConfig["success"]["emit"], response.data);
163+
}
164+
if (
165+
self.componentConfig["success"] != undefined &&
166+
self.componentConfig["success"]["transition"] != undefined &&
167+
(self.componentConfig["success"]["transition"]["follow_response"] == undefined ||
168+
self.componentConfig["success"]["transition"]["follow_response"] === false) &&
169+
self.$store != undefined
170+
) {
171+
let path = self.componentConfig["success"]["transition"]["path"];
172+
self.$store.dispatch("navigateTo", { url: path, backwards: false });
173+
return;
174+
}
175+
if (
176+
self.componentConfig["success"] != undefined &&
177+
self.componentConfig["success"]["transition"] != undefined &&
178+
self.componentConfig["success"]["transition"]["follow_response"] === true &&
179+
self.$store != undefined
180+
) {
181+
let path = response.data["transition_to"] || response.request.responseURL;
182+
self.$store.dispatch("navigateTo", { url: path, backwards: false });
183+
return;
184+
}
185+
if (self.shouldResetFormOnSuccessfulSubmit()) {
186+
self.setProps(self.data, null);
187+
self.initValues();
188+
}
189+
self.showInlineForm = false;
190+
})
191+
.catch(function (error) {
192+
if (error.response && error.response.data && error.response.data.errors) {
193+
self.errors = error.response.data.errors;
194+
}
195+
if (self.componentConfig["failure"] != undefined && self.componentConfig["failure"]["emit"] != undefined) {
196+
matestackEventHub.$emit(self.componentConfig["failure"]["emit"], error.response.data);
197+
}
198+
if (
199+
self.componentConfig["failure"] != undefined &&
200+
self.componentConfig["failure"]["transition"] != undefined &&
201+
self.$store != undefined
202+
) {
203+
let path = self.componentConfig["failure"]["transition"]["path"];
204+
self.$store.dispatch("navigateTo", { url: path, backwards: false });
205+
}
206+
});
207+
},
208+
},
209+
mounted: function () {
210+
this.initValues();
164211
},
165-
mounted: function(){
166-
this.initValues()
167-
}
168-
}
212+
};
169213

170-
let component = Vue.component('matestack-ui-core-form', componentDef)
214+
let component = Vue.component("matestack-ui-core-form", componentDef);
171215

172-
export default componentDef
216+
export default componentDef;

app/concepts/matestack/ui/core/form/input/input.haml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@
3333
step: options[:step],
3434
list: options[:list] }
3535

36+
- if [:file].include?(type)
37+
%input{ @tag_attributes,
38+
type: type,
39+
"@change": "inputChanged(\"#{attr_key}\"); filesAdded('#{attr_key}')",
40+
ref: "input.#{attr_key}",
41+
placeholder: placeholder,
42+
"init-value": init_value,
43+
multiple: options[:multiple] }
44+
3645
%span{ class: "errors", "v-if": error_key }
3746
%span{ class: "error", "v-for": "error in #{error_key}" }
3847
{{ error }}

app/concepts/matestack/ui/core/form/input/input.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ def input_wrapper
4545

4646
def attr_key
4747
if input_wrapper.nil?
48-
return key.to_s
48+
return "#{key.to_s}#{'[]' if options[:multiple]}"
4949
else
50-
return "#{input_wrapper}.#{key.to_s}"
50+
return "#{input_wrapper}.#{key.to_s}#{'[]' if options[:multiple]}"
5151
end
5252
end
5353

spec/dummy/.byebug_history

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
continue
12
exit
23
name
34
exit

spec/dummy/app/controllers/my_app_controller.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ def some_action
3939
end
4040

4141
def form_action
42+
p 'Controller action'
43+
p dummy_model_params[:file]
44+
p dummy_model_params[:files]
45+
dummy_model_params[:files]&.each { |file| p file }
4246
@dummy_model = DummyModel.create(dummy_model_params)
4347
if @dummy_model.errors.any?
4448
render json: {
@@ -84,7 +88,9 @@ def delete_dummy_model
8488
def dummy_model_params
8589
params.require(:dummy_model).permit(
8690
:title,
87-
:description
91+
:description,
92+
:file,
93+
files: []
8894
)
8995
end
9096

0 commit comments

Comments
 (0)