Merge branch 'feature/streaming_widgets' into feature/progress_report

pull/6827/head
Tim Felgentreff 14 years ago
commit 11d5334aa7
  1. 50
      lib/widget.rb
  2. 58
      lib/widget/base.rb
  3. 3
      lib/widget/controls/apply.rb
  4. 4
      lib/widget/controls/clear.rb
  5. 2
      lib/widget/controls/delete.rb
  6. 2
      lib/widget/controls/query_name.rb
  7. 2
      lib/widget/controls/save.rb
  8. 2
      lib/widget/controls/save_as.rb
  9. 4
      lib/widget/filters.rb
  10. 4
      lib/widget/filters/date.rb
  11. 5
      lib/widget/filters/label.rb
  12. 11
      lib/widget/filters/multi_choice.rb
  13. 5
      lib/widget/filters/multi_values.rb
  14. 5
      lib/widget/filters/operators.rb
  15. 4
      lib/widget/filters/remove_button.rb
  16. 4
      lib/widget/filters/text_box.rb
  17. 4
      lib/widget/group_bys.rb
  18. 4
      lib/widget/settings.rb
  19. 6
      lib/widget/settings/fieldset.rb
  20. 119
      lib/widget/table.rb
  21. 148
      lib/widget/table/report_table.rb

@ -1,32 +1,20 @@
class ActionView::Base
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 = controller
i.render_with_options(options, &block).html_safe
end
end
if Rails.version.start_with? "2"
class ::String; def html_safe; self; end; end
end
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
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
@ -38,4 +26,26 @@ class Widget < ActionView::Base
def protect_against_forgery?
false
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,20 +1,72 @@
class Widget::Base < Widget
attr_reader :engine
attr_reader :engine, :output
def self.dont_cache!
@dont_cache = true
end
def self.dont_cache?
@dont_cache
end
def initialize(query)
@query = query
@engine = query.class
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)
@output ||= "".html_safe
@output.write str.html_safe
@cache_output.write(str.html_safe) if @cache_output
end
##
# Render this widget. Abstract method. Needs to call #write at least once
def render
raise NotImplementedError, "#render is missing in my subclass"
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)
if canvas = options[:to]
canvas << "\n" << render(&block)
set_canvas(options[:to]) if options.has_key? :to
render_with_cache(options, &block)
@output
end
def cache_key
"#{self.class.name}/#{subject.hash}"
end
private
def cache?
!self.class.dont_cache?
end
##
# Render this widget or serve it from cache
def render_with_cache(options = {}, &block)
if Rails.cache.exist? cache_key and cache?
write Rails.cache.fetch(cache_key)
else
render(&block)
Rails.cache.write(cache_key, @cache_output || @output)
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)
unless canvas.respond_to? :to_str
@cache_output = @output || "".html_safe
end
@output = canvas
end
end

@ -1,6 +1,7 @@
class Widget::Controls::Apply < Widget::Base
def render
link_to content_tag(:span, content_tag(:em, l(:button_apply))), {},
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')

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

@ -19,6 +19,6 @@ class Widget::Controls::Delete < Widget::Base
end
question + options
end
button + popup
write(button + popup)
end
end

@ -14,7 +14,7 @@ class Widget::Controls::QueryName < Widget::Base
options["data-update-url"] = url_for(:action => "rename", :id => @query.id)
options["data-is_new"] = @query.new_record?
end
content_tag(:span, name, options) + icon
write(content_tag(:span, name, options) + icon)
end
def translations

@ -1,7 +1,7 @@
class Widget::Controls::Save < Widget::Base
def render
return "" if @query.new_record?
link_to content_tag(:span, content_tag(:em, l(:button_save)), :class => "button-icon icon-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),

@ -10,7 +10,7 @@ class Widget::Controls::SaveAs < Widget::Base
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
button + render_popup
write(button + render_popup)
end
def render_popup_form

@ -17,7 +17,7 @@ class Widget::Filters < Widget::Base
:class => "select-small",
:name => nil
end
content_tag(:div, table + select)
write content_tag(:div, table + select)
end
def selectables
@ -72,4 +72,4 @@ class Widget::Filters < Widget::Base
end
render_widget Filters::RemoveButton, f, :to => html
end
end
end

@ -23,7 +23,7 @@ class Widget::Filters::Date < Widget::Filters::Base
name = "values[#{filter_class.underscore_name}][]"
id_prefix = "#{filter_class.underscore_name}_"
content_tag :td do
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")
@ -35,6 +35,6 @@ class Widget::Filters::Date < Widget::Filters::Base
text2 + cal2
end
arg1 + arg2
end
end)
end
end

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

