Before Width: | Height: | Size: 139 B After Width: | Height: | Size: 139 B |
Before Width: | Height: | Size: 67 B After Width: | Height: | Size: 67 B |
Before Width: | Height: | Size: 1022 B After Width: | Height: | Size: 1022 B |
Before Width: | Height: | Size: 64 B After Width: | Height: | Size: 64 B |
Before Width: | Height: | Size: 67 B After Width: | Height: | Size: 67 B |
Before Width: | Height: | Size: 67 B After Width: | Height: | Size: 67 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 54 B After Width: | Height: | Size: 54 B |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 139 B |
After Width: | Height: | Size: 67 B |
After Width: | Height: | Size: 1022 B |
After Width: | Height: | Size: 64 B |
After Width: | Height: | Size: 67 B |
After Width: | Height: | Size: 67 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 54 B |
After Width: | Height: | Size: 3.7 KiB |
@ -0,0 +1,159 @@ |
||||
/* |
||||
* 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(); |
||||
} |
||||
}) |
@ -0,0 +1,72 @@ |
||||
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */ |
||||
/*global window, $, $$, Reporting, Element */ |
||||
|
||||
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 = new Element('div', { |
||||
'id': 'flash_' + type, |
||||
'class': 'flash ' + type |
||||
}).update(new Element('a', { |
||||
'href': '#' |
||||
}).update(string)); |
||||
|
||||
$("content").insert({top: flash}); |
||||
$$("#flash_" + type + " a")[0].focus(); |
||||
}, |
||||
|
||||
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); |
||||
} else { |
||||
// dispatch for firefox + others
|
||||
evt = document.createEvent("HTMLEvents"); |
||||
evt.initEvent(event, true, true); // event type,bubbling,cancelable
|
||||
return !element.dispatchEvent(evt); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
Reporting.require("filters"); |
||||
Reporting.require("group_bys"); |
||||
Reporting.require("restore_query"); |
||||
Reporting.require("controls"); |
||||
Reporting.require("prototype_progress_bar"); |
||||
Reporting.require("progressbar"); |
||||
|
@ -0,0 +1,192 @@ |
||||
/*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); |
||||
}, |
||||
onComplete: function () { |
||||
Reporting.Controls.update_report_lists(); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
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(Reporting._LA["RESPONSE_ERROR"]); |
||||
Reporting.Progress.abort(); |
||||
} |
||||
}, |
||||
|
||||
update_report_lists: function () { |
||||
$$(".report_list").each(function (list) { |
||||
Reporting.Controls.update_report_list(list); |
||||
}); |
||||
}, |
||||
|
||||
update_report_list: function (list) { |
||||
var url = $(list).readAttribute("data-update-url"); |
||||
if (url == null) { |
||||
return; |
||||
} |
||||
new Ajax.Request(url, { |
||||
onSuccess: function (response) { |
||||
list.replace(response.responseText); |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
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); |
||||
}); |
||||
|
||||
|
@ -0,0 +1,518 @@ |
||||
/*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, post_select_values; |
||||
select = $$('.filter-value[data-filter-name="' + filter_name + '"]').first(); |
||||
// 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; |
||||
callback_func(); |
||||
} |
||||
if (select === null || select === undefined) { |
||||
return; |
||||
} |
||||
url = select.readAttribute("data-remote-url"); |
||||
json_post_select_values = select.readAttribute('data-initially-selected') |
||||
if (json_post_select_values !== null && json_post_select_values !== undefined) { |
||||
post_select_values = json_post_select_values.replace(/'/g, '"').evalJSON(true); |
||||
} |
||||
if (select.readAttribute('data-loading') === "ajax" && select.childElements().length === 0) { |
||||
if (window.global_prefix === undefined) { |
||||
window.global_prefix = ""; |
||||
} |
||||
|
||||
new Ajax.Updater({ success: select.id }, url, { |
||||
parameters: { |
||||
filter_name: filter_name, |
||||
values: json_post_select_values |
||||
}, |
||||
insertion: 'bottom', |
||||
evalScripts: false, |
||||
onCreate: function (a, b) { |
||||
$$("select[data-filter-name='" + filter_name + "']").each(function (e) { e.disable(); }); |
||||
}, |
||||
onComplete: function (a, b) { |
||||
$$("select[data-filter-name='" + filter_name + "']").each(function (e) { e.enable(); }); |
||||
if (select.tagName.toLowerCase() === "select") { |
||||
if (post_select_values === undefined || post_select_values === null || post_select_values.size() === 0) { |
||||
select.selectedIndex = 0; |
||||
} else { |
||||
Reporting.Filters.select_values(select, post_select_values); |
||||
} |
||||
} |
||||
callback_func(); |
||||
} |
||||
}); |
||||
Reporting.Filters.multi_select(select, false); |
||||
} else { |
||||
callback_func(); |
||||
} |
||||
}, |
||||
|
||||
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; |
||||
} |
||||
if (options.hide_only === undefined) { |
||||
options.hide_only = false; |
||||
} |
||||
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
|
||||
if (field_el.id !== options.insert_after.id) { |
||||
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); |
||||
if (!options.hide_only) { // remember that this filter used to be selected
|
||||
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"))); |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Activates the filter with the given name and loads dependent filters if necessary. |
||||
* |
||||
* @param filter_name Name of the filter to be activated. |
||||
*/ |
||||
add_filter: function (filter_name, activate_dependent, on_complete) { |
||||
var field = filter_name |
||||
if (activate_dependent === undefined) { |
||||
activate_dependent = true; |
||||
} |
||||
if (on_complete === undefined) { |
||||
on_complete = function() { }; |
||||
} |
||||
// do this immediately instead of in callback to avoid concurrency issues during testing
|
||||
Reporting.Filters.select_option_enabled($("add_filter_select"), filter_name, false); |
||||
Reporting.Filters.show_filter(field, { slowly: true, callback_func: function() { |
||||
if (activate_dependent) { |
||||
Reporting.Filters.activate_dependents($(field + "_arg_1_val")); |
||||
} |
||||
on_complete(); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
remove_filter: function (field, hide_only) { |
||||
Reporting.Filters.show_filter(field, { show_filter: false, hide_only: hide_only }); |
||||
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); |
||||
}, |
||||
|
||||
/* |
||||
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, first; |
||||
if (select === null) { |
||||
return; |
||||
} |
||||
first = false |
||||
if (select.getAttribute("data-first") === undefined || select.getAttribute("data-first") === null) { |
||||
first = true; |
||||
$(select).setAttribute("data-first", "false"); |
||||
} |
||||
option_tag = select.options[select.selectedIndex]; |
||||
arity = parseInt(option_tag.getAttribute("data-arity"), 10); |
||||
Reporting.Filters.change_argument_visibility(field, arity); |
||||
if (option_tag.getAttribute("data-forced") !== undefined && option_tag.getAttribute("data-forced") !== null) { |
||||
Reporting.Filters.force_type(option_tag, first); |
||||
}; |
||||
}, |
||||
|
||||
// Overwrite to customize input enforcements.
|
||||
// option: 'option' HTMLElement
|
||||
// first: Boolean indicating whether the operator changed for the first time
|
||||
force_type: function (option, first) { |
||||
true; |
||||
}, |
||||
|
||||
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(); |
||||
params[i].descendants().each(function (desc) { desc.show(); }); |
||||
} else { |
||||
params[i].hide(); |
||||
params[i].descendants().each(function (desc) { desc.hide(); }); |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
|
||||
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 if it's present
|
||||
if (select.options[0] !== undefined && select.options[0] !== null) { |
||||
select.options[0].selected = false; |
||||
} |
||||
} else { |
||||
select.size = 1; |
||||
} |
||||
}, |
||||
|
||||
toggle_multi_select: function (select) { |
||||
Reporting.Filters.multi_select(select, !select.multiple); |
||||
}, |
||||
|
||||
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 && selectBox.type.toLowerCase() == 'change')) { |
||||
selectBox = this; |
||||
} |
||||
if (selectBox.tagName.toLowerCase() !== "select") { |
||||
return; // only multi_value filters have dependents
|
||||
} |
||||
if (callbackWhenFinished === undefined) { |
||||
callbackWhenFinished = function(dependent) { }; |
||||
} |
||||
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); |
||||
if (!dependent) { |
||||
return; |
||||
} |
||||
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, |
||||
function() { callbackWhenFinished(dependent); } |
||||
); |
||||
}, 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) { |
||||
if (console) { |
||||
console.log(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.value); |
||||
this.selectedIndex = 0; |
||||
}; |
||||
}); |
||||
} |
||||
|
||||
$$(".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); |
||||
}); |
||||
}); |
||||
|
@ -0,0 +1,208 @@ |
||||
/*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 remove_link, remove_icon; |
||||
remove_link = new Element('a', { |
||||
'class': 'group_by_remove in_row', |
||||
'id': group_by.identify() + '_remove', |
||||
'href': 'javascript:' |
||||
}); |
||||
remove_icon = $('hidden_remove_img').clone(); |
||||
remove_icon.removeAttribute('id'); |
||||
remove_icon.removeAttribute('style'); |
||||
|
||||
if (Reporting._LA != undefined) { |
||||
remove_link.setAttribute('title', Reporting._LA["REMOVE"] + ' ' + group_by.down('label').innerHTML); |
||||
remove_icon.setAttribute('alt', Reporting._LA["REMOVE"] + ' ' + group_by.down('label').innerHTML); |
||||
} |
||||
remove_link.observe('click', function(e) { |
||||
Reporting.GroupBys.remove_element_event_action(e, group_by, remove_link) |
||||
}); |
||||
remove_link.observe('keypress', function(e) { |
||||
/* keyCode 32: Space */ |
||||
if (e.keyCode == 32) { |
||||
e.preventDefault(); |
||||
Reporting.GroupBys.remove_element_event_action(e, group_by, remove_link) |
||||
} |
||||
}); |
||||
remove_link.appendChild(remove_icon); |
||||
return remove_link; |
||||
}, |
||||
|
||||
remove_element_event_action: function(event, group_by, button) { |
||||
var node; |
||||
if (node = group_by.next('span')) { |
||||
node = node.down('a'); |
||||
if (node) { |
||||
node.focus(); |
||||
} |
||||
} |
||||
else if (node = group_by.next('select')) { |
||||
node.focus(); |
||||
} |
||||
Reporting.GroupBys.remove_group_by(button.up('.group_by_element')); |
||||
}, |
||||
|
||||
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); |
||||
}; |
||||
}); |
||||
} |
||||
}); |
||||
}); |
@ -0,0 +1,3 @@ |
||||
Reporting._LA = {}; |
||||
Reporting._LA["REMOVE"] = "Lösche"; |
||||
Reporting._LA["RESPONSE_ERROR"] = "Bei der Bearbeitung der Anfrage ist ein Fehler aufgetreten."; |
@ -0,0 +1,3 @@ |
||||
Reporting._LA = {}; |
||||
Reporting._LA["REMOVE"] = "Delete"; |
||||
Reporting._LA["RESPONSE_ERROR"] = "There was an error handling the query."; |
@ -0,0 +1,49 @@ |
||||
/*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(); |
||||
}); |
@ -0,0 +1,116 @@ |
||||
/** |
||||
* @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."; |
||||
} |
||||
if (typeof(Control) === "undefined") { |
||||
if (window.console && window.console.log) { |
||||
window.console.log("Control.ProgressBar requires Prototype ~= 1.7"); |
||||
} |
||||
} else { |
||||
|
||||
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); |
||||
} |
@ -0,0 +1,90 @@ |
||||
/*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; |
||||
} |
||||
} |
||||
}, |
||||
|
||||
restore_dependent_filters: function(filter_name) { |
||||
$$("tr.filter[data-filter-name=" + filter_name + "] select.filter-value").each(function(selectBox) { |
||||
var activateNext = function(dependent) { |
||||
if (!dependent) return; |
||||
Reporting.RestoreQuery.restore_dependent_filters(dependent); |
||||
}; |
||||
if (selectBox.hasAttribute('data-initially-selected')) { |
||||
var selected_values = selectBox.readAttribute('data-initially-selected').replace(/'/g, '"').evalJSON(true); |
||||
Reporting.Filters.select_values(selectBox, selected_values); |
||||
Reporting.Filters.value_changed(filter_name); |
||||
} |
||||
if (selectBox.getValue() !== '<<inactive>>') { |
||||
Reporting.Filters.activate_dependents(selectBox, activateNext); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
restore_filters: function () { |
||||
var deps = $$('.filters-select.filter-value').each(function(select) { |
||||
var tr = select.up('tr'); |
||||
if (tr.visible()) { |
||||
var filter = tr.readAttribute('data-filter-name'); |
||||
var dependent = select.readAttribute('data-dependent'); |
||||
if (filter && dependent) { |
||||
Reporting.Filters.remove_filter(filter, false); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
$$("tr[data-selected=true]").each(function (e) { |
||||
var select = e.down(".filter_values select"); |
||||
if (select && select.hasAttribute("data-dependent")) return; |
||||
var filter_name = e.getAttribute("data-filter-name"); |
||||
var on_complete = function() { |
||||
Reporting.RestoreQuery.restore_dependent_filters(filter_name); |
||||
}; |
||||
Reporting.Filters.add_filter(filter_name, false, on_complete); |
||||
}); |
||||
}, |
||||
|
||||
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,306 @@ |
||||
/* |
||||
Table sorting script by Joost de Valk, check it out at http://www.joostdevalk.nl/code/sortable-table/.
|
||||
Based on a script from http://www.kryogenix.org/code/browser/sorttable/.
|
||||
Distributed under the MIT license: http://www.kryogenix.org/code/browser/licence.html .
|
||||
|
||||
Copyright (c) 1997-2007 Stuart Langridge, Joost de Valk. |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
||||
|
||||
Version 1.5.7 |
||||
*/ |
||||
|
||||
/* You can change these values */ |
||||
var europeandate = true; |
||||
var alternate_row_colors = true; |
||||
|
||||
/* Don't change anything below this unless you know what you're doing */ |
||||
// addEvent(window, "load", sortables_init);
|
||||
|
||||
var SORT_COLUMN_INDEX; |
||||
var thead = false; |
||||
|
||||
function alternate(table) { |
||||
// Take object table and get all it's tbodies.
|
||||
var i, j, tableBodies, tableRows; |
||||
tableBodies = table.getElementsByTagName("tbody"); |
||||
// Loop through these tbodies
|
||||
for (i = 0; i < tableBodies.length; i += 1) { |
||||
// Take the tbody, and get all it's rows
|
||||
tableRows = tableBodies[i].getElementsByTagName("tr"); |
||||
// Loop through these rows
|
||||
// Start at 1 because we want to leave the heading row untouched
|
||||
for (j = 0; j < tableRows.length; j += 1) { |
||||
// Check if j is even, and apply classes for both possible results
|
||||
if ((j % 2) === 0) { |
||||
if (tableRows[j].className.indexOf('odd') !== -1) { |
||||
tableRows[j].className = tableRows[j].className.replace('odd', 'even'); |
||||
} else { |
||||
if (tableRows[j].className.indexOf('even') === -1) { |
||||
tableRows[j].className += " even"; |
||||
} |
||||
} |
||||
} else { |
||||
if (tableRows[j].className.indexOf('even') !== -1) { |
||||
tableRows[j].className = tableRows[j].className.replace('even', 'odd'); |
||||
} else { |
||||
if (tableRows[j].className.indexOf('odd') === -1) { |
||||
tableRows[j].className += " odd"; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
function ts_getInnerText(el) { |
||||
if (typeof el === "string") { |
||||
return el; |
||||
} |
||||
if (typeof el === "undefined") { |
||||
return el; |
||||
} |
||||
if (el.innerText) { |
||||
return el.innerText; |
||||
} //Not needed but it is faster
|
||||
var str, cs, l, i; |
||||
str = ""; |
||||
|
||||
cs = el.childNodes; |
||||
l = cs.length; |
||||
for (i = 0; i < l; i += 1) { |
||||
switch (cs[i].nodeType) { |
||||
case 1: //ELEMENT_NODE
|
||||
str += ts_getInnerText(cs[i]); |
||||
break; |
||||
case 3: //TEXT_NODE
|
||||
str += cs[i].nodeValue; |
||||
break; |
||||
} |
||||
} |
||||
return str; |
||||
} |
||||
|
||||
function ts_makeSortable(t) { |
||||
var firstRow, i, cell, txt; |
||||
if (t.rows && t.rows.length > 0) { |
||||
if (t.tHead && t.tHead.rows.length > 0) { |
||||
firstRow = t.tHead.rows[t.tHead.rows.length - 1]; |
||||
thead = true; |
||||
} else { |
||||
firstRow = t.rows[0]; |
||||
} |
||||
} |
||||
if (!firstRow) { |
||||
return; |
||||
} |
||||
|
||||
// We have a first row: assume it's the header, and make its contents clickable links
|
||||
for (i = 0; i < firstRow.cells.length; i += 1) { |
||||
cell = firstRow.cells[i]; |
||||
txt = ts_getInnerText(cell); |
||||
if (cell.className !== "unsortable" && cell.className.indexOf("unsortable") === -1) { |
||||
cell.innerHTML = '<a href="#" class="sortheader sort" onclick="ts_resortTable(this, ' + |
||||
i + |
||||
');return false;">' + |
||||
txt + |
||||
'</a>'; |
||||
} |
||||
} |
||||
if (alternate_row_colors) { |
||||
alternate(t); |
||||
} |
||||
} |
||||
|
||||
function sortables_init() { |
||||
// Find table with id sortable-table and make them sortable
|
||||
if (!document.getElementById) { |
||||
return; |
||||
} |
||||
var tbl = document.getElementById("sortable-table"); |
||||
ts_makeSortable(tbl); |
||||
} |
||||
|
||||
function getParent(el, pTagName) { |
||||
if (el === null) { |
||||
return null; |
||||
} else if (el.nodeType === 1 && el.tagName.toLowerCase() === pTagName.toLowerCase()) { |
||||
return el; |
||||
} else { |
||||
return getParent(el.parentNode, pTagName); |
||||
} |
||||
} |
||||
|
||||
function ts_get_cell_data(a, idx) { |
||||
var acell, aa; |
||||
if ((typeof idx) === "undefined") { |
||||
acell = a.cells[SORT_COLUMN_INDEX]; |
||||
} else { |
||||
acell = a.cells[idx]; |
||||
} |
||||
if ((aa = acell.getAttribute("raw-data")) === null) { |
||||
aa = ts_getInnerText(acell).toLowerCase(); |
||||
} |
||||
return aa; |
||||
} |
||||
|
||||
function compare_numeric(a, b) { |
||||
var af, bf; |
||||
af = parseFloat(a); |
||||
af = (isNaN(af) ? 0 : af); |
||||
bf = parseFloat(b); |
||||
bf = (isNaN(bf) ? 0 : bf); |
||||
return af - bf; |
||||
} |
||||
|
||||
function ts_sort_numeric(a, b) { |
||||
var cells = [ts_get_cell_data(a), ts_get_cell_data(b)]; |
||||
return compare_numeric(cells[0], cells[1]); |
||||
} |
||||
|
||||
function ts_sort_caseinsensitive(a, b) { |
||||
var cells = [ts_get_cell_data(a), ts_get_cell_data(b)]; |
||||
if (cells[0] === cells[1]) { |
||||
return 0; |
||||
} |
||||
if (cells[0] < cells[1]) { |
||||
return -1; |
||||
} |
||||
return 1; |
||||
} |
||||
|
||||
function trim(s) { |
||||
return s.replace(/^\s+|\s+$/g, ""); |
||||
} |
||||
|
||||
function ts_resortTable(lnk, clid) { |
||||
var td, column, t, first, itm, i, j, k, numeric_flag, all_sort_links, ci, firstRow, newRows, sortfn; |
||||
td = lnk.parentNode; |
||||
column = clid || td.cellIndex; |
||||
t = getParent(td, 'TABLE'); |
||||
|
||||
// Do not sort single a row
|
||||
if (t.rows.length <= 1) { |
||||
return; |
||||
} |
||||
|
||||
// Determine if all rows are equal
|
||||
first = ts_get_cell_data(t.tBodies[0].rows[0], 0); |
||||
itm = first; |
||||
i = 0; |
||||
while (itm === first && i < t.tBodies[0].rows.length) { |
||||
itm = ts_get_cell_data(t.tBodies[0].rows[i], column); |
||||
itm = trim(itm); |
||||
if (itm.substr(0, 4) === "<!--" || itm.length === 0) { |
||||
itm = ""; |
||||
} |
||||
i += 1; |
||||
} |
||||
if (itm === first) { |
||||
return; |
||||
} |
||||
|
||||
// Determine the sort type. You can set numeric=true on the header to force numeric sorting
|
||||
sortfn = ts_sort_caseinsensitive; |
||||
if (thead) { |
||||
if (itm.match(/-?\d+(?:\.\d+)?/)) { |
||||
sortfn = ts_sort_numeric; // Normal number
|
||||
} |
||||
numeric_flag = t.tHead.rows[0].cells[column].getAttribute("numeric"); |
||||
if (numeric_flag === "true") { |
||||
sortfn = ts_sort_numeric; |
||||
} |
||||
} |
||||
|
||||
// Delete any other arrows there may be showing
|
||||
all_sort_links = $$("a.sortheader.sort"); |
||||
for (ci = 0; ci < all_sort_links.length; ci += 1) { |
||||
if (getParent(all_sort_links[ci], "table") === getParent(lnk, "table")) { // in the same table as us?
|
||||
all_sort_links[ci].className = all_sort_links[ci].className.replace(" desc", "").replace(" asc", ""); |
||||
} |
||||
} |
||||
|
||||
// Do the sorting
|
||||
SORT_COLUMN_INDEX = column; |
||||
firstRow = []; |
||||
newRows = []; |
||||
for (k = 0; k < t.tBodies.length; k += 1) { |
||||
for (i = 0; i < t.tBodies[k].rows[0].length; i += 1) { |
||||
firstRow[i] = t.tBodies[k].rows[0][i]; |
||||
} |
||||
} |
||||
for (k = 0; k < t.tBodies.length; k += 1) { |
||||
if (!thead) { |
||||
// Skip the first row
|
||||
for (j = 1; j < t.tBodies[k].rows.length; j += 1) { |
||||
newRows[j - 1] = t.tBodies[k].rows[j]; |
||||
} |
||||
} else { |
||||
// Do NOT skip the first row
|
||||
for (j = 0; j < t.tBodies[k].rows.length; j += 1) { |
||||
newRows[j] = t.tBodies[k].rows[j]; |
||||
} |
||||
} |
||||
} |
||||
newRows.sort(sortfn); |
||||
if (lnk.getAttribute("sortdir") === 'down') { |
||||
lnk.setAttribute('sortdir', 'up'); |
||||
lnk.className += " asc"; |
||||
} else { |
||||
newRows.reverse(); |
||||
lnk.setAttribute('sortdir', 'down'); |
||||
lnk.className += " desc"; |
||||
} |
||||
// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
|
||||
// don't do sortbottom rows
|
||||
for (i = 0; i < newRows.length; i += 1) { |
||||
if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') === -1))) { |
||||
t.tBodies[0].appendChild(newRows[i]); |
||||
} |
||||
} |
||||
// do sortbottom rows only
|
||||
for (i = 0; i < newRows.length; i += 1) { |
||||
if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') !== -1)) { |
||||
t.tBodies[0].appendChild(newRows[i]); |
||||
} |
||||
} |
||||
alternate(t); |
||||
} |
||||
|
||||
function addEvent(elm, evType, fn, useCapture) |
||||
// addEvent and removeEvent
|
||||
// cross-browser event handling for IE5+, NS6 and Mozilla
|
||||
// By Scott Andrew
|
||||
{ |
||||
if (elm.addEventListener) { |
||||
elm.addEventListener(evType, fn, useCapture); |
||||
return true; |
||||
} else if (elm.attachEvent) { |
||||
var r = elm.attachEvent("on" + evType, fn); |
||||
return r; |
||||
} else { |
||||
alert("Handler could not be removed"); |
||||
} |
||||
} |
||||
|
||||
function clean_num(str) { |
||||
str = str.replace(/^[^\-?\d]+/, ""); |
||||
str.replace(/(\d),(\d)/g, "$1$2"); |
||||
return str; |
||||
} |
@ -0,0 +1,56 @@ |
||||
.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; |
||||
} |
@ -0,0 +1,619 @@ |
||||
.cost_types { |
||||
padding-bottom: 3px; |
||||
} |
||||
|
||||
.cost_types a.active { |
||||
color: #000; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.report { |
||||
text-align: center; |
||||
border-collapse: collapse; |
||||
border: solid 1px #ccc !important; |
||||
width: auto !important; |
||||
} |
||||
|
||||
.report td, .report th { |
||||
min-width: 90px; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.report td { |
||||
border: dotted 1px #ddd; |
||||
color: #666; |
||||
text-align: right; |
||||
padding-right: 5px; |
||||
} |
||||
|
||||
.report tbody th, .report tbody td, .inner { |
||||
max-width: 300px; |
||||
white-space: normal !important; |
||||
} |
||||
|
||||
.report td:hover { |
||||
color: #000; |
||||
outline: #ccc 1px solid; |
||||
outline-offset: 1px; |
||||
} |
||||
|
||||
.report td.empty:hover { |
||||
outline: none; |
||||
} |
||||
|
||||
.report th { |
||||
border: solid 1px #ccc; |
||||
background-color: #e3e3e3; |
||||
text-align: center; |
||||
} |
||||
|
||||
.report .odd th.inner { |
||||
background-color: #e8e8e8; |
||||
} |
||||
|
||||
.report .even th.inner { |
||||
background-color: #e3e3e3; |
||||
} |
||||
|
||||
.report th.inner { |
||||
border: solid 1px #ccc; |
||||
background-color: #efefef; |
||||
text-align: right; |
||||
} |
||||
|
||||
.report tr.even:hover .inner, |
||||
.report tr.even:hover .bottom, |
||||
.report tr.even:hover .empty, |
||||
.report tr.even:hover .right { |
||||
background-color: #f5f5c5 !important; |
||||
} |
||||
/* IE7 made me do it! */ |
||||
.report tr.odd:hover .inner, |
||||
.report tr.odd:hover .bottom, |
||||
.report tr.odd:hover .empty, |
||||
.report tr.odd:hover .right { |
||||
background-color: #f5f5c5 !important; |
||||
} |
||||
|
||||
.report .top { |
||||
border-top-style: solid; |
||||
border-top-color: #ccc; |
||||
/* border-top: 2px solid #ccc !important; */ |
||||
} |
||||
|
||||
.report .bottom { |
||||
border-bottom-style: solid; |
||||
border-bottom-color: #ccc; |
||||
/* border-bottom: 2px solid #ccc !important; */ |
||||
} |
||||
|
||||
.report td.penultimate { |
||||
border-right-style: solid; |
||||
} |
||||
|
||||
.report thead .inner, .report tfoot .inner { |
||||
text-align: right; |
||||
padding-right: 5px; |
||||
} |
||||
|
||||
.report .result { |
||||
font-size: 120%; |
||||
text-align: right; |
||||
} |
||||
|
||||
#result-table { |
||||
margin-top: 10px !important; |
||||
} |
||||
|
||||
.report thead tr:hover .inner, .report tfoot tr:hover .inner { |
||||
background-color: #efefef; |
||||
} |
||||
|
||||
.report .left { |
||||
text-align: left !important; |
||||
padding-left: 5px; |
||||
} |
||||
|
||||
.report .right { |
||||
text-align: right !important; |
||||
padding-right: 5px; |
||||
} |
||||
|
||||
/* Details view*/ |
||||
|
||||
.detail-report td { |
||||
text-align: left; |
||||
vertical-align: top; |
||||
} |
||||
|
||||
#query_form_content { |
||||
width: 916px; |
||||
} |
||||
|
||||
#query_form fieldset.header_collapsible.collapsible { |
||||
padding-bottom: 10px; |
||||
} |
||||
|
||||
/* Overwriting styling for headlines within the query. */ |
||||
/* TODO: Font-size seems to be a bit odd. Needs some love. */ |
||||
.new_report fieldset h3 { |
||||
font-size: 1.17em; |
||||
border: none; |
||||
} |
||||
|
||||
.remove-box { |
||||
height: 20px; |
||||
width: 20px; |
||||
background: white; |
||||
float: right; |
||||
padding: 1px 0 0 0; |
||||
display: block; |
||||
margin-top: -5px; |
||||
margin-right: -2px; |
||||
} |
||||
|
||||
.filter_rem { |
||||
color: transparent; |
||||
overflow: hidden; |
||||
cursor: pointer; |
||||
background: no-repeat center center transparent; |
||||
height: 18px; |
||||
width: 18px; |
||||
border-style: none; |
||||
display: block; |
||||
margin: 0 0 0 2px; |
||||
} |
||||
|
||||
.icon-filter-rem { |
||||
background-image: url(../images/close.gif); |
||||
position: relative; |
||||
float: right; |
||||
padding: 0px; |
||||
} |
||||
|
||||
/***** icons *****/ |
||||
/* |
||||
We hide the original content of things with class 'icon' and disply an icon instead. |
||||
This css class is optimized for usage report breadcrumbs. |
||||
Hint: The content of the title attribute is shown as a tooltip if the underlying DOM element is an <a>. |
||||
*/ |
||||
.breadcrumb_icon { |
||||
padding: 3px 5px 0 16px; |
||||
cursor: pointer; |
||||
background: no-repeat left center; |
||||
border-style: none; |
||||
display: inline-block; |
||||
margin-top: 0px; |
||||
margin-bottom: 0px; |
||||
} |
||||
|
||||
.icon-edit { |
||||
background-image: url(../images/edit.png); |
||||
} |
||||
.icon-delete { |
||||
background-image: url(../images/delete.png); |
||||
} |
||||
|
||||
#query-name-edit-button { |
||||
margin-left: 16px; |
||||
font-size: 12px; |
||||
} |
||||
|
||||
.filter { |
||||
-webkit-border-radius: 5px; |
||||
-moz-border-radius: 5px; |
||||
border-radius: 5px; |
||||
} |
||||
|
||||
.inactive-filter { |
||||
background-color: #FCE29A !important; |
||||
} |
||||
|
||||
.dependent-filter-label { |
||||
margin-left: 20px; |
||||
} |
||||
|
||||
.filter_values { |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.filter_radio_option { |
||||
padding-left: 5px; |
||||
padding-right: 5px; |
||||
} |
||||
|
||||
#add_filter_block { |
||||
margin-top: 6px; |
||||
} |
||||
|
||||
#add_filter_select { |
||||
margin-bottom: 10px; |
||||
} |
||||
|
||||
fieldset#filters table tr.filter:hover { |
||||
background: #aaa; |
||||
} |
||||
|
||||
fieldset#filters table tr.filter { |
||||
color: #000; |
||||
background: #ededed; |
||||
-webkit-border-radius: 5px; |
||||
-moz-border-radius: 5px; |
||||
border-radius: 5px; |
||||
} |
||||
|
||||
/* Aligning filter elements at the top. */ |
||||
fieldset#filters table td { |
||||
vertical-align: top; |
||||
border-spacing: 5px 5px; |
||||
border-color: white; |
||||
border-style: solid; |
||||
border-width: 2px 0px 0px; |
||||
padding: 5px 2px 5px 2px; |
||||
} |
||||
|
||||
fieldset#filters table .no-border td { |
||||
border-style: none !important; |
||||
} |
||||
|
||||
fieldset#filters table td > label { |
||||
left: 3px; |
||||
top: 3px; |
||||
position: relative; |
||||
} |
||||
|
||||
/* ----- group by --- */ |
||||
#group_by_area { |
||||
margin: 5px 0 10px 0; |
||||
} |
||||
|
||||
.in_row { |
||||
float: left; |
||||
display: block; |
||||
list-style: none; |
||||
border-width: 0px; |
||||
} |
||||
|
||||
.group_by_element { |
||||
margin-left: -15px; |
||||
cursor: move; |
||||
} |
||||
|
||||
/* have #content here to overwrite line-heigth of other themes through being more specific*/ |
||||
#content .group_by_label { |
||||
margin: 0px; |
||||
padding: 0px 18px 0 0; |
||||
min-width: 60px; |
||||
text-align: center; |
||||
white-space: nowrap; |
||||
font-weight: bold; |
||||
color: #fff; |
||||
background-color: #9A9A9A; |
||||
height: 22px; |
||||
line-height: 22px; |
||||
cursor: move; |
||||
} |
||||
|
||||
.group_by_label.hover { |
||||
background-color: #009999 !important; |
||||
} |
||||
|
||||
.group_by_remove { |
||||
background-color: #9A9A9A; |
||||
height: 7px; |
||||
width: 8px; |
||||
position: absolute; |
||||
top: 7px; |
||||
right: 21px; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.group_by_remove.hover { |
||||
background-color: #009999 !important; |
||||
} |
||||
|
||||
/* Overwrite normal h3 definition - a h-tag is needed for accessibility purposes */ |
||||
h3.reporting_formatting { |
||||
font-weight:normal; |
||||
font-size: 13px; |
||||
margin:0px; |
||||
padding:0px; |
||||
color:#333; |
||||
border:0px; |
||||
} |
||||
|
||||
/* have #content here to overwrite line-heigth of other themes through being more specific*/ |
||||
#content .group_by_caption { |
||||
color: #FFFFFF; |
||||
background-color: #4B4B4B; |
||||
font-weight: bold; |
||||
padding: 0 7px; |
||||
margin: 0; |
||||
height: 22px; |
||||
line-height: 22px; |
||||
min-width: 55px; |
||||
} |
||||
|
||||
.arrow { |
||||
height: 0; |
||||
width: 0; |
||||
border-style: solid; |
||||
border-width: 11px 11px 11px 7px; |
||||
} |
||||
|
||||
.arrow_left { |
||||
border-color: #9A9A9A #9A9A9A #9A9A9A transparent; |
||||
} |
||||
|
||||
.arrow_left.hover { |
||||
border-color: #009999 #009999 #009999 transparent !important; |
||||
} |
||||
|
||||
.arrow_right { |
||||
border-color: transparent transparent transparent #9A9A9A; |
||||
} |
||||
|
||||
.arrow_right.hover { |
||||
border-color: transparent transparent transparent #009999 !important; |
||||
} |
||||
|
||||
.arrow_group_by_caption { |
||||
border-color: transparent transparent transparent #4B4B4B; |
||||
} |
||||
|
||||
.drag_container { |
||||
padding: 0; |
||||
height: 22px; |
||||
margin-bottom: 1em; |
||||
background-color: #EEE; |
||||
} |
||||
|
||||
.drag_container select { |
||||
margin-right: 3px; |
||||
float: right; |
||||
} |
||||
|
||||
.drag_target { |
||||
min-height: 10px; |
||||
} |
||||
|
||||
.drag_container_accept { |
||||
background-color: #F5F5C5; |
||||
} |
||||
|
||||
/* -- end group-by -- */ |
||||
|
||||
td .drill_down, th .drill_down { |
||||
font-size: 8px; |
||||
display: block; |
||||
float: right; |
||||
font-weight: bold; |
||||
visibility: hidden; |
||||
} |
||||
|
||||
td:hover .drill_down, th:hover .drill_down { |
||||
visibility: visible; |
||||
} |
||||
|
||||
/*Buttons*/ |
||||
|
||||
.buttons .reporting_button { |
||||
background-color: #008BD0; |
||||
border: none; |
||||
cursor: pointer; |
||||
margin: 0px; |
||||
overflow: visible; |
||||
text-align: center; |
||||
white-space: nowrap; |
||||
-moz-border-radius: 3px; |
||||
border-radius: 3px; |
||||
padding: 4px; |
||||
} |
||||
|
||||
.form_controls { |
||||
margin-top: 6px; |
||||
} |
||||
|
||||
.form_controls > * { |
||||
margin: 0 5px 0 5px; |
||||
} |
||||
|
||||
.buttons .reporting_button:hover { |
||||
background-color: #24B3E7; |
||||
} |
||||
|
||||
.buttons .reporting_button * em { |
||||
color: white; |
||||
display: block; |
||||
font-style: normal; |
||||
font-weight: bold; |
||||
line-height: 17px; |
||||
} |
||||
|
||||
.buttons .reporting_button > * { |
||||
display: inline-block; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.saved_queries .reporting_button { |
||||
float: left; |
||||
margin: 2px; |
||||
} |
||||
|
||||
.buttons .button { |
||||
border: none; |
||||
cursor: pointer; |
||||
margin: 0px; |
||||
margin-right: 4px; |
||||
overflow: visible; |
||||
text-align: center; |
||||
white-space: nowrap; |
||||
-moz-border-radius: 3px; |
||||
border-radius: 3px; |
||||
padding: 4px; |
||||
} |
||||
|
||||
.buttons .apply { |
||||
background-color: #008BD0; |
||||
} |
||||
|
||||
.buttons .apply:hover { |
||||
background-color: #24B3E7; |
||||
} |
||||
|
||||
.buttons .secondary { |
||||
background-color: #9a9a9a; |
||||
} |
||||
|
||||
.buttons .secondary:hover { |
||||
background-color: #666666; |
||||
} |
||||
|
||||
.buttons .button span em { |
||||
color: white; |
||||
display: block; |
||||
font-style: normal; |
||||
font-weight: bold; |
||||
line-height: 17px; |
||||
} |
||||
|
||||
.buttons .button span { |
||||
display: inline-block; |
||||
margin: 3px; |
||||
} |
||||
|
||||
|
||||
.buttons .button-icon { |
||||
padding: 0px 5px 0 16px; |
||||
cursor: pointer; |
||||
background: no-repeat left center; |
||||
border-style: none; |
||||
display: inline-block; |
||||
margin-top: 0px; |
||||
margin-bottom: 0px; |
||||
} |
||||
|
||||
.button .icon-save { |
||||
background-image: url(../images/disk.gif); |
||||
} |
||||
|
||||
.button .icon-save-as { |
||||
background-image: url(../images/disks.gif); |
||||
} |
||||
|
||||
.button .icon-delete { |
||||
background-image: url(../images/delete.gif); |
||||
} |
||||
|
||||
.button .icon-clear { |
||||
background-image: url(../images/remove.gif); |
||||
} |
||||
|
||||
div.button_form { |
||||
/* TODO IE Compatibility! */ |
||||
background-color: white; |
||||
border: 1px solid gray; |
||||
-moz-border-radius: 3px; |
||||
border-radius: 3px; |
||||
left: 100px; |
||||
position: absolute; |
||||
padding: 5px; |
||||
} |
||||
|
||||
div.button_form * input#name { |
||||
width: 200px |
||||
} |
||||
|
||||
div.button_form * input[type="button"] { |
||||
float: right; |
||||
} br { clear: right; } |
||||
|
||||
div.button_form p * { |
||||
margin: 2px; |
||||
} |
||||
|
||||
/***** Save and Delete Reports ****/ |
||||
#save_as_form, #delete_form { |
||||
z-index: 999; |
||||
} |
||||
|
||||
#progressbar, .hidden { |
||||
display: none; |
||||
} |
||||
|
||||
#progressbar_container { |
||||
width: 300px; |
||||
height: 16px; |
||||
border: 1px solid #ccc; |
||||
padding: 0; |
||||
margin: 0; |
||||
position: relative; |
||||
background-color: #9A9A9A; |
||||
background-repeat: repeat-x; |
||||
} |
||||
|
||||
#progressbar_container div { |
||||
background-color:#fff; |
||||
} |
||||
|
||||
/***** Ajax indicator ******/ |
||||
#ajax-indicator { |
||||
font-family: Verdana, sans-serif; |
||||
position: absolute; /* fixed not supported by IE */ |
||||
background-color:#eee; |
||||
border: 1px solid #bbb; |
||||
top:35%; |
||||
left:40%; |
||||
width:20%; |
||||
font-weight:bold; |
||||
text-align:center; |
||||
padding:0.6em; |
||||
z-index:100; |
||||
filter:alpha(opacity=50); |
||||
opacity: 0.5; |
||||
line-height: 10px; |
||||
font-size: 18px; |
||||
} |
||||
|
||||
html>body #ajax-indicator { position: fixed; } |
||||
|
||||
#ajax-indicator span { |
||||
background-position: 0% 40%; |
||||
background-repeat: no-repeat; |
||||
background-image: url(../images/loading.gif); |
||||
padding-left: 26px; |
||||
vertical-align: bottom; |
||||
} |
||||
|
||||
/* Calendar Fixes */ |
||||
div.calendar /* let calendar expand properly */ |
||||
{ |
||||
font-size: medium; |
||||
line-height: normal; |
||||
} |
||||
|
||||
div.calendar table div { /* make sure the nested divs are large enough, too */ |
||||
font-size: medium; |
||||
line-height: normal; |
||||
} |
||||
|
||||
.calendar .combo .label, .calendar .combo .label-IEfix { /* set the proper size for the combo boxes with months and years */ |
||||
font-size: 10px; |
||||
line-height: normal; |
||||
} |
||||
|
||||
.calendar tbody .day { /* avoid jitter during quick mouse-overs over days */ |
||||
border: 1px dotted transparent; |
||||
padding: 1px 3px 1px 1px; |
||||
} |
||||
|
||||
/* Accessibility specific styles */ |
||||
fieldset#filters table td > label.hidden-for-sighted, .hidden-for-sighted { |
||||
position:absolute; |
||||
left:-10000px; |
||||
top:auto; |
||||
width:1px; |
||||
height:1px; |
||||
overflow:hidden; |
||||
} |
||||
|
@ -0,0 +1,5 @@ |
||||
module OpenProject |
||||
module ReportingEngine |
||||
require "open_project/reporting_engine/engine" |
||||
end |
||||
end |
@ -0,0 +1,33 @@ |
||||
require 'rails/engine' |
||||
|
||||
module OpenProject::ReportingEngine |
||||
class Engine < ::Rails::Engine |
||||
engine_name :openproject_reportingengine |
||||
|
||||
config.autoload_paths += Dir["#{config.root}/lib/"] |
||||
|
||||
initializer 'reportingengine.precompile_assets' do |
||||
Rails.application.config.assets.precompile += %w(reportingengine.css reportingengine.js) |
||||
end |
||||
|
||||
|
||||
config.to_prepare do |
||||
require_dependency 'open_project/reporting_engine/patches' |
||||
require_dependency 'open_project/reporting_engine/patches/big_decimal_patch' |
||||
require_dependency 'open_project/reporting_engine/patches/to_date_patch' |
||||
end |
||||
|
||||
config.after_initialize do |
||||
Redmine::Plugin.register :openproject_reportingengine do |
||||
name 'OpenProject ReportingEngine' |
||||
author 'Finn GmbH' |
||||
description 'A plugin for reports' |
||||
|
||||
url 'https://github.com/finnlabs/openproject_reportingengine' |
||||
author_url 'http://www.finn.de/' |
||||
|
||||
version OpenProject::ReportingEngine::VERSION |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,2 @@ |
||||
module OpenProject::ReportingEngine::Patches |
||||
end |
@ -1,4 +1,4 @@ |
||||
require 'reporting_engine/engine' |
||||
require_dependency 'open_project/reporting_engine/engine_module' |
||||
|
||||
class Report < ActiveRecord::Base |
||||
extend ProactiveAutoloader |
@ -1,5 +1,5 @@ |
||||
require 'set' |
||||
require 'reporting_engine/report' |
||||
require_dependency 'open_project/reporting_engine/report' |
||||
|
||||
module Report::InheritedAttribute |
||||
include Report::QueryUtils |
@ -0,0 +1,5 @@ |
||||
module OpenProject |
||||
module ReportingEngine |
||||
VERSION = '0.0.1' |
||||
end |
||||
end |
@ -1,5 +1,5 @@ |
||||
require 'reporting_engine/reporting_helper' |
||||
require 'reporting_engine/proactive_autoloader' |
||||
require_dependency 'open_project/reporting_engine/helpers/reporting_helper' |
||||
require_dependency 'open_project/reporting_engine/proactive_autoloader' |
||||
|
||||
|
||||
class Widget < ActionView::Base |