populate-me 0.6.1 → 0.6.2

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
  SHA1:
3
- metadata.gz: 4115111c6e8b75beee003063ea35e984d6442b41
4
- data.tar.gz: a180faa064cc0c2e8ade0c106727f1b85af9f40c
3
+ metadata.gz: dbda9949e894e8d1c77c8ad65e0bfdd2bf037873
4
+ data.tar.gz: e7fcb6b304893759a53af5ae065a6674bf2b07bb
5
5
  SHA512:
6
- metadata.gz: c6f25cc60ad3128a6d33fd3b13d0a96fbc221ecc616ef6c272851028a949843c6cf9e2a6a7fc1e9a7ea1c9a4e26b9e2c80188f40a1821220fb0ac6c575b265bc
7
- data.tar.gz: 1e10eb2ceb1de0875817a1d0568f20d76ea70cb9bf64ec4c05e543a9a861ac32a37cb12d61cbb4082de6189d7242bb380165822a839e886b00667dbfb0cbe051
6
+ metadata.gz: e784e1176489b34f3f19331bef9d1800aca5d086eb0f481ea5002e45dfba8d36787b97b8b07c8d7ba2bd3c19f0719b06eb09d810531ce87f92d49d750f913ae5
7
+ data.tar.gz: 6a19f005cb13878124042a563eb621f596cf83d1a3fa063a62e99a5b75366a4a05665033ed8168a1f54fd521db8a8500b92f064e98915d656a3391d28e7f5e27
data/README.md CHANGED
@@ -21,6 +21,7 @@ Table of contents
21
21
  - [Single Documents](#single-documents)
22
22
  - [Mongo documents](#mongo-documents)
23
23
  - [Admin](#admin)
24
+ - [Customize Admin](#customize-admin)
24
25
  - [API](#api)
25
26
 
26
27
  Documents
@@ -403,6 +404,109 @@ probably for [single documents](#single-documents) because they are not part of
403
404
  a list. The ID would then be litterally `unique`, or whatever ID you declared
404
405
  instead.
405
406
 
407
+ ### Customize Admin
408
+
409
+ You can customize the admin with a few settings.
410
+ The main ones are for adding CSS and javascript.
411
+ There are 2 settings for this: `:custom_css_url` and `:custom_js_url`.
412
+
413
+ ```ruby
414
+ # lib/admin.rb
415
+ require "populate_me/admin"
416
+
417
+ class Admin < PopulateMe::Admin
418
+
419
+ set :custom_css_url, '/css/admin.css'
420
+ set :custom_js_url, '/js/admin.js'
421
+
422
+ set :root, ::File.expand_path('../..', __FILE__)
423
+
424
+ set :menu, [
425
+ ['Settings', '/admin/form/settings/unique'],
426
+ ['Articles', '/admin/list/article'],
427
+ ['Staff', [
428
+ ['Designers', '/admin/list/staff-member?filter[job]=Designer'],
429
+ ['Developers', '/admin/list/staff-member?filter[job]=Developer'],
430
+ ]]
431
+ ]
432
+
433
+ end
434
+ ```
435
+
436
+ Inside the javascript file, you can use many functions and variables that
437
+ are under the `PopulateMe` namespace. See source code to know more about it.
438
+ Some are callbacks like `PopulateMe.custom_init_column` which allows you to
439
+ bind events when a column was created.
440
+
441
+ ```javascript
442
+ # /js/admin.js
443
+
444
+ $(function() {
445
+
446
+ $('body').bind('change', 'select.special', function(event) {
447
+ alert('Changed!');
448
+ });
449
+
450
+ PopulateMe.custom_init_column = function(column) {
451
+ $('select.special', column).css({color: 'orange'});
452
+ }
453
+
454
+ });
455
+ ```
456
+
457
+ The other thing you might want to do is adding mustache templates.
458
+ You can do this with the setting `:custom_templates_view`.
459
+
460
+ ```ruby
461
+ # lib/admin.rb
462
+ require "populate_me/admin"
463
+
464
+ class Admin < PopulateMe::Admin
465
+
466
+ set :custom_templates_view, :custom_templates
467
+
468
+ # ...
469
+
470
+ end
471
+ ```
472
+
473
+ Let's say we want to be able to set the size of the preview for attachments,
474
+ as opposed to the default value of 150. We would put this in the view:
475
+
476
+ ```eruby
477
+ <script id="template-attachment-field-custom" type="x-tmpl-mustache">
478
+ {{#url}}
479
+ <img src='{{url}}{{cache_buster}}' alt='Preview' width='{{attachment_preview_width}}' />
480
+ <button class='attachment-deleter'>x</button>
481
+ <br />
482
+ {{/url}}
483
+ <input type='file' name='{{input_name}}' {{#max_size}}data-max-size='{{max_size}}'{{/max_size}} {{{build_input_atrributes}}} />
484
+ </script>
485
+ ```
486
+
487
+ This is the default template except we've replace `150` with the mustache
488
+ variable `attachment_preview_width`. Everything that you set on the schema
489
+ is available in the template, so you can set both the custom template name
490
+ and the width variable in the hash passed to `field` when doing your schema.
491
+ The template name is the ID of the script tag.
492
+
493
+ ```ruby
494
+ # /lib/blog_post.rb
495
+ require 'populate_me/document'
496
+
497
+ class BlogPost < PopulateMe::Document
498
+
499
+ field :title
500
+ field :image, type: :attachment, custom_template: 'template-attachment-field-custom', attachment_preview_width: 200, variations: [
501
+ PopulateMe::Variation.new_image_magick_job(:thumb, :gif, "-resize '300x'")
502
+ ]
503
+
504
+ # ...
505
+
506
+ end
507
+ ```
508
+
509
+
406
510
  API
407
511
  ---
408
512
 
data/example/config.ru CHANGED
@@ -30,7 +30,7 @@ PopulateMe::Document.set :default_attachment_class, PopulateMe::Attachment
30
30
 
31
31
  class BlogPost < PopulateMe::Document
32
32
  field :title, required: true
33
- field :thumbnail, type: :attachment, max_size: 600000, variations: [
33
+ field :thumbnail, type: :attachment, max_size: 600*1024, variations: [
34
34
  PopulateMe::Variation.new_image_magick_job(:populate_me_thumb, :jpg, "-resize '400x225^' -gravity center -extent 400x225")
35
35
  ]
36
36
  field :content, type: :text
@@ -1,81 +1,216 @@
1
- var init_template = function(obj_or_id) {
1
+ // Namespace
2
+ var PopulateMe = {};
3
+
4
+ // Returns a Mustache template from either
5
+ // the dom element itself, or the id of the dom element.
6
+ PopulateMe.init_template = function(obj_or_id) {
2
7
  var obj = (typeof obj_or_id == 'object') ? obj_or_id : $(obj_or_id);
3
8
  var template = obj.html();
4
9
  Mustache.parse(template);
5
10
  return template;
6
11
  };
7
12
 
8
- $(function() {
13
+ // Output a legible version of the size in bytes.
14
+ // 1024 => "1KB"
15
+ // 2097152 => "2MB"
16
+ PopulateMe.display_file_size = function(size) {
17
+ var unit = 'B';
18
+ if (size>1024) {
19
+ size = size / 1024;
20
+ unit = 'KB';
21
+ }
22
+ if (size>1024) {
23
+ size = size / 1024;
24
+ unit = 'MB';
25
+ }
26
+ if (size>1024) {
27
+ size = size / 1024;
28
+ unit = 'GB';
29
+ }
30
+ return size+unit;
31
+ };
32
+
33
+ // Template helpers
34
+ PopulateMe.template_helpers = {
35
+ custom_partial_or_default: function() {
36
+ return function(text,render) {
37
+ return render('{{>'+(this.custom_template||text)+'}}');
38
+ }
39
+ },
40
+ adapted_field: function() {
41
+ return function(text,render) {
42
+ var tmpl;
43
+ if (this.custom_template) {
44
+ tmpl = this.custom_template;
45
+ } else {
46
+ tmpl = 'template_'+this.type+'_field';
47
+ if (!(tmpl in PopulateMe.templates)) tmpl = 'template_string_field';
48
+ }
49
+ return render('{{>'+tmpl+'}}');
50
+ }
51
+ },
52
+ build_input_attributes: function() {
53
+ var out = "";
54
+ $.each(this.input_attributes, function(k,v) {
55
+ if (v!==false) {
56
+ out = out+' '+k;
57
+ if (v!==true) out = out+'=\''+Mustache.escape(v)+'\'';
58
+ }
59
+ });
60
+ return out;
61
+ },
62
+ cache_buster: function() {
63
+ // Simple version which assumes the url has no query yet or hash
64
+ var buster = Math.random()*10000000000000000;
65
+ return '?cache_buster='+buster;
66
+ }
67
+ };
9
68
 
10
- // Vars
11
- var finder = $('#finder');
69
+ // Template render with helpers
70
+ PopulateMe.mustache_render = function(data) {
71
+ var data_and_helpers = $.extend(data,PopulateMe.template_helpers);
72
+ return Mustache.render(PopulateMe.templates[data.template],data_and_helpers,PopulateMe.templates);
73
+ };
12
74
 
13
- // Init templates
14
- var templates = {};
15
- $('[type=x-tmpl-mustache]').each(function() {
16
- var $this = $(this);
17
- templates[$this.attr('id').replace(/-/g,'_')] = init_template($this);
75
+ // Make Sortable from element or css selector
76
+ PopulateMe.make_sortable = function(selector,context) {
77
+ var obj = (typeof selector == 'string') ? $(selector,context) : selector;
78
+ return obj.sortable({
79
+ forcePlaceholderSize: true,
80
+ handle: '.handle'
18
81
  });
82
+ };
19
83
 
20
- // Template helpers
21
- var template_helpers = {
22
- custom_partial_or_default: function() {
23
- return function(text,render) {
24
- return render('{{>'+(this.custom_template||text)+'}}');
25
- }
26
- },
27
- adapted_field: function() {
28
- return function(text,render) {
29
- var tmpl;
30
- if (this.custom_template) {
31
- tmpl = this.custom_template;
32
- } else {
33
- tmpl = 'template_'+this.type+'_field';
34
- if (!(tmpl in templates)) tmpl = 'template_string_field';
35
- }
36
- return render('{{>'+tmpl+'}}');
37
- }
38
- },
39
- build_input_attributes: function() {
40
- var out = "";
41
- $.each(this.input_attributes, function(k,v) {
42
- if (v!==false) {
43
- out = out+' '+k;
44
- if (v!==true) out = out+'=\''+Mustache.escape(v)+'\'';
84
+ // Scroll to an element, possibly in a specific column.
85
+ PopulateMe.scroll_to = function(el, column) {
86
+ if (!column) {
87
+ column = el.closest('.column');
88
+ }
89
+ column.animate({scrollTop: el.position().top});
90
+ };
91
+
92
+ // Mark errors for report after form validation.
93
+ // It adds the .invalid class to invalid fields,
94
+ // and adds the report after the label of the field.
95
+ PopulateMe.mark_errors = function(context,report) {
96
+ $.each(report,function(k,v) {
97
+ var field = context.find('> [data-field-name='+k+']:first');
98
+ if (field.is('fieldset')) {
99
+ $.each(v, function(index,subreport) {
100
+ var subcontext = field.find('> .nested-documents > li:nth('+index+')');
101
+ PopulateMe.mark_errors(subcontext, subreport);
102
+ });
103
+ } else {
104
+ field.addClass('invalid');
105
+ var errors = "<span class='errors'>, "+v.join(', ')+"</span>";
106
+ field.find('> label').append(errors);
107
+ }
108
+ });
109
+ };
110
+
111
+ // JS Validations
112
+ // This adds validations that could happen before sending
113
+ // anything to the server and that cannot be done with
114
+ // browser form validations like "required".
115
+ // For example we can check the file size so that files are not sent
116
+ // to the server at all if they are too big.
117
+ // Returns a boolean.
118
+ PopulateMe.jsValidationsPassed = function(context) {
119
+ if (window.File && window.FileReader && window.FileList && window.Blob) {
120
+ try {
121
+ var max_size_fields = $('input[type=file][data-max-size]', context);
122
+ max_size_fields.each(function() {
123
+ var field = $(this);
124
+ var max_size = parseInt(field.data('max-size'));
125
+ if (field[0].files[0]) {
126
+ var fsize = field[0].files[0].size;
127
+ var fname = field[0].files[0].name;
128
+ if (fsize>max_size) {
129
+ alert('File too big: '+fname+' should be less than '+PopulateMe.display_file_size(max_size)+'.');
130
+ throw "Validation error";
131
+ }
45
132
  }
46
133
  });
47
- return out;
48
- },
49
- cache_buster: function() {
50
- // Simple version which assumes the url has no query yet or hash
51
- var buster = Math.random()*10000000000000000;
52
- return '?cache_buster='+buster;
134
+ } catch(e) {
135
+ if (e==='Validation error') {
136
+ return false;
137
+ } else {
138
+ throw(e);
139
+ }
53
140
  }
54
- };
141
+ }
142
+ return true;
143
+ };
55
144
 
56
- // Template render
57
- var mustache_render = function(data) {
58
- var data_and_helpers = $.extend(data,template_helpers);
59
- return Mustache.render(templates[data.template],data_and_helpers,templates);
60
- };
145
+ // Init column
146
+ // Bind events and init things that need to happen when
147
+ // a new column is added to the finder.
148
+ // The callback `custom_init_column` is also called at the end
149
+ // in case you have things to put in your custom javascript.
150
+ PopulateMe.init_column = function(c) {
61
151
 
62
- // Sortable
63
- var make_sortable = function(selector,context) {
64
- var obj = (typeof selector == 'string') ? $(selector,context) : selector;
65
- return obj.sortable({
66
- forcePlaceholderSize: true,
67
- handle: '.handle'
152
+ // Sort list item
153
+ PopulateMe.make_sortable('.documents',c).bind('sortupdate', function(e, ui) {
154
+ var list = $(ui.item).closest('.documents');
155
+ var ids = list.children().map(function() {
156
+ return $(this).data().id;
157
+ }).get();
158
+ $.ajax({
159
+ url: list.data().sortUrl,
160
+ type: 'put',
161
+ data: {
162
+ action: 'sort',
163
+ field: list.data().sortField,
164
+ ids: ids
165
+ }
68
166
  });
69
- };
167
+ /*
168
+ ui.item contains the current dragged element.
169
+ ui.item.index() contains the new index of the dragged element
170
+ ui.oldindex contains the old index of the dragged element
171
+ ui.startparent contains the element that the dragged item comes from
172
+ ui.endparent contains the element that the dragged item was added to
173
+ */
174
+ });
70
175
 
71
- var scroll_to = function(el, column) {
72
- if (!column) {
73
- column = el.closest('.column');
74
- }
75
- column.animate({scrollTop: el.position().top});
76
- };
176
+ // Sort nested documents
177
+ PopulateMe.make_sortable('.nested-documents',c);
178
+
179
+ // Init textareas
180
+ $('textarea',c).trigger('input');
181
+
182
+ // Init multiple select with asmSelect
183
+ $('select[multiple]').asmSelect({ sortable: true, removeLabel: '&times;' });
184
+
185
+ // Possible callback from custom javascript file.
186
+ // If you need to do something on init_column,
187
+ // create this callback.
188
+ if (PopulateMe.custom_init_column) {
189
+ PopulateMe.custom_init_column(c);
190
+ }
191
+
192
+ }; // End - Init column
193
+
194
+
195
+
196
+ // Dom ready
197
+ $(function() {
198
+
199
+ // Dom elements.
200
+ PopulateMe.finder = $('#finder');
201
+
202
+ // Init templates
203
+ PopulateMe.templates = {};
204
+ $('[type=x-tmpl-mustache]').each(function() {
205
+ var $this = $(this);
206
+ PopulateMe.templates[$this.attr('id').replace(/-/g,'_')] = PopulateMe.init_template($this);
207
+ });
77
208
 
78
209
  // Reactive text area
210
+ // It switches between online or longer depending on the amount
211
+ // of text.
212
+ // Long term, for this reason, it might be used for both
213
+ // :string and :text fields.
79
214
  $('body').on('input propertychange', 'textarea', function() {
80
215
  if (this.value.length>50 || this.value.match(/\n/)) {
81
216
  $(this).removeClass('oneline');
@@ -84,56 +219,11 @@ $(function() {
84
219
  }
85
220
  });
86
221
 
87
- // Errors
88
- var mark_errors = function(context,report) {
89
- $.each(report,function(k,v) {
90
- var field = context.find('> [data-field-name='+k+']:first');
91
- if (field.is('fieldset')) {
92
- $.each(v, function(index,subreport) {
93
- var subcontext = field.find('> .nested-documents > li:nth('+index+')');
94
- mark_errors(subcontext, subreport);
95
- });
96
- } else {
97
- field.addClass('invalid');
98
- var errors = "<span class='errors'>, "+v.join(', ')+"</span>";
99
- field.find('> label').append(errors);
100
- }
101
- });
102
- };
103
-
104
- // JS Validations
105
- var jsValidationsPassed = function(context) {
106
- if (window.File && window.FileReader && window.FileList && window.Blob) {
107
- try {
108
- var max_size_fields = $('input[type=file][data-max-size]', context);
109
- max_size_fields.each(function() {
110
- var field = $(this);
111
- var max_size = parseInt(field.data('max-size'));
112
- if (field[0].files[0]) {
113
- var fsize = field[0].files[0].size;
114
- var fname = field[0].files[0].name;
115
- if (fsize>max_size) {
116
- alert('File too big: '+fname+' should be less than '+max_size/1000+'KB.');
117
- throw "Validation error";
118
- }
119
- }
120
- });
121
- } catch(e) {
122
- if (e==='Validation error') {
123
- return false;
124
- } else {
125
- throw(e);
126
- }
127
- }
128
- }
129
- return true;
130
- };
131
-
132
222
  // Ajax form
133
223
  $('body').on('submit','form.admin-post, form.admin-put', function(e) {
134
224
  e.preventDefault();
135
225
  var self = $(this);
136
- if (jsValidationsPassed(self)) {
226
+ if (PopulateMe.jsValidationsPassed(self)) {
137
227
 
138
228
  var submit_button = $('input[type=submit]',self);
139
229
  submit_button.hide();
@@ -145,16 +235,16 @@ $(function() {
145
235
  contentType: false,
146
236
  success: function(res) {
147
237
  if (res.success==true) {
148
- var reloader = finder.find('> li:nth-last-child(3) .selected')
238
+ var reloader = PopulateMe.finder.find('> li:nth-last-child(3) .selected')
149
239
  if (reloader.size()>0) {
150
240
  reloader.trigger('click.columnav',[function(cb_object) {
151
241
  var target = $('[data-id='+res.data.id+']', cb_object.column);
152
242
  if (target.size()>0) {
153
- scroll_to(target, cb_object.column);
243
+ PopulateMe.scroll_to(target, cb_object.column);
154
244
  }
155
245
  }]);
156
246
  } else {
157
- finder.trigger('pop.columnav');
247
+ PopulateMe.finder.trigger('pop.columnav');
158
248
  }
159
249
  }
160
250
  },
@@ -163,8 +253,8 @@ $(function() {
163
253
  if (res.success==false) {
164
254
  $('.invalid',self).removeClass('invalid');
165
255
  $('.errors',self).remove();
166
- mark_errors(self,res.data);
167
- scroll_to(self.find('.invalid:first'));
256
+ PopulateMe.mark_errors(self,res.data);
257
+ PopulateMe.scroll_to(self.find('.invalid:first'));
168
258
  submit_button.show();
169
259
  }
170
260
  }
@@ -178,9 +268,9 @@ $(function() {
178
268
  e.preventDefault();
179
269
  var self = $(this);
180
270
  $.get(this.href, function(data) {
181
- var content = $(mustache_render(data));
182
- make_sortable(self.closest('fieldset').find('> ol').append(content).sortable('destroy'));
183
- scroll_to(content);
271
+ var content = $(PopulateMe.mustache_render(data));
272
+ PopulateMe.make_sortable(self.closest('fieldset').find('> ol').append(content).sortable('destroy'));
273
+ PopulateMe.scroll_to(content);
184
274
  });
185
275
  });
186
276
 
@@ -229,58 +319,20 @@ $(function() {
229
319
  }
230
320
  });
231
321
 
232
- // Init column
233
- var init_column = function(c) {
234
-
235
- // Sort list item
236
- make_sortable('.documents',c).bind('sortupdate', function(e, ui) {
237
- var list = $(ui.item).closest('.documents');
238
- var ids = list.children().map(function() {
239
- return $(this).data().id;
240
- }).get();
241
- $.ajax({
242
- url: list.data().sortUrl,
243
- type: 'put',
244
- data: {
245
- action: 'sort',
246
- field: list.data().sortField,
247
- ids: ids
248
- }
249
- });
250
- /*
251
- ui.item contains the current dragged element.
252
- ui.item.index() contains the new index of the dragged element
253
- ui.oldindex contains the old index of the dragged element
254
- ui.startparent contains the element that the dragged item comes from
255
- ui.endparent contains the element that the dragged item was added to
256
- */
257
- });
258
-
259
- // Sort nested documents
260
- make_sortable('.nested-documents',c);
261
-
262
- // Init textareas
263
- $('textarea',c).trigger('input');
264
-
265
- // Init multiple select with asmSelect
266
- $('select[multiple]').asmSelect({ sortable: true, removeLabel: '&times;' });
267
-
268
- }; // End - Init column
269
-
270
322
  // Init finder
271
- finder.columnav({
323
+ PopulateMe.finder.columnav({
272
324
  on_push: function(data) {
273
- init_column(data.column);
325
+ PopulateMe.init_column(data.column);
274
326
  },
275
327
  process_data: function(data) {
276
328
  if (typeof data == 'object') {
277
- return mustache_render(data);
329
+ return PopulateMe.mustache_render(data);
278
330
  } else {
279
331
  return data;
280
332
  }
281
333
  }
282
334
  });
283
- finder.trigger('getandpush.columnav',[window.admin_path+window.index_path]);
335
+ PopulateMe.finder.trigger('getandpush.columnav',[window.admin_path+window.index_path]);
284
336
 
285
337
  }); // End - DomReady
286
338
 
@@ -1,4 +1,4 @@
1
1
  module PopulateMe
2
- VERSION = '0.6.1'
2
+ VERSION = '0.6.2'
3
3
  end
4
4
 
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.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mickael Riga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-06 00:00:00.000000000 Z
11
+ date: 2018-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: web-utils