dash_creator 0.1.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.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +170 -0
  4. data/Rakefile +36 -0
  5. data/app/assets/config/dash_creator_manifest.js +2 -0
  6. data/app/assets/javascripts/dash_creator/application.js +24 -0
  7. data/app/assets/javascripts/dash_creator/chart.js +2 -0
  8. data/app/assets/javascripts/dash_creator/dashboard.js +2 -0
  9. data/app/assets/javascripts/dash_creator/dashboard_object.js +2 -0
  10. data/app/assets/javascripts/dash_creator/filter.js +2 -0
  11. data/app/assets/javascripts/dash_creator/libs/Chart.js +12269 -0
  12. data/app/assets/javascripts/dash_creator/libs/bootstrap.js +3535 -0
  13. data/app/assets/javascripts/dash_creator/libs/chartCreator.js +1582 -0
  14. data/app/assets/javascripts/dash_creator/libs/dashboardCreator.js +531 -0
  15. data/app/assets/javascripts/dash_creator/libs/daterangepicker.js +1626 -0
  16. data/app/assets/javascripts/dash_creator/libs/filterCreator.js +733 -0
  17. data/app/assets/javascripts/dash_creator/libs/jquery-ui.js +18706 -0
  18. data/app/assets/javascripts/dash_creator/libs/jquery.minicolors.js +1108 -0
  19. data/app/assets/javascripts/dash_creator/libs/moment.js +4301 -0
  20. data/app/assets/javascripts/dash_creator/libs/tether.js +1811 -0
  21. data/app/assets/javascripts/dash_creator/user.js +2 -0
  22. data/app/assets/stylesheets/dash_creator/application.css +16 -0
  23. data/app/assets/stylesheets/dash_creator/chart.css +4 -0
  24. data/app/assets/stylesheets/dash_creator/dashboard.css +4 -0
  25. data/app/assets/stylesheets/dash_creator/dashboard_object.css +4 -0
  26. data/app/assets/stylesheets/dash_creator/filter.css +4 -0
  27. data/app/assets/stylesheets/dash_creator/fonts/FontAwesome.otf +0 -0
  28. data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.eot +0 -0
  29. data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.svg +2671 -0
  30. data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.ttf +0 -0
  31. data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.woff +0 -0
  32. data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.woff2 +0 -0
  33. data/app/assets/stylesheets/dash_creator/libs/font-awesome.css +2199 -0
  34. data/app/assets/stylesheets/dash_creator/libs/jquery.minicolors.css +319 -0
  35. data/app/assets/stylesheets/dash_creator/libs/jquery.minicolors.png +0 -0
  36. data/app/assets/stylesheets/dash_creator/libs/style.css +13714 -0
  37. data/app/assets/stylesheets/dash_creator/user.css +4 -0
  38. data/app/controllers/dash_creator/application_controller.rb +3 -0
  39. data/app/controllers/dash_creator/chart_controller.rb +81 -0
  40. data/app/controllers/dash_creator/dashboard_controller.rb +37 -0
  41. data/app/controllers/dash_creator/dashboard_object_controller.rb +34 -0
  42. data/app/controllers/dash_creator/filter_controller.rb +60 -0
  43. data/app/controllers/dash_creator/user_controller.rb +82 -0
  44. data/app/helpers/dash_creator/application_helper.rb +4 -0
  45. data/app/helpers/dash_creator/chart_helper.rb +829 -0
  46. data/app/helpers/dash_creator/dashboard_helper.rb +4 -0
  47. data/app/helpers/dash_creator/dashboard_object_helper.rb +4 -0
  48. data/app/helpers/dash_creator/filter_helper.rb +237 -0
  49. data/app/helpers/dash_creator/user_helper.rb +4 -0
  50. data/app/jobs/dash_creator/application_job.rb +4 -0
  51. data/app/mailers/dash_creator/application_mailer.rb +6 -0
  52. data/app/models/dash_creator/application_record.rb +5 -0
  53. data/app/models/dash_creator/chart.rb +21 -0
  54. data/app/models/dash_creator/dashboard.rb +13 -0
  55. data/app/models/dash_creator/dashboard_object.rb +7 -0
  56. data/app/models/dash_creator/filter.rb +22 -0
  57. data/app/views/dash_creator/chart/_chart_creator.html.erb +230 -0
  58. data/app/views/dash_creator/chart/_modals.html.erb +74 -0
  59. data/app/views/dash_creator/chart/_plot_chart.html.erb +45 -0
  60. data/app/views/dash_creator/chart/create_chart.js.erb +53 -0
  61. data/app/views/dash_creator/dashboard/_dashboard_creator.html.erb +182 -0
  62. data/app/views/dash_creator/dashboard/_modals.html.erb +74 -0
  63. data/app/views/dash_creator/dashboard_object/_chart.html.erb +9 -0
  64. data/app/views/dash_creator/dashboard_object/_stat.html.erb +24 -0
  65. data/app/views/dash_creator/dashboard_object/_table.html.erb +16 -0
  66. data/app/views/dash_creator/filter/_filtering_card.html.erb +132 -0
  67. data/app/views/dash_creator/filter/_modals.html.erb +51 -0
  68. data/app/views/dash_creator/filter/_result_tables.html.erb +55 -0
  69. data/app/views/dash_creator/filter/apply_filtering.js.erb +46 -0
  70. data/app/views/dash_creator/filter/create_stat.js.erb +20 -0
  71. data/app/views/dash_creator/layouts/application.html.erb +64 -0
  72. data/app/views/dash_creator/layouts/menu/_left_menu.html.erb +12 -0
  73. data/app/views/dash_creator/user/_section_card.html.erb +17 -0
  74. data/app/views/dash_creator/user/creator.html.erb +11 -0
  75. data/app/views/dash_creator/user/dashboard.html.erb +10 -0
  76. data/config/initializers/dash_creator.rb +0 -0
  77. data/config/routes.rb +30 -0
  78. data/lib/dash_creator.rb +87 -0
  79. data/lib/dash_creator/acts_as_dash_creator.rb +16 -0
  80. data/lib/dash_creator/acts_as_dashboard_object.rb +19 -0
  81. data/lib/dash_creator/engine.rb +11 -0
  82. data/lib/dash_creator/version.rb +3 -0
  83. data/lib/generators/dash_creator/install/install_generator.rb +70 -0
  84. data/lib/generators/dash_creator/install/templates/_chart.html.erb +9 -0
  85. data/lib/generators/dash_creator/install/templates/_section_card.html.erb +17 -0
  86. data/lib/generators/dash_creator/install/templates/_stat.html.erb +24 -0
  87. data/lib/generators/dash_creator/install/templates/_table.html.erb +16 -0
  88. data/lib/generators/dash_creator/install/templates/add_indexes_to_dash_creator_tables.rb +15 -0
  89. data/lib/generators/dash_creator/install/templates/create_dash_creator_charts.rb +25 -0
  90. data/lib/generators/dash_creator/install/templates/create_dash_creator_dashboard_objects.rb +36 -0
  91. data/lib/generators/dash_creator/install/templates/create_dash_creator_dashboards.rb +28 -0
  92. data/lib/generators/dash_creator/install/templates/create_dash_creator_filters.rb +22 -0
  93. data/lib/generators/dash_creator/install/templates/dashboard.html.erb +10 -0
  94. data/lib/generators/dash_creator/install/templates/initializer.rb +48 -0
  95. data/lib/tasks/dash_creator_tasks.rake +4 -0
  96. metadata +196 -0
