module RedmineExtensions
module ApplicationHelper
include RenderingHelper
# -------= Hack methods =------
def plugin_settings_path(plugin, *attrs)
if plugin.is_a?(Redmine::Plugin) && (plugin.settings[:only_easy] || plugin.settings[:easy_settings])
edit_easy_setting_path(plugin, *attrs)
else
super
end
end
# -------= Rendering and presenting methods =-------
def present(model, options={}, &block)
if model.is_a?(RedmineExtensions::BasePresenter)
presenter = model.update_options(options.merge(view_context: self))
else
presenter = RedmineExtensions::BasePresenter.present(model, self, options)
end
if block_given?
yield presenter
else
presenter
end
end
# --- COMMON RENDERING ----
# hide elements for issues and users
def detect_hide_elements(uniq_id, user = nil, default = true)
return ''.html_safe if uniq_id.blank?
return 'style="display:none"'.html_safe if !toggle_button_expanded?(uniq_id, user, default)
end
def url_to_entity(entity, options={})
m = "url_to_#{entity.class.name.underscore}".to_sym
if respond_to?(m)
send(m, entity, options)
else
nil
end
end
def query_for_entity(entity_class)
entity_class_name = entity_class.name
query_class = "Easy#{entity_class_name}Query".constantize rescue nil
return query_class if query_class && query_class < EasyQuery
query_class ||= "#{entity_class_name}Query".constantize rescue nil
end
def render_entity_assignments(entity, target_entity, options = {}, &block)
options ||= {}
collection_name = options.delete(:collection_name) || target_entity.name.pluralize.underscore
query_class = query_for_entity(target_entity)
return '' if !query_class || !entity.respond_to?(collection_name)
project = options.delete(:project)
query = query_class.new(:name => 'c_query')
query.project = project
query.set_entity_scope(entity, collection_name)
query.column_names = options[:query_column_names] unless options[:query_column_names].blank?
entities = query.entities
entities_count = entities.size
options[:entities_count] = entities_count
options[:module_name] ||= "entity_#{entity.class.name.underscore}_#{entity.id}_#{collection_name}"
options[:heading] ||= l("label_#{query.entity.name.underscore}_plural", :default => 'Heading')
if options[:context_menus_path].nil?
options[:context_menus_path] = [
"context_menu_#{collection_name}_path".to_sym,
"context_menus_#{collection_name}_path".to_sym,
"#{collection_name}_context_menu_path".to_sym
].detect do |m|
m if respond_to?(m)
end
end
query.output = options[:display_style] || (entities_count > 3 ? 'list' : 'tiles')
render(:partial => 'easy_entity_assignments/assignments_container', :locals => {
:entity => entity,
:query => query, :project => project,
:entities => entities, :entities_count => entities_count, :options => options})
end
def entity_css_icon(entity_or_entity_class)
return '' if entity_or_entity_class.nil?
if entity_or_entity_class.is_a?(Class) && entity_or_entity_class.respond_to?(:css_icon)
entity_or_entity_class.css_icon
elsif entity_or_entity_class.is_a?(ActiveRecord::Base)
if entity_or_entity_class.respond_to?(:css_icon)
entity_or_entity_class.css_icon
elsif entity_or_entity_class.class.respond_to?(:css_icon)
entity_or_entity_class.class.css_icon
else
"icon icon-#{entity_or_entity_class.class.name.dasherize}"
end
else
"icon icon-#{entity_or_entity_class.class.name.dasherize}"
end
end
# ==== Options
# * class: Hash or String - This option can be used to add custom CSS classes. It can be *String* or *Hash*.
# class: {heading: 'heading-additional-css', container: 'container-additional-css'}
# * heading_tag: name of HTML element of module heading - By default its its *h3*
# ** Aliases for this options are: wrapping_heading_element, header_tag
# * toggle: false - This disable toggle function (collapsible and remember)
# ** Aliases for this options are: collapsible, no_expander
# * remember: false - This disable remember function of toggle container
# ** Aliases for this options are: ajax_call
#
def render_module_easy_box(id, heading, options = {}, &block) # with fallback to old
options[:toggle] = true unless options.key?(:toggle)
options[:remember] = options.delete(:ajax_call) if options.key?(:ajax_call)
options[:collapsible] = !options.delete(:no_expander) if options.key?(:no_expander)
renderer = EasyBoxRenderer.new(self, id, heading, options)
renderer.content = capture {yield renderer}
renderer.render
end
EasyBoxRenderer = Struct.new(:view, :id, :heading, :options) do
attr_writer :container_class, :heading_class, :content_class
attr_writer :heading_links, :footer, :icon
attr_accessor :content
def container_class
s = (@container_class.presence || css_classes[:container]).to_s
s << ' collapsible' if collapsible?
s << ' collapsed' if collapsed?
s
end
def saving_state_enabled?
collapsible? && (options[:remember].nil? || !!options[:remember])
end
def heading_tag
(options[:wrapping_heading_element] || (options[:header_tag] || options[:heading_tag])).presence || 'h3'
end
def heading_class
(@heading_class || css_classes[:heading]).to_s
end
def icon
@icon ||= options[:icon] && " icon #{options[:icon]}"
end
def heading_links
if block_given?
@heading_links = view.capture { yield }
else
@heading_links.to_s.html_safe
end
end
def collapsible?
return @collapsible unless @collapsible.nil?
@collapsible ||= !!options[:toggle] && (options[:collapsible].nil? || !!options[:collapsible])
end
def collapsed?
!!options[:default] || !!options[:collapsed] || !!options[:default_button_state]
end
def footer
if block_given?
@footer = view.capture { yield }
else
@footer.to_s.html_safe
end
end
def render
view.render({partial: 'common/collapsible_module_layout', locals: {renderer: self, content: content}} )
end
private
def css_classes
return @css_classes if @css_classes
if (css_class = options.delete(:class)).is_a?(Hash)
@css_classes = css_class
else
@css_classes = {
container: css_class,
heading: css_class,
content: css_class
}
end
end
end
# Returns a multiselect autocomplete input tag tailored for selecting 1..N values from defined source by +jsonpath_or_array+.
# Preselected will be values in +selected_values+ parameter, or if those are empty, and +select_first_value+ option is set,
# it will select first value from source. See warning from this parameter!
# Additional options on the input tag can be passed as a hash with +options+.
# These options will be passed to the handling javascript.
# Available format for +selected_values+:
# * Array of values. It will search for the values in available_values for values names assigned.
# * Array of objects in format: {id: , value: