Revert "Merge remote-tracking branch 'origin/master'"

This reverts commit c706b9d8b3, reversing
changes made to 30fc64e4fb.
pull/6827/head
Tim Felgentreff 14 years ago
parent c706b9d8b3
commit 7ac3316667
  1. BIN
      assets/images/arrow_B_down.gif
  2. BIN
      assets/images/arrow_B_left.gif
  3. BIN
      assets/images/arrow_B_right.gif
  4. BIN
      assets/images/arrow_B_up.gif
  5. BIN
      assets/images/arrow_D_down.gif
  6. BIN
      assets/images/arrow_D_left.gif
  7. BIN
      assets/images/arrow_D_right.gif
  8. BIN
      assets/images/arrow_D_up.gif
  9. BIN
      assets/images/button_hover.png
  10. BIN
      assets/images/button_inactive.png
  11. BIN
      assets/images/button_normal.png
  12. BIN
      assets/images/close.gif
  13. BIN
      assets/images/delete.gif
  14. BIN
      assets/images/disk.gif
  15. BIN
      assets/images/disks.gif
  16. BIN
      assets/images/icon_info_red.gif
  17. BIN
      assets/images/remove.gif
  18. 159
      assets/javascripts/cordinc_tooltip.js
  19. 432
      assets/javascripts/reporting.js
  20. 171
      assets/javascripts/reporting/controls.js
  21. 444
      assets/javascripts/reporting/filters.js
  22. 174
      assets/javascripts/reporting/group_bys.js
  23. 49
      assets/javascripts/reporting/progressbar.js
  24. 110
      assets/javascripts/reporting/prototype_progress_bar.js
  25. 104
      assets/javascripts/reporting/restore_query.js
  26. 151
      assets/javascripts/select_list_move_optgroup.js
  27. 56
      assets/stylesheets/help.css
  28. 416
      assets/stylesheets/reporting.css
  29. 15
      config/locales/de.yml
  30. 15
      config/locales/en.yml
  31. 5
      init.rb
  32. 48
      lib/report.rb
  33. 42
      lib/report/chainable.rb
  34. 334
      lib/report/controller.rb
  35. 77
      lib/report/filter/base.rb
  36. 11
      lib/report/filter/multi_choice.rb
  37. 10
      lib/report/group_by/base.rb
  38. 36
      lib/report/operator.rb
  39. 69
      lib/report/query_utils.rb
  40. 29
      lib/report/result.rb
  41. 41
      lib/report/sql_statement.rb
  42. 4
      lib/report/table.rb
  43. 59
      lib/widget.rb
  44. 126
      lib/widget/base.rb
  45. 7
      lib/widget/controls.rb
  46. 9
      lib/widget/controls/apply.rb
  47. 9
      lib/widget/controls/clear.rb
  48. 24
      lib/widget/controls/delete.rb
  49. 32
      lib/widget/controls/query_name.rb
  50. 10
      lib/widget/controls/save.rb
  51. 51
      lib/widget/controls/save_as.rb
  52. 79
      lib/widget/filters.rb
  53. 14
      lib/widget/filters/base.rb
  54. 26
      lib/widget/filters/date.rb
  55. 14
      lib/widget/filters/label.rb
  56. 38
      lib/widget/filters/multi_choice.rb
  57. 60
      lib/widget/filters/multi_values.rb
  58. 27
      lib/widget/filters/operators.rb
  59. 11
      lib/widget/filters/remove_button.rb
  60. 13
      lib/widget/filters/text_box.rb
  61. 65
      lib/widget/group_bys.rb
  62. 57
      lib/widget/help.rb
  63. 48
      lib/widget/settings.rb
  64. 24
      lib/widget/settings/fieldset.rb
  65. 107
      lib/widget/table.rb
  66. 23
      lib/widget/table/progressbar.rb
  67. 170
      lib/widget/table/report_table.rb

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 B

@ -1,159 +0,0 @@
/*
* Copyright (c) 2009 Charles Cordingley (www.cordinc.com)
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* cordinc_tooltip.js, v1.0.2 - 27 August 2008
* For help see www.cordinc.com/projects/tooltips.html
*/
var Tooltip = Class.create({
initialize: function(target, tooltip) {
var options = Object.extend({
start_effect: function(element) {},
end_effect: function(element) {},
zindex: 1000,
offset: {x:0, y:0},
hook: {target:'topRight', tip:'bottomLeft'},
trigger: false,
DOM_location: false,
className: false,
delay: {}
}, arguments[2] || {});
this.target = $(target);
this.show_at = (options.show_at_id !== undefined) ? $(options.show_at_id) : undefined
this.tooltip = $(tooltip);
this.options = options;
this.event_target = this.options.trigger?$(this.options.trigger):this.target;
if (this.options.className) {
this.tooltip.addClassName(this.options.className);
}
this.tooltip.hide();
this.display=false;
this.mouse_over = this.displayTooltip.bindAsEventListener(this);
this.mouse_out = this.removeTooltip.bindAsEventListener(this);
this.event_target.observe("mouseover", this.mouse_over);
this.event_target.observe("mouseout", this.mouse_out);
},
displayTooltip: function(event){
event.stop();
if (this.display) {return;}
if (this.options.delay.start) {
var self = this;
this.timer_id = setTimeout(function(){self.timer_id = false; self.showTooltip(event);}, this.options.delay.start*1000);
} else {
this.showTooltip(event);
}
},
showTooltip: function(event) {
var show_at = (this.show_at !== undefined) ? this.show_at : this.target
this.display=true;
position = this.positionTooltip(event);
this.clone = this.tooltip.cloneNode(true);
parentId = this.options.DOM_location?$(this.options.DOM_location.parentId):show_at.parentNode;
successorId = this.options.DOM_location?$(this.options.DOM_location.successorId):show_at;
parentId.insertBefore(this.clone, successorId);
this.clone.setStyle({
position: 'absolute',
top: position.top + "px",
left: position.left + "px",
display: "inline",
zIndex:this.options.zindex,
/* fix for ur dashboard */
visibility: 'visible',
width: "400px"
});
if (this.options.start_effect) {
this.options.start_effect(this.clone);
}
},
positionTooltip: function(event) {
target_position = this.target.cumulativeOffset();
tooltip_dimensions = this.tooltip.getDimensions();
target_dimensions = this.target.getDimensions();
this.positionModify(target_position, target_dimensions, this.options.hook.target, 1);
this.positionModify(target_position, tooltip_dimensions, this.options.hook.tip, -1);
target_position.top += this.options.offset.y;
target_position.left += this.options.offset.x;
return target_position;
},
positionModify: function(position, box, corner, neg) {
if (corner == 'topRight') {
position.left += box.width*neg;
} else if (corner == 'topLeft') {
} else if (corner == 'bottomLeft') {
position.top += box.height*neg;
} else if (corner == 'bottomRight') {
position.top += box.height*neg;
position.left += box.width*neg;
} else if (corner == 'topMid') {
position.left += (box.width/2)*neg;
} else if (corner == 'leftMid') {
position.top += (box.height/2)*neg;
} else if (corner == 'bottomMid') {
position.top += box.height*neg;
position.left += (box.width/2)*neg;
} else if (corner == 'rightMid') {
position.top += (box.height/2)*neg;
position.left += box.width*neg;
}
},
removeTooltip: function(event) {
if (this.timer_id) {
clearTimeout(this.timer_id);
this.timer_id = false;
return;
}
if (this.options.end_effect) {
this.options.end_effect(this.clone);
}
if (this.options.delay.end) {
var self = this;
setTimeout(function(){self.clearTooltip();}, this.options.delay.end*1000);
} else {
this.clearTooltip();
}
},
clearTooltip: function() {
if (this.clone !== undefined && this.clone !== null) {
this.clone.remove();
this.clone = null;
this.display=false;
}
},
destroy: function() {
this.event_target.stopObserving("mouseover", this.mouse_over);
this.event_target.stopObserving("mouseout", this.mouse_out);
this.clearTooltip();
}
})

@ -1,67 +1,373 @@
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */
/*global window, $, $$, Reporting */
window.Reporting = {
source: ($$("head")[0].select("script[src*='reporting.js']")[0].src),
require: function (libraryName) {
var jsName = Reporting.source.replace("reporting.js", "reporting/" + libraryName + ".js");
try {
// inserting via DOM fails in Safari 2.0, so brute force approach
document.write('<script type="text/javascript" src="' + jsName + '"><\/script>');
} catch (e) {
// for xhtml+xml served content, fall back to DOM methods
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = jsName;
document.getElementsByTagName('head')[0].appendChild(script);
}
},
onload: function (func) {
document.observe("dom:loaded", func);
},
flash: function (string, type) {
if (type === undefined) {
type = "error";
}
if ($("flash_" + type) !== null) {
$("flash_" + type).remove();
}
var flash = document.createElement('div');
flash.setAttribute('id', 'flash_' + type);
flash.setAttribute('onclick', '$(this).remove();');
flash.className = 'flash ' + type;
flash.innerHTML = string;
$("content").insert({before: flash});
},
clearFlash: function () {
$$('div[id^=flash]').each(function (oldMsg) {
oldMsg.remove();
});
},
fireEvent: function (element, event) {
var evt;
if (document.createEventObject) {
// dispatch for IE
evt = document.createEventObject();
return element.fireEvent('on' + event, evt);
/*global $, selectAllOptions, moveOptions, Form, Ajax, window, $$, document, Sortable, Effect */
function make_select_accept_multiple_values(select) {
select.multiple = true;
select.size = 4;
// first option just got selected, because THAT'S the kind of world we live in
select.options[0].selected = false;
}
function make_select_accept_single_value(select) {
select.multiple = false;
select.size = 1;
}
function toggle_multi_select(select) {
if (select.multiple === true) {
make_select_accept_single_value(select);
} else {
make_select_accept_multiple_values(select);
}
}
function change_argument_visibility(field, arg_nr) {
var params, i;
params = [$(field + '_arg_1'), $(field + '_arg_2')];
for (i = 0; i < 2; i += 1) {
if (params[i] !== null) {
if (arg_nr >= (i + 1) || arg_nr <= (-1 - i)) {
params[i].show();
}
else {
params[i].hide();
}
}
}
}
function operator_changed(field, select) {
var option_tag, arity;
if (select === null) {
return;
}
option_tag = select.options[select.selectedIndex];
arity = parseInt(option_tag.getAttribute("data-arity"), 10);
change_argument_visibility(field, arity);
}
function display_category(tr_field) {
var label = $(tr_field.getAttribute("data-label"));
if (label !== null) {
label.show();
}
}
function hide_category(tr_field) {
var label = $(tr_field.getAttribute("data-label"));
if (label !== null) {
label.hide();
}
}
function set_remove_button_visibility(field, value) {
var remove = $('rm_' + field);
if (remove !== null) {
if (value === true) {
remove.show();
} else {
remove.hide();
}
}
}
function load_available_values_for_filter(filter_name, callback_func) {
var upd, select;
select = $('' + filter_name + '_arg_1_val');
if (select !== null && select.readAttribute('data-loading') === "ajax" && select.childElements().length === 0) {
upd = new Ajax.Updater({ success: select }, window.global_prefix + '/cost_reports/available_values', {
parameters: { filter_name: filter_name },
insertion: 'bottom',
evalScripts: false,
onCreate: function (a, b) {
$('operators_' + filter_name).disable();
$('' + filter_name + '_arg_1_val').disable();
},
onComplete: function (a, b) {
$('operators_' + filter_name).enable();
$('' + filter_name + '_arg_1_val').enable();
callback_func();
}
});
make_select_accept_single_value(select);
}
else {
callback_func();
}
}
function show_filter_callback(field, slowly, callback_func) {
var field_el, effect;
field_el = $('tr_' + field);
if (field_el !== null) {
load_available_values_for_filter(field, callback_func);
// the following command might be included into the callback_function (which is called after the ajax request) later
$('rm_' + field).value = field;
if (slowly) {
effect = new Effect.Appear(field_el);
} else {
field_el.show();
}
operator_changed(field, $("operators_" + field));
display_category(field_el);
}
}
function show_filter(field) {
show_filter_callback(field, true, function () {});
}
function occupied_category(tr_field) {
var i, data_label, filters;
data_label = tr_field.getAttribute("data-label");
filters = document.getElementsByClassName('filter');
for (i = 0; i < filters.length; i += 1) {
if (filters[i].visible() && filters[i].getAttribute("data-label") === data_label) {
return true;
}
}
return false; //not hit
}
function hide_filter(field, slowly) {
var effect, field_el, operator_select;
field_el = $('tr_' + field);
if (field_el !== null) {
$('rm_' + field).value = "";
if (slowly) {
effect = new Effect.Fade(field_el);
} else {
field_el.hide();
}
operator_select = $("operators_" + field);
if (operator_select !== null) {
// in case the filter doesn't have an operator select field'
operator_changed(field, $("operators_" + field));
}
if (!occupied_category(field_el)) {
hide_category(field_el);
}
}
}
function disable_select_option(select, field) {
for (var i = 0; i < select.options.length; i += 1) {
if (select.options[i].value === field) {
select.options[i].disabled = true;
break;
}
}
}
function enable_select_option(select, field) {
for (var i = 0; i < select.options.length; i += 1) {
if (select.options[i].value === field) {
select.options[i].disabled = false;
break;
}
}
}
function add_filter(select) {
var field;
field = select.value;
show_filter(field);
select.selectedIndex = 0;
disable_select_option(select, field);
}
function remove_filter(field) {
hide_filter(field, true);
enable_select_option($("add_filter_select"), field);
}
function show_group_by(group_by, target) {
var source, group_option, i;
source = $("group_by_container");
group_option = null;
// find group_by option-tag in target select-box
for (i = 0; i < source.options.length; i += 1) {
if (source.options[i].value === group_by) {
group_option = source.options[i];
source.options[i] = null;
break;
}
}
// die if the appropriate option-tag can not be found
if (group_option === null) {
return;
}
// move the option-tag to the taget select-box while keepings its data
target.options[target.length] = group_option;
}
function select_operator(field, operator) {
var select, i;
select = $("operators_" + field);
if (select === null) {
return; // there is no such operator select field
}
for (i = 0; i < select.options.length; i += 1) {
if (select.options[i].value === operator) {
select.selectedIndex = i;
break;
}
}
operator_changed(field, select);
}
function restore_select_values(select, values) {
var i, j;
if (values.length > 1) {
make_select_accept_multiple_values(select);
} else {
make_select_accept_single_value(select);
}
for (i = 0; i < values.length; i += 1) {
for (j = 0; j < select.options.length; j += 1) {
if (select.options[j].value === values[i].toString()) {
try {
select.options[j].selected = true;
break;
} catch (e) {
window.setTimeout('$("' + select.id + '").childElements()[' + j + '].selected = true;', 1);
}
}
}
}
}
function find_arguments(field) {
var args = [], arg_count = 0, arg = null;
arg = $(field + '_arg_' + (arg_count + 1) + '_val');
while (arg !== null) {
args[args.length] = arg;
arg_count = arg_count + 1;
arg = $(field + '_arg_' + (arg_count + 1) + '_val');
}
return args;
}
function restore_values(field, values) {
var op_select, op_arity, args, i;
op_select = $("operators_" + field);
if (op_select !== null) {
op_arity = op_select.options[op_select.selectedIndex].getAttribute("data-arity");
}
else {
op_arity = 0;
}
args = find_arguments(field);
if (args.size() === 0) {
return; // there are no values to set
}
if (!Object.isArray(values)) {
values = [values];
}
if (op_arity < 0 && !(args[0].type.empty()) && args[0].type.include('select')) {
restore_select_values(args[0], values);
} else {
// dispatch for firefox + others
evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
for (i = 0; i < values.length && i < args.length; i += 1) {
args[i].setValue(values[i]);
}
}
}
function restore_filter(field, operator, values) {
select_operator(field, operator);
disable_select_option($("add_filter_select"), field);
show_filter_callback(field, true, function () {
if (typeof(values) !== "undefined") {
restore_values(field, values);
}
});
}
function show_group_by_column(group_by) {
show_group_by(group_by, $('group_by_columns'));
}
function show_group_by_row(group_by) {
show_group_by(group_by, $('group_by_rows'));
}
function disable_all_filters() {
var i, select;
$('filter_table').down().childElements().each(function (e) {
var field, possible_select;
e.hide();
if (e.readAttribute('class') === 'filter') {
field = e.id.gsub('tr_', '');
hide_filter(field, false);
possible_select = $(field + '_arg_1_val');
if (possible_select !== null && possible_select.type && possible_select.type.include('select')) {
make_select_accept_single_value(possible_select);
}
}
});
select = $("add_filter_select");
for (i = 0; i < select.options.length; i += 1) {
if (select.options[i].disabled) {
select.options[i].disabled = false;
}
}
}
function disable_all_group_bys() {
var destination;
destination = $('group_by_container');
[$('group_by_columns'), $('group_by_rows')].each(function (origin) {
selectAllOptions(origin);
moveOptions(origin, destination);
});
}
function serialize_filter_and_group_by() {
var ret_str, rows, columns;
ret_str = Form.serialize('query_form');
rows = Sortable.serialize('group_rows');
columns = Sortable.serialize('group_columns');
if (rows !== null && rows !== "") {
ret_str += "&" + rows;
}
if (columns !== null && columns !== "") {
ret_str += "&" + columns;
}
}
};
return ret_str;
}
Reporting.require("filters");
Reporting.require("group_bys");
Reporting.require("restore_query");
Reporting.require("controls");
Reporting.require("prototype_progress_bar");
Reporting.require("progressbar");
function init_group_bys() {
var options = {
tag: 'span',
overlap: 'horizontal',
constraint: 'horizontal',
containment: ['group_columns', 'group_rows'],
//only: "group_by",
dropOnEmpty: true,
format: /^(.*)$/,
hoverclass: 'drag_container_accept'
};
Sortable.create('group_columns', options);
Sortable.create('group_rows', options);
}
function defineElementGetter() {
if (document.getElementsByClassName === undefined) {
document.getElementsByClassName = function (className)
{
var hasClassName, allElements, results, element, elementClass, i;
hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)");
allElements = document.getElementsByTagName("*");
results = [];
for (i = 0; (element = allElements[i]) !== null; i += 1) {
elementClass = element.className;
if (elementClass && elementClass.indexOf(className) !== -1 && hasClassName.test(elementClass)) {
results.push(element);
}
}
return results;
};
}
}
defineElementGetter();

