app/helpers/application_helper/toolbar_builder.rb
class ApplicationHelper::ToolbarBuilder
include MiqAeClassHelper
include RestfulControllerMixin
include ApplicationHelper::Toolbar::Mixins::CustomButtonToolbarMixin
# Loads the toolbar sent in parameter `toolbar_name`, and builds the buttons
# in the toolbar, unless the group of buttons is meant to be skipped.
#
# Returns built toolbar loaded in instance variable `@toolbar`, or `nil`, if
# no buttons should be in the toolbar.
def build_toolbar(toolbar_name)
return nil if toolbar_name.nil?
build_toolbar_setup
toolbar_class = toolbar_class(toolbar_name)
build_toolbar_from_class(toolbar_class, @record)
end
private
delegate :request, :current_user, :to => :@view_context
delegate :role_allows?, :model_for_vm, :rbac_common_feature_for_buttons, :to => :@view_context
delegate :x_tree_history, :x_node, :x_active_tree, :to => :@view_context
delegate :settings, :is_browser?, :is_browser_os?, :to => :@view_context
def initialize(view_context, view_binding, instance_data)
@view_context = view_context
@view_binding = view_binding
@instance_data = instance_data
instance_data.each do |name, value|
instance_variable_set(:"@#{name}", value)
end
end
# Parses the generic toolbars name and returns his class
def predefined_toolbar_class(tb_name)
class_name = 'ApplicationHelper::Toolbar::' + ActiveSupport::Inflector.camelize(tb_name.sub(/_tb$/, ''))
class_name.constantize
end
def controller
@view_context.respond_to?(:controller) ? @view_context.controller : @view_context
end
def model_for_custom_toolbar
controller.instance_eval { @tree_selected_model } || controller.class.model
end
def evaluate_url_parms(url_parms)
url_parms.kind_of?(Proc) ? @view_context.instance_eval(&url_parms) : url_parms
end
# According to toolbar name in parameter `toolbar_name` either returns class
# for generic toolbar, or starts building custom toolbar
def toolbar_class(toolbar_name)
if Mixins::CustomButtons::Result === toolbar_name
custom_toolbar_class(toolbar_name)
else
predefined_toolbar_class(toolbar_name)
end
end
# Creates a button and sets it's properties
def toolbar_button(inputs, props)
button_class = inputs[:klass] || ApplicationHelper::Button::Basic
props[:options] = inputs[:options] if inputs[:options]
button = button_class.new(@view_context, @view_binding, @instance_data, props)
button.skipped? ? nil : apply_common_props(button, inputs)
end
# Build select button and its child buttons
def build_select_button(bgi, index)
bs_children = false
props = toolbar_button(bgi, :id => bgi[:id], :type => :buttonSelect)
return nil if props.nil?
current_item = props
current_item[:items] ||= []
any_visible = false
bgi[:items].each_with_index do |bsi, bsi_idx|
if bsi.key?(:separator)
props = ApplicationHelper::Button::Separator.new(:id => "sep_#{index}_#{bsi_idx}", :hidden => !any_visible)
else
bs_children = true
props = toolbar_button(bsi, :child_id => bsi[:id], :id => bgi[:id] + "__" + bsi[:id], :type => :button)
next if props.nil?
end
update_common_props(bsi, props) unless bsi.key?(:separator)
current_item[:items] << props
any_visible ||= !props[:hidden] && props[:type] != :separator
end
current_item[:items].reverse_each do |item|
break if !item[:hidden] && item[:type] != :separator
item[:hidden] = true if item[:type] == :separator
end
current_item[:hidden] = !any_visible
if bs_children
@sep_added = true # Separator has officially been added
@sep_needed = true # Need a separator from now on
end
current_item
end
# Set properties for button
def apply_common_props(button, input)
button.update(
:color => input[:color],
:data => button.data(input[:data]),
:hidden => button[:hidden] || !!input[:hidden],
:icon => input[:icon],
:name => button[:id],
:onwhen => input[:onwhen],
:send_checked => input[:send_checked],
)
button[:enabled] = !!input[:enabled] if input.key?(:enabled)
%i[title text confirm].each do |key|
if input[key].present?
button[key] = button.localized(key, input[key])
end
end
button[:url_parms] = update_url_parms(evaluate_url_parms(input[:url_parms])) if input[:url_parms].present?
button[:keepSpinner] = input[:keepSpinner] if input.key?(:keepSpinner)
if input[:popup] # special behavior: button opens window_url in a new window
button[:popup] = true
button[:window_url] = "/#{request.parameters["controller"]}#{input[:url]}"
end
if input[:association_id] # special behavior to pass in id of association
button[:url_parms] = "?show=#{request.parameters[:show]}"
end
button.calculate_properties
button
end
# Build single button
def build_normal_button(bgi, index)
@sep_needed = true
props = toolbar_button(bgi, :id => bgi[:id], :type => :button)
return nil if props.nil?
props[:hidden] = false
_add_separator(index)
props
end
def _add_separator(index)
# Add a separator, if needed, before this button
if !@sep_added && @sep_needed
if @groups_added.include?(index) && @groups_added.length > 1
@toolbar << ApplicationHelper::Button::Separator.new(:id => "sep_#{index}")
@sep_added = true
end
end
@sep_needed = true # Button was added, need separators from now on
end
# Build button with more states
def build_twostate_button(bgi, index)
props = toolbar_button(bgi, :id => bgi[:id], :type => :buttonTwoState)
return nil if props.nil?
props[:selected] = twostate_button_selected(bgi[:id])
_add_separator(index)
props
end
# According to button type in toolbar definition calls appropriate method
def build_button(bgi, index)
props = case bgi[:type]
when :buttonSelect then build_select_button(bgi, index)
when :button then build_normal_button(bgi, index)
when :buttonTwoState then build_twostate_button(bgi, index)
end
unless props.nil?
@toolbar << update_common_props(bgi, props)
end
end
def cb_send_checked_list
@display.present? && custom_button_appliable_class?(@display.camelize.singularize) && @view_context.params[:id] == @record.id.to_s
end
def cb_enabled_value_for_nested
@display == 'generic_objects' && @view_context.params[:id] == @record.id.to_s
end
def create_custom_button(input, model, record)
button_id = input[:id]
button_name = input[:name].to_s
record_id = if cb_send_checked_list
'LIST'
else
record.present? ? record.id : 'LIST'
end
enabled = cb_send_checked_list ? cb_enabled_value_for_nested : input[:enabled]
button = {
:id => "custom__custom_#{button_id}",
:type => :button,
:icon => "#{input[:image]} fa-lg",
:color => input[:color],
:title => !input[:enabled] && input[:disabled_text] ? input[:disabled_text] : input[:description].to_s,
:enabled => enabled,
:klass => ApplicationHelper::Button::ButtonWithoutRbacCheck,
:url => "button",
:url_parms => "?id=#{record_id}&button_id=#{button_id}&cls=#{model}&pressed=custom_button&desc=#{button_name}",
}
button[:text] = button_name if input[:text_display]
button[:onwhen] = '1+' if cb_send_checked_list
button[:send_checked] = true if record_id == 'LIST'
button
end
def create_raw_custom_button_hash(cb, record)
record_id = record.present? ? record.id : 'LIST'
{
:id => cb.id,
:class => cb.applies_to_class,
:description => cb.description,
:name => cb.name,
:image => cb.options[:button_icon],
:color => cb.options[:button_color],
:text_display => cb.options.key?(:display) ? cb.options[:display] : true,
:enabled => cb.evaluate_enablement_expression_for(record),
:disabled_text => cb.disabled_text,
:target_object => record_id
}
end
def custom_button_selects(model, record, toolbar_result)
get_custom_buttons(model, record, toolbar_result).collect do |group|
buttons = group[:buttons].collect { |b| create_custom_button(b, model, record) }
enabled = if cb_send_checked_list
cb_enabled_value_for_nested
else
record ? true : buttons.all? { |button| button[:enabled] }
end
props = {
:id => "custom_#{group[:id]}",
:type => :buttonSelect,
:icon => "#{group[:image]} fa-lg",
:color => group[:color],
:title => group[:description],
:enabled => enabled,
:items => buttons
}
props[:text] = group[:text] if group[:text_display]
props[:onwhen] = '1+' if cb_send_checked_list
{:name => "custom_buttons_#{group[:text]}", :items => [props]}
end
end
def custom_toolbar_class(toolbar_result)
record = @record
if @display == 'generic_objects'
model = GenericObjectDefinition
record = GenericObject.find_by(:id => @sb[:rec_id])
elsif relationship_table_screen?
model = custom_button_class_model(@display.camelize.singularize)
else
model = @record ? @record.class : model_for_custom_toolbar
end
build_custom_toolbar_class(model, record, toolbar_result)
end
def build_custom_toolbar_class(model, record, toolbar_result)
# each custom toolbar is an anonymous subclass of this class
toolbar = Class.new(ApplicationHelper::Toolbar::Basic)
# This creates several drop-down (select) with custom buttons.
# Each select is placed into a separate group.
custom_button_selects(model, record, toolbar_result).each do |button_group|
toolbar.button_group(button_group[:name], button_group[:items])
end
custom_button_add_related_buttons(model, record, toolbar) if record.present?
toolbar
end
def custom_button_add_related_buttons(model, record, toolbar)
# For Service, we include buttons for ServiceTemplate.
# These buttons are added as a single group with multiple buttons
if record.kind_of?(Service)
service_buttons = record_to_service_buttons(record)
unless service_buttons.empty?
buttons = service_buttons.collect { |b| create_custom_button(b, model, record) }
toolbar.button_group("custom_buttons_", buttons)
end
end
if record.kind_of?(GenericObject)
generic_object_buttons = record_to_generic_object_buttons(record)
unless generic_object_buttons.empty?
buttons = generic_object_buttons.collect { |b| create_custom_button(b, model, record) }
toolbar.button_group("custom_buttons_", buttons)
end
end
end
def button_class_name(model)
# Service Buttons are defined in the ServiceTemplate class
model >= Service ? "ServiceTemplate" : model.base_model.name
end
def service_template_id(record)
case record
when Service then record.service_template_id
when ServiceTemplate, GenericObjectDefinition then record.id
when GenericObject then record.generic_object_definition.id
end
end
def create_related_custom_buttons(record, item)
item.direct_custom_buttons.collect { |cb| create_raw_custom_button_hash(cb, record) }
end
def record_to_service_buttons(record)
return [] unless record.kind_of?(Service)
return [] if record.service_template.nil?
create_related_custom_buttons(record, record.service_template)
end
def record_to_generic_object_buttons(record)
return [] unless record.kind_of?(GenericObject)
create_related_custom_buttons(record, record.generic_object_definition)
end
def get_custom_buttons(model, record, toolbar_result)
cbses = CustomButtonSet.find_all_by_class_name(button_class_name(model), service_template_id(record))
cbses = CustomButtonSet.filter_with_visibility_expression(cbses, record)
cbses.sort_by { |cbs| cbs.set_data[:group_index] }.collect do |cbs|
group = {
:id => cbs.id,
:text => cbs.name.split("|").first,
:description => cbs.description,
:image => cbs.set_data[:button_icon],
:color => cbs.set_data[:button_color],
:text_display => cbs.set_data.key?(:display) ? cbs.set_data[:display] : true
}
available = cbs.custom_buttons.select(&:visible_for_current_user?)
available = available.select do |b|
cbs.members.include?(b) && toolbar_result.plural_form_matches(b)
end
group[:buttons] = available.collect { |cb| create_raw_custom_button_hash(cb, record) }.uniq
if cbs.set_data[:button_order] # Show custom buttons in the order they were saved
ordered_buttons = []
cbs.set_data[:button_order].each do |bidx|
group[:buttons].each do |b|
if bidx == b[:id] && !ordered_buttons.include?(b)
ordered_buttons.push(b)
break
end
end
end
group[:buttons] = ordered_buttons
end
group
end
end
# Determine if a button should be selected for buttonTwoState
def twostate_button_selected(id)
return true if id.starts_with?("view_") && id.ends_with?("textual") # Summary view buttons
return true if @ght_type && id.starts_with?("view_") && id.ends_with?(@ght_type) # GHT view buttons on report show
return true if id.starts_with?("compare_") && id.ends_with?(settings(:views, :compare).to_s)
return true if id.starts_with?("drift_") && id.ends_with?(settings(:views, :drift).to_s)
return true if id == "compare_all"
return true if id == "drift_all"
return true if id.starts_with?("comparemode_") && id.ends_with?(settings(:views, :compare_mode).to_s)
return true if id.starts_with?("driftmode_") && id.ends_with?(settings(:views, :drift_mode).to_s)
return true if id == "view_dashboard" && @showtype == "dashboard"
return true if id == "view_summary" && @showtype == "main"
false
end
def url_for_button(name, url_tpl, controller_restful)
url = evaluate_url_parms(url_tpl)
if %w[view_grid view_tile view_list].include?(name) && controller_restful && url =~ %r{^\/(\d+|\d+r\d+)\?$}
# handle restful routes - we want just / if the url is just an id
url = '/'
end
url
end
def update_common_props(item, props)
props[:url] = url_for_button(props[:id], item[:url], controller_restful?) if item[:url]
props[:explorer] = true if @explorer && !item[:url] # Add explorer = true if ajax button
props
end
def update_url_parms(url_parm)
return url_parm unless url_parm.include?("=")
keep_parms = %w[bc escape menu_click sb_controller]
query_string = Rack::Utils.parse_query(URI("?#{request.query_string}").query)
query_string.delete_if { |k, _v| !keep_parms.include?(k) }
url_parm_hash = preprocess_url_param(url_parm)
query_string.merge!(url_parm_hash)
URI::DEFAULT_PARSER.unescape("?#{query_string.to_query}")
end
def preprocess_url_param(url_parm)
parse_questionmark = /^\?/.match(url_parm)
parse_ampersand = /^&/.match(url_parm)
url_parm = parse_questionmark.post_match if parse_questionmark.present?
url_parm = parse_ampersand.post_match if parse_ampersand.present?
encoded_url = URI::DEFAULT_PARSER.escape(url_parm)
Rack::Utils.parse_query(URI("?#{encoded_url}").query)
end
def build_toolbar_setup
@toolbar = []
@groups_added = []
@sep_needed = false
@sep_added = false
end
def build_toolbar_from_class(toolbar_class, record)
toolbar_class.definition(record).each_with_index do |(_name, group), group_index|
@sep_added = false
@groups_added.push(group_index)
case group
when ApplicationHelper::Toolbar::Group
group.buttons.each do |bgi|
build_button(bgi, group_index)
end
when ApplicationHelper::Toolbar::Custom
@toolbar << { :custom => true, :name => group.name, :props => group.evaluate(@view_context) }
end
end
@toolbar = nil if @toolbar.empty?
@toolbar
end
end