@@ -0,0 +1,733 @@
1
+ /*!
2
+ * Filter Creator v1.0
3
+ * 2017 Elie Oriol
4
+ */
5
+
6
+ (function(factory) {
7
+ if (typeof define === 'function' && define.amd) {
8
+ define([ 'jquery', 'moment' ], factory);
9
+ }
10
+ else if (typeof exports === 'object') { // Node/CommonJS
11
+ module.exports = factory(require('jquery'), require('moment'));
12
+ }
13
+ else {
14
+ factory(jQuery, moment);
15
+ }
16
+ })(function($, moment) {
17
+ var FilterCreator = function(element, options, callback) {
18
+ this.parentEl = $(element);
19
+
20
+ if (typeof options !== 'object' || options === null)
21
+ options = {};
22
+
23
+ this.models_data = (typeof options.models_data === 'object') ?
24
+ options.models_data : {};
25
+
26
+ this.attributes_aliases = (typeof options.attributes_aliases === 'object') ?
27
+ options.attributes_aliases : {};
28
+
29
+ this.displayed_model_names = (typeof options.displayed_model_names === 'object') ?
30
+ options.displayed_model_names : {};
31
+
32
+ this.displayed_attribute_names = (typeof options.displayed_attribute_names === 'object') ?
33
+ options.displayed_attribute_names : {};
34
+
35
+ this.callback = (typeof callback === 'function') ? callback : function() {};
36
+
37
+ if (typeof options.template !== 'string' && !(options.template instanceof $)) {
38
+ options.template = '<ul class="row" id="filters">' +
39
+ '<div id="add-btn-container" style="margin-bottom: 30px;">' +
40
+ '<button id="add-filter-btn" class="btn btn-primary pull-right">+</button>' +
41
+ '</div>' +
42
+ '</ul>';
43
+ }
44
+
45
+ this.filtersContainer = $(options.template).appendTo(this.parentEl);
46
+
47
+ this.filtersContainer
48
+ .on('click', '#add-filter-btn', $.proxy(this.addFilter, this))
49
+ .on('click', '.remove-filter-btn', $.proxy(this.removeFilter, this))
50
+ .on('focusin', '.model-select', $.proxy(this.modelSelection, this))
51
+ .on('change', '.model-select', $.proxy(this.modelSelectionCallback, this))
52
+ .on('change', '.attribute-select', $.proxy(this.attributeSelectionCallback, this))
53
+ .on('click', 'input[type="radio"]', $.proxy(this.radioButtonCallback, this));
54
+ };
55
+
56
+ FilterCreator.prototype = {
57
+
58
+ constructor: FilterCreator,
59
+
60
+ // --------------- Add and remove filters ---------------
61
+ // Add filter
62
+ addFilter: function() {
63
+ var last_filter = this.filtersContainer.find('.top-filter:last');
64
+ var filter_id = last_filter.length === 0 ? 0 : parseInt(last_filter.attr("data-id")) + 1;
65
+
66
+ var filter_field_string = '<li class="filter top-filter" data-id="' + filter_id + '" data-sublevel="0" style="width:100%; margin-bottom:50px;"> ' +
67
+ '<div class="row">' +
68
+ '<div class="col-md-1"> ' +
69
+ '<span class="filter-num">' + filter_id + '</span> ' +
70
+ '<button type="button" class="close remove-filter-btn" aria-label="Close"> ' +
71
+ '<span aria-hidden="true">×</span> ' +
72
+ '</button> ' +
73
+ '</div> ' +
74
+ '<div class="col-md-3"> ' +
75
+ '<select name="model" class="model-select form-control">' +
76
+ '<option value="">Choose model</option>';
77
+
78
+ var displayed_model_names = this.displayed_model_names;
79
+ $.each(models_data, function(model, model_data) {
80
+ var model_name_to_display = displayed_model_names[model];
81
+ if (typeof model_name_to_display === 'undefined')
82
+ model_name_to_display = model.humanize();
83
+ filter_field_string += '<option value="' + model + '">' + model_name_to_display + '</option>';
84
+ });
85
+
86
+ filter_field_string += '</select>' +
87
+ '</div> ' +
88
+ '<div class="col-md-3"> ' +
89
+ '<label>Number of records</label> ' +
90
+ '<input type="number" name="num-records" min="0"> ' +
91
+ '</div> ' +
92
+ '<div class="col-md-4 select-attribute-div"> ' +
93
+ '</div> ' +
94
+ '</div> ' +
95
+ '</li>';
96
+
97
+ // Append to list of filters
98
+ var filter = $(filter_field_string).appendTo(this.filtersContainer);
99
+ if (filter.prev('.top-filter').length !== 0)
100
+ $('<hr>').insertBefore(filter);
101
+ },
102
+
103
+ // Remove filter
104
+ removeFilter: function(e) {
105
+ var filter = $(e.target).closest(".filter");
106
+
107
+ var prev_hr = filter.prev('hr');
108
+ if (prev_hr.length === 0)
109
+ filter.next('hr').remove();
110
+
111
+ filter.prev('hr').remove();
112
+ filter.remove();
113
+ },
114
+
115
+
116
+ // --------------- Model selection ---------------
117
+ // Display models not already chosen in an other filter field
118
+ modelSelection: function(e) {
119
+ var target = $(e.target);
120
+
121
+ // Get models already chosen
122
+ var excluded_models = [];
123
+ target.closest(".top-filter").siblings().each(function() {
124
+ var model = $(this).find('[name="model"]').val();
125
+ if (model !== "")
126
+ excluded_models.push(model);
127
+ });
128
+
129
+ // Show and hide the right options
130
+ target.find("option").each(function() {
131
+ ($.inArray($(this).val(), excluded_models) === -1) ? $(this).show() : $(this).hide();
132
+ });
133
+ },
134
+
135
+ // Callback on model selection
136
+ modelSelectionCallback: function(e) {
137
+ var target = $(e.target);
138
+
139
+ // Get filter
140
+ var filter = target.closest(".top-filter");
141
+
142
+ // Remove stuff if there is already a field from a previous model choice
143
+ filter.find(".attribute-select").remove();
144
+ filter.find(".attribute-options:first").remove();
145
+
146
+ // Get model & append to filter field if non empty value
147
+ var model = target.val();
148
+ if (model !== '') {
149
+ var attribute_select_string = this.createAttributeSelectString(model);
150
+ $(attribute_select_string).appendTo(filter.find('.select-attribute-div'));
151
+ }
152
+ },
153
+
154
+ createAttributeSelectString: function(model) {
155
+ var attribute_select_string = '<select name="attributes" multiple="multiple" class="attribute-select form-control">';
156
+
157
+ // Add attributes options to select field
158
+ var displayed_attribute_names = this.displayed_attribute_names[model];
159
+ $.each(this.models_data[model], function(idx, str) {
160
+ var str_arr = str.split('-');
161
+ var attribute = str_arr[0], type = str_arr[1];
162
+
163
+ var attribute_name_to_display = (typeof displayed_attribute_names === 'undefined') ? undefined : displayed_attribute_names[attribute];
164
+ if (typeof attribute_name_to_display === 'undefined')
165
+ attribute_name_to_display = attribute.humanize();
166
+
167
+ attribute_select_string += '<option value="' + attribute + '-' + type + '" >' + attribute_name_to_display;
168
+
169
+ if (type === 'has')
170
+ attribute_select_string += ' (has relation)';
171
+
172
+ if (type === 'ref')
173
+ attribute_select_string += ' (belongs relation)';
174
+
175
+ attribute_select_string += '</option>';
176
+ });
177
+
178
+ attribute_select_string += '</select>';
179
+
180
+ return attribute_select_string;
181
+ },
182
+
183
+
184
+ // --------------- Attribute selection ---------------
185
+ attributeSelectionCallback: function(e) {
186
+ var target = $(e.target);
187
+
188
+ var filter = target.closest(".filter");
189
+
190
+ // Get the filter nesting sublevel from above sublevel
191
+ var sublevel = parseInt(filter.attr("data-sublevel")) + 1;
192
+
193
+ // Append a new list for the attributes if it does not exist
194
+ var parent_div = filter.find('.attribute-options[data-sublevel="' + sublevel + '"]');
195
+ if (parent_div.length === 0) {
196
+ var options_div_string = '<ul class="col-md-10 offset-md-2 attribute-options" data-sublevel="' + sublevel + '"></ul>';
197
+ parent_div = $(options_div_string).appendTo(filter);
198
+ }
199
+
200
+ // Add field for each selected attribute
201
+ var values = target.val();
202
+ if (values === null)
203
+ values = [];
204
+ var selected_values = []; // Used to after remove eventual non kept previous attributes
205
+
206
+ var creator = this;
207
+ var model = 'Email';
208
+ var displayed_attribute_names = this.displayed_attribute_names[model];
209
+ values.forEach(function(s) {
210
+ // Get name and type of field
211
+ var arr = s.split('-');
212
+ var attribute = arr[0], type = arr[1];
213
+ selected_values.push(attribute);
214
+
215
+ // Next if field already exists
216
+ if (parent_div.find('.filter[data-attribute="' + attribute + '"][data-sublevel="' + sublevel + '"]').length !== 0)
217
+ return false;
218
+
219
+ // Append a list element and add to filters data
220
+ var parent_string = '<li class="filter" data-sublevel="' + sublevel + '" data-attribute="' + attribute + '" style="width: 100%"></li>';
221
+ var parent = $(parent_string).appendTo(parent_div);
222
+
223
+ // Provide name to display
224
+ // FIND MODEL FROM UPPER SUBLEVEL : case top filter and case sub filter
225
+ var attribute_name_to_display = (typeof displayed_attribute_names === 'undefined') ? undefined : displayed_attribute_names[attribute];
226
+ if (typeof attribute_name_to_display === 'undefined')
227
+ attribute_name_to_display = attribute.humanize();
228
+
229
+ // Switch case depending on attribute type to get HTML to append
230
+ switch(type) {
231
+ case 'datetime':
232
+ creator.addDatetimeAttribute(parent, attribute_name_to_display);
233
+ parent.attr("data-type", "datetime");
234
+ break;
235
+
236
+ case 'text':
237
+ creator.addTextAttribute(parent, attribute_name_to_display);
238
+ parent.attr("data-type", "text");
239
+ break;
240
+
241
+ case 'ref':
242
+ creator.addRefAttribute(parent, attribute_name_to_display);
243
+ parent.attr("data-type", "ref");
244
+ break;
245
+
246
+ case 'has':
247
+ creator.addHasAttribute(parent, attribute_name_to_display);
248
+ parent.attr("data-type", "has");
249
+ break;
250
+
251
+ case 'numeric':
252
+ creator.addNumericAttribute(parent, attribute_name_to_display);
253
+ parent.attr("data-type", "numeric");
254
+ break;
255
+
256
+ case 'boolean':
257
+ creator.addBooleanAttribute(parent, attribute_name_to_display);
258
+ parent.attr("data-type", "boolean");
259
+ break;
260
+
261
+ default:
262
+ creator.addErrorMessage(parent, attribute_name_to_display, type);
263
+ parent.attr("data-type", "error");
264
+ break;
265
+ }
266
+ });
267
+
268
+ // Remove all fields that are not selected anymore
269
+ parent_div.find('.filter[data-sublevel="' + sublevel + '"]').each(function() {
270
+ if ($.inArray($(this).attr("data-attribute"), selected_values) === -1)
271
+ $(this).remove();
272
+ });
273
+ },
274
+
275
+
276
+ // --------------- Add type attributes ---------------
277
+ addDatetimeAttribute: function(parent, attribute_name_to_display) {
278
+ var attribute = parent.attr("data-attribute"),
279
+ sublevel = parent.attr("data-sublevel"),
280
+ filter_id = parent.closest('.top-filter').attr("data-id");
281
+
282
+ // Append (empty) daterangepicker
283
+ var label = attribute_name_to_display + ' in range';
284
+ var daterange_input_string = '<label for="daterange">' + label + '</label>'
285
+ + '<div class="daterange" data-id="' + filter_id + '" data-attribute="' + attribute + '" data-sublevel="' + sublevel + '" style="background: #fff; cursor: pointer; padding: 5px 10px; border: 1px solid #ccc; width: 100%">'
286
+ + '<i class="glyphicon glyphicon-calendar fa fa-calendar"></i>&nbsp;'
287
+ + '<span class="daterange-value"></span>'
288
+ + '<b class="caret"></b>'
289
+ + '</div>';
290
+ var daterange_input = $(daterange_input_string + '<hr>').appendTo(parent);
291
+
292
+ // Customize daterangepicker
293
+ var creator = this;
294
+ $(function() {
295
+ // Default start, end
296
+ var start = moment().subtract(29, 'days');
297
+ var end = moment();
298
+
299
+ // Change date format
300
+ function cb(start, end) {
301
+ creator.filtersContainer.find('div[data-id="' + filter_id + '"][data-sublevel="'+ sublevel + '"][data-attribute="' + attribute + '"] span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
302
+ }
303
+
304
+ // Set as daterangepicker with different defaults
305
+ daterange_input.daterangepicker({
306
+ startDate: start,
307
+ endDate: end,
308
+ ranges: {
309
+ 'Today': [moment(), moment()],
310
+ 'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
311
+ 'Last 7 Days': [moment().subtract(6, 'days'), moment()],
312
+ 'Last 30 Days': [moment().subtract(29, 'days'), moment()],
313
+ 'This Month': [moment().startOf('month'), moment().endOf('month')],
314
+ 'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
315
+ }
316
+ }, cb);
317
+
318
+ cb(start, end);
319
+ });
320
+ },
321
+
322
+ addTextAttribute: function(parent, attribute_name_to_display) {
323
+ var attribute = parent.attr("data-attribute");
324
+ var count = this.filtersContainer.find('.filter').length;
325
+
326
+ // Empty or not choice
327
+ var radio_button_string = '<input class="form-control" type="radio" name="present-' + attribute + '-' + count + '" value="';
328
+ var text_field_string = '<label for="radio">' + attribute_name_to_display + '</label><br>'
329
+ + radio_button_string + 'false' + '"> Empty'
330
+ + radio_button_string + 'true' + '" checked="checked"> Not empty <br>';
331
+
332
+ // Search if string/text contains string
333
+ text_field_string += '<label>Contains ?</label>'
334
+ + '<input class="form-control" type="text" name="contains">';
335
+
336
+ // Append attribute filter field
337
+ $(text_field_string + '<hr>').appendTo(parent);
338
+ },
339
+
340
+ addRefAttribute: function(parent, attribute_name_to_display) {
341
+ var attribute = parent.attr("data-attribute");
342
+ var count = this.filtersContainer.find('.filter').length;
343
+
344
+ // Belongs to or not choice
345
+ var radio_button_string = '<input class="form-control" type="radio" name="ref-' + attribute + '-' + count + '" value="';
346
+ var ref_field_string = '<label for="radio">' + attribute_name_to_display + '</label><br>'
347
+ + radio_button_string + 'true' + '" checked="checked"> Belongs to'
348
+ + radio_button_string + 'false' + '"> Does not belong to <br>';
349
+
350
+ // Add object attributes filters
351
+ var alias = this.attributes_aliases[attribute];
352
+ attribute = (typeof alias === 'undefined') ? attribute : alias;
353
+ ref_field_string += '<br>' + this.createAttributeSelectString(attribute.classify());
354
+
355
+ // Append attribute filter field
356
+ $(ref_field_string + '<hr>').appendTo(parent);
357
+ },
358
+
359
+ addHasAttribute: function(parent, attribute_name_to_display) {
360
+ var attribute = parent.attr("data-attribute");
361
+ var count = this.filtersContainer.find('.filter').length;
362
+
363
+ // Has or not choice
364
+ var radio_button_string = '<input class="form-control" type="radio" name="has-' + attribute + '-' + count + '" value="';
365
+ var has_field_string = '<label for="radio">' + attribute_name_to_display + '</label><br>'
366
+ + radio_button_string + 'true' + '" checked="checked"> Has'
367
+ + radio_button_string + 'false' + '"> Does not have <br>';
368
+
369
+ // Add number of objects
370
+ has_field_string += '<label>Number of ' + attribute_name_to_display + ' in range </label>'
371
+ + '<input class="form-control" type="number" name="has-inf" min="1">'
372
+ + '<input class="form-control" type="number" name="has-sup" min="1">';
373
+
374
+ // Add objects attributes filters
375
+ var alias = this.attributes_aliases[attribute];
376
+ attribute = (typeof alias === 'undefined') ? attribute : alias;
377
+ has_field_string += '<br>' + this.createAttributeSelectString(attribute.classify());
378
+
379
+ // Append attribute filter field
380
+ $(has_field_string + '<hr>').appendTo(parent);
381
+ },
382
+
383
+ addNumericAttribute: function(parent, attribute_name_to_display) {
384
+ var attribute = parent.attr("data-attribute");
385
+ var count = this.filtersContainer.find('.filter').length;
386
+
387
+ var numeric_field_string = '<label>' + attribute_name_to_display + '</label><br>',
388
+ radio_button_string = '<input class="form-control" type="radio" name="value-' + attribute + '-' + count + '" value="';
389
+
390
+ switch(attribute.classify()) {
391
+ case 'Status':
392
+ // ??? emails (sent or not ?) & tasks (finished or not)
393
+ break;
394
+
395
+ case 'SharingStatus':
396
+ numeric_field_string += radio_button_string + '0' + '"> Not shared'
397
+ + radio_button_string + '1' + '"> Shared in team'
398
+ + radio_button_string + '2' + '"> Shared in company <br>';
399
+ break;
400
+
401
+ case 'State':
402
+ numeric_field_string += radio_button_string + '0' + '"> Temporary'
403
+ + radio_button_string + '1' + '"> Unverified'
404
+ + radio_button_string + '2' + '"> Verified <br>';
405
+ break;
406
+
407
+ case 'TemplateType':
408
+ numeric_field_string += radio_button_string + '0' + '"> Snippet'
409
+ + radio_button_string + '1' + '"> Template <br>';
410
+ break;
411
+
412
+ case 'TriggerCount':
413
+ numeric_field_string += '<input class="form-control" type="number" name="inf" min="0">'
414
+ + '<input class="form-control" type="number" name="sup" min="0">';
415
+ break;
416
+
417
+ default:
418
+ break;
419
+ }
420
+
421
+ // Append attribute filter field
422
+ $(numeric_field_string + '<hr>').appendTo(parent);
423
+ },
424
+
425
+ addBooleanAttribute: function(parent, attribute_name_to_display) {
426
+ var attribute = parent.attr("data-attribute");
427
+ var count = this.filtersContainer.find('.filter').length;
428
+
429
+ // Has or not choice
430
+ var radio_button_string = '<input class="form-control" type="radio" name="value-' + attribute + '-' + count + '" value="';
431
+ var boolean_field_string = '<label for="radio">' + attribute_name_to_display + '</label><br>'
432
+ + radio_button_string + 'true' + '" checked="checked"> True'
433
+ + radio_button_string + 'false' + '"> False <br>';
434
+
435
+ // Append attribute filter field
436
+ $(boolean_field_string + '<hr>').appendTo(parent);
437
+ },
438
+
439
+ addErrorMessage: function(parent, attribute_name_to_display, type) {
440
+ var attribute = parent.attr("data-attribute");
441
+
442
+ var error_message = '<label>' + attribute_name_to_display + ': type ' + type + ' is not handled</label><br>';
443
+
444
+ // Append error message
445
+ $(error_message + '<hr>').appendTo(parent);
446
+ },
447
+
448
+
449
+ // --------------- Radio buttons show/hide ---------------
450
+ radioButtonCallback: function(e) {
451
+ var target = $(e.target);
452
+
453
+ var name = target.attr("name");
454
+ var value = target.attr("value");
455
+ target.siblings('').not('input[name="' + name + '"]').not('label[for="radio"]').not('br').each(function() {
456
+ (value === "false") ? $(this).hide() : $(this).show();
457
+ });
458
+ },
459
+
460
+
461
+ // --------------- Prepare data ---------------
462
+ getFiltersData: function() {
463
+ // Format the sent data
464
+ var filters_data = {};
465
+
466
+ // Add all filtered models
467
+ var creator = this;
468
+ this.filtersContainer.find('.filter[data-sublevel="0"]').each(function() {
469
+ var model = $(this).find('[name="model"]').val();
470
+ var num_records = $(this).find('[name="num-records"]').val();
471
+
472
+ if (model !== "") {
473
+ // Create subdata for model
474
+ var data = filters_data[model] = {};
475
+
476
+ // Add number of records for model
477
+ data['num_records'] = num_records;
478
+
479
+ // Add data from child ul (recursive call)
480
+ var attribute_options = $(this).find('.attribute-options[data-sublevel="1"]');
481
+ if (attribute_options.length !== 0) {
482
+ creator.getSublevelData(data, attribute_options);
483
+ }
484
+ }
485
+ });
486
+
487
+ return JSON.parse(JSON.stringify(filters_data));
488
+ },
489
+
490
+ getSublevelData: function(data, attribute_options) {
491
+ var creator = this;
492
+
493
+ var sublevel = attribute_options.attr("data-sublevel");
494
+
495
+ attribute_options.find('.filter[data-sublevel="' + sublevel + '"]').each(function() {
496
+ // Get attribute and create subdata with it
497
+ var attribute = $(this).attr("data-attribute"), data_type = $(this).attr("data-type");
498
+ var subdata = data[attribute] = {type: data_type};
499
+
500
+ // Recurse through sublevel if there is a child ul
501
+ attribute_options = $(this).find('.attribute-options[data-sublevel="' + (parseInt(sublevel) + 1) + '"]');
502
+ if (attribute_options.length !== 0) {
503
+ creator.getSublevelData(subdata, attribute_options);
504
+ }
505
+
506
+ // TODO: Handle json
507
+ $(this).children("input").each(function() {
508
+ var name = $(this).attr("name");
509
+
510
+ switch ($(this).attr("type")) {
511
+ case 'radio':
512
+ // Add the value of the checked radio button (redundant but not a big deal)
513
+ var checked_radio_btn = $(this).siblings('input[name="' + name + '"]:checked');
514
+ if (checked_radio_btn.length !== 0)
515
+ subdata[name.split('-')[0]] = checked_radio_btn.val();
516
+ break;
517
+
518
+ case 'number':
519
+ case 'text':
520
+ // Add value if any
521
+ var value = $(this).val();
522
+ if (typeof value !== 'undefined' && value !== "" && value !== null)
523
+ subdata[name] = value;
524
+ break;
525
+
526
+ default:
527
+ break;
528
+ }
529
+ });
530
+
531
+ // Add daterange if any
532
+ var daterange_value = $(this).children(".daterange").find(".daterange-value").text();
533
+ if (typeof daterange_value !== 'undefined' && daterange_value !== "") {
534
+ var startend = daterange_value.split(' - ');
535
+ subdata['start'] = startend[0];
536
+ subdata['end'] = startend[1];
537
+ }
538
+ });
539
+ },
540
+
541
+
542
+ // --------------- Build filters from data ---------------
543
+ buildFilters: function(data) {
544
+ var creator = this;
545
+ creator.filtersContainer.children('.top-filter').remove();
546
+ $.each(data, function(model_name, model_attributes_hash) {
547
+ creator.buildModel(model_name, model_attributes_hash);
548
+ });
549
+ },
550
+
551
+ buildModel: function(model_name, model_attributes_hash) {
552
+ var creator = this;
553
+
554
+ creator.addFilter();
555
+ var new_filter = this.filtersContainer.find('.top-filter:last');
556
+ new_filter.find('.model-select:last').val(model_name).change();
557
+
558
+ var num_records = model_attributes_hash['num_records'];
559
+ new_filter.find('[name="num-records"]:last').val(num_records).change();
560
+
561
+ creator.buildSelectAttributes(new_filter, model_attributes_hash);
562
+
563
+ $.each(model_attributes_hash, function(attribute_name, attribute_hash) {
564
+ var sub_filter = new_filter.find('.filter[data-attribute="' + attribute_name + '"]');
565
+ creator.buildAttribute(sub_filter, attribute_hash);
566
+ });
567
+ },
568
+
569
+ buildSelectAttributes: function(filter, model_attributes_hash) {
570
+ var attributes_values = [], not_attributes = ['num_records', 'type', 'has', 'ref', 'has-inf', 'has-sup'];
571
+ $.each(Object.keys(model_attributes_hash), function(idx, attribute) {
572
+ if ($.inArray(attribute, not_attributes) !== -1)
573
+ return true;
574
+
575
+ attributes_values.push(attribute + '-' + model_attributes_hash[attribute]['type']);
576
+ });
577
+ filter.find('.attribute-select:last').val(attributes_values).change();
578
+ },
579
+
580
+ buildAttribute: function(filter, attribute_hash) {
581
+ switch(attribute_hash['type']) {
582
+ case 'datetime':
583
+ this.buildDatetimeAttribute(filter, attribute_hash);
584
+ break;
585
+
586
+ case 'numeric':
587
+ this.buildNumericAttribute(filter, attribute_hash);
588
+ break;
589
+
590
+ case 'boolean':
591
+ this.buildBooleanAttribute(filter, attribute_hash);
592
+ break;
593
+
594
+ case 'text':
595
+ this.buildTextAttribute(filter, attribute_hash);
596
+ break;
597
+
598
+ case 'has':
599
+ this.buildHasAttribute(filter, attribute_hash);
600
+ break;
601
+
602
+ case 'ref':
603
+ this.buildRefAttribute(filter, attribute_hash);
604
+ break;
605
+
606
+ default:
607
+ break;
608
+ }
609
+ },
610
+
611
+ buildDatetimeAttribute: function(filter, attribute_hash) {
612
+ if ('start' in attribute_hash) {
613
+ var daterange_string = attribute_hash['start'] + ' - ' + attribute_hash['end'];
614
+ filter.find('.daterange-value').text(daterange_string);
615
+ }
616
+ },
617
+
618
+ buildNumericAttribute: function(filter, attribute_hash) {
619
+ if ('value' in attribute_hash) {
620
+ var value = attribute_hash['value'];
621
+ filter.find('[value="' + value + '"]:first').click();
622
+ }
623
+ if ('inf' in attribute_hash) {
624
+ var inf = attribute_hash['inf'];
625
+ filter.find('[name="inf"]:first').val(inf);
626
+ }
627
+ if ('sup' in attribute_hash) {
628
+ var sup = attribute_hash['sup'];
629
+ filter.find('[name="sup"]:first').val(sup);
630
+ }
631
+ },
632
+
633
+ buildBooleanAttribute: function(filter, attribute_hash) {
634
+ if ('value' in attribute_hash) {
635
+ var value = attribute_hash['value'];
636
+ filter.find('[value="' + value + '"]:first').click();
637
+ }
638
+ },
639
+
640
+ buildTextAttribute: function(filter, attribute_hash) {
641
+ if ('present' in attribute_hash) {
642
+ var present = attribute_hash['present'];
643
+ filter.find('[value="' + present + '"]:first').click();
644
+
645
+ if (present === 'true' && 'contains' in attribute_hash) {
646
+ var contains = attribute_hash['contains'];
647
+ filter.find('[name="contains"]:first').val(contains);
648
+ }
649
+ }
650
+ },
651
+
652
+ buildHasAttribute: function(filter, attribute_hash) {
653
+ if ('has' in attribute_hash) {
654
+ var has = attribute_hash['has'];
655
+ filter.find('[value="' + has + '"]:first').click();
656
+
657
+ if (has === 'true' && 'has-inf' in attribute_hash) {
658
+ var has_inf = attribute_hash['has-inf'];
659
+ filter.find('[name="has-inf"]:first').val(has_inf);
660
+ }
661
+ if (has === 'true' && 'has-sup' in attribute_hash) {
662
+ var has_sup = attribute_hash['has-sup'];
663
+ filter.find('[name="has-sup"]:first').val(has_sup);
664
+ }
665
+ }
666
+
667
+ var creator = this;
668
+ creator.buildSelectAttributes(filter, attribute_hash);
669
+
670
+ var not_attributes = ['num_records', 'type', 'has', 'ref', 'has-inf', 'has-sup'];
671
+ $.each(attribute_hash, function(attribute_name, sub_attribute_hash) {
672
+ if ($.inArray(attribute_name, not_attributes) === -1) {
673
+ var sub_filter = filter.find('.filter[data-attribute="' + attribute_name + '"]');
674
+ creator.buildAttribute(sub_filter, sub_attribute_hash);
675
+ }
676
+ });
677
+ },
678
+
679
+ buildRefAttribute: function(filter, attribute_hash) {
680
+ if ('ref' in attribute_hash) {
681
+ var ref = attribute_hash['ref'];
682
+ filter.find('[value="' + ref + '"]:first').click();
683
+ }
684
+
685
+ var creator = this;
686
+ creator.buildSelectAttributes(filter, attribute_hash);
687
+
688
+ var not_attributes = ['num_records', 'type', 'has', 'ref', 'has-inf', 'has-sup'];
689
+ $.each(attribute_hash, function(attribute_name, sub_attribute_hash) {
690
+ if ($.inArray(attribute_name, not_attributes) === -1) {
691
+ var sub_filter = filter.find('.filter[data-attribute="' + attribute_name + '"]');
692
+ creator.buildAttribute(sub_filter, sub_attribute_hash);
693
+ }
694
+ });
695
+ }
696
+ };
697
+
698
+ $.fn.filterCreator = function(options, callback) {
699
+ this.each(function(i, _element) {
700
+ var el = $(_element);
701
+ if (el.data('filterCreator'))
702
+ el.data('filterCreator').remove();
703
+ el.data('filterCreator', new FilterCreator(el, options, callback));
704
+ });
705
+ return this;
706
+ };
707
+
708
+ // String function helpers
709
+ String.prototype.capitalizedArray = function() {
710
+ var arr = this.split('_');
711
+ if (arr[arr.length - 1] === 'id')
712
+ arr.pop();
713
+
714
+ for (var i = 0; i < arr.length; i++) {
715
+ arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
716
+ }
717
+ return arr;
718
+ };
719
+
720
+ String.prototype.underscore = function(){
721
+ return this.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();}).replace(/^_/, "");
722
+ };
723
+
724
+ String.prototype.humanize = function(){
725
+ return this.underscore().capitalizedArray().join(' ');
726
+ };
727
+
728
+ String.prototype.classify = function(){
729
+ return this.capitalizedArray().join('');
730
+ };
731
+
732
+ return FilterCreator;
733
+ });