discerner 1.1.18
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 +97 -0
- data/Rakefile +29 -0
- data/app/assets/images/discerner/add.png +0 -0
- data/app/assets/images/discerner/ajax-loader.gif +0 -0
- data/app/assets/images/discerner/bar.gif +0 -0
- data/app/assets/images/discerner/bullet_arrow_down.png +0 -0
- data/app/assets/images/discerner/bullet_arrow_up.png +0 -0
- data/app/assets/images/discerner/cog.png +0 -0
- data/app/assets/images/discerner/delete.png +0 -0
- data/app/assets/images/discerner/edit.png +0 -0
- data/app/assets/images/discerner/excel.png +0 -0
- data/app/assets/images/discerner/greencheck.gif +0 -0
- data/app/assets/images/discerner/rails.png +0 -0
- data/app/assets/images/discerner/show.png +0 -0
- data/app/assets/images/discerner/switch_minus.gif +0 -0
- data/app/assets/images/discerner/switch_plus.gif +0 -0
- data/app/assets/javascripts/discerner/application.js +20 -0
- data/app/assets/javascripts/discerner/combobox.js +301 -0
- data/app/assets/javascripts/discerner/discerner.js +19 -0
- data/app/assets/javascripts/discerner/export_parameters.js +13 -0
- data/app/assets/javascripts/discerner/jquery/jquery.blockUI.js +576 -0
- data/app/assets/javascripts/discerner/jquery/jquery.form.js +1074 -0
- data/app/assets/javascripts/discerner/nested_attributes.js +69 -0
- data/app/assets/javascripts/discerner/search_combinations.js +49 -0
- data/app/assets/javascripts/discerner/search_parameter.js +131 -0
- data/app/assets/javascripts/discerner/search_parameter_value.js +172 -0
- data/app/assets/javascripts/discerner/searches.js +93 -0
- data/app/assets/javascripts/discerner/url.js +42 -0
- data/app/assets/stylesheets/discerner/application.css +16 -0
- data/app/assets/stylesheets/discerner/buttons.sass +13 -0
- data/app/assets/stylesheets/discerner/categorized_autocompleter.sass +53 -0
- data/app/assets/stylesheets/discerner/discerner.sass +51 -0
- data/app/assets/stylesheets/discerner/export_parameters.sass +43 -0
- data/app/assets/stylesheets/discerner/links.sass +29 -0
- data/app/assets/stylesheets/discerner/searches.sass +205 -0
- data/app/assets/stylesheets/discerner/tables.sass +32 -0
- data/app/controllers/discerner/application_controller.rb +2 -0
- data/app/controllers/discerner/export_parameters_controller.rb +5 -0
- data/app/controllers/discerner/parameters_controller.rb +5 -0
- data/app/controllers/discerner/searches_controller.rb +5 -0
- data/app/helpers/discerner/application_helper.rb +4 -0
- data/app/helpers/discerner/parameters_helper.rb +5 -0
- data/app/helpers/discerner/searches_helper.rb +5 -0
- data/app/models/discerner/dictionary.rb +5 -0
- data/app/models/discerner/export_parameter.rb +5 -0
- data/app/models/discerner/operator.rb +5 -0
- data/app/models/discerner/parameter.rb +5 -0
- data/app/models/discerner/parameter_category.rb +5 -0
- data/app/models/discerner/parameter_type.rb +5 -0
- data/app/models/discerner/parameter_value.rb +5 -0
- data/app/models/discerner/parameter_value_categorization.rb +5 -0
- data/app/models/discerner/parameter_value_category.rb +5 -0
- data/app/models/discerner/search.rb +5 -0
- data/app/models/discerner/search_combination.rb +5 -0
- data/app/models/discerner/search_parameter.rb +5 -0
- data/app/models/discerner/search_parameter_value.rb +5 -0
- data/app/views/discerner/export_parameters/index.html.haml +55 -0
- data/app/views/discerner/parameters/_values_autocompleter.html.haml +49 -0
- data/app/views/discerner/parameters/values.html.haml +1 -0
- data/app/views/discerner/searches/_form.html.haml +21 -0
- data/app/views/discerner/searches/_form_combined_searches.html.haml +32 -0
- data/app/views/discerner/searches/_form_controls.html.haml +8 -0
- data/app/views/discerner/searches/_form_header.html.haml +39 -0
- data/app/views/discerner/searches/_form_search_parameters.html.haml +28 -0
- data/app/views/discerner/searches/_list.html.haml +17 -0
- data/app/views/discerner/searches/_search_combination_fields.html.haml +22 -0
- data/app/views/discerner/searches/_search_parameter_fields.html.haml +48 -0
- data/app/views/discerner/searches/_search_parameter_value_fields.html.haml +62 -0
- data/app/views/discerner/searches/_summary.html.haml +25 -0
- data/app/views/discerner/searches/edit.html.haml +8 -0
- data/app/views/discerner/searches/index.html.haml +13 -0
- data/app/views/discerner/searches/index.js.haml +1 -0
- data/app/views/discerner/searches/new.html.haml +3 -0
- data/app/views/discerner/searches/rename.html.haml +6 -0
- data/app/views/discerner/searches/update.js.haml +7 -0
- data/app/views/discerner/shared/_categorized_autocompleter_items.html.haml +15 -0
- data/app/views/discerner/shared/_error_messages.html.haml +5 -0
- data/app/views/layouts/discerner/searches.html.erb +17 -0
- data/config/cucumber.yml +8 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20121004040716_create_discerner_dictionaries.rb +11 -0
- data/db/migrate/20121004153043_create_discerner_parameter_categories.rb +12 -0
- data/db/migrate/20121005194843_create_discerner_parameter_types.rb +11 -0
- data/db/migrate/20121005203223_create_discerner_parameters.rb +14 -0
- data/db/migrate/20121008154855_create_discerner_operators.rb +13 -0
- data/db/migrate/20121008160313_create_discerner_operators_parameter_types.rb +8 -0
- data/db/migrate/20121008161455_create_discerner_parameter_values.rb +13 -0
- data/db/migrate/20121008180829_create_discerner_searches.rb +11 -0
- data/db/migrate/20121008182443_create_discerner_search_parameters.rb +11 -0
- data/db/migrate/20121011205130_create_discerner_search_parameter_values.rb +15 -0
- data/db/migrate/20121211213215_add_dictionary_id_to_discerner_searches.rb +5 -0
- data/db/migrate/20130205193602_create_discerner_search_combinations.rb +12 -0
- data/db/migrate/20130211230636_create_discerner_export_parameters.rb +9 -0
- data/db/migrate/20130213185818_add_search_columns_to_discerner_parameters.rb +38 -0
- data/db/migrate/20130213205255_rename_database_name_to_search_value_in_discerner_parameter_values.rb +5 -0
- data/db/migrate/20130215165416_add_searchable_to_discerner_parameters.rb +5 -0
- data/db/migrate/20130218230257_add_exclusive_to_discerner_parameters.rb +5 -0
- data/db/migrate/20130220163015_replace_integer_type_with_numeric.rb +22 -0
- data/db/migrate/20130221172826_add_unique_identifier_to_discerner_parameters.rb +5 -0
- data/db/migrate/20130222052924_change_search_attribute_to_method.rb +29 -0
- data/db/migrate/20130222070959_add_export_columns_to_discerner_parameters.rb +6 -0
- data/db/migrate/20130227031747_add_unique_identifier_to_discerner_operators.rb +16 -0
- data/db/migrate/20130306015019_change_discerner_parameter_values_name.rb +9 -0
- data/db/migrate/20130306212430_add_deleted_at_to_discerner_search_parameters.rb +5 -0
- data/db/migrate/20130306212504_add_deleted_at_to_discerner_search_parameter_values.rb +5 -0
- data/db/migrate/20130306212527_add_deleted_at_to_discerner_search_combinations.rb +5 -0
- data/db/migrate/20130306212818_add_deleted_at_to_discerner_export_parameters.rb +5 -0
- data/db/migrate/20130311190717_add_operator_type_to_discerner_operators.rb +5 -0
- data/db/migrate/20131212175110_remove_extra_search_parameter_values.rb +10 -0
- data/db/migrate/20140204170625_create_discerner_parameter_value_categories.rb +16 -0
- data/db/migrate/20140204170646_create_discerner_parameter_value_categorizations.rb +12 -0
- data/db/migrate/20140227191827_remove_blank_parameter_values_from_export_parameters.rb +10 -0
- data/db/seeds.rb +0 -0
- data/lib/discerner/engine.rb +16 -0
- data/lib/discerner/methods/controllers/export_parameters_controller.rb +38 -0
- data/lib/discerner/methods/controllers/parameters_controller.rb +30 -0
- data/lib/discerner/methods/controllers/searches_controller.rb +204 -0
- data/lib/discerner/methods/helpers/searches_helper.rb +145 -0
- data/lib/discerner/methods/models/dictionary.rb +54 -0
- data/lib/discerner/methods/models/export_parameter.rb +35 -0
- data/lib/discerner/methods/models/operator.rb +42 -0
- data/lib/discerner/methods/models/parameter.rb +90 -0
- data/lib/discerner/methods/models/parameter_category.rb +60 -0
- data/lib/discerner/methods/models/parameter_type.rb +33 -0
- data/lib/discerner/methods/models/parameter_value.rb +87 -0
- data/lib/discerner/methods/models/parameter_value_categorization.rb +21 -0
- data/lib/discerner/methods/models/parameter_value_category.rb +45 -0
- data/lib/discerner/methods/models/search.rb +147 -0
- data/lib/discerner/methods/models/search_combination.rb +50 -0
- data/lib/discerner/methods/models/search_parameter.rb +128 -0
- data/lib/discerner/methods/models/search_parameter_value.rb +130 -0
- data/lib/discerner/methods/models/soft_delete.rb +35 -0
- data/lib/discerner/methods/models/warning.rb +15 -0
- data/lib/discerner/parser.rb +497 -0
- data/lib/discerner/version.rb +3 -0
- data/lib/discerner.rb +4 -0
- data/lib/generators/discerner/dictionary/dictionary_generator.rb +38 -0
- data/lib/generators/discerner/dictionary/templates/model.rb +15 -0
- data/lib/generators/discerner/dictionary/templates/show.xls.erb +37 -0
- data/lib/generators/discerner/dictionary/templates/view.html.haml +2 -0
- data/lib/generators/discerner/install/install_generator.rb +153 -0
- data/lib/generators/discerner/install/templates/controllers/export_parameters_controller.rb +20 -0
- data/lib/generators/discerner/install/templates/controllers/parameters_controller.rb +15 -0
- data/lib/generators/discerner/install/templates/controllers/searches_controller.rb +39 -0
- data/lib/generators/discerner/install/templates/dictionaries.yml +236 -0
- data/lib/generators/discerner/install/templates/helpers/searches_helper.rb +9 -0
- data/lib/generators/discerner/install/templates/models/book.rb +15 -0
- data/lib/generators/discerner/install/templates/models/dictionary.rb +9 -0
- data/lib/generators/discerner/install/templates/models/export_parameter.rb +9 -0
- data/lib/generators/discerner/install/templates/models/operator.rb +9 -0
- data/lib/generators/discerner/install/templates/models/parameter.rb +10 -0
- data/lib/generators/discerner/install/templates/models/parameter_category.rb +9 -0
- data/lib/generators/discerner/install/templates/models/parameter_type.rb +9 -0
- data/lib/generators/discerner/install/templates/models/parameter_value.rb +9 -0
- data/lib/generators/discerner/install/templates/models/parameter_value_categorization.rb +9 -0
- data/lib/generators/discerner/install/templates/models/parameter_value_category.rb +9 -0
- data/lib/generators/discerner/install/templates/models/search.rb +9 -0
- data/lib/generators/discerner/install/templates/models/search_combination.rb +9 -0
- data/lib/generators/discerner/install/templates/models/search_parameter.rb +9 -0
- data/lib/generators/discerner/install/templates/models/search_parameter_value.rb +9 -0
- data/lib/generators/discerner/install/templates/views/layouts/searches.html.erb +17 -0
- data/lib/setup/operators.yml +91 -0
- data/lib/tasks/cucumber.rake +65 -0
- data/lib/tasks/discerner_tasks.rake +30 -0
- metadata +531 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
NestedAttributes = function (config) {
|
|
2
|
+
var that = this,
|
|
3
|
+
defaultConfig = {
|
|
4
|
+
removeStyle: 'hide',
|
|
5
|
+
undeleteOnChange: false,
|
|
6
|
+
add: true,
|
|
7
|
+
remove: true
|
|
8
|
+
};
|
|
9
|
+
this.config = $.extend(defaultConfig, config);
|
|
10
|
+
|
|
11
|
+
if (this.config.undeleteOnChange) {
|
|
12
|
+
$(this.config.container).find("input, select, textarea").change(function () {
|
|
13
|
+
$(that.config.container).find('.delete_' + that.config.association).prev("input[type=hidden]").val("0");
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$(this.config.container).find("input, select, textarea").focus(function () {
|
|
18
|
+
$(this).select();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (this.config.remove) {
|
|
22
|
+
var link = $(this.config.container).find('.delete_' + this.config.association)
|
|
23
|
+
$(document).off('click', link.selector)
|
|
24
|
+
$(document).on('click', link.selector, function() {
|
|
25
|
+
that.remove_fields(this, that.config.removeStyle);
|
|
26
|
+
|
|
27
|
+
if (that.config.removeHandler) {
|
|
28
|
+
that.config.removeHandler.call(that.config.caller);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (this.config.add) {
|
|
34
|
+
var link = $(this.config.container).find('.add_' + this.config.association)
|
|
35
|
+
$(document).off('click', link.selector)
|
|
36
|
+
$(document).on('click', link.selector, function() {
|
|
37
|
+
that.add_fields(this, that.config.association, that.config.content);
|
|
38
|
+
|
|
39
|
+
if (that.config.addHandler) {
|
|
40
|
+
that.config.addHandler.call(that.config.caller);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
NestedAttributes.prototype = {
|
|
47
|
+
remove_fields: function (link, removeStyle) {
|
|
48
|
+
$(link).prev("input[type=hidden]").val("1");
|
|
49
|
+
if (removeStyle === 'hide' ) {
|
|
50
|
+
$(link).closest(".fields").hide();
|
|
51
|
+
$(link).closest(".fields").prev('.error').hide();
|
|
52
|
+
}
|
|
53
|
+
else if (removeStyle === 'clear' ) {
|
|
54
|
+
$(link).closest(".fields").find('input').clearFields();
|
|
55
|
+
$(link).closest(".fields").find('select').clearFields();
|
|
56
|
+
$(link).closest(".fields").find('textarea').clearFields();
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
add_fields: function(link, association, content) {
|
|
61
|
+
var new_id = new Date().getTime(),
|
|
62
|
+
regexp1 = new RegExp("new_" + association, "g"),
|
|
63
|
+
regexp2 = new RegExp("new_nested_record", "g");
|
|
64
|
+
|
|
65
|
+
content = content.replace(regexp1, new_id);
|
|
66
|
+
content = content.replace(regexp2, 'new_nested_record_' + new_id);
|
|
67
|
+
$(link).closest("." + association).find('.nested_records_' + association).append(content);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Place all the behaviors and hooks related to the matching controller here.
|
|
2
|
+
// All this logic will automatically be available in application.js.
|
|
3
|
+
|
|
4
|
+
Discerner.SearchCombination.UI = function (config) {
|
|
5
|
+
var dictionarySelector = $('.discerner_search_dictionary select#search_dictionary_id'),
|
|
6
|
+
selectedDictionaryOption = $(dictionarySelector).find('option:selected:last'),
|
|
7
|
+
|
|
8
|
+
setupCombinedSearches = function () {
|
|
9
|
+
var search_combinations = $('.search_combinations').find('.nested_records_search_combinations tr.search_combination'),
|
|
10
|
+
display_orders = search_combinations.find('input[name$="[display_order]"]'),
|
|
11
|
+
i = 0;
|
|
12
|
+
|
|
13
|
+
// set up predicate for selected searches
|
|
14
|
+
search_combinations.filter(':visible:not(:first)').find('.combined_search_operator span').html('and');
|
|
15
|
+
|
|
16
|
+
// get max display order
|
|
17
|
+
$.each(display_orders, function(){
|
|
18
|
+
var val = parseInt($(this).val());
|
|
19
|
+
if (val >= i) { i = val }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// assign display order to combined searches without it
|
|
23
|
+
$.each(display_orders, function(){
|
|
24
|
+
if ($(this).val().length == 0) {
|
|
25
|
+
i = i + 1
|
|
26
|
+
$(this).val(i);
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// hide combined searches options that do not belong to selected dictionary
|
|
31
|
+
if (dictionarySelector.length > 0){
|
|
32
|
+
dictionary_class = $(dictionarySelector).find('option:selected:last').attr('class')
|
|
33
|
+
$('.search_combinations_combobox_autocompleter').find('option:not(.' + dictionary_class +'):not([value=""])').detach();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// setup autocompleters for newly added row
|
|
37
|
+
$(".search_combinations_combobox_autocompleter").combobox({watermark:'an existing search'});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
searchCombinationNestedAttributesForm = new NestedAttributes({
|
|
41
|
+
container: $('.search_combinations'),
|
|
42
|
+
association: 'search_combinations',
|
|
43
|
+
content: config.searchCombinationsTemplate,
|
|
44
|
+
addHandler: setupCombinedSearches,
|
|
45
|
+
caller: this
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
setupCombinedSearches();
|
|
49
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Place all the behaviors and hooks related to the matching controller here.
|
|
2
|
+
// All this logic will automatically be available in application.js.
|
|
3
|
+
|
|
4
|
+
Discerner.SearchParameter.UI = function (config) {
|
|
5
|
+
var parametersUrl = new Discerner.Url(config.parametersUrl),
|
|
6
|
+
dictionarySelector = $('.discerner_search_dictionary select#search_dictionary_id'),
|
|
7
|
+
selectedDictionaryOption = $(dictionarySelector).find('option:selected:last'),
|
|
8
|
+
|
|
9
|
+
setupParameters = function () {
|
|
10
|
+
var search_parameters = $('.search_parameters').find('.nested_records_search_parameters tr.search_parameter'),
|
|
11
|
+
display_orders = search_parameters.find('input:not([name*="search_parameter_values_attributes"])[name$="[display_order]"]'),
|
|
12
|
+
i = 0;
|
|
13
|
+
|
|
14
|
+
search_parameters.filter(':visible:not(:first)').find('.parameter_boolean_operator span').html('and');
|
|
15
|
+
|
|
16
|
+
// remove parameter options that do not belong to selected dictionary
|
|
17
|
+
if (dictionarySelector.length > 0){
|
|
18
|
+
dictionary_class = $(dictionarySelector).find('option:selected:last').attr('class');
|
|
19
|
+
$.each($('div.parameter_category'), function(){
|
|
20
|
+
if (!$(this).hasClass(dictionary_class)) {
|
|
21
|
+
$(this).remove();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
$.each($('select.parameters_combobox_autocompleter option'), function(){
|
|
25
|
+
if (!($(this).hasClass(dictionary_class) || $(this).val() == '')) {
|
|
26
|
+
$(this).remove();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
$(".parameters_combobox_autocompleter").combobox({watermark:'a criteria'});
|
|
31
|
+
|
|
32
|
+
// get max display order
|
|
33
|
+
$.each(display_orders, function(){
|
|
34
|
+
var val = parseInt($(this).val());
|
|
35
|
+
if (val >= i) { i = val }
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// assign display order to search predicates without it
|
|
39
|
+
$.each(display_orders, function(){
|
|
40
|
+
if ($(this).val().length == 0) {
|
|
41
|
+
i = i + 1
|
|
42
|
+
$(this).val(i);
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
},
|
|
46
|
+
searchParametersNestedAttributesForm = new NestedAttributes({
|
|
47
|
+
container: $('.search_parameters'),
|
|
48
|
+
association: 'search_parameters',
|
|
49
|
+
content: config.searchParametersTemplate,
|
|
50
|
+
addHandler: setupParameters,
|
|
51
|
+
caller: this
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// handle criteria autocompleter button click
|
|
55
|
+
$(document).on('click', '.parameter .categorized_autocompleter_link', function () {
|
|
56
|
+
var popup = $(this).siblings('.div-category-popup'),
|
|
57
|
+
select = $(this).siblings('select').first(),
|
|
58
|
+
// get all the "sibling" dropdowns
|
|
59
|
+
sibling_selects = $('select.' + select.attr('class')).filter(function(){
|
|
60
|
+
return $(this).closest('tr').find('td.remove input[name$="[_destroy]"]:not([name*="[search_parameter_values_attributes]"])').filter(function() { return this.value === '1'; }).length == 0 // exclude rows marked for destroy
|
|
61
|
+
}),
|
|
62
|
+
// get all selected parameter options from sibling selects
|
|
63
|
+
matching_selected_options = sibling_selects.find('option.exclusive:selected');
|
|
64
|
+
// match selected options from sibling selects with the source select options
|
|
65
|
+
// (they will have different base ids for each set but same value)
|
|
66
|
+
popup.find('.criteria a.categorized_autocompleter_item_link').removeClass('selection_disabled');
|
|
67
|
+
$.each(matching_selected_options, function(){
|
|
68
|
+
option = select.find('option[value=' + $(this).val() + ']');
|
|
69
|
+
popup.find('.parameter_category .criteria a.categorized_autocompleter_item_link[rel="' + $(option).attr('id') + '"]').addClass('selection_disabled');
|
|
70
|
+
})
|
|
71
|
+
toggleCategorizedAutocompleterPopup(this);
|
|
72
|
+
toggleCategorizedAutocompleterPopup($(this).closest('.search_parameter').find('.parameter_value a.expanded_categorized_autocompleter_link'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// handle criteria popup list link click
|
|
76
|
+
$(document).on('click', '.parameter .categorized_autocompleter_item_link:not(.selection_disabled)', function () {
|
|
77
|
+
var autocompleter = $(this).parents('.categorized_autocompleter').find('select.parameters_combobox_autocompleter'),
|
|
78
|
+
categorizedItemEl = document.getElementById($(this).attr('rel'));
|
|
79
|
+
categorizedAutocompleterLink = $(this).parents('.categorized_autocompleter').find('.categorized_autocompleter_link');
|
|
80
|
+
autocompleter.combobox('setValue', categorizedItemEl.text);
|
|
81
|
+
autocompleter.change();
|
|
82
|
+
toggleCategorizedAutocompleterPopup(categorizedAutocompleterLink);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// toggle criteria popup
|
|
86
|
+
var toggleCategorizedAutocompleterPopup = function (link) {
|
|
87
|
+
var popup = $(link).parents('.categorized_autocompleter').find('.div-category-popup');
|
|
88
|
+
if ($(link).hasClass('collapsed_categorized_autocompleter_link')) {
|
|
89
|
+
popup.show();
|
|
90
|
+
$(link).removeClass('collapsed_categorized_autocompleter_link');
|
|
91
|
+
$(link).addClass('expanded_categorized_autocompleter_link');
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
popup.hide();
|
|
95
|
+
$(link).removeClass('expanded_categorized_autocompleter_link');
|
|
96
|
+
$(link).addClass('collapsed_categorized_autocompleter_link');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// handle changing selections for criteria autocompleter
|
|
101
|
+
$(document).on('change', '.parameter select.parameters_combobox_autocompleter', function () {
|
|
102
|
+
var that = this,
|
|
103
|
+
predicate = $(that).closest('.search_parameter'),
|
|
104
|
+
selected_option = $(that).find('option:selected:last'),
|
|
105
|
+
add_button = $(predicate).find('a.add_search_parameter_values');
|
|
106
|
+
|
|
107
|
+
$(predicate).find('.nested_records_search_parameter_values .search_parameter_value .delete_search_parameter_values').click();
|
|
108
|
+
if ($(selected_option).length == 0 || $(selected_option).val() == ''){
|
|
109
|
+
$(add_button).hide();
|
|
110
|
+
}
|
|
111
|
+
else if ($(selected_option).hasClass('list')){
|
|
112
|
+
$($(predicate).find('.tmp_link')).remove();
|
|
113
|
+
console.log(parametersUrl.sub({ question_id: this.value }))
|
|
114
|
+
$.get(parametersUrl.sub({ question_id: this.value }), function (data) {
|
|
115
|
+
$.each(data.parameter_values, function() {
|
|
116
|
+
$(add_button).click();
|
|
117
|
+
var row = $(predicate).find('.nested_records_search_parameter_values .search_parameter_value').filter(':last');
|
|
118
|
+
row.find('.parameter_value span').text(this.name);
|
|
119
|
+
row.find('.parameter_value_id').val(this.parameter_value_id);
|
|
120
|
+
$(add_button).hide();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
$(add_button).show();
|
|
125
|
+
$(add_button).click();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
setupParameters();
|
|
130
|
+
};
|
|
131
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Discerner.SearchParameterValue.UI = function (config) {
|
|
2
|
+
var that = this,
|
|
3
|
+
parametersUrl = new Discerner.Url(config.parametersUrl),
|
|
4
|
+
hideValue = function(e){
|
|
5
|
+
if (!$(e).hasClass('invisible')){
|
|
6
|
+
$(e).addClass('invisible');
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
showValue = function(e){
|
|
10
|
+
if ($(e).hasClass('invisible')){
|
|
11
|
+
$(e).removeClass('invisible');
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
setupOperator = function(o){
|
|
15
|
+
var row = $(o).closest('tr');
|
|
16
|
+
option = $(o).find('option:selected');
|
|
17
|
+
if (option.hasClass('range')){
|
|
18
|
+
showValue($(row).find('.additional_value'));
|
|
19
|
+
} else {
|
|
20
|
+
hideValue($(row).find('.additional_value'));
|
|
21
|
+
}
|
|
22
|
+
if (option.hasClass('presence')){
|
|
23
|
+
hideValue($(row).find('.value'));
|
|
24
|
+
} else {
|
|
25
|
+
showValue($(row).find('.value'));
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
setupParameterValues = function(){
|
|
29
|
+
var row = $(config.container).find('.nested_records_search_parameter_values .search_parameter_value').last(),
|
|
30
|
+
selectedParameter = $(config.container).find('select.parameters_combobox_autocompleter option:selected:last');
|
|
31
|
+
|
|
32
|
+
row.find('td').hide();
|
|
33
|
+
hideValue($(row).find('.additional_value').show());
|
|
34
|
+
row.find('.parameter_values_boolean_operator').show();
|
|
35
|
+
|
|
36
|
+
if ($(selectedParameter).hasClass('list')) { // list parameters
|
|
37
|
+
row.find('.chosen, .parameter_value').show();
|
|
38
|
+
$(config.container).find('a.add_search_parameter_values').hide();
|
|
39
|
+
$(config.container).find('.additional_value').hide();
|
|
40
|
+
} else if ($(selectedParameter).hasClass('combobox')) { // combobox parameters
|
|
41
|
+
var input = $(row).find('input.parameter_value_id');
|
|
42
|
+
$(config.container).find('.additional_value').hide();
|
|
43
|
+
if (input.length > 0) {
|
|
44
|
+
$.get( parametersUrl.sub({ question_id: selectedParameter.val(), search_parameter_value_id: input.closest('tr.search_parameter_value').attr('id') }), function( data ) {
|
|
45
|
+
container = $(input).closest('td');
|
|
46
|
+
$(input).detach();
|
|
47
|
+
container.append( data );
|
|
48
|
+
container.find('select').attr('id', input.attr('id')).attr('name', input.attr('name')).combobox({ watermark:'a value'});
|
|
49
|
+
handleParameterValuelPopupListClick(row);
|
|
50
|
+
handleParameterValueAutocompleterButtonLink(row);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
$(config.container).find('.parameter_value, .remove').show();
|
|
54
|
+
$(config.container).find('a.add_search_parameter_values').show();
|
|
55
|
+
} else { // date, text and numeric parameters
|
|
56
|
+
var parameter_classes = ['date', 'numeric', 'text', 'string'];
|
|
57
|
+
for (var i in parameter_classes) {
|
|
58
|
+
if ($(selectedParameter).hasClass(parameter_classes[i])) {
|
|
59
|
+
row.find('.operator option:not(.' + parameter_classes[i] +')').detach();
|
|
60
|
+
row.find('.operator, .value, .remove').show();
|
|
61
|
+
$(config.container).find('a.add_search_parameter_values').show();
|
|
62
|
+
if (parameter_classes[i] == 'date') {
|
|
63
|
+
row.find('.value input, .additional_value input').addClass('datepicker');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
$(config.container).find('.nested_records_search_parameter_values tr.search_parameter_value:visible:not(:first)')
|
|
69
|
+
.find('.parameter_values_boolean_operator span').html('or');
|
|
70
|
+
|
|
71
|
+
var i = 0,
|
|
72
|
+
display_orders = config.container.find('input[name*="search_parameter_values_attributes"][name$="[display_order]"]');
|
|
73
|
+
|
|
74
|
+
// get max display order
|
|
75
|
+
$.each(display_orders, function(){
|
|
76
|
+
var val = parseInt($(this).val());
|
|
77
|
+
if (val >= i) { i = val }
|
|
78
|
+
});
|
|
79
|
+
// assign display order to search predicates without it
|
|
80
|
+
$.each(display_orders, function(){
|
|
81
|
+
if ($(this).val().length == 0) {
|
|
82
|
+
i = i + 1
|
|
83
|
+
$(this).val(i);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
$(config.container).find(".parameter_values_combobox_autocompleter").combobox({watermark:'a value'});
|
|
87
|
+
},
|
|
88
|
+
searchParameterValuesNestedAttributesForm = new NestedAttributes({
|
|
89
|
+
container: config.container,
|
|
90
|
+
association: 'search_parameter_values',
|
|
91
|
+
content: config.searchParameterValuesTemplate,
|
|
92
|
+
addHandler: setupParameterValues,
|
|
93
|
+
caller: this
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// toggle parameter value popup
|
|
97
|
+
var toggleCategorizedAutocompleterPopup = function (link) {
|
|
98
|
+
var popup = $(link).parents('.categorized_autocompleter').find('.div-category-popup');
|
|
99
|
+
if ($(link).hasClass('collapsed_categorized_autocompleter_link')) {
|
|
100
|
+
popup.show();
|
|
101
|
+
$(link).removeClass('collapsed_categorized_autocompleter_link');
|
|
102
|
+
$(link).addClass('expanded_categorized_autocompleter_link');
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
popup.hide();
|
|
106
|
+
$(link).removeClass('expanded_categorized_autocompleter_link');
|
|
107
|
+
$(link).addClass('collapsed_categorized_autocompleter_link');
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// handle parameter value autocompleter button click
|
|
112
|
+
var handleParameterValueAutocompleterButtonLink = function(container){
|
|
113
|
+
$(container).find('.parameter_value .categorized_autocompleter_link').on('click', function(){
|
|
114
|
+
var popup = $(this).siblings('.div-category-popup'),
|
|
115
|
+
select = $(this).siblings('select').first(),
|
|
116
|
+
// get all the "sibling" dropdowns
|
|
117
|
+
sibling_selects = $('select.' + select.attr('class')).filter(function(){
|
|
118
|
+
return $(this).closest('tr').find('td.remove input[name$="[_destroy]"]:not([name*="[search_parameter_values_attributes]"])').filter(function() { return this.value === '1'; }).length == 0 // exclude rows marked for destroy
|
|
119
|
+
}),
|
|
120
|
+
// get all selected parameter options from sibling selects
|
|
121
|
+
matching_selected_options = sibling_selects.find('option.exclusive:selected');
|
|
122
|
+
// match selected options from sibling selects with the source select options
|
|
123
|
+
// (they will have different base ids for each set but same value)
|
|
124
|
+
popup.find('.parameter value a.categorized_autocompleter_item_link').removeClass('selection_disabled');
|
|
125
|
+
$.each(matching_selected_options, function(){
|
|
126
|
+
option = select.find('option[value=' + $(this).val() + ']');
|
|
127
|
+
popup.find('.search_parameter .parameter value a.categorized_autocompleter_item_link[rel="' + $(option).attr('id') + '"]').addClass('selection_disabled');
|
|
128
|
+
})
|
|
129
|
+
toggleCategorizedAutocompleterPopup(this);
|
|
130
|
+
toggleCategorizedAutocompleterPopup($(this).closest('.search_parameter').find('.parameter a.expanded_categorized_autocompleter_link'));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// handle parameter value popup list link click
|
|
135
|
+
var handleParameterValuelPopupListClick = function(container) {
|
|
136
|
+
$(container).find('.parameter_value .categorized_autocompleter_item_link:not(.selection_disabled)').on('click', function(e){
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
var autocompleter = $(this).parents('.categorized_autocompleter').find('select.parameter_values_combobox_autocompleter'),
|
|
139
|
+
categorizedItemEl = document.getElementById($(this).attr('rel'));
|
|
140
|
+
categorizedAutocompleterLink = $(this).parents('.categorized_autocompleter').find('.categorized_autocompleter_link');
|
|
141
|
+
autocompleter.combobox('setValue', categorizedItemEl.text);
|
|
142
|
+
autocompleter.change();
|
|
143
|
+
toggleCategorizedAutocompleterPopup(categorizedAutocompleterLink);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setupParameterValues();
|
|
148
|
+
handleParameterValuelPopupListClick(config.container);
|
|
149
|
+
handleParameterValueAutocompleterButtonLink(config.container);
|
|
150
|
+
|
|
151
|
+
$.each($('.operator'), function() { setupOperator(this); });
|
|
152
|
+
|
|
153
|
+
$(document).on('change', '.operator select', function(){
|
|
154
|
+
setupOperator(this);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
$(document).on('click', '.show-category-items', function(e) {
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
$(this).closest('.categorized-parameter-values, .uncategorized-parameter-values').find('.category-items').show();
|
|
160
|
+
$(this).addClass('hide-category-items');
|
|
161
|
+
$(this).removeClass('show-category-items');
|
|
162
|
+
$(this).html('less')
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
$(document).on('click', '.hide-category-items', function(e) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
$(this).closest('.categorized-parameter-values, .uncategorized-parameter-values').find('.category-items').hide();
|
|
168
|
+
$(this).addClass('show-category-items');
|
|
169
|
+
$(this).removeClass('hide-category-items');
|
|
170
|
+
$(this).html('more')
|
|
171
|
+
});
|
|
172
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Place all the behaviors and hooks related to the matching controller here.
|
|
2
|
+
// All this logic will automatically be available in application.js.
|
|
3
|
+
|
|
4
|
+
Discerner.Search.UI = function (config) {
|
|
5
|
+
var dictionarySelector = $('.discerner_search_dictionary select#search_dictionary_id'),
|
|
6
|
+
dictionaryContainer = $('.discerner_search_dictionary span'),
|
|
7
|
+
selectedDictionaryOption = $(dictionarySelector).find('option:selected:last');
|
|
8
|
+
|
|
9
|
+
var toggleControls = function() {
|
|
10
|
+
selectedDictionaryOption = $(dictionarySelector).find('option:selected:last');
|
|
11
|
+
if (dictionarySelector.length > 0 && selectedDictionaryOption.length == 0 || selectedDictionaryOption.val() == '') {
|
|
12
|
+
$('.search_parameters').hide();
|
|
13
|
+
$('.search_combinations').hide();
|
|
14
|
+
$('.discerner_dictionary_required_message').show();
|
|
15
|
+
} else {
|
|
16
|
+
$('.search_parameters').show();
|
|
17
|
+
$('.search_combinations').show();
|
|
18
|
+
$('.discerner_dictionary_required_message').hide();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// handle dictionary selection change
|
|
23
|
+
$(dictionarySelector).bind('change', function(){
|
|
24
|
+
$('a.delete_search_parameters').trigger('click');
|
|
25
|
+
$('a.delete_search_combinations').trigger('click');
|
|
26
|
+
toggleControls();
|
|
27
|
+
$('a.add_search_parameters').trigger('click');
|
|
28
|
+
$('a.add_search_combinations').trigger('click');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// block UI on form submit
|
|
32
|
+
$('#discerner_search_form form, #results form').bind('submit', function(){
|
|
33
|
+
$.blockUI({
|
|
34
|
+
title: 'Loading ... ',
|
|
35
|
+
message: '<p>Please be patient</p>',
|
|
36
|
+
fadeIn: 0,
|
|
37
|
+
timeout: 20000000,
|
|
38
|
+
showOverlay: true,
|
|
39
|
+
centerY: true,
|
|
40
|
+
theme: true
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
$(document).on('mouseover', '.datepicker', function() {
|
|
45
|
+
$(this).datepicker({
|
|
46
|
+
altFormat: 'yy-mm-dd',
|
|
47
|
+
dateFormat: 'yy-mm-dd',
|
|
48
|
+
changeMonth: true,
|
|
49
|
+
changeYear: true,
|
|
50
|
+
yearRange: config.yearRange
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// handle search name editing
|
|
55
|
+
$('#discerner_search_form .discerner_search_name_edit a').bind('click', function(){
|
|
56
|
+
$(this).closest('span.discerner_search_name_edit').hide();
|
|
57
|
+
$(this).closest('span.discerner_search_name_edit').siblings('span.discerner_search_name').hide();
|
|
58
|
+
$(this).closest('div').append($('<span>').load(config.renameUrl + ' form').addClass('discerner_search_name_edit_dialog'));
|
|
59
|
+
return false;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// handle search name editing (submit the form)
|
|
63
|
+
$(document).on('click','#discerner_search_form .discerner_search_name_edit_dialog input[type="submit"]', function(){
|
|
64
|
+
var form = $(this).closest('form');
|
|
65
|
+
$.ajax({
|
|
66
|
+
type: form.attr('method'),
|
|
67
|
+
url: form.attr('action'),
|
|
68
|
+
data: form.serializeArray(),
|
|
69
|
+
dataType: 'script'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return false;
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// handle cancel on search name editing
|
|
76
|
+
$(document).on('click', '#discerner_search_form .discerner_search_name_edit_dialog a.cancel', function() {
|
|
77
|
+
$('span.discerner_search_name_edit_dialog').siblings('span.discerner_search_name').show();
|
|
78
|
+
$('span.discerner_search_name_edit_dialog').siblings('span.discerner_search_name_edit').show();
|
|
79
|
+
$('span.discerner_search_name_edit_dialog').remove();
|
|
80
|
+
$("#messages").html('');
|
|
81
|
+
return false;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
$(document).ready(function(){
|
|
85
|
+
if ((dictionarySelector.length == 0 || selectedDictionaryOption.length != 0) && ($('tr.search_parameter:visible').length == 0)){
|
|
86
|
+
$('a.add_search_parameters').trigger('click');
|
|
87
|
+
$('a.add_search_combinations').trigger('click');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
toggleControls();
|
|
92
|
+
};
|
|
93
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Discerner.Url = function (url) {
|
|
2
|
+
this.url = url;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
Discerner.Url.prototype = {
|
|
6
|
+
sub: function (params) {
|
|
7
|
+
var url = this.url,
|
|
8
|
+
newUrl = this.url,
|
|
9
|
+
queryString = [],
|
|
10
|
+
encodedArg = undefined,
|
|
11
|
+
encodeParam = undefined,
|
|
12
|
+
param = undefined;
|
|
13
|
+
|
|
14
|
+
for (param in params) {
|
|
15
|
+
if (params.hasOwnProperty(param)) {
|
|
16
|
+
encodedParam = encodeURIComponent(param);
|
|
17
|
+
encodedArg = encodeURIComponent(params[param]);
|
|
18
|
+
newUrl = url.replace(':' + param, encodedArg);
|
|
19
|
+
if (url == newUrl) {
|
|
20
|
+
queryString.push(encodedParam + '=' + encodedArg);
|
|
21
|
+
}
|
|
22
|
+
url = newUrl;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (queryString.length > 0) {
|
|
27
|
+
if (url.indexOf('?') > 0) {
|
|
28
|
+
return url + queryString.join('&');
|
|
29
|
+
} else {
|
|
30
|
+
return url + '?' + queryString.join('&');
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
return url;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
parameters: function () {
|
|
38
|
+
return $.map(this.url.match(/:\w+/g) || [], function (o, i) {
|
|
39
|
+
return o.substring(1);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
|
10
|
+
*
|
|
11
|
+
*= require_self
|
|
12
|
+
*= require jquery.ui.autocomplete
|
|
13
|
+
*= require jquery.ui.datepicker
|
|
14
|
+
*= require jquery.ui.button
|
|
15
|
+
*= require_tree .
|
|
16
|
+
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#discerner_searches, #discerner_search
|
|
2
|
+
div.discerner-buttons
|
|
3
|
+
:margin 1em 0
|
|
4
|
+
|
|
5
|
+
a.discerner-button-negative, button.discerner-button-negative, input.discerner-button-negative
|
|
6
|
+
:color #800000
|
|
7
|
+
:font-weight bold
|
|
8
|
+
|
|
9
|
+
a.discerner-button-positive, button.discerner-button-positive, input.discerner-button-positive
|
|
10
|
+
:color #529214
|
|
11
|
+
:font-weight bold
|
|
12
|
+
|
|
13
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#discerner_searches, #discerner_search
|
|
2
|
+
.categorized_autocompleter_link, .categorized_autocompleter_link:hover
|
|
3
|
+
:padding 2px 20px 2px 2px
|
|
4
|
+
:text-decoration none !important
|
|
5
|
+
|
|
6
|
+
.collapsed_categorized_autocompleter_link, .collapsed_categorized_autocompleter_link:hover
|
|
7
|
+
:background url(bullet_arrow_down.png) 48px 2px no-repeat
|
|
8
|
+
.expanded_categorized_autocompleter_link, .expanded_categorized_autocompleter_link:hover
|
|
9
|
+
:background url(bullet_arrow_up.png) 48px 2px no-repeat
|
|
10
|
+
|
|
11
|
+
.div-category-popup
|
|
12
|
+
:position absolute
|
|
13
|
+
:z-index 1000
|
|
14
|
+
:background white
|
|
15
|
+
|
|
16
|
+
.div-category-list
|
|
17
|
+
:overflow auto
|
|
18
|
+
|
|
19
|
+
.category
|
|
20
|
+
:padding-bottom 5px
|
|
21
|
+
:padding-left 5px
|
|
22
|
+
:padding-right 5px
|
|
23
|
+
:padding-top 15px
|
|
24
|
+
:clear both
|
|
25
|
+
|
|
26
|
+
.category-name
|
|
27
|
+
:font-weight bold
|
|
28
|
+
:margin-bottom 0
|
|
29
|
+
:margin-left 0
|
|
30
|
+
:margin-right 0
|
|
31
|
+
:margin-top 0
|
|
32
|
+
:padding-bottom 0
|
|
33
|
+
:padding-left 0
|
|
34
|
+
:padding-right 0
|
|
35
|
+
:padding-top 0
|
|
36
|
+
|
|
37
|
+
.column
|
|
38
|
+
:float left
|
|
39
|
+
:margin-right 5px
|
|
40
|
+
:width 225px
|
|
41
|
+
|
|
42
|
+
.criteria
|
|
43
|
+
:margin-bottom 3px
|
|
44
|
+
:margin-left 0
|
|
45
|
+
:margin-right 0
|
|
46
|
+
:margin-top 3px
|
|
47
|
+
|
|
48
|
+
.categorized_autocompleter
|
|
49
|
+
:position relative
|
|
50
|
+
|
|
51
|
+
button.ui-button
|
|
52
|
+
:display none
|
|
53
|
+
|