This reverts commitpull/6827/headc706b9d8b3
, reversing changes made to30fc64e4fb
.
After Width: | Height: | Size: 51 B |
After Width: | Height: | Size: 50 B |
After Width: | Height: | Size: 51 B |
After Width: | Height: | Size: 49 B |
After Width: | Height: | Size: 51 B |
After Width: | Height: | Size: 53 B |
After Width: | Height: | Size: 53 B |
After Width: | Height: | Size: 52 B |
After Width: | Height: | Size: 937 B |
After Width: | Height: | Size: 937 B |
After Width: | Height: | Size: 937 B |
Before Width: | Height: | Size: 69 B |
Before Width: | Height: | Size: 67 B |
Before Width: | Height: | Size: 64 B |
Before Width: | Height: | Size: 67 B |
Before Width: | Height: | Size: 67 B |
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,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 |
@ -1,11 +0,0 @@ |
||||
class Report::Filter |
||||
class MultiChoice < Base |
||||
|
||||
dont_inherit :available_operators |
||||
use '=' |
||||
|
||||
def self.is_multiple_choice? |
||||
true |
||||
end |
||||
end |
||||
end |
@ -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'> </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 |
||||
|