@ -1,171 +0,0 @@
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */
/*global window, $, $$, Reporting, Effect, Ajax, Element, selectAllOptions, Form */
Reporting.Controls = {
query_name_editor: function (target_id) {
var target = $(target_id);
var isPublic = target.getAttribute("data-is_public") === "true";
var updateUrl = target.getAttribute("data-update-url");
var translations = target.getAttribute("data-translations");
if (translations.isJSON()) {
translations = translations.evalJSON(true);
}
if (translations === undefined) {
translations = {};
}
if (translations.rename === undefined) {
translations.rename = 'ok';
}
if (translations.cancel === undefined) {
translations.cancel = 'cancel';
}
if (translations.saving === undefined) {
translations.saving = 'Saving...';
}
if (translations.loading === undefined) {
translations.loading = 'Loading...';
}
if (translations.clickToEdit === undefined) {
translations.loading = 'Click to edit';
}
var editor = new Ajax.InPlaceEditor(target_id, updateUrl, {
callback: function (form, value) {
return 'query_name=' + encodeURIComponent(value);
},
okControl: 'button',
cancelControl: 'button',
externalControl: 'query-name-edit-button',
okText: translations.rename,
cancelText: translations.cancel,
savingText: translations.saving,
loadingText: translations.loading,
clickToEditText: translations.clickToEdit,
onFailure: function (editor, response) {
Reporting.flash(response.responseText);
}
});
},
toggle_delete_form: function (e) {
var offset = $('query-icon-delete').positionedOffset().left;
$('delete_form').setStyle("left: " + offset + "px").toggle();
e.preventDefault();
},
toggle_save_as_form: function (e) {
var offset = $('query-icon-save-as').positionedOffset().left;
$('save_as_form').setStyle("left: " + offset + "px").toggle();
e.preventDefault();
},
clear_query: function (e) {
Reporting.Filters.clear();
Reporting.GroupBys.clear();
e.preventDefault();
},
send_settings_data: function (targetUrl, callback, failureCallback) {
if (failureCallback === undefined) {
failureCallback = Reporting.Controls.default_failure_callback;
}
Reporting.clearFlash();
new Ajax.Request(
targetUrl,
{ asynchronous: true,
evalScripts: true,
postBody: Reporting.Controls.serialize_settings_form(),
onSuccess: callback,
onFailure: failureCallback });
},
serialize_settings_form: function() {
var ret_str, grouping_str;
ret_str = Form.serialize('query_form');
grouping_str = $w('rows columns').inject('', function(grouping, type) {
return grouping + $('group_by_' + type).select('.group_by_element').map(function(group_by) {
return 'groups[' + type + '][]=' + group_by.readAttribute('data-group-by');
}).inject('', function(all_group_str, group_str) {
return all_group_str + '&' + group_str;
});
});
if (grouping_str.length > 0) {
ret_str += grouping_str;
}
return ret_str;
},
attach_settings_callback: function (element, callback) {
if (element === null) {
return;
}
failureCallback = function (response) {
$('result-table').update("");
Reporting.Controls.default_failure_callback(response);
};
element.observe("click", function (e) {
Reporting.Controls.send_settings_data(this.getAttribute("data-target"), callback, failureCallback);
e.preventDefault();
});
},
observe_click: function (element_id, callback) {
var el = $(element_id);
if (el !== null && el !== undefined) {
el.observe("click", callback);
}
},
update_result_table: function (response) {
$('result-table').update(response.responseText);
Reporting.Progress.confirm_question();
},
default_failure_callback: function (response) {
if (response.status >= 400 && response.status < 500) {
Reporting.flash(response.responseText);
Reporting.Progress.abort();
} else {
Reporting.flash("There was an error getting the results. The administrator has been informed.");
Reporting.Progress.abort();
}
}
};
Reporting.onload(function () {
if ($('query_saved_name') !== null) {
if ($('query_saved_name').getAttribute("data-update-url") !== null) {
Reporting.Controls.query_name_editor('query_saved_name');
}
// don't concern ourselves with new queries
if ($('query_saved_name').getAttribute("data-is_new") !== null) {
if ($('query-icon-delete') !== null) {
Reporting.Controls.observe_click("query-icon-delete", Reporting.Controls.toggle_delete_form);
Reporting.Controls.observe_click("query-icon-delete-cancel", Reporting.Controls.toggle_delete_form);
$('delete_form').hide();
}
if ($("query-breadcrumb-save") !== null) {
// When saving an update of an exisiting query or apply filters, we replace the table on success
Reporting.Controls.attach_settings_callback($("query-breadcrumb-save"), Reporting.Controls.update_result_table);
}
}
}
Reporting.Controls.observe_click("query-icon-save-as", Reporting.Controls.toggle_save_as_form);
Reporting.Controls.observe_click("query-icon-save-as-cancel", Reporting.Controls.toggle_save_as_form);
if ($('save_as_form') !== null) {
$('save_as_form').hide();
}
// When saving a new query, the success-response is the new saved query's url -> redirect to that
Reporting.Controls.attach_settings_callback($("query-icon-save-button"), function (response) {
Ajax.activeRequestCount = Ajax.activeRequestCount + 1; // HACK: Prevent Loading spinner from disappearing
document.location = response.responseText;
});
// When saving an update of an exisiting query or apply filters, we replace the table on success
Reporting.Controls.attach_settings_callback($("query-icon-apply-button"), Reporting.Controls.update_result_table);
Reporting.Controls.observe_click($('query-link-clear'), Reporting.Controls.clear_query);
});

