populate-me 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a92596c5ed11fb3d67f18b48fdc1011ecf423f26390c2f71c3587c3c2cae93cf
4
- data.tar.gz: 70e4e6572ed7e98ace3444ee6c6c18ff937b0ed72aa35b80a5c5d2ad99865d91
3
+ metadata.gz: ab01b0340f42771cfda2e496e4dee387c3e098ee2757ee374f5fb9a95fda48b0
4
+ data.tar.gz: b74800276c349db0d4dcc74f46f36d5b6fb6a46cb4d727c5dbfb9e46d35d0aa4
5
5
  SHA512:
6
- metadata.gz: cd01c21a0a760cb1691453ca86f22a75cbabd423134bd25c699fbd0ea96ef6a1845a88d130c61f62fcec9a2f77fb527759152307fb0fb762f0778a2f657bbe3d
7
- data.tar.gz: fc764eb27f6c4dd76bde7b952432dc0a647872a061f564f3ac9104511b7baecd0a67866b9777726e329619cd6e1b3663e107f3e774d908e794cfe100592c44b4
6
+ metadata.gz: 7621910d4fec3622e2bad85742c0877fbe6bafdf9dcb28f804e40d5ae255b362de66cdf6d978a1d49fa27874527705d2cec80146e2624f71ac4783a4bd8b5554
7
+ data.tar.gz: c65c5db1c8361cb50ada68e63cc246325590a0e8ca00a2f6a3e90e047f73cbc4a0072293ab52a60317a9b0710e96d7439ac3efd1860d3d134edd848ae144670f
@@ -214,6 +214,14 @@ fieldset {
214
214
  color: #dc322f;
215
215
  }
216
216
 
217
+ .error {
218
+ color: #dc322f;
219
+ }
220
+
221
+ .batch-upload-report {
222
+ margin: 20px;
223
+ }
224
+
217
225
  /* asmSelect */
218
226
 
