populate-me 0.6.1 → 0.6.2

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
  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