@ -1,8 +1,9 @@
class Widget::Filters::MultiChoice < Widget::Filters::Base
def render
filterName = filter_class.underscore_name
content_tag :td do
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 = {
@ -21,11 +22,11 @@ class Widget::Filters::MultiChoice < Widget::Filters::Base
content_tag :div, choices.join.html_safe,
:id => "#{filter_class.underscore_name}_arg_1_val"
end
end
end)
end
private
def translate(label)
if label.is_a?(Symbol)
::I18n.t(label)
@ -33,5 +34,5 @@ class Widget::Filters::MultiChoice < Widget::Filters::Base
label
end
end
end

@ -1,7 +1,8 @@
class Widget::Filters::MultiValues < Widget::Filters::Base
def render
content_tag :td do
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}][]",
@ -52,6 +53,6 @@ class Widget::Filters::MultiValues < Widget::Filters::Base
:"data-filter-name" => filter_class.underscore_name
box + plus
end
end
end)
end
end

@ -1,6 +1,7 @@
class Widget::Filters::Operators < Widget::Filters::Base
def render
content_tag :td, :width => 100 do
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
@ -19,6 +20,6 @@ class Widget::Filters::Operators < Widget::Filters::Base
l(filter_class.available_operators.first.label)
end
hide_select_box ? select_box + label : select_box
end
end)
end
end

@ -1,11 +1,11 @@
class Widget::Filters::RemoveButton < Widget::Filters::Base
def render
content_tag :td, :width => "25px" do
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
end

@ -1,6 +1,6 @@
class Widget::Filters::TextBox < Widget::Filters::Base
def render
content_tag :td do
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",
@ -8,6 +8,6 @@ class Widget::Filters::TextBox < Widget::Filters::Base
:id => "#{filter_class.underscore_name}_arg_1_val",
:'data-filter-name' => filter_class.underscore_name)
end
end
end)
end
end

@ -51,10 +51,10 @@ class Widget::GroupBys < Widget::Base
end
def render
content_tag :div, :id => 'group_by_area' do
write(content_tag :div, :id => 'group_by_area' do
out = render_group 'columns', @query.group_bys(:column)
out += render_group 'rows', @query.group_bys(:row)
out.html_safe
end
end)
end
end

@ -1,6 +1,6 @@
class Widget::Settings < Widget::Base
def render
form_tag("#", {:id => 'query_form', :method => :post}) do
write(form_tag("#", {:id => 'query_form', :method => :post}) do
content_tag :div, :id => "query_form_content" do
fieldsets = render_widget Widget::Settings::Fieldset, @query, { :type => "filter" } do
@ -21,6 +21,6 @@ class Widget::Settings < Widget::Base
fieldsets + controls
end
end
end)
end
end

@ -1,4 +1,6 @@
class Widget::Settings::Fieldset < Widget::Base
dont_cache!
def render_with_options(options, &block)
@type = options.delete(:type) || "filter"
@id = "#{@type}-settings"
@ -7,9 +9,9 @@ class Widget::Settings::Fieldset < Widget::Base
end
def render
content_tag :fieldset, :id => @id, :class => "collapsible collapsed" do
write(content_tag :fieldset, :id => @id, :class => "collapsible collapsed" do
html = content_tag :legend, l(@label), :onclick => "toggleFieldset(this);" #FIXME: onclick
html + yield
end
end)
end
end

@ -1,5 +1,6 @@
class Widget::Table < Widget::Base
extend Report::InheritedAttribute
include ReportingHelper
attr_accessor :debug
attr_accessor :fields
@ -10,65 +11,65 @@ class Widget::Table < Widget::Base
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] || @query.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 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] || @query.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 @query.depth_of(:row) == 0

