populate-me 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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