質問編集履歴
3
内容訂正
test
CHANGED
@@ -1 +1 @@
|
|
1
|
-
【Rails】
|
1
|
+
【Rails】画像データをドラッグ&ドロップでアップする方法
|
test
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
######ドラッグ&ドロップでのファイルアップロードに苦戦しています。
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
ファイルのアップロードはgem'Dropzonejs'を使っています。
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
店舗を検索するサイトを作っており、住所等の店舗情報を入力するフォーム内に、同時に画像データも保存させるようにしたいと考えております。
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
ただ残念ながらDropzonejsはデフォルトではデータをドラッグしたタイミングに保存されるようになっています。
|
16
|
+
|
17
|
+
そのため、shop.newの時点でshop_idを持たないimageをそのまま保存することが出来ない状況です。
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
現状、shopをcreate後、shoop_idを用いて@shop.images.buildしてnewページを出していますが、店舗情報と画像登録画面が別になっておりスマートな方法ではありません。
|
22
|
+
|
23
|
+
これをドラッグしたタイミング→データの保持、保存ボタンを押したタイミング→データを保存というように変更したいと考えています。
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
①Dropzonejsのjsファイルをカスタマイズする
|
28
|
+
|
29
|
+
②Dropzonejsは使わずに、独自でドラッグ&ドロップフォームを作る
|
30
|
+
|
31
|
+
のいずれかが解決方法となるかと思います。
|
32
|
+
|
33
|
+
グーグルで検索しても目ぼしい情報が得られずにいますので、ご教示頂けますと幸いです。
|
34
|
+
|
1
35
|
###環境
|
2
36
|
|
3
37
|
Ruby2.3.3
|
@@ -10,410 +44,176 @@
|
|
10
44
|
|
11
45
|
gem 'dropzonejs-rails'
|
12
46
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
店舗
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
dict
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
this
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
resize: function(file) {
|
188
|
-
|
189
|
-
var info, srcRatio, trgRatio;
|
190
|
-
|
191
|
-
info = {
|
192
|
-
|
193
|
-
srcX: 0,
|
194
|
-
|
195
|
-
srcY: 0,
|
196
|
-
|
197
|
-
srcWidth: file.width,
|
198
|
-
|
199
|
-
srcHeight: file.height
|
200
|
-
|
201
|
-
};
|
202
|
-
|
203
|
-
srcRatio = file.width / file.height;
|
204
|
-
|
205
|
-
info.optWidth = this.options.thumbnailWidth;
|
206
|
-
|
207
|
-
info.optHeight = this.options.thumbnailHeight;
|
208
|
-
|
209
|
-
if ((info.optWidth == null) && (info.optHeight == null)) {
|
210
|
-
|
211
|
-
info.optWidth = info.srcWidth;
|
212
|
-
|
213
|
-
info.optHeight = info.srcHeight;
|
214
|
-
|
215
|
-
} else if (info.optWidth == null) {
|
216
|
-
|
217
|
-
info.optWidth = srcRatio * info.optHeight;
|
218
|
-
|
219
|
-
} else if (info.optHeight == null) {
|
220
|
-
|
221
|
-
info.optHeight = (1 / srcRatio) * info.optWidth;
|
222
|
-
|
223
|
-
}
|
224
|
-
|
225
|
-
trgRatio = info.optWidth / info.optHeight;
|
226
|
-
|
227
|
-
if (file.height < info.optHeight || file.width < info.optWidth) {
|
228
|
-
|
229
|
-
info.trgHeight = info.srcHeight;
|
230
|
-
|
231
|
-
info.trgWidth = info.srcWidth;
|
232
|
-
|
233
|
-
} else {
|
234
|
-
|
235
|
-
if (srcRatio > trgRatio) {
|
236
|
-
|
237
|
-
info.srcHeight = file.height;
|
238
|
-
|
239
|
-
info.srcWidth = info.srcHeight * trgRatio;
|
240
|
-
|
241
|
-
} else {
|
242
|
-
|
243
|
-
info.srcWidth = file.width;
|
244
|
-
|
245
|
-
info.srcHeight = info.srcWidth / trgRatio;
|
246
|
-
|
247
|
-
}
|
248
|
-
|
249
|
-
}
|
250
|
-
|
251
|
-
info.srcX = (file.width - info.srcWidth) / 2;
|
252
|
-
|
253
|
-
info.srcY = (file.height - info.srcHeight) / 2;
|
254
|
-
|
255
|
-
return info;
|
256
|
-
|
257
|
-
},
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
/*
|
262
|
-
|
263
|
-
Those functions register themselves to the events on init and handle all
|
264
|
-
|
265
|
-
the user interface specific stuff. Overwriting them won't break the upload
|
266
|
-
|
267
|
-
but can break the way it's displayed.
|
268
|
-
|
269
|
-
You can overwrite them if you don't like the default behavior. If you just
|
270
|
-
|
271
|
-
want to add an additional event handler, register it on the dropzone object
|
272
|
-
|
273
|
-
and don't overwrite those options.
|
274
|
-
|
275
|
-
*/
|
276
|
-
|
277
|
-
drop: function(e) {
|
278
|
-
|
279
|
-
return this.element.classList.remove("dz-drag-hover");
|
280
|
-
|
281
|
-
},
|
282
|
-
|
283
|
-
dragstart: noop,
|
284
|
-
|
285
|
-
dragend: function(e) {
|
286
|
-
|
287
|
-
return this.element.classList.remove("dz-drag-hover");
|
288
|
-
|
289
|
-
},
|
290
|
-
|
291
|
-
dragenter: function(e) {
|
292
|
-
|
293
|
-
return this.element.classList.add("dz-drag-hover");
|
294
|
-
|
295
|
-
},
|
296
|
-
|
297
|
-
dragover: function(e) {
|
298
|
-
|
299
|
-
return this.element.classList.add("dz-drag-hover");
|
300
|
-
|
301
|
-
},
|
302
|
-
|
303
|
-
dragleave: function(e) {
|
304
|
-
|
305
|
-
return this.element.classList.remove("dz-drag-hover");
|
306
|
-
|
307
|
-
},
|
308
|
-
|
309
|
-
paste: noop,
|
310
|
-
|
311
|
-
reset: function() {
|
312
|
-
|
313
|
-
return this.element.classList.remove("dz-started");
|
314
|
-
|
315
|
-
},
|
316
|
-
|
317
|
-
addedfile: function(file) {
|
318
|
-
|
319
|
-
var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
|
320
|
-
|
321
|
-
if (this.element === this.previewsContainer) {
|
322
|
-
|
323
|
-
this.element.classList.add("dz-started");
|
324
|
-
|
325
|
-
}
|
326
|
-
|
327
|
-
if (this.previewsContainer) {
|
328
|
-
|
329
|
-
file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim());
|
330
|
-
|
331
|
-
file.previewTemplate = file.previewElement;
|
332
|
-
|
333
|
-
this.previewsContainer.appendChild(file.previewElement);
|
334
|
-
|
335
|
-
_ref = file.previewElement.querySelectorAll("[data-dz-name]");
|
336
|
-
|
337
|
-
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
338
|
-
|
339
|
-
node = _ref[_i];
|
340
|
-
|
341
|
-
node.textContent = this._renameFilename(file.name);
|
342
|
-
|
343
|
-
}
|
344
|
-
|
345
|
-
_ref1 = file.previewElement.querySelectorAll("[data-dz-size]");
|
346
|
-
|
347
|
-
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
348
|
-
|
349
|
-
node = _ref1[_j];
|
350
|
-
|
351
|
-
node.innerHTML = this.filesize(file.size);
|
352
|
-
|
353
|
-
}
|
354
|
-
|
355
|
-
if (this.options.addRemoveLinks) {
|
356
|
-
|
357
|
-
file._removeLink = Dropzone.createElement("<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>" + this.options.dictRemoveFile + "</a>");
|
358
|
-
|
359
|
-
file.previewElement.appendChild(file._removeLink);
|
360
|
-
|
361
|
-
}
|
362
|
-
|
363
|
-
removeFileEvent = (function(_this) {
|
364
|
-
|
365
|
-
return function(e) {
|
366
|
-
|
367
|
-
e.preventDefault();
|
368
|
-
|
369
|
-
e.stopPropagation();
|
370
|
-
|
371
|
-
if (file.status === Dropzone.UPLOADING) {
|
372
|
-
|
373
|
-
return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() {
|
374
|
-
|
375
|
-
return _this.removeFile(file);
|
376
|
-
|
377
|
-
});
|
378
|
-
|
379
|
-
} else {
|
380
|
-
|
381
|
-
if (_this.options.dictRemoveFileConfirmation) {
|
382
|
-
|
383
|
-
return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() {
|
384
|
-
|
385
|
-
return _this.removeFile(file);
|
386
|
-
|
387
|
-
});
|
388
|
-
|
389
|
-
} else {
|
390
|
-
|
391
|
-
return _this.removeFile(file);
|
392
|
-
|
393
|
-
}
|
394
|
-
|
395
|
-
}
|
396
|
-
|
397
|
-
};
|
398
|
-
|
399
|
-
})(this);
|
400
|
-
|
401
|
-
_ref2 = file.previewElement.querySelectorAll("[data-dz-remove]");
|
402
|
-
|
403
|
-
_results = [];
|
404
|
-
|
405
|
-
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
406
|
-
|
407
|
-
removeLink = _ref2[_k];
|
408
|
-
|
409
|
-
_results.push(removeLink.addEventListener("click", removeFileEvent));
|
410
|
-
|
411
|
-
}
|
412
|
-
|
413
|
-
return _results;
|
414
|
-
|
415
|
-
}
|
416
|
-
|
417
|
-
},
|
418
|
-
|
419
|
-
```
|
47
|
+
###モデル
|
48
|
+
|
49
|
+
Shop→imageをネストしています。
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
shop(店舗)モデル
|
54
|
+
|
55
|
+
|id|name|address||
|
56
|
+
|
57
|
+
|:--:|:--:|:--:|
|
58
|
+
|
59
|
+
|1|店舗名|住所|
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
image(画像)モデル
|
64
|
+
|
65
|
+
|id|shop_id|file|
|
66
|
+
|
67
|
+
|:--:|:--:|:--:|
|
68
|
+
|
69
|
+
|1|1|file|
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
###該当箇所のコード
|
74
|
+
|
75
|
+
routes.rb
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
|
79
|
+
resources :shops, only: [:show] do
|
80
|
+
|
81
|
+
resources :images, only: [:new, :create, :destroy] do
|
82
|
+
|
83
|
+
collection do
|
84
|
+
|
85
|
+
post :upload
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
```
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
shop.rb
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
|
101
|
+
class Shop < ApplicationRecord
|
102
|
+
|
103
|
+
has_many :images, dependent: :destroy
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
```
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
image.rb
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
|
115
|
+
class ShopImage < ApplicationRecord
|
116
|
+
|
117
|
+
belongs_to :shop, optional: true
|
118
|
+
|
119
|
+
mount_uploader :file, ImageUploader
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
```
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
shops_controller.rb
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
|
131
|
+
def create
|
132
|
+
|
133
|
+
@shop = current_user.shops.build(shop_params)
|
134
|
+
|
135
|
+
respond_to do |format|
|
136
|
+
|
137
|
+
if verify_recaptcha(model: @shop) && @shop.save
|
138
|
+
|
139
|
+
format.html { redirect_to "/shops/#{@shop.id}/images/new", notice: '画像を登録してください。' }
|
140
|
+
|
141
|
+
format.json { render :show, status: :created, location: @shop }
|
142
|
+
|
143
|
+
else
|
144
|
+
|
145
|
+
format.html { render :new }
|
146
|
+
|
147
|
+
format.json { render json: @shop.errors, status: :unprocessable_entity }
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
```
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
image_controller.rb
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
|
163
|
+
def upload
|
164
|
+
|
165
|
+
@shop = Shop.find(params[:shop_id])
|
166
|
+
|
167
|
+
image = @shop.images.build(file: params['file'])
|
168
|
+
|
169
|
+
image.save!
|
170
|
+
|
171
|
+
render status: 200, json: @shop.images
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
```
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
images/new.html.erb
|
180
|
+
|
181
|
+
```
|
182
|
+
|
183
|
+
<h1>画像の登録</h1>
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
<%= render 'form' %>
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
<% @shop.images.each do |image| %>
|
192
|
+
|
193
|
+
<div id="image_<%=image.id%>">
|
194
|
+
|
195
|
+
<%= image_tag(image.file.url, :width => '200px') %>
|
196
|
+
|
197
|
+
<%= link_to content_tag(:i, '', class: 'fa fa-trash'), image_path(@shop, image), method: :delete, data: { confirm: "本当に削除してもよろしいですか" }, class: "image_delete", remote: true %>
|
198
|
+
|
199
|
+
</div>
|
200
|
+
|
201
|
+
<% end %>
|
202
|
+
|
203
|
+
```
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
images/_form.html.erb
|
208
|
+
|
209
|
+
```html
|
210
|
+
|
211
|
+
<div class="fallback">
|
212
|
+
|
213
|
+
<%= form_tag(upload_shop_images_path(@shop, remote: true),
|
214
|
+
|
215
|
+
:id => 'upload-dropzone', :class => 'dropzone', method: :post) %>
|
216
|
+
|
217
|
+
</div>
|
218
|
+
|
219
|
+
```
|
2
モデルのリレーションについて追加
test
CHANGED
File without changes
|
test
CHANGED
@@ -16,9 +16,15 @@
|
|
16
16
|
|
17
17
|
Railsのファイルのアップロードでgem'Dropzonejs'を使っています。
|
18
18
|
|
19
|
-
|
19
|
+
店舗を検索するサイトを作っており、店舗名や住所データと同時に画像データも保存させるようにしたいと考えております。
|
20
|
+
|
20
|
-
|
21
|
+
なお、店舗モデル(shop)と画像モデル(shop_image)を分けており、リレーションは「shop has_many shop_images」としています。
|
22
|
+
|
23
|
+
|
24
|
+
|
21
|
-
な
|
25
|
+
残念ながらDropzonejsはデフォルトではデータをドラッグしたタイミングに保存されるようになっています。
|
26
|
+
|
27
|
+
そのため、shop_idを持たないshop_imageをそのまま保存することが出来ない状況です。
|
22
28
|
|
23
29
|
これをドラッグしたタイミング→データの保持、保存ボタンを押したタイミング→データを保存というように変更したいと考えています。
|
24
30
|
|
1
誤字を訂正しました。
test
CHANGED
@@ -1 +1 @@
|
|
1
|
-
【Rails】Dropzone
|
1
|
+
【Rails】Dropzonejsでデータを保存するタイミングの変更方法
|
test
CHANGED
@@ -1,309 +1,413 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
1
|
+
###環境
|
2
|
+
|
3
|
+
Ruby2.3.3
|
4
|
+
|
5
|
+
Rails5.0.1
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
###Gem
|
10
|
+
|
11
|
+
gem 'dropzonejs-rails'
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
###問題
|
16
|
+
|
17
|
+
Railsのファイルのアップロードでgem'Dropzonejs'を使っています。
|
18
|
+
|
19
|
+
フォーム内で他のデータと同じタイミングで画像データを保存できるようにしたいです。
|
20
|
+
|
21
|
+
なお、デフォルトではデータをドラッグしたタイミング→保存されるようになっています。
|
22
|
+
|
23
|
+
これをドラッグしたタイミング→データの保持、保存ボタンを押したタイミング→データを保存というように変更したいと考えています。
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
以下JSファイルのどこかを編集すれば良いのだと思いますが、当方JSの知識がほぼゼロでございます。。
|
28
|
+
|
29
|
+
グーグルで検索しても目ぼしい情報が得られずにいますので、ご教示頂けますと幸いです。
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
###恐らく編集すべきファイル「dropzone.js」のこの部分であろう一部を抜粋
|
38
|
+
|
39
|
+
```JavaScript
|
40
|
+
|
41
|
+
Dropzone.prototype.defaultOptions = {
|
42
|
+
|
43
|
+
url: null,
|
44
|
+
|
45
|
+
method: "post",
|
46
|
+
|
47
|
+
withCredentials: false,
|
48
|
+
|
49
|
+
parallelUploads: 2,
|
50
|
+
|
51
|
+
uploadMultiple: false,
|
52
|
+
|
53
|
+
maxFilesize: 256,
|
54
|
+
|
55
|
+
paramName: "file",
|
56
|
+
|
57
|
+
createImageThumbnails: true,
|
58
|
+
|
59
|
+
maxThumbnailFilesize: 10,
|
60
|
+
|
61
|
+
thumbnailWidth: 120,
|
62
|
+
|
63
|
+
thumbnailHeight: 120,
|
64
|
+
|
65
|
+
filesizeBase: 1000,
|
66
|
+
|
67
|
+
maxFiles: null,
|
68
|
+
|
69
|
+
params: {},
|
70
|
+
|
71
|
+
clickable: true,
|
72
|
+
|
73
|
+
ignoreHiddenFiles: true,
|
74
|
+
|
75
|
+
acceptedFiles: null,
|
76
|
+
|
77
|
+
acceptedMimeTypes: null,
|
78
|
+
|
79
|
+
autoProcessQueue: true,
|
80
|
+
|
81
|
+
autoQueue: true,
|
82
|
+
|
83
|
+
addRemoveLinks: false,
|
84
|
+
|
85
|
+
previewsContainer: null,
|
86
|
+
|
87
|
+
hiddenInputContainer: "body",
|
88
|
+
|
89
|
+
capture: null,
|
90
|
+
|
91
|
+
renameFilename: null,
|
92
|
+
|
93
|
+
dictDefaultMessage: "Drop files here to upload",
|
94
|
+
|
95
|
+
dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.",
|
96
|
+
|
97
|
+
dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.",
|
98
|
+
|
99
|
+
dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",
|
100
|
+
|
101
|
+
dictInvalidFileType: "You can't upload files of this type.",
|
102
|
+
|
103
|
+
dictResponseError: "Server responded with {{statusCode}} code.",
|
104
|
+
|
105
|
+
dictCancelUpload: "Cancel upload",
|
106
|
+
|
107
|
+
dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?",
|
108
|
+
|
109
|
+
dictRemoveFile: "Remove file",
|
110
|
+
|
111
|
+
dictRemoveFileConfirmation: null,
|
112
|
+
|
113
|
+
dictMaxFilesExceeded: "You can not upload any more files.",
|
114
|
+
|
115
|
+
accept: function(file, done) {
|
116
|
+
|
117
|
+
return done();
|
118
|
+
|
119
|
+
},
|
120
|
+
|
121
|
+
init: function() {
|
122
|
+
|
123
|
+
return noop;
|
124
|
+
|
125
|
+
},
|
126
|
+
|
127
|
+
forceFallback: false,
|
128
|
+
|
129
|
+
fallback: function() {
|
130
|
+
|
131
|
+
var child, messageElement, span, _i, _len, _ref;
|
132
|
+
|
133
|
+
this.element.className = "" + this.element.className + " dz-browser-not-supported";
|
134
|
+
|
135
|
+
_ref = this.element.getElementsByTagName("div");
|
136
|
+
|
137
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
138
|
+
|
139
|
+
child = _ref[_i];
|
140
|
+
|
141
|
+
if (/(^| )dz-message($| )/.test(child.className)) {
|
142
|
+
|
143
|
+
messageElement = child;
|
144
|
+
|
145
|
+
child.className = "dz-message";
|
146
|
+
|
147
|
+
continue;
|
148
|
+
|
149
|
+
}
|
150
|
+
|
151
|
+
}
|
152
|
+
|
153
|
+
if (!messageElement) {
|
154
|
+
|
155
|
+
messageElement = Dropzone.createElement("<div class=\"dz-message\"><span></span></div>");
|
156
|
+
|
157
|
+
this.element.appendChild(messageElement);
|
158
|
+
|
159
|
+
}
|
160
|
+
|
161
|
+
span = messageElement.getElementsByTagName("span")[0];
|
162
|
+
|
163
|
+
if (span) {
|
164
|
+
|
165
|
+
if (span.textContent != null) {
|
166
|
+
|
167
|
+
span.textContent = this.options.dictFallbackMessage;
|
168
|
+
|
169
|
+
} else if (span.innerText != null) {
|
170
|
+
|
171
|
+
span.innerText = this.options.dictFallbackMessage;
|
172
|
+
|
173
|
+
}
|
174
|
+
|
175
|
+
}
|
176
|
+
|
177
|
+
return this.element.appendChild(this.getFallbackForm());
|
178
|
+
|
179
|
+
},
|
180
|
+
|
181
|
+
resize: function(file) {
|
182
|
+
|
183
|
+
var info, srcRatio, trgRatio;
|
184
|
+
|
185
|
+
info = {
|
186
|
+
|
187
|
+
srcX: 0,
|
188
|
+
|
189
|
+
srcY: 0,
|
190
|
+
|
191
|
+
srcWidth: file.width,
|
192
|
+
|
193
|
+
srcHeight: file.height
|
194
|
+
|
195
|
+
};
|
196
|
+
|
197
|
+
srcRatio = file.width / file.height;
|
198
|
+
|
199
|
+
info.optWidth = this.options.thumbnailWidth;
|
200
|
+
|
201
|
+
info.optHeight = this.options.thumbnailHeight;
|
202
|
+
|
203
|
+
if ((info.optWidth == null) && (info.optHeight == null)) {
|
204
|
+
|
205
|
+
info.optWidth = info.srcWidth;
|
206
|
+
|
207
|
+
info.optHeight = info.srcHeight;
|
208
|
+
|
209
|
+
} else if (info.optWidth == null) {
|
210
|
+
|
211
|
+
info.optWidth = srcRatio * info.optHeight;
|
212
|
+
|
213
|
+
} else if (info.optHeight == null) {
|
214
|
+
|
215
|
+
info.optHeight = (1 / srcRatio) * info.optWidth;
|
216
|
+
|
217
|
+
}
|
218
|
+
|
219
|
+
trgRatio = info.optWidth / info.optHeight;
|
220
|
+
|
221
|
+
if (file.height < info.optHeight || file.width < info.optWidth) {
|
222
|
+
|
223
|
+
info.trgHeight = info.srcHeight;
|
224
|
+
|
225
|
+
info.trgWidth = info.srcWidth;
|
226
|
+
|
227
|
+
} else {
|
228
|
+
|
229
|
+
if (srcRatio > trgRatio) {
|
230
|
+
|
231
|
+
info.srcHeight = file.height;
|
232
|
+
|
233
|
+
info.srcWidth = info.srcHeight * trgRatio;
|
234
|
+
|
235
|
+
} else {
|
236
|
+
|
237
|
+
info.srcWidth = file.width;
|
238
|
+
|
239
|
+
info.srcHeight = info.srcWidth / trgRatio;
|
240
|
+
|
241
|
+
}
|
242
|
+
|
243
|
+
}
|
244
|
+
|
245
|
+
info.srcX = (file.width - info.srcWidth) / 2;
|
246
|
+
|
247
|
+
info.srcY = (file.height - info.srcHeight) / 2;
|
248
|
+
|
249
|
+
return info;
|
250
|
+
|
251
|
+
},
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
/*
|
256
|
+
|
257
|
+
Those functions register themselves to the events on init and handle all
|
258
|
+
|
259
|
+
the user interface specific stuff. Overwriting them won't break the upload
|
260
|
+
|
261
|
+
but can break the way it's displayed.
|
262
|
+
|
263
|
+
You can overwrite them if you don't like the default behavior. If you just
|
264
|
+
|
265
|
+
want to add an additional event handler, register it on the dropzone object
|
266
|
+
|
267
|
+
and don't overwrite those options.
|
268
|
+
|
269
|
+
*/
|
270
|
+
|
271
|
+
drop: function(e) {
|
272
|
+
|
273
|
+
return this.element.classList.remove("dz-drag-hover");
|
274
|
+
|
275
|
+
},
|
276
|
+
|
277
|
+
dragstart: noop,
|
278
|
+
|
279
|
+
dragend: function(e) {
|
280
|
+
|
281
|
+
return this.element.classList.remove("dz-drag-hover");
|
282
|
+
|
283
|
+
},
|
284
|
+
|
285
|
+
dragenter: function(e) {
|
286
|
+
|
287
|
+
return this.element.classList.add("dz-drag-hover");
|
288
|
+
|
289
|
+
},
|
290
|
+
|
291
|
+
dragover: function(e) {
|
292
|
+
|
293
|
+
return this.element.classList.add("dz-drag-hover");
|
294
|
+
|
295
|
+
},
|
296
|
+
|
297
|
+
dragleave: function(e) {
|
298
|
+
|
299
|
+
return this.element.classList.remove("dz-drag-hover");
|
300
|
+
|
301
|
+
},
|
302
|
+
|
303
|
+
paste: noop,
|
304
|
+
|
305
|
+
reset: function() {
|
306
|
+
|
307
|
+
return this.element.classList.remove("dz-started");
|
308
|
+
|
309
|
+
},
|
310
|
+
|
311
|
+
addedfile: function(file) {
|
312
|
+
|
313
|
+
var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results;
|
314
|
+
|
315
|
+
if (this.element === this.previewsContainer) {
|
316
|
+
|
317
|
+
this.element.classList.add("dz-started");
|
318
|
+
|
319
|
+
}
|
320
|
+
|
321
|
+
if (this.previewsContainer) {
|
322
|
+
|
323
|
+
file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim());
|
324
|
+
|
325
|
+
file.previewTemplate = file.previewElement;
|
326
|
+
|
327
|
+
this.previewsContainer.appendChild(file.previewElement);
|
328
|
+
|
329
|
+
_ref = file.previewElement.querySelectorAll("[data-dz-name]");
|
330
|
+
|
331
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
332
|
+
|
333
|
+
node = _ref[_i];
|
334
|
+
|
335
|
+
node.textContent = this._renameFilename(file.name);
|
336
|
+
|
337
|
+
}
|
338
|
+
|
339
|
+
_ref1 = file.previewElement.querySelectorAll("[data-dz-size]");
|
340
|
+
|
341
|
+
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
342
|
+
|
343
|
+
node = _ref1[_j];
|
344
|
+
|
345
|
+
node.innerHTML = this.filesize(file.size);
|
346
|
+
|
347
|
+
}
|
348
|
+
|
349
|
+
if (this.options.addRemoveLinks) {
|
350
|
+
|
351
|
+
file._removeLink = Dropzone.createElement("<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>" + this.options.dictRemoveFile + "</a>");
|
352
|
+
|
353
|
+
file.previewElement.appendChild(file._removeLink);
|
354
|
+
|
355
|
+
}
|
356
|
+
|
357
|
+
removeFileEvent = (function(_this) {
|
358
|
+
|
359
|
+
return function(e) {
|
360
|
+
|
361
|
+
e.preventDefault();
|
362
|
+
|
363
|
+
e.stopPropagation();
|
364
|
+
|
365
|
+
if (file.status === Dropzone.UPLOADING) {
|
366
|
+
|
367
|
+
return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() {
|
368
|
+
|
369
|
+
return _this.removeFile(file);
|
370
|
+
|
371
|
+
});
|
372
|
+
|
373
|
+
} else {
|
374
|
+
|
375
|
+
if (_this.options.dictRemoveFileConfirmation) {
|
376
|
+
|
377
|
+
return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() {
|
378
|
+
|
379
|
+
return _this.removeFile(file);
|
380
|
+
|
381
|
+
});
|
382
|
+
|
383
|
+
} else {
|
384
|
+
|
385
|
+
return _this.removeFile(file);
|
386
|
+
|
387
|
+
}
|
388
|
+
|
389
|
+
}
|
390
|
+
|
391
|
+
};
|
392
|
+
|
393
|
+
})(this);
|
394
|
+
|
395
|
+
_ref2 = file.previewElement.querySelectorAll("[data-dz-remove]");
|
396
|
+
|
397
|
+
_results = [];
|
398
|
+
|
399
|
+
for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
|
400
|
+
|
401
|
+
removeLink = _ref2[_k];
|
402
|
+
|
403
|
+
_results.push(removeLink.addEventListener("click", removeFileEvent));
|
404
|
+
|
405
|
+
}
|
406
|
+
|
407
|
+
return _results;
|
408
|
+
|
409
|
+
}
|
410
|
+
|
411
|
+
},
|
16
412
|
|
17
413
|
```
|
18
|
-
|
19
|
-
ActionView::MissingTemplate (Missing template shops/create, application/create with {:locale=>[:en], :formats=>[:all], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :arb, :jbuilder]}. Searched in:
|
20
|
-
|
21
|
-
```
|
22
|
-
|
23
|
-
とありますので、テンプレートが見つからないが原因部分かと見ています。
|
24
|
-
|
25
|
-
その対策としてコントローラに挙動を指定しました。
|
26
|
-
|
27
|
-
```Ruby
|
28
|
-
|
29
|
-
format.any
|
30
|
-
|
31
|
-
format.html { render html: @shop }
|
32
|
-
|
33
|
-
format.json { render json: @shop }
|
34
|
-
|
35
|
-
```
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
ですが、上記のエラーは変わらずに出てしまいます。
|
40
|
-
|
41
|
-
恐れ入りますが不足している点を教えていただけませんでしょうか。
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
---
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
以下元ファイルです。
|
52
|
-
|
53
|
-
```
|
54
|
-
|
55
|
-
#Gemをインストール
|
56
|
-
|
57
|
-
gem 'dropzonejs-rails', '~> 0.7.3'
|
58
|
-
|
59
|
-
```
|
60
|
-
|
61
|
-
app/assets/javascripts/application.js
|
62
|
-
|
63
|
-
```JavaScript
|
64
|
-
|
65
|
-
//= require dropzone
|
66
|
-
|
67
|
-
```
|
68
|
-
|
69
|
-
app/assets/stylesheets/application.css
|
70
|
-
|
71
|
-
```CSS
|
72
|
-
|
73
|
-
*= require dropzone/basic
|
74
|
-
|
75
|
-
*= require dropzone/dropzone
|
76
|
-
|
77
|
-
```
|
78
|
-
|
79
|
-
app/models/shop.rb
|
80
|
-
|
81
|
-
```Ruby
|
82
|
-
|
83
|
-
class Shop < ActiveRecord::Base
|
84
|
-
|
85
|
-
#attachmentを子に指定します
|
86
|
-
|
87
|
-
has_many :attachments, dependent: :destroy
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
```
|
92
|
-
|
93
|
-
app/models/attachment.rb
|
94
|
-
|
95
|
-
```Ruby
|
96
|
-
|
97
|
-
class Attachment < ActiveRecord::Base
|
98
|
-
|
99
|
-
#Shopを親モデルに指定します
|
100
|
-
|
101
|
-
belongs_to :shop
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
```
|
106
|
-
|
107
|
-
app/controllers/shops_controller.rb
|
108
|
-
|
109
|
-
```Ruby
|
110
|
-
|
111
|
-
class ShopsController < ApplicationController
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def new
|
116
|
-
|
117
|
-
@shop = Shop.new
|
118
|
-
|
119
|
-
#attachmentをビルドします
|
120
|
-
|
121
|
-
@shop.attachments.build
|
122
|
-
|
123
|
-
respond_with(@shop)
|
124
|
-
|
125
|
-
end
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
def create
|
130
|
-
|
131
|
-
@shop = current_user.shops.build(shop_params)
|
132
|
-
|
133
|
-
respond_to do |format|
|
134
|
-
|
135
|
-
if @shop.save
|
136
|
-
|
137
|
-
format.any
|
138
|
-
|
139
|
-
format.html { render html: @shop }
|
140
|
-
|
141
|
-
format.json { render json: @shop }
|
142
|
-
|
143
|
-
else
|
144
|
-
|
145
|
-
format.html { render action: 'new' }
|
146
|
-
|
147
|
-
end
|
148
|
-
|
149
|
-
end
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
def shop_params
|
154
|
-
|
155
|
-
params.require(:shop).permit(:name,
|
156
|
-
|
157
|
-
attachments_attributes: [:id, :image, :_destroy]
|
158
|
-
|
159
|
-
)
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
end
|
164
|
-
|
165
|
-
```
|
166
|
-
|
167
|
-
app/views/documents/new.html.erb
|
168
|
-
|
169
|
-
```
|
170
|
-
|
171
|
-
<h1>New shop</h1>
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
<%= render 'form' %>
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
<%= link_to 'Back', shops_path %>
|
180
|
-
|
181
|
-
```
|
182
|
-
|
183
|
-
app/views/documents/_form.html.erb
|
184
|
-
|
185
|
-
```
|
186
|
-
|
187
|
-
<%= form_for(@shop, html: {multipart: true, class: 'dropzone', id: 'my-awesome-dropzone', remote: true}) do |f| %>
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
<div class="row">
|
192
|
-
|
193
|
-
<div class="col-md-10 col-md-offset-1">
|
194
|
-
|
195
|
-
<%= render partial: 'upload_photos_form', locals: { shop_id: @shop.id } %>
|
196
|
-
|
197
|
-
</div>
|
198
|
-
|
199
|
-
</div>
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
<%= f.label :name %>
|
204
|
-
|
205
|
-
<%= f.text_field :name, :size => '10' %><br>
|
206
|
-
|
207
|
-
<% end %>
|
208
|
-
|
209
|
-
```
|
210
|
-
|
211
|
-
app/views/documents/_upload_photos_form.html.erb
|
212
|
-
|
213
|
-
```
|
214
|
-
|
215
|
-
<div class="fallback">
|
216
|
-
|
217
|
-
<%= file_field_tag('shop[attachments]') %>
|
218
|
-
|
219
|
-
</div>
|
220
|
-
|
221
|
-
```
|
222
|
-
|
223
|
-
コンソール
|
224
|
-
|
225
|
-
```
|
226
|
-
|
227
|
-
Started GET "/shops/new" for ::1 at 2016-12-30 10:46:45 +0700
|
228
|
-
|
229
|
-
Processing by ShopsController#new as HTML
|
230
|
-
|
231
|
-
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
|
232
|
-
|
233
|
-
Rendered shops/_upload_photos_form.html.erb (0.1ms)
|
234
|
-
|
235
|
-
Rendered shops/_form.html.erb (13.9ms)
|
236
|
-
|
237
|
-
Rendered shops/new.html.erb within layouts/application (15.5ms)
|
238
|
-
|
239
|
-
Completed 200 OK in 353ms (Views: 348.5ms | ActiveRecord: 0.1ms)
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
Started POST "/shops" for ::1 at 2016-12-30 10:46:58 +0700
|
246
|
-
|
247
|
-
Processing by ShopsController#create as JSON
|
248
|
-
|
249
|
-
Parameters: {"utf8"=>"✓", "shop"=>{"name"=>"テスト"}, "commit"=>"登録", @original_filename="253_main.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"file\"; filename=\"253_main.jpg\"\r\nContent-Type: image/jpeg\r\n">}
|
250
|
-
|
251
|
-
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
|
252
|
-
|
253
|
-
Unpermitted parameter: name_en
|
254
|
-
|
255
|
-
(0.2ms) begin transaction
|
256
|
-
|
257
|
-
SQL (0.7ms) INSERT INTO "shops" ("name", "user_id", "created_at", "updated_at") VALUES (?,?,?,?) [["name", "テスト"], ["user_id", 1], ["created_at", "2016-12-30 03:46:58.203299"], ["updated_at", "2016-12-30 03:46:58.203299"]]
|
258
|
-
|
259
|
-
(1.0ms) commit transaction
|
260
|
-
|
261
|
-
Completed 200 OK in 55ms (Views: 2.1ms | ActiveRecord: 6.1ms)
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
Started POST "/shops" for ::1 at 2016-12-30 10:47:00 +0700
|
268
|
-
|
269
|
-
Processing by ShopsController#create as JS
|
270
|
-
|
271
|
-
Parameters: {"utf8"=>"✓", "shop"=>{"name"=>"テスト"}, "commit"=>"登録"}
|
272
|
-
|
273
|
-
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
|
274
|
-
|
275
|
-
Unpermitted parameter: name_en
|
276
|
-
|
277
|
-
(0.1ms) begin transaction
|
278
|
-
|
279
|
-
SQL (0.5ms) INSERT INTO "shops" ("name", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "テスト"], ["user_id", 1], ["created_at", "2016-12-30 03:47:00.724796"], ["updated_at", "2016-12-30 03:47:00.724796"]]
|
280
|
-
|
281
|
-
(2.3ms) commit transaction
|
282
|
-
|
283
|
-
Completed 500 Internal Server Error in 51ms (ActiveRecord: 3.3ms)
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
ActionView::MissingTemplate (Missing template shops/create, application/create with {:locale=>[:en], :formats=>[:all], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :arb, :jbuilder]}. Searched in:
|
288
|
-
|
289
|
-
* "/Users/yousukesatou/projects/HerokuApp/app/views"
|
290
|
-
|
291
|
-
* "/Users/yousukesatou/projects/HerokuApp/vendor/bundle/ruby/2.3.0/bundler/gems/active_admin-4f494073c6c0/app/views"
|
292
|
-
|
293
|
-
* "/Users/yousukesatou/projects/HerokuApp/vendor/bundle/ruby/2.3.0/gems/twitter-bootstrap-rails-3.2.2/app/views"
|
294
|
-
|
295
|
-
* "/Users/yousukesatou/projects/HerokuApp/vendor/bundle/ruby/2.3.0/gems/devise-3.5.1/app/views"
|
296
|
-
|
297
|
-
* "/Users/yousukesatou/projects/HerokuApp/vendor/bundle/ruby/2.3.0/gems/kaminari-0.15.1/app/views"
|
298
|
-
|
299
|
-
):
|
300
|
-
|
301
|
-
app/controllers/shops_controller.rb:60:in `create'
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
Rendered vendor/bundle/ruby/2.3.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb (0.4ms)
|
308
|
-
|
309
|
-
```
|