@ -7,129 +7,113 @@ class Widget::Table::ReportTable < Widget::Table
@walker = query.walker
end
def render
configure_walker
content = content_tag :table, :class => 'list report' do
header + footer + body
def configure_query
if @query.depth_of(:row) == 0
@query.row(:singleton_value)
elsif @query.depth_of(:column) == 0
@query.column(:singleton_value)
end
content += (debug_content if debug?)
end
def configure_walker
walker.for_final_row do |row, cells|
final_row_html = content_tag :th, :class => 'normal inner left' do
"#{show_row(row)}#{debug_fields(row)}"
end
final_row_html += cells.join.html_safe
final_row_html += content_tag :th, :class => 'normal inner right' do
"#{show_result(row)}#{debug_fields(row)}"
end
final_row_html
@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|
@walker.for_row do |row, subrows|
subrows.flatten!
unless row.fields.empty?
subrows[0] = ''
subrows[0] += content_tag(:th, :class => 'top left', :rowspan => subrows.size) do
"#{show_row(row)}#{debug_fields(row)}"
end
subrows[0].gsub("class='normal'", "class='top'")
subrows[0] += content_tag(:th, :class => 'top right', :rowspan => subrows.size) do
"#{show_result(row)}#{debug_fields(row)}"
end
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 do
content_tag(:td, :class =>'normal empty') do
" "
end
end
@walker.for_empty_cell { "<td class='normal empty'>&nbsp;</td>".html_safe }
walker.for_cell do |result|
content_tag :td, :class => 'normal right' do
"#{show_result(result)}#{debug_fields(result)}"
end
@walker.for_cell do |result|
"<td class='normal right'>#{show_result result}#{debug_fields(result)}</td>".html_safe
end
end
def header
header_content = ""
def render
configure_query
configure_walker
write "<table class='list report'>"
render_thead
render_tfoot
render_tbody
write "</table>"
end
def render_thead
write "<thead>"
walker.headers do |list, first, first_in_col, last_in_col|
header_content += '<tr>' if first_in_col
if first
header_content += content_tag :th, :rowspan => @query.depth_of(:column), :colspan => @query.depth_of(:row) do
""
end
end
write '<tr>' if first_in_col
write "<th rowspan='#{@query.depth_of(:column)}' colspan='#{@query.depth_of(:row)}'></th>" if first
list.each do |column|
opts = { :colspan => column.final_number(:column) }
opts.merge!(:class => "inner") if column.final?(:column)
header_content += content_tag :th, opts do
show_row column
end
end
if first
header_content += content_tag :th, :rowspan => @query.depth_of(:column), :colspan => @query.depth_of(:row) do
""
end
end
header_content += '</tr>' if last_in_col
write "<th colspan=#{column.final_number(:column)}"
write ' class="inner"' if column.final?(:column)
write ">"
write show_row(column)
write "</th>"
end
write "<th rowspan='#{@query.depth_of(:column)}' colspan='#{@query.depth_of(:row)}'></th>" if first
write '</tr>' if last_in_col
end
content_tag :thead, header_content.html_safe
write "</thead>"
end
def footer
reverse_headers = ""
def render_tfoot
write "<tfoot>"
walker.reverse_headers do |list, first, first_in_col, last_in_col|
if first_in_col
reverse_headers += '<tr>'
if first
reverse_headers += content_tag :th, :rowspan => @query.depth_of(:column), :colspan => @query.depth_of(:row), :class => 'top' do
" "
end
end
write "<tr>" if first_in_col
if first
write "<th rowspan='#{@query.depth_of(:column)}'
colspan='#{@query.depth_of(:row)}' class='top'>&nbsp;</th>"
end
list.each do |column|
opts = { :colspan => column.final_number(:column) }
opts.merge!(:class => "inner") if first
reverse_headers += content_tag :th, opts do
"#{show_result(column)}#{debug_fields(column)}"
end
write "<th colspan='#{column.final_number(:column)}'"
write ' class="inner"' if first
write '>'
write show_result(column)
# FIXME: write debug_fields(column)
write "</th>"
end
if last_in_col
if first
reverse_headers += content_tag :th,
:rowspan => @query.depth_of(:column),
:colspan => @query.depth_of(:row),
:class => 'top result' do
show_result @query
end
write "<th rowspan='#{@query.depth_of(:column)}'
colspan='#{@query.depth_of(:row)}' class='top result'>"
write show_result(@query)
write "</th>"
end
reverse_headers += '</tr>'
write "</tr>"
end
end
content_tag :tfoot, reverse_headers.html_safe
write "</tfoot>"
end
def body
def render_tbody
write "<tbody>"
first = true
walker_body = ""
odd = true
walker.body do |line|
if first
line.gsub!("class='normal", "class='top")
first = false
end
walker_body += content_tag :tr, :class => cycle("odd", "even") do
line.html_safe
end
write "<tr class='#{odd ? "odd" : "even"}'>#{line}</tr>"
odd = !odd
end
content_tag :tbody, walker_body.html_safe
write "</tbody>"
end
def debug_content

Loading…
Cancel
Save