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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +170 -0
- data/Rakefile +36 -0
- data/app/assets/config/dash_creator_manifest.js +2 -0
- data/app/assets/javascripts/dash_creator/application.js +24 -0
- data/app/assets/javascripts/dash_creator/chart.js +2 -0
- data/app/assets/javascripts/dash_creator/dashboard.js +2 -0
- data/app/assets/javascripts/dash_creator/dashboard_object.js +2 -0
- data/app/assets/javascripts/dash_creator/filter.js +2 -0
- data/app/assets/javascripts/dash_creator/libs/Chart.js +12269 -0
- data/app/assets/javascripts/dash_creator/libs/bootstrap.js +3535 -0
- data/app/assets/javascripts/dash_creator/libs/chartCreator.js +1582 -0
- data/app/assets/javascripts/dash_creator/libs/dashboardCreator.js +531 -0
- data/app/assets/javascripts/dash_creator/libs/daterangepicker.js +1626 -0
- data/app/assets/javascripts/dash_creator/libs/filterCreator.js +733 -0
- data/app/assets/javascripts/dash_creator/libs/jquery-ui.js +18706 -0
- data/app/assets/javascripts/dash_creator/libs/jquery.minicolors.js +1108 -0
- data/app/assets/javascripts/dash_creator/libs/moment.js +4301 -0
- data/app/assets/javascripts/dash_creator/libs/tether.js +1811 -0
- data/app/assets/javascripts/dash_creator/user.js +2 -0
- data/app/assets/stylesheets/dash_creator/application.css +16 -0
- data/app/assets/stylesheets/dash_creator/chart.css +4 -0
- data/app/assets/stylesheets/dash_creator/dashboard.css +4 -0
- data/app/assets/stylesheets/dash_creator/dashboard_object.css +4 -0
- data/app/assets/stylesheets/dash_creator/filter.css +4 -0
- data/app/assets/stylesheets/dash_creator/fonts/FontAwesome.otf +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.eot +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.svg +2671 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.ttf +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.woff +0 -0
- data/app/assets/stylesheets/dash_creator/fonts/fontawesome-webfont.woff2 +0 -0
- data/app/assets/stylesheets/dash_creator/libs/font-awesome.css +2199 -0
- data/app/assets/stylesheets/dash_creator/libs/jquery.minicolors.css +319 -0
- data/app/assets/stylesheets/dash_creator/libs/jquery.minicolors.png +0 -0
- data/app/assets/stylesheets/dash_creator/libs/style.css +13714 -0
- data/app/assets/stylesheets/dash_creator/user.css +4 -0
- data/app/controllers/dash_creator/application_controller.rb +3 -0
- data/app/controllers/dash_creator/chart_controller.rb +81 -0
- data/app/controllers/dash_creator/dashboard_controller.rb +37 -0
- data/app/controllers/dash_creator/dashboard_object_controller.rb +34 -0
- data/app/controllers/dash_creator/filter_controller.rb +60 -0
- data/app/controllers/dash_creator/user_controller.rb +82 -0
- data/app/helpers/dash_creator/application_helper.rb +4 -0
- data/app/helpers/dash_creator/chart_helper.rb +829 -0
- data/app/helpers/dash_creator/dashboard_helper.rb +4 -0
- data/app/helpers/dash_creator/dashboard_object_helper.rb +4 -0
- data/app/helpers/dash_creator/filter_helper.rb +237 -0
- data/app/helpers/dash_creator/user_helper.rb +4 -0
- data/app/jobs/dash_creator/application_job.rb +4 -0
- data/app/mailers/dash_creator/application_mailer.rb +6 -0
- data/app/models/dash_creator/application_record.rb +5 -0
- data/app/models/dash_creator/chart.rb +21 -0
- data/app/models/dash_creator/dashboard.rb +13 -0
- data/app/models/dash_creator/dashboard_object.rb +7 -0
- data/app/models/dash_creator/filter.rb +22 -0
- data/app/views/dash_creator/chart/_chart_creator.html.erb +230 -0
- data/app/views/dash_creator/chart/_modals.html.erb +74 -0
- data/app/views/dash_creator/chart/_plot_chart.html.erb +45 -0
- data/app/views/dash_creator/chart/create_chart.js.erb +53 -0
- data/app/views/dash_creator/dashboard/_dashboard_creator.html.erb +182 -0
- data/app/views/dash_creator/dashboard/_modals.html.erb +74 -0
- data/app/views/dash_creator/dashboard_object/_chart.html.erb +9 -0
- data/app/views/dash_creator/dashboard_object/_stat.html.erb +24 -0
- data/app/views/dash_creator/dashboard_object/_table.html.erb +16 -0
- data/app/views/dash_creator/filter/_filtering_card.html.erb +132 -0
- data/app/views/dash_creator/filter/_modals.html.erb +51 -0
- data/app/views/dash_creator/filter/_result_tables.html.erb +55 -0
- data/app/views/dash_creator/filter/apply_filtering.js.erb +46 -0
- data/app/views/dash_creator/filter/create_stat.js.erb +20 -0
- data/app/views/dash_creator/layouts/application.html.erb +64 -0
- data/app/views/dash_creator/layouts/menu/_left_menu.html.erb +12 -0
- data/app/views/dash_creator/user/_section_card.html.erb +17 -0
- data/app/views/dash_creator/user/creator.html.erb +11 -0
- data/app/views/dash_creator/user/dashboard.html.erb +10 -0
- data/config/initializers/dash_creator.rb +0 -0
- data/config/routes.rb +30 -0
- data/lib/dash_creator.rb +87 -0
- data/lib/dash_creator/acts_as_dash_creator.rb +16 -0
- data/lib/dash_creator/acts_as_dashboard_object.rb +19 -0
- data/lib/dash_creator/engine.rb +11 -0
- data/lib/dash_creator/version.rb +3 -0
- data/lib/generators/dash_creator/install/install_generator.rb +70 -0
- data/lib/generators/dash_creator/install/templates/_chart.html.erb +9 -0
- data/lib/generators/dash_creator/install/templates/_section_card.html.erb +17 -0
- data/lib/generators/dash_creator/install/templates/_stat.html.erb +24 -0
- data/lib/generators/dash_creator/install/templates/_table.html.erb +16 -0
- data/lib/generators/dash_creator/install/templates/add_indexes_to_dash_creator_tables.rb +15 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_charts.rb +25 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_dashboard_objects.rb +36 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_dashboards.rb +28 -0
- data/lib/generators/dash_creator/install/templates/create_dash_creator_filters.rb +22 -0
- data/lib/generators/dash_creator/install/templates/dashboard.html.erb +10 -0
- data/lib/generators/dash_creator/install/templates/initializer.rb +48 -0
- data/lib/tasks/dash_creator_tasks.rake +4 -0
- 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> '
|
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
|
+
});
|