219
227
  .asmListItem {
@@ -117,13 +117,17 @@ PopulateMe.scroll_to = function(el, column) {
117
117
  // Mark errors for report after form validation.
118
118
  // It adds the .invalid class to invalid fields,
119
119
  // and adds the report after the label of the field.
120
- PopulateMe.mark_errors = function(context,report) {
120
+ PopulateMe.mark_errors = function(context, report, isSubcontext) {
121
+ if (!isSubcontext) {
122
+ $('.invalid', context).removeClass('invalid');
123
+ $('.errors', context).remove();
124
+ }
121
125
  $.each(report,function(k,v) {
122
126
  var field = context.find('> [data-field-name='+k+']:first');
123
127
  if (field.is('fieldset')) {
124
128
  $.each(v, function(index,subreport) {
125
129
  var subcontext = field.find('> .nested-documents > li:nth('+index+')');
126
- PopulateMe.mark_errors(subcontext, subreport);
130
+ PopulateMe.mark_errors(subcontext, subreport, true);
127
131
  });
128
132
  } else {
129
133
  field.addClass('invalid');
@@ -133,6 +137,54 @@ PopulateMe.mark_errors = function(context,report) {
133
137
  });
134
138
  };
135
139
 
140
+ // Navigate columns back
141
+ PopulateMe.navigate_back = function(target_id) {
142
+ // !!! Careful, it only works if we call this from the last column
143
+ var reloader = PopulateMe.finder.find('> li:nth-last-child(3) .selected');
144
+ if (reloader.size() > 0) {
145
+ var reloadee = PopulateMe.finder.find('> li:nth-last-child(2)');
146
+ var current_search = PopulateMe.copy_column_search(reloadee);
147
+ reloader.trigger('click.columnav',[function(cb_object) {
148
+ PopulateMe.restore_column_search(cb_object.column, current_search);
149
+ if (target_id) {
150
+ var target = $('[data-id=' + target_id + ']', cb_object.column);
151
+ if (target.size() > 0) {
152
+ PopulateMe.scroll_to(target, cb_object.column);
153
+ }
154
+ }
155
+ }]);
156
+ } else {
157
+ PopulateMe.finder.trigger('pop.columnav');
158
+ }
159
+ }
160
+
161
+ PopulateMe.fieldMaxSize = function(field) {
162
+ var maxSizeData = field.data('max-size');
163
+ if (!maxSizeData) {
164
+ return null;
165
+ }
166
+ return field.data('max-size');
167
+ };
168
+
169
+ PopulateMe.fieldHasFileTooBig = function(field, maybeFile) {
170
+ var file = maybeFile || field[0].files[0];
171
+ if (!file) {
172
+ return false;
173
+ }
174
+ var maxSize = PopulateMe.fieldMaxSize(field);
175
+ if (!maxSize) {
176
+ return false;
177
+ }
178
+ return file.size > maxSize;
179
+ };
180
+
181
+ PopulateMe.fileTooBigErrorMessage = function(fname, max_size) {
182
+ if (typeof max_size != 'number') {
183
+ max_size = PopulateMe.fieldMaxSize(max_size);
184
+ }
185
+ return 'File too big: ' + fname + ' should be less than ' + PopulateMe.display_file_size(max_size) + '.';
186
+ };
187
+
136
188
  // JS Validations
137
189
  // This adds validations that could happen before sending
138
190
  // anything to the server and that cannot be done with
@@ -146,18 +198,13 @@ PopulateMe.jsValidationsPassed = function(context) {
146
198
  var max_size_fields = $('input[type=file][data-max-size]', context);
147
199
  max_size_fields.each(function() {
148
200
  var field = $(this);
149
- var max_size = parseInt(field.data('max-size'));
150
- if (field[0].files[0]) {
151
- var fsize = field[0].files[0].size;
152
- var fname = field[0].files[0].name;
153
- if (fsize>max_size) {
154
- alert('File too big: '+fname+' should be less than '+PopulateMe.display_file_size(max_size)+'.');
155
- throw "Validation error";
156
- }
201
+ if (PopulateMe.fieldHasFileTooBig(field)) {
202
+ alert(PopulateMe.fileTooBigErrorMessage(file.name, field));
203
+ throw "Validation error";
157
204
  }
158
205
  });
159
206
  } catch(e) {
160
- if (e==='Validation error') {
207
+ if (e==='Validation error') {
161
208
  return false;
162
209
  } else {
163
210
  throw(e);
@@ -167,7 +214,7 @@ PopulateMe.jsValidationsPassed = function(context) {
167
214
  return true;
168
215
  };
169
216
 
170
- // Init column
217
+ // Init column
171
218
  // Bind events and init things that need to happen when
172
219
  // a new column is added to the finder.
173
220
  // The callback `custom_init_column` is also called at the end
@@ -303,48 +350,111 @@ $(function() {
303
350
  });
304
351
 
305
352
  // Ajax form
353
+
354
+ var ajaxSubmitSuccess = function(res) {
355
+ if (res.success == true) {
356
+ PopulateMe.navigate_back(res.data._id);
357
+ }
358
+ };
359
+
360
+ var ajaxSubmitError = function(xhr, ctx) {
361
+ res = xhr.responseJSON;
362
+ if (res.success == false) {
363
+ PopulateMe.mark_errors(ctx, res.data);
364
+ PopulateMe.scroll_to(ctx.find('.invalid:first'));
365
+ $('input[type=submit]', ctx).show();
366
+ ctx.fadeTo("fast", 1);
367
+ }
368
+ };
369
+
306
370
  $('body').on('submit','form.admin-post, form.admin-put', function(e) {
307
371
  e.preventDefault();
308
372
  var self = $(this);
309
- if (PopulateMe.jsValidationsPassed(self)) {
310
-
311
- var submit_button = $('input[type=submit]',self);
312
- submit_button.hide();
313
- $.ajax({
314
- url: self.attr('action'),
315
- type: (self.is('.admin-put') ? 'put' : 'post'),
316
- data: new FormData(this),
317
- processData: false,
318
- contentType: false,
319
- success: function(res) {
320
- if (res.success==true) {
321
- var reloader = PopulateMe.finder.find('> li:nth-last-child(3) .selected');
322
- if (reloader.size()>0) {
323
- var reloadee = PopulateMe.finder.find('> li:nth-last-child(2)');
324
- var current_search = PopulateMe.copy_column_search(reloadee);
325
- reloader.trigger('click.columnav',[function(cb_object) {
326
- var target = $('[data-id='+res.data._id+']', cb_object.column);
327
- if (target.size()>0) {
328
- PopulateMe.restore_column_search(cb_object.column, current_search);
329
- PopulateMe.scroll_to(target, cb_object.column);
330
- }
331
- }]);
332
- } else {
333
- PopulateMe.finder.trigger('pop.columnav');
334
- }
373
+ self.fadeTo("fast", 0.3);
374
+ var submit_button = $('input[type=submit]', self);
375
+ var formData = new FormData(this);
376
+ var batchField = self.data('batch-field');
377
+ var batchFieldEl = $("input[name='" + batchField + "']");
378
+ var isBatchUpload = self.is('.admin-post') &&
379
+ batchField &&
380
+ formData.getAll(batchField).length > 1;
381
+
382
+ if (isBatchUpload) {
383
+
384
+ if (confirm("You've selected multiple images. This will create an entry for each image. It may take a while to upload everything. Do you wish to proceed ?")) {
385
+ submit_button.hide();
386
+ var files = formData.getAll(batchField);
387
+ var report = $("<div class='batch-upload-report'></div>").insertAfter(self);
388
+ var latestSuccessfulId;
389
+ var errorSize = 0;
390
+ var successSize = 0;
391
+ var addCloseButtonIfDone = function() {
392
+ if (errorSize + successSize >= files.length) {
393
+ var closeButton = $("<button type='button'>Close</button>").click(function(e) {
394
+ e.preventDefault();
395
+ PopulateMe.navigate_back();
396
+ });
397
+ report.append(closeButton);
335
398
  }
336
- },
337
- error: function(xhr) {
338
- res = xhr.responseJSON;
339
- if (res.success==false) {
340
- $('.invalid',self).removeClass('invalid');
341
- $('.errors',self).remove();
342
- PopulateMe.mark_errors(self,res.data);
343
- PopulateMe.scroll_to(self.find('.invalid:first'));
344
- submit_button.show();
399
+ };
400
+ for (var i = 0; i < files.length; i++) {
401
+ var file = files[i];
402
+ if (PopulateMe.fieldHasFileTooBig(batchFieldEl, file)) {
403
+ errorSize += 1;
404
+ var msg = PopulateMe.fileTooBigErrorMessage(file.name, batchFieldEl);
405
+ report.append("<div class='error'>" + msg + "</div>");
406
+ addCloseButtonIfDone();
407
+ } else {
408
+ formData.set(batchField, file, file.name);
409
+ $.ajax({
410
+ url: self.attr('action'),
411
+ type: (self.is('.admin-put') ? 'put' : 'post'), // Always post ?
412
+ data: formData,
413
+ successData: {filename: file.name, index: i+1},
414
+ processData: false,
415
+ contentType: false,
416
+ success: function(res, textStatus, xhr) {
417
+ successSize += 1;
418
+ if (res.success == true) {
419
+ latestSuccessfulId = res.data._id;
420
+ report.append("<div>Uploaded: " + this.successData.filename + "</div>");
421
+ }
422
+ if (successSize >= files.length) {
423
+ PopulateMe.navigate_back(res.data._id);
424
+ } else {
425
+ addCloseButtonIfDone();
426
+ }
427
+ },
428
+ error: function(xhr, textStatus, errorThrown) {
429
+ errorSize += 1;
430
+ report.append("<div class='error'>Error: " + this.successData.filename + "</div>");
431
+ res = xhr.responseJSON;
432
+ if (res.success == false) {
433
+ PopulateMe.mark_errors(self, res.data);
434
+ }
435
+ addCloseButtonIfDone();
436
+ }
437
+ });
345
438
  }
346
439
  }
347
- });
440
+ }
441
+
442
+ } else {
443
+
444
+ if (PopulateMe.jsValidationsPassed(self)) {
445
+ submit_button.hide();
446
+ $.ajax({
447
+ url: self.attr('action'),
448
+ type: (self.is('.admin-put') ? 'put' : 'post'),
449
+ data: formData,
450
+ processData: false,
451
+ contentType: false,
452
+ success: ajaxSubmitSuccess,
453
+ error: function(xhr, textStatus, errorThrown) {
454
+ ajaxSubmitError(xhr, self);
455
+ }
456
+ });
457
+ }
348
458
 
349
459
  }
350
460
  });
@@ -4,6 +4,7 @@
4
4
  <head>
5
5
  <meta charset="utf-8" />
6
6
  <title><%= settings.meta_title %></title>
7
+ <link href="<%= request.script_name %>/__assets__/img/favicon.png" rel="icon" type="image/png">
7
8
  <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/jquery-ui.min.css" type="text/css" media='screen' />
8
9
  <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/asmselect.css" type="text/css" media='screen' />
9
10
  <link rel="stylesheet" href="<%= request.script_name %>/__assets__/css/main.css" type="text/css" media='screen' />
@@ -79,7 +80,7 @@
79
80
  {{#polymorphic_type}}
80
81
  <p>({{polymorphic_type}})</p>
81
82
  {{/polymorphic_type}}
82
- <form action="<%= request.script_name %>/api/{{admin_url}}" method="POST" accept-charset="utf-8" class='admin-{{#is_new}}post{{/is_new}}{{^is_new}}put{{/is_new}}'>
83
+ <form action="<%= request.script_name %>/api/{{admin_url}}" method="POST" accept-charset="utf-8" class='admin-{{#is_new}}post{{/is_new}}{{^is_new}}put{{/is_new}}' {{#batch_field}}{{#is_new}}data-batch-field="{{batch_field}}"{{/is_new}}{{/batch_field}}>
83
84
  {{#custom_partial_or_default}}template_form_fields{{/custom_partial_or_default}}
84
85
  {{^is_new}}
85
86
  <input type="hidden" name="_method" value="PUT" />
@@ -156,7 +157,7 @@
156
157
  <button class='attachment-deleter'>x</button>
157
158
  <br />
158
159
  {{/url}}
159
- <input type='file' name='{{input_name}}' {{#max_size}}data-max-size='{{max_size}}'{{/max_size}} {{{build_input_atrributes}}} />
160
+ <input type='file' name='{{input_name}}' {{#multiple}}multiple{{/multiple}} {{#max_size}}data-max-size='{{max_size}}'{{/max_size}} {{{build_input_atrributes}}} />
160
161
  </script>
161
162
 
162
163
  <script id="template-list-field" type="x-tmpl-mustache">
@@ -27,7 +27,7 @@ module PopulateMe
27
27
  # It can be used on its own but it keeps everything
28
28
  # in memory. Which means it is only for tests and conceptual
29
29
  # understanding.
30
-
30
+
31
31
  include DocumentMixins::Typecasting
32
32
  include DocumentMixins::Outcasting
33
33
  include DocumentMixins::Schema
@@ -48,12 +48,16 @@ module PopulateMe
48
48
  end
49
49
  page_title = self.new? ? "New #{self.class.to_s_short}" : self.to_s
50
50
  # page_title << " (#{self.polymorphic_type})" if self.class.polymorphic?
51
+ batch_field_item = items.find do |item|
52
+ item[:field_name] == self.class.batch_field
53
+ end
51
54
  {
52
55
  template: "template#{'_nested' if o[:nested]}_form",
53
56
  page_title: page_title,
54
57
  admin_url: self.to_admin_url,
55
58
  is_new: self.new?,
56
59
  polymorphic_type: self.class.polymorphic? ? self.polymorphic_type : nil,
60
+ batch_field: (not self.new? or batch_field_item.nil?) ? nil : batch_field_item[:input_name],
57
61
  fields: items
58
62
  }
59
63
  end
@@ -74,6 +74,7 @@ module PopulateMe
74
74
  def outcast_attachment field, item, o={}
75
75
  item = item.dup
76
76
  item[:url] = self.attachment(field).url
77
+ item[:multiple] = (self.new? and self.class.batch_field == field)
77
78
  item
78
79
  end
79
80
 
@@ -118,14 +118,21 @@ module PopulateMe
118
118
  def label sym # sets the label_field
119
119
  @label_field = sym.to_sym
120
120
  end
121
-
121
+
122
122
  def label_field
123
123
  return @label_field if self.fields.empty?
124
- @label_field || self.fields.find do |k,v|
124
+ @label_field || self.fields.find do |k,v|
125
125
  not [:id,:polymorphic_type].include?(v[:type])
126
126
  end[0]
127
127
  end
128
128
 
129
+ def batch_on_field sym # sets the batch_field
130
+ @batch_field = sym.to_sym
131
+ end
132
+ def batch_field
133
+ @batch_field
134
+ end
135
+
129
136
  def sort_by f, direction=:asc
130
137
  raise(ArgumentError) unless [:asc,:desc].include? direction
131
138
  raise(ArgumentError) unless self.new.respond_to? f
@@ -1,4 +1,4 @@
1
1
  module PopulateMe
2
- VERSION = '0.16.0'
2
+ VERSION = '0.17.0'
3
3
  end
4
4
 
@@ -178,7 +178,7 @@ describe PopulateMe::Document, 'AdminAdapter' do
178
178
  describe '#to_admin_form' do
179
179
  class PolyForm < PopulateMe::Document
180
180
  field :name
181
- field :image, only_for: 'Image'
181
+ field :image, only_for: 'Image', type: :attachment, class_name: PopulateMe::Attachment
182
182
  field :title, only_for: 'Article'
183
183
  field :content, only_for: 'Article'
184
184
  field :position
@@ -215,6 +215,29 @@ describe PopulateMe::Document, 'AdminAdapter' do
215
215
  form = obj.to_admin_form
216
216
  assert_nil form[:polymorphic_type]
217
217
  end
218
+
219
+ class WithBatchField < PopulateMe::Document
220
+ field :name
221
+ field :thumbnail, type: :attachment, class_name: PopulateMe::Attachment
222
+ batch_on_field :thumbnail
223
+ end
224
+ it 'Sets :batch_field if there is one' do
225
+ obj = PolyForm.new
226
+ form = obj.to_admin_form
227
+ assert_nil form[:batch_field]
228
+ obj = NotPolyForm.new
229
+ form = obj.to_admin_form
230
+ assert_nil form[:batch_field]
231
+ obj = WithBatchField.new
232
+ form = obj.to_admin_form
233
+ assert_equal 'data[thumbnail]', form[:batch_field]
234
+ end
235
+ it 'Does not add a batch field when updating' do
236
+ obj = WithBatchField.new
237
+ obj._is_new = false
238
+ form = obj.to_admin_form
239
+ assert_nil form[:batch_field]
240
+ end
218
241
  end
219
242
 
220
243
  end
@@ -20,10 +20,13 @@ class Outcasted < PopulateMe::Document
20
20
  field :tags, type: :select, select_options: ['art','sport','science'], multiple: true
21
21
  field :related_properties, type: :select, select_options: ['prop1','prop2','prop3'], multiple: true
22
22
  field :pdf, type: :attachment
23
+ field :image, type: :attachment
23
24
  field :authors, type: :list
24
25
  field :weirdo, type: :strange
25
26
  field :price, type: :price
26
27
 
28
+ batch_on_field :image
29
+
27
30
  def get_category_autocomplete_list
28
31
  ['Horse', 'Bear']
29
32
  end
@@ -42,6 +45,11 @@ class Outcasted::Author < PopulateMe::Document
42
45
  field :name
43
46
  end
44
47
 
48
+ class OutcastedNoBatchField < PopulateMe::Document
49
+ set :default_attachment_class, PopulateMe::Attachment
50
+ field :image, type: :attachment
51
+ end
52
+
45
53
  describe PopulateMe::Document, 'Outcasting' do
46
54
 
47
55
  parallelize_me!
@@ -276,6 +284,35 @@ describe PopulateMe::Document, 'Outcasting' do
276
284
  assert_equal outcasted.attachment(:pdf).url, output[:url]
277
285
  end
278
286
 
287
+ it 'Sets multiple if field is the batch field and document is new' do
288
+ original = Outcasted.fields[:image]
289
+ outcasted = Outcasted.new
290
+ output = outcasted.outcast(:image, original, {input_name_prefix: 'data'})
291
+ assert output[:multiple]
292
+ end
293
+
294
+ it 'Does not set multiple if there is no batch field' do
295
+ original = OutcastedNoBatchField.fields[:image]
296
+ outcasted = OutcastedNoBatchField.new
297
+ output = outcasted.outcast(:image, original, {input_name_prefix: 'data'})
298
+ refute output[:multiple]
299
+ end
300
+
301
+ it 'Does not set multiple if document is not new' do
302
+ original = Outcasted.fields[:image]
303
+ outcasted = Outcasted.new
304
+ outcasted._is_new = false
305
+ output = outcasted.outcast(:image, original, {input_name_prefix: 'data'})
306
+ refute output[:multiple]
307
+ end
308
+
309
+ it 'Does not set multiple if field not the batch field' do
310
+ original = Outcasted.fields[:pdf]
311
+ outcasted = Outcasted.new
312
+ output = outcasted.outcast(:pdf, original, {input_name_prefix: 'data'})
313
+ refute output[:multiple]
314
+ end
315
+
279
316
  end
280
317
 
281
318
  end
@@ -6,6 +6,26 @@ describe PopulateMe::Document, 'Schema' do
6
6
 
7
7
  parallelize_me!
8
8
 
9
+ describe "Batch field" do
10
+
11
+ class BatchFieldSet < PopulateMe::Document
12
+ field :name
13
+ field :image
14
+ batch_on_field :image
15
+ end
16
+
17
+ class NotBatchFieldSet < PopulateMe::Document
18
+ field :name
19
+ field :image
20
+ end
21
+
22
+ it 'Sets batch field correctly' do
23
+ assert_equal :image, BatchFieldSet.batch_field
24
+ assert_nil NotBatchFieldSet.batch_field
25
+ end
26
+
27
+ end
28
+
9
29
  describe "Relationships" do
10
30
 
11
31
  class Relative < PopulateMe::Document
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: populate-me
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mickael Riga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-07 00:00:00.000000000 Z
11
+ date: 2022-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: web-utils
@@ -198,6 +198,7 @@ files:
198
198
  - lib/populate_me/admin/__assets__/css/asmselect.css
199
199
  - lib/populate_me/admin/__assets__/css/jquery-ui.min.css
200
200
  - lib/populate_me/admin/__assets__/css/main.css
201
+ - lib/populate_me/admin/__assets__/img/favicon.png
201
202
  - lib/populate_me/admin/__assets__/img/file.png
202
203
  - lib/populate_me/admin/__assets__/img/help/children.png
203
204
  - lib/populate_me/admin/__assets__/img/help/create.png