@ -1,444 +0,0 @@
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */
/*global window, $, $$, Reporting, Effect, Ajax, Element, Form */
Reporting.Filters = {
load_available_values_for_filter: function (filter_name, callback_func) {
var select, radio_options;
select = $('' + filter_name + '_arg_1_val');
//TODO: the following code ist cost report specific, we should refactor that to be general useful
if (select !== null && select.readAttribute('data-loading') === "ajax" && select.childElements().length === 0) {
Ajax.Updater({ success: select }, window.global_prefix + '/cost_reports/available_values', {
parameters: { filter_name: filter_name },
insertion: 'bottom',
evalScripts: false,
onCreate: function (a, b) {
$('operators_' + filter_name).disable();
$('' + filter_name + '_arg_1_val').disable();
},
onComplete: function (a, b) {
$('operators_' + filter_name).enable();
$('' + filter_name + '_arg_1_val').enable();
callback_func();
}
});
Reporting.Filters.multi_select(select, false);
} else {
callback_func();
}
// select first option by default
if (select.tagName.toLowerCase() === "div") {
// check if we might have a radio-box
radio_options = $$('.' + filter_name + '_radio_option input');
if (radio_options && radio_options.size() !== 0) {
radio_options.first().checked = true;
}
} else if (select.tagName.toLowerCase() === "select") {
select.selectedIndex = 0;
}
},
show_filter: function (field, options) {
if (options === undefined) {
options = {};
}
if (options.callback_func === undefined) {
options.callback_func = function () {};
}
if (options.slowly === undefined) {
options.slowly = false;
}
if (options.show_filter === undefined) {
options.show_filter = true;
}
var field_el = $('tr_' + field);
if (field_el !== null) {
if (options.insert_after === undefined) {
options.insert_after = Reporting.Filters.last_visible_filter();
}
if (options.insert_after !== undefined && options.show_filter) {
// Move the filter down to appear after the last currently visible filter
field_el.remove();
options.insert_after.insert({after: field_el});
}
// the following command might be included into the callback_function (which is called after the ajax request) later
var display_functor;
if (options.show_filter) {
(options.slowly ? Effect.Appear : Element.show)(field_el);
Reporting.Filters.load_available_values_for_filter(field, options.callback_func);
$('rm_' + field).value = field; // set the value, so the serialized form will return this filter
Reporting.Filters.value_changed(field);
Reporting.Filters.set_filter_value_widths(100);
} else {
(options.slowly ? Effect.Fade : Element.hide)(field_el);
field_el.removeAttribute('data-selected');
$('rm_' + field).value = ""; // reset the value, so the serialized form will not return this filter
Reporting.Filters.set_filter_value_widths(5000);
}
Reporting.Filters.operator_changed(field, $("operators[" + field + "]"));
Reporting.Filters.display_category($(field_el.getAttribute("data-label")));
}
},
/*
Smoothly sets the width of currently displayed filters.
Params:
delay:Int
Time to wait before resizing the filters width */
set_filter_value_widths: function (delay) {
window.clearTimeout(Reporting.Filters.set_filter_value_widths_timeout);
if (Reporting.Filters.visible_filters().size() > 0) {
Reporting.Filters.set_filter_value_widths_timeout = window.setTimeout(function () {
var table_data = $("tr_" + Reporting.Filters.visible_filters().first()).select(".filter_values").first().up();
var current_width = table_data.getWidth();
var filter_values = $($$(".filter_values"));
// First, reset all widths
filter_values.each(function (f) {
$(f).up().style.width = "auto";
});
// Now, get the current width
// Any width will be fine, as the table layout makes all elements the same width
var new_width = table_data.getWidth();
if (new_width < current_width) {
// Set all widths to previous, so we can animate
filter_values.each(function (f) {
$(f).up().style.width = current_width + "px";
});
}
// Now, set all widths to be the widest
filter_values.each(function (f) {
if (new_width < current_width) {
$(f).up().morph("width: " + new_width + "px;");
} else {
$(f).up().style.width = new_width + "px";
}
});
}, delay);
}
},
set_filter_value_widths_timeout: undefined,
last_visible_filter: function () {
return $($$('.filter')).reverse().detect(function (f) {
return f.visible();
});
},
/* Display the given category if any of its filters are visible. Otherwise hide it */
display_category: function (label) {
if (label !== null) {
var filters = $$('.filter');
for (var i = 0; i < filters.length; i += 1) {
if (filters[i].visible() && filters[i].getAttribute("data-label") === label) {
Element.show(label);
return;
}
}
Element.hide(label);
}
},
operator_changed: function (field, select) {
var option_tag, arity;
if (select === null) {
return;
}
option_tag = select.options[select.selectedIndex];
arity = parseInt(option_tag.getAttribute("data-arity"), 10);
Reporting.Filters.change_argument_visibility(field, arity);
},
value_changed: function (field) {
var val, tr;
val = $(field + '_arg_1_val');
tr = $('tr_' + field);
if (!val) {
return;
}
if (val.value === '<<inactive>>') {
tr.addClassName('inactive-filter');
} else {
tr.removeClassName('inactive-filter');
}
},
change_argument_visibility: function (field, arg_nr) {
var params, i;
params = [$(field + '_arg_1'), $(field + '_arg_2')];
for (i = 0; i < 2; i += 1) {
if (params[i] !== null) {
if (arg_nr >= (i + 1) || arg_nr <= (-1 - i)) {
params[i].show();
} else {
params[i].hide();
}
}
}
},
add_filter: function (select) {
var field;
field = select.value;
Reporting.Filters.show_filter(field, { slowly: true });
select.selectedIndex = 0;
Reporting.Filters.select_option_enabled(select, field, false);
},
select_option_enabled: function (box, value, state) {
var option = box.select("[value='" + value + "']").first();
if (option !== undefined) {
option.disabled = !state;
}
},
multi_select: function (select, multi) {
select.multiple = multi;
if (multi) {
select.size = 4;
// deselect first option
select.options[0].selected = false;
} else {
select.size = 1;
}
},
toggle_multi_select: function (select) {
Reporting.Filters.multi_select(select, !select.multiple);
},
remove_filter: function (field) {
Reporting.Filters.show_filter(field, { show_filter: false });
var dependent = Reporting.Filters.get_dependents($(field + '_arg_1_val'), false).find(function(d) {
return Reporting.Filters.visible_filters().include(d);
});
if (dependent !== undefined) {
Reporting.Filters.remove_filter(dependent);
}
Reporting.Filters.select_option_enabled($("add_filter_select"), field, true);
},
visible_filters: function () {
return $("filter_table").select("tr").select(function (tr) {
return tr.visible() === true;
}).collect(function (filter) {
return filter.getAttribute("data-filter-name");
});
},
clear: function () {
Reporting.Filters.visible_filters().each(function (filter) {
Reporting.Filters.remove_filter(filter);
});
},
// Returns an array of dependents of the given element
// get_all -> Boolean: whether to return all dependends (even the
// dependents of this filters dependents) or not
get_dependents: function (element, get_all) {
var dependent_field = "data-all-dependents";
if (get_all === false) {
dependent_field = "data-next-dependents";
}
if (element.hasAttribute(dependent_field)) {
return element.getAttribute(dependent_field).replace(/'/g, '"').evalJSON(true);
} else {
return [];
}
},
// Activate the first dependent of the changed filter, if it is not already active.
// Afterwards, collect the visible filters from the dependents list and start
// narrowing down their values.
// Param: select [optional] - the select-box of the filter which should activate it's dependents
activate_dependents: function (selectBox, callbackWhenFinished) {
var all_dependents, next_dependents, dependent, active_filters, source;
if (selectBox === undefined || selectBox.type.toLowerCase() == 'change') {
selectBox = this;
}
if (callbackWhenFinished === undefined) {
callbackWhenFinished = function() {};
}
source = selectBox.getAttribute("data-filter-name");
all_dependents = Reporting.Filters.get_dependents(selectBox);
next_dependents = Reporting.Filters.get_dependents(selectBox, false);
dependent = Reporting.Filters.which_dependent_shall_i_take(source, next_dependents);
active_filters = Reporting.Filters.visible_filters();
if (!active_filters.include(dependent)) {
// in case we run into a situation where the dependent to show is not in the currently selected dependency chain
// we have to remove all filters until we reach the source and add the new dependent
if (next_dependents.any( function(d){ return active_filters.include(d) } )) {
while (active_filters.last() !== source) {
Reporting.Filters.show_filter(active_filters.pop(1), { show_filter: false, slowly: true });
}
}
Reporting.Filters.show_filter(dependent, { slowly: true, insert_after: $(selectBox.up(".filter")) });
// render filter inactive if possible to avoid unintended filtering
$(dependent + '_arg_1_val').value = '<<inactive>>'
Reporting.Filters.operator_changed(dependent, $('operators[' + dependent + ']'));
// Hide remove box of dependent
$('rm_box_' + dependent).hide();
// Remove border of dependent, so it "merges" with the filter before
$('tr_' + dependent).addClassName("no-border");
active_filters.unshift(dependent);
}
setTimeout(function () { // Make sure the newly shown filters are in the DOM
var active_dependents = all_dependents.select(function (d) {
return active_filters.include(d);
});
Reporting.Filters.narrow_values(Reporting.Filters.dependent_for(source), active_dependents, callbackWhenFinished);
}, 1);
},
// return an array of all filters that depend on the given filter plus the given filter
dependent_for: function(field) {
var deps = $$('.filters-select[data-all-dependents]').findAll(function(selectBox) {
return selectBox.up('tr').visible() && Reporting.Filters.get_dependents(selectBox).include(field)
}).map(function(selectBox) {
return selectBox.getAttribute("data-filter-name");
});
return deps === undefined ? [ field ] : [ field ].concat(deps)
},
// Select the given values of the selectBox.
// Toggle multi-select state of the selectBox depending on how many values were given.
select_values: function(selectBox, values_to_select) {
Reporting.Filters.multi_select(selectBox, values_to_select.size() > 1);
values_to_select.each(function (val) {
var opt = selectBox.select("option[value='" + val + "']");
if (opt.size() === 1) {
opt.first().selected = true;
}
});
},
exists: function (filter) {
return Reporting.Filters.visible_filters().include(filter);
},
// Narrow down the available values for the [dependents] of [sources].
// This will narrow down for each dependent separately, adding each finished
// dependent to the sources array and removing it from the dependents array.
narrow_values: function (sources, dependents, callbackWhenFinished) {
if (sources.size() === 0 || dependents.size === 0 || dependents.first() === undefined) {
return;
}
if (callbackWhenFinished === undefined) {
callbackWhenFinished = function() {};
}
var params = document.location.href.include('?') ? '&' : '?'
params = params + "narrow_values=1&dependent=" + dependents.first();
sources.each(function (filter) {
params = params + "&sources[]=" + filter;
});
var targetUrl = document.location.href + params;
var currentDependent = dependents.first();
var updater = new Ajax.Request(targetUrl,
{
asynchronous: true,
evalScripts: true,
postBody: Reporting.Controls.serialize_settings_form(),
onSuccess: function (response) {
Reporting.clearFlash();
if (response.responseJSON !== undefined) {
var continue_narrowing = true;
var selectBox = $(currentDependent + "_arg_1_val");
var selected = selectBox.select("option").collect(function (sel) {
if (sel.selected) {
return sel.value;
}
}).compact();
// remove old values
$(selectBox).childElements().each(function (o) {
o.remove();
});
// insert new values
response.responseJSON.each(function (o) {
var ary = [ (o === null ? "" : o) ].flatten();
var label = ary.first();
var value = ary.last();
// cannot use .innerhtml due to IE wierdness
$(selectBox).insert(new Element('option', {value: value}).update(label.escapeHTML()));
});
Reporting.Filters.select_values(selectBox, selected);
sources.push(currentDependent); // Add as last element
dependents.splice(0, 1); // Delete first element
// if we got no values besides the <<inactive>> value, do not show this selectBox
if (!selectBox.select("option").any(function (opt) { return opt.value != '<<inactive>>' })) {
Reporting.Filters.show_filter(currentDependent, { show_filter: false });
continue_narrowing = false;
}
// if the current filter is inactive, hide dependent - otherwise recurisvely narrow dependent values
if (selectBox.value == '<<inactive>>') {
Reporting.Filters.value_changed(currentDependent);
dependents.each(function (dependent) {
Reporting.Filters.show_filter(dependent, {
slowly: true,
show_filter: false });
});
continue_narrowing = false;
}
if (continue_narrowing) {
Reporting.Filters.narrow_values(sources, dependents);
}
}
callbackWhenFinished();
},
onException: function (response, error) {
Reporting.flash("Loading of filter values failed. Probably, the server is temporary offline for maintenance.");
var selectBox = $(currentDependent + "_arg_1_val");
$(selectBox).insert(new Element('option', {value: '<<inactive>>'}).update('Failed to load values.'));
}
}
);
},
// This method may be overridden by the actual application to define custon behavior
// If there are multiple possible dependents to follow.
// The dependent to follow should be returned.
which_dependent_shall_i_take: function(source, dependents) {
return dependents.first();
}
};
Reporting.onload(function () {
if ($("add_filter_select")) {
$("add_filter_select").observe("change", function () {
if (!(Reporting.Filters.exists(this.value))) {
Reporting.Filters.add_filter(this);
};
});
}
$$(".filter_rem").each(function (e) {
e.observe("click", function () {
var filter_name = this.up('tr').getAttribute("data-filter-name");
Reporting.Filters.remove_filter(filter_name);
});
});
$$(".filter_operator").each(function (e) {
e.observe("change", function (evt) {
var filter_name = this.getAttribute("data-filter-name");
Reporting.Filters.operator_changed(filter_name, this);
Reporting.fireEvent($(filter_name + "_arg_1_val"), "change");
});
});
$$(".filter_multi-select").each(function (e) {
e.observe("click", function () {
Reporting.Filters.toggle_multi_select($(this.getAttribute("data-filter-name") + '_arg_1_val'));
});
});
$$(".filters-select").each(function (s) {
var selected_size = Array.from(s.options).findAll(function (o) {
return o.selected === true;
}).size();
s.multiple = (selected_size > 1);
s.observe("change", function (evt) {
var filter_name = this.up('tr').getAttribute("data-filter-name");
Reporting.Filters.value_changed(filter_name);
});
});
$$('.filters-select[data-all-dependents]').each(function (dependency) {
dependency.observe("change", Reporting.Filters.activate_dependents);
});
});

@ -1,174 +0,0 @@
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */
/*global window, $, $$, Reporting, Effect, Ajax, selectAllOptions, moveOptions, moveOptionUp, moveOptionDown */
Reporting.GroupBys = {
group_by_container_ids: function() {
var ids = $w('group_by_columns group_by_rows');
return ids.select(function (i) {
return $(i) !== null
});
},
sortable_options: function() {
return {
tag: 'span',
only: "drag_element",
overlap: 'horizontal',
constraint:'horizontal',
containment: Reporting.GroupBys.group_by_container_ids(),
dropOnEmpty: true,
hoverclass: 'drag_container_accept'
};
},
recreate_sortables: function() {
Reporting.GroupBys.group_by_container_ids().each(function(id) {
Sortable.create(id, Reporting.GroupBys.sortable_options());
});
},
initialize_drag_and_drop_areas: function() {
Reporting.GroupBys.recreate_sortables();
},
create_label: function(group_by, text) {
return new Element('label', {
'class': 'in_row group_by_label',
'for': group_by.identify(),
'id': group_by.identify() + '_label'
}).update(text);
},
create_remove_button: function(group_by) {
var button = new Element('span', {
'class': 'group_by_remove in_row',
'id': group_by.identify() + '_remove'
});
button.observe('mousedown', function() { Reporting.GroupBys.remove_group_by(button.up('.group_by_element')) });
return button;
},
create_arrow: function(group_by, position) {
return new Element('span', {
'class': 'arrow in_row arrow_' + position,
'id': group_by.identify() + '_arrow_' + position
});
},
create_group_by: function(field, caption) {
var group_by, label, right_arrow, left_arrow, remove_button;
group_by = new Element('span', {
'class': 'in_row drag_element group_by_element',
'data-group-by': field
});
group_by.identify(); // give it a unique id
left_arrow = Reporting.GroupBys.create_arrow(group_by, 'left');
group_by.appendChild(left_arrow);
label = Reporting.GroupBys.create_label(group_by, caption);
Reporting.GroupBys.init_group_by_hover_effects([group_by, label]);
group_by.appendChild(label);
remove_button = Reporting.GroupBys.create_remove_button(group_by);
group_by.appendChild(remove_button);
right_arrow = Reporting.GroupBys.create_arrow(group_by, 'right');
group_by.appendChild(right_arrow);
return group_by;
},
// on mouse_over of a group_by or it's label, change the color of the group_by
// also change the color of the arrows
init_group_by_hover_effects: function(elements) {
elements.each(function(element) {
['mouseover', 'mouseout'].each(function(event_type) {
element.observe(event_type, function(event) {
Reporting.GroupBys.group_by_hover_effect(event, event_type == 'mouseover');
});
});
});
},
group_by_hover_effect: function(event, do_hover) {
var group_by = $(Event.element(event));
// we possibly hit a tag inside the group_by, so go search the group_by then
if (!group_by.hasClassName('group_by_element')) {
group_by = group_by.up('.group_by_element');
}
if (group_by !== null) {
Reporting.GroupBys.group_by_hover(group_by, do_hover);
}
},
group_by_hover: function(group_by, state) {
if (state) {
group_by.childElements().each(function(e) { e.addClassName('hover'); });
} else {
group_by.childElements().each(function(e) { e.removeClassName('hover'); });
}
},
// This is whether it is possible to add a new group if <<field>> through the
// add-group-by select-box or not.
adding_group_by_enabled: function(field, state) {
$w('add_group_by_columns add_group_by_rows').each(function(container_id) {
Reporting.Filters.select_option_enabled($(container_id), field, state);
});
},
remove_group_by: function(group_by) {
Reporting.GroupBys.adding_group_by_enabled(group_by.readAttribute('data-group-by'), true);
group_by.remove();
},
add_group_by_from_select: function(select) {
var field, caption, container, selected_option;
field = $(select).getValue();
container = select.up('.drag_container');
selected_option = select.select("[value='" + field + "']").first();
caption = selected_option.readAttribute('data-label');
Reporting.GroupBys.add_group_by(field, caption, container);
select.select("[value='']").first().selected = true;
},
add_group_by: function(field, caption, container) {
var group_by, add_groups_select_box;
add_groups_select_box = container.select('select').first();
group_by = Reporting.GroupBys.create_group_by(field, caption);
add_groups_select_box.insert({ before: group_by });
Reporting.GroupBys.adding_group_by_enabled(field, false);
Reporting.GroupBys.recreate_sortables();
},
clear: function() {
Reporting.GroupBys.visible_group_bys().each(function (group_by) {
Reporting.GroupBys.remove_group_by(group_by);
});
},
visible_group_bys: function() {
return Reporting.GroupBys.group_by_container_ids().collect(function (container) {
return $(container).select('[data-group-by]')
}).flatten();
},
exists: function(group_by_name) {
return Reporting.GroupBys.visible_group_bys().any(function (grp) {
return grp.getAttribute('data-group-by') == group_by_name;
});
}
};
Reporting.onload(function () {
Reporting.GroupBys.initialize_drag_and_drop_areas();
[$('add_group_by_rows'), $('add_group_by_columns')].each(function (select) {
if (select !== null) {
select.observe("change", function () {
if (!(Reporting.GroupBys.exists(this.value))) {
Reporting.GroupBys.add_group_by_from_select(this);
};
});
}
});
});

@ -1,49 +0,0 @@
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */
/*global window, $, $$, Reporting, Effect, Ajax */
Reporting.Progress = {
abort: function () {
if (window.progressbar !== undefined && window.progressbar !== null) {
window.progressbar.stop();
}
},
replace_with_bar: function (element) {
var parent = element.up();
var size = parseInt(element.getAttribute('data-query-size'), 10) || 500;
element.remove();
window.progressbar = Reporting.Progress.add_bar_to_parent(parent);
// Speed determined through laborous experimentation!
window.progressbar.options.interval = (size * (Math.log(size))) / 100000;
window.progressbar.start();
},
add_bar_to_parent: function (parent) {
parent.appendChild(new Element('div', {
'id': 'progressbar_container',
'class': 'progressbar_container'
}));
return new Control.ProgressBar('progressbar_container');
},
confirm_question: function () {
var bar = $('progressbar');
if (bar !== null && bar !== undefined) {
var size = bar.getAttribute('data-size');
var question = bar.getAttribute('data-translation');
if (confirm(question)) {
var target = bar.getAttribute("data-target");
bar.up().show();
Reporting.Progress.replace_with_bar(bar);
Reporting.Controls.send_settings_data(target, Reporting.Controls.update_result_table);
} else {
bar.toggle();
}
}
}
};
Reporting.onload(function () {
Reporting.Progress.confirm_question();
});

@ -1,110 +0,0 @@
/**
* @author Ryan Johnson <http://syntacticx.com/>
* @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
* @package LivePipe UI
* @license MIT
* @url http://livepipe.net/control/progressbar
* @require prototype.js, livepipe.js
*/
/*global document, Prototype, Ajax, Class, PeriodicalExecuter, $, $A, Control */
if (typeof(Prototype) === "undefined") {
throw "Control.ProgressBar requires Prototype to be loaded.";
}
if (typeof(Event) === "undefined") {
throw "Control.ProgressBar requires Event to be loaded.";
}
Control.ProgressBar = Class.create({
initialize: function(container, options) {
this.progress = 0;
this.executer = false;
this.active = false;
this.poller = false;
this.container = $(container);
this.containerWidth = this.container.getDimensions().width;
this.progressContainer = $(document.createElement('div'));
this.progressContainer.setStyle({
width: this.containerWidth + 'px',
height: '100%',
position: 'absolute',
top: '0px',
right: '0px'
});
this.container.appendChild(this.progressContainer);
this.options = {
afterChange: Prototype.emptyFunction,
interval: 0.25,
step: 1,
classNames: {
active: 'progress_bar_active',
inactive: 'progress_bar_inactive'
}
};
Object.extend(this.options, options || {});
this.container.addClassName(this.options.classNames.inactive);
this.active = false;
},
setProgress: function (value) {
this.progress = value;
this.draw();
if (this.progress >= 100) {
this.stop(false);
}
this.notify('afterChange', this.progress, this.active);
},
poll: function (url, interval, ajaxOptions) {
// Extend the passed ajax options and success callback with our own.
ajaxOptions = ajaxOptions || {};
var success = ajaxOptions.onSuccess || Prototype.emptyFunction;
ajaxOptions.onSuccess = success.wrap(function (callOriginal, request) {
this.setProgress(parseInt(request.responseText, 10));
if (!this.active) {
this.poller.stop();
}
callOriginal(request);
}).bind(this);
this.active = true;
this.poller = new PeriodicalExecuter(function () {
var a = new Ajax.Request(url, ajaxOptions);
}.bindAsEventListener(this), interval || 3);
},
start: function () {
this.active = true;
this.container.removeClassName(this.options.classNames.inactive);
this.container.addClassName(this.options.classNames.active);
this.executer = new PeriodicalExecuter(this.step.bind(this, this.options.step), this.options.interval);
},
stop: function (reset) {
this.active = false;
if (this.executer) {
this.executer.stop();
}
this.container.removeClassName(this.options.classNames.active);
this.container.addClassName(this.options.classNames.inactive);
if (typeof reset === 'undefined' || reset === true) {
this.reset();
}
},
step: function (amount) {
this.active = true;
this.setProgress(Math.min(100, this.progress + amount));
},
reset: function () {
this.active = false;
this.setProgress(0);
},
draw: function () {
this.progressContainer.setStyle({
width: (100 - this.progress) + "%"
});
},
notify: function (event_name) {
if (this.options[event_name]) {
return [this.options[event_name].apply(this.options[event_name], $A(arguments).slice(1))];
}
}
});
Event.extend(Control.ProgressBar);

@ -1,104 +0,0 @@
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */
/*global window, $, $$, Reporting, Effect, Ajax */
Reporting.RestoreQuery = {
select_operator: function (field, operator) {
var select, i;
select = $("operators_" + field);
if (select === null) {
return; // there is no such operator select field
}
for (i = 0; i < select.options.length; i += 1) {
if (select.options[i].value === operator) {
select.selectedIndex = i;
break;
}
}
Reporting.Filters.operator_changed(field, select);
},
disable_select_option: function (select, field) {
for (var i = 0; i < select.options.length; i += 1) {
if (select.options[i].value === field) {
select.options[i].disabled = true;
break;
}
}
},
// This is called the first time the report loads.
// Params:
// elements: Array of visible filter-select-boxes that have dependents
// (and possibly are dependents themselfes)
initialize_load_dependent_filters: function(elements) {
var filters_to_load, dependent_filters;
dependent_filters = elements.findAll(function (select) { return select.getValue() == '<<inactive>>' || select.select('option[selected]').size()==0 });
filters_to_load = elements.reject( function (select) { return select.getValue() == '<<inactive>>' });
// Filters which are <<inactive>> are probably dependents themselfes, so remove and forget them for now.
// This is OK as they get reloaded later
dependent_filters.each(function(select) {
Reporting.Filters.remove_filter(select.up('tr').readAttribute("data-filter-name"));
});
// For each dependent filter we reload its dependent chain
filters_to_load.each(function(selectBox) {
var sources, selected_values;
Reporting.Filters.activate_dependents(selectBox, function() {
sources = Reporting.Filters.get_dependents(selectBox).collect(function(field) {
return $('tr_' + field).select('.filter_values select').first();
});
sources.each(function(source) {
if (source.hasAttribute('data-initially-selected')) {
selected_values = source.readAttribute('data-initially-selected').replace(/'/g, '"').evalJSON(true);
Reporting.Filters.select_values(source, selected_values);
Reporting.Filters.value_changed(source.up('tr').readAttribute("data-filter-name"));
}
});
if (sources.reject( function (select) { return select.value == '<<inactive>>' }).size() == 0) {
Reporting.Filters.activate_dependents(selectBox);
}
else {
Reporting.RestoreQuery.initialize_load_dependent_filters(sources);
}
});
});
},
restore_filters: function () {
// FIXME: rm_xxx values for filters have to be set after re-displaying them
$$("tr[data-selected=true]").each(function (e) {
var rm_box, filter_name;
rm_box = e.select("input[id^=rm]").first();
filter_name = e.getAttribute("data-filter-name");
rm_box.value = filter_name;
Reporting.Filters.select_option_enabled($("add_filter_select"), filter_name, false);
// correctly display number of arguments of filters depending on their arity
Reporting.Filters.operator_changed(filter_name, $("operators[" + filter_name + "]"));
});
// restore values of dependent filters
Reporting.RestoreQuery.initialize_load_dependent_filters($$('.filters-select[data-all-dependents]').findAll(function(select) {
return select.up('tr').visible()
}));
},
restore_group_bys: function () {
Reporting.GroupBys.group_by_container_ids().each(function(id) {
var container, selected_groups;
container = $(id);
if (container.hasAttribute('data-initially-selected')) {
selected_groups = container.readAttribute('data-initially-selected').replace(/'/g, '"').evalJSON(true);
selected_groups.each(function(group_and_label) {
var group, label;
group = group_and_label[0];
label = group_and_label[1];
Reporting.GroupBys.add_group_by(group, label, container);
});
}
});
}
};
Reporting.onload(function () {
Reporting.RestoreQuery.restore_group_bys();
Reporting.RestoreQuery.restore_filters();
});

@ -0,0 +1,151 @@
var NS4 = (navigator.appName === "Netscape" && parseInt(navigator.appVersion, 10) < 5);
function createOption(theText, theValue, theCategory) {
var newOpt = document.createElement('option');
newOpt.text = theText;
newOpt.value = theValue;
newOpt.setAttribute("data-category", theCategory);
return newOpt;
}
function addOption(theSel, newOpt)
{
var theCategory, opt_groups, i;
theCategory = newOpt.getAttribute("data-category");
theSel = $(theSel);
if (theCategory && (theSel.childElements().length > 0) && theSel.down(0).tagName === "OPTGROUP") { // add the opt to the given category
opt_groups = theSel.childElements();
for (i = 0; i < opt_groups.length; i += 1) {
if (opt_groups[i].getAttribute("data-category") === theCategory) {
opt_groups[i].appendChild(newOpt);
break;
}
}
}
else { // no category given, just add the opt to the end of the select list
theSel.appendChild(newOpt);
}
}
function swapOptions(theSel, index1, index2)
{
theSel = $(theSel);
var text, value, category;
text = theSel.options[index1].text;
value = theSel.options[index1].value;
category = theSel.options[index1].getAttribute("data-category");
theSel.options[index1].text = theSel.options[index2].text;
theSel.options[index1].value = theSel.options[index2].value;
theSel.options[index1].setAttribute("data-category", theSel.options[index2].getAttribute("data-category"));
theSel.options[index2].text = text;
theSel.options[index2].value = value;
theSel.options[index2].setAttribute("data-category", category);
}
function deleteOption(theSel, theIndex)
{
theSel = $(theSel);
var selLength = theSel.length;
if (selLength > 0)
{
theSel.options[theIndex] = null;
}
}
// Returns true if the given select-box has optgroups.
// We assume that a possibly present optgroup is the first child element of the select-box.
function has_optgroups(theSel) {
theSel = $(theSel);
return (theSel.childElements().length > 0) && (theSel.down(0).tagName === "OPTGROUP");
}
// Compares two option elements (return -1 if a < b, if not return 1).
// If those elements have a 'data-sort_by' attribute, we compare that attribute.
// If this is not the case we just compare their labels.
function compareOptions(a, b) {
var a_cmp, b_cmp;
a_cmp = a.getAttribute("data-sort_by") ? a.getAttribute("data-sort_by") : a.text.toLowerCase();
b_cmp = b.getAttribute("data-sort_by") ? b.getAttribute("data-sort_by") : b.text.toLowerCase();
return (a_cmp < b_cmp) ? -1 : 1;
}
// Sorts all elements of the given select-box.
// If that select-box contains optgroups, the options are sorted for each optgroup separately.
function sortOptions(theSel) {
theSel = $(theSel);
if (has_optgroups(theSel)) {
// handle each optgroup separately
theSel.childElements().each(function (group) {
var sorted_elements;
// get all elements of this optgroup and sort them
sorted_elements = $A(group.childElements()).sort(compareOptions);
// make optgroup empty
$A(group.childElements()).each(function (o) {
$(o).remove();
});
// insert sorted elements into opgroup
sorted_elements.each(function (o) {
$(group).insert({'bottom' : o});
});
});
}
else {
// there is no optgroup, so just sort the options
$A(theSel.options).sort(compareOptions).each(function (o, i) {
theSel.options[i] = o;
});
}
}
function moveOptions(theSelFrom, theSelTo)
{
var selLength, selectedText, selectedValues, selectedCategories, selectedCount, i;
theSelFrom = $(theSelFrom);
theSelTo = $(theSelTo);
selLength = theSelFrom.length;
selectedText = [];
selectedValues = [];
selectedCategories = [];
selectedCount = 0;
for (i = selLength - 1; i >= 0; i -= 1) {
if (theSelFrom.options[i].selected)
{
addOption(theSelTo, theSelFrom.options[i].cloneNode(true));
deleteOption(theSelFrom, i);
}
}
if (has_optgroups(theSelTo)) {
sortOptions(theSelTo);
}
if (NS4) {
history.go(0);
}
}
function moveOptionUp(theSel) {
theSel = $(theSel);
var index = theSel.selectedIndex;
if (index > 0) {
swapOptions(theSel, index - 1, index);
theSel.selectedIndex = index - 1;
}
}
function moveOptionDown(theSel) {
theSel = $(theSel);
var index = theSel.selectedIndex;
if (index < theSel.length - 1) {
swapOptions(theSel, index, index + 1);
theSel.selectedIndex = index + 1;
}
}
function selectAllOptions(select)
{
select = $(select);
for (var i = 0; i < select.options.length; i += 1) {
select.options[i].selected = true;
}
}

@ -1,56 +0,0 @@
.help {
margin-left: 5px;
margin-right: 5px;
}
.tooltip {
position: absolute;
margin-top: 3px;
margin-bottom: 3px;
padding: 3px;
width: 400px;
z-index: 256;
color: #000000;
border: 1px solid #000000;
background: #FFFFCC;
font: 12px Verdana, sans-serif;
text-align: left;
padding: -50px;
line-height: 16px;
font-size: 11px;
white-space: normal;
}
.filter-icon {
}
.filter-tip {
}
.group-by-icon {
float: right;
margin-right: 5px;
}
.group-by-tip {
margin-top: -300px;
margin-left: -475px;
}
.filter-legend-icon {
}
.filter-legend-tip {
margin-left: 10px;
}
.group_by-legend-icon {
}
.group_by-legend-tip {
margin-left: 10px;
}

@ -1,11 +1,3 @@
#content p,
#content label,
#content a,
#content div {
font-size: 11px;
line-height: 16px;
}
.cost_types {
padding-bottom: 3px;
}
@ -50,53 +42,29 @@
}
.report th {
border: solid 1px #ccc;
background-color: #e3e3e3;
text-align: center;
}
.report .odd th.inner {
background-color: #e8e8e8;
}
.report .even th.inner {
background-color: #e3e3e3;
border: dotted 1px #ccc;
background-color: #e3e3e3 !important;
}
.report th.inner {
border: solid 1px #ccc;
background-color: #efefef;
text-align: right;
background-color: #efefef !important;
}
.report tr.even:hover .inner,
.report tr.even:hover .bottom,
.report tr.even:hover .empty,
.report tr.even:hover .right {
background-color: #f5f5c5 !important;
.report .odd th.inner {
background-color: #e8e8e8 !important;
}
/* IE7 made me do it! */
.report tr.odd:hover .inner,
.report tr.odd:hover .bottom,
.report tr.odd:hover .empty,
.report tr.odd:hover .right {
.report tr:hover .inner {
background-color: #f5f5c5 !important;
}
.report .top {
border-top-style: solid;
border-top-color: #ccc;
/* border-top: 2px solid #ccc !important; */
border-top: solid 2px #ccc !important;
}
.report .bottom {
border-bottom-style: solid;
border-bottom-color: #ccc;
/* border-bottom: 2px solid #ccc !important; */
}
.report td.penultimate {
border-right-style: solid;
border-bottom: solid 2px #ccc !important;
}
.report thead .inner, .report tfoot .inner {
@ -106,24 +74,19 @@
.report .result {
font-size: 120%;
text-align: right;
}
#result-table {
margin-top: 10px !important;
}
.report thead tr:hover .inner, .report tfoot tr:hover .inner {
background-color: #efefef;
background-color: #efefef !important;
}
.report .left {
text-align: left !important;
text-align: left;
padding-left: 5px;
}
.report .right {
text-align: right !important;
text-align: right;
padding-right: 5px;
}
@ -134,10 +97,20 @@
vertical-align: top;
}
#query_form_content {
width: 916px;
/* Aligning filter elements at the top. */
.new_report fieldset#filters table td {
vertical-align: top;
border-spacing: 5px 5px;
border-color: white;
border-style: solid;
border-width: 2px 0px 0px;
}
.new_report fieldset#filters table td > label {
left: 3px;
top: 3px;
position: relative;
}
/* Overwriting styling for headlines within the query. */
/* TODO: Font-size seems to be a bit odd. Needs some love. */
@ -146,102 +119,111 @@
border: none;
}
.remove-box {
height: 20px;
width: 20px;
background: white;
float: right;
padding: 1px 0 0 0;
display: block;
margin-top: -5px;
margin-right: -2px;
.group_by {
background-color: transparent;
background-position: 50%;
background-repeat: no-repeat;
border: 1px solid #900;
height: 10px;
width: 10px;
margin: 1px;
}
.filter_rem {
color: transparent;
overflow: hidden;
cursor: pointer;
background: no-repeat center center transparent;
height: 18px;
width: 18px;
border-style: none;
display: block;
margin: 0 0 0 2px;
.group_by:hover {
background-color: #EEE;
}
.icon-filter-rem {
background-image: url(../images/close.gif);
position: relative;
float:right;
.move {
height: 25px;
width: 25px;
}
.filter {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
.sort {
height: 15px;
width: 15px;
}
.inactive-filter {
background-color: #FCE29A !important;
.moveUp {
margin-top: 0px;
margin-bottom: 0px;
background-image: url(../images/arrow_D_up.gif);
}
.dependent-filter-label {
margin-left: 20px;
.moveDown {
margin-top: 0px;
margin-bottom: 0px;
background-image: url(../images/arrow_D_down.gif);
}
.filter_values {
white-space: nowrap;
.moveLeft {
margin-left: 0px;
margin-right: 0px;
background-image: url(../images/arrow_D_left.gif);
}
.filter_radio_option {
padding-left: 5px;
padding-right: 5px;
.moveRight {
margin-left: 0px;
margin-right: 0px;
background-image: url(../images/arrow_D_right.gif);
}
#add_filter_block {
margin-top: 6px;
.sortUp {
margin-left: 0px;
margin-right: 0px;
background-image: url(../images/arrow_B_up.gif);
}
#add_filter_select {
margin-bottom: 10px;
.sortDown {
margin-left: 0px;
margin-right: 0px;
background-image: url(../images/arrow_B_down.gif);
}
fieldset#filter-settings table tr.filter:hover {
background: #aaa;
.filter_rem {
overflow: hidden;
font-size: 0 !important;
line-height: 0 !important;
cursor: pointer;
background-repeat: no-repeat;
background-color: transparent;
background-position: 50%;
height: 16px;
width: 16px;
border-style: none;
}
fieldset#filter-settings table tr.filter {
color: #000;
background: #ededed;
.icon-filter-rem {
background-image: url(../images/remove.png);
margin-top: 3px;
margin-right: 3px;
position: absolute;
}
.filter {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
/* Aligning filter elements at the top. */
fieldset#filter-settings table td {
vertical-align: top;
border-spacing: 5px 5px;
border-color: white;
border-style: solid;
border-width: 2px 0px 0px;
padding: 5px 2px 5px 2px;
#add_filter_block {
margin-top: 6px;
}
fieldset#filter-settings table .no-border td {
border-style: none !important;
.new_report fieldset#filters table tr.filter:hover {
background: #aaa;
}
fieldset#filter-settings table td > label {
left: 3px;
top: 3px;
position: relative;
.new_report fieldset#filters table tr.filter {
color: #000;
background: #ededed;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
/* ----- group by --- */
#group_by_area {
font-family: Arial,Geneva,Helvetica,sans-serif;
margin: 5px 0 10px 0;
.drag_element {
/*cursor: move;*/
}
.in_row {
@ -251,94 +233,64 @@ fieldset#filter-settings table td > label {
border-width: 0px;
}
.group_by_element {
margin-left: -15px;
cursor: move;
}
/* have #content here to overwrite line-heigth of other themes through being more specific*/
#content .group_by_label {
.group_by_label {
margin: 0px;
padding: 0px 18px 0 0;
height: 14px !important;
min-width: 60px;
text-align: center;
white-space: nowrap;
font-weight: bold;
padding: 2px 5px 0 5px;
color: #fff;
background-color: #9A9A9A;
height: 22px;
line-height: 22px;
cursor: move;
}
.group_by_label.hover {
background-color: #009999 !important;
.arrow {
background-repeat: no-repeat;
height: 19px !important;
width: 19px !important;
border: none;
margin-left: auto;
margin-right: auto;
}
.group_by_remove {
background-color: #9A9A9A;
height: 7px;
width: 8px;
position: absolute;
top: 7px;
right: 18px;
cursor: pointer;
background: no-repeat left center;
background-image: url(../images/remove.gif);
.arrow_both {
background-image: url(../images/arrow_both.png);
}
.group_by_remove.hover {
background-color: #009999 !important;
.arrow_both_hover_left {
background-image: url(../images/arrow_both_hover_left.png);
}
/* have #content here to overwrite line-heigth of other themes through being more specific*/
#content .group_by_caption {
color: #FFFFFF;
background-color: #000000;
font-weight: bold;
padding: 0 7px;
height: 22px;
line-height: 22px;
min-width: 55px;
.arrow_both_remove {
background-image: url(../images/arrow_both_remove.png);
}
.arrow {
height: 0;
width: 0;
border-style: solid;
border-width: 11px 11px 11px 7px;
.arrow_both_hover_right {
background-image: url(../images/arrow_both_hover_right.png);
}
.arrow_left {
border-color: #9A9A9A #9A9A9A #9A9A9A transparent;
background-image: url(../images/arrow_left.png);
}
.arrow_left.hover {
border-color: #009999 #009999 #009999 transparent !important;
.arrow_left_remove {
background-image: url(../images/arrow_left_remove.png);
}
.arrow_right {
border-color: transparent transparent transparent #9A9A9A;
}
.arrow_right.hover {
border-color: transparent transparent transparent #009999 !important;
}
.arrow_group_by_caption {
border-color: transparent transparent transparent #000000;
.arrow_left_hover {
background-image: url(../images/arrow_left_hover.png);
}
.drag_container {
padding: 0;
height: 22px;
margin-bottom: 1em;
background-color: #EEE;
padding: 5px 0 3px 10px;
background-color: #ededed;
border: 1px dotted #CCCCCC;
margin-bottom: 4px;
height: 23px;
}
.drag_container select {
margin-right: 3px;
float: right;
float: right;
margin-right: 20px;
}
.drag_target {
@ -347,10 +299,9 @@ fieldset#filter-settings table td > label {
.drag_container_accept {
background-color: #F5F5C5;
/*border: 1px solid #BBBBBB;*/
}
/* -- end group-by -- */
td .drill_down, th .drill_down {
font-size: 8px;
display: block;
@ -378,10 +329,6 @@ td:hover .drill_down, th:hover .drill_down {
padding: 4px;
}
.form_controls {
margin-top: 6px;
}
.buttons .reporting_button:hover {
background-color: #666666;
}
@ -405,11 +352,11 @@ td:hover .drill_down, th:hover .drill_down {
margin: 2px;
}
.buttons .button {
.buttons .apply {
background-color: #9a9a9a;
border: none;
cursor: pointer;
margin: 0px;
margin-right: 4px;
overflow: visible;
text-align: center;
white-space: nowrap;
@ -418,23 +365,11 @@ td:hover .drill_down, th:hover .drill_down {
padding: 4px;
}
.buttons .apply {
background-color: #990000;
}
.buttons .apply:hover {
background-color: #CC0000;
}
.buttons .secondary {
background-color: #9a9a9a;
}
.buttons .secondary:hover {
background-color: #666666;
}
.buttons .button span em {
.buttons .apply span em {
color: white;
display: block;
font-size: 11px;
@ -443,88 +378,33 @@ td:hover .drill_down, th:hover .drill_down {
line-height: 17px;
}
.buttons .button span {
display: inline-block;
margin: 3px;
}
.buttons .button-icon {
padding: 0px 5px 0 16px;
cursor: pointer;
background: no-repeat left center;
border-style: none;
.buttons .apply span {
display: inline-block;
margin-top: 0px;
margin-bottom: 0px;
}
.button .icon-save {
background-image: url(../images/disk.gif);
}
.button .icon-save-as {
background-image: url(../images/disks.gif);
}
.button .icon-delete {
background-image: url(../images/delete.gif);
}
.button .icon-clear {
background-image: url(../images/remove.gif);
margin: 0px;
}
div.button_form {
div#button_form {
/* TODO IE Compatibility! */
background-color: white;
border: 1px solid gray;
border: 1px solid grey;
-moz-border-radius: 3px;
border-radius: 3px;
left: 100px;
position: absolute;
padding: 5px;
margin-top: -10px;
}
div.button_form * input#name {
div#button_form * input#name {
width: 200px
}
div.button_form * input[type="button"] {
div#button_form * input[type="button"] {
float: right;
} br { clear: right; }
div.button_form p * {
margin: 2px;
}
/***** Save and Delete Reports ****/
#save_as_form, #delete_form {
z-index: 999;
}
#progressbar {
display: none;
}
#progressbar_container {
width: 300px;
height: 16px;
border: 1px solid #ccc;
padding: 0;
margin: 0;
position: relative;
background-color: #9A9A9A;
background-repeat: repeat-x;
}
#progressbar_container div {
background-color:#fff;
}
/***** Ajax indicator ******/
#ajax-indicator {
font-family: Verdana, sans-serif;
position: absolute; /* fixed not supported by IE */
background-color:#eee;
border: 1px solid #bbb;
@ -551,25 +431,3 @@ html>body #ajax-indicator { position: fixed; }
vertical-align: bottom;
}
/* Calendar Fixes */
div.calendar /* let calendar expand properly */
{
font-size: medium;
line-height: normal;
}
div.calendar table div { /* make sure the nested divs are large enough, too */
font-size: medium;
line-height: normal;
}
.calendar .combo .label, .calendar .combo .label-IEfix { /* set the proper size for the combo boxes with months and years */
font-size: 10px;
line-height: normal;
}
.calendar tbody .day { /* avoid jitter during quick mouse-overs over days */
border: 1px dotted transparent;
padding: 1px 3px 1px 1px;
}

@ -3,29 +3,14 @@ de:
units: Einheiten
label_less: "<"
label_greater: ">"
label_inactive: "«nicht aktiviert»"
label_report: "Report"
label_columns: "Spalten"
label_rows: "Zeilen"
label_yes: Ja
label_no: Nein
label_group_by: "Gruppieren nach"
label_group_by_add: "Gruppierung hinzufügen"
label_filter: "Filter"
label_filter_plural: "Filter"
label_filter_add: "Filter hinzufügen"
label_count: Anzahl
label_sum: Summe
label_none: "(Keine Angabe)"
label_help: Hilfe
description_drill_down: Details anzeigen
validation_failure_date: "ist kein gültiges Datum"
validation_failure_integer: "ist keine ganze Zahl"
load_query_question: "Der Report wird %{size} Tabellen-Zellen haben, was sehr rechenintensiv sein kann. Wollen Sie dennoch versuchen, den Report durch zu führen?"
label_progress_bar_explanation: "Report wird erstellt ..."

@ -3,29 +3,14 @@ en:
units: Units
label_less: "<"
label_greater: ">"
label_inactive: "«inactive»"
label_report: "Report"
label_columns: "Columns"
label_rows: "Rows"
label_group_by: "Group by"
label_group_by_add: "Add Group-by Attribute"
label_filter: "Filter"
label_filter_plural: "Filters"
label_filter_add: Add Filter
label_yes: "Yes"
label_no: "No"
label_count: Count
label_sum: Sum
label_none: "(no data)"
label_help: Help
description_drill_down: Show details
validation_failure_date: "is not a valid date"
validation_failure_integer: "is not a valid integer"
load_query_question: "Report will have %{size} table cells and may take some time to render. Do you still want to try rendering it?"
label_progress_bar_explanation: "Generating report..."

@ -3,8 +3,3 @@ fail "upgrade ruby version, ruby < 1.8.7 suffers from Hash#hash bug" if {:a => 1
require 'big_decimal_patch'
require 'to_date_patch'
# Defines the minimum number of cells for a 'big' report
# Big reports may be handled differently in the UI - i.e. ask the user
# if he's really sure to execute such a heavy report
Widget::Table::Progressbar.const_set 'THRESHHOLD', 2000

@ -18,10 +18,6 @@ class Report < ActiveRecord::Base
@@accepted_properties ||= []
end
def self.reporting_connection
connection
end
def self.chain_initializer
@chain_initializer ||= []
end
@ -34,8 +30,8 @@ class Report < ActiveRecord::Base
end
def serialize
# have to take the reverse group_bys to retain the original order when deserializing
self.serialized = { :filters => filters.collect(&:serialize).sort, :group_bys => group_bys.collect(&:serialize).reverse }
# have to take the reverse to retain the original order when deserializing
self.serialized = { :filters => filters.collect(&:serialize).reverse, :group_bys => group_bys.collect(&:serialize).reverse }
end
def deserialize
@ -77,10 +73,7 @@ class Report < ActiveRecord::Base
def chain(klass = nil, options = {})
build_new_chain unless @chain
if klass
@chain = klass.new @chain, options
@chain.engine = self.class
end
@chain = klass.new @chain, options if klass
@chain = @chain.parent until @chain.top?
@chain
end
@ -114,8 +107,8 @@ class Report < ActiveRecord::Base
@table = self.class::Table.new(self)
end
def group_bys(type=nil)
chain.select { |c| c.group_by? && (type.nil? || c.type == type) }
def group_bys
chain.select { |c| c.group_by? }
end
def filters
@ -130,7 +123,7 @@ class Report < ActiveRecord::Base
def_delegators :transformer, :column_first, :row_first
def_delegators :chain, :empty_chain, :top, :bottom, :chain_collect, :sql_statement, :all_group_fields, :child, :clear, :result
def_delegators :result, :each_direct_result, :recursive_each, :recursive_each_with_level, :each, :each_row, :count,
:units, :final_number
:units, :size, :final_number
def_delegators :table, :row_index, :colum_index
def to_a
@ -141,17 +134,28 @@ class Report < ActiveRecord::Base
chain.to_s
end
def size
size = 0
recursive_each {|r| size += r.size }
size
def hash
report_string = ""
report_string.concat('filters: [')
report_string.concat(filters.map { |f|
f.class.underscore_name + f.operator.to_s + (f.values ? f.values.to_json : "")
}.sort.join(', '))
report_string.concat(']')
report_string.concat(', group_bys: {')
report_string.concat(group_bys.group_by(&:type).map { |t, gbs|
"#{t} : [#{gbs.collect(&:class).collect(&:underscore_name).join(', ')}]"
}.join(', '))
report_string.concat('}')
report_string.hash
end
def cache_key
deserialize unless @chain
parts = [self.class.table_name.sub('_reports', '')]
parts.concat [filters.sort, group_bys].map { |l| l.map(&:cache_key).join(" ") }
parts.join '/'
def == another_report
hash == another_report.hash
end
private

@ -45,7 +45,7 @@ class Report < ActiveRecord::Base
end
def self.table_joins
(@table_joins ||= []).clone
@table_joins ||= []
end
def self.table_from(value)
@ -56,7 +56,7 @@ class Report < ActiveRecord::Base
def self.join_table(*args)
@last_table = table_from(args.last)
(@table_joins ||= []) << args
table_joins << args
end
def self.underscore_name
@ -78,10 +78,6 @@ class Report < ActiveRecord::Base
engine.chain_initializer.push block
end
def self.cache_key
@cache_key ||= underscore_name
end
inherited_attribute :label, :default => :translation_needed
inherited_attribute :properties, :list => true
@ -137,7 +133,7 @@ class Report < ActiveRecord::Base
options.each do |key, value|
unless self.class.extra_options.include? key
raise ArgumentError, "may not set #{key}" unless engine.accepted_properties.include? key.to_s
send "#{key}=", value
send "#{key}=", value if value
end
end
self.child, child.parent = child, self if child
@ -210,7 +206,7 @@ class Report < ActiveRecord::Base
end
def compute_result
engine::Result.new engine.reporting_connection.select_all(sql_statement.to_s), {}, type
engine::Result.new engine.connection.select_all(sql_statement.to_s), {}, type
end
def table_joins
@ -305,35 +301,5 @@ class Report < ActiveRecord::Base
self.class.field
end
def mapping
self.class.method(:mapping).to_proc
end
def self.mapping(value)
value.to_s
end
def self.mapping_for(field)
@field_map ||= (engine::Filter.all + engine.GroupBy.all).inject(Hash.new {|h,k| h[k] = []}) do |hash,cbl|
hash[cbl.field] << cbl.mapping
end
@field_map[field]
end
def help_text
self.class.help_text
end
##
# Sets a help text to be displayed for this kind of Chainable.
def self.help_text=(sym)
@help_text = sym
end
def self.help_text(sym = nil)
@help_text = sym if sym
@help_text
end
end
end

@ -1,334 +0,0 @@
module Report::Controller
def self.included(base)
base.class_eval do
attr_accessor :report_engine
helper_method :current_user
helper_method :allowed_to?
include ReportingHelper
helper ReportingHelper
helper { def engine; @report_engine; end }
before_filter :determine_engine
before_filter :prepare_query, :only => [:index, :create]
before_filter :find_optional_report, :only => [:index, :show, :update, :delete, :rename]
before_filter :possibly_only_narrow_values
before_filter { @no_progress = no_progress? }
end
end
def index
table
end
##
# Render the report. Renders either the complete index or the table only
def table
if set_filter?
if no_progress?
table_without_progress_info
else
table_with_progress_info
end
end
end
def table_without_progress_info
stream do |response, output|
render_widget Widget::Table::ReportTable, @query, :to => output
end
end
def table_with_progress_info
render :text => render_widget(Widget::Table::Progressbar, @query), :layout => false
end
if Rails.version.start_with? "3"
def stream(&block)
self.response_body = block
end
else
def stream(&block)
render :text => block, :layout => false
end
end
##
# Create a new saved query. Returns the redirect url to an XHR or redirects directly
def create
@query.name = params[:query_name].present? ? params[:query_name] : ::I18n.t(:label_default)
@query.is_public = !!params[:query_is_public]
@query.send("#{user_key}=", current_user.id)
@query.save!
if request.xhr? # Update via AJAX - return url for redirect
render :text => url_for(:action => "show", :id => @query.id)
else # Redirect to the new record
redirect_to :action => "show", :id => @query.id
end
end
##
# Show a saved record, if found. Raises RecordNotFound if the specified query
# at :id does not exist
def show
if @query
store_query(@query)
table
render :action => "index" unless performed?
else
raise ActiveRecord::RecordNotFound
end
end
##
# Delete a saved record, if found. Redirects to index on success, raises a
# RecordNotFound if the query at :id does not exist
def delete
if @query
@query.destroy if allowed_to? :delete, @query
else
raise ActiveRecord::RecordNotFound
end
redirect_to :action => "index", :default => 1
end
##
# Update a record with new query parameters and save it. Redirects to the
# specified record or renders the updated table on XHR
def update
if params[:set_filter].to_i == 1 #save
old_query = @query
prepare_query
old_query.migrate(@query)
old_query.save!
@query = old_query
end
if request.xhr?
table
else
redirect_to :action => "show", :id => @query.id
end
end
##
# Rename a record and update its publicity. Redirects to the updated record or
# renders the updated name on XHR
def rename
@query.name = params[:query_name]
if params.has_key?(:query_is_public)
@query.is_public = params[:query_is_public] == 'true'
end
@query.save!
store_query(@query)
unless request.xhr?
redirect_to :action => "show", :id => @query.id
else
render :text => @query.name
end
end
##
# Determine the available values for the specified filter and return them as
# json, if that was requested. This will be executed INSTEAD of the actual action
def possibly_only_narrow_values
if params[:narrow_values] == "1"
sources = params[:sources]
dependent = params[:dependent]
query = report_engine.new
sources.each do |dependency|
query.filter(dependency.to_sym,
:operator => params[:operators][dependency],
:values => params[:values][dependency])
end
query.column(dependent)
values = [[::I18n.t(:label_inactive), '<<inactive>>']] + query.result.collect {|r| r.fields[query.group_bys.first.field] }
# replace null-values with corresponding placeholder
values = values.map { |value| value.nil? ? [::I18n.t(:label_none), '<<null>>'] : value }
# try to find corresponding labels to the given values
values = values.map do |value|
filter = report_engine::Filter.const_get(dependent.camelcase.to_sym)
filter_value = filter.label_for_value value
if filter_value && filter_value.first.is_a?(Symbol)
[::I18n.t(filter_value.first), filter_value.second]
elsif filter_value && filter_value.first.is_a?(String)
[filter_value.first, filter_value.second]
else
value
end
end
render :json => values.to_json
end
end
##
# Determine the requested engine by constantizing from the :engine parameter
# Sets @report_engine and @title based on that, and makes the engine available
# to views and widgets via the #engine method.
# Raises RecordNotFound on failure
def determine_engine
@report_engine = params[:engine].constantize
@title = "label_#{@report_engine.name.underscore}"
rescue NameError
raise ActiveRecord::RecordNotFound, "No engine found - override #determine_engine"
end
##
# Determines if the request contains filters to set
def set_filter? #FIXME: rename to set_query?
params[:set_filter].to_i == 1
end
##
# Determines if the requested table should be rendered with a progressbar
def no_progress?
!!params[:immediately]
end
##
# Return the active filters
def filter_params
filters = http_filter_parameters if set_filter?
filters ||= session[report_engine.name.underscore.to_sym].try(:[], :filters)
filters ||= default_filter_parameters
end
##
# Return the active group bys
def group_params
groups = http_group_parameters if set_filter?
groups ||= session[report_engine.name.underscore.to_sym].try(:[], :groups)
groups ||= default_group_parameters
end
##
# Extract active filters from the http params
def http_filter_parameters
params[:fields] ||= []
(params[:fields].reject { |f| f.empty? } || []).inject({:operators => {}, :values => {}}) do |hash, field|
hash[:operators][field.to_sym] = params[:operators][field]
hash[:values][field.to_sym] = params[:values][field]
hash
end
end
##
# Extract active group bys from the http params
def http_group_parameters
if params[:groups]
rows = params[:groups]["rows"]
columns = params[:groups]["columns"]
end
{:rows => (rows || []), :columns => (columns || [])}
end
##
# Set a default query to cut down initial load time
def default_filter_parameters
{ :operators => {}, :values => {} }
end
##
# Set a default query to cut down initial load time
def default_group_parameters
{:columns => [:sector_id], :rows => [:country_id]}
end
##
# Determines if the query settings should be reset
def force_default?
params[:default].to_i == 1
end
##
# Prepare the query from the request
def prepare_query
determine_settings
@query = build_query(session[report_engine.name.underscore.to_sym][:filters],
session[report_engine.name.underscore.to_sym][:groups])
end
##
# Determine the query settings the current request and save it to
# the session.
def determine_settings
if force_default?
filters = default_filter_parameters
groups = default_group_parameters
session[report_engine.name.underscore.to_sym].try :delete, :name
else
filters = filter_params
groups = group_params
end
cookie = session[report_engine.name.underscore.to_sym] || {}
session[report_engine.name.underscore.to_sym] = cookie.merge({:filters => filters, :groups => groups})
end
##
# Build the query from the passed session hash
def build_query(filters, groups = {})
query = report_engine.new
query.tap do |q|
filters[:operators].each do |filter, operator|
unless filters[:values][filter]==["<<inactive>>"]
values = filters[:values][filter].map{ |v| v=='<<null>>' ? nil : v }
q.filter(filter.to_sym,
:operator => operator,
:values => values )
end
end
end
groups[:rows].try(:reverse_each) {|r| query.row(r) }
groups[:columns].try(:reverse_each) {|c| query.column(c) }
query
end
##
# Store query in the session
def store_query(query)
cookie = {}
cookie[:groups] = @query.group_bys.inject({}) do |h, group|
((h[:"#{group.type}s"] ||= []) << group.field.to_sym) && h
end
cookie[:filters] = @query.filters.inject({:operators => {}, :values => {}}) do |h, filter|
h[:operators][filter.field.to_sym] = filter.operator.to_s
h[:values][filter.field.to_sym] = filter.values
h
end
cookie[:name] = @query.name if @query.name
session[report_engine.name.underscore.to_sym] = cookie
end
##
# Override in subclass if user key
def user_key
'user_id'
end
##
# Fallback: @current_user needs to be set for the engine
def current_user
if @current_user.nil?
raise NotImplementedError, "The #{self.class} should have set @current_user before this request"
end
@current_user
end
##
# Abstract: Implementation required in application
def allowed_to?(action, subject, user = current_user)
raise NotImplementedError, "The #{self.class} should have implemented #allowed_to?(action, subject, user)"
end
##
# Find a report if :id was passed as parameter.
# Raises RecordNotFound if an invalid :id was passed.
def find_optional_report
if params[:id]
@query = report_engine.find(params[:id].to_i,
:conditions => ["(is_public = 1) OR (#{user_key} = ?)", current_user.id])
@query.deserialize if @query
end
rescue ActiveRecord::RecordNotFound
end
end

@ -16,10 +16,6 @@ class Report::Filter
attr_accessor :values
def cache_key
self.class.cache_key + operator.to_s + Array(values).join(',')
end
##
# A Filter is 'heavy' if it possibly returns a _hughe_ number of available_values.
# In that case the UI-guys should think twice about displaying all the values.
@ -27,71 +23,6 @@ class Report::Filter
false
end
# Indicates whether this Filter is a multiple choice filter,
# meaning that the user must select a value of a given set of choices.
def self.is_multiple_choice?
false
end
##
# A Filter may have depentent filters. See the following example:
# Filter::Project.dependents --> [Filter::IssueId]
# This could result in a UI where, if the Project-Filter was selected,
# the IssueId-filter automatically shows up.
# Arguments:
# - any subclass of Reporting::Filter::Base which shall be the dependent filter
# - OR multiple Filters if there are multiple possible dependents and you
# want the application-js to decide which dependent to follow
def self.dependent(*args)
@dependents ||= []
@dependents += args unless args.empty?
@dependents
end
class << self
alias :dependents :dependent
end
# need this for sort
def <=> other
self.class.underscore_name <=> other.class.underscore_name
end
def self.has_dependent?
!dependents.empty?
end
##
# Returns an array of filters of which this filter is a dependent
def self.dependent_from
engine::Filter.all.select { |f| f.dependents.include? self }
end
##
# Returns true/false depending of wether any filter has this filter a a dependent
def self.is_dependent?
!dependent_from.empty?
end
def self.cached(*args)
@cached ||= {}
@cached[args] ||= send(*args)
end
##
# all_dependents computes the depentends of this filter and recursively
# all_dependents of this class' dependents.
def self.all_dependents
self.cached(:compute_all_dependents)
end
def self.compute_all_dependents(starting_from = nil)
starting_from ||= dependents
starting_from.inject([]) do |list,dependent|
list + Array(dependent) + dependent.all_dependents
end
end
def value=(val)
self.values = [val]
end
@ -126,11 +57,7 @@ class Report::Filter
end
def self.available_values(params = {})
[] #array of [:label_of_value, value]-kind arrays
end
def self.label_for_value(value)
available_values(:reverse_search => true).find{ |v| v.second == value || v.second.to_s == value }
raise NotImplementedError, "subclass responsibility"
end
def correct_position?
@ -190,8 +117,6 @@ class Report::Filter
super.tap do |query|
arity = operator.arity
values = [*self.values].compact
#if there is just the nil it might be actually intendet to be there
values.unshift nil if Array(self.values).size==1 && Array(self.values).first.nil?
values = values[0, arity] if values and arity >= 0 and arity != values.size
operator.modify(query, field, *values) unless field.empty?
end

@ -1,11 +0,0 @@
class Report::Filter
class MultiChoice < Base
dont_inherit :available_operators
use '='
def self.is_multiple_choice?
true
end
end
end

@ -4,6 +4,11 @@ class Report::GroupBy
inherited_attributes :group_fields, :list => true, :merge => false
def self.inherited(klass)
klass.group_fields klass.field
super
end
def correct_position?
type == :row or !child.is_a?(engine::GroupBy::Base) or child.type == :column
end
@ -16,10 +21,6 @@ class Report::GroupBy
child.filter?
end
def cache_key
self.class.cache_key + type.to_s[0,1]
end
##
# @param [FalseClass, TrueClass] prefix Whether or not add a table prefix the field names
# @return [Array<String,Symbol>] List of group by fields corresponding to self and all parents'
@ -70,7 +71,6 @@ class Report::GroupBy
def initialize(child = nil, optios = {})
super
extend aggregation_mixin
group_fields field
end
def result

@ -85,10 +85,7 @@ class Report::Operator
new "=", :label => :label_equals do
def modify(query, field, *values)
case
when values.size == 1 && values.first.nil?
query.where "#{field} IS NULL"
when values.compact.empty?
if values.compact.empty?
query.where "1=0"
else
query.where "#{field} IN #{collection(*values)}"
@ -183,16 +180,6 @@ class Report::Operator
end
end
new "?=", :label => :label_null_or_equal do
def modify(query, field, *values)
where_clause = "(#{field} IS NULL"
where_clause += " OR #{field} IN #{collection(*values)}" unless values.compact.empty?
where_clause += ")"
query.where where_clause
query
end
end
end
#############################################################################################
@ -223,10 +210,6 @@ class Report::Operator
all[name.to_s] or raise ArgumentError, "Operator #{name.inspect} not defined"
end
def self.exists?(name)
all.has_key?(name.to_s)
end
def self.defaults(&block)
class_eval &block
end
@ -292,17 +275,6 @@ class Report::Operator
self.name <=> other.name
end
## Creates an alias for a given operator.
def aka(alt_name, alt_label)
all = self.class.all
alt = alt_name.to_s
raise ArgumentError, "Can't alias operator with an existing one's name ( #{alt} )." if all.has_key?(alt)
op = all[name].clone
op.send(:rename_to, alt_name)
op.singleton_class.send(:define_method, 'label') { alt_label }
all[alt] = op
end
module DateRange
def modify(query, field, from, to)
query.where ["#{field} > '%s'", quoted_date((Date.yesterday + from).to_time.end_of_day)] if from
@ -311,12 +283,6 @@ class Report::Operator
end
end
private
def rename_to(new_name)
@name = new_name
end
# Done with class method definition, let's initialize the operators
load

@ -1,9 +1,7 @@
module Report::QueryUtils
Infinity = 1.0/0
alias singleton_class metaclass unless respond_to? :singleton_class
delegate :quoted_false, :quoted_true, :to => "engine.reporting_connection"
delegate :quoted_false, :quoted_true, :to => "engine.connection"
attr_writer :engine
module PropagationHook
@ -34,14 +32,8 @@ module Report::QueryUtils
#
# @return [Class] subclass
def engine
return @engine if @engine
if is_a? Module
@engine = Object.const_get(name[/^[^:]+/] || :Report)
elsif respond_to? :parent and parent.respond_to? :engine
parent.engine
else
self.class.engine
end
return self.class.engine unless is_a? Module
@engine ||= Object.const_get(name[/^[^:]+/] || :Report)
end
##
@ -51,11 +43,7 @@ module Report::QueryUtils
# @return [Object] Quoted version
def quote_string(str)
return str unless str.respond_to? :to_str
engine.reporting_connection.quote_string(str)
end
def current_language
::I18n.locale
engine.connection.quote_string(str)
end
##
@ -83,7 +71,7 @@ module Report::QueryUtils
end
def quoted_date(date)
engine.reporting_connection.quoted_date date.to_dateish
engine.connection.quoted_date date.to_dateish
end
##
@ -175,59 +163,22 @@ module Report::QueryUtils
"-- code specific for #{adapter_name}\n\t" << super(field)
end
##
# Converts value with a given behavior, but treats nil differently.
# Params
# - value: the value to convert
# - weight_of_nil (optional): How a nil should be treated.
# :infinit - makes a nil weight really heavy, which will make it stay
# at the very end when sorting
# :negative_infinit - opposite of :infinit, let's the nil stay at the very beginning
# any other object - nil's will be replaced by thyt object
# - block (optional) - defines how to convert values which are not nil
# if no block is given, values stay untouched
def convert_unless_nil(value, weight_of_nil = :infinit)
if value.nil?
if weight_of_nil == :infinit
1.0/0 # Infinity, which is greater than any string or number
elsif weight_of_nil == :negative_infinit
-1.0/0 # negative Infinity, which is smaller than any string or number
else
weight_of_nil
end
else
if block_given?
yield value
else
value
end
end
end
def map_field(key, value)
case key.to_s
when "singleton_value", /_id$/ then convert_unless_nil(value) {|v| v.to_i }
else convert_unless_nil(value) {|v| v.to_s }
if key.to_s == "singleton_value"
value.to_i
else
value.to_s
end
end
def adapter_name
engine.reporting_connection.adapter_name.downcase.to_sym
engine.connection.adapter_name.downcase.to_sym
end
def cache
Report::QueryUtils.cache
end
def compare(first, second)
first = Array(first).flatten
second = Array(second).flatten
first.zip second do |a, b|
return (a <=> b) || (a == Infinity ? 1 : -1) if a != b
end
second.size > first.size ? -1 : 0
end
def mysql?
[:mysql, :mysql2].include? adapter_name.to_s.downcase.to_sym
end

@ -31,24 +31,6 @@ class Report::Result
fields[key]
end
##
# Override if you want to influence the result grouping.
#
# @return A value for grouping or nil if the given field should
# not be considered for grouping.
def map_group_by_value(key, value)
value
end
##
# This method is called when this result is requested as #grouped_by something
# just before the result is returned.
#
# @param data This result's grouped data.
def group_by_data_ready(data)
# good to know!
end
def grouped_by(fields, type, important_fields = [])
@grouped_by ||= {}
list = begin
@ -58,12 +40,8 @@ class Report::Result
data = group_by do |entry|
# index for group is a hash
# i.e. { :foo => 10, :bar => 20 } <= this is just the KEY!!!!
fields.inject({}) do |hash, key|
val = map_group_by_value(key, entry.fields[key])
hash.merge key => val
end
fields.inject({}) { |hash, key| hash.merge key => entry.fields[key] }
end
group_by_data_ready(data)
# map group back to array, all fields with same key get grouped into one list
data.keys.map { |f| engine::Result.new data[f], f, type, important_fields }
end
@ -93,7 +71,7 @@ class Report::Result
end
def final?(type)
type? type and (direct? or size == 0 or first.type != type)
type? type and (direct? or first.type != type)
end
def type?(type)
@ -128,6 +106,7 @@ class Report::Result
def set_key(index = [])
self.key = index.map { |k| map_field(k, fields[k]) }
end
end
class DirectResult < Base
@ -176,7 +155,7 @@ class Report::Result
def sort!(force = false)
return false if @sorted and not force
values.sort! { |a,b| compare a.key, b.key }
values.sort! { |a,b| a.key <=> b.key }
values.each { |e| e.sort! force }
@sorted = true
end

@ -30,8 +30,8 @@ class Report::SqlStatement
# Generates new SqlStatement.
#
# @param [String, #to_s] table Table name (or subselect) for from part.
def initialize(table, desc = "")
self.desc = desc
def initialize(table, desc = nil)
self.desc = desc || "unkown statement from #{caller.first}"
from table
end
@ -74,7 +74,6 @@ class Report::SqlStatement
# FIXME I'm ugly
@sql ||= begin
sql = "\n-- BEGIN #{desc}\n" \
"-- DB: #{ConnectionSwitcher.config_name}\n" \
"SELECT\n#{select.map { |e| "\t#{e}" }.join ",\n"}" \
"\nFROM\n\t#{from.gsub("\n", "\n\t")}" \
"\n\t#{joins.map { |e| e.gsub("\n", "\n\t") }.join "\n\t"}" \
@ -175,7 +174,7 @@ class Report::SqlStatement
return(@select || default_select) if fields.empty?
(@select ||= []).tap do
@sql = nil
fields.reject {|f| never_select.include? f}.each do |f|
fields.each do |f|
case f
when Array
if f.size == 2 and f.first.respond_to? :table_name then select field_name_for(f)
@ -193,22 +192,6 @@ class Report::SqlStatement
end
end
def unselect(*fields)
@sql = nil
@select = @select.reject do |field|
fields.find { |f| f == field }
end
end
def never_select(*fields)
(@never_select ||= []).tap do
unless fields.empty?
@never_select += fields
unselect *fields
end
end
end
##
# Return the names which have been bound through select statements
# @return [Array<String>] All fields for select part
@ -226,7 +209,7 @@ class Report::SqlStatement
def group_by(*fields)
@sql = nil unless fields.empty?
(@group_by ||= []).tap do
fields.reject {|f| never_group_by.include? f}.each do |e|
fields.each do |e|
if e.is_a? Array and (e.size != 2 or !e.first.respond_to? :table_name)
group_by(*e)
else
@ -237,22 +220,6 @@ class Report::SqlStatement
end
end
def group_not_by(*fields)
@sql = nil
@group_by = @group_by.reject do |field|
fields.find { |f| f == field }
end
end
def never_group_by(*fields)
(@never_group_by ||= []).tap do
unless fields.empty?
@never_group_by += fields
group_not_by *fields
end
end
end
##
# @return [TrueClass, FalseClass] Whether or not to add a group by part.
def group_by?

@ -35,7 +35,7 @@ class Report::Table
##
# @param [Array] expected Fields expected
# @param [Array,Hash,Result] given Fields/result to be tested
# @param [Array,Hash,Resul] given Fields/result to be tested
# @return [TrueClass,FalseClass]
def satisfies?(type, expected, given)
given = fields_from(given, type) if given.respond_to? :to_hash
@ -78,7 +78,7 @@ class Report::Table
@indexes ||= begin
indexes = Hash.new { |h,k| h[k] = Set.new }
query.each_direct_result { |result| [:row, :column].each { |t| indexes[t] << fields_from(result, t) } }
indexes.keys.each { |k| indexes[k] = indexes[k].sort { |x, y| compare x, y } }
indexes.keys.each { |k| indexes[k] = indexes[k].sort { |x, y| x <=> y } }
indexes
end
@indexes[type]

@ -1,59 +0,0 @@
class Widget < ActionView::Base
include ActionView::Helpers::TagHelper
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::JavaScriptHelper
attr_accessor :output_buffer, :controller, :config, :_content_for, :_routes, :subject
extend ProactiveAutoloader
def self.new(subject)
super(subject).tap do |o|
o.subject = subject
end
end
# FIXME: There's a better one in ReportingHelper, remove this one
def l(s)
::I18n.t(s.to_sym, :default => s.to_s.humanize)
end
def current_language
::I18n.locale
end
def protect_against_forgery?
false
end
def method_missing(name, *args, &block)
begin
controller.send(name, *args, &block)
rescue NoMethodError
raise NoMethodError, "undefined method `#{name}' for #<#{self.class}:0x#{self.object_id}>"
end
end
module RenderWidgetInstanceMethods
def render_widget(widget, subject, options = {}, &block)
i = widget.new(subject)
if Rails.version.start_with? "3"
i.config = config
i._routes = _routes
else
i.output_buffer = ""
end
i._content_for = @_content_for
i.controller = respond_to?(:controller) ? controller : self
i.render_with_options(options, &block)
end
end
end
ActionView::Base.send(:include, Widget::RenderWidgetInstanceMethods)
ActionController::Base.send(:include, Widget::RenderWidgetInstanceMethods)
if Rails.version.start_with? "2"
class ::String; def html_safe; self; end; end
end
class ::String; def write(s); concat(s); end; end

@ -1,126 +0,0 @@
require 'digest/sha1'
class Widget::Base < Widget
attr_reader :engine, :output
def self.dont_cache!
@dont_cache = true
end
def self.dont_cache?
@dont_cache
end
def initialize(query)
@subject = query
@engine = query.class
@options = {}
end
##
# Write a string to the canvas. The string is marked as html_safe.
# This will write twice, if @cache_output is set.
def write(str)
str ||= ""
@output ||= "".html_safe
@output.write str.html_safe
@cache_output.write(str.html_safe) if @cache_output
str.html_safe
end
##
# Render this widget. Abstract method. Needs to call #write at least once
def render
raise NotImplementedError, "#render is missing in my subclass #{self.class}"
end
##
# Render this widget, passing options.
# Available options:
# :to => canvas - The canvas (streaming or otherwise) to render to. Has to respond to #write
def render_with_options(options = {}, &block)
@help_text = options[:help_text]
set_canvas(options.delete(:to)) if options.has_key? :to
@options = options
render_with_cache(options, &block)
@output
end
##
# An optional help text. If defined the Help Widget
# displaying the given text is going to be placed
# next to this Widget, if it supports that.
def help_text
@help_text
end
def help_text=(text)
@help_text = text
end
def cache_key
@cache_key ||= Digest::SHA1::hexdigest begin
if subject.respond_to? :cache_key
"#{I18n.locale.to_s}/#{self.class.name.demodulize}/#{subject.cache_key}/#{@options.sort_by(&:to_s)}"
else
subject.inspect
end
end
end
def cached?
cache? && Rails.cache.exist?(cache_key)
end
private
def cache?
!self.class.dont_cache?
end
##
# Render this widget or serve it from cache
def render_with_cache(options = {}, &block)
if cached?
write Rails.cache.fetch(cache_key)
else
render(&block)
Rails.cache.write(cache_key, @cache_output || @output) if cache?
end
end
##
# Set the canvas. If the canvas object isn't a string (e.g. cannot be cached easily),
# a @cache_output String is created, that will mirror what is being written to the canvas.
def set_canvas(canvas)
@cache_output = "".html_safe
@output = canvas
end
##
# Appends the Help Widget with this Widget's help text.
# If no help-text was given and no default help-text is set,
# the given default html will be printed instead.
# Params:
# - html
# - options-hash
# - :fallback_html (string, default: '') - the html code to render if no help-text was found
# - :help_text (string) - the help text to render
# - :instant_write (bool, default: true) - wether to write
# the help-widget instantly to the output-buffer.
# If set to false you should care to save the rendered text.
def maybe_with_help(options = {})
options[:instant_write] = true if options[:instant_write].nil?
options[:fallback_html] ||= ''
output = "".html_safe
if text = options[:help_text] || help_text
output += render_widget Widget::Help, text do
options
end
else
output += options[:fallback_html]
end
write output if options[:instant_write]
output.html_safe
end
end

@ -1,7 +0,0 @@
class Widget::Controls < Widget::Base
extend ProactiveAutoloader
def cache_key
"#{super}#{@subject.new_record? ? 1 : 0}"
end
end

@ -1,9 +0,0 @@
class Widget::Controls::Apply < Widget::Controls
def render
write link_to content_tag(:span, content_tag(:em, l(:button_apply))), {},
:href => "#", :id => "query-icon-apply-button",
:class => "button apply reporting_button",
:"data-target" => url_for(:action => 'index', :set_filter => '1')
end
end

@ -1,9 +0,0 @@
class Widget::Controls::Clear < Widget::Controls
def render
html = link_to(content_tag(:span, content_tag(:em, l(:"button_clear"), :class => "button-icon icon-clear")),
'#', :id => 'query-link-clear', :class => 'button secondary')
write html
maybe_with_help
end
end

@ -1,24 +0,0 @@
class Widget::Controls::Delete < Widget::Controls
def render
return "" if @subject.new_record? or !@options[:can_delete]
button = link_to content_tag(:span, content_tag(:em, l(:button_delete), :class => "button-icon icon-delete")), "#",
:class => 'button secondary',
:id => 'query-icon-delete',
:title => l(:button_delete)
popup = content_tag :div, :id => "delete_form", :style => "display:none", :class => "button_form" do
question = content_tag :p, l(:label_really_delete_question)
options = content_tag :p do
delete_button = content_tag :span do
span = content_tag :em do
l(:button_delete)
end
end
opt1 = link_to delete_button, url_for(:action => 'delete', :id => @subject.id), :class => "button apply"
opt2 = link_to l(:button_cancel), "#", :id => "query-icon-delete-cancel", :class => 'icon icon-cancel'
opt1 + opt2
end
question + options
end
write(button + popup)
end
end

@ -1,32 +0,0 @@
class Widget::Controls::QueryName < Widget::Controls
dont_cache! # The name might change, but the query stays the same...
def render
options = { :id => "query_saved_name", "data-translations" => translations }
if @subject.new_record?
name = l(:label_new_report)
icon = ""
else
name = @subject.name
if @options[:can_rename]
icon = content_tag :a, :href => "#", :class => 'breadcrumb_icon icon-edit',
:id => "query-name-edit-button", :title => "#{l(:button_rename)}" do
l(:button_rename)
end
options["data-update-url"] = url_for(:action => "rename", :id => @subject.id)
end
options["data-is_public"] = @subject.is_public
options["data-is_new"] = @subject.new_record?
end
write(content_tag(:span, name, options) + icon)
end
def translations
{ :rename => l(:button_rename),
:cancel => l(:button_cancel),
:loading => l(:label_loading),
:clickToEdit => l(:label_click_to_edit),
:isPublic => l(:field_is_public),
:saving => l(:label_saving) }.to_json
end
end

@ -1,10 +0,0 @@
class Widget::Controls::Save < Widget::Controls
def render
return "" if @subject.new_record? or !@options[:can_save]
write link_to content_tag(:span, content_tag(:em, l(:button_save)), :class => "button-icon icon-save"), {},
:href => "#", :id => "query-breadcrumb-save",
:class => "button secondary",
:title => l(:button_save),
:"data-target" => url_for(:action => 'update', :id => @subject.id, :set_filter => '1')
end
end

@ -1,51 +0,0 @@
class Widget::Controls::SaveAs < Widget::Controls
def render
if @subject.new_record?
link_name = l(:button_save)
icon = "icon-save"
else
link_name = l(:button_save_as)
icon = "icon-save-as"
end
button = link_to content_tag(:span, content_tag(:em, link_name, :class => "button-icon icon-save-as")), "#",
:class => "button secondary",
:id => 'query-icon-save-as', :title => link_name
write(button + render_popup)
maybe_with_help
end
def render_popup_form
name = content_tag :p do
label_tag(:query_name, l(:field_name)) +
text_field_tag(:query_name, @subject.name)
end
if @options[:can_save_as_public]
box = content_tag :p do
label_tag(:query_is_public, l(:field_is_public)) +
check_box_tag(:query_is_public)
end
name + box
else
name
end
end
def render_popup_buttons
content_tag(:p) do
save = link_to content_tag(:span, content_tag(:em, l(:button_save))), "#",
:id => "query-icon-save-button",
:class => "button reporting_button save",
:"data-target" => url_for(:action => 'create', :set_filter => '1')
cancel = link_to l(:button_cancel), "#",
:id => "query-icon-save-as-cancel",
:class => 'icon icon-cancel'
save + cancel
end
end
def render_popup
content_tag :div, :id => 'save_as_form', :class => "button_form", :style => "display:none" do
render_popup_form + render_popup_buttons
end
end
end

@ -1,79 +0,0 @@
class Widget::Filters < Widget::Base
extend ProactiveAutoloader
def render
table = content_tag :table, :width => "100%" do
content_tag :tr do
content_tag :td do
content_tag :table, :id => "filter_table" do
render_filters
end
end
end
end
select = content_tag :div, :id => "add_filter_block" do
add_filter = select_tag 'add_filter_select',
options_for_select([["-- #{l(:label_filter_add)} --",'']] + selectables),
:class => "select-small",
:name => nil
add_filter += maybe_with_help :icon => { :class => 'filter-icon' },
:tooltip => { :class => 'filter-tip' },
:instant_write => false
add_filter.html_safe
end
write content_tag(:div, table + select)
end
def selectables
filters = engine::Filter.all
filters.sort_by do |filter|
l(filter.label)
end.select do |filter|
filter.selectable?
end.collect do |filter|
[ l(filter.label), filter.underscore_name ]
end
end
def render_filters
active_filters = @subject.filters.select { |f| f.class.display? }
engine::Filter.all.collect do |filter|
opts = {:id => "tr_#{filter.underscore_name}",
:class => "#{filter.underscore_name} filter",
:"data-filter-name" => filter.underscore_name }
active_instance = active_filters.detect { |f| f.class == filter }
if active_instance
opts[:"data-selected"] = true
else
opts[:style] = "display:none"
end
content_tag :tr, opts do
render_filter filter, active_instance
end
end.join.html_safe
end
def render_filter(f_cls, f_inst)
f = f_inst || f_cls
html = render_widget Filters::Label, f
render_widget Filters::Operators, f, :to => html
if engine::Operator.string_operators.all? { |o| f_cls.available_operators.include? o }
render_widget Filters::TextBox, f, :to => html
elsif engine::Operator.time_operators.all? { |o| f_cls.available_operators.include? o }
render_widget Filters::Date, f, :to => html
elsif engine::Operator.integer_operators.all? {|o| f_cls.available_operators.include? o }
if f_cls.available_values.empty?
render_widget Filters::TextBox, f, :to => html
else
render_widget Filters::MultiValues, f, :to => html
end
else
if f_cls.is_multiple_choice?
render_widget Filters::MultiChoice, f, :to => html
else
render_widget Filters::MultiValues, f, :to => html
end
end
render_widget Filters::RemoveButton, f, :to => html
end
end

@ -1,14 +0,0 @@
class Widget::Filters::Base < Widget::Base
attr_reader :filter, :filter_class
def initialize(filter)
if filter.class == Class
@filter_class = filter
@filter = filter.new
else
@filter = filter
@filter_class = filter.class
end
@engine = filter.engine
end
end

@ -1,26 +0,0 @@
class Widget::Filters::Date < Widget::Filters::Base
def calendar_for(field_id)
image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
end
def render
name = "values[#{filter_class.underscore_name}][]"
id_prefix = "#{filter_class.underscore_name}_"
write(content_tag :td do
arg1 = content_tag :span, :id => "#{id_prefix}arg_1", :class => "filter_values" do
text1 = text_field_tag name, @filter.values.first.to_s, :size => 10, :class => "select-small", :id => "#{id_prefix}arg_1_val"
cal1 = calendar_for("#{id_prefix}arg_1_val")
text1 + cal1
end
arg2 = content_tag :span, :id => "#{id_prefix}arg_2", :class => "between_tags" do
text2 = text_field_tag "#{name}", @filter.values.second.to_s, :size => 10, :class => "select-small", :id => "#{id_prefix}arg_2_val"
cal2 = calendar_for "#{id_prefix}arg_2_val"
text2 + cal2
end
arg1 + arg2
end)
end
end

@ -1,14 +0,0 @@
class Widget::Filters::Label < Widget::Filters::Base
def render
write(content_tag :td, :width => 150 do
options = { :id => filter_class.underscore_name }
if (engine::Filter.all.any? {|f| f.dependents.include?(filter_class)})
options.merge! :class => 'dependent-filter-label'
end
content_tag :label, options do
l(filter_class.label)
end
end)
end
end

@ -1,38 +0,0 @@
class Widget::Filters::MultiChoice < Widget::Filters::Base
def render
filterName = filter_class.underscore_name
write(content_tag :td do
content_tag :div, :id => "#{filterName}_arg_1", :class => "filter_values" do
choices = filter_class.available_values.each_with_index.map do |(label, value), i|
opts = {
:type => "radio",
:name => "values[#{filterName}][]",
:id => "#{filterName}_radio_option_#{i}",
:value => value
}
opts[:checked] = "checked" if filter.values == value
radio_button = tag :input, opts
content_tag :label, radio_button + translate(label),
:for => "#{filterName}_radio_option_#{i}",
:'data-filter-name' => filter_class.underscore_name,
:class => "#{filterName}_radio_option filter_radio_option"
end
content_tag :div, choices.join.html_safe,
:id => "#{filter_class.underscore_name}_arg_1_val"
end
end)
end
private
def translate(label)
if label.is_a?(Symbol)
::I18n.t(label)
else
label
end
end
end

@ -1,60 +0,0 @@
class Widget::Filters::MultiValues < Widget::Filters::Base
def render
write(content_tag :td do
content_tag :div, :id => "#{filter_class.underscore_name}_arg_1", :class => "filter_values" do
select_options = { :style => "vertical-align: top;", # FIXME: Do CSS
:name => "values[#{filter_class.underscore_name}][]",
:id => "#{filter_class.underscore_name}_arg_1_val",
:class => "select-small filters-select",
:"data-filter-name" => filter_class.underscore_name,
:multiple => "multiple" }
# multiple will be disabled/enabled later by JavaScript anyhow.
# We need to specify multiple here because of an IE6-bug.
if filter_class.has_dependent?
all_dependents = filter_class.all_dependents.map {|d| d.underscore_name}.to_json
select_options.merge! :"data-all-dependents" => all_dependents.gsub!('"', "'")
next_dependents = filter_class.dependents.map {|d| d.underscore_name}.to_json
select_options.merge! :"data-next-dependents" => next_dependents.gsub!('"', "'")
end
# store selected value(s) in data-initially-selected if this filter is a dependent
# of another filter, as we have to restore values manually in the client js
if filter_class.is_dependent? && !Array(filter.values).empty?
select_options.merge! :"data-initially-selected" => filter.values.to_json.gsub!('"', "'")
end
box = content_tag :select, select_options do
first = true
filter_class.available_values.collect do |name, id, *args|
options = args.first || {} # optional configuration for values
level = options[:level] # nesting_level is optional for values
name = l(name) if name.is_a? Symbol
name = name.empty? ? l(:label_none) : name
name_prefix = ((level && level > 0) ? (' ' * 2 * level + '> ') : '')
unless options[:optgroup]
opts = { :value => id }
if (Array(filter.values).map{ |val| val.to_s }.include? id.to_s) || (first && Array(filter.values).empty?)
opts[:selected] = "selected"
end
first = false
# TODO: The following line was escaping some parts of the name. I
# don't exatly know why, but it was causing double escaping bugs
# in a Rails 3 context. Maybe this is needed for Rails 2. I don't
# know. Please review and remove this comment if feasible.
#
# content_tag(:option, opts) { name_prefix + h(name) }
content_tag(:option, opts) { name_prefix + name }
else
tag :optgroup, :label => l(:label_sector)
end
end.join.html_safe
end
plus = image_tag 'bullet_toggle_plus.png',
:class => "filter_multi-select",
:style => "vertical-align: bottom;",
:"data-filter-name" => filter_class.underscore_name
box + plus
end
end)
end
end

@ -1,27 +0,0 @@
class Widget::Filters::Operators < Widget::Filters::Base
def render
write(content_tag :td, :width => 100 do
hide_select_box = filter_class.available_operators.count == 1
options = {:class => "select-small filters-select filter_operator",
:style => "vertical-align: top", # FIXME: put into CSS
:id => "operators[#{filter_class.underscore_name}]",
:name => "operators[#{filter_class.underscore_name}]",
:"data-filter-name" => filter_class.underscore_name }
options.merge! :style => "display: none" if hide_select_box
select_box = content_tag :select, options do
filter_class.available_operators.collect do |o|
opts = {:value => h(o.to_s), :"data-arity" => o.arity}
opts[:selected] = "selected" if filter.operator.to_s == o.to_s
content_tag(:option, opts) { h(l(o.label)) }
end.join.html_safe
end
label = content_tag :label do
if filter_class.available_operators.any?
l(filter_class.available_operators.first.label)
end
end
hide_select_box ? select_box + label : select_box
end)
end
end

@ -1,11 +0,0 @@
class Widget::Filters::RemoveButton < Widget::Filters::Base
def render
write( content_tag :td, :width => "25px" do
hidden_field = tag :input, :id => "rm_#{filter_class.underscore_name}",
:name => "fields[]", :type => "hidden", :value => ""
button = tag :input, :type => "button", :value => "",
:class => "icon filter_rem icon-filter-rem"
content_tag(:div, hidden_field + button, :id => "rm_box_#{filter_class.underscore_name}", :class => "remove-box")
end)
end
end

@ -1,13 +0,0 @@
class Widget::Filters::TextBox < Widget::Filters::Base
def render
write(content_tag :td do
content_tag :div, :id => "#{filter_class.underscore_name}_arg_1", :class => "filter_values" do
text_field_tag("values[#{filter_class.underscore_name}]", "",
:size => "6",
:class => "select-small",
:id => "#{filter_class.underscore_name}_arg_1_val",
:'data-filter-name' => filter_class.underscore_name)
end
end)
end
end

@ -1,65 +0,0 @@
class Widget::GroupBys < Widget::Base
extend ProactiveAutoloader
def render_options(group_by_ary)
group_by_ary.sort_by do |group_by|
l(group_by.label)
end.collect do |group_by|
next unless group_by.selectable?
content_tag :option, :value => group_by.underscore_name, :'data-label' => "#{l(group_by.label)}" do
l(group_by.label)
end
end.join.html_safe
end
def render_group_caption(type)
content_tag :span do
out = content_tag :span, :class => 'in_row group_by_caption' do
l("label_#{type}".to_sym) # :label_rows, :label_columns
end
out += content_tag :span, :class => 'arrow in_row arrow_group_by_caption' do
'' #cannot use tag here as it would generate <span ... /> which leads to wrong interpretation in most browsers
end
out.html_safe
end
end
def render_group(type, initially_selected, show_help = false)
initially_selected = initially_selected.map do |group_by|
[group_by.class.underscore_name, l(group_by.class.label)]
end
content_tag :div,
:id => "group_by_#{type}",
:class => 'drag_target drag_container',
:'data-initially-selected' => initially_selected.to_json.gsub('"', "'") do
out = render_group_caption type
out += content_tag :select, :id => "add_group_by_#{type}", :class => 'select-small' do
content = content_tag :option, :value => '' do
"-- #{l(:label_group_by_add)} --"
end
content += engine::GroupBy.all_grouped.sort_by do |label, group_by_ary|
l(label)
end.collect do |label, group_by_ary|
content_tag :optgroup, :label => l(label) do
render_options group_by_ary
end
end.join.html_safe
content
end
if show_help
out += maybe_with_help :icon => { :class => 'group-by-icon' },
:tooltip => { :class => 'group-by-tip' },
:instant_write => false
end
out
end
end
def render
write(content_tag :div, :id => 'group_by_area' do
out = render_group 'columns', @subject.group_bys(:column), true
out += render_group 'rows', @subject.group_bys(:row)
out.html_safe
end)
end
end

@ -1,57 +0,0 @@
##
# Usage: render_widget Widget::Help, :text
#
# Where :text is a i18n key.
class Widget::Help < Widget::Base
dont_cache!
def render
id = "tip:#{@subject}"
options = {:icon => {}, :tooltip => {}}
options.merge!(yield) if block_given?
sai = options[:show_at_id] ? ", show_at_id: '#{options[:show_at_id]}'" : ""
icon = tag :img, :src => image_path('icon_info_red.gif'), :id => "target:#{@subject}"
tip = content_tag_string :div, l(@subject), tip_config(options[:tooltip]), false
script = content_tag :script,
"new Tooltip('target:#{@subject}', 'tip:#{@subject}', {className: 'tooltip'#{sai}});",
{:type => 'text/javascript'}, false
target = content_tag :a, icon + tip, icon_config(options[:icon])
write(target + script)
end
def icon_config(options)
add_class = lambda do |cl|
if cl
"help #{cl}"
else
"help"
end
end
options.mega_merge! :href => '#', :class => add_class
end
def tip_config(options)
add_class = lambda do |cl|
if cl
"#{cl} tooltip"
else
"tooltip"
end
end
options.mega_merge! :id => "tip:#{@subject}", :class => add_class
end
end
class Hash
def mega_merge!(hash)
hash.each do |key, value|
if value.kind_of?(Proc)
self[key] = value.call(self[key])
else
self[key] = value
end
end
self
end
end

@ -1,48 +0,0 @@
class Widget::Settings < Widget::Base
dont_cache! # Settings may change due to permissions
def render
write(form_tag("#", {:id => 'query_form', :method => :post}) do
content_tag :div, :id => "query_form_content" do
fieldsets = render_widget Widget::Settings::Fieldset, @subject,
{ :type => "filter", :help_text => self.filter_help } do
render_widget Widget::Filters, @subject
end
fieldsets += render_widget Widget::Settings::Fieldset, @subject,
{ :type => "group_by", :help_text => self.group_by_help } do
render_widget Widget::GroupBys, @subject
end
controls = content_tag :div, :class => "buttons form_controls" do
widgets = render_widget(Widget::Controls::Apply, @subject)
render_widget(Widget::Controls::Save, @subject, :to => widgets,
:can_save => allowed_to?(:save, @subject, current_user))
render_widget(Widget::Controls::SaveAs, @subject, :to => widgets,
:can_save_as_public => allowed_to?(:save_as_public, @subject, current_user))
render_widget(Widget::Controls::Clear, @subject, :to => widgets)
render_widget(Widget::Controls::Delete, @subject, :to => widgets,
:can_delete => allowed_to?(:delete, @subject, current_user))
end
fieldsets + controls
end
end)
end
def filter_help
if help_text.kind_of?(Array)
help_text[0]
else
nil
end
end
def group_by_help
if help_text.kind_of?(Array)
help_text[1]
else
nil
end
end
end

@ -1,24 +0,0 @@
class Widget::Settings::Fieldset < Widget::Base
dont_cache!
def render_with_options(options, &block)
@type = options.delete(:type) || "filter"
@id = "#{@type}-settings"
@label = :"label_#{@type}"
super(options, &block)
end
def render
hash = self.hash
write(content_tag :fieldset, :id => @id, :class => "collapsible collapsed" do
html = content_tag :legend,
:show_at_id => hash.to_s,
:icon => "#{@type}-legend-icon",
:tooltip => "#{@type}-legend-tip",
:onclick => "toggleFieldset(this);", :id => hash.to_s do #FIXME: onclick
(l(@label) + maybe_with_help(:instant_write => false)).html_safe
end
html + yield
end)
end
end

@ -1,107 +0,0 @@
class Widget::Table < Widget::Base
extend Report::InheritedAttribute
include ReportingHelper
attr_accessor :debug
attr_accessor :fields
attr_accessor :mapping
def initialize(query)
raise ArgumentError, "Tables only work on Reports!" unless query.is_a? Report
super
end
# def debug?
# !!debug
# end
# def show_result(*args)
# map :show_result, *args
# end
# def show_row(*args)
# map :show_row, *args
# end
# def label_for(*args)
# map :label, *args
# end
# def entry_for(*args)
# map :entry, *args
# end
# def edit_content(*args)
# map :edit, *args
# end
# def debug_fields(*args)
# map :debug_fields, *args
# end
# def raw_result(result)
# mapped = mapping[:raw_result]
# if mapped
# mapped.call self, result
# else
# show_result(result, 0)
# end
# end
# def show_field(field, *args)
# mapped = mapping[:show_field]
# if mapped
# mapped.call self, field, *args
# else
# engine::Chainable.mapping_for(field).first.call field, *args
# end
# end
# def map(to_map, *args)
# fail "Table Widget #{self.class} needs a mapping for :#{to_map}" unless mapping[to_map]
# mapping[to_map.to_sym].call self, *args
# end
# def render_with_options(options = {}, &block)
# @fields ||= (options[:fields] || @subject.result.important_fields)
# @debug ||= (options[:debug] || false)
# @mapping ||= options[:mapping]
# fail "mappings need to respond to #call" if mapping.values.any? { |val| not val.respond_to? :call }
# canvas = options[:to] ? options[:to] << "\n" : ""
# canvas << render(&block)
# end
def fancy_table
if @subject.depth_of(:row) == 0
@subject.row(:singleton_value)
elsif @subject.depth_of(:column) == 0
@subject.column(:singleton_value)
end
Widget::Table::ReportTable
end
def simple_table
Widget::Table::SimpleTable
end
def entry_table
Widget::Table::EntryTable
end
def render
content_tag :div, :id => "result-table" do
options = { :debug => debug?, :mapping => @mapping, :fields => @fields }
return content_tag :p, l(:label_no_data), :class => "nodata" if @subject.result.count <= 0
if @subject.group_bys.empty?
render_widget entry_table, @subject, options
elsif @subject.group_bys.size == 1
render_widget simple_table, @subject, options
else
render_widget fancy_table, @subject, options
end
end
end
end

@ -1,23 +0,0 @@
class Widget::Table::Progressbar < Widget::Base
dont_cache!
def render
if Widget::Table::ReportTable.new(@subject).cached? || @subject.size <= THRESHHOLD
render_widget Widget::Table::ReportTable, @subject, :to => (@output ||= "".html_safe)
else
write(content_tag :label, :style => "display:none" do
content_tag(:div, l(:label_progress_bar_explanation).html_safe) +
render_progress_bar
end)
end
end
def render_progress_bar
content_tag(:div, "",
:id => "progressbar",
:class => "form_controls",
:"data-query-size" => @subject.size,
:"data-translation" => ::I18n.translate(:label_load_query_question, :size => @subject.size),
:"data-target" => url_for(:action => 'index', :set_filter => '1', :immediately => true))
end
end

@ -1,170 +0,0 @@
class Widget::Table::ReportTable < Widget::Table
attr_accessor :walker
def initialize(query)
super
@walker = query.walker
end
def configure_query
if @subject.depth_of(:row) == 0
@subject.row(:singleton_value)
elsif @subject.depth_of(:column) == 0
@subject.column(:singleton_value)
end
end
def configure_walker
@walker.for_final_row do |row, cells|
html = "<th class='normal inner left'>#{show_row row}#{debug_fields(row)}</th>"
html << cells.join
html << "<th class='normal inner right'>#{show_result(row)}#{debug_fields(row)}</th>"
html.html_safe
end
@walker.for_row do |row, subrows|
subrows.flatten!
unless row.fields.empty?
subrows[0] = %Q{
<th class='top left' rowspan='#{subrows.size}'>#{show_row row}#{debug_fields(row)}</th>
#{subrows[0].gsub("class='normal", "class='top")}
<th class='top right' rowspan='#{subrows.size}'>#{show_result(row)}#{debug_fields(row )}</th>
}.html_safe
end
subrows.last.gsub!("class='normal", "class='bottom")
subrows.last.gsub!("class='top", "class='bottom top")
subrows
end
@walker.for_empty_cell { "<td class='normal empty'>&nbsp;</td>".html_safe }
@walker.for_cell do |result|
write(' '.html_safe) # XXX: This keeps the Apache from timing out on us. Keep-Alive byte!
"<td class='normal right'>#{show_result result}#{debug_fields(result)}</td>".html_safe
end
end
def render
configure_query
configure_walker
write "<table class='list report'>"
render_thead
render_tfoot
render_tbody
write "</table>"
render_xls_export
end
def render_tbody
write "<tbody>"
first = true
odd = true
walker.body do |line|
if first
line.gsub!("class='normal", "class='top")
first = false
end
mark_penultimate_column! line
write "<tr class='#{odd ? "odd" : "even"}'>#{line}</tr>"
odd = !odd
end
write "</tbody>"
end
def mark_penultimate_column!(line)
line.gsub! /(<td class='([^']+)'[^<]+<\/td>)[^<]*<th .+/ do |m|
m.sub /class='([^']+)'/, 'class=\'\1 penultimate\''
end
end
def render_thead
write "<thead>"
walker.headers do |list, first, first_in_col, last_in_col|
write '<tr>' if first_in_col
if first
write (content_tag :th, :rowspan => @subject.depth_of(:column), :colspan => @subject.depth_of(:row) do
""
end)
end
list.each do |column|
opts = { :colspan => column.final_number(:column) }
opts.merge!(:class => "inner") if column.final?(:column)
write (content_tag :th, opts do
show_row column
end)
end
if first
write (content_tag :th, :rowspan => @subject.depth_of(:column), :colspan => @subject.depth_of(:row) do
""
end)
end
write '</tr>' if last_in_col
end
write "</thead>"
end
def render_tfoot
write "<tfoot>"
walker.reverse_headers do |list, first, first_in_col, last_in_col|
if first_in_col
write '<tr>'
if first
write (content_tag :th, :rowspan => @subject.depth_of(:column), :colspan => @subject.depth_of(:row), :class => 'top' do
" "
end)
end
end
list.each do |column|
opts = { :colspan => column.final_number(:column) }
opts.merge!(:class => "inner") if first
write (content_tag :th, opts do
"#{show_result(column)}" #{debug_fields(column)}
end)
end
if last_in_col
if first
write (content_tag :th,
:rowspan => @subject.depth_of(:column),
:colspan => @subject.depth_of(:row),
:class => 'top result' do
show_result @subject
end)
end
write '</tr>'
end
end
write "</tfoot>"
end
def render_xls_export
write (content_tag :div, :id => "result-formats", :style => "font-size: 14px; line-height: 2;" do
link_to l(:export_as_excel), :action => :index, :format => "xls"
end)
end
def debug_content
content_tag :pre do
debug_pre_content = "[ Query ]" +
@subject.chain.each do |child|
"#{h child.class.inspect}, #{h child.type}"
end
debug_pre_content += "[ RESULT ]"
@subject.result.recursive_each_with_level do |level, result|
debug_pre_content += ">>> " * (level+1)
debug_pre_content += h(result.inspect)
debug_pre_content += " " * (level+1)
debug_pre_content += h(result.type.inspect)
debug_pre_content += " " * (level+1)
debug_pre_content += h(result.fields.inspect)
end
debug_pre_content += "[ HEADER STACK ]"
debug_pre_content += walker.header_stack.each do |l|
">>> #{l.inspect}"
end
end
end
end
Loading…
Cancel
Save